@pikacss/core 0.0.62 → 0.0.64

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 +734 -1021
  2. package/dist/index.mjs +1116 -558
  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 = {
@@ -2193,8 +2091,222 @@ function createExtractFn(options) {
2193
2091
  });
2194
2092
  }
2195
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
2196
2308
  //#region src/plugin.ts
2197
- const VOID_HOOKS = new Set(["preflightUpdated", "autocompleteConfigUpdated"]);
2309
+ const VOID_HOOKS = new Set(["preflightUpdated"]);
2198
2310
  const DEFAULT_PLUGIN_CONTEXT = {
2199
2311
  onDiagnostic: noopDiagnosticHandler,
2200
2312
  state: void 0,
@@ -2337,19 +2449,48 @@ function createEngineHooks(context) {
2337
2449
  if (entry.status === "failed") throw entry.error;
2338
2450
  return entry.context;
2339
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
+ };
2340
2483
  return {
2341
2484
  configureRawConfig: (plugins, config) => execAsyncHook(plugins, "configureRawConfig", config, contextFor),
2342
2485
  rawConfigConfigured: (plugins, config) => execSyncHook(plugins, "rawConfigConfigured", config, contextFor),
2343
2486
  configureResolvedConfig: (plugins, resolvedConfig) => execAsyncHook(plugins, "configureResolvedConfig", resolvedConfig, contextFor),
2344
- configureEngine: (plugins, engine) => execAsyncHook(plugins, "configureEngine", engine, contextFor),
2487
+ configureEngine,
2345
2488
  transformSelectors: (plugins, selectors) => execAsyncHook(plugins, "transformSelectors", selectors, contextFor),
2346
2489
  transformStyleItems: (plugins, styleItems) => execAsyncHook(plugins, "transformStyleItems", styleItems, contextFor),
2347
2490
  transformStyleDefinitions: (plugins, styleDefinitions) => execAsyncHook(plugins, "transformStyleDefinitions", styleDefinitions, contextFor),
2348
2491
  transformStyleContents: (plugins, styleContents) => execAsyncHook(plugins, "transformStyleContents", styleContents, contextFor),
2349
2492
  preflightUpdated: (plugins) => execSyncHook(plugins, "preflightUpdated", void 0, contextFor),
2350
- atomicStyleAdded: (plugins, atomicStyle) => execSyncHook(plugins, "atomicStyleAdded", atomicStyle, contextFor),
2351
- autocompleteConfigUpdated: (plugins) => execSyncHook(plugins, "autocompleteConfigUpdated", void 0, contextFor),
2352
- configDependencyAdded: (plugins, path) => execSyncHook(plugins, "configDependencyAdded", path, contextFor)
2493
+ atomicStyleAdded: (plugins, atomicStyle) => execSyncHook(plugins, "atomicStyleAdded", atomicStyle, contextFor)
2353
2494
  };
2354
2495
  }
2355
2496
  createEngineHooks(DEFAULT_PLUGIN_CONTEXT);
@@ -2398,7 +2539,7 @@ function modifyPropertyValue(value) {
2398
2539
  *
2399
2540
  * @returns An `EnginePlugin` that intercepts `transformStyleDefinitions` to conditionally append `!important` to every property value.
2400
2541
  *
2401
- * @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).
2402
2543
  *
2403
2544
  * @example
2404
2545
  * ```ts
@@ -2406,7 +2547,6 @@ function modifyPropertyValue(value) {
2406
2547
  * ```
2407
2548
  */
