mergeActiveVariants method

  1. @visibleForTesting
Style<S> mergeActiveVariants(
  1. BuildContext context, {
  2. required Set<NamedVariant> namedVariants,
})

Merges all active variants with their nested variants recursively.

Evaluates which variants are active in context and against namedVariants, then recursively resolves the nested variants inside each active variant's style.

Active variants apply in two priority groups, lowest first: those that declare no ContextVariant.widgetStateDependencies, then those that do. Within a group, the variant declared last merges last and so wins on the properties they share.

Implementation

@visibleForTesting
Style<S> mergeActiveVariants(
  BuildContext context, {
  required Set<NamedVariant> namedVariants,
}) {
  final variants = $variants;
  if (variants == null) return this;

  // Partition, don't sort: declaration order inside a group is load-bearing,
  // and List.sort is only stable by accident of the insertion sort it falls
  // back to below 32 elements.
  final lowPriority = <VariantStyle<S>>[];
  final highPriority = <VariantStyle<S>>[];

  for (final variantAttr in variants) {
    final variant = variantAttr.variant;

    final isActive = switch (variant) {
      ContextVariant() => variant.when(context),
      NamedVariant() => namedVariants.contains(variant),
      ContextVariantBuilder() => true,
    };
    if (!isActive) continue;

    // Keyed off the declaration, not the class: FocusVisibleVariant is not a
    // WidgetStateVariant yet reads WidgetState.focused just the same, and
    // NotVariant forwards whatever its inner variant reads.
    //
    // The getter stays on ContextVariant rather than moving up to Variant
    // because ContextVariant is also the only kind widgetStates walks, so a
    // dependency declared on any other kind would never get tracking
    // installed.
    final readsWidgetState =
        variant is ContextVariant &&
        variant.widgetStateDependencies.isNotEmpty;

    (readsWidgetState ? highPriority : lowPriority).add(variantAttr);
  }

  // Extract the style from each active variant
  final stylesToMerge = <(Style<S>, bool)>[]; // (style, isFromStyleVariation)

  for (final variantAttr in lowPriority.followedBy(highPriority)) {
    final result = switch (variantAttr.variant) {
      ContextVariantBuilder variant => (
        variant.build(context) as Style<S>,
        false,
      ),
      (ContextVariant() || NamedVariant()) => () {
        // Check if the value is a StyleVariation
        // ignore: avoid-unrelated-type-assertions
        if (variantAttr.value is StyleVariation<S>) {
          // ignore: avoid-unrelated-type-casts
          final styleVariation = variantAttr.value as StyleVariation<S>;
          // Only apply if this variant is active
          if (namedVariants.contains(styleVariation.variantType)) {
            return (
              styleVariation.styleBuilder(this, namedVariants, context),
              true,
            );
          }
        }

        return (variantAttr.value, false);
      }(),
    };
    stylesToMerge.add(result);
  }

  // Start with current style as base
  Style<S> mergedStyle = this;

  // Merge each variant style, recursively resolving nested variants
  for (final (variantStyle, isFromStyleVariation) in stylesToMerge) {
    final fullyResolvedStyle = isFromStyleVariation
        // For StyleVariation results, we don't recursively resolve variants
        // since StyleVariation.styleBuilder should handle its own variant logic
        // and return a final style. This prevents infinite recursion.
        ? variantStyle
        // For regular variants, recursively resolve any nested variants
        : variantStyle.mergeActiveVariants(
            context,
            namedVariants: namedVariants,
          );
    mergedStyle = _mergeStyles(mergedStyle, fullyResolvedStyle);
  }

  return mergedStyle;
}