@tenphi/tasty 0.0.0-snapshot.b3fbcbb → 0.0.0-snapshot.bae20d4

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 (163) hide show
  1. package/README.md +130 -7
  2. package/dist/chunks/cacheKey.js +16 -8
  3. package/dist/chunks/cacheKey.js.map +1 -1
  4. package/dist/chunks/definitions.js +1 -2
  5. package/dist/chunks/definitions.js.map +1 -1
  6. package/dist/chunks/renderChunk.js +31 -32
  7. package/dist/chunks/renderChunk.js.map +1 -1
  8. package/dist/config.d.ts +45 -10
  9. package/dist/config.js +75 -13
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/index.d.ts +9 -8
  12. package/dist/core/index.js +7 -6
  13. package/dist/hooks/useGlobalStyles.d.ts +3 -0
  14. package/dist/hooks/useGlobalStyles.js +28 -1
  15. package/dist/hooks/useGlobalStyles.js.map +1 -1
  16. package/dist/hooks/useKeyframes.js +18 -3
  17. package/dist/hooks/useKeyframes.js.map +1 -1
  18. package/dist/hooks/useProperty.js +36 -13
  19. package/dist/hooks/useProperty.js.map +1 -1
  20. package/dist/hooks/useRawCSS.js +13 -1
  21. package/dist/hooks/useRawCSS.js.map +1 -1
  22. package/dist/hooks/useStyles.d.ts +5 -0
  23. package/dist/hooks/useStyles.js +87 -7
  24. package/dist/hooks/useStyles.js.map +1 -1
  25. package/dist/index.d.ts +9 -8
  26. package/dist/index.js +7 -6
  27. package/dist/injector/index.d.ts +9 -1
  28. package/dist/injector/index.js +9 -1
  29. package/dist/injector/index.js.map +1 -1
  30. package/dist/injector/injector.d.ts +9 -0
  31. package/dist/injector/injector.js +21 -17
  32. package/dist/injector/injector.js.map +1 -1
  33. package/dist/injector/sheet-manager.d.ts +1 -0
  34. package/dist/injector/sheet-manager.js +1 -0
  35. package/dist/injector/sheet-manager.js.map +1 -1
  36. package/dist/parser/classify.js +2 -1
  37. package/dist/parser/classify.js.map +1 -1
  38. package/dist/parser/parser.js +1 -1
  39. package/dist/pipeline/index.d.ts +1 -1
  40. package/dist/pipeline/index.js +24 -14
  41. package/dist/pipeline/index.js.map +1 -1
  42. package/dist/pipeline/materialize.js +380 -119
  43. package/dist/pipeline/materialize.js.map +1 -1
  44. package/dist/pipeline/parseStateKey.d.ts +1 -1
  45. package/dist/pipeline/parseStateKey.js +20 -11
  46. package/dist/pipeline/parseStateKey.js.map +1 -1
  47. package/dist/plugins/okhsl-plugin.js +2 -275
  48. package/dist/plugins/okhsl-plugin.js.map +1 -1
  49. package/dist/plugins/types.d.ts +9 -2
  50. package/dist/properties/index.js +14 -28
  51. package/dist/properties/index.js.map +1 -1
  52. package/dist/properties/property-type-resolver.js +12 -0
  53. package/dist/properties/property-type-resolver.js.map +1 -1
  54. package/dist/ssr/astro.d.ts +29 -0
  55. package/dist/ssr/astro.js +65 -0
  56. package/dist/ssr/astro.js.map +1 -0
  57. package/dist/ssr/async-storage.d.ts +17 -0
  58. package/dist/ssr/async-storage.js +35 -0
  59. package/dist/ssr/async-storage.js.map +1 -0
  60. package/dist/ssr/collect-auto-properties.js +40 -0
  61. package/dist/ssr/collect-auto-properties.js.map +1 -0
  62. package/dist/ssr/collector.d.ts +85 -0
  63. package/dist/ssr/collector.js +183 -0
  64. package/dist/ssr/collector.js.map +1 -0
  65. package/dist/ssr/context.d.ts +8 -0
  66. package/dist/ssr/context.js +14 -0
  67. package/dist/ssr/context.js.map +1 -0
  68. package/dist/ssr/format-global-rules.js +22 -0
  69. package/dist/ssr/format-global-rules.js.map +1 -0
  70. package/dist/ssr/format-keyframes.js +70 -0
  71. package/dist/ssr/format-keyframes.js.map +1 -0
  72. package/dist/ssr/format-property.js +50 -0
  73. package/dist/ssr/format-property.js.map +1 -0
  74. package/dist/ssr/format-rules.js +70 -0
  75. package/dist/ssr/format-rules.js.map +1 -0
  76. package/dist/ssr/hydrate.d.ts +22 -0
  77. package/dist/ssr/hydrate.js +50 -0
  78. package/dist/ssr/hydrate.js.map +1 -0
  79. package/dist/ssr/index.d.ts +5 -0
  80. package/dist/ssr/index.js +12 -0
  81. package/dist/ssr/index.js.map +1 -0
  82. package/dist/ssr/next.d.ts +45 -0
  83. package/dist/ssr/next.js +71 -0
  84. package/dist/ssr/next.js.map +1 -0
  85. package/dist/ssr/ssr-collector-ref.js +12 -0
  86. package/dist/ssr/ssr-collector-ref.js.map +1 -0
  87. package/dist/states/index.js +10 -257
  88. package/dist/states/index.js.map +1 -1
  89. package/dist/styles/color.js +9 -5
  90. package/dist/styles/color.js.map +1 -1
  91. package/dist/styles/createStyle.js +24 -21
  92. package/dist/styles/createStyle.js.map +1 -1
  93. package/dist/styles/index.js +1 -1
  94. package/dist/styles/predefined.d.ts +0 -2
  95. package/dist/styles/predefined.js +0 -3
  96. package/dist/styles/predefined.js.map +1 -1
  97. package/dist/styles/preset.js +1 -1
  98. package/dist/styles/preset.js.map +1 -1
  99. package/dist/styles/scrollbar.d.ts +9 -5
  100. package/dist/styles/scrollbar.js +25 -89
  101. package/dist/styles/scrollbar.js.map +1 -1
  102. package/dist/styles/transition.js +1 -1
  103. package/dist/styles/transition.js.map +1 -1
  104. package/dist/styles/types.d.ts +24 -14
  105. package/dist/tasty.d.ts +20 -21
  106. package/dist/tasty.js +24 -11
  107. package/dist/tasty.js.map +1 -1
  108. package/dist/types.d.ts +1 -1
  109. package/dist/utils/cache-wrapper.js +4 -8
  110. package/dist/utils/cache-wrapper.js.map +1 -1
  111. package/dist/utils/color-math.d.ts +45 -0
  112. package/dist/utils/color-math.js +749 -0
  113. package/dist/utils/color-math.js.map +1 -0
  114. package/dist/utils/color-space.d.ts +5 -0
  115. package/dist/utils/color-space.js +228 -0
  116. package/dist/utils/color-space.js.map +1 -0
  117. package/dist/utils/colors.js +3 -1
  118. package/dist/utils/colors.js.map +1 -1
  119. package/dist/utils/has-keys.js +13 -0
  120. package/dist/utils/has-keys.js.map +1 -0
  121. package/dist/utils/mod-attrs.js +1 -1
  122. package/dist/utils/process-tokens.d.ts +3 -13
  123. package/dist/utils/process-tokens.js +17 -97
  124. package/dist/utils/process-tokens.js.map +1 -1
  125. package/dist/utils/selector-transform.js +32 -0
  126. package/dist/utils/selector-transform.js.map +1 -0
  127. package/dist/utils/styles.d.ts +2 -79
  128. package/dist/utils/styles.js +26 -554
  129. package/dist/utils/styles.js.map +1 -1
  130. package/dist/utils/typography.d.ts +24 -13
  131. package/dist/utils/typography.js +6 -16
  132. package/dist/utils/typography.js.map +1 -1
  133. package/dist/zero/babel.d.ts +48 -6
  134. package/dist/zero/babel.js +63 -5
  135. package/dist/zero/babel.js.map +1 -1
  136. package/dist/zero/next.d.ts +44 -30
  137. package/dist/zero/next.js +102 -38
  138. package/dist/zero/next.js.map +1 -1
  139. package/docs/PIPELINE.md +519 -0
  140. package/docs/adoption.md +286 -0
  141. package/docs/comparison.md +413 -0
  142. package/docs/configuration.md +263 -0
  143. package/docs/debug.md +505 -0
  144. package/docs/design-system.md +401 -0
  145. package/docs/dsl.md +540 -0
  146. package/docs/getting-started.md +201 -0
  147. package/docs/injector.md +528 -0
  148. package/docs/methodology.md +501 -0
  149. package/docs/runtime.md +291 -0
  150. package/docs/ssr.md +382 -0
  151. package/docs/styles.md +574 -0
  152. package/docs/tasty-static.md +420 -0
  153. package/package.json +50 -30
  154. package/dist/styles/styledScrollbar.d.ts +0 -47
  155. package/dist/styles/styledScrollbar.js +0 -38
  156. package/dist/styles/styledScrollbar.js.map +0 -1
  157. package/dist/tokens/typography.d.ts +0 -19
  158. package/dist/tokens/typography.js +0 -237
  159. package/dist/tokens/typography.js.map +0 -1
  160. package/dist/utils/hsl-to-rgb.js +0 -38
  161. package/dist/utils/hsl-to-rgb.js.map +0 -1
  162. package/dist/utils/okhsl-to-rgb.js +0 -296
  163. package/dist/utils/okhsl-to-rgb.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Lru } from "../parser/lru.js";
