@cloudscape-design/component-toolkit
Advanced tools
Comparing version
@@ -15,3 +15,3 @@ export { useComponentMetadata, COMPONENT_METADATA_KEY } from './base-component/component-metadata'; | ||
export { getGlobalFlag } from './global-flags'; | ||
export { SingleTabStopNavigationAPI, SingleTabStopNavigationProvider, SingleTabStopNavigationReset, useSingleTabStopNavigation, } from './single-tab-stop'; | ||
export { SingleTabStopNavigationAPI, SingleTabStopNavigationProvider, useSingleTabStopNavigation, } from './single-tab-stop'; | ||
export { isFocusable, getAllFocusables, getFirstFocusable, getLastFocusable } from './focus-lock-utils/utils'; | ||
@@ -18,0 +18,0 @@ export { default as handleKey } from './utils/handle-key'; |
@@ -17,3 +17,3 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
export { getGlobalFlag } from './global-flags'; | ||
export { SingleTabStopNavigationProvider, SingleTabStopNavigationReset, useSingleTabStopNavigation, } from './single-tab-stop'; | ||
export { SingleTabStopNavigationProvider, useSingleTabStopNavigation, } from './single-tab-stop'; | ||
export { isFocusable, getAllFocusables, getFirstFocusable, getLastFocusable } from './focus-lock-utils/utils'; | ||
@@ -20,0 +20,0 @@ export { default as handleKey } from './utils/handle-key'; |
{ | ||
"commit": "96fb0e354ed4443d45a934d5c9fec85b2f3a9a92" | ||
"commit": "75b3106e6ac2a2fa179d550916189b70f7852b33" | ||
} |
import React from 'react'; | ||
export type FocusableChangeHandler = (isFocusable: boolean) => void; | ||
export declare const defaultValue: { | ||
navigationActive: boolean; | ||
registerFocusable(focusable: HTMLElement, handler: FocusableChangeHandler): () => void; | ||
}; | ||
/** | ||
@@ -10,3 +14,2 @@ * Single tab stop navigation context is used together with keyboard navigation that requires a single tab stop. | ||
registerFocusable(focusable: HTMLElement, handler: FocusableChangeHandler): () => void; | ||
resetFocusTarget(): void; | ||
}>; | ||
@@ -32,6 +35,3 @@ export declare function useSingleTabStopNavigation(focusable: null | React.RefObject<HTMLElement>, options?: { | ||
} | ||
export declare function SingleTabStopNavigationReset({ children }: { | ||
children: React.ReactNode; | ||
}): JSX.Element; | ||
export declare const SingleTabStopNavigationProvider: React.ForwardRefExoticComponent<SingleTabStopNavigationProviderProps & React.RefAttributes<SingleTabStopNavigationAPI>>; | ||
export {}; |
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import React, { createContext, forwardRef, useContext, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react'; | ||
import { useEffectOnUpdate } from '../use-effect-on-update'; | ||
import nodeBelongs from '../../dom/node-belongs'; | ||
const defaultValue = { | ||
export const defaultValue = { | ||
navigationActive: false, | ||
registerFocusable: () => () => { }, | ||
resetFocusTarget: () => { }, | ||
}; | ||
@@ -34,5 +32,2 @@ /** | ||
} | ||
export function SingleTabStopNavigationReset({ children }) { | ||
return (React.createElement(SingleTabStopNavigationContext.Provider, { value: defaultValue }, children)); | ||
} | ||
export const SingleTabStopNavigationProvider = forwardRef(({ navigationActive, children, getNextFocusTarget, isElementSuppressed, onRegisterFocusable, onUnregisterActive, }, ref) => { | ||
@@ -57,5 +52,2 @@ // A set of registered focusable elements that can use keyboard navigation. | ||
function registerFocusable(focusableElement, changeHandler) { | ||
// In case the contexts are nested, we must that the components register to all of them, | ||
// so that switching between contexts dynamically is possible. | ||
const parentUnregister = parentContext.registerFocusable(focusableElement, changeHandler); | ||
focusables.current.add(focusableElement); | ||
@@ -70,6 +62,3 @@ focusHandlers.current.set(focusableElement, changeHandler); | ||
onRegisterFocusable === null || onRegisterFocusable === void 0 ? void 0 : onRegisterFocusable(focusableElement); | ||
return () => { | ||
parentUnregister(); | ||
unregisterFocusable(focusableElement); | ||
}; | ||
return () => unregisterFocusable(focusableElement); | ||
} | ||
@@ -82,3 +71,3 @@ function unregisterFocusable(focusableElement) { | ||
// Update focus target with next single focusable element and notify all registered focusables of a change. | ||
function updateFocusTarget(forceUpdate = false) { | ||
function updateFocusTarget() { | ||
var _a; | ||
@@ -89,3 +78,3 @@ focusTarget.current = getNextFocusTarget(); | ||
const newIsFocusable = focusTarget.current === focusableElement || !!(isElementSuppressed === null || isElementSuppressed === void 0 ? void 0 : isElementSuppressed(focusableElement)); | ||
if (newIsFocusable !== isFocusable || forceUpdate) { | ||
if (newIsFocusable !== isFocusable) { | ||
focusablesState.current.set(focusableElement, newIsFocusable); | ||
@@ -96,5 +85,2 @@ focusHandlers.current.get(focusableElement)(newIsFocusable); | ||
} | ||
function resetFocusTarget() { | ||
updateFocusTarget(true); | ||
} | ||
function getFocusTarget() { | ||
@@ -107,23 +93,3 @@ return focusTarget.current; | ||
useImperativeHandle(ref, () => ({ updateFocusTarget, getFocusTarget, isRegistered })); | ||
// Only one STSN context should be active at a time. | ||
// The outer context is preferred over the inners. The components using STSN | ||
// must either work with either outer or inner context, or an explicit switch mechanism | ||
// needs to be implemented (that turns the outer context on and off based on user interaction). | ||
const parentContext = useContext(SingleTabStopNavigationContext); | ||
const value = parentContext.navigationActive | ||
? parentContext | ||
: { navigationReset: false, navigationActive, registerFocusable, updateFocusTarget, resetFocusTarget }; | ||
// When contexts switching occurs, it is essential that the now-active one updates the focus target | ||
// to ensure the tab indices are correctly set. | ||
useEffectOnUpdate(() => { | ||
if (parentContext.navigationActive) { | ||
parentContext.resetFocusTarget(); | ||
} | ||
else { | ||
resetFocusTarget(); | ||
} | ||
// The updateFocusTarget and its dependencies must be pure. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [parentContext.navigationActive]); | ||
return React.createElement(SingleTabStopNavigationContext.Provider, { value: value }, children); | ||
return (React.createElement(SingleTabStopNavigationContext.Provider, { value: { navigationActive, registerFocusable } }, children)); | ||
}); |
@@ -5,2 +5,1 @@ export { clearOneTimeMetricsCache } from './base-component/metrics/metrics'; | ||
export { clearVisualRefreshState } from './visual-mode'; | ||
export { renderWithSingleTabStopNavigation } from './single-tab-stop/__tests__/utils'; |
@@ -7,2 +7,1 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
export { clearVisualRefreshState } from './visual-mode'; | ||
export { renderWithSingleTabStopNavigation } from './single-tab-stop/__tests__/utils'; |
{ | ||
"name": "@cloudscape-design/component-toolkit", | ||
"version": "1.0.0-beta.116", | ||
"version": "1.0.0-beta.117", | ||
"files": [ | ||
@@ -5,0 +5,0 @@ "container-queries", |
113150
-4.32%101
-3.81%2281
-4.32%