Skip to content

Commit

Permalink
Foldable widgets: added keyboard navigation, removed (broken) animati…
Browse files Browse the repository at this point in the history
  • Loading branch information
isoos authored Jan 2, 2024
1 parent b43eacd commit 3896a9f
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 45 deletions.
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.
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 {
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

0 comments on commit 3896a9f

Please sign in to comment.