2
- import { getConditionUniqueId, not } from "./conditions.js";
2
+ import { getConditionUniqueId } from "./conditions.js";
3
3
 
4
4
  //#region src/pipeline/materialize.ts
5
5
  /**
@@ -24,11 +24,12 @@ function emptyVariant() {
24
24
  return {
25
25
  modifierConditions: [],
26
26
  pseudoConditions: [],
27
- ownConditions: [],
27
+ selectorGroups: [],
28
+ ownGroups: [],
28
29
  mediaConditions: [],
29
30
  containerConditions: [],
30
31
  supportsConditions: [],
31
- rootConditions: [],
32
+ rootGroups: [],
32
33
  parentGroups: [],
33
34
  startingStyle: false
34
35
  };
@@ -63,9 +64,9 @@ function stateToCSS(state) {
63
64
  }),
64
65
  isImpossible: false
65
66
  };
66
- case "root": return innerConditionToVariants(state.innerCondition, state.negated ?? false, "rootConditions");
67
+ case "root": return innerConditionToVariants(state.innerCondition, state.negated ?? false, "rootGroups");
67
68
  case "parent": return parentConditionToVariants(state.innerCondition, state.negated ?? false, state.direct);
68
- case "own": return innerConditionToVariants(state.innerCondition, state.negated ?? false, "ownConditions");
69
+ case "own": return innerConditionToVariants(state.innerCondition, state.negated ?? false, "ownGroups");
69
70
  case "modifier": {
70
71
  const v = emptyVariant();
71
72
  v.modifierConditions.push(modifierToParsed(state));
@@ -141,14 +142,31 @@ function pseudoToParsed(state) {
141
142
  };
142
143
  }
143
144
  /**
144
- * Convert parsed pseudo to CSS selector string (for final output)
145
+ * Convert parsed pseudo to CSS selector string (for final output).
146
+ *
147
+ * :not() is normalized to negated :is() at parse time, so pseudo.pseudo
148
+ * never starts with ':not(' here. When negated:
149
+ * - :is(X) → :not(X) (unwrap :is)
150
+ * - :where(X) → :not(X) (unwrap :where)
151
+ * - :has(X) → :not(:has(X))
152
+ * - other → :not(other)
153
+ *
154
+ * When not negated, single-argument :is()/:where() is unwrapped when the
155
+ * inner content is a simple compound selector that can safely append to
156
+ * the base selector (this happens after double-negation of :not()).
145
157
  */
