Skip to content

Commit

Permalink
stabilize scope hooks; update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Feb 14, 2024
1 parent 4506f36 commit 84c08c9
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 17 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,11 @@ const FocusTrackingButton = ({ children }) => {
);
};
const RowingFocusInternalTrap = () => {
const { autofocus, focusNext, focusPrev } = useFocusScope();
const { autoFocus, focusNext, focusPrev } = useFocusScope();
// use useFocusController(divRef) if there is no FocusLock around

useEffect(() => {
autofocus();
autoFocus();
}, []);

const onKey = (event) => {
Expand Down
6 changes: 3 additions & 3 deletions UI/UI.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,17 @@ export type FocusControl = {
/**
* moves focus to the current scope, can be considered as autofocus
*/
autofocus():void;
autoFocus():Promise<void>;
/**
* focuses the next element in the scope.
* If active element is not in the scope, autofocus will be triggered first
*/
focusNext(options:FocusOptions):void;
focusNext(options:FocusOptions):Promise<void>;
/**
* focuses the prev element in the scope.
* If active element is not in the scope, autofocus will be triggered first
*/
focusPrev():void;
focusPrev():Promise<void>;
}


Expand Down
2 changes: 1 addition & 1 deletion _tests/FocusLock.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ describe('react-focus-lock', () => {
</div>,
);

control.autofocus();
control.autoFocus();
await tick();
expect(document.activeElement.innerHTML).to.be.equal('button1');
control.focusNext();
Expand Down
30 changes: 30 additions & 0 deletions _tests/hooks.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import {
render,
} from '@testing-library/react';
import { expect } from 'chai';
import { useFocusController } from '../src/UI';

describe('Hooks w/o sidecar', () => {
it('controls focus', async () => {
let control;
const ref = React.createRef();
const Capture = () => {
control = useFocusController(ref);
return null;
};
render(
<div ref={ref}>
<button id="b1">button1</button>
<button id="b2">button2</button>
<Capture />
</div>,
);
expect(document.activeElement).to.be.equal(document.body);
const p = control.autoFocus();
// async
expect(document.activeElement).to.be.equal(document.body);
await p;
expect(document.activeElement).to.be.equal(document.getElementById('b1'));
});
});
2 changes: 1 addition & 1 deletion _tests/sidecar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Sidecar', () => {
);

// FIXME: sidecar needs a custom SSR override to run this test
it.skip('properly handles dynamic sidecar', async () => {
it('properly handles dynamic sidecar', async () => {
let resolve;
const lock = new Promise((res) => {
resolve = res;
Expand Down
2 changes: 2 additions & 0 deletions src/MoveFocusInside.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import PropTypes from 'prop-types';
import * as constants from 'focus-lock/constants';
import { inlineProp } from './util';
import { mediumEffect } from './medium';
import { requireSideCar } from './require-side-car';

export const useFocusInside = (observedRef) => {
React.useEffect(() => {
let enabled = true;
requireSideCar();
mediumEffect.useMedium((car) => {
const observed = observedRef && observedRef.current;
if (enabled && observed) {
Expand Down
2 changes: 1 addition & 1 deletion src/medium.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export const mediumSidecar = createSidecarMedium({
async: true,
// focus-lock sidecar is not required on the server
// however, it might be required for JSDOM tests
// ssr: true,
ssr: typeof document !== 'undefined',
});
4 changes: 4 additions & 0 deletions src/require-side-car.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const requireSideCar = () => {
// eslint-disable-next-line no-unused-expressions
import('./sidecar');
};
16 changes: 11 additions & 5 deletions src/use-focus-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import { useContext, useMemo, useRef } from 'react';
import { focusScope } from './scope';
import { mediumEffect } from './medium';
import { extractRef } from './util';
import { requireSideCar } from './require-side-car';

const collapseRefs = shards => (
shards.map(extractRef).filter(Boolean)
);

const withMedium = fn => mediumEffect.useMedium(fn);
const withMedium = (fn) => {
requireSideCar();
return new Promise(resolve => mediumEffect.useMedium((...args) => {
resolve(fn(...args));
}));
};
export const useFocusController = (...shards) => {
if (!shards.length) {
throw new Error('useFocusController requires at least one target element');
Expand All @@ -16,11 +22,11 @@ export const useFocusController = (...shards) => {
ref.current = shards;

return useMemo(() => ({
autofocus(focusOptions = {}) {
withMedium(car => car.moveFocusInside(collapseRefs(ref.current), null, focusOptions));
autoFocus(focusOptions = {}) {
return withMedium(car => car.moveFocusInside(collapseRefs(ref.current), null, focusOptions));
},
focusNext(options) {
withMedium((car) => {
return withMedium((car) => {
car.moveFocusInside(collapseRefs(ref.current), null);
car.focusNextElement(
document.activeElement,
Expand All @@ -29,7 +35,7 @@ export const useFocusController = (...shards) => {
});
},
focusPrev(options) {
withMedium((car) => {
return withMedium((car) => {
car.moveFocusInside(collapseRefs(ref.current), null);
car.focusPrevElement(
document.activeElement,
Expand Down
8 changes: 4 additions & 4 deletions stories/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import FocusLock, { useFocusScope } from '../src/index';
import { useFocusState } from '../src/use-focus-state';

const ControlTrap = () => {
const { autofocus, focusNext, focusPrev } = useFocusScope();
const { autoFocus, focusNext, focusPrev } = useFocusScope();

useEffect(() => {
autofocus();
autoFocus();
}, []);

const onKey = (event) => {
Expand Down Expand Up @@ -39,10 +39,10 @@ const FocusButton = ({ children }) => {
);
};
const RowingFocusTrap = () => {
const { autofocus, focusNext, focusPrev } = useFocusScope();
const { autoFocus, focusNext, focusPrev } = useFocusScope();

useEffect(() => {
autofocus();
autoFocus();
}, []);

const onKey = (event) => {
Expand Down

0 comments on commit 84c08c9

Please sign in to comment.