Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Make the view focus binding report focus transitions across elements. #50610

Merged
merged 1 commit into from
Feb 21, 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
22 changes: 15 additions & 7 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2475,20 +2475,28 @@ DomPath2D createDomPath2D([Object? path]) {
}
}

@JS('MouseEvent')
@staticInterop
class DomMouseEvent extends DomUIEvent {
external factory DomMouseEvent.arg1(JSString type);
external factory DomMouseEvent.arg2(JSString type, JSAny initDict);
}

@JS('InputEvent')
@staticInterop
class DomInputEvent extends DomUIEvent {
external factory DomInputEvent.arg1(JSString type);
external factory DomInputEvent.arg2(JSString type, JSAny initDict);
}

@JS('FocusEvent')
@staticInterop
class DomFocusEvent extends DomUIEvent {}

extension DomFocusEventExtension on DomFocusEvent {
external DomEventTarget? get relatedTarget;
}

@JS('MouseEvent')
@staticInterop
class DomMouseEvent extends DomUIEvent {
external factory DomMouseEvent.arg1(JSString type);
external factory DomMouseEvent.arg2(JSString type, JSAny initDict);
}

extension DomMouseEventExtension on DomMouseEvent {
@JS('clientX')
external JSNumber get _clientX;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ final class ViewFocusBinding {
/// Subscribes the [listener] to [ui.ViewFocusEvent] events.
void addListener(ui.ViewFocusChangeCallback listener) {
if (_listeners.isEmpty) {
domDocument.body?.addEventListener(_focusin, _focusChangeHandler, true);
domDocument.body?.addEventListener(_focusout, _focusChangeHandler, true);
domDocument.body?.addEventListener(_focusin, _handleFocusin, true);
domDocument.body?.addEventListener(_focusout, _handleFocusout, true);
}
_listeners.add(listener);
}
Expand All @@ -28,8 +28,8 @@ final class ViewFocusBinding {
void removeListener(ui.ViewFocusChangeCallback listener) {
_listeners.remove(listener);
if (_listeners.isEmpty) {
domDocument.body?.removeEventListener(_focusin, _focusChangeHandler, true);
domDocument.body?.removeEventListener(_focusout, _focusChangeHandler, true);
domDocument.body?.removeEventListener(_focusin, _handleFocusin, true);
domDocument.body?.removeEventListener(_focusout, _handleFocusout, true);
}
}

Expand All @@ -39,9 +39,17 @@ final class ViewFocusBinding {
}
}

late final DomEventListener _handleFocusin = createDomEventListener(
(DomEvent event) => _handleFocusChange(event.target as DomElement?),
);

late final DomEventListener _handleFocusout = createDomEventListener(
(DomEvent event) => _handleFocusChange((event as DomFocusEvent).relatedTarget as DomElement?),
);

int? _lastViewId;
late final DomEventListener _focusChangeHandler = createDomEventListener((DomEvent event) {
final int? viewId = _viewId(domDocument.activeElement);
void _handleFocusChange(DomElement? focusedElement) {
final int? viewId = _viewId(focusedElement);
if (viewId == _lastViewId) {
return;
}
Expand All @@ -62,7 +70,7 @@ final class ViewFocusBinding {
}
_lastViewId = viewId;
_notify(event);
});
}

static int? _viewId(DomElement? element) {
final DomElement? viewElement = element?.closest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,15 @@ void testMain() {
focusableViewElement1.focus();
focusableViewElement2.focus();

expect(viewFocusEvents, hasLength(3));
expect(viewFocusEvents, hasLength(2));

expect(viewFocusEvents[0].viewId, view1.viewId);
expect(viewFocusEvents[0].state, ui.ViewFocusState.focused);
expect(viewFocusEvents[0].direction, ui.ViewFocusDirection.forward);

expect(viewFocusEvents[1].viewId, view1.viewId);
expect(viewFocusEvents[1].state, ui.ViewFocusState.unfocused);
expect(viewFocusEvents[1].direction, ui.ViewFocusDirection.undefined);

expect(viewFocusEvents[2].viewId, view2.viewId);
expect(viewFocusEvents[2].state, ui.ViewFocusState.focused);
expect(viewFocusEvents[2].direction, ui.ViewFocusDirection.forward);
expect(viewFocusEvents[1].viewId, view2.viewId);
expect(viewFocusEvents[1].state, ui.ViewFocusState.focused);
expect(viewFocusEvents[1].direction, ui.ViewFocusDirection.forward);
});

test('fires a focus event - focus transitions on and off views', () async {
Expand All @@ -125,23 +121,19 @@ void testMain() {
focusableViewElement2.focus();
focusableViewElement2.blur();

expect(viewFocusEvents, hasLength(4));
expect(viewFocusEvents, hasLength(3));

expect(viewFocusEvents[0].viewId, view1.viewId);
expect(viewFocusEvents[0].state, ui.ViewFocusState.focused);
expect(viewFocusEvents[0].direction, ui.ViewFocusDirection.forward);

expect(viewFocusEvents[1].viewId, view1.viewId);
expect(viewFocusEvents[1].state, ui.ViewFocusState.unfocused);
expect(viewFocusEvents[1].direction, ui.ViewFocusDirection.undefined);
expect(viewFocusEvents[1].viewId, view2.viewId);
expect(viewFocusEvents[1].state, ui.ViewFocusState.focused);
expect(viewFocusEvents[1].direction, ui.ViewFocusDirection.forward);

expect(viewFocusEvents[2].viewId, view2.viewId);
expect(viewFocusEvents[2].state, ui.ViewFocusState.focused);
expect(viewFocusEvents[2].direction, ui.ViewFocusDirection.forward);

expect(viewFocusEvents[3].viewId, view2.viewId);
expect(viewFocusEvents[3].state, ui.ViewFocusState.unfocused);
expect(viewFocusEvents[3].direction, ui.ViewFocusDirection.undefined);
expect(viewFocusEvents[2].state, ui.ViewFocusState.unfocused);
expect(viewFocusEvents[2].direction, ui.ViewFocusDirection.undefined);
});
});
}