146
158
  function pseudoToCSS(pseudo) {
159
+ const p = pseudo.pseudo;
147
160
  if (pseudo.negated) {
148
- if (pseudo.pseudo.startsWith(":not(")) return pseudo.pseudo.slice(5, -1);
149
- return `:not(${pseudo.pseudo})`;
161
+ if (p.startsWith(":is(") || p.startsWith(":where(")) return `:not(${p.slice(p.indexOf("(") + 1, -1)})`;
162
+ return `:not(${p})`;
150
163
  }
151
- return pseudo.pseudo;
164
+ if ((p.startsWith(":is(") || p.startsWith(":where(")) && !p.includes(",")) {
165
+ const inner = p.slice(p.indexOf("(") + 1, -1);
166
+ const ch = inner[0];
167
+ if ((ch === ":" || ch === "." || ch === "[" || ch === "#") && !/\s/.test(inner)) return inner;
168
+ }
169
+ return p;
152
170
  }
153
171
  /**
154
172
  * Convert media condition to parsed structure(s)
@@ -247,40 +265,50 @@ function collectSelectorConditions(variant) {
247
265
  return [...variant.modifierConditions, ...variant.pseudoConditions];
248
266
  }
249
267
  /**
250
- * Convert an inner condition tree into SelectorVariants.
251
- * Each inner OR branch becomes a separate variant, preserving disjunction.
268
+ * Convert an inner condition tree into a single SelectorVariant with
269
+ * one SelectorGroup whose branches represent the inner OR alternatives.
252
270
  * Shared by @root() and @own().
271
+ *
272
+ * Both positive and negated cases produce one variant with one group.
273
+ * Negation simply sets the `negated` flag, which swaps :is() for :not()
274
+ * in the final CSS output — no De Morgan transformation is needed.
275
+ *
276
+ * This mirrors parentConditionToVariants: OR branches are kept inside
277
+ * a single group and rendered as comma-separated arguments in
278
+ * :is()/:not(), e.g. :root:is([a], [b]) or [el]:not([a], [b]).
253
279
  */
