Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foldable widgets: added keyboard navigation, removed (broken) animation. #7336

Merged
merged 2 commits into from
Jan 2, 2024
Merged
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
2 changes: 1 addition & 1 deletion app/test/frontend/static_files_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ void main() {
test('script.dart.js and parts size check', () {
final file = cache.getFile('/static/js/script.dart.js');
expect(file, isNotNull);
expect((file!.bytes.length / 1024).round(), closeTo(313, 1));
expect((file!.bytes.length / 1024).round(), closeTo(317, 1));

final parts = cache.paths
.where((path) =>
Expand Down
71 changes: 29 additions & 42 deletions pkg/web_app/lib/src/foldable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:html';
import 'dart:math' show min, max;

Expand All @@ -22,41 +23,26 @@ void _setEventForFoldable() {
final scrollContainer = _parentWithClass(h, 'scroll-container');
if (content == null) continue;

Future<void> update(bool isActive) async {
// Closing is simple: no measurements, no scrolling.
Future<void> toggle() async {
final isActive = foldable.classes.toggle('-active');
if (!isActive) {
content.style.maxHeight = '0px';
return;
}

/// The following coordinate and dimension measurements trigger a reflow,
/// but it is acceptable, as it is local to this event processing and the
/// impact is low.
if (content.style.display != 'block') {
content.style.display = 'block';
}
// Needs to be empty to measure real dimension.
content.style.maxHeight = '';

final contentHeight = content.offsetHeight;
final boundingRect = content.getBoundingClientRect();
final scrollContainerHeight = scrollContainer?.clientHeight;
final buttonHeight = h.offsetHeight;

// Reset content state as hidden.
content.style.maxHeight = '0px';

// Wait one animation frame before trigger the full height content.
await window.animationFrame;
content.style.maxHeight = '${contentHeight}px';

num scrollDiff = 0;
if (scrollContainer != null) {
// Wait one animation frame before measurements.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I understand all this scrolling logic...

Do we really need it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is for the mobile view: when the navigation menu is at the bottom, and it is clicked, there is no indication that something has been displayed under the fold. Scrolling to it makes the new content visible.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just use scrollIntoView?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we have tried it and it worked with full-page elements, but had issues with embedded-scroll of the navigation. It may be worth to re-evaluate it again, a lot of time has passed since.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#7342 to track it separately

await window.animationFrame;

final boundingRect = content.getBoundingClientRect();
final scrollContainerHeight = scrollContainer.clientHeight;
final buttonHeight = h.offsetHeight;

/// Calculate the required amount of scrolling in order to have the
/// entire content in the view, aligning it at the bottom of the visible
/// scroll view.
final outsideViewDiff =
boundingRect.top + boundingRect.height - scrollContainerHeight!;
boundingRect.top + boundingRect.height - scrollContainerHeight;

/// Limit the maximum scrolling to the screen height minus the button
/// component's height, in order to make sure it will be still visible
Expand All @@ -65,30 +51,31 @@ void _setEventForFoldable() {

/// Scroll the smaller amount of the two.
scrollDiff = max(0, min(screenLimit, outsideViewDiff));
}

/// Do not scroll if the difference is small, otherwise scroll in small
/// steps synchronized to the animation frames.
if (scrollDiff > 8) {
final originalScrollTop = scrollContainer!.scrollTop;
final maxSteps = 20;
for (var i = 1; i <= maxSteps; i++) {
if (i > 1) await window.animationFrame;
final nextPos = originalScrollTop + (scrollDiff * i / maxSteps);
scrollContainer.scrollTo(0, nextPos);
/// Do not scroll if the difference is small, otherwise scroll in small
/// steps synchronized to the animation frames.
if (scrollDiff > 8) {
final originalScrollTop = scrollContainer.scrollTop;
final maxSteps = 20;
for (var i = 1; i <= maxSteps; i++) {
if (i > 1) await window.animationFrame;
final nextPos = originalScrollTop + (scrollDiff * i / maxSteps);
scrollContainer.scrollTo(0, nextPos);
}
}
}

// Wait one animation frame before enabling the content to resize on its own.
await window.animationFrame;
content.style.maxHeight = 'none';
}

h.querySelector('.foldable-icon')!.attributes['tabindex'] = '0';

// listen on trigger events
h.onClick.listen((e) async {
// Toggle state.
final isActive = foldable.classes.toggle('-active');
await update(isActive);
e.preventDefault();
await toggle();
});
h.onKeyDown.where((e) => e.key == 'Enter').listen((e) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps also spacebar?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was wondering about this, whether we should add Space or not, and if we add it here, we should add it at other places too. Created a new issue to track it: #7341

e.preventDefault();
await toggle();
});
}
}
Expand Down
2 changes: 0 additions & 2 deletions pkg/web_css/lib/src/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,6 @@ pre {
.foldable {
.foldable-content {
display: none;
overflow: hidden;
transition: max-height 0.6s ease;
}

&.-active {
Expand Down