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
11 changes: 11 additions & 0 deletions packages/analysis_defaults/lib/analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ analyzer:

linter:
rules:
- async_return_with_no_await
- deprecated_member_use_from_same_package
- discarded_futures
- empty_container_bodies
- eol_at_end_of_file
- implicit_reopen
- initialize_in_field_declaration
- invalid_case_patterns
- matching_super_parameters
- missing_code_block_language_in_doc_comment
Expand All @@ -20,7 +23,15 @@ linter:
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_if_elements_to_conditional_expressions
- simple_directive_paths
- simplify_variable_pattern
- specify_nonobvious_property_types
- switch_on_type
- unnecessary_async
- unnecessary_breaks
- unnecessary_null_aware_operator_on_extension_on_nullable
- unnecessary_primary_constructor_body
- unnecessary_unawaited
- use_enums
- use_truncating_division
2 changes: 1 addition & 1 deletion packages/analysis_defaults/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ publish_to: none

resolution: workspace
environment:
sdk: ^3.12.0
sdk: ^3.13.0

# NOTE: Code isn't allowed in this package.
# Don't add dependencies besides the underlying lints package.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class _BlogCategoriesState extends State<BlogCategories> {
[
button(
onClick: () => _selectCategory(null),
[const .text('All')],
const [.text('All')],
),
],
),
Expand Down
24 changes: 12 additions & 12 deletions packages/site_shared/lib/components/blog/client/share_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ class ShareButton extends StatelessComponent {
to: false,
);
},
[
const MaterialIcon('content_copy'),
const .text('Copy link'),
const [
MaterialIcon('content_copy'),
.text('Copy link'),
],
);
},
Expand All @@ -58,16 +58,16 @@ class ShareButton extends StatelessComponent {
href:
'https://twitter.com/intent/tweet?text=${Uri.encodeComponent(title)}&url=${Uri.encodeComponent(url)}',
target: Target.blank,
[
const svg([
const [
svg([
Component.element(
tag: 'use',
attributes: {
'href': '/packages/site_shared/assets/social/x.svg#x',
},
),
]),
const .text('Share on X'),
.text('Share on X'),
],
),
]),
Expand All @@ -76,16 +76,16 @@ class ShareButton extends StatelessComponent {
href:
'https://bsky.app/intent/compose?text=${Uri.encodeComponent("$title $url")}',
target: Target.blank,
[
const svg([
const [
svg([
Component.element(
tag: 'use',
attributes: {
'href': '/packages/site_shared/assets/social/bluesky.svg#bluesky',
},
),
]),
const .text('Share on Bluesky'),
.text('Share on Bluesky'),
],
),
]),
Expand All @@ -94,16 +94,16 @@ class ShareButton extends StatelessComponent {
href:
'https://www.linkedin.com/sharing/share-offsite/?url=${Uri.encodeComponent(url)}',
target: Target.blank,
[
const svg([
const [
svg([
Component.element(
tag: 'use',
attributes: {
'href': '/packages/site_shared/assets/social/linkedin.svg#linkedin',
},
),
]),
const .text('Share on LinkedIn'),
.text('Share on LinkedIn'),
],
),
]),
Expand Down
2 changes: 1 addition & 1 deletion packages/site_shared/lib/components/common/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class Button extends StatelessComponent {
final children = <Component>[
if (icon case final iconId?) MaterialIcon(iconId),
if (content case final contentText?)
asRaw ? RawText(contentText) : .text(contentText),
if (asRaw) RawText(contentText) else .text(contentText),
if (trailingIcon case final iconId?) MaterialIcon(iconId),
];

Expand Down
10 changes: 5 additions & 5 deletions packages/site_shared/lib/components/common/chip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ChipSet extends StatelessComponent {
id: 'reset-filters',
classes: 'text-button',
events: {'click': (_) => onReset()},
[const .text('Clear filters')],
const [.text('Clear filters')],
),
]);
}
Expand Down Expand Up @@ -124,8 +124,8 @@ class FilterChip extends StatelessComponent {
'viewBox': iconViewBox,
'aria-hidden': 'true',
},
[
const Component.element(
const [
Component.element(
tag: 'path',
attributes: {
'd':
Expand Down Expand Up @@ -244,7 +244,7 @@ class _SelectChipState<T> extends State<SelectChip<T>> {
width: 24.px,
height: 24.px,
viewBox: '0 0 24 24',
attributes: {'aria-hidden': 'true'},
attributes: const {'aria-hidden': 'true'},
[
Component.element(
tag: 'path',
Expand Down Expand Up @@ -322,7 +322,7 @@ class SelectMenuItem<T> {
width: iconSize.px,
height: iconSize.px,
viewBox: iconViewBox,
attributes: {'aria-hidden': 'true'},
attributes: const {'aria-hidden': 'true'},
[
Component.element(
tag: 'path',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ final class OnThisPageButton extends StatelessComponent {
}
},
},
[
const MaterialIcon('list'),
const span([.text('On this page')]),
const [
MaterialIcon('list'),
span([.text('On this page')]),
],
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ final class _PageHeaderOptionsState extends State<PageHeaderOptions> {
toggle: const Button(icon: 'more_vert', title: 'View page options.'),
content: nav(
classes: 'dropdown-menu',
attributes: {
attributes: const {
'role': 'menu',
},
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class FragmentTarget extends StatelessComponent {
Component build(BuildContext context) => a(
href: '',
id: id,
attributes: {'aria-hidden': 'true'},
[],
attributes: const {'aria-hidden': 'true'},
const [],
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ class _IdeBadgeDot extends StatelessComponent {
'aria-label': badge,
'title': badge,
},
[],
const [],
);
}
}
Expand Down
50 changes: 25 additions & 25 deletions packages/site_shared/lib/components/common/tooltip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ class Tooltip extends StatefulComponent {
}

class _TooltipState extends State<Tooltip> {
static final isTouchscreen =
static final bool _isTouchscreen =
kIsWeb && web.window.matchMedia('(pointer: coarse)').matches;

final wrapperKey = GlobalNodeKey<web.HTMLElement>();
final targetKey = GlobalNodeKey<web.HTMLElement>();
final tooltipKey = GlobalNodeKey<web.HTMLElement>();
final GlobalNodeKey<web.HTMLElement> _wrapperKey = GlobalNodeKey();
final GlobalNodeKey<web.HTMLElement> _targetKey = GlobalNodeKey();
final GlobalNodeKey<web.HTMLElement> _tooltipKey = GlobalNodeKey();

bool isVisible = false;
double tooltipOffset = 0.0;
bool _isVisible = false;
double _tooltipOffset = 0;
Comment on lines +34 to +35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The window resize event listener registered in setupTooltip is never cancelled, which will cause a memory leak. When this state/component is disposed, the listener on the global web.window remains active, keeping a reference to the state and preventing it from being garbage collected.

To fix this, store the StreamSubscription and cancel it in the dispose method:

  1. Add a _resizeSubscription field.
  2. Assign the subscription in setupTooltip:
    _resizeSubscription = web.EventStreamProviders.resizeEvent.forTarget(web.window).listen((_) {
      ensureVisible();
    });
  3. Implement dispose to cancel the subscription:
    @override
    void dispose() {
      _resizeSubscription?.cancel();
      super.dispose();
    }
Suggested change
bool _isVisible = false;
double _tooltipOffset = 0;
bool _isVisible = false;
double _tooltipOffset = 0;
StreamSubscription<web.Event>? _resizeSubscription;


@override
void initState() {
Expand All @@ -55,29 +55,29 @@ class _TooltipState extends State<Tooltip> {
/// Adjust the tooltip position to ensure it is fully inside the
/// ancestor .content element.
void ensureVisible() {
final target = targetKey.currentNode;
final tooltip = tooltipKey.currentNode;
final target = _targetKey.currentNode;
final tooltip = _tooltipKey.currentNode;
if (tooltip == null || target == null) return;

setState(() {
tooltipOffset = calculateTooltipOffset(target, tooltip);
_tooltipOffset = calculateTooltipOffset(target, tooltip);
});
}

@override
Component build(BuildContext context) {
return span(
key: wrapperKey,
key: _wrapperKey,
classes: 'tooltip-wrapper',
[
span(
key: targetKey,
key: _targetKey,
classes: 'tooltip-target',
events: {
if (isTouchscreen)
if (_isTouchscreen)
'click': (e) {
if (!isVisible) {
setState(() => isVisible = true);
if (!_isVisible) {
setState(() => _isVisible = true);
e.preventDefault();
}
},
Expand All @@ -87,33 +87,33 @@ class _TooltipState extends State<Tooltip> {
if (component.content case final content?)
GlobalEventListener(
// Close tooltip when clicking outside of this wrapper.
onClick: isTouchscreen
onClick: _isTouchscreen
? (e) {
if (wrapperKey.currentNode?.contains(
if (_wrapperKey.currentNode?.contains(
e.target as web.Node?,
) ==
true) {
return;
}
setState(() => isVisible = false);
setState(() => _isVisible = false);
}
: null,
// On touchscreen devices, close tooltips when scrolling.
onScroll: isTouchscreen
onScroll: _isTouchscreen
? (_) {
setState(() => isVisible = false);
setState(() => _isVisible = false);
}
: null,
span(
key: tooltipKey,
classes: ['tooltip', if (isVisible) 'visible'].toClasses,
key: _tooltipKey,
classes: ['tooltip', if (_isVisible) 'visible'].toClasses,
styles: Styles(
raw: {
'left': tooltipOffset == 0
'left': _tooltipOffset == 0
? '50%'
: tooltipOffset > 0
? 'calc(50% + ${tooltipOffset}px)'
: 'calc(50% - ${tooltipOffset.abs()}px)',
: _tooltipOffset > 0
? 'calc(50% + ${_tooltipOffset}px)'
: 'calc(50% - ${_tooltipOffset.abs()}px)',
},
),
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class YoutubeEmbed with CustomComponentBase {
classes: 'lite-youtube-fallback',
href: 'https://www.youtube.com/watch/$videoId',
target: Target.blank,
attributes: {'rel': 'noopener'},
attributes: const {'rel': 'noopener'},
[
.text('Watch on YouTube in a new tab: "$videoTitle"'),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class _EmbeddedDartPadState extends State<EmbeddedDartPad> {
src: component._iframeUrl,
loading: MediaLoading.lazy,
allow: 'clipboard-write',
[],
const [],
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class SiteSwitcher extends StatelessComponent {
toggle: const Button(icon: 'apps', title: 'Visit related sites.'),
content: nav(
classes: 'dropdown-menu',
attributes: {'role': 'menu'},
attributes: const {'role': 'menu'},
[
ul([
if (isFlutter) ...[
Expand Down Expand Up @@ -124,7 +124,7 @@ class _SiteWordMarkListEntry extends StatelessComponent {
),
span(
classes: 'name',
attributes: {
attributes: const {
'translate': 'no',
},
[.text(name)],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class InteractiveProgressRing extends StatelessComponent {
'stroke-dasharray': '$inactiveLength ${full - inactiveLength}',
'stroke-dashoffset': '${quarter - activeLength - gap * 1.5}',
},
[],
const [],
),

// Active portion, drawn from 0° to (progress)°
Expand All @@ -80,7 +80,7 @@ class InteractiveProgressRing extends StatelessComponent {
'stroke-dasharray': '$activeLength ${full - activeLength}',
'stroke-dashoffset': '${quarter - gap / 2}',
},
[],
const [],
),
],
);
Expand Down
3 changes: 3 additions & 0 deletions packages/site_shared/lib/src/utils/retake_element_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ String extractContent(Element element) {
}

class RawNode extends Component {
// Identity is important to these components and a
// web node can't be retrieved at compile time anyway.
// ignore: prefer_const_constructors_in_immutables
RawNode(this.node, {super.key});

final web.Node node;
Expand Down
Loading
Loading