@@ -400,6 +400,7 @@ export function buildConditionalDeltaIndex(
400400 elements : readonly LayoutElementNode [ ] ,
401401 appliesByNode : ReadonlyMap < LayoutElementNode , readonly LayoutMatchEdge [ ] > ,
402402 monitoredDeclarationsBySelectorId : ReadonlyMap < number , readonly MonitoredDeclaration [ ] > ,
403+ selectorsById : ReadonlyMap < number , SelectorEntity > ,
403404) : ConditionalDeltaIndex {
404405 const conditionalSignalDeltaFactsByNode = new Map < LayoutElementNode , ReadonlyMap < LayoutSignalName , LayoutConditionalSignalDeltaFact > > ( )
405406 const elementsWithConditionalDeltaBySignal = new Map < LayoutSignalName , LayoutElementNode [ ] > ( )
@@ -414,13 +415,25 @@ export function buildConditionalDeltaIndex(
414415
415416 if ( edges !== undefined && edges . length > 0 ) {
416417 const byProperty = new Map < LayoutSignalName , { conditional : Set < string > ; unconditional : Set < string > } > ( )
418+ // Lazily allocated. Tracks which attribute dispatch group each conditional (property, value)
419+ // belongs to. Used to detect mutually exclusive attribute value selectors (e.g.
420+ // [data-sizing="intrinsic"] vs [data-sizing="flex"]) where only one can match at a time.
421+ let conditionalAttributeDispatch : Map < LayoutSignalName , Map < string , string > > | null = null
417422
418423 for ( let j = 0 ; j < edges . length ; j ++ ) {
419424 const currentEdge = edges [ j ]
420425 if ( ! currentEdge ) continue
421426 const declarations = monitoredDeclarationsBySelectorId . get ( currentEdge . selectorId )
422427 if ( ! declarations ) continue
423428
429+ // Identify the dynamic attribute causing conditionality for this edge.
430+ // A conditional match from a selector like [data-sizing="intrinsic"] on an element
431+ // with data-sizing=null (dynamic) means the conditionality comes from data-sizing.
432+ let conditionalAttributeName : string | null = null
433+ if ( currentEdge . conditionalMatch ) {
434+ conditionalAttributeName = identifyConditionalAttribute ( currentEdge . selectorId , node , selectorsById )
435+ }
436+
424437 for ( let k = 0 ; k < declarations . length ; k ++ ) {
425438 const declaration = declarations [ k ]
426439 if ( ! declaration ) continue
@@ -440,6 +453,16 @@ export function buildConditionalDeltaIndex(
440453
441454 if ( declaration . guardProvenance . kind === LayoutSignalGuard . Conditional || currentEdge . conditionalMatch ) {
442455 bucket . conditional . add ( expandedEntry . value )
456+ // Track the attribute dispatch source for this conditional value
457+ if ( conditionalAttributeName !== null && declaration . guardProvenance . kind !== LayoutSignalGuard . Conditional ) {
458+ if ( conditionalAttributeDispatch === null ) conditionalAttributeDispatch = new Map ( )
459+ let dispatchMap = conditionalAttributeDispatch . get ( property )
460+ if ( ! dispatchMap ) {
461+ dispatchMap = new Map ( )
462+ conditionalAttributeDispatch . set ( property , dispatchMap )
463+ }
464+ dispatchMap . set ( expandedEntry . value , conditionalAttributeName )
465+ }
443466 continue
444467 }
445468 bucket . unconditional . add ( expandedEntry . value )
@@ -467,6 +490,29 @@ export function buildConditionalDeltaIndex(
467490 }
468491 }
469492
493+ // Suppress delta when all conditional values come from mutually exclusive
494+ // attribute value selectors on the same attribute. E.g., [data-sizing="intrinsic"]
495+ // sets white-space:nowrap and [data-sizing="flex"] sets white-space:normal — these
496+ // are mutually exclusive on the same element, so the property never actually shifts.
497+ if ( hasDelta && conditionalAttributeDispatch !== null ) {
498+ const dispatchMap = conditionalAttributeDispatch . get ( property )
499+ if ( dispatchMap !== undefined && dispatchMap . size === conditionalValues . length ) {
500+ let singleAttribute : string | null = null
501+ let allSameAttribute = true
502+ for ( const attrName of dispatchMap . values ( ) ) {
503+ if ( singleAttribute === null ) {
504+ singleAttribute = attrName
505+ } else if ( singleAttribute !== attrName ) {
506+ allSameAttribute = false
507+ break
508+ }
509+ }
510+ if ( allSameAttribute && singleAttribute !== null ) {
511+ hasDelta = false
512+ }
513+ }
514+ }
515+
470516 const scrollProfile = buildScrollValueProfile ( property , conditionalValues , unconditionalValues )
471517
472518 facts . set ( property , {
@@ -571,6 +617,45 @@ export function buildConditionalDeltaSignalGroupElements(
571617 return out
572618}
573619
620+ /**
621+ * Identify the single dynamic attribute on the element that caused a conditional
622+ * selector match. Returns the attribute name if exactly one attribute constraint
623+ * in the selector's subject compound targets a dynamic attribute (value=null) on
624+ * the element with an `equals` operator. Returns null if the conditionality comes
625+ * from multiple attributes, non-equals operators, or non-attribute sources.
626+ */
627+ function identifyConditionalAttribute (
628+ selectorId : number ,
629+ node : LayoutElementNode ,
630+ selectorsById : ReadonlyMap < number , SelectorEntity > ,
631+ ) : string | null {
632+ const selector = selectorsById . get ( selectorId )
633+ if ( ! selector ) return null
634+
635+ const constraints = selector . anchor . attributes
636+ let dynamicAttributeName : string | null = null
637+
638+ for ( let i = 0 ; i < constraints . length ; i ++ ) {
639+ const constraint = constraints [ i ]
640+ if ( ! constraint ) continue
641+ if ( constraint . operator !== "equals" ) continue
642+ if ( constraint . value === null ) continue
643+
644+ // Check if this attribute is dynamic on the element.
645+ // attributes.get returns undefined (absent), string (known), or null (dynamic).
646+ // Only null (dynamic value) is the conditionality source.
647+ const elementValue = node . attributes . get ( constraint . name )
648+ if ( elementValue !== null ) continue
649+ if ( dynamicAttributeName !== null && dynamicAttributeName !== constraint . name ) {
650+ // Multiple different dynamic attributes — can't determine single dispatch source
651+ return null
652+ }
653+ dynamicAttributeName = constraint . name
654+ }
655+
656+ return dynamicAttributeName
657+ }
658+
574659function buildScrollValueProfile (
575660 property : LayoutSignalName ,
576661 conditionalValues : readonly string [ ] ,
0 commit comments