Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions super_text_layout/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [0.1.21]
### TBD
* [FEATURE]: `SuperText` now supports an explicit `strutStyle` and inherited `SuperTextStrutStyle`.

## [0.1.20]
### Feb, 2026
* [FEATURE]: `SuperText` now supports `maxLines` and `overflow` effects.
Expand Down
38 changes: 38 additions & 0 deletions super_text_layout/lib/src/super_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SuperText extends StatefulWidget {
this.textAlign = TextAlign.left,
this.textDirection = TextDirection.ltr,
this.textScaler,
this.strutStyle,
this.maxLines,
this.overflow = TextOverflow.clip,
this.layerBeneathBuilder,
Expand All @@ -49,6 +50,12 @@ class SuperText extends StatefulWidget {
/// Defaults to `MediaQuery.textScalerOf`.
final TextScaler? textScaler;

/// The strut style to use for [richText] display.
///
/// If null, [SuperText] falls back to the nearest [SuperTextStrutStyle]
/// ancestor, if one exists.
final StrutStyle? strutStyle;

/// The maximum number of lines of text that are permitted to be displayed until [overflow]
/// is applied to the text.
final int? maxLines;
Expand Down Expand Up @@ -101,6 +108,7 @@ class SuperTextState extends ProseTextState<SuperText> with ProseTextBlock {
textAlign: widget.textAlign,
textDirection: widget.textDirection,
textScaler: widget.textScaler ?? MediaQuery.textScalerOf(context),
strutStyle: widget.strutStyle ?? SuperTextStrutStyle.maybeOf(context),
maxLines: widget.maxLines,
overflow: widget.overflow,
onMarkNeedsLayout: _invalidateParagraph,
Expand Down Expand Up @@ -141,6 +149,35 @@ class SuperTextState extends ProseTextState<SuperText> with ProseTextBlock {
}
}

/// Provides a [StrutStyle] to descendant [SuperText] widgets.
///
/// Use [SuperTextStrutStyle] when a text surface needs consistent line metrics
/// across a subtree without threading a [StrutStyle] through every [SuperText]
/// instance. A [SuperText.strutStyle] value takes precedence over the inherited
/// value.
class SuperTextStrutStyle extends InheritedWidget {
const SuperTextStrutStyle({
Key? key,
required this.strutStyle,
required Widget child,
}) : super(key: key, child: child);

/// Returns the nearest inherited [StrutStyle], or null when no
/// [SuperTextStrutStyle] exists above [context].
static StrutStyle? maybeOf(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<SuperTextStrutStyle>()
?.strutStyle;
}

final StrutStyle strutStyle;

@override
bool updateShouldNotify(SuperTextStrutStyle oldWidget) {
return strutStyle != oldWidget.strutStyle;
}
}

@visibleForTesting
class SuperTextAnalytics extends InheritedWidget {
static SuperTextAnalytics? of(BuildContext context) {
Expand Down Expand Up @@ -309,6 +346,7 @@ class LayoutAwareRichText extends RichText {
super.textAlign = TextAlign.left,
super.textDirection = TextDirection.ltr,
super.textScaler = TextScaler.noScaling,
super.strutStyle,
super.maxLines,
super.overflow,
required this.onMarkNeedsLayout,
Expand Down
99 changes: 99 additions & 0 deletions super_text_layout/test/super_text_strut_style_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:super_text_layout/super_text_layout.dart';

import 'test_tools.dart';

void main() {
group('SuperText strut style', () {
const inheritedStrut = StrutStyle(
fontSize: 18,
height: 1.6,
forceStrutHeight: true,
);
const explicitStrut = StrutStyle(
fontSize: 20,
height: 1.4,
forceStrutHeight: true,
);

testWidgets(
'uses an inherited strut style when no explicit strut is provided',
(tester) async {
await tester.pumpWidget(
buildTestScaffold(
child: const SuperTextStrutStyle(
strutStyle: inheritedStrut,
child: SuperText(
richText: TextSpan(text: 'Text with inherited strut'),
),
),
),
);

final richText = tester.widget<LayoutAwareRichText>(
find.byType(LayoutAwareRichText),
);
expect(richText.strutStyle, inheritedStrut);
},
);

testWidgets(
'prefers an explicit strut style over an inherited strut style',
(tester) async {
await tester.pumpWidget(
buildTestScaffold(
child: const SuperTextStrutStyle(
strutStyle: inheritedStrut,
child: SuperText(
richText: TextSpan(text: 'Text with explicit strut'),
strutStyle: explicitStrut,
),
),
),
);

final richText = tester.widget<LayoutAwareRichText>(
find.byType(LayoutAwareRichText),
);
expect(richText.strutStyle, explicitStrut);
},
);

testWidgets('updates text layout when the inherited strut style changes', (
tester,
) async {
await tester.pumpWidget(
buildTestScaffold(
child: const SuperTextStrutStyle(
strutStyle: inheritedStrut,
child: SuperText(
richText: TextSpan(text: 'Text with changing strut'),
),
),
),
);

LayoutAwareRichText richText = tester.widget<LayoutAwareRichText>(
find.byType(LayoutAwareRichText),
);
expect(richText.strutStyle, inheritedStrut);

await tester.pumpWidget(
buildTestScaffold(
child: const SuperTextStrutStyle(
strutStyle: explicitStrut,
child: SuperText(
richText: TextSpan(text: 'Text with changing strut'),
),
),
),
);

richText = tester.widget<LayoutAwareRichText>(
find.byType(LayoutAwareRichText),
);
expect(richText.strutStyle, explicitStrut);
});
});
}