@homebound/truss 2.1.0-next.1 → 2.1.0-next.2

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.
@@ -165,16 +165,32 @@ function collectVariableRules(rules, seg, mapping) {
165
165
  for (const prop of seg.variableProps) {
166
166
  const className = prefix ? `${prefix}${baseKey}_var` : `${baseKey}_var`;
167
167
  const varName = toCssVariableName(className, baseKey, prop);
168
- if (!rules.has(className)) {
168
+ const declaration = { cssProperty: camelToKebab(prop), cssValue: `var(${varName})`, cssVarName: varName };
169
+ const existingRule = rules.get(className);
170
+ if (!existingRule) {
169
171
  rules.set(className, {
170
172
  className,
171
- cssProperty: camelToKebab(prop),
172
- cssValue: `var(${varName})`,
173
+ cssProperty: declaration.cssProperty,
174
+ cssValue: declaration.cssValue,
175
+ declarations: [declaration],
173
176
  pseudoClass: seg.pseudoClass ?? void 0,
174
177
  mediaQuery: seg.mediaQuery ?? void 0,
175
178
  pseudoElement: seg.pseudoElement ?? void 0,
176
179
  cssVarName: varName
177
180
  });
181
+ continue;
182
+ }
183
+ existingRule.declarations ??= [
184
+ {
185
+ cssProperty: existingRule.cssProperty,
186
+ cssValue: existingRule.cssValue,
187
+ cssVarName: existingRule.cssVarName
188
+ }
189
+ ];
190
+ if (!existingRule.declarations.some(function(entry) {
191
+ return entry.cssProperty === declaration.cssProperty;
192
+ })) {
193
+ existingRule.declarations.push(declaration);
178
194
  }
179
195
  }
180
196
  if (seg.variableExtraDefs) {
@@ -227,11 +243,14 @@ function collectWhenVariableRules(rules, seg, mapping) {
227
243
  for (const prop of seg.variableProps) {
228
244
  const className = `${prefix}${baseKey}_var`;
229
245
  const varName = toCssVariableName(className, baseKey, prop);
230
- if (!rules.has(className)) {
246
+ const declaration = { cssProperty: camelToKebab(prop), cssValue: `var(${varName})`, cssVarName: varName };
247
+ const existingRule = rules.get(className);
248
+ if (!existingRule) {
231
249
  rules.set(className, {
232
250
  className,
233
- cssProperty: camelToKebab(prop),
234
- cssValue: `var(${varName})`,
251
+ cssProperty: declaration.cssProperty,
252
+ cssValue: declaration.cssValue,
253
+ declarations: [declaration],
235
254
  cssVarName: varName,
236
255
  whenSelector: {
237
256
  relationship: wp.relationship ?? "ancestor",
@@ -239,6 +258,19 @@ function collectWhenVariableRules(rules, seg, mapping) {
239
258
  pseudo: wp.pseudo
240
259
  }
241
260
  });
261
+ continue;
262
+ }
263
+ existingRule.declarations ??= [
264
+ {
265
+ cssProperty: existingRule.cssProperty,
266
+ cssValue: existingRule.cssValue,
267
+ cssVarName: existingRule.cssVarName
268
+ }
269
+ ];
270
+ if (!existingRule.declarations.some(function(entry) {
271
+ return entry.cssProperty === declaration.cssProperty;
272
+ })) {
273
+ existingRule.declarations.push(declaration);
242
274
  }
243
275
  }
244
276
  if (seg.variableExtraDefs) {
@@ -351,30 +383,26 @@ function generateCssText(rules) {
351
383
  lines.push(formatMediaPseudoElementRule(rule));
352
384
  }
353
385
  for (const rule of allRules) {
354
- if (rule.cssVarName) {
355
- lines.push(`@property ${rule.cssVarName} {
386
+ for (const declaration of getRuleDeclarations(rule)) {
387
+ if (declaration.cssVarName) {
388
+ lines.push(`@property ${declaration.cssVarName} {
356
389
  syntax: "*";
357
390
  inherits: false;
358
391
  }`);
392
+ }
359
393
  }
360
394
  }
361
395
  return lines.join("\n");
362
396
  }
363
397
  function formatBaseRule(rule) {
364
- return `.${rule.className} {
365
- ${rule.cssProperty}: ${rule.cssValue};
366
- }`;
398
+ return formatRuleBlock(`.${rule.className}`, rule);
367
399
  }
368
400
  function formatPseudoRule(rule) {
369
401
  const pe = rule.pseudoElement ? rule.pseudoElement : "";
370
- return `.${rule.className}${rule.pseudoClass}${pe} {
371
- ${rule.cssProperty}: ${rule.cssValue};
372
- }`;
402
+ return formatRuleBlock(`.${rule.className}${rule.pseudoClass}${pe}`, rule);
373
403
  }
374
404
  function formatPseudoElementRule(rule) {
375
- return `.${rule.className}${rule.pseudoElement} {
376
- ${rule.cssProperty}: ${rule.cssValue};
377
- }`;
405
+ return formatRuleBlock(`.${rule.className}${rule.pseudoElement}`, rule);
378
406
  }
379
407
  function formatWhenRule(rule) {
380
408
  const whenSelector = rule.whenSelector;
@@ -384,53 +412,50 @@ function formatWhenRule(rule) {
384
412
  const markerSelector = `.${whenSelector.markerClass}${whenSelector.pseudo}`;
385
413
  const targetSelector = `.${rule.className}`;
386
414
  if (whenSelector.relationship === "ancestor") {
387
- return `${markerSelector} ${targetSelector} {
388
- ${rule.cssProperty}: ${rule.cssValue};
389
- }`;
415
+ return formatRuleBlock(`${markerSelector} ${targetSelector}`, rule);
390
416
  }
391
417
  if (whenSelector.relationship === "descendant") {
392
- return `${targetSelector}:has(${markerSelector}) {
393
- ${rule.cssProperty}: ${rule.cssValue};
394
- }`;
418
+ return formatRuleBlock(`${targetSelector}:has(${markerSelector})`, rule);
395
419
  }
396
420
  if (whenSelector.relationship === "siblingAfter") {
397
- return `${targetSelector}:has(~ ${markerSelector}) {
398
- ${rule.cssProperty}: ${rule.cssValue};
399
- }`;
421
+ return formatRuleBlock(`${targetSelector}:has(~ ${markerSelector})`, rule);
400
422
  }
401
423
  if (whenSelector.relationship === "siblingBefore") {
402
- return `${markerSelector} ~ ${targetSelector} {
403
- ${rule.cssProperty}: ${rule.cssValue};
404
- }`;
424
+ return formatRuleBlock(`${markerSelector} ~ ${targetSelector}`, rule);
405
425
  }
406
426
  if (whenSelector.relationship === "anySibling") {
407
- return `${targetSelector}:has(~ ${markerSelector}), ${markerSelector} ~ ${targetSelector} {
408
- ${rule.cssProperty}: ${rule.cssValue};
409
- }`;
427
+ return formatRuleBlock(`${targetSelector}:has(~ ${markerSelector}), ${markerSelector} ~ ${targetSelector}`, rule);
410
428
  }
411
- return `${markerSelector} ${targetSelector} {
412
- ${rule.cssProperty}: ${rule.cssValue};
413
- }`;
429
+ return formatRuleBlock(`${markerSelector} ${targetSelector}`, rule);
414
430
  }
415
431
  function formatMediaRule(rule) {
416
- return `${rule.mediaQuery} {
417
- .${rule.className}.${rule.className} {
418
- ${rule.cssProperty}: ${rule.cssValue};
419
- }
420
- }`;
432
+ return formatNestedRuleBlock(rule.mediaQuery, `.${rule.className}.${rule.className}`, rule);
421
433
  }
422
434
  function formatMediaPseudoRule(rule) {
423
- return `${rule.mediaQuery} {
424
- .${rule.className}.${rule.className}${rule.pseudoClass} {
425
- ${rule.cssProperty}: ${rule.cssValue};
426
- }
427
- }`;
435
+ return formatNestedRuleBlock(rule.mediaQuery, `.${rule.className}.${rule.className}${rule.pseudoClass}`, rule);
428
436
  }
429
437
  function formatMediaPseudoElementRule(rule) {
430
438
  const pe = rule.pseudoElement ?? "";
431
- return `${rule.mediaQuery} {
432
- .${rule.className}.${rule.className}${pe} {
433
- ${rule.cssProperty}: ${rule.cssValue};
439
+ return formatNestedRuleBlock(rule.mediaQuery, `.${rule.className}.${rule.className}${pe}`, rule);
440
+ }
441
+ function getRuleDeclarations(rule) {
442
+ return rule.declarations ?? [{ cssProperty: rule.cssProperty, cssValue: rule.cssValue, cssVarName: rule.cssVarName }];
443
+ }
444
+ function formatRuleBlock(selector, rule) {
445
+ const body = getRuleDeclarations(rule).map(function(declaration) {
446
+ return ` ${declaration.cssProperty}: ${declaration.cssValue};`;
447
+ }).join("\n");
448
+ return `${selector} {
449
+ ${body}
450
+ }`;
451
+ }
452
+ function formatNestedRuleBlock(wrapper, selector, rule) {
453
+ const body = getRuleDeclarations(rule).map(function(declaration) {
454
+ return ` ${declaration.cssProperty}: ${declaration.cssValue};`;
455
+ }).join("\n");
456
+ return `${wrapper} {
457
+ ${selector} {
458
+ ${body}
434
459
  }
435
460
  }`;
436
461
  }
@@ -573,6 +598,31 @@ function resolveFullChain(chain, mapping, options) {
573
598
  let currentNodes = [];
574
599
  while (i < filteredChain.length) {
575
600
  const node = filteredChain[i];
601
+ const mediaStart = getMediaConditionalStartNode(node, mapping);
602
+ if (mediaStart) {
603
+ const elseIndex = findElseIndex(filteredChain, i + 1);
604
+ if (elseIndex !== -1) {
605
+ if (currentNodes.length > 0) {
606
+ const unconditionalSegs = resolveChain(currentNodes, mapping);
607
+ parts.push({
608
+ type: "unconditional",
609
+ segments: options?.skipMerge ? unconditionalSegs : mergeOverlappingConditions(unconditionalSegs)
610
+ });
611
+ currentNodes = [];
612
+ }
613
+ const thenNodes = mediaStart.thenNodes ? [...mediaStart.thenNodes, ...filteredChain.slice(i + 1, elseIndex)] : filteredChain.slice(i, elseIndex);
614
+ const elseNodes = [makeMediaQueryNode(mediaStart.inverseMediaQuery), ...filteredChain.slice(elseIndex + 1)];
615
+ const thenSegs = resolveChain(thenNodes, mapping);
616
+ const elseSegs = resolveChain(elseNodes, mapping);
617
+ const combinedSegs = [...thenSegs, ...elseSegs];
618
+ parts.push({
619
+ type: "unconditional",
620
+ segments: options?.skipMerge ? combinedSegs : mergeOverlappingConditions(combinedSegs)
621
+ });
622
+ i = filteredChain.length;
623
+ break;
624
+ }
625
+ }
576
626
  if (node.type === "if") {
577
627
  if (node.conditionNode.type === "StringLiteral") {
578
628
  const mediaQuery = node.conditionNode.value;
@@ -639,6 +689,53 @@ function resolveFullChain(chain, mapping, options) {
639
689
  }
640
690
  return { parts, markers, errors: [...scanErrors, ...segmentErrors] };
641
691
  }
692
+ function getMediaConditionalStartNode(node, mapping) {
693
+ if (node.type === "if" && node.conditionNode.type === "StringLiteral") {
694
+ return {
695
+ inverseMediaQuery: invertMediaQuery(node.conditionNode.value),
696
+ thenNodes: [makeMediaQueryNode(node.conditionNode.value)]
697
+ };
698
+ }
699
+ if (node.type === "getter" && mapping.breakpoints && node.name in mapping.breakpoints) {
700
+ return { inverseMediaQuery: invertMediaQuery(mapping.breakpoints[node.name]) };
701
+ }
702
+ return null;
703
+ }
704
+ function findElseIndex(chain, start) {
705
+ for (let i = start; i < chain.length; i++) {
706
+ if (chain[i].type === "if") {
707
+ return -1;
708
+ }
709
+ if (chain[i].type === "else") {
710
+ return i;
711
+ }
712
+ }
713
+ return -1;
714
+ }
715
+ function makeMediaQueryNode(mediaQuery) {
716
+ return { type: "__mediaQuery", mediaQuery };
717
+ }
718
+ function invertMediaQuery(query) {
719
+ const screenPrefix = "@media screen and ";
720
+ if (query.startsWith(screenPrefix)) {
721
+ const conditions = query.slice(screenPrefix.length).trim();
722
+ const rangeMatch = conditions.match(/^\(min-width: (\d+)px\) and \(max-width: (\d+)px\)$/);
723
+ if (rangeMatch) {
724
+ const min = Number(rangeMatch[1]);
725
+ const max = Number(rangeMatch[2]);
726
+ return `@media screen and (max-width: ${min - 1}px), screen and (min-width: ${max + 1}px)`;
727
+ }
728
+ const minMatch = conditions.match(/^\(min-width: (\d+)px\)$/);
729
+ if (minMatch) {
730
+ return `@media screen and (max-width: ${Number(minMatch[1]) - 1}px)`;
731
+ }
732
+ const maxMatch = conditions.match(/^\(max-width: (\d+)px\)$/);
733
+ if (maxMatch) {
734
+ return `@media screen and (min-width: ${Number(maxMatch[1]) + 1}px)`;
735
+ }
736
+ }
737
+ return query.replace("@media", "@media not");
738
+ }
642
739
  function resolveChain(chain, mapping) {
643
740
  const segments = [];
644
741
  let currentMediaQuery = null;