diff --git a/docs/docs/mover.md b/docs/docs/mover.md index 952fac56..24e7ceb3 100644 --- a/docs/docs/mover.md +++ b/docs/docs/mover.md @@ -103,10 +103,6 @@ very inconvenient for the virtualized lists when the scrolling causes more items With `visibilityAware` we can alter that behaviour to be able to Tab to the first visible element instead of the first rendered one. Enables `trackState` internally. -### disableHomeEndKeys?: _boolean_ - -As the name says, we can disable Home/End keys if needed. - ## Examples [See a few Mover examples in the Storybook](https://tabster.io/storybook/?path=/story/mover). diff --git a/src/Mover.ts b/src/Mover.ts index a07a02cf..bb1b847d 100644 --- a/src/Mover.ts +++ b/src/Mover.ts @@ -285,32 +285,40 @@ export class Mover : undefined; } - const { memorizeCurrent, visibilityAware } = this._props; + const { memorizeCurrent, visibilityAware, hasDefault } = this._props; const moverElement = this.getElement(); if ( moverElement && - (memorizeCurrent || visibilityAware) && + (memorizeCurrent || visibilityAware || hasDefault) && (!moverElement.contains(state.from) || ( state.from as HTMLElementWithDummyContainer ).__tabsterDummyContainer?.get() === moverElement) ) { + let found: HTMLElement | undefined | null; + if (memorizeCurrent) { const current = this._current?.get(); if (current && state.acceptCondition(current)) { - state.found = true; - state.foundElement = current; - state.lastToIgnore = moverElement; - return NodeFilter.FILTER_ACCEPT; + found = current; } } - if (visibilityAware) { - const found = this._tabster.focusable.findElement({ + if (!found && hasDefault) { + found = this._tabster.focusable.findDefault({ + container: moverElement, + ignoreUncontrolled: true, + useActiveModalizer: true, + }); + } + + if (!found && visibilityAware) { + found = this._tabster.focusable.findElement({ container: moverElement, ignoreUncontrolled: true, + useActiveModalizer: true, isBackward: state.isBackward, acceptCondition: (el) => { const id = getElementUId(this._win, el); @@ -329,13 +337,13 @@ export class Mover ); }, }); + } - if (found) { - state.found = true; - state.foundElement = found; - state.lastToIgnore = moverElement; - return NodeFilter.FILTER_ACCEPT; - } + if (found) { + state.found = true; + state.foundElement = found; + state.lastToIgnore = moverElement; + return NodeFilter.FILTER_ACCEPT; } } @@ -823,13 +831,6 @@ export class MoverAPI implements Types.MoverAPI { focusedElementX2 = Math.floor(focusedElementRect.right); } - if ( - moverProps.disableHomeEndKeys && - (keyCode === Keys.Home || keyCode === Keys.End) - ) { - return; - } - if (ctx.isRtl) { if (keyCode === Keys.Right) { keyCode = Keys.Left; diff --git a/src/Types.ts b/src/Types.ts index e0e72365..2c8d0ef4 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -482,6 +482,7 @@ export type FindDefaultProps = Pick< | "modalizerId" | "includeProgrammaticallyFocusable" | "useActiveModalizer" + | "ignoreUncontrolled" | "ignoreAccessibiliy" >; @@ -595,7 +596,11 @@ export interface MoverProps { * element in DOM) when tabbing from outside of the mover. */ visibilityAware?: Visibility; - disableHomeEndKeys?: boolean; + /** + * When true, Mover will try to locate a focusable with Focusable.isDefault + * property as a prioritized element to focus. + */ + hasDefault?: boolean; } export type MoverEvent = TabsterEventWithDetails; diff --git a/stories/Mover/Mover.ts b/stories/Mover/Mover.ts index 77e3aed0..2c0bf3a3 100644 --- a/stories/Mover/Mover.ts +++ b/stories/Mover/Mover.ts @@ -11,11 +11,11 @@ export type MoverProps = TabsterTypes.MoverProps; export const createBasicMover = ({ cyclic, direction, - disableHomeEndKeys, memorizeCurrent, tabbable, trackState, visibilityAware, + hasDefault, }: MoverProps) => { console.log(direction); @@ -34,11 +34,11 @@ export const createBasicMover = ({ mover: { cyclic, direction, - disableHomeEndKeys, memorizeCurrent, tabbable, trackState, visibilityAware, + hasDefault, }, }, true @@ -52,22 +52,22 @@ export const createBasicMover = ({ export const createTableMover = ({ cyclic, direction, - disableHomeEndKeys, memorizeCurrent, tabbable, trackState, visibilityAware, + hasDefault, }: MoverProps) => { const attr = getTabsterAttribute( { mover: { cyclic, direction, - disableHomeEndKeys, memorizeCurrent, tabbable, trackState, visibilityAware, + hasDefault, }, }, true diff --git a/tests/Mover.test.tsx b/tests/Mover.test.tsx index 1f7758dc..d635c8d3 100644 --- a/tests/Mover.test.tsx +++ b/tests/Mover.test.tsx @@ -2011,3 +2011,103 @@ describe("Adjacent Movers", () => { } ); }); + +describe("Mover with default element", () => { + beforeEach(async () => { + await BroTest.bootstrapTabsterPage({ mover: true }); + }); + + it("should focus default focusable when tabbing from outside", async () => { + await new BroTest.BroTest( + ( +
+ +
+ + + + +
+ +
+ ) + ) + .pressTab() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button1"); + }) + .pressTab() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button4"); + }) + .pressUp() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button3"); + }) + .pressTab() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button6"); + }) + .pressTab(true) + .activeElement((el) => { + expect(el?.textContent).toEqual("Button4"); + }); + }); + + it("should focus memorized element or default focusable when tabbing from outside", async () => { + await new BroTest.BroTest( + ( +
+ +
+ + + + +
+ +
+ ) + ) + .pressTab() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button1"); + }) + .pressTab() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button4"); + }) + .pressUp() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button3"); + }) + .pressTab() + .activeElement((el) => { + expect(el?.textContent).toEqual("Button6"); + }) + .pressTab(true) + .activeElement((el) => { + expect(el?.textContent).toEqual("Button3"); + }); + }); +});