2408
2549
  function important() {
2409
- let defaultValue;
2410
2550
  function propagateExplicitFlag(v, flag) {
2411
2551
  if (Array.isArray(v)) return v.map((item) => typeof item === "object" && item !== null && !Array.isArray(item) ? {
2412
2552
  __important: flag,
@@ -2419,23 +2559,28 @@ function important() {
2419
2559
  }
2420
2560
  return defineEnginePlugin({
2421
2561
  name: "core:important",
2422
- rawConfigConfigured(config) {
2423
- defaultValue = config.important?.default ?? false;
2562
+ createState: () => ({ defaultValue: false }),
2563
+ rawConfigConfigured(config, context) {
2564
+ context.state.defaultValue = config.important?.default ?? false;
2424
2565
  },
2425
- configureEngine(engine) {
2426
- engine.appendAutocomplete({
2427
- extraProperties: "__important",
2428
- 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"
2429
2575
  });
2430
2576
  },
2431
- transformStyleDefinitions(styleDefinitions) {
2577
+ transformStyleDefinitions(styleDefinitions, context) {
2432
2578
  return styleDefinitions.map((styleDefinition) => {
2433
2579
  const { __important, ...rest } = styleDefinition;
2434
2580
  const explicit = __important;
2435
- const important = explicit ?? defaultValue;
2581
+ const important = explicit ?? context.state.defaultValue;
2436
2582
  if (important === false && explicit == null) return rest;
2437
2583
  return Object.fromEntries(Object.entries(rest).map(([k, v]) => {
2438
- if (k === "__shortcut") return [k, v];
2439
2584
  if (isPropertyValue(v)) return [k, important ? modifyPropertyValue(v) : v];
2440
2585
  return [k, explicit == null ? v : propagateExplicitFlag(v, explicit)];
2441
2586
  }));
@@ -2444,45 +2589,147 @@ function important() {
2444
2589
  });
2445
2590
  }
2446
2591
  //#endregion
2447
- //#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
+ }
2448
2604
  /**
2449
- * 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.
2450
2606
  *
2451
- * @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.
2452
2612
  *
2453
- * @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.
2454
2616
  *
2455
- * @example
2456
- * ```ts
2457
- * createEngine({ plugins: [keyframes()] })
2458
- * ```
2617
+ * @internal
2459
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. */
2460
2707
  function keyframes() {
2461
- let resolveKeyframesConfig;
2462
- let configList;
2463
2708
  return defineEnginePlugin({
2464
2709
  name: "core:keyframes",
2465
- rawConfigConfigured(config) {
2466
- resolveKeyframesConfig = createResolveConfigFn({ pruneUnused: config.keyframes?.pruneUnused });
2467
- 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;
2468
2718
  },
2469
- configureEngine(engine) {
2470
- engine.keyframes = {
2471
- store: /* @__PURE__ */ new Map(),
2472
- add: (...list) => {
2473
- list.forEach((config) => {
2474
- const resolved = resolveKeyframesConfig(config);
2475
- const { name, frames, autocomplete: autocompleteAnimation } = resolved;
2476
- if (frames != null) engine.keyframes.store.set(name, resolved);
2477
- engine.appendAutocomplete({ cssProperties: {
2478
- animationName: name,
2479
- animation: autocompleteAnimation.length > 0 ? [`${name} `, ...autocompleteAnimation] : `${name} `
2480
- } });
2481
- });
2482
- engine.notifyPreflightUpdated();
2483
- }
2484
- };
2485
- 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;
2486
2733
  engine.addPreflight((engine, _isFormatted, ctx) => {
2487
2734
  const maybeUsedName = /* @__PURE__ */ new Set();
2488
2735
  engine.store.atomicStyles.forEach(({ content: { property, value } }, id) => {
@@ -2497,41 +2744,41 @@ function keyframes() {
2497
2744
  });
2498
2745
  });
2499
2746
  });
2500
- const maybeUsedKeyframes = Array.from(engine.keyframes.store.values()).filter(({ name, frames, pruneUnused }) => (pruneUnused === false || maybeUsedName.has(name)) && frames != null);
2501
2747
  const preflightDefinition = {};
2502
- maybeUsedKeyframes.forEach(({ name, frames }) => {
2748
+ for (const { name, frames, pruneUnused } of state.store.values()) {
2749
+ if (frames == null || pruneUnused !== false && !maybeUsedName.has(name)) continue;
2503
2750
  preflightDefinition[`@keyframes ${name}`] = frames;
2504
- });
2751
+ }
2505
2752
  return preflightDefinition;
2506
2753
  });
2507
2754
  }
2508
2755
  });
2509
2756
  }
2510
- function createResolveConfigFn({ pruneUnused: defaultPruneUnused = true } = {}) {
2511
- return function resolveKeyframesConfig(config) {
2512
- if (typeof config === "string") return {
2513
- name: config,
2514
- frames: null,
2515
- autocomplete: [],
2516
- pruneUnused: defaultPruneUnused
2517
- };
2518
- if (Array.isArray(config)) {
2519
- const [name, frames, autocomplete = [], pruneUnused = defaultPruneUnused] = config;
2520
- return {
2521
- name,
2522
- frames,
2523
- autocomplete,
2524
- pruneUnused
2525
- };
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
+ });
2526
2780
  }
2527
- const { name, frames, autocomplete = [], pruneUnused = defaultPruneUnused } = config;
2528
- return {
2529
- name,
2530
- frames,
2531
- autocomplete,
2532
- pruneUnused
2533
- };
2534
- };
2781
+ });
2535
2782
  }
2536
2783
  //#endregion
2537
2784
  //#region src/resolver.ts
@@ -2539,6 +2786,12 @@ function stripGlobalFlag(re) {
2539
2786
  if (!re.global) return re;
2540
2787
  return new RegExp(re.source, re.flags.replace("g", ""));
2541
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
+ }
2542
2795
  /**
2543
2796
  * Base resolver class that manages static and dynamic rules and caches resolution results.
2544
2797
  * @internal
@@ -2853,308 +3106,311 @@ function createDynamicResolvedFactory(fn) {
2853
3106
  };
2854
3107
  }
2855
3108
  /**
2856
- * Normalizes a user-supplied rule shorthand into a `ResolvedRuleConfig`, a plain redirect string, or `undefined`.
2857
- * @internal
2858
- *
2859
- * @typeParam T - The element type of the rule's resolved value array.
2860
- * @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.
2861
- * @param keyName - The property name on an object-form config that holds the match key or pattern.
2862
- * @returns A `ResolvedRuleConfig<T>` for valid static/dynamic configs, the original string for redirect configs, or `undefined` if the config shape is unrecognized.
2863
- *
2864
- * @remarks Handles three config shapes:
2865
- * - **String**: returned as-is for the caller to treat as a redirect to another rule.
2866
- * - **Tuple**: `[string, T | T[]]` for static rules, `[RegExp, fn, autocomplete?]` for dynamic rules.
2867
- * - **Object**: `{ [keyName]: string | RegExp, value: T | fn, autocomplete?: string[] }`.
2868
- *
2869
- * 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.
2870
- *
2871
- * @example
2872
- * ```ts
2873
- * resolveRuleConfig(['hover', '$:hover'], 'selector')
2874
- * // { type: 'static', rule: { key: 'hover', ... }, autocomplete: ['hover'] }
2875
- * ```
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.
2876
3114
  */
2877
- function resolveRuleConfig(config, keyName) {
2878
- if (typeof config === "string") return config;
2879
- if (typeof config !== "object" || config === null) return;
2880
- const { key, value, autocomplete } = Array.isArray(config) ? {
2881
- key: config[0],
2882
- value: config[1],
2883
- autocomplete: config[2]
2884
- } : {
2885
- key: config[keyName],
2886
- value: config.value,
2887
- autocomplete: config.autocomplete
2888
- };
2889
- 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 {
2890
3119
  type: "static",
2891
3120
  rule: {
2892
- key,
2893
- string: key,
2894
- resolved: [value].flat(1)
3121
+ key: definition.name,
3122
+ string: definition.name,
3123
+ resolved: [definition.value].flat(1)
2895
3124
  },
2896
- autocomplete: [key]
3125
+ autocomplete: [definition.name]
2897
3126
  };
2898
- 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 {
2899
3128
  type: "dynamic",
2900
3129
  rule: {
2901
- key: key.source,
2902
- stringPattern: stripGlobalFlag(key),
2903
- createResolved: createDynamicResolvedFactory(value)
3130
+ key: definition.pattern.source,
3131
+ stringPattern: stripGlobalFlag(definition.pattern),
3132
+ createResolved: createDynamicResolvedFactory(definition.resolve)
2904
3133
  },
2905
- autocomplete: autocomplete != null ? [autocomplete].flat(1) : []
3134
+ autocomplete: definition.autocomplete == null ? [] : [definition.autocomplete].flat(1)
2906
3135
  };
2907
3136
  }
2908
3137
  //#endregion
2909
3138
  //#region src/plugins/selectors.ts
2910
- /**
2911
- * Built-in engine plugin that provides the selector resolution system.
2912
- *
2913
- * @returns An `EnginePlugin` that registers the `selectors` resolver on the engine and hooks into `transformSelectors` to expand selector names into resolved CSS selectors.
2914
- *
2915
- * @remarks Reads `EngineConfig.selectors` during `rawConfigConfigured`, attaches a `RecursiveResolver` to `engine.selectors` during `configureEngine`, and resolves all selector strings in the `transformSelectors` hook.
2916
- *
2917
- * @example
2918
- * ```ts
2919
- * createEngine({ plugins: [selectors()] })
2920
- * ```
2921
- */
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. */
2922
3171
  function selectors() {
2923
- let engine;
2924
- let configList;
2925
3172
  return defineEnginePlugin({
2926
3173
  name: "core:selectors",
2927
- rawConfigConfigured(config) {
2928
- configList = config.selectors?.definitions ?? [];
3174
+ createState: () => ({ definitions: [] }),
3175
+ rawConfigConfigured(config, context) {
3176
+ context.state.definitions = config.selectors?.definitions ?? [];
2929
3177
  },
2930
- configureEngine(_engine) {
2931
- engine = _engine;
2932
- engine.selectors = {
2933
- resolver: new SelectorResolver(engine.onDiagnostic),
2934
- add: (...list) => {
2935
- list.forEach((config) => {
2936
- const resolved = resolveSelectorConfig(config);
2937
- if (resolved == null) return;
2938
- if (typeof resolved === "string") {
2939
- engine.appendAutocomplete({ selectors: resolved });
2940
- return;
2941
- }
2942
- if (resolved.type === "static") engine.selectors.resolver.addStaticRule(resolved.rule);
2943
- else engine.selectors.resolver.addDynamicRule(resolved.rule);
2944
- engine.appendAutocomplete({ selectors: resolved.autocomplete });
2945
- });
2946
- }
2947
- };
2948
- engine.selectors.add(...configList);
2949
- engine.selectors.resolver.onResolved = (string, type) => {
2950
- if (type === "dynamic") engine.appendAutocomplete({ selectors: string });
2951
- };
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
+ });
2952
3202
  },
2953
- async transformSelectors(selectors) {
3203
+ async transformSelectors(selectors, context) {
3204
+ const resolver = context.state.resolver;
3205
+ if (resolver == null) return selectors;
2954
3206
  const result = [];
2955
- 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));
2956
3208
  return result;
2957
3209
  }
2958
3210
  });
