Files
ionic-framework/core/src/utils/focus-visible.ts
Liam DeBeasi 15a02253d3 chore: add stronger types to several files (#28347)
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

As part of FW-2832, the team would like to swap out usages of the any
type for stronger types.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->


c529bc23f1
- `scrollToTop` doesn't return anything, so I added the `void` return
type


a96971ad28
- `animation.effect` is a type of
[AnimationEffect](https://developer.mozilla.org/en-US/docs/Web/API/Animation/effect).
One of the more common types of effects is a `KeyframeEffect`. However,
TypeScript doesn't know which specific type of AnimationEffect we are
using, so I cast `animation.effect` as KeyframeEffect where appropriate.
- I also added `!` to places where we know the effect and other
properties are always defined (since they run after the web animation
has been constructed)
- Added stronger types to the internal to/from/fromTo functions (the
public facing type improvements are in
https://github.com/ionic-team/ionic-framework/pull/28334)


fdaf550059
- `getRootNode` can return multiple types of objects, so I cast it to
the specific types that we work with in `isFocused`.


46a6efa510
- Added the "Animation" type and resolved related errors once we had
stronger types


a7cb9a5685
- Made heavier use of the `T` generic
- Once we know `node` is an Element (`nodeType === 1`) we manually cast
the element as `T`


6a9d1f095d
- The focus visible utility is an internal utility, but it was lacking
an interface, so I added one.


90b64c2de5
- Removed unneeded HTMLElement casting
- Added `!` since we can assume the selected elements are defined with
the refresher
- Added documentation as to why casting `referencEl.style` as `any` is
something we need to keep.


3a084caf83
- Avoided the Event naming collision by using globalThis

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Note: This PR contains only type changes. Changes the required updates
to the implementation of Ionic are pulled out into separate PRs and
target a minor release branch to minimize risk.

---------

Co-authored-by: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com>
2023-10-23 16:46:42 +00:00

83 lines
2.1 KiB
TypeScript

const ION_FOCUSED = 'ion-focused';
const ION_FOCUSABLE = 'ion-focusable';
const FOCUS_KEYS = [
'Tab',
'ArrowDown',
'Space',
'Escape',
' ',
'Shift',
'Enter',
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'Home',
'End',
];
export interface FocusVisibleUtility {
destroy: () => void;
setFocus: (elements: Element[]) => void;
}
export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => {
let currentFocus: Element[] = [];
let keyboardMode = true;
const ref = rootEl ? rootEl.shadowRoot! : document;
const root = rootEl ? rootEl : document.body;
const setFocus = (elements: Element[]) => {
currentFocus.forEach((el) => el.classList.remove(ION_FOCUSED));
elements.forEach((el) => el.classList.add(ION_FOCUSED));
currentFocus = elements;
};
const pointerDown = () => {
keyboardMode = false;
setFocus([]);
};
const onKeydown = (ev: Event) => {
keyboardMode = FOCUS_KEYS.includes((ev as KeyboardEvent).key);
if (!keyboardMode) {
setFocus([]);
}
};
const onFocusin = (ev: Event) => {
if (keyboardMode && ev.composedPath !== undefined) {
const toFocus = ev.composedPath().filter((el: any) => {
// TODO(FW-2832): type
if (el.classList) {
return el.classList.contains(ION_FOCUSABLE);
}
return false;
}) as Element[];
setFocus(toFocus);
}
};
const onFocusout = () => {
if (ref.activeElement === root) {
setFocus([]);
}
};
ref.addEventListener('keydown', onKeydown);
ref.addEventListener('focusin', onFocusin);
ref.addEventListener('focusout', onFocusout);
ref.addEventListener('touchstart', pointerDown, { passive: true });
ref.addEventListener('mousedown', pointerDown);
const destroy = () => {
ref.removeEventListener('keydown', onKeydown);
ref.removeEventListener('focusin', onFocusin);
ref.removeEventListener('focusout', onFocusout);
ref.removeEventListener('touchstart', pointerDown);
ref.removeEventListener('mousedown', pointerDown);
};
return {
destroy,
setFocus,
};
};