254
280
  function innerConditionToVariants(innerCondition, negated, target) {
255
- const innerCSS = conditionToCSS(negated ? not(innerCondition) : innerCondition);
281
+ const innerCSS = conditionToCSS(innerCondition);
256
282
  if (innerCSS.isImpossible || innerCSS.variants.length === 0) return {
257
283
  variants: [],
258
284
  isImpossible: true
259
285
  };
260
- const variants = [];
286
+ const branches = [];
261
287
  for (const innerVariant of innerCSS.variants) {
262
288
  const conditions = collectSelectorConditions(innerVariant);
263
- if (conditions.length > 0) {
264
- const v = emptyVariant();
265
- v[target].push(...conditions);
266
- variants.push(v);
267
- }
289
+ if (conditions.length > 0) branches.push(conditions);
268
290
  }
269
- if (variants.length === 0) return {
291
+ if (branches.length === 0) return {
270
292
  variants: [emptyVariant()],
271
293
  isImpossible: false
272
294
  };
295
+ const v = emptyVariant();
296
+ v[target].push({
297
+ branches,
298
+ negated
299
+ });
273
300
  return {
274
- variants,
301
+ variants: [v],
275
302
  isImpossible: false
276
303
  };
277
304
  }
278
305
  /**
279
- * Convert a @parent() inner condition into SelectorVariants with ParentGroups.
306
+ * Convert a @parent() inner condition into a single SelectorVariant with
307
+ * one ParentGroup whose branches represent the inner OR alternatives.
280
308
  *
281
- * Positive: each inner OR branch becomes a separate variant with one :is() group.
282
- * Negated: !(A | B) = !A & !B all branches become :not() groups collected
283
- * into a single variant so they produce :not([a] *):not([b] *) on one element.
309
+ * Both positive and negated cases produce one variant with one group.
310
+ * Negation simply sets the `negated` flag, which swaps :is() for :not()
311
+ * in the final CSS output no structural transformation is needed.
284
312
  */
285
313
  function parentConditionToVariants(innerCondition, negated, direct) {
286
314
  const innerCSS = conditionToCSS(innerCondition);
@@ -288,68 +316,178 @@ function parentConditionToVariants(innerCondition, negated, direct) {
288
316
  variants: [],
289
317
  isImpossible: true
290
318
  };
291
- if (negated) {
292
- const v = emptyVariant();
293
- for (const innerVariant of innerCSS.variants) {
294
- const conditions = collectSelectorConditions(innerVariant);
295
- if (conditions.length > 0) v.parentGroups.push({
296
- conditions,
297
- direct,
298
- negated: true
299
- });
300
- }
301
- if (v.parentGroups.length === 0) return {
302
- variants: [emptyVariant()],
303
- isImpossible: false
304
- };
305
- return {
306
- variants: [v],
307
- isImpossible: false
308
- };
309
- }
310
- const variants = [];
319
+ const branches = [];
311
320
  for (const innerVariant of innerCSS.variants) {
312
321
  const conditions = collectSelectorConditions(innerVariant);
313
- if (conditions.length > 0) {
314
- const v = emptyVariant();
315
- v.parentGroups.push({
316
- conditions,
317
- direct,
318
- negated: false
319
- });
320
- variants.push(v);
321
- }
322
+ if (conditions.length > 0) branches.push(conditions);
322
323
  }
323
- if (variants.length === 0) return {
324
+ if (branches.length === 0) return {
324
325
  variants: [emptyVariant()],
325
326
  isImpossible: false
326
327
  };
328
+ const v = emptyVariant();
329
+ v.parentGroups.push({
330
+ branches,
331
+ direct,
332
+ negated
333
+ });
327
334
  return {
328
- variants,
335
+ variants: [v],
329
336
  isImpossible: false
330
337
  };
331
338
  }
332
339
  /**
333
- * Convert parsed root conditions to CSS selector prefix (for final output)
340
+ * Sort key for canonical condition output within selectors.
341
+ *
342
+ * Priority order:
343
+ * 0: Boolean attribute selectors ([data-hovered])
344
+ * 1: Value attribute selectors ([data-size="small"])
345
+ * 2: Negated boolean attributes (:not([data-disabled]))
346
+ * 3: Negated value attributes (:not([data-size="small"]))
347
+ * 4: Pseudo-classes (:hover, :focus)
348
+ * 5: Negated pseudo-classes (:not(:disabled))
349
+ *
350
+ * Secondary sort: alphabetical by attribute name / pseudo string.
351
+ */
352
+ function conditionSortKey(cond) {
353
+ if ("attribute" in cond) {
354
+ const hasValue = cond.value !== void 0 ? 1 : 0;
355
+ return `${(cond.negated ? 2 : 0) + hasValue}|${cond.attribute}|${cond.value ?? ""}`;
356
+ }
357
+ return `${cond.negated ? 5 : 4}|${cond.pseudo}`;
358
+ }
359
+ function sortConditions(conditions) {
360
+ return conditions.toSorted((a, b) => conditionSortKey(a).localeCompare(conditionSortKey(b)));
361
+ }
362
+ function branchToCSS(branch) {
363
+ let parts = "";
364
+ for (const cond of sortConditions(branch)) parts += selectorConditionToCSS(cond);
365
+ return parts;
366
+ }
367
+ /**
368
+ * Wrap serialized selector arguments in :is() or :not().
369
+ * Arguments are sorted for canonical output.
370
+ */
371
+ function wrapInIsOrNot(args, negated) {
372
+ return `${negated ? ":not" : ":is"}(${args.sort().join(", ")})`;
373
+ }
374
+ /**
375
+ * Convert a selector group to a CSS selector fragment.
376
+ *
377
+ * Single-branch groups are unwrapped (no :is() wrapper).
378
+ * Multi-branch groups use :is() or :not().
379
+ * Negation swaps :is() for :not().
380
+ */
381
+ function selectorGroupToCSS(group) {
382
+ if (group.branches.length === 0) return "";
383
+ if (group.branches.length === 1) {
384
+ const parts = branchToCSS(group.branches[0]);
385
+ if (group.negated) return `:not(${parts})`;
386
+ return parts;
387
+ }
388
+ return wrapInIsOrNot(group.branches.map(branchToCSS), group.negated);
389
+ }
390
+ /**
391
+ * Collect facts about modifier conditions for subsumption analysis.
392
+ * Tracks negated boolean attrs (:not([attr])) and positive exact values ([attr="X"]).
393
+ */
394
+ function collectSubsumptionFacts(modifiers) {
395
+ const negatedBooleanAttrs = /* @__PURE__ */ new Set();
396
+ const positiveExactValuesByAttr = /* @__PURE__ */ new Map();
397
+ for (const mod of modifiers) {
398
+ if (mod.negated && mod.value === void 0) negatedBooleanAttrs.add(mod.attribute);
399
+ if (!mod.negated && mod.value !== void 0 && (mod.operator ?? "=") === "=") {
400
+ let vals = positiveExactValuesByAttr.get(mod.attribute);
401
+ if (!vals) {
402
+ vals = /* @__PURE__ */ new Set();
403
+ positiveExactValuesByAttr.set(mod.attribute, vals);
404
+ }
405
+ vals.add(mod.value);
406
+ }
407
+ }
408
+ return {
409
+ negatedBooleanAttrs,
410
+ positiveExactValuesByAttr
411
+ };
412
+ }
413
+ /**
414
+ * Check if a negated-value modifier is subsumed by stronger facts:
415
+ * - :not([attr]) subsumes :not([attr="val"])
416
+ * - [attr="X"] implies :not([attr="Y"]) is redundant (single exact value)
417
+ *
418
+ * Only applies to exact-match (=) operators; substring operators don't
419
+ * imply exclusivity between values.
420
+ */
421
+ function isSubsumedNegatedModifier(mod, facts) {
422
+ if (!mod.negated || mod.value === void 0) return false;
423
+ if (facts.negatedBooleanAttrs.has(mod.attribute)) return true;
424
+ if ((mod.operator ?? "=") === "=") {
425
+ const posVals = facts.positiveExactValuesByAttr.get(mod.attribute);
426
+ if (posVals && posVals.size === 1 && !posVals.has(mod.value)) return true;
427
+ }
428
+ return false;
429
+ }
430
+ /**
431
+ * Remove redundant single-condition groups that are subsumed by stronger
432
+ * groups on the same attribute. O(n) — only inspects single-branch,
433
+ * single-condition groups.
434
+ */
435
+ function optimizeGroups(groups) {
436
+ if (groups.length <= 1) return groups;
437
+ const seen = /* @__PURE__ */ new Set();
438
+ const result = [];
439
+ for (const g of groups) {
440
+ const key = getSelectorGroupKey(g);
441
+ if (!seen.has(key)) {
442
+ seen.add(key);
443
+ result.push(g);
444
+ }
445
+ }
446
+ if (result.length <= 1) return result;
447
+ const effectiveModifiers = [];
448
+ for (const g of result) {
449
+ if (g.branches.length !== 1 || g.branches[0].length !== 1) continue;
450
+ const cond = g.branches[0][0];
451
+ if (!("attribute" in cond)) continue;
452
+ effectiveModifiers.push({
453
+ ...cond,
454
+ negated: g.negated !== cond.negated
455
+ });
456
+ }
457
+ const facts = collectSubsumptionFacts(effectiveModifiers);
458
+ if (facts.negatedBooleanAttrs.size === 0 && facts.positiveExactValuesByAttr.size === 0) return result;
459
+ return result.filter((g) => {
460
+ if (g.branches.length !== 1 || g.branches[0].length !== 1) return true;
461
+ const cond = g.branches[0][0];
462
+ if (!("attribute" in cond) || !g.negated || cond.negated || cond.value === void 0) return true;
463
+ return !isSubsumedNegatedModifier({
464
+ ...cond,
465
+ negated: true
466
+ }, facts);
467
+ });
468
+ }
469
+ /**
470
+ * Convert root groups to CSS selector prefix (for final output)
334
471
  */
335
- function rootConditionsToCSS(roots) {
336
- if (roots.length === 0) return void 0;
472
+ function rootGroupsToCSS(groups) {
473
+ if (groups.length === 0) return void 0;
474
+ const optimized = optimizeGroups(groups);
475
+ if (optimized.length === 0) return void 0;
337
476
  let prefix = ":root";
338
- for (const cond of roots) prefix += selectorConditionToCSS(cond);
477
+ for (const group of optimized) prefix += selectorGroupToCSS(group);
339
478
  return prefix;
340
479
  }
341
480
  /**
342
481
  * Convert parent groups to CSS selector fragments (for final output).
343
- * Each group produces its own :is() wrapper.
482
+ * Each group produces its own :is()/:not() wrapper with a combinator
483
+ * suffix (` *` or ` > *`) appended to each branch.
344
484
  */
345
485
  function parentGroupsToCSS(groups) {
346
486
  let result = "";
347
487
  for (const group of groups) {
348
488
  const combinator = group.direct ? " > *" : " *";
349
- let parts = "";
350
- for (const cond of group.conditions) parts += selectorConditionToCSS(cond);
351
- const wrapper = group.negated ? ":not" : ":is";
352
- result += `${wrapper}(${parts}${combinator})`;
489
+ const args = group.branches.map((branch) => branchToCSS(branch) + combinator);
490
+ result += wrapInIsOrNot(args, group.negated);
353
491
  }
354
492
  return result;
355
493
  }
@@ -385,7 +523,7 @@ function getSelectorConditionKey(cond) {
385
523
  */
386
524
  function dedupeSelectorConditions(conditions) {
387
525
  const seen = /* @__PURE__ */ new Set();
388
- let result = [];
526
+ const result = [];
389
527
  for (const c of conditions) {
390
528
  const key = getSelectorConditionKey(c);
391
529
  if (!seen.has(key)) {
@@ -393,30 +531,12 @@ function dedupeSelectorConditions(conditions) {
393
531
  result.push(c);
394
532
  }
395
533
  }
396
- const negatedBooleanAttrs = /* @__PURE__ */ new Set();
397
- const positiveExactValuesByAttr = /* @__PURE__ */ new Map();
398
- for (const c of result) {
399
- if (!("attribute" in c)) continue;
400
- if (c.negated && c.value === void 0) negatedBooleanAttrs.add(c.attribute);
401
- const op = c.operator ?? "=";
402
- if (!c.negated && c.value !== void 0 && op === "=") {
403
- let values = positiveExactValuesByAttr.get(c.attribute);
404
- if (!values) {
405
- values = /* @__PURE__ */ new Set();
406
- positiveExactValuesByAttr.set(c.attribute, values);
407
- }
408
- values.add(c.value);
409
- }
410
- }
411
- result = result.filter((c) => {
412
- if (!("attribute" in c) || !c.negated || c.value === void 0) return true;
413
- if (negatedBooleanAttrs.has(c.attribute)) return false;
414
- if ((c.operator ?? "=") !== "=") return true;
415
- const positiveValues = positiveExactValuesByAttr.get(c.attribute);
416
- if (positiveValues !== void 0 && positiveValues.size === 1 && !positiveValues.has(c.value)) return false;
417
- return true;
534
+ const facts = collectSubsumptionFacts(result.filter((c) => "attribute" in c));
535
+ if (facts.negatedBooleanAttrs.size === 0 && facts.positiveExactValuesByAttr.size === 0) return result;
536
+ return result.filter((c) => {
537
+ if (!("attribute" in c)) return true;
538
+ return !isSubsumedNegatedModifier(c, facts);
418
539
  });
419
- return result;
420
540
  }
421
541
  /**
422
542
  * Check for modifier contradiction: same attribute with opposite negation
@@ -459,10 +579,32 @@ function hasSelectorConditionContradiction(conditions) {
459
579
  * with opposite negation. E.g. :not([data-hovered] *) and :is([data-hovered] *)
460
580
  * in the same variant is impossible.
461
581
  */
582
+ function getBranchesKey(branches) {
583
+ if (branches.length === 1) {
584
+ const b = branches[0];
585
+ if (b.length === 1) return getSelectorConditionKey(b[0]);
586
+ return b.map(getSelectorConditionKey).sort().join("+");
587
+ }
588
+ return branches.map((b) => b.map(getSelectorConditionKey).sort().join("+")).sort().join(",");
589
+ }
462
590
  function hasParentGroupContradiction(groups) {
463
591
  const byBaseKey = /* @__PURE__ */ new Map();
464
592
  for (const g of groups) {
465
- const baseKey = `${g.direct ? ">" : ""}(${g.conditions.map(getSelectorConditionKey).sort().join(",")})`;
593
+ const baseKey = `${g.direct ? ">" : ""}(${getBranchesKey(g.branches)})`;
594
+ const existing = byBaseKey.get(baseKey);
595
+ if (existing !== void 0 && existing !== !g.negated) return true;
596
+ byBaseKey.set(baseKey, !g.negated);
597
+ }
598
+ return false;
599
+ }
600
+ /**
601
+ * Check for selector group contradiction: same branches with opposite negation.
602
+ * E.g. :is([data-a]) and :not([data-a]) in the same variant is impossible.
603
+ */
604
+ function hasSelectorGroupContradiction(groups) {
605
+ const byBaseKey = /* @__PURE__ */ new Map();
606
+ for (const g of groups) {
607
+ const baseKey = getBranchesKey(g.branches);
466
608
  const existing = byBaseKey.get(baseKey);
467
609
  if (existing !== void 0 && existing !== !g.negated) return true;
468
610
  byBaseKey.set(baseKey, !g.negated);
@@ -476,15 +618,17 @@ function hasParentGroupContradiction(groups) {
476
618
  function mergeVariants(a, b) {
477
619
  const mergedMedia = dedupeMediaConditions([...a.mediaConditions, ...b.mediaConditions]);
478
620
  if (hasMediaContradiction(mergedMedia)) return null;
479
- const mergedRoots = dedupeSelectorConditions([...a.rootConditions, ...b.rootConditions]);
480
- if (hasSelectorConditionContradiction(mergedRoots)) return null;
621
+ const mergedRootGroups = optimizeGroups([...a.rootGroups, ...b.rootGroups]);
622
+ if (hasSelectorGroupContradiction(mergedRootGroups)) return null;
481
623
  const mergedModifiers = dedupeSelectorConditions([...a.modifierConditions, ...b.modifierConditions]);
482
624
  const mergedPseudos = dedupeSelectorConditions([...a.pseudoConditions, ...b.pseudoConditions]);
483
625
  if (hasSelectorConditionContradiction([...mergedModifiers, ...mergedPseudos])) return null;
626
+ const mergedSelectorGroups = optimizeGroups([...a.selectorGroups, ...b.selectorGroups]);
627
+ if (hasSelectorGroupContradiction(mergedSelectorGroups)) return null;
484
628
  const mergedParentGroups = [...a.parentGroups, ...b.parentGroups];
485
629
  if (hasParentGroupContradiction(mergedParentGroups)) return null;
486
- const mergedOwn = dedupeSelectorConditions([...a.ownConditions, ...b.ownConditions]);
487
- if (hasSelectorConditionContradiction(mergedOwn)) return null;
630
+ const mergedOwnGroups = optimizeGroups([...a.ownGroups, ...b.ownGroups]);
631
+ if (hasSelectorGroupContradiction(mergedOwnGroups)) return null;
488
632
  const mergedContainers = dedupeContainerConditions([...a.containerConditions, ...b.containerConditions]);
489
633
  if (hasContainerStyleContradiction(mergedContainers)) return null;
490
634
  const mergedSupports = dedupeSupportsConditions([...a.supportsConditions, ...b.supportsConditions]);
@@ -492,11 +636,12 @@ function mergeVariants(a, b) {
492
636
  return {
493
637
  modifierConditions: mergedModifiers,
494
638
  pseudoConditions: mergedPseudos,
495
- ownConditions: mergedOwn,
639
+ selectorGroups: mergedSelectorGroups,
640
+ ownGroups: mergedOwnGroups,
496
641
  mediaConditions: mergedMedia,
497
642
  containerConditions: mergedContainers,
498
643
  supportsConditions: mergedSupports,
499
- rootConditions: mergedRoots,
644
+ rootGroups: mergedRootGroups,
500
645
  parentGroups: mergedParentGroups,
501
646
  startingStyle: a.startingStyle || b.startingStyle
502
647
  };
@@ -627,28 +772,45 @@ const variantKeyCache = /* @__PURE__ */ new WeakMap();
627
772
  * Cached via WeakMap since variants are compared multiple times during
628
773
  * deduplication and sorting.
629
774
  */
630
- function getVariantKey(v) {
631
- const cached = variantKeyCache.get(v);
632
- if (cached !== void 0) return cached;
633
- const modifierKey = v.modifierConditions.map(getModifierKey).sort().join("|");
634
- const pseudoKey = v.pseudoConditions.map(getPseudoKey).sort().join("|");
635
- const ownKey = v.ownConditions.map(getSelectorConditionKey).sort().join("|");
636
- const containerKey = v.containerConditions.map((c) => `${c.name ?? ""}:${c.negated ? "!" : ""}${c.condition}`).sort().join("|");
637
- const key = [
638
- modifierKey,
639
- pseudoKey,
640
- ownKey,
775
+ function getSelectorGroupKey(g) {
776
+ return `${g.negated ? "!" : ""}(${getBranchesKey(g.branches)})`;
777
+ }
778
+ /**
779
+ * Get a context key for a variant — everything except flat modifier/pseudo
780
+ * conditions. Variants with the same context key can be merged into an
781
+ * :is() group. Also used by getVariantKey as the shared non-selector portion.
782
+ */
783
+ function getVariantContextKey(v) {
784
+ return [
641
785
  v.mediaConditions.map((c) => `${c.subtype}:${c.negated ? "!" : ""}${c.condition}`).sort().join("|"),
642
- containerKey,
786
+ v.containerConditions.map((c) => `${c.name ?? ""}:${c.negated ? "!" : ""}${c.condition}`).sort().join("|"),
643
787
  v.supportsConditions.map((c) => `${c.subtype}:${c.negated ? "!" : ""}${c.condition}`).sort().join("|"),
644
- v.rootConditions.map(getSelectorConditionKey).sort().join("|"),
788
+ v.rootGroups.map(getSelectorGroupKey).sort().join("|"),
645
789
  v.parentGroups.map(getParentGroupKey).sort().join("|"),
790
+ v.ownGroups.map(getSelectorGroupKey).sort().join("|"),
791
+ v.selectorGroups.map(getSelectorGroupKey).sort().join("|"),
646
792
  v.startingStyle ? "1" : "0"
647
793
  ].join("###");
794
+ }
795
+ function getVariantKey(v) {
796
+ const cached = variantKeyCache.get(v);
797
+ if (cached !== void 0) return cached;
798
+ const modifierKey = v.modifierConditions.map(getModifierKey).sort().join("|");
799
+ const pseudoKey = v.pseudoConditions.map(getPseudoKey).sort().join("|");
800
+ const key = modifierKey + "###" + pseudoKey + "###" + getVariantContextKey(v);
648
801
  variantKeyCache.set(v, key);
649
802
  return key;
650
803
  }
651
804
  /**
805
+ * Total number of leaf conditions in a variant (for superset / dedup comparisons).
806
+ */
807
+ function groupConditionCount(groups) {
808
+ return groups.reduce((sum, g) => sum + g.branches.reduce((s, b) => s + b.length, 0), 0);
809
+ }
810
+ function variantConditionCount(v) {
811
+ return v.modifierConditions.length + v.pseudoConditions.length + groupConditionCount(v.selectorGroups) + groupConditionCount(v.ownGroups) + v.mediaConditions.length + v.containerConditions.length + v.supportsConditions.length + groupConditionCount(v.rootGroups) + groupConditionCount(v.parentGroups);
812
+ }
813
+ /**
652
814
  * Check if variant A is a superset of variant B (A is more restrictive)
653
815
  *
654
816
  * If A has all of B's conditions plus more, then A is redundant
@@ -661,16 +823,16 @@ function getVariantKey(v) {
661
823
  */
662
824
  function isVariantSuperset(a, b) {
663
825
  if (a.startingStyle !== b.startingStyle) return false;
664
- if (!isSelectorConditionsSuperset(a.rootConditions, b.rootConditions)) return false;
826
+ if (!isSelectorGroupsSuperset(a.rootGroups, b.rootGroups)) return false;
665
827
  if (!isMediaConditionsSuperset(a.mediaConditions, b.mediaConditions)) return false;
666
828
  if (!isContainerConditionsSuperset(a.containerConditions, b.containerConditions)) return false;
667
829
  if (!isSupportsConditionsSuperset(a.supportsConditions, b.supportsConditions)) return false;
668
830
  if (!isModifierConditionsSuperset(a.modifierConditions, b.modifierConditions)) return false;
669
831
  if (!isPseudoConditionsSuperset(a.pseudoConditions, b.pseudoConditions)) return false;
670
- if (!isSelectorConditionsSuperset(a.ownConditions, b.ownConditions)) return false;
832
+ if (!isSelectorGroupsSuperset(a.selectorGroups, b.selectorGroups)) return false;
833
+ if (!isSelectorGroupsSuperset(a.ownGroups, b.ownGroups)) return false;
671
834
  if (!isParentGroupsSuperset(a.parentGroups, b.parentGroups)) return false;
672
- const parentConditionCount = (groups) => groups.reduce((sum, g) => sum + g.conditions.length, 0);
673
- return a.mediaConditions.length + a.containerConditions.length + a.supportsConditions.length + a.modifierConditions.length + a.pseudoConditions.length + a.rootConditions.length + parentConditionCount(a.parentGroups) + a.ownConditions.length > b.mediaConditions.length + b.containerConditions.length + b.supportsConditions.length + b.modifierConditions.length + b.pseudoConditions.length + b.rootConditions.length + parentConditionCount(b.parentGroups) + b.ownConditions.length;
835
+ return variantConditionCount(a) > variantConditionCount(b);
674
836
  }
675
837
  /**
676
838
  * Generic superset check: true if every item in B has a matching key in A.
@@ -694,8 +856,9 @@ function isModifierConditionsSuperset(a, b) {
694
856
  function isPseudoConditionsSuperset(a, b) {
695
857
  return isConditionsSuperset(a, b, getPseudoKey);
696
858
  }
697
- function isSelectorConditionsSuperset(a, b) {
698
- return isConditionsSuperset(a, b, getSelectorConditionKey);
859
+ function isSelectorGroupsSuperset(a, b) {
860
+ if (a.length < b.length) return false;
861
+ return isConditionsSuperset(a, b, getSelectorGroupKey);
699
862
  }
700
863
  /**
701
864
  * Check if parent groups A is a superset of B.
@@ -706,7 +869,7 @@ function isParentGroupsSuperset(a, b) {
706
869
  return isConditionsSuperset(a, b, getParentGroupKey);
707
870
  }
708
871
  function getParentGroupKey(g) {
709
- return `${g.negated ? "!" : ""}${g.direct ? ">" : ""}(${g.conditions.map(getSelectorConditionKey).sort().join(",")})`;
872
+ return `${g.negated ? "!" : ""}${g.direct ? ">" : ""}(${getBranchesKey(g.branches)})`;
710
873
  }
711
874
  /**
712
875
  * Deduplicate variants
@@ -716,6 +879,7 @@ function getParentGroupKey(g) {
716
879
  * 2. Superset variants (more restrictive selectors that are redundant)
717
880
  */
718
881
  function dedupeVariants(variants) {
882
+ if (variants.length <= 1) return variants;
719
883
  const seen = /* @__PURE__ */ new Set();
720
884
  const result = [];
721
885
  for (const v of variants) {
@@ -725,7 +889,7 @@ function dedupeVariants(variants) {
725
889
  result.push(v);
726
890
  }
727
891
  }
728
- const variantConditionCount = (v) => v.modifierConditions.length + v.pseudoConditions.length + v.ownConditions.length + v.mediaConditions.length + v.containerConditions.length + v.supportsConditions.length + v.rootConditions.length + v.parentGroups.reduce((sum, g) => sum + g.conditions.length, 0);
892
+ if (result.length <= 1) return result;
729
893
  result.sort((a, b) => variantConditionCount(a) - variantConditionCount(b));
730
894
  const filtered = [];
731
895
  for (const candidate of result) {
@@ -775,8 +939,8 @@ function andToCSS(children) {
775
939
  * Combine OR conditions into CSS
776
940
  *
777
941
  * OR in CSS means multiple selector variants (DNF).
778
- * Each variant becomes a separate selector in the comma-separated list,
779
- * or multiple CSS rules if they have different at-rules.
942
+ * After deduplication, variants that differ only in their base
943
+ * modifier/pseudo conditions are merged into :is() groups.
780
944
  *
781
945
  * Note: OR exclusivity is handled at the pipeline level (expandOrConditions),
782
946
  * so here we just collect all variants. Any remaining ORs in the condition
@@ -799,6 +963,103 @@ function orToCSS(children) {
799
963
  };
800
964
  }
801
965
  /**
966
+ * Find keys present in ALL condition arrays.
967
+ */
968
+ function findCommonKeys(conditionSets, getKey) {
969
+ if (conditionSets.length === 0) return /* @__PURE__ */ new Set();
970
+ const common = new Set(conditionSets[0].map(getKey));
971
+ for (let i = 1; i < conditionSets.length; i++) {
972
+ const keys = new Set(conditionSets[i].map(getKey));
973
+ for (const key of common) if (!keys.has(key)) common.delete(key);
974
+ }
975
+ return common;
976
+ }
977
+ /**
978
+ * Merge OR variants that share the same "context" (at-rules, root, parent,
979
+ * own, starting) into a single variant with a SelectorGroup.
980
+ *
981
+ * Variants with no modifier/pseudo conditions are kept separate (they match
982
+ * unconditionally and can't be expressed inside :is()).
983
+ */
984
+ function mergeVariantsIntoSelectorGroups(variants) {
985
+ if (variants.length <= 1) return variants;
986
+ const groups = /* @__PURE__ */ new Map();
987
+ for (const v of variants) {
988
+ const key = getVariantContextKey(v);
989
+ const group = groups.get(key);
990
+ if (group) group.push(v);
991
+ else groups.set(key, [v]);
992
+ }
993
+ const result = [];
994
+ for (const group of groups.values()) {
995
+ if (group.length === 1) {
996
+ result.push(group[0]);
997
+ continue;
998
+ }
999
+ const withSelectors = [];
1000
+ const withoutSelectors = [];
1001
+ for (const v of group) if (v.modifierConditions.length === 0 && v.pseudoConditions.length === 0) withoutSelectors.push(v);
1002
+ else withSelectors.push(v);
1003
+ result.push(...withoutSelectors);
1004
+ if (withSelectors.length <= 1) {
1005
+ result.push(...withSelectors);
1006
+ continue;
1007
+ }
1008
+ result.push(factorAndGroup(withSelectors));
1009
+ }
1010
+ return result;
1011
+ }
1012
+ /**
1013
+ * Factor common modifier/pseudo conditions out of variants and create
1014
+ * a single variant with a SelectorGroup for the remaining (differing)
1015
+ * conditions.
1016
+ *
1017
+ * Precondition: all variants must share the same context key (identical
1018
+ * at-rules, root/parent/own/selector groups, startingStyle).
1019
+ */
1020
+ function factorAndGroup(variants) {
1021
+ {
1022
+ const key0 = getVariantContextKey(variants[0]);
1023
+ for (let i = 1; i < variants.length; i++) {
1024
+ const keyI = getVariantContextKey(variants[i]);
1025
+ if (keyI !== key0) throw new Error(`factorAndGroup: context key mismatch at index ${i}.\n expected: ${key0}\n got: ${keyI}`);
1026
+ }
1027
+ }
1028
+ const commonModKeys = findCommonKeys(variants.map((v) => v.modifierConditions), getModifierKey);
1029
+ const commonPseudoKeys = findCommonKeys(variants.map((v) => v.pseudoConditions), getPseudoKey);
1030
+ const commonModifiers = variants[0].modifierConditions.filter((m) => commonModKeys.has(getModifierKey(m)));
1031
+ const commonPseudos = variants[0].pseudoConditions.filter((p) => commonPseudoKeys.has(getPseudoKey(p)));
1032
+ const branches = [];
1033
+ let hasEmptyBranch = false;
1034
+ for (const v of variants) {
1035
+ const branch = [];
1036
+ for (const mod of v.modifierConditions) if (!commonModKeys.has(getModifierKey(mod))) branch.push(mod);
1037
+ for (const pseudo of v.pseudoConditions) if (!commonPseudoKeys.has(getPseudoKey(pseudo))) branch.push(pseudo);
1038
+ if (branch.length > 0) branches.push(branch);
1039
+ else hasEmptyBranch = true;
1040
+ }
1041
+ if (hasEmptyBranch) return {
1042
+ ...variants[0],
1043
+ modifierConditions: commonModifiers,
1044
+ pseudoConditions: commonPseudos
1045
+ };
1046
+ return {
1047
+ modifierConditions: commonModifiers,
1048
+ pseudoConditions: commonPseudos,
1049
+ selectorGroups: [...variants[0].selectorGroups, {
1050
+ branches,
1051
+ negated: false
1052
+ }],
1053
+ ownGroups: [...variants[0].ownGroups],
1054
+ mediaConditions: [...variants[0].mediaConditions],
1055
+ containerConditions: [...variants[0].containerConditions],
1056
+ supportsConditions: [...variants[0].supportsConditions],
1057
+ rootGroups: [...variants[0].rootGroups],
1058
+ parentGroups: [...variants[0].parentGroups],
1059
+ startingStyle: variants[0].startingStyle
1060
+ };
1061
+ }
1062
+ /**
802
1063
  * Build at-rules array from a variant
803
1064
  */
804
1065
  function buildAtRulesFromVariant(variant) {
@@ -840,5 +1101,5 @@ function buildAtRulesFromVariant(variant) {
840
1101
  }
841
1102
 
842
1103
  //#endregion
843
- export { buildAtRulesFromVariant, conditionToCSS, modifierToCSS, parentGroupsToCSS, pseudoToCSS, rootConditionsToCSS, selectorConditionToCSS };
1104
+ export { branchToCSS, buildAtRulesFromVariant, conditionToCSS, mergeVariantsIntoSelectorGroups, optimizeGroups, parentGroupsToCSS, rootGroupsToCSS, selectorGroupToCSS };
844
1105
  //# sourceMappingURL=materialize.js.map