2959
3211
  }
2960
3212
  var SelectorResolver = class extends RecursiveResolver {};
2961
- /**
2962
- * Normalizes a `Selector` configuration into a `ResolvedRuleConfig`, a redirect string, or `undefined`.
2963
- *
2964
- * @param config - The selector rule configuration to resolve.
2965
- * @returns A resolved static/dynamic rule config, a redirect string, or `undefined` if the shape is unrecognized.
2966
- *
2967
- * @remarks Delegates to the generic `resolveRuleConfig` with `'selector'` as the key name.
2968
- *
2969
- * @example
2970
- * ```ts
2971
- * const resolved = resolveSelectorConfig(['hover', '$:hover'])
2972
- * ```
2973
- */
3213
+ /** @internal */
2974
3214
  function resolveSelectorConfig(config) {
2975
- return resolveRuleConfig(config, "selector");
3215
+ return resolveRuleConfig(config);
2976
3216
  }
2977
3217
  //#endregion
2978
3218
  //#region src/plugins/shortcuts.ts
2979
- /**
2980
- * Built-in engine plugin that provides the shortcut resolution system.
2981
- *
2982
- * @returns An `EnginePlugin` that registers the `shortcuts` resolver on the engine and hooks into `transformStyleItems` and `transformStyleDefinitions` to expand shortcut names into style items.
2983
- *
2984
- * @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).
2985
- *
2986
- * @example
2987
- * ```ts
2988
- * createEngine({ plugins: [shortcuts()] })
2989
- * ```
2990
- */
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. */
2991
3372
  function shortcuts() {
2992
- let engine;
2993
- let configList;
2994
3373
  return defineEnginePlugin({
2995
3374
  name: "core:shortcuts",
2996
- rawConfigConfigured(config) {
2997
- configList = config.shortcuts?.definitions ?? [];
3375
+ createState: () => ({ definitions: [] }),
3376
+ rawConfigConfigured(config, context) {
3377
+ context.state.definitions = config.shortcuts?.definitions ?? [];
2998
3378
  },
2999
- configureEngine(_engine) {
3000
- engine = _engine;
3001
- engine.shortcuts = {
3002
- resolver: new ShortcutResolver(engine.onDiagnostic),
3003
- add: (...list) => {
3004
- list.forEach((config) => {
3005
- const resolved = resolveShortcutConfig(config);
3006
- if (resolved == null) return;
3007
- if (typeof resolved === "string") {
3008
- engine.appendAutocomplete({ shortcuts: resolved });
3009
- return;
3010
- }
3011
- if (resolved.type === "static") engine.shortcuts.resolver.addStaticRule(resolved.rule);
3012
- else engine.shortcuts.resolver.addDynamicRule(resolved.rule);
3013
- engine.appendAutocomplete({ shortcuts: resolved.autocomplete });
3014
- });
3015
- }
3016
- };
3017
- engine.shortcuts.add(...configList);
3018
- engine.shortcuts.resolver.onResolved = (string, type) => {
3019
- if (type === "dynamic") engine.appendAutocomplete({ shortcuts: string });
3020
- };
3021
- const unionType = ["(string & {})", "Autocomplete['Shortcut']"].join(" | ");
3022
- engine.appendAutocomplete({
3023
- extraProperties: "__shortcut",
3024
- 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" }
3025
3389
  });
3390
+ registerCoreEngineFinalizer(configurator.runtime, () => finalizeShortcutTypegen(configurator.runtime, acceptedDefinitions, configurator.onDiagnostic));
3026
3391
  },
3027
- async transformStyleItems(styleItems) {
3392
+ async transformStyleItems(styleItems, context) {
3393
+ const resolver = context.state.resolver;
3394
+ if (resolver == null) return styleItems;
3028
3395
  const result = [];
3029
3396
  for (const styleItem of styleItems) {
3030
3397
  if (typeof styleItem === "string") {
3031
- result.push(...await engine.shortcuts.resolver.resolve(styleItem));
3398
+ result.push(...await resolver.resolve(styleItem));
3032
3399
  continue;
3033
3400
  }
3034
3401
  result.push(styleItem);
3035
3402
  }
3036
3403
  return result;
3037
- },
3038
- async transformStyleDefinitions(styleDefinitions) {
3039
- const result = [];
3040
- for (const styleDefinition of styleDefinitions) if ("__shortcut" in styleDefinition) {
3041
- const { __shortcut, ...rest } = styleDefinition;
3042
- const explicitImportant = rest.__important ?? null;
3043
- const applied = [];
3044
- for (const shortcut of __shortcut == null ? [] : [__shortcut].flat(1)) {
3045
- const resolved = (await engine.shortcuts.resolver.resolve(shortcut)).filter(isNotString);
3046
- applied.push(...explicitImportant == null ? resolved : resolved.map((definition) => ({
3047
- __important: explicitImportant,
3048
- ...definition
3049
- })));
3050
- }
3051
- result.push(...applied, rest);
3052
- } else result.push(styleDefinition);
3053
- return result;
3054
3404
  }
3055
3405
  });
3056
3406
  }
3057
3407
  var ShortcutResolver = class extends RecursiveResolver {};
3408
+ /** @internal */
3058
3409
  function resolveShortcutConfig(config) {
3059
- return resolveRuleConfig(config, "shortcut");
3410
+ return resolveShortcutConfigForContext(config);
3060
3411
  }
3061
3412
  //#endregion
3062
3413
  //#region src/plugins/variables.ts
3063
- /**
3064
- * Built-in engine plugin that provides CSS custom properties (variables) with smart pruning and autocomplete integration.
3065
- *
3066
- * @returns An `EnginePlugin` that registers variable definitions, manages a preflight for emitting `:root` / scoped variables, and prunes unused variables from the output.
3067
- *
3068
- * @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.
3069
- *
3070
- * @example
3071
- * ```ts
3072
- * createEngine({ plugins: [variables()] })
3073
- * ```
3074
- */
3075
- function variables() {
3076
- let resolveVariables;
3077
- let rawVariables;
3078
- let safeSet;
3079
- return defineEnginePlugin({
3080
- name: "core:variables",
3081
- rawConfigConfigured(config, context) {
3082
- resolveVariables = createResolveVariablesFn({
3083
- pruneUnused: config.variables?.pruneUnused,
3084
- onDiagnostic: context?.onDiagnostic
3085
- });
3086
- rawVariables = normalizeVariablesConfig(config.variables);
3087
- safeSet = new Set(config.variables?.safeList ?? []);
3088
- },
3089
- configureEngine(engine) {
3090
- engine.variables = {
3091
- store: /* @__PURE__ */ new Map(),
3092
- add: (variables) => {
3093
- resolveVariables(variables).forEach((resolved) => {
3094
- const { name, value, autocomplete: { asValueOf, asProperty } } = resolved;
3095
- const cssProperties = Object.fromEntries(asValueOf.filter((p) => p !== "-").map((p) => [p, `var(${name})`]));
3096
- engine.appendAutocomplete({
3097
- cssProperties,
3098
- extraCssProperties: asProperty ? name : void 0
3099
- });
3100
- if (value != null) {
3101
- const list = engine.variables.store.get(name) ?? [];
3102
- list.push(resolved);
3103
- engine.variables.store.set(name, list);
3104
- }
3105
- });
3106
- engine.notifyPreflightUpdated();
3107
- }
3108
- };
3109
- rawVariables.forEach((variables) => engine.variables.add(variables));
3110
- engine.addPreflight({
3111
- id: "core:variables",
3112
- preflight: async (engine, isFormatted, ctx) => {
3113
- const used = /* @__PURE__ */ new Set();
3114
- engine.store.atomicStyles.forEach(({ content: { value } }, id) => {
3115
- if (ctx?.usedAtomicStyleIds != null && ctx.usedAtomicStyleIds.has(id) === false) return;
3116
- value.flatMap(extractUsedVarNames).forEach((name) => used.add(normalizeVariableName(name)));
3117
- });
3118
- const otherPreflights = engine.config.preflights.filter((p) => p.id !== "core:variables");
3119
- (await Promise.all(otherPreflights.map(({ fn }) => engine.invokePreflight(fn, isFormatted, ctx).catch(() => null)))).forEach((result) => {
3120
- if (result == null) return;
3121
- extractUsedVarNamesFromPreflightResult(result).forEach((name) => used.add(name));
3122
- });
3123
- const varMap = engine.variables.store;
3124
- for (const [name, entries] of varMap.entries()) {
3125
- if (used.has(name)) continue;
3126
- if (safeSet.has(name) || entries.some((entry) => entry.pruneUnused === false)) used.add(name);
3127
- }
3128
- const queue = Array.from(used);
3129
- while (queue.length > 0) {
3130
- const name = queue.pop();
3131
- const entries = varMap.get(name);
3132
- if (!entries) continue;
3133
- for (const { value } of entries) {
3134
- const referencedValue = Array.isArray(value) ? value.join(" ") : String(value);
3135
- for (const refName of extractUsedVarNames(referencedValue).map(normalizeVariableName)) if (!used.has(refName)) {
3136
- used.add(refName);
3137
- queue.push(refName);
3138
- }
3139
- }
3140
- }
3141
- const usedVariables = Array.from(engine.variables.store.values()).flat().filter(({ name, pruneUnused, value }) => (safeSet.has(name) || pruneUnused === false || used.has(name)) && value != null);
3142
- const preflightDefinition = {};
3143
- for (const { name, value, selector: _selector } of usedVariables) {
3144
- const selector = await engine.pluginHooks.transformSelectors(engine.config.plugins, _selector);
3145
- let current = preflightDefinition;
3146
- selector.forEach((s) => {
3147
- current[s] ||= {};
3148
- current = current[s];
3149
- });
3150
- Object.assign(current, { [name]: value });
3151
- }
3152
- return preflightDefinition;
3153
- }
3154
- });
3155
- }
3156
- });
3157
- }
3158
3414
  function normalizeVariablesConfig(config) {
3159
3415
  if (config == null) return [];
3160
3416
  const merged = {};
@@ -3171,49 +3427,236 @@ function mergeVariablesDefinition(target, source) {
3171
3427
  }
3172
3428
  return target;
3173
3429
  }
3430
+ function resolveSuggestTargets(asValueOf) {
3431
+ if (asValueOf === false || asValueOf == null) return [];
3432
+ return [...new Set([asValueOf].flat().map(String))];
3433
+ }
3174
3434
  function createResolveVariablesFn({ pruneUnused: defaultPruneUnused = true, onDiagnostic = noopDiagnosticHandler } = {}) {
3175
- function _resolveVariables(variables, levels, result) {
3176
- for (const [key, value] of Object.entries(variables)) if (key.startsWith("--")) {
3177
- const { value: varValue, autocomplete = {}, pruneUnused = defaultPruneUnused } = isPlainObjectRecord(value) ? value : { value };
3178
- result.push({
3179
- name: key,
3180
- value: varValue,
3181
- selector: levels.length > 0 ? levels : [":root"],
3182
- autocomplete: {
3183
- asValueOf: resolveAutocompleteValueTargets({ asValueOf: autocomplete.asValueOf }),
3184
- asProperty: autocomplete.asProperty ?? true
3185
- },
3186
- pruneUnused
3187
- });
3188
- } else {
3189
- if (!isPlainObjectRecord(value)) {
3190
- const message = `Invalid variables scope for selector "${key}". Expected a nested object, received ${typeof value}. Skipping.`;
3191
- if (onDiagnostic === noopDiagnosticHandler) log.warn(message);
3192
- else emitDiagnostic(onDiagnostic, {
3193
- level: "warning",
3194
- code: "variables-invalid-scope",
3195
- 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
3196
3480
  });
3197
3481
  continue;
3198
3482
  }
3199
- _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);
3200
3488
  }
3201
3489
  return result;
3202
3490
  }
3203
- return function resolveVariables(variables) {
3204
- return _resolveVariables(variables, [], []);
3205
- };
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;
3206
3504
  }
3207
- function resolveAutocompleteValueTargets({ asValueOf }) {
3208
- const explicitTargets = asValueOf == null ? [] : [asValueOf].flat().map((value) => String(value));
3209
- if (explicitTargets.includes("-")) return [];
3210
- const targets = /* @__PURE__ */ new Set();
3211
- if (asValueOf == null) targets.add("*");
3212
- explicitTargets.forEach((target) => {
3213
- targets.add(target);
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");
3560
+ }
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
+ }
3214
3659
  });
