@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.
Files changed (3) hide show
  1. package/dist/index.d.mts +881 -1023
  2. package/dist/index.mjs +1345 -591
  3. 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 is used by `appendAutocomplete` to determine whether the autocomplete config actually changed, avoiding unnecessary notification callbacks.
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`, and the `stored` ID map.
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 num = stored.size;
1715
- const id = `${prefix}${numberToChars(num)}`;
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`, and the per-use-call `resolvedIdsByBaseKey` map for order-sensitive reuse tracking.
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", "autocompleteConfigUpdated"]);
2111
- const DEFAULT_PLUGIN_CONTEXT = { onDiagnostic: noopDiagnosticHandler };
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, context));
2371
+ current = applyHookPayload(current, await invokePluginHook(hookFn, hook, current, pluginContext));
2165
2372
  logPluginHookEnd(plugin, hook);
2166
2373
  } catch (error) {
2167
- reportPluginHookError(context, plugin, hook, error);
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, context));
2397
+ current = applyHookPayload(current, invokePluginHook(hookFn, hook, current, pluginContext));
2190
2398
  logPluginHookEnd(plugin, hook);
2191
2399
  } catch (error) {
2192
- reportPluginHookError(context, plugin, hook, error);
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, context),
2207
- rawConfigConfigured: (plugins, config) => execSyncHook(plugins, "rawConfigConfigured", config, context),
2208
- configureResolvedConfig: (plugins, resolvedConfig) => execAsyncHook(plugins, "configureResolvedConfig", resolvedConfig, context),
2209
- configureEngine: (plugins, engine) => execAsyncHook(plugins, "configureEngine", engine, context),
2210
- transformSelectors: (plugins, selectors) => execAsyncHook(plugins, "transformSelectors", selectors, context),
2211
- transformStyleItems: (plugins, styleItems) => execAsyncHook(plugins, "transformStyleItems", styleItems, context),
2212
- transformStyleDefinitions: (plugins, styleDefinitions) => execAsyncHook(plugins, "transformStyleDefinitions", styleDefinitions, context),
2213
- preflightUpdated: (plugins) => execSyncHook(plugins, "preflightUpdated", void 0, context),
2214
- atomicStyleAdded: (plugins, atomicStyle) => execSyncHook(plugins, "atomicStyleAdded", atomicStyle, context),
2215
- autocompleteConfigUpdated: (plugins) => execSyncHook(plugins, "autocompleteConfigUpdated", void 0, context)
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). The `__shortcut` reference is never modified.
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
- rawConfigConfigured(config) {
2282
- defaultValue = config.important?.default ?? false;
2562
+ createState: () => ({ defaultValue: false }),
2563
+ rawConfigConfigured(config, context) {
2564
+ context.state.defaultValue = config.important?.default ?? false;
2283
2565
  },
2284
- configureEngine(engine) {
2285
- engine.appendAutocomplete({
2286
- extraProperties: "__important",
2287
- properties: { __important: "boolean" }
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/plugins/keyframes.ts
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 `![${alt}](${href})`;
2603
+ }
2307
2604
  /**
2308
- * Built-in engine plugin that provides CSS `@keyframes` registration, autocomplete integration, and smart pruning.
2605
+ * Renders one lexical-safe JSDoc block from path-free semantic documentation.
2309
2606
  *
2310
- * @returns An `EnginePlugin` that registers keyframes definitions, wires up `animationName`/`animation` autocomplete entries, and emits a preflight containing only the `@keyframes` rules actually referenced by atomic styles.
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
- * @remarks Reads `EngineConfig.keyframes` during `rawConfigConfigured` and attaches the `engine.keyframes` management interface during `configureEngine`. Unused keyframes are pruned from the output unless `pruneUnused: false` is set on the individual definition or globally.
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
- * @example
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
- rawConfigConfigured(config) {
2325
- resolveKeyframesConfig = createResolveConfigFn({ pruneUnused: config.keyframes?.pruneUnused });
2326
- configList = config.keyframes?.definitions ?? [];
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(engine) {
2329
- engine.keyframes = {
2330
- store: /* @__PURE__ */ new Map(),
2331
- add: (...list) => {
2332
- list.forEach((config) => {
2333
- const resolved = resolveKeyframesConfig(config);
2334
- const { name, frames, autocomplete: autocompleteAnimation } = resolved;
2335
- if (frames != null) engine.keyframes.store.set(name, resolved);
2336
- engine.appendAutocomplete({ cssProperties: {
2337
- animationName: name,
2338
- animation: autocompleteAnimation.length > 0 ? [`${name} `, ...autocompleteAnimation] : `${name} `
2339
- } });
2340
- });
2341
- engine.notifyPreflightUpdated();
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
- maybeUsedKeyframes.forEach(({ name, frames }) => {
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
- function createResolveConfigFn({ pruneUnused: defaultPruneUnused = true } = {}) {
2370
- return function resolveKeyframesConfig(config) {
2371
- if (typeof config === "string") return {
2372
- name: config,
2373
- frames: null,
2374
- autocomplete: [],
2375
- pruneUnused: defaultPruneUnused
2376
- };
2377
- if (Array.isArray(config)) {
2378
- const [name, frames, autocomplete = [], pruneUnused = defaultPruneUnused] = config;
2379
- return {
2380
- name,
2381
- frames,
2382
- autocomplete,
2383
- pruneUnused
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
- const { name, frames, autocomplete = [], pruneUnused = defaultPruneUnused } = config;
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 a user-supplied rule shorthand into a `ResolvedRuleConfig`, a plain redirect string, or `undefined`.
2716
- * @internal
2717
- *
2718
- * @typeParam T - The element type of the rule's resolved value array.
2719
- * @param config - The raw rule configuration: a string redirect, a tuple (`[string, value]` or `[RegExp, fn, autocomplete?]`), or an object with `keyName` and `value` properties.
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, keyName) {
2737
- if (typeof config === "string") return config;
2738
- if (typeof config !== "object" || config === null) return;
2739
- const { key, value, autocomplete } = Array.isArray(config) ? {
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: key,
2753
- resolved: [value].flat(1)
3121
+ key: definition.name,
3122
+ string: definition.name,
3123
+ resolved: [definition.value].flat(1)
2754
3124
  },
2755
- autocomplete: [key]
3125
+ autocomplete: [definition.name]
2756
3126
  };
2757
- if (key instanceof RegExp && typeof value === "function") return {
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: key.source,
2761
- stringPattern: stripGlobalFlag(key),
2762
- createResolved: createDynamicResolvedFactory(value)
3130
+ key: definition.pattern.source,
3131
+ stringPattern: stripGlobalFlag(definition.pattern),
3132
+ createResolved: createDynamicResolvedFactory(definition.resolve)
2763
3133
  },
2764
- autocomplete: autocomplete != null ? [autocomplete].flat(1) : []
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
- * Built-in engine plugin that provides the selector resolution system.
2771
- *
2772
- * @returns An `EnginePlugin` that registers the `selectors` resolver on the engine and hooks into `transformSelectors` to expand selector names into resolved CSS selectors.
2773
- *
2774
- * @remarks Reads `EngineConfig.selectors` during `rawConfigConfigured`, attaches a `RecursiveResolver` to `engine.selectors` during `configureEngine`, and resolves all selector strings in the `transformSelectors` hook.
2775
- *
2776
- * @example
2777
- * ```ts
2778
- * createEngine({ plugins: [selectors()] })
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
- rawConfigConfigured(config) {
2787
- configList = config.selectors?.definitions ?? [];
3174
+ createState: () => ({ definitions: [] }),
3175
+ rawConfigConfigured(config, context) {
3176
+ context.state.definitions = config.selectors?.definitions ?? [];
2788
3177
  },
2789
- configureEngine(_engine) {
2790
- engine = _engine;
2791
- engine.selectors = {
2792
- resolver: new SelectorResolver(engine.onDiagnostic),
2793
- add: (...list) => {
2794
- list.forEach((config) => {
2795
- const resolved = resolveSelectorConfig(config);
2796
- if (resolved == null) return;
2797
- if (typeof resolved === "string") {
2798
- engine.appendAutocomplete({ selectors: resolved });
2799
- return;
2800
- }
2801
- if (resolved.type === "static") engine.selectors.resolver.addStaticRule(resolved.rule);
2802
- else engine.selectors.resolver.addDynamicRule(resolved.rule);
2803
- engine.appendAutocomplete({ selectors: resolved.autocomplete });
2804
- });
2805
- }
2806
- };
2807
- engine.selectors.add(...configList);
2808
- engine.selectors.resolver.onResolved = (string, type) => {
2809
- if (type === "dynamic") engine.appendAutocomplete({ selectors: string });
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 engine.selectors.resolver.resolve(selector));
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, "selector");
3215
+ return resolveRuleConfig(config);
2835
3216
  }
2836
3217
  //#endregion
2837
3218
  //#region src/plugins/shortcuts.ts
2838
- /**
2839
- * Built-in engine plugin that provides the shortcut resolution system.
2840
- *
2841
- * @returns An `EnginePlugin` that registers the `shortcuts` resolver on the engine and hooks into `transformStyleItems` and `transformStyleDefinitions` to expand shortcut names into style items.
2842
- *
2843
- * @remarks Reads `EngineConfig.shortcuts` during `rawConfigConfigured`, attaches a `RecursiveResolver` to `engine.shortcuts` during `configureEngine`, and expands shortcut references in both `transformStyleItems` (string style items) and `transformStyleDefinitions` (the `__shortcut` pseudo-property).
2844
- *
2845
- * @example
2846
- * ```ts
2847
- * createEngine({ plugins: [shortcuts()] })
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
- rawConfigConfigured(config) {
2856
- configList = config.shortcuts?.definitions ?? [];
3375
+ createState: () => ({ definitions: [] }),
3376
+ rawConfigConfigured(config, context) {
3377
+ context.state.definitions = config.shortcuts?.definitions ?? [];
2857
3378
  },
2858
- configureEngine(_engine) {
2859
- engine = _engine;
2860
- engine.shortcuts = {
2861
- resolver: new ShortcutResolver(engine.onDiagnostic),
2862
- add: (...list) => {
2863
- list.forEach((config) => {
2864
- const resolved = resolveShortcutConfig(config);
2865
- if (resolved == null) return;
2866
- if (typeof resolved === "string") {
2867
- engine.appendAutocomplete({ shortcuts: resolved });
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 engine.shortcuts.resolver.resolve(styleItem));
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 resolveRuleConfig(config, "shortcut");
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
- function _resolveVariables(variables, levels, result) {
3035
- for (const [key, value] of Object.entries(variables)) if (key.startsWith("--")) {
3036
- const { value: varValue, autocomplete = {}, pruneUnused = defaultPruneUnused } = isPlainObjectRecord(value) ? value : { value };
3037
- result.push({
3038
- name: key,
3039
- value: varValue,
3040
- selector: levels.length > 0 ? levels : [":root"],
3041
- autocomplete: {
3042
- asValueOf: resolveAutocompleteValueTargets({ asValueOf: autocomplete.asValueOf }),
3043
- asProperty: autocomplete.asProperty ?? true
3044
- },
3045
- pruneUnused
3046
- });
3047
- } else {
3048
- if (!isPlainObjectRecord(value)) {
3049
- const message = `Invalid variables scope for selector "${key}". Expected a nested object, received ${typeof value}. Skipping.`;
3050
- if (onDiagnostic === noopDiagnosticHandler) log.warn(message);
3051
- else emitDiagnostic(onDiagnostic, {
3052
- level: "warning",
3053
- code: "variables-invalid-scope",
3054
- message
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
- _resolveVariables(value, [...levels, key], result);
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 function resolveVariables(variables) {
3063
- return _resolveVariables(variables, [], []);
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 resolveAutocompleteValueTargets({ asValueOf }) {
3067
- const explicitTargets = asValueOf == null ? [] : [asValueOf].flat().map((value) => String(value));
3068
- if (explicitTargets.includes("-")) return [];
3069
- const targets = /* @__PURE__ */ new Set();
3070
- if (asValueOf == null) targets.add("*");
3071
- explicitTargets.forEach((target) => {
3072
- targets.add(target);
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 pluginHooks = createEngineHooks({ onDiagnostic });
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.appendAutocomplete({
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`, `appendAutocomplete`, `appendCssImport`).
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
- * Absolute paths of external files this engine's config depends on (e.g. token files loaded by plugins).
3254
- *
3255
- * @remarks Plugins register paths via `addConfigDependency` during `configureEngine`. Integration layers (e.g. the unplugin) watch these files and re-create the engine when they change.
3256
- */
3257
- configDependencies = /* @__PURE__ */ new Set();
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 an external file path as a config dependency of this engine.
3938
+ * Registers a file dependency during Engine initialization.
3316
3939
  *
3317
- * @param path - The file path (ideally absolute) the current config was derived from.
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.configDependencies.add(path);
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 `use()` when a previously unseen atomic style is resolved.
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
- * Processes style items through the plugin pipeline and registers the resulting atomic styles in the store.
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 An array containing any unresolved string references first, followed by atomic style IDs in resolution order.
4036
+ * @returns A promise of the {@link StyleUsePlan} to pass to `commitUse()`.
3428
4037
  *
3429
- * @remarks Runs `transformStyleItems` and `extractStyleDefinition` hooks, resolves each extracted content into an atomic style, deduplicates by base key, and fires `atomicStyleAdded` for new entries.
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 ids = await engine.use({ color: 'red' }, { padding: '1rem' })
4049
+ * const plan = await engine.prepareUse({ color: 'red' })
4050
+ * const ids = engine.commitUse(plan)
3434
4051
  * ```
3435
4052
  */
3436
- async use(...itemList) {
3437
- log.debug(`Processing ${itemList.length} style items`);
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
- this.notifyAtomicStyleAdded(atomicStyle);
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, `isPreview` to use placeholder IDs.
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. When `isPreview` is true, atomic style IDs remain as placeholders for tooling previews.
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, isPreview = false } = options;
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 (preview: ${isPreview})`);
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, and autocomplete initialized.
4397
+ * @returns A `ResolvedEngineConfig` with defaults applied, plugins sorted, preflights resolved, .
3739
4398
  *
3740
- * @remarks Merges `DEFAULT_LAYERS`, normalizes CSS imports, resolves preflight definitions, and initializes the empty autocomplete sets/maps.
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, isPreview, isFormatted }) {
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: isPreview ? selector : selector.map((s) => replaceAtomicStyleIdPlaceholder(s, id)),
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`, `isPreview`, `isFormatted`, `defaultSelector`, and optional `layers`/`defaultUtilitiesLayer`.
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, isPreview: false, isFormatted: true, defaultSelector: '.pk-__ID__', layers: { utilities: 10 } })
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, isPreview, isFormatted, defaultSelector, layers, defaultUtilitiesLayer } = payload;
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. Useful in `pika.config.ts` files for IDE support.
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
- * export default defineEngineConfig({ prefix: 'pk-', plugins: [myPlugin()] })
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 { appendAutocomplete, createEngine, createLogger, defineEngineConfig, defineEnginePlugin, escapeRegExp, isPlainObjectRecord, log, renderCSSStyleBlocks, sortLayerNames };
4772
+ export { createEngine, createLogger, defineEngineConfig, defineEnginePlugin, escapeRegExp, isPlainObjectRecord, log, renderCSSStyleBlocks, renderTypegenDocument, renderTypegenJSDoc, sortLayerNames };