Angular Data GridFocus scopes
Manage focus boundaries and keyboard shortcuts contexts with focus scopes.
Overview
Focus scopes allow you to create isolated focus boundaries within your Handsontable instance, enabling seamless focus switching between the grid and your custom UI elements. They automatically manage keyboard shortcuts contexts and provide fine-grained control over which elements can receive focus and which shortcuts are active.
TIP
To use the Handsontable API, you'll need access to the Handsontable instance. You can do that by utilizing a reference to the HotTable component, and reading its hotInstance property.
For more information, see the Instance access page.
- Access the
FocusScopeManagerAPI:hot.getFocusScopeManager(); - Register a focus scope with a container element:
const focusScopeManager = hot.getFocusScopeManager(); focusScopeManager.registerScope('customScope', containerElement, { shortcutsContextName: 'plugin:customScope', onActivate: (focusSource) => { // Focus the first focusable element in your plugin's UI container }, });
Focus scope types
Focus scopes come in two types, each with different behavior:
| Type | Description |
|---|---|
inline (default) | The scope is inline and allows the rest of the component to receive focus in the order of the rendered elements in the DOM. |
modal | The scope is modal and blocks the rest of the component from receiving focus. Only elements within the scope can be focused. |
Inline scopes
Inline scopes allow natural tab navigation through the DOM. Users can navigate to other parts of the grid using the Tab or Shift+Tab keys.
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.registerScope('pagination', paginationContainer, {
type: 'inline',
shortcutsContextName: 'plugin:pagination',
});
The example below demonstrates how the inline focus scope works using the pagination plugin as an example. Let's focus on the top text input and press the Tab key through the grid to see how the focus moves to the bottom text input and how the internal state changes.
Modal scopes
Modal scopes have a priority over inline scopes. If a modal scope is active, the inline scopes will not be activated. This is useful for dialogs, popups, or any UI that may overlap other inline scopes and need to be activated first.
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.registerScope('dialog', dialogContainerElement, {
type: 'modal',
shortcutsContextName: 'plugin:dialog',
// Only activate the scope if the dialog is open
runOnlyIf: () => isDialogOpen(),
});
The example below demonstrates how the modal focus scope works using the dialog plugin as an example. The dialog scope
takes over focus management for all inline scopes (grid and pagination) automatically, even when the dialog element
appears after the inline scope elements in the DOM.
Register a focus scope
To register a focus scope:
Access the
FocusScopeManagerAPI:const focusScopeManager = hot.getFocusScopeManager();Use the
registerScope()method:focusScopeManager.registerScope('customScope', containerElement);
Connect a scope with a shortcuts context
To connect a scope with a shortcuts context, use the shortcutsContextName option. When the scope is activated, the shortcuts context automatically switches to the scope's specified context name. This allows you to define custom shortcuts that work only within that scope. For more information, see the Custom shortcuts page. If no context name is specified, the scope uses the grid context name by default.
const focusScopeManager = hot.getFocusScopeManager();
const shortcutManager = hot.getShortcutManager();
focusScopeManager.registerScope('customScope', containerElement, {
shortcutsContextName: 'plugin:customScope',
});
// Add shortcuts to the customScope context
const customScopeContext = shortcutManager.getContext('plugin:customScope');
customScopeContext.addShortcut({
group: 'customScope',
keys: [['enter']],
callback: () => {
console.log('Enter pressed within the customScope scope');
},
});
Add conditional scope activation
To add conditional scope activation, use the runOnlyIf option. This allows you to enable or disable the scope based on custom logic. The option is useful for situations where your UI depends on whether it has any focusable elements, or when you want to prevent the scope from activating for a particular part of the UI. For cases where focus should bypass the scope activation after Tab or Shift+Tab key presses, the logic should return false.
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.registerScope('customScope', containerElement, {
runOnlyIf: () => isPluginActive(),
});
Custom focus detection
By default, a scope is considered active if the focused element is within the scope's container element. However, there are cases where you need to provide custom logic to determine if the scope should be active. You can provide custom logic using the contains option.
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.registerScope('myPlugin', containerElement, {
contains: (target) => {
// check if the target is within the scope's container element or if its parent has the 'my-plugin' class
return containerElement.contains(target) || target.closest('.my-plugin');
},
onActivate: (focusSource) => {
console.log('MyPlugin scope activated');
},
});
Scope callbacks
You can provide a callback function to be called when the scope is activated using the onActivate option. The callback function is called with the source of the activation as the first argument. You can also provide a callback function to be called when the scope is deactivated using the onDeactivate option.
The focusSource argument can be one of the following values:
unknown: The scope is activated by an unknown source.click: The scope is activated by a click event.tab_from_above: The scope is activated by a Tab key press.tab_from_below: The scope is activated by a Shift+Tab key press.
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.registerScope('customScope', containerElement, {
onActivate: (focusSource) => {
console.log('Custom scope activated');
},
onDeactivate: () => {
console.log('Custom scope deactivated');
},
});
Manage focus scopes
Get the active scope
To get the currently active scope ID:
const focusScopeManager = hot.getFocusScopeManager();
const activeScopeId = focusScopeManager.getActiveScopeId();
if (activeScopeId) {
console.log(`Active scope: ${activeScopeId}`);
} else {
console.log('No active scope');
}
Activate a scope
The focus manager automatically tries to activate the appropriate scope based on the focused element. However, there are cases where you need to manually activate a scope. For example, you might need to activate a scope after programmatically triggering an action. An example is the Dialog plugin: when the show() method is called, it manually activates its focus scope as there is no event triggered that would activate the scope.
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.activateScope('myPlugin');
Deactivate a scope
To manually deactivate a scope by its ID:
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.deactivateScope('myPlugin');
Unregister a scope
To remove a scope from the collection:
const focusScopeManager = hot.getFocusScopeManager();
focusScopeManager.unregisterScope('myPlugin');
Automatic focus management
The focus scope manager automatically:
- Listens to document events and activates the appropriate scope based on the focused element
- Updates the
Core#isListeningstate based on scope activity - Switches the shortcuts context to the scope's specified context name when the scope is activated
- Handles tab navigation between scopes
API reference
For the complete API reference, see the following pages:
- APIs:
- Configuration options:
- Core methods:
- Hooks:
Troubleshooting
Didn't find what you need? Try this:
- View related topics (opens new window) on GitHub
- Report an issue (opens new window) on GitHub
- Ask a question (opens new window) on Stack Overflow
- Start a discussion (opens new window) on Handsontable's forum
- Contact our technical support (opens new window) to get help