3215
- if (targets.has("*")) return ["*"];
3216
- return [...targets];
3217
3660
  }
3218
3661
  const VAR_NAME_RE = /var\(\s*(--[\w-]+)/g;
3219
3662
  /**
@@ -3319,6 +3762,26 @@ const DEFAULT_LAYERS = {
3319
3762
  [DEFAULT_PREFLIGHTS_LAYER]: 1,
3320
3763
  [DEFAULT_UTILITIES_LAYER]: 10
3321
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
+ }
3322
3785
  /**
3323
3786
  * Creates and initializes a PikaCSS engine with the given configuration.
3324
3787
  *
@@ -3339,14 +3802,17 @@ async function createEngine(config = {}, options = {}) {
3339
3802
  config = cloneEngineConfig(config);
3340
3803
  const hostOnDiagnostic = options.onDiagnostic ?? noopDiagnosticHandler;
3341
3804
  const onDiagnostic = (diagnostic) => emitDiagnostic(hostOnDiagnostic, diagnostic);
3805
+ const host = snapshotEngineHostContext(options.host);
3806
+ const atomicStyleIdStrategy = options.atomicStyleIdStrategy ?? defaultAtomicStyleIdStrategy;
3342
3807
  const pluginHooks = createEngineHooks({
3343
3808
  onDiagnostic,
3344
- host: options.host ?? {}
3809
+ host
3345
3810
  });
3346
3811
  log.debug("Creating engine with config:", config);
3347
3812
  const corePlugins = [
3348
3813
  variables(),
3349
3814
  keyframes(),
3815
+ layers(),
3350
3816
  selectors(),
3351
3817
  shortcuts(),
3352
3818
  important()
@@ -3363,20 +3829,18 @@ async function createEngine(config = {}, options = {}) {
3363
3829
  let resolvedConfig = await resolveEngineConfig(config);
3364
3830
  log.debug("Engine config resolved with prefix:", resolvedConfig.prefix);
3365
3831
  resolvedConfig = await pluginHooks.configureResolvedConfig(resolvedConfig.plugins, resolvedConfig);
3366
- let engine = new Engine(resolvedConfig, hostOnDiagnostic, pluginHooks);
3367
- engine.appendAutocomplete({
3368
- extraProperties: "__layer",
3369
- properties: { __layer: "Autocomplete['Layer']" }
3370
- });
3832
+ let engine = new Engine(resolvedConfig, hostOnDiagnostic, pluginHooks, atomicStyleIdStrategy);
3833
+ engineInitializationStates.get(engine).onConfigDependency = options.onConfigDependency;
3371
3834
  log.debug("Engine instance created");
3372
3835
  engine = await pluginHooks.configureEngine(engine.config.plugins, engine);
3836
+ await finalizeEngineInitialization(engine);
3373
3837
  log.debug("Engine initialized successfully");
3374
3838
  return engine;
3375
3839
  }
3376
3840
  /**
3377
3841
  * The PikaCSS engine: manages atomic style resolution, rendering, preflights, and plugin hooks.
3378
3842
  *
3379
- * @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`).
3380
3844
  *
3381
3845
  * @example
3382
3846
  * ```ts
@@ -3392,16 +3856,21 @@ var Engine = class {
3392
3856
  onDiagnostic;
3393
3857
  /** Reference to the instance-scoped plugin hook dispatcher. */
3394
3858
  pluginHooks;
3859
+ /** Finalized/read-side first-level Pika static authoring extension registry. */
3860
+ pika;
3861
+ /** Finalized/read-side Typegen semantic registry. */
3862
+ typegen;
3395
3863
  /** The extraction function that decomposes style definitions into atomic style contents. */
3396
3864
  extract;
3397
3865
  /** The engine's runtime store holding registered atomic styles and their ID mappings. */
3398
3866
  store = createEngineStore();
3399
- /**
3400
- * Absolute paths of external files this engine's config depends on (e.g. token files loaded by plugins).
3401
- *
3402
- * @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.
3403
- */
3404
- 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;
3405
3874
  /**
3406
3875
  * Creates an engine instance from a resolved configuration.
3407
3876
  *
@@ -3414,11 +3883,18 @@ var Engine = class {
3414
3883
  * const engine = new Engine(resolvedConfig)
3415
3884
  * ```
3416
3885
  */
3417
- 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;
3418
3892
  const safeOnDiagnostic = (diagnostic) => emitDiagnostic(onDiagnostic, diagnostic);
3419
3893
  this.config = config;
3420
3894
  this.onDiagnostic = safeOnDiagnostic;
3421
3895
  this.pluginHooks = pluginHooks ?? createEngineHooks({ onDiagnostic: safeOnDiagnostic });
3896
+ this.pika = createPikaManager();
3897
+ this.typegen = createTypegenManager();
3422
3898
  this.extract = createExtractFn({
3423
3899
  defaultSelector: this.config.defaultSelector,
3424
3900
  transformSelectors: (selectors) => this.pluginHooks.transformSelectors(this.config.plugins, selectors),
@@ -3459,23 +3935,34 @@ var Engine = class {
3459
3935
  return invocation;
3460
3936
  }
3461
3937
  /**
3462
- * Registers an external file path as a config dependency of this engine.
3463
- *
3464
- * @param path - The file path (ideally absolute) the current config was derived from.
3938
+ * Registers a file dependency during Engine initialization.
3465
3939
  *
3466
- * @remarks Call from a plugin after loading data from disk — typically in `configureEngine`, but registering during later hooks (e.g. while resolving inside `engine.use()`) is fully supported: each genuinely new path fires the `configDependencyAdded` committed notification so integration layers can extend an already-running watcher (#122). Integration layers watch registered paths and rebuild the engine when any of them changes.
3467
- *
3468
- * @example
3469
- * ```ts
3470
- * engine.addConfigDependency('/project/design.md')
3471
- * ```
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.
3472
3942
  */
3473
3943
  addConfigDependency(path) {
3474
- if (this.configDependencies.has(path)) return;
3475
- this.configDependencies.add(path);
3476
- try {
3477
- this.pluginHooks.configDependencyAdded(this.config.plugins, path);
3478
- } catch {}
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);
3479
3966
  }
3480
3967
  /**
3481
3968
  * Fires the `preflightUpdated` hook to notify plugins that preflight content has changed.
@@ -3507,35 +3994,6 @@ var Engine = class {
3507
3994
  this.pluginHooks.atomicStyleAdded(this.config.plugins, atomicStyle);
3508
3995
  }
3509
3996
  /**
3510
- * Fires the `autocompleteConfigUpdated` hook to notify plugins that autocomplete entries changed.
3511
- *
3512
- *
3513
- * @remarks Called automatically after `appendAutocomplete` when the contribution modifies the resolved autocomplete config.
3514
- *
3515
- * @example
3516
- * ```ts
3517
- * engine.notifyAutocompleteConfigUpdated()
3518
- * ```
3519
- */
3520
- notifyAutocompleteConfigUpdated() {
3521
- this.pluginHooks.autocompleteConfigUpdated(this.config.plugins);
3522
- }
3523
- /**
3524
- * Merges an autocomplete contribution into the resolved autocomplete config.
3525
- *
3526
- * @param contribution - The autocomplete entries to append (selectors, properties, CSS properties, etc.).
3527
- *
3528
- * @remarks Delegates to the `appendAutocomplete` utility and fires `autocompleteConfigUpdated` if the config was actually modified.
3529
- *
3530
- * @example
3531
- * ```ts
3532
- * engine.appendAutocomplete({ selectors: 'hover', cssProperties: { color: 'red' } })
3533
- * ```
3534
- */
3535
- appendAutocomplete(contribution) {
3536
- if (appendAutocomplete(this.config, contribution)) this.notifyAutocompleteConfigUpdated();
3537
- }
3538
- /**
3539
3997
  * Appends a CSS `@import` statement to the preflight output.
3540
3998
  *
3541
3999
  * @param cssImport - The raw `@import` string (a trailing semicolon is appended if missing).
@@ -3631,7 +4089,8 @@ var Engine = class {
3631
4089
  content,
3632
4090
  prefix: this.config.prefix,
3633
4091
  store: this.store,
3634
- resolvedIdsByBaseKey
4092
+ resolvedIdsByBaseKey,
4093
+ atomicStyleIdStrategy: this.#atomicStyleIdStrategy
3635
4094
  });
3636
4095
  resolvedIds.push(id);
3637
4096
  resolvedIdsByBaseKey.set(getAtomicStyleBaseKey(content), id);
@@ -3935,9 +4394,9 @@ function resolvePreflight(preflight) {
3935
4394
  * @internal
3936
4395
  *
3937
4396
  * @param config - The raw engine configuration.
3938
- * @returns A `ResolvedEngineConfig` with defaults applied, plugins sorted, preflights resolved, and autocomplete initialized.
4397
+ * @returns A `ResolvedEngineConfig` with defaults applied, plugins sorted, preflights resolved, .
3939
4398
  *
3940
- * @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.
3941
4400
  *
3942
4401
  * @example
3943
4402
  * ```ts
@@ -3959,23 +4418,8 @@ async function resolveEngineConfig(config) {
3959
4418
  cssImports: [...new Set(cssImports.map(normalizeCssImport).filter(isNotNullish))],
3960
4419
  layers,
3961
4420
  defaultPreflightsLayer,
3962
- defaultUtilitiesLayer,
3963
- autocomplete: {
3964
- selectors: /* @__PURE__ */ new Set(),
3965
- shortcuts: /* @__PURE__ */ new Set(),
3966
- extraProperties: /* @__PURE__ */ new Set(),
3967
- extraCssProperties: /* @__PURE__ */ new Set(),
3968
- properties: /* @__PURE__ */ new Map(),
3969
- cssProperties: /* @__PURE__ */ new Map(),
3970
- patterns: {
3971
- selectors: /* @__PURE__ */ new Set(),
3972
- shortcuts: /* @__PURE__ */ new Set(),
3973
- properties: /* @__PURE__ */ new Map(),
3974
- cssProperties: /* @__PURE__ */ new Map()
3975
- }
3976
- }
4421
+ defaultUtilitiesLayer
3977
4422
  };
