@pikacss/core 0.0.61 → 0.0.63
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/dist/index.d.mts +881 -1023
- package/dist/index.mjs +1345 -591
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1362,25 +1362,6 @@ function isNotNullish(value) {
|
|
|
1362
1362
|
return value != null;
|
|
1363
1363
|
}
|
|
1364
1364
|
/**
|
|
1365
|
-
* Type-narrowing guard that returns `true` when the value is not a string, narrowing the type to `Exclude<V, string>`.
|
|
1366
|
-
* @internal
|
|
1367
|
-
*
|
|
1368
|
-
* @typeParam V - The union type of the input value.
|
|
1369
|
-
* @param value - The value to test.
|
|
1370
|
-
* @returns `true` if the value is not a `string`.
|
|
1371
|
-
*
|
|
1372
|
-
* @remarks Useful for filtering processed style items to separate resolved definition objects from unresolved string references.
|
|
1373
|
-
*
|
|
1374
|
-
* @example
|
|
1375
|
-
* ```ts
|
|
1376
|
-
* const items: (string | object)[] = ['btn', { color: 'red' }]
|
|
1377
|
-
* const objects = items.filter(isNotString) // [{ color: 'red' }]
|
|
1378
|
-
* ```
|
|
1379
|
-
*/
|
|
1380
|
-
function isNotString(value) {
|
|
1381
|
-
return typeof value !== "string";
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
1365
|
* Type-narrowing guard that returns `true` when the value is a plain object record (non-null, non-array object).
|
|
1385
1366
|
* @internal
|
|
1386
1367
|
*
|
|
@@ -1513,7 +1494,7 @@ function serialize(value) {
|
|
|
1513
1494
|
* @param values - Values to add.
|
|
1514
1495
|
* @returns `true` if at least one new element was added (the set grew).
|
|
1515
1496
|
*
|
|
1516
|
-
* @remarks The boolean return
|
|
1497
|
+
* @remarks The boolean return lets callers detect whether adding values changed the set.
|
|
1517
1498
|
*
|
|
1518
1499
|
* @example
|
|
1519
1500
|
* ```ts
|
|
@@ -1528,95 +1509,6 @@ function addToSet(set, ...values) {
|
|
|
1528
1509
|
return set.size !== before;
|
|
1529
1510
|
}
|
|
1530
1511
|
/**
|
|
1531
|
-
* Flattens an `Arrayable<string>` value and adds all entries to a `Set`, returning whether the set grew.
|
|
1532
|
-
* @internal
|
|
1533
|
-
*
|
|
1534
|
-
* @param set - The target set to append to.
|
|
1535
|
-
* @param values - A single string or array of strings to add, or `undefined`/`null` to skip.
|
|
1536
|
-
* @returns `true` if at least one new entry was added; `false` if the input was nullish or all entries already existed.
|
|
1537
|
-
*
|
|
1538
|
-
* @remarks Short-circuits on nullish input for convenience, since many autocomplete contribution fields are optional.
|
|
1539
|
-
*
|
|
1540
|
-
* @example
|
|
1541
|
-
* ```ts
|
|
1542
|
-
* const s = new Set<string>()
|
|
1543
|
-
* appendAutocompleteEntries(s, 'hover') // true
|
|
1544
|
-
* appendAutocompleteEntries(s, ['hover']) // false (already present)
|
|
1545
|
-
* appendAutocompleteEntries(s, undefined) // false
|
|
1546
|
-
* ```
|
|
1547
|
-
*/
|
|
1548
|
-
function appendAutocompleteEntries(set, values) {
|
|
1549
|
-
if (values == null) return false;
|
|
1550
|
-
return addToSet(set, ...[values].flat());
|
|
1551
|
-
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Merges a record of `Arrayable<string>` values into a `Map<string, string[]>`, returning whether any entry was added.
|
|
1554
|
-
* @internal
|
|
1555
|
-
*
|
|
1556
|
-
* @param map - The target map to append entries to.
|
|
1557
|
-
* @param entries - A record mapping keys to single or arrayed string values, or `undefined` to skip.
|
|
1558
|
-
* @returns `true` if at least one entry was added or extended; `false` if the input was nullish or empty.
|
|
1559
|
-
*
|
|
1560
|
-
* @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.
|
|
1561
|
-
*
|
|
1562
|
-
* @example
|
|
1563
|
-
* ```ts
|
|
1564
|
-
* const map = new Map<string, string[]>()
|
|
1565
|
-
* appendAutocompleteRecordEntries(map, { color: ['red', 'blue'] }) // true
|
|
1566
|
-
* appendAutocompleteRecordEntries(map, { color: 'green' }) // true (now ['red','blue','green'])
|
|
1567
|
-
* appendAutocompleteRecordEntries(map, { color: 'green' }) // false (no change)
|
|
1568
|
-
* ```
|
|
1569
|
-
*/
|
|
1570
|
-
function appendAutocompleteRecordEntries(map, entries) {
|
|
1571
|
-
if (entries == null) return false;
|
|
1572
|
-
let changed = false;
|
|
1573
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
1574
|
-
const current = map.get(key);
|
|
1575
|
-
const existing = new Set(current);
|
|
1576
|
-
const added = [value].flat().filter((v) => !existing.has(v));
|
|
1577
|
-
if (added.length === 0) continue;
|
|
1578
|
-
map.set(key, [...current ?? [], ...added]);
|
|
1579
|
-
changed = true;
|
|
1580
|
-
}
|
|
1581
|
-
return changed;
|
|
1582
|
-
}
|
|
1583
|
-
function normalizeAutocompleteRecordEntries(entries) {
|
|
1584
|
-
if (entries == null) return void 0;
|
|
1585
|
-
return Array.isArray(entries) ? Object.fromEntries(entries) : entries;
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* Merges an `AutocompleteContribution` or `AutocompleteConfig` into the resolved autocomplete state, returning whether any entry changed.
|
|
1589
|
-
*
|
|
1590
|
-
* @param config - The resolved engine config (or a subset with the `autocomplete` field) to mutate.
|
|
1591
|
-
* @param contribution - The autocomplete entries to merge in.
|
|
1592
|
-
* @returns `true` if any selector, shortcut, property, CSS property, or pattern entry was added or extended.
|
|
1593
|
-
*
|
|
1594
|
-
* @remarks Called by `engine.appendAutocomplete()` and during initial config resolution. Each sub-field (selectors, shortcuts, etc.) is independently merged and the function returns `true` if any of them changed, which triggers an `autocompleteConfigUpdated` notification.
|
|
1595
|
-
*
|
|
1596
|
-
* @example
|
|
1597
|
-
* ```ts
|
|
1598
|
-
* const changed = appendAutocomplete(resolvedConfig, {
|
|
1599
|
-
* selectors: 'dark',
|
|
1600
|
-
* cssProperties: { color: 'primary' },
|
|
1601
|
-
* })
|
|
1602
|
-
* ```
|
|
1603
|
-
*/
|
|
1604
|
-
function appendAutocomplete(config, contribution) {
|
|
1605
|
-
const { patterns, properties, cssProperties, ...literals } = contribution;
|
|
1606
|
-
return [
|
|
1607
|
-
appendAutocompleteEntries(config.autocomplete.selectors, literals.selectors),
|
|
1608
|
-
appendAutocompleteEntries(config.autocomplete.shortcuts, literals.shortcuts),
|
|
1609
|
-
appendAutocompleteEntries(config.autocomplete.extraProperties, literals.extraProperties),
|
|
1610
|
-
appendAutocompleteEntries(config.autocomplete.extraCssProperties, literals.extraCssProperties),
|
|
1611
|
-
appendAutocompleteRecordEntries(config.autocomplete.properties, normalizeAutocompleteRecordEntries(properties)),
|
|
1612
|
-
appendAutocompleteRecordEntries(config.autocomplete.cssProperties, normalizeAutocompleteRecordEntries(cssProperties)),
|
|
1613
|
-
appendAutocompleteEntries(config.autocomplete.patterns.selectors, patterns?.selectors),
|
|
1614
|
-
appendAutocompleteEntries(config.autocomplete.patterns.shortcuts, patterns?.shortcuts),
|
|
1615
|
-
appendAutocompleteRecordEntries(config.autocomplete.patterns.properties, patterns?.properties),
|
|
1616
|
-
appendAutocompleteRecordEntries(config.autocomplete.patterns.cssProperties, patterns?.cssProperties)
|
|
1617
|
-
].some(Boolean);
|
|
1618
|
-
}
|
|
1619
|
-
/**
|
|
1620
1512
|
* Serializes a `CSSStyleBlocks` tree into a CSS string, optionally formatted with indentation and newlines.
|
|
1621
1513
|
*
|
|
1622
1514
|
* @param blocks - The CSS block tree to render.
|
|
@@ -1688,21 +1580,22 @@ function createEngineStore() {
|
|
|
1688
1580
|
* Assigns or retrieves a compact atomic style ID for the given resolved style content.
|
|
1689
1581
|
* @internal
|
|
1690
1582
|
*
|
|
1691
|
-
* @param options - Object containing the style `content`, the engine `prefix`,
|
|
1583
|
+
* @param options - Object containing the style `content`, the engine `prefix`, the `stored` ID map, and the resolved allocation strategy.
|
|
1692
1584
|
* @param options.content - The resolved style content to hash and identify.
|
|
1693
1585
|
* @param options.prefix - The class-name prefix used when constructing a new atomic style ID.
|
|
1694
1586
|
* @param options.stored - The map that caches assigned IDs by serialized key.
|
|
1587
|
+
* @param options.atomicStyleIdStrategy - Engine-owned strategy used only when a new ID is required.
|
|
1695
1588
|
* @returns The short alphabetic ID string (e.g. `'pk-a'`, `'pk-bA'`).
|
|
1696
1589
|
*
|
|
1697
1590
|
* @remarks For non-order-sensitive content, returns a cached ID if one already exists for the same base key. For order-sensitive content (where `orderSensitiveTo` is set), always generates a new ID to prevent incorrect reuse across different call-site orderings.
|
|
1698
1591
|
*
|
|
1699
1592
|
* @example
|
|
1700
1593
|
* ```ts
|
|
1701
|
-
* const id = getAtomicStyleId({ content, prefix: 'pk-', stored: store.atomicStyleIds })
|
|
1594
|
+
* const id = getAtomicStyleId({ content, prefix: 'pk-', stored: store.atomicStyleIds, atomicStyleIdStrategy })
|
|
1702
1595
|
* // 'pk-a'
|
|
1703
1596
|
* ```
|
|
1704
1597
|
*/
|
|
1705
|
-
function getAtomicStyleId({ content, prefix, stored }) {
|
|
1598
|
+
function getAtomicStyleId({ content, prefix, stored, atomicStyleIdStrategy }) {
|
|
1706
1599
|
const baseKey = getAtomicStyleBaseKey(content);
|
|
1707
1600
|
if (isOrderSensitiveContent(content) === false) {
|
|
1708
1601
|
const cached = stored.get(baseKey);
|
|
@@ -1711,12 +1604,15 @@ function getAtomicStyleId({ content, prefix, stored }) {
|
|
|
1711
1604
|
return cached;
|
|
1712
1605
|
}
|
|
1713
1606
|
}
|
|
1714
|
-
const
|
|
1715
|
-
const id =
|
|
1607
|
+
const index = stored.size;
|
|
1608
|
+
const id = atomicStyleIdStrategy({
|
|
1609
|
+
index,
|
|
1610
|
+
prefix
|
|
1611
|
+
});
|
|
1716
1612
|
const key = getAtomicStyleStoredKey({
|
|
1717
1613
|
content,
|
|
1718
1614
|
baseKey,
|
|
1719
|
-
num
|
|
1615
|
+
num: index
|
|
1720
1616
|
});
|
|
1721
1617
|
stored.set(key, id);
|
|
1722
1618
|
log.debug(`Generated new atomic style ID: ${id}`);
|
|
@@ -1726,11 +1622,12 @@ function getAtomicStyleId({ content, prefix, stored }) {
|
|
|
1726
1622
|
* Resolves a `StyleContent` into an atomic style: either reusing an existing ID or creating a new `AtomicStyle` entry in the store.
|
|
1727
1623
|
* @internal
|
|
1728
1624
|
*
|
|
1729
|
-
* @param options - Object containing the style `content`, `prefix`, `store`,
|
|
1625
|
+
* @param options - Object containing the style `content`, `prefix`, `store`, the per-use-call `resolvedIdsByBaseKey` map, and the engine-owned allocation strategy.
|
|
1730
1626
|
* @param options.content - The style content to resolve into a cached or newly registered atomic style.
|
|
1731
1627
|
* @param options.prefix - The atomic style ID prefix for any newly created IDs.
|
|
1732
1628
|
* @param options.store - The engine store holding existing atomic styles and lookup maps.
|
|
1733
1629
|
* @param options.resolvedIdsByBaseKey - Per-call memoization map for reusing order-sensitive IDs within one `engine.use()` execution.
|
|
1630
|
+
* @param options.atomicStyleIdStrategy - Engine-owned strategy used only at the new-allocation boundary.
|
|
1734
1631
|
* @returns An `AtomicStyleResolution` with the assigned `id` and optionally the newly created `atomicStyle` (absent when the ID was already registered).
|
|
1735
1632
|
*
|
|
1736
1633
|
* @remarks First checks for reusable order-sensitive IDs within the current `engine.use()` call, then falls back to `getAtomicStyleId` for general ID assignment. When a new atomic style is created, it is registered in all store indices.
|
|
@@ -1738,11 +1635,11 @@ function getAtomicStyleId({ content, prefix, stored }) {
|
|
|
1738
1635
|
* @example
|
|
1739
1636
|
* ```ts
|
|
1740
1637
|
* const { id, atomicStyle } = resolveAtomicStyle({
|
|
1741
|
-
* content, prefix: 'pk-', store, resolvedIdsByBaseKey,
|
|
1638
|
+
* content, prefix: 'pk-', store, resolvedIdsByBaseKey, atomicStyleIdStrategy,
|
|
1742
1639
|
* })
|
|
1743
1640
|
* ```
|
|
1744
1641
|
*/
|
|
1745
|
-
function resolveAtomicStyle({ content, prefix, store, resolvedIdsByBaseKey }) {
|
|
1642
|
+
function resolveAtomicStyle({ content, prefix, store, resolvedIdsByBaseKey, atomicStyleIdStrategy }) {
|
|
1746
1643
|
const reusableId = findReusableAtomicStyleId({
|
|
1747
1644
|
content,
|
|
1748
1645
|
store,
|
|
@@ -1755,7 +1652,8 @@ function resolveAtomicStyle({ content, prefix, store, resolvedIdsByBaseKey }) {
|
|
|
1755
1652
|
const id = getAtomicStyleId({
|
|
1756
1653
|
content,
|
|
1757
1654
|
prefix,
|
|
1758
|
-
stored: store.atomicStyleIds
|
|
1655
|
+
stored: store.atomicStyleIds,
|
|
1656
|
+
atomicStyleIdStrategy
|
|
1759
1657
|
});
|
|
1760
1658
|
if (store.atomicStyles.has(id)) return { id };
|
|
1761
1659
|
const atomicStyle = {
|
|
@@ -1873,6 +1771,93 @@ function getOrderSensitiveDependencyKeys(scoped, property) {
|
|
|
1873
1771
|
return dependencyKeys;
|
|
1874
1772
|
}
|
|
1875
1773
|
//#endregion
|
|
1774
|
+
//#region src/config-clone.ts
|
|
1775
|
+
/**
|
|
1776
|
+
* Deep-copies ordinary config data while preserving behavioral identities.
|
|
1777
|
+
*
|
|
1778
|
+
* Recursively isolated (fresh copies): plain objects (null or Object
|
|
1779
|
+
* prototype, third-party augmented fields included), arrays, `Map` keys and
|
|
1780
|
+
* values, `Set` values, `Date`, `RegExp` (with `lastIndex`).
|
|
1781
|
+
*
|
|
1782
|
+
* Identity-preserved (returned as-is): primitives, functions/callbacks, and
|
|
1783
|
+
* any other non-plain instance (class instances, typed arrays, promises, …) —
|
|
1784
|
+
* Core cannot know a safe clone semantic for those, so they are treated as
|
|
1785
|
+
* opaque immutable values.
|
|
1786
|
+
*
|
|
1787
|
+
* Cycles and diamond references between plain objects/arrays/Maps/Sets are
|
|
1788
|
+
* preserved through `seen`; `Date`/`RegExp` diamonds become independent
|
|
1789
|
+
* value copies (they are immutable-by-convention config data).
|
|
1790
|
+
*/
|
|
1791
|
+
function cloneConfigValue(value, seen) {
|
|
1792
|
+
if (typeof value !== "object" || value == null) return value;
|
|
1793
|
+
const cached = seen.get(value);
|
|
1794
|
+
if (cached != null) return cached;
|
|
1795
|
+
if (Array.isArray(value)) {
|
|
1796
|
+
const copy = [];
|
|
1797
|
+
seen.set(value, copy);
|
|
1798
|
+
for (const item of value) copy.push(cloneConfigValue(item, seen));
|
|
1799
|
+
return copy;
|
|
1800
|
+
}
|
|
1801
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
1802
|
+
if (value instanceof RegExp) {
|
|
1803
|
+
const copy = new RegExp(value.source, value.flags);
|
|
1804
|
+
copy.lastIndex = value.lastIndex;
|
|
1805
|
+
return copy;
|
|
1806
|
+
}
|
|
1807
|
+
if (value instanceof Map) {
|
|
1808
|
+
const copy = /* @__PURE__ */ new Map();
|
|
1809
|
+
seen.set(value, copy);
|
|
1810
|
+
for (const [key, entry] of value) copy.set(cloneConfigValue(key, seen), cloneConfigValue(entry, seen));
|
|
1811
|
+
return copy;
|
|
1812
|
+
}
|
|
1813
|
+
if (value instanceof Set) {
|
|
1814
|
+
const copy = /* @__PURE__ */ new Set();
|
|
1815
|
+
seen.set(value, copy);
|
|
1816
|
+
for (const item of value) copy.add(cloneConfigValue(item, seen));
|
|
1817
|
+
return copy;
|
|
1818
|
+
}
|
|
1819
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1820
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
1821
|
+
const copy = prototype === null ? Object.create(null) : {};
|
|
1822
|
+
seen.set(value, copy);
|
|
1823
|
+
for (const [key, entry] of Object.entries(value)) if (key === "__proto__") Object.defineProperty(copy, key, {
|
|
1824
|
+
value: cloneConfigValue(entry, seen),
|
|
1825
|
+
enumerable: true,
|
|
1826
|
+
writable: true,
|
|
1827
|
+
configurable: true
|
|
1828
|
+
});
|
|
1829
|
+
else copy[key] = cloneConfigValue(entry, seen);
|
|
1830
|
+
return copy;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Creates the engine-local mutable working copy of a caller-owned config.
|
|
1834
|
+
* @internal
|
|
1835
|
+
*
|
|
1836
|
+
* @param config - The caller-owned engine configuration.
|
|
1837
|
+
* @returns An independent working config for one `createEngine()` invocation.
|
|
1838
|
+
*
|
|
1839
|
+
* @remarks
|
|
1840
|
+
* `createEngine(config)` treats the caller's `EngineConfig` graph as
|
|
1841
|
+
* immutable input (#117): plugin configuration hooks mutate this working
|
|
1842
|
+
* copy, never the caller's objects, so one caller config can be reused
|
|
1843
|
+
* across sequential or concurrent engine creations without accumulating
|
|
1844
|
+
* setup mutations. Ordinary config data is recursively isolated —
|
|
1845
|
+
* including module-augmented third-party fields; functions and opaque
|
|
1846
|
+
* class instances keep their identity; and the `plugins` array is copied
|
|
1847
|
+
* while the `EnginePlugin` definition objects inside it keep their
|
|
1848
|
+
* identity, per the #116 reusable-definition contract (per-engine plugin
|
|
1849
|
+
* state is keyed by definition identity).
|
|
1850
|
+
*/
|
|
1851
|
+
function cloneEngineConfig(config) {
|
|
1852
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
1853
|
+
const plugins = config.plugins;
|
|
1854
|
+
if (plugins != null) {
|
|
1855
|
+
seen.set(plugins, [...plugins]);
|
|
1856
|
+
for (const plugin of plugins) if (typeof plugin === "object" && plugin != null) seen.set(plugin, plugin);
|
|
1857
|
+
}
|
|
1858
|
+
return cloneConfigValue(config, seen);
|
|
1859
|
+
}
|
|
1860
|
+
//#endregion
|
|
1876
1861
|
//#region src/constants.ts
|
|
1877
1862
|
/**
|
|
1878
1863
|
* CSS `@layer` at-rule prefix used when constructing layer-scoped selectors
|
|
@@ -2106,9 +2091,230 @@ function createExtractFn(options) {
|
|
|
2106
2091
|
});
|
|
2107
2092
|
}
|
|
2108
2093
|
//#endregion
|
|
2094
|
+
//#region src/finalization.ts
|
|
2095
|
+
const finalizers = /* @__PURE__ */ new WeakMap();
|
|
2096
|
+
/** @internal */
|
|
2097
|
+
function registerCoreEngineFinalizer(engine, finalizer) {
|
|
2098
|
+
const tasks = finalizers.get(engine) ?? [];
|
|
2099
|
+
tasks.push(finalizer);
|
|
2100
|
+
finalizers.set(engine, tasks);
|
|
2101
|
+
}
|
|
2102
|
+
/** @internal */
|
|
2103
|
+
async function runCoreEngineFinalizers(engine) {
|
|
2104
|
+
const tasks = finalizers.get(engine) ?? [];
|
|
2105
|
+
finalizers.delete(engine);
|
|
2106
|
+
for (const task of tasks) await task();
|
|
2107
|
+
}
|
|
2108
|
+
//#endregion
|
|
2109
|
+
//#region src/pika.ts
|
|
2110
|
+
const states$1 = /* @__PURE__ */ new WeakMap();
|
|
2111
|
+
/** @internal */
|
|
2112
|
+
function createPikaManager() {
|
|
2113
|
+
const manager = {
|
|
2114
|
+
hasStatic(name) {
|
|
2115
|
+
return states$1.get(manager).entries.has(name);
|
|
2116
|
+
},
|
|
2117
|
+
getStatic(name) {
|
|
2118
|
+
return states$1.get(manager).entries.get(name)?.implementation;
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
states$1.set(manager, {
|
|
2122
|
+
entries: /* @__PURE__ */ new Map(),
|
|
2123
|
+
finalized: false
|
|
2124
|
+
});
|
|
2125
|
+
return manager;
|
|
2126
|
+
}
|
|
2127
|
+
/** @internal */
|
|
2128
|
+
function createPikaRegistrationController(manager, owner) {
|
|
2129
|
+
const gate = { open: false };
|
|
2130
|
+
return {
|
|
2131
|
+
capability: Object.freeze({ extendStatic(name, implementation) {
|
|
2132
|
+
const state = states$1.get(manager);
|
|
2133
|
+
if (state.finalized) throw new Error("Pika static extensions are finalized and cannot be modified");
|
|
2134
|
+
if (!gate.open) throw new Error("Pika static extensions may only be registered during this plugin configureEngine hook");
|
|
2135
|
+
if (typeof name !== "string" || name.trim().length === 0) throw new Error("Pika static extension name must be a non-empty string");
|
|
2136
|
+
if (state.entries.has(name)) throw new Error(`Pika static extension root "${name}" is already registered`);
|
|
2137
|
+
state.entries.set(name, {
|
|
2138
|
+
owner,
|
|
2139
|
+
implementation
|
|
2140
|
+
});
|
|
2141
|
+
} }),
|
|
2142
|
+
open: () => {
|
|
2143
|
+
gate.open = true;
|
|
2144
|
+
},
|
|
2145
|
+
close: () => {
|
|
2146
|
+
gate.open = false;
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
/** @internal */
|
|
2151
|
+
function getPikaStaticOwner(manager, name) {
|
|
2152
|
+
return states$1.get(manager).entries.get(name)?.owner;
|
|
2153
|
+
}
|
|
2154
|
+
/** @internal */
|
|
2155
|
+
function finalizePikaManager(manager) {
|
|
2156
|
+
const state = states$1.get(manager);
|
|
2157
|
+
state.finalized = true;
|
|
2158
|
+
Object.freeze(manager);
|
|
2159
|
+
}
|
|
2160
|
+
//#endregion
|
|
2161
|
+
//#region src/typegen/registry.ts
|
|
2162
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
2163
|
+
const snapshotRenderOverrides = /* @__PURE__ */ new WeakMap();
|
|
2164
|
+
const MANAGED_REF_KEYS = [
|
|
2165
|
+
"selectors",
|
|
2166
|
+
"properties",
|
|
2167
|
+
"cssProperties",
|
|
2168
|
+
"cssPropertyValues",
|
|
2169
|
+
"propertyConstraints"
|
|
2170
|
+
];
|
|
2171
|
+
function compareStrings$3(a, b) {
|
|
2172
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2173
|
+
}
|
|
2174
|
+
function validateContribution(contribution) {
|
|
2175
|
+
if (contribution == null || typeof contribution !== "object" || Array.isArray(contribution)) throw new Error("Typegen contribution must be an object");
|
|
2176
|
+
if (typeof contribution.id !== "string" || contribution.id.trim().length === 0) throw new Error("Typegen contribution id must be a non-empty string");
|
|
2177
|
+
if (contribution.declarations !== void 0 && typeof contribution.declarations !== "string") throw new Error("Typegen declarations must be a string when provided");
|
|
2178
|
+
for (const key of MANAGED_REF_KEYS) {
|
|
2179
|
+
const ref = contribution[key];
|
|
2180
|
+
if (ref !== void 0 && (typeof ref !== "string" || ref.trim().length === 0)) throw new Error(`Typegen managed attachment "${key}" must be a non-empty string when provided`);
|
|
2181
|
+
}
|
|
2182
|
+
if (contribution.pika !== void 0) {
|
|
2183
|
+
if (contribution.pika == null || typeof contribution.pika !== "object" || Array.isArray(contribution.pika)) throw new Error("Typegen Pika attachment must be an object when provided");
|
|
2184
|
+
for (const [root, ref] of Object.entries(contribution.pika)) {
|
|
2185
|
+
if (root.trim().length === 0) throw new Error("Typegen Pika root must be a non-empty string");
|
|
2186
|
+
if (typeof ref !== "string" || ref.trim().length === 0) throw new Error(`Typegen Pika root "${root}" must reference a non-empty TypeScript expression`);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function freezePikaAttachment(pika) {
|
|
2191
|
+
return Object.freeze(Object.fromEntries(Object.entries(pika).sort(([a], [b]) => compareStrings$3(a, b))));
|
|
2192
|
+
}
|
|
2193
|
+
function freezeContribution(contribution) {
|
|
2194
|
+
const result = {
|
|
2195
|
+
id: contribution.id,
|
|
2196
|
+
...contribution.declarations === void 0 ? {} : { declarations: contribution.declarations },
|
|
2197
|
+
...contribution.pika === void 0 ? {} : { pika: freezePikaAttachment(contribution.pika) },
|
|
2198
|
+
...contribution.selectors === void 0 ? {} : { selectors: contribution.selectors },
|
|
2199
|
+
...contribution.properties === void 0 ? {} : { properties: contribution.properties },
|
|
2200
|
+
...contribution.cssProperties === void 0 ? {} : { cssProperties: contribution.cssProperties },
|
|
2201
|
+
...contribution.cssPropertyValues === void 0 ? {} : { cssPropertyValues: contribution.cssPropertyValues },
|
|
2202
|
+
...contribution.propertyConstraints === void 0 ? {} : { propertyConstraints: contribution.propertyConstraints }
|
|
2203
|
+
};
|
|
2204
|
+
return Object.freeze(result);
|
|
2205
|
+
}
|
|
2206
|
+
/** @internal */
|
|
2207
|
+
function createTypegenManager() {
|
|
2208
|
+
const manager = { get snapshot() {
|
|
2209
|
+
const snapshot = states.get(manager).snapshot;
|
|
2210
|
+
if (snapshot == null) throw new Error("Typegen snapshot is not available until Engine finalization");
|
|
2211
|
+
return snapshot;
|
|
2212
|
+
} };
|
|
2213
|
+
states.set(manager, {
|
|
2214
|
+
contributions: [],
|
|
2215
|
+
ids: /* @__PURE__ */ new Set(),
|
|
2216
|
+
pikaOwners: /* @__PURE__ */ new Map(),
|
|
2217
|
+
previewAssets: /* @__PURE__ */ new Map(),
|
|
2218
|
+
renderOverrides: /* @__PURE__ */ new Map(),
|
|
2219
|
+
finalized: false
|
|
2220
|
+
});
|
|
2221
|
+
return manager;
|
|
2222
|
+
}
|
|
2223
|
+
/** @internal */
|
|
2224
|
+
function createTypegenRegistrationController(manager, owner) {
|
|
2225
|
+
const gate = { open: false };
|
|
2226
|
+
return {
|
|
2227
|
+
capability: Object.freeze({ add(contribution) {
|
|
2228
|
+
const state = states.get(manager);
|
|
2229
|
+
if (state.finalized) throw new Error("Typegen contributions are finalized and cannot be modified");
|
|
2230
|
+
if (!gate.open) throw new Error("Typegen contributions may only be registered during this plugin configureEngine hook");
|
|
2231
|
+
validateContribution(contribution);
|
|
2232
|
+
if (state.ids.has(contribution.id)) throw new Error(`Typegen contribution id "${contribution.id}" is already registered`);
|
|
2233
|
+
for (const root of Object.keys(contribution.pika ?? {}).sort(compareStrings$3)) if (state.pikaOwners.has(root)) throw new Error(`Typegen Pika root "${root}" is already registered`);
|
|
2234
|
+
const value = freezeContribution(contribution);
|
|
2235
|
+
state.ids.add(value.id);
|
|
2236
|
+
for (const root of Object.keys(value.pika ?? {}).sort(compareStrings$3)) state.pikaOwners.set(root, owner);
|
|
2237
|
+
state.contributions.push({
|
|
2238
|
+
owner,
|
|
2239
|
+
value
|
|
2240
|
+
});
|
|
2241
|
+
} }),
|
|
2242
|
+
open: () => {
|
|
2243
|
+
gate.open = true;
|
|
2244
|
+
},
|
|
2245
|
+
close: () => {
|
|
2246
|
+
gate.open = false;
|
|
2247
|
+
}
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Replaces one Core-owned generated declaration and attaches path-free render
|
|
2252
|
+
* metadata after plugin configuration settles but before Typegen finalization.
|
|
2253
|
+
* Third-party raw `declarations` never pass through this seam.
|
|
2254
|
+
* @internal
|
|
2255
|
+
*/
|
|
2256
|
+
function setCoreGeneratedTypegenContribution(manager, id, options) {
|
|
2257
|
+
const state = states.get(manager);
|
|
2258
|
+
if (state.finalized) throw new Error("Typegen contributions are finalized and cannot be modified");
|
|
2259
|
+
const index = state.contributions.findIndex(({ value }) => value.id === id);
|
|
2260
|
+
if (index < 0) throw new Error(`Typegen contribution id "${id}" is not registered`);
|
|
2261
|
+
const validatedAssets = /* @__PURE__ */ new Map();
|
|
2262
|
+
for (const asset of options.previewAssets ?? []) {
|
|
2263
|
+
if (asset == null || typeof asset !== "object" || typeof asset.id !== "string" || asset.id.length === 0) throw new Error("Typegen preview asset id must be a non-empty string");
|
|
2264
|
+
if (typeof asset.content !== "string" || typeof asset.mediaType !== "string" || asset.mediaType.length === 0) throw new Error(`Typegen preview asset "${asset.id}" must provide string content and a non-empty mediaType`);
|
|
2265
|
+
const previous = validatedAssets.get(asset.id) ?? state.previewAssets.get(asset.id);
|
|
2266
|
+
if (previous != null && (previous.content !== asset.content || previous.mediaType !== asset.mediaType)) throw new Error(`Typegen preview asset id "${asset.id}" is already registered with different content`);
|
|
2267
|
+
validatedAssets.set(asset.id, Object.freeze({ ...asset }));
|
|
2268
|
+
}
|
|
2269
|
+
const existing = state.contributions[index];
|
|
2270
|
+
state.contributions[index] = {
|
|
2271
|
+
owner: existing.owner,
|
|
2272
|
+
value: Object.freeze({
|
|
2273
|
+
...existing.value,
|
|
2274
|
+
declarations: options.declarations
|
|
2275
|
+
})
|
|
2276
|
+
};
|
|
2277
|
+
state.renderOverrides.set(id, options.renderDeclarations);
|
|
2278
|
+
for (const [assetId, asset] of validatedAssets) state.previewAssets.set(assetId, asset);
|
|
2279
|
+
}
|
|
2280
|
+
/** @internal */
|
|
2281
|
+
function renderTypegenContributionDeclarations(snapshot, contribution, bindings) {
|
|
2282
|
+
return snapshotRenderOverrides.get(snapshot)?.get(contribution.id)?.(bindings) ?? contribution.declarations;
|
|
2283
|
+
}
|
|
2284
|
+
/** @internal */
|
|
2285
|
+
function validateTypegenPikaOwners(manager, getRuntimeOwner) {
|
|
2286
|
+
const state = states.get(manager);
|
|
2287
|
+
for (const root of [...state.pikaOwners.keys()].sort(compareStrings$3)) {
|
|
2288
|
+
const typegenOwner = state.pikaOwners.get(root);
|
|
2289
|
+
const runtimeOwner = getRuntimeOwner(root);
|
|
2290
|
+
if (runtimeOwner != null && runtimeOwner !== typegenOwner) throw new Error(`Pika root "${root}" has different runtime and Typegen owners`);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
/** @internal */
|
|
2294
|
+
function finalizeTypegenManager(manager) {
|
|
2295
|
+
const state = states.get(manager);
|
|
2296
|
+
const contributions = Object.freeze(state.contributions.map(({ value }) => value).sort((a, b) => compareStrings$3(a.id, b.id)));
|
|
2297
|
+
const previewAssets = Object.freeze([...state.previewAssets.values()].sort((a, b) => compareStrings$3(a.id, b.id)));
|
|
2298
|
+
const snapshot = Object.freeze({
|
|
2299
|
+
contributions,
|
|
2300
|
+
previewAssets
|
|
2301
|
+
});
|
|
2302
|
+
state.snapshot = snapshot;
|
|
2303
|
+
snapshotRenderOverrides.set(snapshot, new Map(state.renderOverrides));
|
|
2304
|
+
state.finalized = true;
|
|
2305
|
+
Object.freeze(manager);
|
|
2306
|
+
}
|
|
2307
|
+
//#endregion
|
|
2109
2308
|
//#region src/plugin.ts
|
|
2110
|
-
const VOID_HOOKS = new Set(["preflightUpdated"
|
|
2111
|
-
const DEFAULT_PLUGIN_CONTEXT = {
|
|
2309
|
+
const VOID_HOOKS = new Set(["preflightUpdated"]);
|
|
2310
|
+
const DEFAULT_PLUGIN_CONTEXT = {
|
|
2311
|
+
onDiagnostic: noopDiagnosticHandler,
|
|
2312
|
+
state: void 0,
|
|
2313
|
+
host: {}
|
|
2314
|
+
};
|
|
2315
|
+
function resolvePluginContext(source, plugin) {
|
|
2316
|
+
return typeof source === "function" ? source(plugin) : source;
|
|
2317
|
+
}
|
|
2112
2318
|
function getPluginHook(plugin, hook) {
|
|
2113
2319
|
const hookFn = plugin[hook];
|
|
2114
2320
|
return typeof hookFn === "function" ? hookFn : null;
|
|
@@ -2159,12 +2365,13 @@ async function execAsyncHook(plugins, hook, payload, context = DEFAULT_PLUGIN_CO
|
|
|
2159
2365
|
for (const plugin of plugins) {
|
|
2160
2366
|
const hookFn = getPluginHook(plugin, hook);
|
|
2161
2367
|
if (hookFn == null) continue;
|
|
2368
|
+
const pluginContext = resolvePluginContext(context, plugin);
|
|
2162
2369
|
try {
|
|
2163
2370
|
logPluginHookStart(plugin, hook);
|
|
2164
|
-
current = applyHookPayload(current, await invokePluginHook(hookFn, hook, current,
|
|
2371
|
+
current = applyHookPayload(current, await invokePluginHook(hookFn, hook, current, pluginContext));
|
|
2165
2372
|
logPluginHookEnd(plugin, hook);
|
|
2166
2373
|
} catch (error) {
|
|
2167
|
-
reportPluginHookError(
|
|
2374
|
+
reportPluginHookError(pluginContext, plugin, hook, error);
|
|
2168
2375
|
throw error;
|
|
2169
2376
|
}
|
|
2170
2377
|
}
|
|
@@ -2184,12 +2391,13 @@ function execSyncHook(plugins, hook, payload, context = DEFAULT_PLUGIN_CONTEXT)
|
|
|
2184
2391
|
for (const plugin of plugins) {
|
|
2185
2392
|
const hookFn = getPluginHook(plugin, hook);
|
|
2186
2393
|
if (hookFn == null) continue;
|
|
2394
|
+
const pluginContext = resolvePluginContext(context, plugin);
|
|
2187
2395
|
try {
|
|
2188
2396
|
logPluginHookStart(plugin, hook);
|
|
2189
|
-
current = applyHookPayload(current, invokePluginHook(hookFn, hook, current,
|
|
2397
|
+
current = applyHookPayload(current, invokePluginHook(hookFn, hook, current, pluginContext));
|
|
2190
2398
|
logPluginHookEnd(plugin, hook);
|
|
2191
2399
|
} catch (error) {
|
|
2192
|
-
reportPluginHookError(
|
|
2400
|
+
reportPluginHookError(pluginContext, plugin, hook, error);
|
|
2193
2401
|
throw error;
|
|
2194
2402
|
}
|
|
2195
2403
|
}
|
|
@@ -2200,19 +2408,89 @@ function execSyncHook(plugins, hook, payload, context = DEFAULT_PLUGIN_CONTEXT)
|
|
|
2200
2408
|
* Creates an engine-local hook dispatcher bound to one diagnostic context.
|
|
2201
2409
|
*
|
|
2202
2410
|
* @internal
|
|
2411
|
+
* @remarks
|
|
2412
|
+
* Each dispatcher instance owns one plugin-context store: every plugin
|
|
2413
|
+
* definition gets exactly one `EnginePluginContext` (with `state` initialized
|
|
2414
|
+
* lazily via `createState()`) per dispatcher — i.e. per engine, since
|
|
2415
|
+
* `createEngine` creates one dispatcher per engine (#116). The same plugin
|
|
2416
|
+
* definition used with another dispatcher/engine gets a distinct context and
|
|
2417
|
+
* distinct state.
|
|
2203
2418
|
*/
|
|
2204
2419
|
function createEngineHooks(context) {
|
|
2420
|
+
const pluginContexts = /* @__PURE__ */ new WeakMap();
|
|
2421
|
+
const host = context.host ?? {};
|
|
2422
|
+
const contextFor = (plugin) => {
|
|
2423
|
+
let entry = pluginContexts.get(plugin);
|
|
2424
|
+
if (entry == null) {
|
|
2425
|
+
try {
|
|
2426
|
+
entry = {
|
|
2427
|
+
status: "ok",
|
|
2428
|
+
context: {
|
|
2429
|
+
onDiagnostic: context.onDiagnostic,
|
|
2430
|
+
state: plugin.createState?.(),
|
|
2431
|
+
host
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
entry = {
|
|
2436
|
+
status: "failed",
|
|
2437
|
+
error
|
|
2438
|
+
};
|
|
2439
|
+
emitDiagnostic(context.onDiagnostic, {
|
|
2440
|
+
level: "error",
|
|
2441
|
+
code: "plugin-state-init-error",
|
|
2442
|
+
message: `Plugin "${plugin.name}" failed to initialize its engine-local state: ${error instanceof Error ? error.message : String(error)}`,
|
|
2443
|
+
cause: error,
|
|
2444
|
+
plugin: plugin.name
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
pluginContexts.set(plugin, entry);
|
|
2448
|
+
}
|
|
2449
|
+
if (entry.status === "failed") throw entry.error;
|
|
2450
|
+
return entry.context;
|
|
2451
|
+
};
|
|
2452
|
+
const configureEngine = async (plugins, engine) => {
|
|
2453
|
+
logHookStart("Async", "configureEngine");
|
|
2454
|
+
for (const plugin of plugins) {
|
|
2455
|
+
const hookFn = getPluginHook(plugin, "configureEngine");
|
|
2456
|
+
if (hookFn == null) continue;
|
|
2457
|
+
const pluginContext = contextFor(plugin);
|
|
2458
|
+
const pikaRegistration = createPikaRegistrationController(engine.pika, plugin);
|
|
2459
|
+
const typegenRegistration = createTypegenRegistrationController(engine.typegen, plugin);
|
|
2460
|
+
const configurator = Object.freeze({
|
|
2461
|
+
...pluginContext,
|
|
2462
|
+
runtime: engine,
|
|
2463
|
+
pika: pikaRegistration.capability,
|
|
2464
|
+
typegen: typegenRegistration.capability
|
|
2465
|
+
});
|
|
2466
|
+
pikaRegistration.open();
|
|
2467
|
+
typegenRegistration.open();
|
|
2468
|
+
try {
|
|
2469
|
+
logPluginHookStart(plugin, "configureEngine");
|
|
2470
|
+
await hookFn(configurator);
|
|
2471
|
+
logPluginHookEnd(plugin, "configureEngine");
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
reportPluginHookError(pluginContext, plugin, "configureEngine", error);
|
|
2474
|
+
throw error;
|
|
2475
|
+
} finally {
|
|
2476
|
+
pikaRegistration.close();
|
|
2477
|
+
typegenRegistration.close();
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
logHookEnd("Async", "configureEngine");
|
|
2481
|
+
return engine;
|
|
2482
|
+
};
|
|
2205
2483
|
return {
|
|
2206
|
-
configureRawConfig: (plugins, config) => execAsyncHook(plugins, "configureRawConfig", config,
|
|
2207
|
-
rawConfigConfigured: (plugins, config) => execSyncHook(plugins, "rawConfigConfigured", config,
|
|
2208
|
-
configureResolvedConfig: (plugins, resolvedConfig) => execAsyncHook(plugins, "configureResolvedConfig", resolvedConfig,
|
|
2209
|
-
configureEngine
|
|
2210
|
-
transformSelectors: (plugins, selectors) => execAsyncHook(plugins, "transformSelectors", selectors,
|
|
2211
|
-
transformStyleItems: (plugins, styleItems) => execAsyncHook(plugins, "transformStyleItems", styleItems,
|
|
2212
|
-
transformStyleDefinitions: (plugins, styleDefinitions) => execAsyncHook(plugins, "transformStyleDefinitions", styleDefinitions,
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2484
|
+
configureRawConfig: (plugins, config) => execAsyncHook(plugins, "configureRawConfig", config, contextFor),
|
|
2485
|
+
rawConfigConfigured: (plugins, config) => execSyncHook(plugins, "rawConfigConfigured", config, contextFor),
|
|
2486
|
+
configureResolvedConfig: (plugins, resolvedConfig) => execAsyncHook(plugins, "configureResolvedConfig", resolvedConfig, contextFor),
|
|
2487
|
+
configureEngine,
|
|
2488
|
+
transformSelectors: (plugins, selectors) => execAsyncHook(plugins, "transformSelectors", selectors, contextFor),
|
|
2489
|
+
transformStyleItems: (plugins, styleItems) => execAsyncHook(plugins, "transformStyleItems", styleItems, contextFor),
|
|
2490
|
+
transformStyleDefinitions: (plugins, styleDefinitions) => execAsyncHook(plugins, "transformStyleDefinitions", styleDefinitions, contextFor),
|
|
2491
|
+
transformStyleContents: (plugins, styleContents) => execAsyncHook(plugins, "transformStyleContents", styleContents, contextFor),
|
|
2492
|
+
preflightUpdated: (plugins) => execSyncHook(plugins, "preflightUpdated", void 0, contextFor),
|
|
2493
|
+
atomicStyleAdded: (plugins, atomicStyle) => execSyncHook(plugins, "atomicStyleAdded", atomicStyle, contextFor)
|
|
2216
2494
|
};
|
|
2217
2495
|
}
|
|
2218
2496
|
createEngineHooks(DEFAULT_PLUGIN_CONTEXT);
|
|
@@ -2235,6 +2513,10 @@ function resolvePlugins(plugins) {
|
|
|
2235
2513
|
*
|
|
2236
2514
|
* @param plugin - The plugin definition to return unchanged.
|
|
2237
2515
|
* @returns The same plugin instance.
|
|
2516
|
+
*
|
|
2517
|
+
* @remarks
|
|
2518
|
+
* When the plugin declares `createState`, the state type is inferred from its
|
|
2519
|
+
* return value and every hook's `context.state` is typed accordingly.
|
|
2238
2520
|
*/
|
|
2239
2521
|
function defineEnginePlugin(plugin) {
|
|
2240
2522
|
return plugin;
|
|
@@ -2257,7 +2539,7 @@ function modifyPropertyValue(value) {
|
|
|
2257
2539
|
*
|
|
2258
2540
|
* @returns An `EnginePlugin` that intercepts `transformStyleDefinitions` to conditionally append `!important` to every property value.
|
|
2259
2541
|
*
|
|
2260
|
-
* @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).
|
|
2542
|
+
* @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).
|
|
2261
2543
|
*
|
|
2262
2544
|
* @example
|
|
2263
2545
|
* ```ts
|
|
@@ -2265,7 +2547,6 @@ function modifyPropertyValue(value) {
|
|
|
2265
2547
|
* ```
|
|
2266
2548
|
*/
|
|
2267
2549
|
function important() {
|
|
2268
|
-
let defaultValue;
|
|
2269
2550
|
function propagateExplicitFlag(v, flag) {
|
|
2270
2551
|
if (Array.isArray(v)) return v.map((item) => typeof item === "object" && item !== null && !Array.isArray(item) ? {
|
|
2271
2552
|
__important: flag,
|
|
@@ -2278,23 +2559,28 @@ function important() {
|
|
|
2278
2559
|
}
|
|
2279
2560
|
return defineEnginePlugin({
|
|
2280
2561
|
name: "core:important",
|
|
2281
|
-
|
|
2282
|
-
|
|
2562
|
+
createState: () => ({ defaultValue: false }),
|
|
2563
|
+
rawConfigConfigured(config, context) {
|
|
2564
|
+
context.state.defaultValue = config.important?.default ?? false;
|
|
2283
2565
|
},
|
|
2284
|
-
configureEngine(
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2566
|
+
configureEngine(configurator) {
|
|
2567
|
+
configurator.typegen.add({
|
|
2568
|
+
id: "core:important",
|
|
2569
|
+
declarations: [
|
|
2570
|
+
"interface __PikaImportantProperties {",
|
|
2571
|
+
" __important?: boolean",
|
|
2572
|
+
"}"
|
|
2573
|
+
].join("\n"),
|
|
2574
|
+
properties: "__PikaImportantProperties"
|
|
2288
2575
|
});
|
|
2289
2576
|
},
|
|
2290
|
-
transformStyleDefinitions(styleDefinitions) {
|
|
2577
|
+
transformStyleDefinitions(styleDefinitions, context) {
|
|
2291
2578
|
return styleDefinitions.map((styleDefinition) => {
|
|
2292
2579
|
const { __important, ...rest } = styleDefinition;
|
|
2293
2580
|
const explicit = __important;
|
|
2294
|
-
const important = explicit ?? defaultValue;
|
|
2581
|
+
const important = explicit ?? context.state.defaultValue;
|
|
2295
2582
|
if (important === false && explicit == null) return rest;
|
|
2296
2583
|
return Object.fromEntries(Object.entries(rest).map(([k, v]) => {
|
|
2297
|
-
if (k === "__shortcut") return [k, v];
|
|
2298
2584
|
if (isPropertyValue(v)) return [k, important ? modifyPropertyValue(v) : v];
|
|
2299
2585
|
return [k, explicit == null ? v : propagateExplicitFlag(v, explicit)];
|
|
2300
2586
|
}));
|
|
@@ -2303,45 +2589,147 @@ function important() {
|
|
|
2303
2589
|
});
|
|
2304
2590
|
}
|
|
2305
2591
|
//#endregion
|
|
2306
|
-
//#region src/
|
|
2592
|
+
//#region src/typegen/jsdoc.ts
|
|
2593
|
+
const LEFT_TO_RIGHT_MARK = "";
|
|
2594
|
+
const RE_LEADING_INDENT = /^(\s*)/;
|
|
2595
|
+
const RE_USER_JSDOC_TAG = /^(\s*)@/;
|
|
2596
|
+
function sanitizeJSDocText(text, neutralizeTags) {
|
|
2597
|
+
return text.replaceAll("*/", `*${LEFT_TO_RIGHT_MARK}/`).split("\n").map((line) => {
|
|
2598
|
+
return `${LEFT_TO_RIGHT_MARK}${(neutralizeTags ? line.replace(RE_USER_JSDOC_TAG, `$1${LEFT_TO_RIGHT_MARK}@`) : line).replace(RE_LEADING_INDENT, `$1${LEFT_TO_RIGHT_MARK}`)}`;
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
function markdownImage(alt, href) {
|
|
2602
|
+
return ``;
|
|
2603
|
+
}
|
|
2307
2604
|
/**
|
|
2308
|
-
*
|
|
2605
|
+
* Renders one lexical-safe JSDoc block from path-free semantic documentation.
|
|
2309
2606
|
*
|
|
2310
|
-
* @
|
|
2607
|
+
* @remarks
|
|
2608
|
+
* The renderer preserves the historical `### PikaCSS Preview` fenced-CSS
|
|
2609
|
+
* convention and U+200E safety workaround. Arbitrary descriptions are prevented
|
|
2610
|
+
* from becoming semantic JSDoc `@tags`. Preview-image hrefs are supplied only at
|
|
2611
|
+
* final render time, so semantic snapshots never contain host paths or URIs.
|
|
2311
2612
|
*
|
|
2312
|
-
* @
|
|
2613
|
+
* @param documentation - Path-free description, preview, and semantic tags to render.
|
|
2614
|
+
* @param bindings - Host callbacks used to resolve semantic preview asset IDs to hrefs.
|
|
2615
|
+
* @param indent - Prefix applied to every line of the generated JSDoc block.
|
|
2313
2616
|
*
|
|
2314
|
-
* @
|
|
2315
|
-
* ```ts
|
|
2316
|
-
* createEngine({ plugins: [keyframes()] })
|
|
2317
|
-
* ```
|
|
2617
|
+
* @internal
|
|
2318
2618
|
*/
|
|
2619
|
+
function renderTypegenJSDoc(documentation, bindings = {}, indent = "") {
|
|
2620
|
+
const body = [];
|
|
2621
|
+
if (documentation.description != null && documentation.description.length > 0) body.push(...sanitizeJSDocText(documentation.description, true));
|
|
2622
|
+
const imageLines = (documentation.previewImages ?? []).flatMap((image) => {
|
|
2623
|
+
const href = bindings.resolvePreviewImageHref?.(image.assetId);
|
|
2624
|
+
if (href == null) return [];
|
|
2625
|
+
return sanitizeJSDocText(markdownImage(image.alt ?? "PikaCSS Preview", href), true);
|
|
2626
|
+
});
|
|
2627
|
+
const hasPreview = (documentation.previewCss?.length ?? 0) > 0 || imageLines.length > 0;
|
|
2628
|
+
if (hasPreview && body.length > 0) body.push("");
|
|
2629
|
+
if (hasPreview) body.push(`${LEFT_TO_RIGHT_MARK}### PikaCSS Preview`);
|
|
2630
|
+
body.push(...imageLines);
|
|
2631
|
+
if ((documentation.previewCss?.length ?? 0) > 0) {
|
|
2632
|
+
body.push(`${LEFT_TO_RIGHT_MARK}\`\`\`css`);
|
|
2633
|
+
body.push(...sanitizeJSDocText(documentation.previewCss, false));
|
|
2634
|
+
body.push(`${LEFT_TO_RIGHT_MARK}\`\`\``);
|
|
2635
|
+
}
|
|
2636
|
+
const tags = (documentation.tags ?? []).flatMap((tag) => {
|
|
2637
|
+
if (!/^[A-Z][\w-]*$/i.test(tag.name)) throw new Error(`Invalid Typegen JSDoc tag name: ${tag.name}`);
|
|
2638
|
+
const text = tag.text == null || tag.text.length === 0 ? [] : sanitizeJSDocText(tag.text, true);
|
|
2639
|
+
if (text.length === 0) return [`@${tag.name}`];
|
|
2640
|
+
return [`@${tag.name} ${text[0]}`, ...text.slice(1)];
|
|
2641
|
+
});
|
|
2642
|
+
if (tags.length > 0 && body.length > 0) body.push("");
|
|
2643
|
+
body.push(...tags);
|
|
2644
|
+
if (body.length === 0) return [];
|
|
2645
|
+
return [
|
|
2646
|
+
`${indent}/**`,
|
|
2647
|
+
...body.map((line) => `${indent} * ${line}`),
|
|
2648
|
+
`${indent} */`
|
|
2649
|
+
];
|
|
2650
|
+
}
|
|
2651
|
+
//#endregion
|
|
2652
|
+
//#region src/plugins/keyframes.ts
|
|
2653
|
+
function resolveKeyframesConfig(config, defaultPruneUnused) {
|
|
2654
|
+
if ("external" in config) {
|
|
2655
|
+
if (typeof config.external !== "string" || config.external.trim().length === 0) return void 0;
|
|
2656
|
+
return {
|
|
2657
|
+
name: config.external,
|
|
2658
|
+
animationValues: [config.animationValues ?? []].flat(),
|
|
2659
|
+
description: config.description,
|
|
2660
|
+
external: true,
|
|
2661
|
+
pruneUnused: false
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
if (typeof config.name !== "string" || config.name.trim().length === 0 || config.frames == null) return void 0;
|
|
2665
|
+
return {
|
|
2666
|
+
name: config.name,
|
|
2667
|
+
frames: config.frames,
|
|
2668
|
+
animationValues: [config.animationValues ?? []].flat(),
|
|
2669
|
+
description: config.description,
|
|
2670
|
+
external: false,
|
|
2671
|
+
pruneUnused: config.pruneUnused ?? defaultPruneUnused
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
function renderKeyframesPreview(name, frames) {
|
|
2675
|
+
const lines = [`@keyframes ${name} {`];
|
|
2676
|
+
for (const [stop, properties] of Object.entries(frames)) {
|
|
2677
|
+
lines.push(` ${stop} {`);
|
|
2678
|
+
for (const [property, rawValue] of Object.entries(properties)) {
|
|
2679
|
+
const values = normalizeValue(rawValue) ?? [];
|
|
2680
|
+
for (const value of values) lines.push(` ${toKebab(property)}: ${value};`);
|
|
2681
|
+
}
|
|
2682
|
+
lines.push(" }");
|
|
2683
|
+
}
|
|
2684
|
+
lines.push("}");
|
|
2685
|
+
return lines.join("\n");
|
|
2686
|
+
}
|
|
2687
|
+
function renderKeyframesDeclarations(definitions) {
|
|
2688
|
+
const ordered = [...definitions].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
2689
|
+
const lines = ["interface __PikaKeyframes {"];
|
|
2690
|
+
for (const definition of ordered) {
|
|
2691
|
+
lines.push(...renderTypegenJSDoc({
|
|
2692
|
+
description: definition.description,
|
|
2693
|
+
previewCss: definition.frames == null ? void 0 : renderKeyframesPreview(definition.name, definition.frames)
|
|
2694
|
+
}, {}, " "));
|
|
2695
|
+
lines.push(` ${JSON.stringify(definition.name)}: ${JSON.stringify(definition.name)}`);
|
|
2696
|
+
}
|
|
2697
|
+
lines.push("}");
|
|
2698
|
+
const names = ordered.map(({ name }) => JSON.stringify(name));
|
|
2699
|
+
const animationValues = ordered.flatMap(({ name, animationValues }) => [name, ...animationValues]).filter((value, index, list) => list.indexOf(value) === index).map((value) => JSON.stringify(value));
|
|
2700
|
+
lines.push("interface __PikaKeyframePropertyValues {");
|
|
2701
|
+
lines.push(` animationName: ${names.length === 0 ? "never" : names.join(" | ")}`);
|
|
2702
|
+
lines.push(` animation: ${animationValues.length === 0 ? "never" : animationValues.join(" | ")}`);
|
|
2703
|
+
lines.push("}");
|
|
2704
|
+
return lines.join("\n");
|
|
2705
|
+
}
|
|
2706
|
+
/** Built-in keyframes subsystem with config-only semantic ingress. */
|
|
2319
2707
|
function keyframes() {
|
|
2320
|
-
let resolveKeyframesConfig;
|
|
2321
|
-
let configList;
|
|
2322
2708
|
return defineEnginePlugin({
|
|
2323
2709
|
name: "core:keyframes",
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2710
|
+
createState: () => ({
|
|
2711
|
+
definitions: [],
|
|
2712
|
+
defaultPruneUnused: true,
|
|
2713
|
+
store: /* @__PURE__ */ new Map()
|
|
2714
|
+
}),
|
|
2715
|
+
rawConfigConfigured(config, context) {
|
|
2716
|
+
context.state.definitions = config.keyframes?.definitions ?? [];
|
|
2717
|
+
context.state.defaultPruneUnused = config.keyframes?.pruneUnused ?? true;
|
|
2327
2718
|
},
|
|
2328
|
-
configureEngine(
|
|
2329
|
-
engine
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
}
|
|
2343
|
-
};
|
|
2344
|
-
engine.keyframes.add(...configList);
|
|
2719
|
+
configureEngine(configurator) {
|
|
2720
|
+
const engine = configurator.runtime;
|
|
2721
|
+
const resolved = configurator.state.definitions.map((definition) => resolveKeyframesConfig(definition, configurator.state.defaultPruneUnused)).filter((definition) => definition != null);
|
|
2722
|
+
configurator.state.store.clear();
|
|
2723
|
+
for (const definition of resolved) if (!definition.external) configurator.state.store.set(definition.name, definition);
|
|
2724
|
+
const namespace = Object.freeze(Object.fromEntries(resolved.map(({ name }) => [name, name])));
|
|
2725
|
+
configurator.pika.extendStatic("kf", namespace);
|
|
2726
|
+
configurator.typegen.add({
|
|
2727
|
+
id: "core:keyframes",
|
|
2728
|
+
declarations: renderKeyframesDeclarations(resolved),
|
|
2729
|
+
pika: { kf: "__PikaKeyframes" },
|
|
2730
|
+
cssPropertyValues: "__PikaKeyframePropertyValues"
|
|
2731
|
+
});
|
|
2732
|
+
const state = configurator.state;
|
|
2345
2733
|
engine.addPreflight((engine, _isFormatted, ctx) => {
|
|
2346
2734
|
const maybeUsedName = /* @__PURE__ */ new Set();
|
|
2347
2735
|
engine.store.atomicStyles.forEach(({ content: { property, value } }, id) => {
|
|
@@ -2356,41 +2744,41 @@ function keyframes() {
|
|
|
2356
2744
|
});
|
|
2357
2745
|
});
|
|
2358
2746
|
});
|
|
2359
|
-
const maybeUsedKeyframes = Array.from(engine.keyframes.store.values()).filter(({ name, frames, pruneUnused }) => (pruneUnused === false || maybeUsedName.has(name)) && frames != null);
|
|
2360
2747
|
const preflightDefinition = {};
|
|
2361
|
-
|
|
2748
|
+
for (const { name, frames, pruneUnused } of state.store.values()) {
|
|
2749
|
+
if (frames == null || pruneUnused !== false && !maybeUsedName.has(name)) continue;
|
|
2362
2750
|
preflightDefinition[`@keyframes ${name}`] = frames;
|
|
2363
|
-
}
|
|
2751
|
+
}
|
|
2364
2752
|
return preflightDefinition;
|
|
2365
2753
|
});
|
|
2366
2754
|
}
|
|
2367
2755
|
});
|
|
2368
2756
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2757
|
+
//#endregion
|
|
2758
|
+
//#region src/plugins/layers.ts
|
|
2759
|
+
function compareStrings$2(a, b) {
|
|
2760
|
+
return a < b ? -1 : 1;
|
|
2761
|
+
}
|
|
2762
|
+
function renderLayerDeclarations(names) {
|
|
2763
|
+
return [
|
|
2764
|
+
`type __PikaLayerName = ${[...[...new Set(names)].sort(compareStrings$2).map((name) => JSON.stringify(name)), "(string & {})"].join(" | ")}`,
|
|
2765
|
+
"interface __PikaLayerProperties {",
|
|
2766
|
+
" __layer?: __PikaLayerName",
|
|
2767
|
+
"}"
|
|
2768
|
+
].join("\n");
|
|
2769
|
+
}
|
|
2770
|
+
/** Internal Core owner for the `__layer` authoring directive Typegen surface. */
|
|
2771
|
+
function layers() {
|
|
2772
|
+
return defineEnginePlugin({
|
|
2773
|
+
name: "core:layers",
|
|
2774
|
+
configureEngine(configurator) {
|
|
2775
|
+
configurator.typegen.add({
|
|
2776
|
+
id: "core:layers",
|
|
2777
|
+
declarations: renderLayerDeclarations(Object.keys(configurator.runtime.config.layers)),
|
|
2778
|
+
properties: "__PikaLayerProperties"
|
|
2779
|
+
});
|
|
2385
2780
|
}
|
|
2386
|
-
|
|
2387
|
-
return {
|
|
2388
|
-
name,
|
|
2389
|
-
frames,
|
|
2390
|
-
autocomplete,
|
|
2391
|
-
pruneUnused
|
|
2392
|
-
};
|
|
2393
|
-
};
|
|
2781
|
+
});
|
|
2394
2782
|
}
|
|
2395
2783
|
//#endregion
|
|
2396
2784
|
//#region src/resolver.ts
|
|
@@ -2398,6 +2786,12 @@ function stripGlobalFlag(re) {
|
|
|
2398
2786
|
if (!re.global) return re;
|
|
2399
2787
|
return new RegExp(re.source, re.flags.replace("g", ""));
|
|
2400
2788
|
}
|
|
2789
|
+
/** Tests one input without observing or mutating the caller's RegExp lastIndex. */
|
|
2790
|
+
function matchesRulePattern(pattern, input) {
|
|
2791
|
+
const isolated = new RegExp(pattern.source, pattern.flags);
|
|
2792
|
+
isolated.lastIndex = 0;
|
|
2793
|
+
return isolated.test(input);
|
|
2794
|
+
}
|
|
2401
2795
|
/**
|
|
2402
2796
|
* Base resolver class that manages static and dynamic rules and caches resolution results.
|
|
2403
2797
|
* @internal
|
|
@@ -2712,308 +3106,311 @@ function createDynamicResolvedFactory(fn) {
|
|
|
2712
3106
|
};
|
|
2713
3107
|
}
|
|
2714
3108
|
/**
|
|
2715
|
-
* Normalizes
|
|
2716
|
-
*
|
|
2717
|
-
*
|
|
2718
|
-
*
|
|
2719
|
-
*
|
|
2720
|
-
* @param keyName - The property name on an object-form config that holds the match key or pattern.
|
|
2721
|
-
* @returns A `ResolvedRuleConfig<T>` for valid static/dynamic configs, the original string for redirect configs, or `undefined` if the config shape is unrecognized.
|
|
2722
|
-
*
|
|
2723
|
-
* @remarks Handles three config shapes:
|
|
2724
|
-
* - **String**: returned as-is for the caller to treat as a redirect to another rule.
|
|
2725
|
-
* - **Tuple**: `[string, T | T[]]` for static rules, `[RegExp, fn, autocomplete?]` for dynamic rules.
|
|
2726
|
-
* - **Object**: `{ [keyName]: string | RegExp, value: T | fn, autocomplete?: string[] }`.
|
|
2727
|
-
*
|
|
2728
|
-
* 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.
|
|
2729
|
-
*
|
|
2730
|
-
* @example
|
|
2731
|
-
* ```ts
|
|
2732
|
-
* resolveRuleConfig(['hover', '$:hover'], 'selector')
|
|
2733
|
-
* // { type: 'static', rule: { key: 'hover', ... }, autocomplete: ['hover'] }
|
|
2734
|
-
* ```
|
|
3109
|
+
* Normalizes the frozen object-only selector/shortcut rule grammar.
|
|
3110
|
+
* Static definitions use `{ name, value }`; dynamic definitions use
|
|
3111
|
+
* `{ pattern, inputType, resolve, autocomplete? }`. `inputType` and rich
|
|
3112
|
+
* documentation metadata are semantic Typegen inputs and intentionally do not
|
|
3113
|
+
* affect runtime matching here.
|
|
2735
3114
|
*/
|
|
2736
|
-
function resolveRuleConfig(config
|
|
2737
|
-
if (typeof config
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
key: config[0],
|
|
2741
|
-
value: config[1],
|
|
2742
|
-
autocomplete: config[2]
|
|
2743
|
-
} : {
|
|
2744
|
-
key: config[keyName],
|
|
2745
|
-
value: config.value,
|
|
2746
|
-
autocomplete: config.autocomplete
|
|
2747
|
-
};
|
|
2748
|
-
if (typeof key === "string" && typeof value !== "function") return {
|
|
3115
|
+
function resolveRuleConfig(config) {
|
|
3116
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) return void 0;
|
|
3117
|
+
const definition = config;
|
|
3118
|
+
if (typeof definition.name === "string" && definition.name.trim().length > 0 && "value" in definition) return {
|
|
2749
3119
|
type: "static",
|
|
2750
3120
|
rule: {
|
|
2751
|
-
key,
|
|
2752
|
-
string:
|
|
2753
|
-
resolved: [value].flat(1)
|
|
3121
|
+
key: definition.name,
|
|
3122
|
+
string: definition.name,
|
|
3123
|
+
resolved: [definition.value].flat(1)
|
|
2754
3124
|
},
|
|
2755
|
-
autocomplete: [
|
|
3125
|
+
autocomplete: [definition.name]
|
|
2756
3126
|
};
|
|
2757
|
-
if (
|
|
3127
|
+
if (definition.pattern instanceof RegExp && typeof definition.inputType === "string" && definition.inputType.trim().length > 0 && typeof definition.resolve === "function") return {
|
|
2758
3128
|
type: "dynamic",
|
|
2759
3129
|
rule: {
|
|
2760
|
-
key:
|
|
2761
|
-
stringPattern: stripGlobalFlag(
|
|
2762
|
-
createResolved: createDynamicResolvedFactory(
|
|
3130
|
+
key: definition.pattern.source,
|
|
3131
|
+
stringPattern: stripGlobalFlag(definition.pattern),
|
|
3132
|
+
createResolved: createDynamicResolvedFactory(definition.resolve)
|
|
2763
3133
|
},
|
|
2764
|
-
autocomplete: autocomplete
|
|
3134
|
+
autocomplete: definition.autocomplete == null ? [] : [definition.autocomplete].flat(1)
|
|
2765
3135
|
};
|
|
2766
3136
|
}
|
|
2767
3137
|
//#endregion
|
|
2768
3138
|
//#region src/plugins/selectors.ts
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
3139
|
+
function renderSelectorDeclarations(definitions, onInvalidAutocomplete) {
|
|
3140
|
+
const explicit = /* @__PURE__ */ new Map();
|
|
3141
|
+
const dynamicTypes = [];
|
|
3142
|
+
for (const definition of definitions) {
|
|
3143
|
+
if ("name" in definition) {
|
|
3144
|
+
explicit.set(definition.name, definition.description);
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3147
|
+
dynamicTypes.push(definition.inputType);
|
|
3148
|
+
for (const value of [definition.autocomplete ?? []].flat()) {
|
|
3149
|
+
if (!matchesRulePattern(definition.pattern, value)) {
|
|
3150
|
+
onInvalidAutocomplete(value, definition.pattern);
|
|
3151
|
+
continue;
|
|
3152
|
+
}
|
|
3153
|
+
explicit.set(value, definition.description);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
const lines = ["interface __PikaExplicitSelectors {"];
|
|
3157
|
+
for (const [name, description] of [...explicit].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
3158
|
+
lines.push(...renderTypegenJSDoc({ description }, {}, " "));
|
|
3159
|
+
lines.push(` ${JSON.stringify(name)}?: __StyleDefinition | __StyleItem[]`);
|
|
3160
|
+
}
|
|
3161
|
+
lines.push("}");
|
|
3162
|
+
if (dynamicTypes.length === 0) lines.push("type __PikaSelectors = __PikaExplicitSelectors");
|
|
3163
|
+
else {
|
|
3164
|
+
lines.push(`type __PikaDynamicSelectorInput = ${dynamicTypes.join(" | ")}`);
|
|
3165
|
+
lines.push("type __PikaDynamicSelectors = { [K in __PikaDynamicSelectorInput]?: __StyleDefinition | __StyleItem[] }");
|
|
3166
|
+
lines.push("type __PikaSelectors = __PikaExplicitSelectors & __PikaDynamicSelectors");
|
|
3167
|
+
}
|
|
3168
|
+
return lines.join("\n");
|
|
3169
|
+
}
|
|
3170
|
+
/** Built-in selector subsystem. Effective raw config is its only semantic ingress. */
|
|
2781
3171
|
function selectors() {
|
|
2782
|
-
let engine;
|
|
2783
|
-
let configList;
|
|
2784
3172
|
return defineEnginePlugin({
|
|
2785
3173
|
name: "core:selectors",
|
|
2786
|
-
|
|
2787
|
-
|
|
3174
|
+
createState: () => ({ definitions: [] }),
|
|
3175
|
+
rawConfigConfigured(config, context) {
|
|
3176
|
+
context.state.definitions = config.selectors?.definitions ?? [];
|
|
2788
3177
|
},
|
|
2789
|
-
configureEngine(
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
3178
|
+
configureEngine(configurator) {
|
|
3179
|
+
const resolver = new SelectorResolver(configurator.onDiagnostic);
|
|
3180
|
+
const acceptedDefinitions = [];
|
|
3181
|
+
for (const definition of configurator.state.definitions) {
|
|
3182
|
+
const resolved = resolveSelectorConfig(definition);
|
|
3183
|
+
if (resolved == null) continue;
|
|
3184
|
+
acceptedDefinitions.push(definition);
|
|
3185
|
+
if (resolved.type === "static") resolver.addStaticRule(resolved.rule);
|
|
3186
|
+
else resolver.addDynamicRule(resolved.rule);
|
|
3187
|
+
}
|
|
3188
|
+
configurator.state.definitions = acceptedDefinitions;
|
|
3189
|
+
configurator.state.resolver = resolver;
|
|
3190
|
+
const declarations = renderSelectorDeclarations(acceptedDefinitions, (value, pattern) => {
|
|
3191
|
+
configurator.onDiagnostic({
|
|
3192
|
+
level: "warning",
|
|
3193
|
+
code: "selector-autocomplete-pattern-mismatch",
|
|
3194
|
+
message: `Selector autocomplete value "${value}" does not match ${pattern}`
|
|
3195
|
+
});
|
|
3196
|
+
});
|
|
3197
|
+
configurator.typegen.add({
|
|
3198
|
+
id: "core:selectors",
|
|
3199
|
+
declarations,
|
|
3200
|
+
selectors: "__PikaSelectors"
|
|
3201
|
+
});
|
|
2811
3202
|
},
|
|
2812
|
-
async transformSelectors(selectors) {
|
|
3203
|
+
async transformSelectors(selectors, context) {
|
|
3204
|
+
const resolver = context.state.resolver;
|
|
3205
|
+
if (resolver == null) return selectors;
|
|
2813
3206
|
const result = [];
|
|
2814
|
-
for (const selector of selectors) result.push(...await
|
|
3207
|
+
for (const selector of selectors) result.push(...await resolver.resolve(selector));
|
|
2815
3208
|
return result;
|
|
2816
3209
|
}
|
|
2817
3210
|
});
|
|
2818
3211
|
}
|
|
2819
3212
|
var SelectorResolver = class extends RecursiveResolver {};
|
|
2820
|
-
/**
|
|
2821
|
-
* Normalizes a `Selector` configuration into a `ResolvedRuleConfig`, a redirect string, or `undefined`.
|
|
2822
|
-
*
|
|
2823
|
-
* @param config - The selector rule configuration to resolve.
|
|
2824
|
-
* @returns A resolved static/dynamic rule config, a redirect string, or `undefined` if the shape is unrecognized.
|
|
2825
|
-
*
|
|
2826
|
-
* @remarks Delegates to the generic `resolveRuleConfig` with `'selector'` as the key name.
|
|
2827
|
-
*
|
|
2828
|
-
* @example
|
|
2829
|
-
* ```ts
|
|
2830
|
-
* const resolved = resolveSelectorConfig(['hover', '$:hover'])
|
|
2831
|
-
* ```
|
|
2832
|
-
*/
|
|
3213
|
+
/** @internal */
|
|
2833
3214
|
function resolveSelectorConfig(config) {
|
|
2834
|
-
return resolveRuleConfig(config
|
|
3215
|
+
return resolveRuleConfig(config);
|
|
2835
3216
|
}
|
|
2836
3217
|
//#endregion
|
|
2837
3218
|
//#region src/plugins/shortcuts.ts
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
3219
|
+
function createStrictShortcutNamespace(definitions) {
|
|
3220
|
+
const staticNames = new Set(definitions.flatMap((definition) => "name" in definition ? [definition.name] : []));
|
|
3221
|
+
const dynamicPatterns = definitions.flatMap((definition) => "pattern" in definition ? [definition.pattern] : []);
|
|
3222
|
+
return new Proxy(Object.create(null), { get(_target, property) {
|
|
3223
|
+
if (typeof property !== "string") return void 0;
|
|
3224
|
+
return staticNames.has(property) || dynamicPatterns.some((pattern) => matchesRulePattern(pattern, property)) ? property : void 0;
|
|
3225
|
+
} });
|
|
3226
|
+
}
|
|
3227
|
+
function renderShortcutDeclarations(definitions, onInvalidAutocomplete, documentation = /* @__PURE__ */ new Map(), bindings = {}) {
|
|
3228
|
+
const explicit = /* @__PURE__ */ new Map();
|
|
3229
|
+
const dynamicTypes = [];
|
|
3230
|
+
for (const definition of definitions) {
|
|
3231
|
+
if ("name" in definition) {
|
|
3232
|
+
explicit.set(definition.name, { description: definition.description });
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
dynamicTypes.push(definition.inputType);
|
|
3236
|
+
for (const value of [definition.autocomplete ?? []].flat()) {
|
|
3237
|
+
if (!matchesRulePattern(definition.pattern, value)) {
|
|
3238
|
+
onInvalidAutocomplete(value, definition.pattern);
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
explicit.set(value, documentation.get(value) ?? { description: definition.description });
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
const lines = ["interface __PikaExplicitShortcuts {"];
|
|
3245
|
+
for (const [name, docs] of [...explicit].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
3246
|
+
lines.push(...renderTypegenJSDoc(docs, bindings, " "));
|
|
3247
|
+
lines.push(` ${JSON.stringify(name)}: string`);
|
|
3248
|
+
}
|
|
3249
|
+
lines.push("}");
|
|
3250
|
+
if (dynamicTypes.length === 0) lines.push("type __PikaShortcuts = __PikaExplicitShortcuts");
|
|
3251
|
+
else {
|
|
3252
|
+
lines.push(`type __PikaDynamicShortcutInput = ${dynamicTypes.join(" | ")}`);
|
|
3253
|
+
lines.push("type __PikaDynamicShortcuts = { [K in __PikaDynamicShortcutInput]: string }");
|
|
3254
|
+
lines.push("type __PikaShortcuts = __PikaExplicitShortcuts & __PikaDynamicShortcuts");
|
|
3255
|
+
}
|
|
3256
|
+
return lines.join("\n");
|
|
3257
|
+
}
|
|
3258
|
+
function resolveShortcutConfigForContext(config, context) {
|
|
3259
|
+
if (context != null && typeof config === "object" && config != null && !Array.isArray(config) && "pattern" in config) return resolveRuleConfig({
|
|
3260
|
+
...config,
|
|
3261
|
+
resolve: (matched) => config.resolve(matched, context)
|
|
3262
|
+
});
|
|
3263
|
+
return resolveRuleConfig(config);
|
|
3264
|
+
}
|
|
3265
|
+
function createShortcutResolver(definitions, onDiagnostic, context) {
|
|
3266
|
+
const resolver = new ShortcutResolver(onDiagnostic);
|
|
3267
|
+
for (const definition of definitions) {
|
|
3268
|
+
const resolved = resolveShortcutConfigForContext(definition, context);
|
|
3269
|
+
if (resolved?.type === "static") resolver.addStaticRule(resolved.rule);
|
|
3270
|
+
else if (resolved?.type === "dynamic") resolver.addDynamicRule(resolved.rule);
|
|
3271
|
+
}
|
|
3272
|
+
return resolver;
|
|
3273
|
+
}
|
|
3274
|
+
function renderPreviewCss(contents) {
|
|
3275
|
+
if (contents.length === 0) return "";
|
|
3276
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
3277
|
+
for (const { selector: rawSelector, property, value } of contents) {
|
|
3278
|
+
const selector = [...rawSelector];
|
|
3279
|
+
if (selector[0]?.startsWith("@layer ")) {
|
|
3280
|
+
const layer = selector.shift().slice(7).trim();
|
|
3281
|
+
if (layer.length > 0) selector.unshift(`@layer ${layer}`);
|
|
3282
|
+
}
|
|
3283
|
+
const renderedSelectors = selector.map((part) => replaceAtomicStyleIdPlaceholder(part, "pika-preview"));
|
|
3284
|
+
if (renderedSelectors.length === 0) continue;
|
|
3285
|
+
let current = blocks;
|
|
3286
|
+
for (let index = 0; index < renderedSelectors.length; index++) {
|
|
3287
|
+
const currentSelector = renderedSelectors[index];
|
|
3288
|
+
const body = current.get(currentSelector) ?? { properties: [] };
|
|
3289
|
+
if (index === renderedSelectors.length - 1) body.properties.push(...value.map((v) => ({
|
|
3290
|
+
property,
|
|
3291
|
+
value: v
|
|
3292
|
+
})));
|
|
3293
|
+
else body.children ??= /* @__PURE__ */ new Map();
|
|
3294
|
+
current.set(currentSelector, body);
|
|
3295
|
+
if (index < renderedSelectors.length - 1) current = body.children;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
return renderCSSStyleBlocks(blocks, true).trim();
|
|
3299
|
+
}
|
|
3300
|
+
async function finalizeShortcutTypegen(engine, definitions, onDiagnostic) {
|
|
3301
|
+
const documentation = /* @__PURE__ */ new Map();
|
|
3302
|
+
const previewAssets = [];
|
|
3303
|
+
const concreteSet = /* @__PURE__ */ new Set();
|
|
3304
|
+
for (const definition of definitions) {
|
|
3305
|
+
if ("pattern" in definition === false) continue;
|
|
3306
|
+
for (const value of [definition.autocomplete ?? []].flat()) {
|
|
3307
|
+
if (!matchesRulePattern(definition.pattern, value)) {
|
|
3308
|
+
onDiagnostic({
|
|
3309
|
+
level: "warning",
|
|
3310
|
+
code: "shortcut-autocomplete-pattern-mismatch",
|
|
3311
|
+
message: `Shortcut autocomplete value "${value}" does not match ${definition.pattern}`
|
|
3312
|
+
});
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
concreteSet.add(value);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
const concrete = [...concreteSet].sort();
|
|
3319
|
+
for (let memberIndex = 0; memberIndex < concrete.length; memberIndex++) {
|
|
3320
|
+
const member = concrete[memberIndex];
|
|
3321
|
+
const owner = definitions.find((definition) => "pattern" in definition && matchesRulePattern(definition.pattern, member));
|
|
3322
|
+
if (owner == null) continue;
|
|
3323
|
+
const images = [];
|
|
3324
|
+
let imageIndex = 0;
|
|
3325
|
+
const context = Object.freeze({ preview: Object.freeze({ image(image) {
|
|
3326
|
+
const assetId = `core:shortcuts:${memberIndex}:image:${imageIndex++}`;
|
|
3327
|
+
previewAssets.push(Object.freeze({
|
|
3328
|
+
id: assetId,
|
|
3329
|
+
content: image.content,
|
|
3330
|
+
mediaType: image.mediaType
|
|
3331
|
+
}));
|
|
3332
|
+
images.push(Object.freeze({
|
|
3333
|
+
assetId,
|
|
3334
|
+
...image.alt == null ? {} : { alt: image.alt }
|
|
3335
|
+
}));
|
|
3336
|
+
} }) });
|
|
3337
|
+
let previewCss;
|
|
3338
|
+
try {
|
|
3339
|
+
let resolutionFailure;
|
|
3340
|
+
const resolved = await createShortcutResolver(definitions, (diagnostic) => {
|
|
3341
|
+
if (diagnostic.code === "resolver-resolution-error") {
|
|
3342
|
+
resolutionFailure ??= diagnostic.cause ?? new Error(diagnostic.message);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
engine.reportDiagnostic(diagnostic);
|
|
3346
|
+
}, context).resolve(member);
|
|
3347
|
+
if (resolutionFailure != null) throw resolutionFailure;
|
|
3348
|
+
const styleItems = resolved.filter((item) => typeof item !== "string");
|
|
3349
|
+
if (styleItems.length > 0) previewCss = renderPreviewCss((await engine.prepareUse(...styleItems)).contents) || void 0;
|
|
3350
|
+
} catch (cause) {
|
|
3351
|
+
onDiagnostic({
|
|
3352
|
+
level: "warning",
|
|
3353
|
+
code: "shortcut-preview-resolution-error",
|
|
3354
|
+
message: `Failed to render Typegen preview for shortcut "${member}": ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
3355
|
+
cause
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
documentation.set(member, Object.freeze({
|
|
3359
|
+
description: owner.description,
|
|
3360
|
+
...previewCss == null ? {} : { previewCss },
|
|
3361
|
+
...images.length === 0 ? {} : { previewImages: Object.freeze(images) }
|
|
3362
|
+
}));
|
|
3363
|
+
}
|
|
3364
|
+
const render = (bindings) => renderShortcutDeclarations(definitions, () => {}, documentation, bindings);
|
|
3365
|
+
setCoreGeneratedTypegenContribution(engine.typegen, "core:shortcuts", {
|
|
3366
|
+
declarations: render({}),
|
|
3367
|
+
renderDeclarations: render,
|
|
3368
|
+
previewAssets
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
/** Built-in shortcut subsystem. Effective raw config is its only semantic ingress. */
|
|
2850
3372
|
function shortcuts() {
|
|
2851
|
-
let engine;
|
|
2852
|
-
let configList;
|
|
2853
3373
|
return defineEnginePlugin({
|
|
2854
3374
|
name: "core:shortcuts",
|
|
2855
|
-
|
|
2856
|
-
|
|
3375
|
+
createState: () => ({ definitions: [] }),
|
|
3376
|
+
rawConfigConfigured(config, context) {
|
|
3377
|
+
context.state.definitions = config.shortcuts?.definitions ?? [];
|
|
2857
3378
|
},
|
|
2858
|
-
configureEngine(
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
return;
|
|
2869
|
-
}
|
|
2870
|
-
if (resolved.type === "static") engine.shortcuts.resolver.addStaticRule(resolved.rule);
|
|
2871
|
-
else engine.shortcuts.resolver.addDynamicRule(resolved.rule);
|
|
2872
|
-
engine.appendAutocomplete({ shortcuts: resolved.autocomplete });
|
|
2873
|
-
});
|
|
2874
|
-
}
|
|
2875
|
-
};
|
|
2876
|
-
engine.shortcuts.add(...configList);
|
|
2877
|
-
engine.shortcuts.resolver.onResolved = (string, type) => {
|
|
2878
|
-
if (type === "dynamic") engine.appendAutocomplete({ shortcuts: string });
|
|
2879
|
-
};
|
|
2880
|
-
const unionType = ["(string & {})", "Autocomplete['Shortcut']"].join(" | ");
|
|
2881
|
-
engine.appendAutocomplete({
|
|
2882
|
-
extraProperties: "__shortcut",
|
|
2883
|
-
properties: { __shortcut: [unionType, `(${unionType})[]`] }
|
|
3379
|
+
configureEngine(configurator) {
|
|
3380
|
+
const acceptedDefinitions = configurator.state.definitions.filter((definition) => resolveShortcutConfig(definition) != null);
|
|
3381
|
+
const resolver = createShortcutResolver(acceptedDefinitions, configurator.onDiagnostic);
|
|
3382
|
+
configurator.state.definitions = acceptedDefinitions;
|
|
3383
|
+
configurator.state.resolver = resolver;
|
|
3384
|
+
configurator.pika.extendStatic("sc", createStrictShortcutNamespace(acceptedDefinitions));
|
|
3385
|
+
configurator.typegen.add({
|
|
3386
|
+
id: "core:shortcuts",
|
|
3387
|
+
declarations: renderShortcutDeclarations(acceptedDefinitions, () => {}),
|
|
3388
|
+
pika: { sc: "__PikaShortcuts" }
|
|
2884
3389
|
});
|
|
3390
|
+
registerCoreEngineFinalizer(configurator.runtime, () => finalizeShortcutTypegen(configurator.runtime, acceptedDefinitions, configurator.onDiagnostic));
|
|
2885
3391
|
},
|
|
2886
|
-
async transformStyleItems(styleItems) {
|
|
3392
|
+
async transformStyleItems(styleItems, context) {
|
|
3393
|
+
const resolver = context.state.resolver;
|
|
3394
|
+
if (resolver == null) return styleItems;
|
|
2887
3395
|
const result = [];
|
|
2888
3396
|
for (const styleItem of styleItems) {
|
|
2889
3397
|
if (typeof styleItem === "string") {
|
|
2890
|
-
result.push(...await
|
|
3398
|
+
result.push(...await resolver.resolve(styleItem));
|
|
2891
3399
|
continue;
|
|
2892
3400
|
}
|
|
2893
3401
|
result.push(styleItem);
|
|
2894
3402
|
}
|
|
2895
3403
|
return result;
|
|
2896
|
-
},
|
|
2897
|
-
async transformStyleDefinitions(styleDefinitions) {
|
|
2898
|
-
const result = [];
|
|
2899
|
-
for (const styleDefinition of styleDefinitions) if ("__shortcut" in styleDefinition) {
|
|
2900
|
-
const { __shortcut, ...rest } = styleDefinition;
|
|
2901
|
-
const explicitImportant = rest.__important ?? null;
|
|
2902
|
-
const applied = [];
|
|
2903
|
-
for (const shortcut of __shortcut == null ? [] : [__shortcut].flat(1)) {
|
|
2904
|
-
const resolved = (await engine.shortcuts.resolver.resolve(shortcut)).filter(isNotString);
|
|
2905
|
-
applied.push(...explicitImportant == null ? resolved : resolved.map((definition) => ({
|
|
2906
|
-
__important: explicitImportant,
|
|
2907
|
-
...definition
|
|
2908
|
-
})));
|
|
2909
|
-
}
|
|
2910
|
-
result.push(...applied, rest);
|
|
2911
|
-
} else result.push(styleDefinition);
|
|
2912
|
-
return result;
|
|
2913
3404
|
}
|
|
2914
3405
|
});
|
|
2915
3406
|
}
|
|
2916
3407
|
var ShortcutResolver = class extends RecursiveResolver {};
|
|
3408
|
+
/** @internal */
|
|
2917
3409
|
function resolveShortcutConfig(config) {
|
|
2918
|
-
return
|
|
3410
|
+
return resolveShortcutConfigForContext(config);
|
|
2919
3411
|
}
|
|
2920
3412
|
//#endregion
|
|
2921
3413
|
//#region src/plugins/variables.ts
|
|
2922
|
-
/**
|
|
2923
|
-
* Built-in engine plugin that provides CSS custom properties (variables) with smart pruning and autocomplete integration.
|
|
2924
|
-
*
|
|
2925
|
-
* @returns An `EnginePlugin` that registers variable definitions, manages a preflight for emitting `:root` / scoped variables, and prunes unused variables from the output.
|
|
2926
|
-
*
|
|
2927
|
-
* @remarks Reads `EngineConfig.variables` during `rawConfigConfigured` and attaches the `engine.variables` management interface during `configureEngine`. A preflight is registered that collects variable references from atomic styles and other preflights, transitively expands dependencies, and emits only used (or safe-listed) variables.
|
|
2928
|
-
*
|
|
2929
|
-
* @example
|
|
2930
|
-
* ```ts
|
|
2931
|
-
* createEngine({ plugins: [variables()] })
|
|
2932
|
-
* ```
|
|
2933
|
-
*/
|
|
2934
|
-
function variables() {
|
|
2935
|
-
let resolveVariables;
|
|
2936
|
-
let rawVariables;
|
|
2937
|
-
let safeSet;
|
|
2938
|
-
return defineEnginePlugin({
|
|
2939
|
-
name: "core:variables",
|
|
2940
|
-
rawConfigConfigured(config, context) {
|
|
2941
|
-
resolveVariables = createResolveVariablesFn({
|
|
2942
|
-
pruneUnused: config.variables?.pruneUnused,
|
|
2943
|
-
onDiagnostic: context?.onDiagnostic
|
|
2944
|
-
});
|
|
2945
|
-
rawVariables = normalizeVariablesConfig(config.variables);
|
|
2946
|
-
safeSet = new Set(config.variables?.safeList ?? []);
|
|
2947
|
-
},
|
|
2948
|
-
configureEngine(engine) {
|
|
2949
|
-
engine.variables = {
|
|
2950
|
-
store: /* @__PURE__ */ new Map(),
|
|
2951
|
-
add: (variables) => {
|
|
2952
|
-
resolveVariables(variables).forEach((resolved) => {
|
|
2953
|
-
const { name, value, autocomplete: { asValueOf, asProperty } } = resolved;
|
|
2954
|
-
const cssProperties = Object.fromEntries(asValueOf.filter((p) => p !== "-").map((p) => [p, `var(${name})`]));
|
|
2955
|
-
engine.appendAutocomplete({
|
|
2956
|
-
cssProperties,
|
|
2957
|
-
extraCssProperties: asProperty ? name : void 0
|
|
2958
|
-
});
|
|
2959
|
-
if (value != null) {
|
|
2960
|
-
const list = engine.variables.store.get(name) ?? [];
|
|
2961
|
-
list.push(resolved);
|
|
2962
|
-
engine.variables.store.set(name, list);
|
|
2963
|
-
}
|
|
2964
|
-
});
|
|
2965
|
-
engine.notifyPreflightUpdated();
|
|
2966
|
-
}
|
|
2967
|
-
};
|
|
2968
|
-
rawVariables.forEach((variables) => engine.variables.add(variables));
|
|
2969
|
-
engine.addPreflight({
|
|
2970
|
-
id: "core:variables",
|
|
2971
|
-
preflight: async (engine, isFormatted, ctx) => {
|
|
2972
|
-
const used = /* @__PURE__ */ new Set();
|
|
2973
|
-
engine.store.atomicStyles.forEach(({ content: { value } }, id) => {
|
|
2974
|
-
if (ctx?.usedAtomicStyleIds != null && ctx.usedAtomicStyleIds.has(id) === false) return;
|
|
2975
|
-
value.flatMap(extractUsedVarNames).forEach((name) => used.add(normalizeVariableName(name)));
|
|
2976
|
-
});
|
|
2977
|
-
const otherPreflights = engine.config.preflights.filter((p) => p.id !== "core:variables");
|
|
2978
|
-
(await Promise.all(otherPreflights.map(({ fn }) => engine.invokePreflight(fn, isFormatted, ctx).catch(() => null)))).forEach((result) => {
|
|
2979
|
-
if (result == null) return;
|
|
2980
|
-
extractUsedVarNamesFromPreflightResult(result).forEach((name) => used.add(name));
|
|
2981
|
-
});
|
|
2982
|
-
const varMap = engine.variables.store;
|
|
2983
|
-
for (const [name, entries] of varMap.entries()) {
|
|
2984
|
-
if (used.has(name)) continue;
|
|
2985
|
-
if (safeSet.has(name) || entries.some((entry) => entry.pruneUnused === false)) used.add(name);
|
|
2986
|
-
}
|
|
2987
|
-
const queue = Array.from(used);
|
|
2988
|
-
while (queue.length > 0) {
|
|
2989
|
-
const name = queue.pop();
|
|
2990
|
-
const entries = varMap.get(name);
|
|
2991
|
-
if (!entries) continue;
|
|
2992
|
-
for (const { value } of entries) {
|
|
2993
|
-
const referencedValue = Array.isArray(value) ? value.join(" ") : String(value);
|
|
2994
|
-
for (const refName of extractUsedVarNames(referencedValue).map(normalizeVariableName)) if (!used.has(refName)) {
|
|
2995
|
-
used.add(refName);
|
|
2996
|
-
queue.push(refName);
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
const usedVariables = Array.from(engine.variables.store.values()).flat().filter(({ name, pruneUnused, value }) => (safeSet.has(name) || pruneUnused === false || used.has(name)) && value != null);
|
|
3001
|
-
const preflightDefinition = {};
|
|
3002
|
-
for (const { name, value, selector: _selector } of usedVariables) {
|
|
3003
|
-
const selector = await engine.pluginHooks.transformSelectors(engine.config.plugins, _selector);
|
|
3004
|
-
let current = preflightDefinition;
|
|
3005
|
-
selector.forEach((s) => {
|
|
3006
|
-
current[s] ||= {};
|
|
3007
|
-
current = current[s];
|
|
3008
|
-
});
|
|
3009
|
-
Object.assign(current, { [name]: value });
|
|
3010
|
-
}
|
|
3011
|
-
return preflightDefinition;
|
|
3012
|
-
}
|
|
3013
|
-
});
|
|
3014
|
-
}
|
|
3015
|
-
});
|
|
3016
|
-
}
|
|
3017
3414
|
function normalizeVariablesConfig(config) {
|
|
3018
3415
|
if (config == null) return [];
|
|
3019
3416
|
const merged = {};
|
|
@@ -3030,49 +3427,236 @@ function mergeVariablesDefinition(target, source) {
|
|
|
3030
3427
|
}
|
|
3031
3428
|
return target;
|
|
3032
3429
|
}
|
|
3430
|
+
function resolveSuggestTargets(asValueOf) {
|
|
3431
|
+
if (asValueOf === false || asValueOf == null) return [];
|
|
3432
|
+
return [...new Set([asValueOf].flat().map(String))];
|
|
3433
|
+
}
|
|
3033
3434
|
function createResolveVariablesFn({ pruneUnused: defaultPruneUnused = true, onDiagnostic = noopDiagnosticHandler } = {}) {
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3435
|
+
const warn = (code, message) => {
|
|
3436
|
+
if (onDiagnostic === noopDiagnosticHandler) log.warn(message);
|
|
3437
|
+
else emitDiagnostic(onDiagnostic, {
|
|
3438
|
+
level: "warning",
|
|
3439
|
+
code,
|
|
3440
|
+
message
|
|
3441
|
+
});
|
|
3442
|
+
};
|
|
3443
|
+
function walk(variables, levels, result) {
|
|
3444
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
3445
|
+
if (key.startsWith("--")) {
|
|
3446
|
+
if (!isPlainObjectRecord(value) || !("external" in value) && !("value" in value)) {
|
|
3447
|
+
warn("variables-invalid-leaf", `Invalid variable leaf for "${key}". Variable leaves must use the canonical object form. Skipping.`);
|
|
3448
|
+
continue;
|
|
3449
|
+
}
|
|
3450
|
+
if (value.external === true) {
|
|
3451
|
+
if (levels.length > 0) {
|
|
3452
|
+
warn("variables-scoped-external", `External variable "${key}" cannot be declared under selector scope "${levels.join(" -> ")}". Skipping.`);
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
result.push({
|
|
3456
|
+
name: key,
|
|
3457
|
+
selector: [":root"],
|
|
3458
|
+
pruneUnused: false,
|
|
3459
|
+
suggest: {
|
|
3460
|
+
asValueOf: resolveSuggestTargets(value.suggest?.asValueOf),
|
|
3461
|
+
asProperty: value.suggest?.asProperty ?? true
|
|
3462
|
+
},
|
|
3463
|
+
description: typeof value.description === "string" ? value.description : void 0,
|
|
3464
|
+
external: true
|
|
3465
|
+
});
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
const leaf = value;
|
|
3469
|
+
result.push({
|
|
3470
|
+
name: key,
|
|
3471
|
+
value: leaf.value,
|
|
3472
|
+
selector: levels.length > 0 ? levels : [":root"],
|
|
3473
|
+
pruneUnused: leaf.pruneUnused ?? defaultPruneUnused,
|
|
3474
|
+
suggest: {
|
|
3475
|
+
asValueOf: resolveSuggestTargets(leaf.suggest?.asValueOf),
|
|
3476
|
+
asProperty: leaf.suggest?.asProperty ?? true
|
|
3477
|
+
},
|
|
3478
|
+
description: leaf.description,
|
|
3479
|
+
external: false
|
|
3055
3480
|
});
|
|
3056
3481
|
continue;
|
|
3057
3482
|
}
|
|
3058
|
-
|
|
3483
|
+
if (!isPlainObjectRecord(value)) {
|
|
3484
|
+
warn("variables-invalid-scope", `Invalid variables scope for selector "${key}". Expected a nested object, received ${typeof value}. Skipping.`);
|
|
3485
|
+
continue;
|
|
3486
|
+
}
|
|
3487
|
+
walk(value, [...levels, key], result);
|
|
3059
3488
|
}
|
|
3060
3489
|
return result;
|
|
3061
3490
|
}
|
|
3062
|
-
return
|
|
3063
|
-
|
|
3064
|
-
|
|
3491
|
+
return (variables) => walk(variables, [], []);
|
|
3492
|
+
}
|
|
3493
|
+
function compareStrings$1(a, b) {
|
|
3494
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
3495
|
+
}
|
|
3496
|
+
function groupVariablesByName(resolved) {
|
|
3497
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3498
|
+
for (const variable of resolved) {
|
|
3499
|
+
const list = groups.get(variable.name) ?? [];
|
|
3500
|
+
list.push(variable);
|
|
3501
|
+
groups.set(variable.name, list);
|
|
3502
|
+
}
|
|
3503
|
+
return groups;
|
|
3504
|
+
}
|
|
3505
|
+
function renderVariablePreview(name, entries) {
|
|
3506
|
+
const emitted = entries.filter((entry) => !entry.external && entry.value != null);
|
|
3507
|
+
if (emitted.length === 0) return void 0;
|
|
3508
|
+
const lines = [];
|
|
3509
|
+
for (const entry of emitted) {
|
|
3510
|
+
entry.selector.forEach((selector, depth) => {
|
|
3511
|
+
lines.push(`${" ".repeat(depth)}${selector} {`);
|
|
3512
|
+
});
|
|
3513
|
+
const values = Array.isArray(entry.value) ? [...entry.value[1].map(String), String(entry.value[0])] : [String(entry.value)];
|
|
3514
|
+
const propertyIndent = " ".repeat(entry.selector.length);
|
|
3515
|
+
for (const value of values) lines.push(`${propertyIndent}${name}: ${value};`);
|
|
3516
|
+
for (let depth = entry.selector.length - 1; depth >= 0; depth--) lines.push(`${" ".repeat(depth)}}`);
|
|
3517
|
+
}
|
|
3518
|
+
return lines.join("\n");
|
|
3519
|
+
}
|
|
3520
|
+
function renderVariableDeclarations(resolved) {
|
|
3521
|
+
const groups = groupVariablesByName(resolved);
|
|
3522
|
+
const orderedNames = [...groups.keys()].sort(compareStrings$1);
|
|
3523
|
+
const lines = ["interface __PikaVariables {"];
|
|
3524
|
+
for (const name of orderedNames) {
|
|
3525
|
+
const entries = groups.get(name);
|
|
3526
|
+
const descriptions = [...new Set(entries.flatMap((entry) => entry.description == null ? [] : [entry.description]))].sort(compareStrings$1);
|
|
3527
|
+
const reference = `var(${name})`;
|
|
3528
|
+
lines.push(...renderTypegenJSDoc({
|
|
3529
|
+
description: [...descriptions, `CSS variable reference: ${reference}`].join("\n\n"),
|
|
3530
|
+
previewCss: renderVariablePreview(name, entries)
|
|
3531
|
+
}, {}, " "));
|
|
3532
|
+
lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(reference)}`);
|
|
3533
|
+
}
|
|
3534
|
+
lines.push("}");
|
|
3535
|
+
lines.push("interface __PikaVariableProperties {");
|
|
3536
|
+
for (const name of orderedNames) {
|
|
3537
|
+
const entries = groups.get(name);
|
|
3538
|
+
if (!entries.some((entry) => entry.suggest.asProperty)) continue;
|
|
3539
|
+
const descriptions = [...new Set(entries.flatMap((entry) => entry.description == null ? [] : [entry.description]))].sort(compareStrings$1);
|
|
3540
|
+
lines.push(...renderTypegenJSDoc({ description: descriptions.join("\n\n") || void 0 }, {}, " "));
|
|
3541
|
+
lines.push(` ${JSON.stringify(name)}?: string | [value: string, fallback: string[]] | null | undefined`);
|
|
3542
|
+
}
|
|
3543
|
+
lines.push("}");
|
|
3544
|
+
const valuesByTarget = /* @__PURE__ */ new Map();
|
|
3545
|
+
for (const [name, entries] of groups) {
|
|
3546
|
+
const reference = `var(${name})`;
|
|
3547
|
+
for (const target of entries.flatMap((entry) => entry.suggest.asValueOf)) {
|
|
3548
|
+
const values = valuesByTarget.get(target) ?? /* @__PURE__ */ new Set();
|
|
3549
|
+
values.add(reference);
|
|
3550
|
+
valuesByTarget.set(target, values);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
lines.push("interface __PikaVariablePropertyValues {");
|
|
3554
|
+
for (const target of [...valuesByTarget.keys()].sort(compareStrings$1)) {
|
|
3555
|
+
const values = [...valuesByTarget.get(target)].sort(compareStrings$1).map((value) => JSON.stringify(value));
|
|
3556
|
+
lines.push(` ${JSON.stringify(target)}: ${values.join(" | ")}`);
|
|
3557
|
+
}
|
|
3558
|
+
lines.push("}");
|
|
3559
|
+
return lines.join("\n");
|
|
3065
3560
|
}
|
|
3066
|
-
function
|
|
3067
|
-
const
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3561
|
+
function collectAtomicVariableUsage(engine, usedAtomicStyleIds) {
|
|
3562
|
+
const used = /* @__PURE__ */ new Set();
|
|
3563
|
+
engine.store.atomicStyles.forEach(({ content: { value } }, id) => {
|
|
3564
|
+
if (usedAtomicStyleIds != null && usedAtomicStyleIds.has(id) === false) return;
|
|
3565
|
+
value.flatMap(extractUsedVarNames).map(normalizeVariableName).forEach((name) => used.add(name));
|
|
3566
|
+
});
|
|
3567
|
+
return used;
|
|
3568
|
+
}
|
|
3569
|
+
function expandVariableUsage(used, store) {
|
|
3570
|
+
const queue = Array.from(used);
|
|
3571
|
+
while (queue.length > 0) {
|
|
3572
|
+
const name = queue.pop();
|
|
3573
|
+
const entries = store.get(name);
|
|
3574
|
+
if (entries == null) continue;
|
|
3575
|
+
for (const { value } of entries) {
|
|
3576
|
+
const referencedValue = Array.isArray(value) ? value.join(" ") : String(value);
|
|
3577
|
+
for (const refName of extractUsedVarNames(referencedValue).map(normalizeVariableName)) {
|
|
3578
|
+
if (used.has(refName)) continue;
|
|
3579
|
+
used.add(refName);
|
|
3580
|
+
queue.push(refName);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
return used;
|
|
3585
|
+
}
|
|
3586
|
+
/** Built-in CSS variable subsystem with config-only semantic ingress. */
|
|
3587
|
+
function variables() {
|
|
3588
|
+
return defineEnginePlugin({
|
|
3589
|
+
name: "core:variables",
|
|
3590
|
+
createState: () => ({
|
|
3591
|
+
definitions: [],
|
|
3592
|
+
defaultPruneUnused: true,
|
|
3593
|
+
safeSet: /* @__PURE__ */ new Set(),
|
|
3594
|
+
resolved: [],
|
|
3595
|
+
store: /* @__PURE__ */ new Map()
|
|
3596
|
+
}),
|
|
3597
|
+
rawConfigConfigured(config, context) {
|
|
3598
|
+
context.state.definitions = normalizeVariablesConfig(config.variables);
|
|
3599
|
+
context.state.defaultPruneUnused = config.variables?.pruneUnused ?? true;
|
|
3600
|
+
context.state.safeSet = new Set(config.variables?.safeList ?? []);
|
|
3601
|
+
},
|
|
3602
|
+
configureEngine(configurator) {
|
|
3603
|
+
const engine = configurator.runtime;
|
|
3604
|
+
const resolveVariables = createResolveVariablesFn({
|
|
3605
|
+
pruneUnused: configurator.state.defaultPruneUnused,
|
|
3606
|
+
onDiagnostic: configurator.onDiagnostic
|
|
3607
|
+
});
|
|
3608
|
+
const resolved = configurator.state.definitions.flatMap(resolveVariables);
|
|
3609
|
+
configurator.state.resolved = resolved;
|
|
3610
|
+
configurator.state.store.clear();
|
|
3611
|
+
for (const variable of resolved) {
|
|
3612
|
+
if (variable.external || variable.value == null) continue;
|
|
3613
|
+
const list = configurator.state.store.get(variable.name) ?? [];
|
|
3614
|
+
list.push(variable);
|
|
3615
|
+
configurator.state.store.set(variable.name, list);
|
|
3616
|
+
}
|
|
3617
|
+
const groups = groupVariablesByName(resolved);
|
|
3618
|
+
const namespace = Object.freeze(Object.fromEntries([...groups.keys()].sort(compareStrings$1).map((name) => [name, `var(${name})`])));
|
|
3619
|
+
configurator.pika.extendStatic("var", namespace);
|
|
3620
|
+
configurator.typegen.add({
|
|
3621
|
+
id: "core:variables",
|
|
3622
|
+
declarations: renderVariableDeclarations(resolved),
|
|
3623
|
+
pika: { var: "__PikaVariables" },
|
|
3624
|
+
cssProperties: "__PikaVariableProperties",
|
|
3625
|
+
cssPropertyValues: "__PikaVariablePropertyValues"
|
|
3626
|
+
});
|
|
3627
|
+
const state = configurator.state;
|
|
3628
|
+
engine.getUsedVariableNames = () => new Set(expandVariableUsage(collectAtomicVariableUsage(engine), state.store));
|
|
3629
|
+
engine.addPreflight({
|
|
3630
|
+
id: "core:variables",
|
|
3631
|
+
preflight: async (engine, isFormatted, ctx) => {
|
|
3632
|
+
const used = collectAtomicVariableUsage(engine, ctx?.usedAtomicStyleIds);
|
|
3633
|
+
const otherPreflights = engine.config.preflights.filter((p) => p.id !== "core:variables");
|
|
3634
|
+
(await Promise.all(otherPreflights.map(({ fn }) => engine.invokePreflight(fn, isFormatted, ctx).catch(() => null)))).forEach((result) => {
|
|
3635
|
+
if (result == null) return;
|
|
3636
|
+
extractUsedVarNamesFromPreflightResult(result).forEach((name) => used.add(name));
|
|
3637
|
+
});
|
|
3638
|
+
const varMap = state.store;
|
|
3639
|
+
for (const [name, entries] of varMap.entries()) {
|
|
3640
|
+
if (used.has(name)) continue;
|
|
3641
|
+
if (state.safeSet.has(name) || entries.some((entry) => entry.pruneUnused === false)) used.add(name);
|
|
3642
|
+
}
|
|
3643
|
+
expandVariableUsage(used, varMap);
|
|
3644
|
+
const usedVariables = Array.from(varMap.values()).flat().filter(({ name, pruneUnused, value }) => (state.safeSet.has(name) || pruneUnused === false || used.has(name)) && value != null);
|
|
3645
|
+
const preflightDefinition = {};
|
|
3646
|
+
for (const { name, value, selector: rawSelector } of usedVariables) {
|
|
3647
|
+
const selector = await engine.pluginHooks.transformSelectors(engine.config.plugins, rawSelector);
|
|
3648
|
+
let current = preflightDefinition;
|
|
3649
|
+
for (const item of selector) {
|
|
3650
|
+
current[item] ||= {};
|
|
3651
|
+
current = current[item];
|
|
3652
|
+
}
|
|
3653
|
+
Object.assign(current, { [name]: value });
|
|
3654
|
+
}
|
|
3655
|
+
return preflightDefinition;
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3073
3659
|
});
|
|
3074
|
-
if (targets.has("*")) return ["*"];
|
|
3075
|
-
return [...targets];
|
|
3076
3660
|
}
|
|
3077
3661
|
const VAR_NAME_RE = /var\(\s*(--[\w-]+)/g;
|
|
3078
3662
|
/**
|
|
@@ -3178,6 +3762,26 @@ const DEFAULT_LAYERS = {
|
|
|
3178
3762
|
[DEFAULT_PREFLIGHTS_LAYER]: 1,
|
|
3179
3763
|
[DEFAULT_UTILITIES_LAYER]: 10
|
|
3180
3764
|
};
|
|
3765
|
+
const engineInitializationStates = /* @__PURE__ */ new WeakMap();
|
|
3766
|
+
const defaultAtomicStyleIdStrategy = ({ index, prefix }) => `${prefix}${numberToChars(index)}`;
|
|
3767
|
+
function snapshotEngineHostContext(host) {
|
|
3768
|
+
const snapshot = {};
|
|
3769
|
+
if (host?.projectRoot != null) Object.assign(snapshot, { projectRoot: host.projectRoot });
|
|
3770
|
+
if (host?.privateCssDiscriminator != null) Object.assign(snapshot, { privateCssDiscriminator: host.privateCssDiscriminator });
|
|
3771
|
+
return Object.freeze(snapshot);
|
|
3772
|
+
}
|
|
3773
|
+
function snapshotConfigDependencies(state) {
|
|
3774
|
+
return Object.freeze([...state.dependencies.keys()].sort().map((key) => Object.freeze({ ...state.dependencies.get(key) })));
|
|
3775
|
+
}
|
|
3776
|
+
async function finalizeEngineInitialization(engine) {
|
|
3777
|
+
const state = engineInitializationStates.get(engine);
|
|
3778
|
+
await runCoreEngineFinalizers(engine);
|
|
3779
|
+
validateTypegenPikaOwners(engine.typegen, (root) => getPikaStaticOwner(engine.pika, root));
|
|
3780
|
+
finalizePikaManager(engine.pika);
|
|
3781
|
+
finalizeTypegenManager(engine.typegen);
|
|
3782
|
+
state.finalizedDependencies = snapshotConfigDependencies(state);
|
|
3783
|
+
state.finalized = true;
|
|
3784
|
+
}
|
|
3181
3785
|
/**
|
|
3182
3786
|
* Creates and initializes a PikaCSS engine with the given configuration.
|
|
3183
3787
|
*
|
|
@@ -3187,19 +3791,28 @@ const DEFAULT_LAYERS = {
|
|
|
3187
3791
|
*
|
|
3188
3792
|
* @remarks Core plugins (`important`, `variables`, `keyframes`, `selectors`, `shortcuts`) are prepended automatically. The function resolves plugins, runs all configuration hooks in sequence, and returns the ready-to-use engine.
|
|
3189
3793
|
*
|
|
3794
|
+
* The caller-owned `config` graph is treated as immutable input (#117): the engine clones it into an engine-local working copy before any plugin configuration hook runs, so plugin hooks that mutate their config (`config.layers ??= {}` and friends) never write back into caller-owned objects, and the same config object can be reused across sequential or concurrent `createEngine()` calls without accumulating setup mutations. Ordinary config data (plain objects/arrays, `Map`/`Set` contents, `Date`, `RegExp`) is recursively isolated — module-augmented plugin fields included; functions and other opaque class instances keep their identity and are treated as immutable values; the `plugins` array is copied while plugin definition objects keep their identity (#116).
|
|
3795
|
+
*
|
|
3190
3796
|
* @example
|
|
3191
3797
|
* ```ts
|
|
3192
3798
|
* const engine = await createEngine({ prefix: 'pk-', plugins: [myPlugin()] })
|
|
3193
3799
|
* ```
|
|
3194
3800
|
*/
|
|
3195
3801
|
async function createEngine(config = {}, options = {}) {
|
|
3802
|
+
config = cloneEngineConfig(config);
|
|
3196
3803
|
const hostOnDiagnostic = options.onDiagnostic ?? noopDiagnosticHandler;
|
|
3197
3804
|
const onDiagnostic = (diagnostic) => emitDiagnostic(hostOnDiagnostic, diagnostic);
|
|
3198
|
-
const
|
|
3805
|
+
const host = snapshotEngineHostContext(options.host);
|
|
3806
|
+
const atomicStyleIdStrategy = options.atomicStyleIdStrategy ?? defaultAtomicStyleIdStrategy;
|
|
3807
|
+
const pluginHooks = createEngineHooks({
|
|
3808
|
+
onDiagnostic,
|
|
3809
|
+
host
|
|
3810
|
+
});
|
|
3199
3811
|
log.debug("Creating engine with config:", config);
|
|
3200
3812
|
const corePlugins = [
|
|
3201
3813
|
variables(),
|
|
3202
3814
|
keyframes(),
|
|
3815
|
+
layers(),
|
|
3203
3816
|
selectors(),
|
|
3204
3817
|
shortcuts(),
|
|
3205
3818
|
important()
|
|
@@ -3216,20 +3829,18 @@ async function createEngine(config = {}, options = {}) {
|
|
|
3216
3829
|
let resolvedConfig = await resolveEngineConfig(config);
|
|
3217
3830
|
log.debug("Engine config resolved with prefix:", resolvedConfig.prefix);
|
|
3218
3831
|
resolvedConfig = await pluginHooks.configureResolvedConfig(resolvedConfig.plugins, resolvedConfig);
|
|
3219
|
-
let engine = new Engine(resolvedConfig, hostOnDiagnostic, pluginHooks);
|
|
3220
|
-
engine.
|
|
3221
|
-
extraProperties: "__layer",
|
|
3222
|
-
properties: { __layer: "Autocomplete['Layer']" }
|
|
3223
|
-
});
|
|
3832
|
+
let engine = new Engine(resolvedConfig, hostOnDiagnostic, pluginHooks, atomicStyleIdStrategy);
|
|
3833
|
+
engineInitializationStates.get(engine).onConfigDependency = options.onConfigDependency;
|
|
3224
3834
|
log.debug("Engine instance created");
|
|
3225
3835
|
engine = await pluginHooks.configureEngine(engine.config.plugins, engine);
|
|
3836
|
+
await finalizeEngineInitialization(engine);
|
|
3226
3837
|
log.debug("Engine initialized successfully");
|
|
3227
3838
|
return engine;
|
|
3228
3839
|
}
|
|
3229
3840
|
/**
|
|
3230
3841
|
* The PikaCSS engine: manages atomic style resolution, rendering, preflights, and plugin hooks.
|
|
3231
3842
|
*
|
|
3232
|
-
* @remarks Constructed via `createEngine()`. Holds the resolved configuration, the atomic style store, and exposes methods for processing style items (`use`), rendering CSS output (`renderPreflights`, `renderAtomicStyles`, `renderLayerOrderDeclaration`), and managing runtime extensions (`addPreflight`, `
|
|
3843
|
+
* @remarks Constructed via `createEngine()`. Holds the resolved configuration, the atomic style store, and exposes methods for processing style items (`use`), rendering CSS output (`renderPreflights`, `renderAtomicStyles`, `renderLayerOrderDeclaration`), and managing runtime extensions (`addPreflight`, `appendCssImport`).
|
|
3233
3844
|
*
|
|
3234
3845
|
* @example
|
|
3235
3846
|
* ```ts
|
|
@@ -3245,16 +3856,21 @@ var Engine = class {
|
|
|
3245
3856
|
onDiagnostic;
|
|
3246
3857
|
/** Reference to the instance-scoped plugin hook dispatcher. */
|
|
3247
3858
|
pluginHooks;
|
|
3859
|
+
/** Finalized/read-side first-level Pika static authoring extension registry. */
|
|
3860
|
+
pika;
|
|
3861
|
+
/** Finalized/read-side Typegen semantic registry. */
|
|
3862
|
+
typegen;
|
|
3248
3863
|
/** The extraction function that decomposes style definitions into atomic style contents. */
|
|
3249
3864
|
extract;
|
|
3250
3865
|
/** The engine's runtime store holding registered atomic styles and their ID mappings. */
|
|
3251
3866
|
store = createEngineStore();
|
|
3252
|
-
/**
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3867
|
+
/** Finalized external file and directory-membership dependencies for this engine. */
|
|
3868
|
+
get configDependencies() {
|
|
3869
|
+
const state = engineInitializationStates.get(this);
|
|
3870
|
+
return state.finalizedDependencies ?? snapshotConfigDependencies(state);
|
|
3871
|
+
}
|
|
3872
|
+
/** Engine-owned atomic-style ID allocation strategy. */
|
|
3873
|
+
#atomicStyleIdStrategy;
|
|
3258
3874
|
/**
|
|
3259
3875
|
* Creates an engine instance from a resolved configuration.
|
|
3260
3876
|
*
|
|
@@ -3267,11 +3883,18 @@ var Engine = class {
|
|
|
3267
3883
|
* const engine = new Engine(resolvedConfig)
|
|
3268
3884
|
* ```
|
|
3269
3885
|
*/
|
|
3270
|
-
constructor(config, onDiagnostic = noopDiagnosticHandler, pluginHooks) {
|
|
3886
|
+
constructor(config, onDiagnostic = noopDiagnosticHandler, pluginHooks, atomicStyleIdStrategy = defaultAtomicStyleIdStrategy) {
|
|
3887
|
+
engineInitializationStates.set(this, {
|
|
3888
|
+
finalized: false,
|
|
3889
|
+
dependencies: /* @__PURE__ */ new Map()
|
|
3890
|
+
});
|
|
3891
|
+
this.#atomicStyleIdStrategy = atomicStyleIdStrategy;
|
|
3271
3892
|
const safeOnDiagnostic = (diagnostic) => emitDiagnostic(onDiagnostic, diagnostic);
|
|
3272
3893
|
this.config = config;
|
|
3273
3894
|
this.onDiagnostic = safeOnDiagnostic;
|
|
3274
3895
|
this.pluginHooks = pluginHooks ?? createEngineHooks({ onDiagnostic: safeOnDiagnostic });
|
|
3896
|
+
this.pika = createPikaManager();
|
|
3897
|
+
this.typegen = createTypegenManager();
|
|
3275
3898
|
this.extract = createExtractFn({
|
|
3276
3899
|
defaultSelector: this.config.defaultSelector,
|
|
3277
3900
|
transformSelectors: (selectors) => this.pluginHooks.transformSelectors(this.config.plugins, selectors),
|
|
@@ -3312,19 +3935,34 @@ var Engine = class {
|
|
|
3312
3935
|
return invocation;
|
|
3313
3936
|
}
|
|
3314
3937
|
/**
|
|
3315
|
-
* Registers
|
|
3938
|
+
* Registers a file dependency during Engine initialization.
|
|
3316
3939
|
*
|
|
3317
|
-
* @param path -
|
|
3318
|
-
*
|
|
3319
|
-
* @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.
|
|
3320
|
-
*
|
|
3321
|
-
* @example
|
|
3322
|
-
* ```ts
|
|
3323
|
-
* engine.addConfigDependency('/project/design.md')
|
|
3324
|
-
* ```
|
|
3940
|
+
* @param path - File path whose content/existence participates in Engine configuration semantics. Missing files are allowed.
|
|
3941
|
+
* @throws If registration is attempted after Engine finalization.
|
|
3325
3942
|
*/
|
|
3326
3943
|
addConfigDependency(path) {
|
|
3327
|
-
this
|
|
3944
|
+
this.#registerConfigDependency("file", path);
|
|
3945
|
+
}
|
|
3946
|
+
/**
|
|
3947
|
+
* Registers a direct directory-membership dependency during Engine initialization.
|
|
3948
|
+
*
|
|
3949
|
+
* @param path - Directory path whose direct member create/delete/rename events invalidate Engine configuration semantics.
|
|
3950
|
+
* @throws If registration is attempted after Engine finalization.
|
|
3951
|
+
*/
|
|
3952
|
+
addConfigDirectoryMembershipDependency(path) {
|
|
3953
|
+
this.#registerConfigDependency("directory-membership", path);
|
|
3954
|
+
}
|
|
3955
|
+
#registerConfigDependency(type, path) {
|
|
3956
|
+
const state = engineInitializationStates.get(this);
|
|
3957
|
+
if (state == null || state.finalized) throw new Error("Engine config dependencies are finalized and cannot be modified");
|
|
3958
|
+
const key = `${type === "file" ? "0" : "1"}\0${path}`;
|
|
3959
|
+
if (state.dependencies.has(key)) return;
|
|
3960
|
+
const dependency = Object.freeze({
|
|
3961
|
+
type,
|
|
3962
|
+
path
|
|
3963
|
+
});
|
|
3964
|
+
state.dependencies.set(key, dependency);
|
|
3965
|
+
state.onConfigDependency?.(dependency);
|
|
3328
3966
|
}
|
|
3329
3967
|
/**
|
|
3330
3968
|
* Fires the `preflightUpdated` hook to notify plugins that preflight content has changed.
|
|
@@ -3345,7 +3983,7 @@ var Engine = class {
|
|
|
3345
3983
|
*
|
|
3346
3984
|
* @param atomicStyle - The atomic style that was just added to the store.
|
|
3347
3985
|
*
|
|
3348
|
-
* @remarks Called automatically by `
|
|
3986
|
+
* @remarks Called automatically by `commitUse()` when a previously unseen atomic style is registered. This is a committed notification: the style's ID, cache keys, and store indices are already established, so mutating the payload is unsupported — plugins that need to transform styles must use the provisional hooks (`transformStyleItems`, `transformStyleDefinitions`, `transformSelectors`, `transformStyleContents`) instead (#114).
|
|
3349
3987
|
*
|
|
3350
3988
|
* @example
|
|
3351
3989
|
* ```ts
|
|
@@ -3356,35 +3994,6 @@ var Engine = class {
|
|
|
3356
3994
|
this.pluginHooks.atomicStyleAdded(this.config.plugins, atomicStyle);
|
|
3357
3995
|
}
|
|
3358
3996
|
/**
|
|
3359
|
-
* Fires the `autocompleteConfigUpdated` hook to notify plugins that autocomplete entries changed.
|
|
3360
|
-
*
|
|
3361
|
-
*
|
|
3362
|
-
* @remarks Called automatically after `appendAutocomplete` when the contribution modifies the resolved autocomplete config.
|
|
3363
|
-
*
|
|
3364
|
-
* @example
|
|
3365
|
-
* ```ts
|
|
3366
|
-
* engine.notifyAutocompleteConfigUpdated()
|
|
3367
|
-
* ```
|
|
3368
|
-
*/
|
|
3369
|
-
notifyAutocompleteConfigUpdated() {
|
|
3370
|
-
this.pluginHooks.autocompleteConfigUpdated(this.config.plugins);
|
|
3371
|
-
}
|
|
3372
|
-
/**
|
|
3373
|
-
* Merges an autocomplete contribution into the resolved autocomplete config.
|
|
3374
|
-
*
|
|
3375
|
-
* @param contribution - The autocomplete entries to append (selectors, properties, CSS properties, etc.).
|
|
3376
|
-
*
|
|
3377
|
-
* @remarks Delegates to the `appendAutocomplete` utility and fires `autocompleteConfigUpdated` if the config was actually modified.
|
|
3378
|
-
*
|
|
3379
|
-
* @example
|
|
3380
|
-
* ```ts
|
|
3381
|
-
* engine.appendAutocomplete({ selectors: 'hover', cssProperties: { color: 'red' } })
|
|
3382
|
-
* ```
|
|
3383
|
-
*/
|
|
3384
|
-
appendAutocomplete(contribution) {
|
|
3385
|
-
if (appendAutocomplete(this.config, contribution)) this.notifyAutocompleteConfigUpdated();
|
|
3386
|
-
}
|
|
3387
|
-
/**
|
|
3388
3997
|
* Appends a CSS `@import` statement to the preflight output.
|
|
3389
3998
|
*
|
|
3390
3999
|
* @param cssImport - The raw `@import` string (a trailing semicolon is appended if missing).
|
|
@@ -3421,43 +4030,95 @@ var Engine = class {
|
|
|
3421
4030
|
this.notifyPreflightUpdated();
|
|
3422
4031
|
}
|
|
3423
4032
|
/**
|
|
3424
|
-
*
|
|
4033
|
+
* Provisionally resolves style items into a commit-ready plan without touching committed engine state.
|
|
3425
4034
|
*
|
|
3426
4035
|
* @param itemList - Style items to process: string references (shortcuts) and/or style definition objects.
|
|
3427
|
-
* @returns
|
|
4036
|
+
* @returns A promise of the {@link StyleUsePlan} to pass to `commitUse()`.
|
|
3428
4037
|
*
|
|
3429
|
-
* @remarks
|
|
4038
|
+
* @remarks
|
|
4039
|
+
* Runs the full provisional pipeline: `transformStyleItems`, extraction
|
|
4040
|
+
* (`transformStyleDefinitions`/`transformSelectors`), normalization, and the
|
|
4041
|
+
* normalized-content seam `transformStyleContents`. It allocates no atomic
|
|
4042
|
+
* style IDs, mutates no `EngineStore` state, and fires no committed
|
|
4043
|
+
* notifications — a rejection anywhere leaves the engine exactly as it was.
|
|
4044
|
+
* Plans deliberately carry no IDs: reuse-vs-fresh decisions read live store
|
|
4045
|
+
* state and are only valid inside `commitUse()` (#114).
|
|
3430
4046
|
*
|
|
3431
4047
|
* @example
|
|
3432
4048
|
* ```ts
|
|
3433
|
-
* const
|
|
4049
|
+
* const plan = await engine.prepareUse({ color: 'red' })
|
|
4050
|
+
* const ids = engine.commitUse(plan)
|
|
3434
4051
|
* ```
|
|
3435
4052
|
*/
|
|
3436
|
-
async
|
|
3437
|
-
log.debug(`
|
|
4053
|
+
async prepareUse(...itemList) {
|
|
4054
|
+
log.debug(`Preparing ${itemList.length} style items`);
|
|
3438
4055
|
const { unknown, contents } = await resolveStyleItemList({
|
|
3439
4056
|
itemList,
|
|
3440
4057
|
transformStyleItems: (styleItems) => this.pluginHooks.transformStyleItems(this.config.plugins, styleItems),
|
|
3441
4058
|
extractStyleDefinition: (styleDefinition) => this.extract(styleDefinition)
|
|
3442
4059
|
});
|
|
4060
|
+
return {
|
|
4061
|
+
unknown,
|
|
4062
|
+
contents: optimizeAtomicStyleContents(await this.pluginHooks.transformStyleContents(this.config.plugins, contents))
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
/**
|
|
4066
|
+
* Commits a prepared plan: allocates/reuses atomic style IDs and registers new styles in the store.
|
|
4067
|
+
*
|
|
4068
|
+
* @param plan - A plan produced by `prepareUse()`.
|
|
4069
|
+
* @returns An array containing any unresolved string references first, followed by atomic style IDs in resolution order.
|
|
4070
|
+
*
|
|
4071
|
+
* @remarks
|
|
4072
|
+
* This is the short, mutation-critical section and MUST stay synchronous:
|
|
4073
|
+
* integration layers commit whole modules inside a revision/epoch-checked
|
|
4074
|
+
* synchronous block, so an `await` here would reopen the stale-commit race
|
|
4075
|
+
* (#114). `atomicStyleAdded` fires per newly registered style as a committed
|
|
4076
|
+
* notification; a throwing observer is reported through the diagnostic
|
|
4077
|
+
* context but never rolls back the already-committed registration.
|
|
4078
|
+
*
|
|
4079
|
+
* @example
|
|
4080
|
+
* ```ts
|
|
4081
|
+
* const ids = engine.commitUse(await engine.prepareUse({ color: 'red' }))
|
|
4082
|
+
* ```
|
|
4083
|
+
*/
|
|
4084
|
+
commitUse(plan) {
|
|
3443
4085
|
const resolvedIds = [];
|
|
3444
4086
|
const resolvedIdsByBaseKey = /* @__PURE__ */ new Map();
|
|
3445
|
-
for (const content of contents) {
|
|
4087
|
+
for (const content of plan.contents) {
|
|
3446
4088
|
const { id, atomicStyle } = resolveAtomicStyle({
|
|
3447
4089
|
content,
|
|
3448
4090
|
prefix: this.config.prefix,
|
|
3449
4091
|
store: this.store,
|
|
3450
|
-
resolvedIdsByBaseKey
|
|
4092
|
+
resolvedIdsByBaseKey,
|
|
4093
|
+
atomicStyleIdStrategy: this.#atomicStyleIdStrategy
|
|
3451
4094
|
});
|
|
3452
4095
|
resolvedIds.push(id);
|
|
3453
4096
|
resolvedIdsByBaseKey.set(getAtomicStyleBaseKey(content), id);
|
|
3454
4097
|
if (atomicStyle != null) {
|
|
3455
4098
|
log.debug(`Atomic style added: ${id}`);
|
|
3456
|
-
|
|
4099
|
+
try {
|
|
4100
|
+
this.notifyAtomicStyleAdded(atomicStyle);
|
|
4101
|
+
} catch {}
|
|
3457
4102
|
}
|
|
3458
4103
|
}
|
|
3459
|
-
log.debug(`Resolved ${resolvedIds.length} atomic styles, ${unknown.size} unknown items`);
|
|
3460
|
-
return [...unknown, ...resolvedIds];
|
|
4104
|
+
log.debug(`Resolved ${resolvedIds.length} atomic styles, ${plan.unknown.size} unknown items`);
|
|
4105
|
+
return [...plan.unknown, ...resolvedIds];
|
|
4106
|
+
}
|
|
4107
|
+
/**
|
|
4108
|
+
* Processes style items through the plugin pipeline and registers the resulting atomic styles in the store.
|
|
4109
|
+
*
|
|
4110
|
+
* @param itemList - Style items to process: string references (shortcuts) and/or style definition objects.
|
|
4111
|
+
* @returns An array containing any unresolved string references first, followed by atomic style IDs in resolution order.
|
|
4112
|
+
*
|
|
4113
|
+
* @remarks Equivalent to `commitUse(await prepareUse(...itemList))` — the convenience path for direct consumers. Integration layers that need whole-module transactionality call the two phases separately (#114).
|
|
4114
|
+
*
|
|
4115
|
+
* @example
|
|
4116
|
+
* ```ts
|
|
4117
|
+
* const ids = await engine.use({ color: 'red' }, { padding: '1rem' })
|
|
4118
|
+
* ```
|
|
4119
|
+
*/
|
|
4120
|
+
async use(...itemList) {
|
|
4121
|
+
return this.commitUse(await this.prepareUse(...itemList));
|
|
3461
4122
|
}
|
|
3462
4123
|
/**
|
|
3463
4124
|
* Renders all registered preflight definitions into a CSS string.
|
|
@@ -3519,12 +4180,11 @@ var Engine = class {
|
|
|
3519
4180
|
* Renders atomic styles into a CSS string, optionally filtered by ID and grouped by layer.
|
|
3520
4181
|
*
|
|
3521
4182
|
* @param isFormatted - Whether to produce human-readable CSS with newlines and indentation.
|
|
3522
|
-
* @param options - Optional filtering: `atomicStyleIds` to render a subset
|
|
4183
|
+
* @param options - Optional filtering: `atomicStyleIds` to render a subset.
|
|
3523
4184
|
* @param options.atomicStyleIds - Specific atomic style IDs to render instead of the full store.
|
|
3524
|
-
* @param options.isPreview - Whether to keep placeholder IDs instead of substituting real class names.
|
|
3525
4185
|
* @returns The rendered atomic-style CSS.
|
|
3526
4186
|
*
|
|
3527
|
-
* @remarks Styles are sorted by rendering weight (selector specificity depth), grouped into configured `@layer` blocks, and rendered.
|
|
4187
|
+
* @remarks Styles are sorted by rendering weight (selector specificity depth), grouped into configured `@layer` blocks, and rendered.
|
|
3528
4188
|
*
|
|
3529
4189
|
* @example
|
|
3530
4190
|
* ```ts
|
|
@@ -3533,13 +4193,12 @@ var Engine = class {
|
|
|
3533
4193
|
*/
|
|
3534
4194
|
async renderAtomicStyles(isFormatted, options = {}) {
|
|
3535
4195
|
log.debug("Rendering atomic styles...");
|
|
3536
|
-
const { atomicStyleIds = null
|
|
4196
|
+
const { atomicStyleIds = null } = options;
|
|
3537
4197
|
const atomicStyles = atomicStyleIds == null ? [...this.store.atomicStyles.values()] : atomicStyleIds.map((id) => this.store.atomicStyles.get(id)).filter(isNotNullish);
|
|
3538
|
-
log.debug(`Rendering ${atomicStyles.length} atomic styles
|
|
4198
|
+
log.debug(`Rendering ${atomicStyles.length} atomic styles`);
|
|
3539
4199
|
reportUnknownAtomicStyleLayers(this, atomicStyles);
|
|
3540
4200
|
return renderAtomicStyles({
|
|
3541
4201
|
atomicStyles,
|
|
3542
|
-
isPreview,
|
|
3543
4202
|
isFormatted,
|
|
3544
4203
|
defaultSelector: this.config.defaultSelector,
|
|
3545
4204
|
layers: this.config.layers,
|
|
@@ -3735,9 +4394,9 @@ function resolvePreflight(preflight) {
|
|
|
3735
4394
|
* @internal
|
|
3736
4395
|
*
|
|
3737
4396
|
* @param config - The raw engine configuration.
|
|
3738
|
-
* @returns A `ResolvedEngineConfig` with defaults applied, plugins sorted, preflights resolved,
|
|
4397
|
+
* @returns A `ResolvedEngineConfig` with defaults applied, plugins sorted, preflights resolved, .
|
|
3739
4398
|
*
|
|
3740
|
-
* @remarks Merges `DEFAULT_LAYERS`, normalizes CSS imports, resolves preflight definitions
|
|
4399
|
+
* @remarks Merges `DEFAULT_LAYERS`, normalizes CSS imports, and resolves preflight definitions.
|
|
3741
4400
|
*
|
|
3742
4401
|
* @example
|
|
3743
4402
|
* ```ts
|
|
@@ -3759,23 +4418,8 @@ async function resolveEngineConfig(config) {
|
|
|
3759
4418
|
cssImports: [...new Set(cssImports.map(normalizeCssImport).filter(isNotNullish))],
|
|
3760
4419
|
layers,
|
|
3761
4420
|
defaultPreflightsLayer,
|
|
3762
|
-
defaultUtilitiesLayer
|
|
3763
|
-
autocomplete: {
|
|
3764
|
-
selectors: /* @__PURE__ */ new Set(),
|
|
3765
|
-
shortcuts: /* @__PURE__ */ new Set(),
|
|
3766
|
-
extraProperties: /* @__PURE__ */ new Set(),
|
|
3767
|
-
extraCssProperties: /* @__PURE__ */ new Set(),
|
|
3768
|
-
properties: /* @__PURE__ */ new Map(),
|
|
3769
|
-
cssProperties: /* @__PURE__ */ new Map(),
|
|
3770
|
-
patterns: {
|
|
3771
|
-
selectors: /* @__PURE__ */ new Set(),
|
|
3772
|
-
shortcuts: /* @__PURE__ */ new Set(),
|
|
3773
|
-
properties: /* @__PURE__ */ new Map(),
|
|
3774
|
-
cssProperties: /* @__PURE__ */ new Map()
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
4421
|
+
defaultUtilitiesLayer
|
|
3777
4422
|
};
|
|
3778
|
-
appendAutocomplete(resolvedConfig, config.autocomplete ?? {});
|
|
3779
4423
|
const resolvedPreflights = preflights.map(resolvePreflight);
|
|
3780
4424
|
resolvedConfig.preflights.push(...resolvedPreflights);
|
|
3781
4425
|
log.debug(`Engine config resolved: ${resolvedPreflights.length} preflights processed`);
|
|
@@ -3834,13 +4478,13 @@ async function resolveStyleItemList({ itemList, transformStyleItems, extractStyl
|
|
|
3834
4478
|
function sortAtomicStyles(styles, defaultSelector) {
|
|
3835
4479
|
return [...styles].sort((a, b) => calcAtomicStyleRenderingWeight(a, defaultSelector) - calcAtomicStyleRenderingWeight(b, defaultSelector));
|
|
3836
4480
|
}
|
|
3837
|
-
function renderAtomicStylesCss({ atomicStyles,
|
|
4481
|
+
function renderAtomicStylesCss({ atomicStyles, isFormatted }) {
|
|
3838
4482
|
const blocks = /* @__PURE__ */ new Map();
|
|
3839
4483
|
atomicStyles.forEach(({ id, content: { selector: rawSelector, property, value } }) => {
|
|
3840
4484
|
const { selector } = splitLayerSelector(rawSelector);
|
|
3841
4485
|
if (selector.some((s) => hasAtomicStyleIdPlaceholder(s)) === false || value == null) return;
|
|
3842
4486
|
const renderObject = {
|
|
3843
|
-
selector:
|
|
4487
|
+
selector: selector.map((s) => replaceAtomicStyleIdPlaceholder(s, id)),
|
|
3844
4488
|
properties: value.map((v) => ({
|
|
3845
4489
|
property,
|
|
3846
4490
|
value: v
|
|
@@ -3863,9 +4507,8 @@ function renderAtomicStylesCss({ atomicStyles, isPreview, isFormatted }) {
|
|
|
3863
4507
|
* Standalone function that renders atomic styles into CSS with layer grouping.
|
|
3864
4508
|
* @internal
|
|
3865
4509
|
*
|
|
3866
|
-
* @param payload - An object containing `atomicStyles`, `
|
|
4510
|
+
* @param payload - An object containing `atomicStyles`, `isFormatted`, `defaultSelector`, and optional `layers`/`defaultUtilitiesLayer`.
|
|
3867
4511
|
* @param payload.atomicStyles - The atomic styles to render.
|
|
3868
|
-
* @param payload.isPreview - Whether placeholder IDs should be preserved for preview output.
|
|
3869
4512
|
* @param payload.isFormatted - Whether to render with indentation and line breaks.
|
|
3870
4513
|
* @param payload.defaultSelector - The engine default selector used when computing render order.
|
|
3871
4514
|
* @param payload.layers - Optional configured CSS layers to group atomic styles into.
|
|
@@ -3876,15 +4519,14 @@ function renderAtomicStylesCss({ atomicStyles, isPreview, isFormatted }) {
|
|
|
3876
4519
|
*
|
|
3877
4520
|
* @example
|
|
3878
4521
|
* ```ts
|
|
3879
|
-
* const css = renderAtomicStyles({ atomicStyles,
|
|
4522
|
+
* const css = renderAtomicStyles({ atomicStyles, isFormatted: true, defaultSelector: '.pk-__ID__', layers: { utilities: 10 } })
|
|
3880
4523
|
* ```
|
|
3881
4524
|
*/
|
|
3882
4525
|
function renderAtomicStyles(payload) {
|
|
3883
|
-
const { atomicStyles,
|
|
4526
|
+
const { atomicStyles, isFormatted, defaultSelector, layers, defaultUtilitiesLayer } = payload;
|
|
3884
4527
|
const sortedStyles = sortAtomicStyles(atomicStyles, defaultSelector);
|
|
3885
4528
|
if (layers == null) return renderAtomicStylesCss({
|
|
3886
4529
|
atomicStyles: sortedStyles,
|
|
3887
|
-
isPreview,
|
|
3888
4530
|
isFormatted
|
|
3889
4531
|
});
|
|
3890
4532
|
const layerOrder = sortLayerNames(layers);
|
|
@@ -3897,7 +4539,6 @@ function renderAtomicStyles(payload) {
|
|
|
3897
4539
|
const parts = [];
|
|
3898
4540
|
if (unlayeredStyles.length > 0) parts.push(renderAtomicStylesCss({
|
|
3899
4541
|
atomicStyles: unlayeredStyles,
|
|
3900
|
-
isPreview,
|
|
3901
4542
|
isFormatted
|
|
3902
4543
|
}));
|
|
3903
4544
|
parts.push(...renderLayerBlocks({
|
|
@@ -3906,7 +4547,6 @@ function renderAtomicStyles(payload) {
|
|
|
3906
4547
|
isFormatted,
|
|
3907
4548
|
render: (styles) => renderAtomicStylesCss({
|
|
3908
4549
|
atomicStyles: styles,
|
|
3909
|
-
isPreview,
|
|
3910
4550
|
isFormatted
|
|
3911
4551
|
})
|
|
3912
4552
|
}));
|
|
@@ -3995,6 +4635,115 @@ async function renderPreflightDefinition(payload) {
|
|
|
3995
4635
|
}), isFormatted);
|
|
3996
4636
|
}
|
|
3997
4637
|
//#endregion
|
|
4638
|
+
//#region src/typegen/render.ts
|
|
4639
|
+
function joinRefs(contributions, key) {
|
|
4640
|
+
const refs = contributions.flatMap((contribution) => contribution[key] == null ? [] : [contribution[key]]);
|
|
4641
|
+
return refs.length === 0 ? "never" : refs.join(" | ");
|
|
4642
|
+
}
|
|
4643
|
+
function joinIntersectionRefs(contributions, key) {
|
|
4644
|
+
const refs = contributions.flatMap((contribution) => contribution[key] == null ? [] : [contribution[key]]);
|
|
4645
|
+
return refs.length === 0 ? "{}" : refs.join(" & ");
|
|
4646
|
+
}
|
|
4647
|
+
function compareStrings(a, b) {
|
|
4648
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
4649
|
+
}
|
|
4650
|
+
function renderPikaMembers(contributions) {
|
|
4651
|
+
return contributions.flatMap((contribution) => Object.entries(contribution.pika ?? {})).sort(([a], [b]) => compareStrings(a, b)).map(([root, ref]) => ` ${JSON.stringify(root)}: ${ref}`);
|
|
4652
|
+
}
|
|
4653
|
+
function renderUnit(unit, index) {
|
|
4654
|
+
const namespace = `__PikaTypegenUnit${index}`;
|
|
4655
|
+
const contributions = [...unit.snapshot.contributions].sort((a, b) => compareStrings(a.id, b.id));
|
|
4656
|
+
const declarations = contributions.flatMap((contribution) => {
|
|
4657
|
+
const declarations = renderTypegenContributionDeclarations(unit.snapshot, contribution, unit.hostBindings ?? {});
|
|
4658
|
+
return declarations == null ? [] : [declarations];
|
|
4659
|
+
});
|
|
4660
|
+
const resultType = unit.transformedFormat === "array" ? "string[]" : "string";
|
|
4661
|
+
const selectors = joinIntersectionRefs(contributions, "selectors");
|
|
4662
|
+
const properties = joinRefs(contributions, "properties");
|
|
4663
|
+
const cssProperties = joinRefs(contributions, "cssProperties");
|
|
4664
|
+
const cssPropertyValues = joinRefs(contributions, "cssPropertyValues");
|
|
4665
|
+
const propertyConstraints = joinIntersectionRefs(contributions, "propertyConstraints");
|
|
4666
|
+
const pikaMembers = renderPikaMembers(contributions);
|
|
4667
|
+
const moduleName = JSON.stringify(unit.publicModule);
|
|
4668
|
+
return {
|
|
4669
|
+
namespace,
|
|
4670
|
+
lines: [
|
|
4671
|
+
`declare namespace ${namespace} {`,
|
|
4672
|
+
...declarations,
|
|
4673
|
+
...declarations.length === 0 ? [] : [""],
|
|
4674
|
+
" type __UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends ((value: infer I) => void) ? I : never",
|
|
4675
|
+
" type __Additive<T> = [T] extends [never] ? {} : __UnionToIntersection<T>",
|
|
4676
|
+
` type __SelectorContributions = ${selectors}`,
|
|
4677
|
+
` type __PropertyContributions = ${properties}`,
|
|
4678
|
+
` type __CssPropertyContributions = ${cssProperties}`,
|
|
4679
|
+
` type __CssPropertyValueContributions = ${cssPropertyValues}`,
|
|
4680
|
+
` type __PropertyConstraints = ${propertyConstraints}`,
|
|
4681
|
+
` type __CustomProperties = { [K in \`--\${string}\`]?: import(${moduleName}).TypegenCSSPropertyInputValue<__CssPropertyValueContributions, import(${moduleName}).UnionString, K> }`,
|
|
4682
|
+
` type __Properties = import(${moduleName}).TypegenCSSPropertiesInput<__CssPropertyValueContributions>`,
|
|
4683
|
+
` & import(${moduleName}).TypegenCSSPropertiesHyphenInput<__CssPropertyValueContributions>`,
|
|
4684
|
+
" & __CustomProperties",
|
|
4685
|
+
" & __Additive<__PropertyContributions>",
|
|
4686
|
+
" & __Additive<__CssPropertyContributions>",
|
|
4687
|
+
" interface __StyleDefinitionMapBase {",
|
|
4688
|
+
" [selector: string]:",
|
|
4689
|
+
` | import(${moduleName}).PropertyValue<import(${moduleName}).UnionString>`,
|
|
4690
|
+
" | __StyleDefinition",
|
|
4691
|
+
" | __StyleItem[]",
|
|
4692
|
+
" | undefined",
|
|
4693
|
+
" }",
|
|
4694
|
+
" type __ConstrainedProperties = Omit<__Properties, keyof __PropertyConstraints> & __PropertyConstraints",
|
|
4695
|
+
" type __StyleDefinitionMap = __StyleDefinitionMapBase & __SelectorContributions & __PropertyConstraints",
|
|
4696
|
+
" type __StyleDefinition = __ConstrainedProperties | __StyleDefinitionMap",
|
|
4697
|
+
` type __StyleItem = import(${moduleName}).UnionString | __StyleDefinition`,
|
|
4698
|
+
` type __StyleFn = (...params: __StyleItem[]) => ${resultType}`,
|
|
4699
|
+
...pikaMembers.length === 0 ? [" export type Pika = __StyleFn"] : [
|
|
4700
|
+
" type __StaticExtensions = {",
|
|
4701
|
+
...pikaMembers,
|
|
4702
|
+
" }",
|
|
4703
|
+
" export type Pika = __StyleFn & __StaticExtensions"
|
|
4704
|
+
],
|
|
4705
|
+
"}"
|
|
4706
|
+
]
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
/**
|
|
4710
|
+
* Renders one collision-safe TypeScript declaration document from isolated
|
|
4711
|
+
* finalized Engine Typegen snapshots and explicit project/host bindings.
|
|
4712
|
+
*
|
|
4713
|
+
* @param units - Isolated finalized snapshots and host bindings to render as one declaration document.
|
|
4714
|
+
*/
|
|
4715
|
+
function renderTypegenDocument(units) {
|
|
4716
|
+
const fnNames = /* @__PURE__ */ new Set();
|
|
4717
|
+
const rendered = units.map((unit, index) => {
|
|
4718
|
+
if (fnNames.has(unit.fnName)) throw new Error(`Typegen render unit fnName "${unit.fnName}" is duplicated`);
|
|
4719
|
+
fnNames.add(unit.fnName);
|
|
4720
|
+
return {
|
|
4721
|
+
unit,
|
|
4722
|
+
...renderUnit(unit, index)
|
|
4723
|
+
};
|
|
4724
|
+
});
|
|
4725
|
+
const vueUnits = rendered.filter(({ unit }) => unit.vueTemplateGlobals === true);
|
|
4726
|
+
return [
|
|
4727
|
+
"// Auto-generated by PikaCSS",
|
|
4728
|
+
...rendered.flatMap(({ lines }) => ["", ...lines]),
|
|
4729
|
+
"",
|
|
4730
|
+
"declare global {",
|
|
4731
|
+
...rendered.map(({ namespace, unit }) => ` const ${unit.fnName}: ${namespace}.Pika`),
|
|
4732
|
+
"}",
|
|
4733
|
+
...vueUnits.length === 0 ? [] : [
|
|
4734
|
+
"",
|
|
4735
|
+
"declare module 'vue' {",
|
|
4736
|
+
" interface ComponentCustomProperties {",
|
|
4737
|
+
...vueUnits.map(({ namespace, unit }) => ` ${unit.fnName}: ${namespace}.Pika`),
|
|
4738
|
+
" }",
|
|
4739
|
+
"}"
|
|
4740
|
+
],
|
|
4741
|
+
"",
|
|
4742
|
+
"export {}",
|
|
4743
|
+
""
|
|
4744
|
+
].join("\n");
|
|
4745
|
+
}
|
|
4746
|
+
//#endregion
|
|
3998
4747
|
//#region src/index.ts
|
|
3999
4748
|
/**
|
|
4000
4749
|
* Identity helper that returns the engine configuration as-is, providing TypeScript type inference and autocompletion.
|
|
@@ -4003,11 +4752,16 @@ async function renderPreflightDefinition(payload) {
|
|
|
4003
4752
|
* @param config - The engine configuration object.
|
|
4004
4753
|
* @returns The same configuration object, unchanged.
|
|
4005
4754
|
*
|
|
4006
|
-
* @remarks A compile-time-only helper with no runtime effect.
|
|
4755
|
+
* @remarks A compile-time-only helper with no runtime effect. Use it for low-level `EngineConfig` authoring or as a typed value nested under the canonical project `defineConfig({ engine: ... })` surface. It is not itself the default-export root for `pika.config.*`.
|
|
4007
4756
|
*
|
|
4008
4757
|
* @example
|
|
4009
4758
|
* ```ts
|
|
4010
|
-
*
|
|
4759
|
+
* import { defineEngineConfig } from '@pikacss/core'
|
|
4760
|
+
*
|
|
4761
|
+
* const engineConfig = defineEngineConfig({
|
|
4762
|
+
* prefix: 'pk-',
|
|
4763
|
+
* plugins: [],
|
|
4764
|
+
* })
|
|
4011
4765
|
* ```
|
|
4012
4766
|
*/
|
|
4013
4767
|
function defineEngineConfig(config) {
|
|
@@ -4015,4 +4769,4 @@ function defineEngineConfig(config) {
|
|
|
4015
4769
|
}
|
|
4016
4770
|
/* c8 ignore end */
|
|
4017
4771
|
//#endregion
|
|
4018
|
-
export {
|
|
4772
|
+
export { createEngine, createLogger, defineEngineConfig, defineEnginePlugin, escapeRegExp, isPlainObjectRecord, log, renderCSSStyleBlocks, renderTypegenDocument, renderTypegenJSDoc, sortLayerNames };
|