3978
- appendAutocomplete(resolvedConfig, config.autocomplete ?? {});
3979
4423
  const resolvedPreflights = preflights.map(resolvePreflight);
3980
4424
  resolvedConfig.preflights.push(...resolvedPreflights);
3981
4425
  log.debug(`Engine config resolved: ${resolvedPreflights.length} preflights processed`);
@@ -4191,6 +4635,115 @@ async function renderPreflightDefinition(payload) {
4191
4635
  }), isFormatted);
4192
4636
  }
4193
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
4194
4747
  //#region src/index.ts
4195
4748
  /**
4196
4749
  * Identity helper that returns the engine configuration as-is, providing TypeScript type inference and autocompletion.
@@ -4199,11 +4752,16 @@ async function renderPreflightDefinition(payload) {
4199
4752
  * @param config - The engine configuration object.
4200
4753
  * @returns The same configuration object, unchanged.
4201
4754
  *
4202
- * @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.*`.
4203
4756
  *
4204
4757
  * @example
4205
4758
  * ```ts
4206
- * export default defineEngineConfig({ prefix: 'pk-', plugins: [myPlugin()] })
4759
+ * import { defineEngineConfig } from '@pikacss/core'
4760
+ *
4761
+ * const engineConfig = defineEngineConfig({
4762
+ * prefix: 'pk-',
4763
+ * plugins: [],
4764
+ * })
4207
4765
  * ```
4208
4766
  */
4209
4767
  function defineEngineConfig(config) {
@@ -4211,4 +4769,4 @@ function defineEngineConfig(config) {
4211
4769
  }
4212
4770
  /* c8 ignore end */
4213
4771
  //#endregion
4214
- export { appendAutocomplete, createEngine, createLogger, defineEngineConfig, defineEnginePlugin, escapeRegExp, isPlainObjectRecord, log, renderCSSStyleBlocks, sortLayerNames };
4772
+ export { createEngine, createLogger, defineEngineConfig, defineEnginePlugin, escapeRegExp, isPlainObjectRecord, log, renderCSSStyleBlocks, renderTypegenDocument, renderTypegenJSDoc, sortLayerNames };