import { Optional } from '@ephox/katamari'; import { Attribute } from '@ephox/sugar'; import * as Behaviour from '../../api/behaviour/Behaviour'; import { Focusing } from '../../api/behaviour/Focusing'; import { Keying } from '../../api/behaviour/Keying'; import { Replacing } from '../../api/behaviour/Replacing'; import type { AlloyComponent } from '../../api/component/ComponentApi'; import * as GuiFactory from '../../api/component/GuiFactory'; import type { BlockFn, BlockingConfig, BlockingState, UnblockFn } from './BlockingTypes'; // Mark this component as busy, or blocked. export const block: BlockFn = ( component, config, state, // This works in conjunction with the 'getRoot' function in the config. To // attach a blocker component to the dom, ensure that 'getRoot' returns a // component, and this function returns the specification of the component to // attach. getBusySpec ) => { Attribute.set(component.element, 'aria-busy', true); const root = config.getRoot(component).getOr(component); const blockerBehaviours = Behaviour.derive([ // Trap the "Tab" key and don't let it escape. Keying.config({ mode: 'special', onTab: () => Optional.some(true), onShiftTab: () => Optional.some(true) }), Focusing.config({ }) ]); const blockSpec = getBusySpec(root, blockerBehaviours); const blocker = root.getSystem().build(blockSpec); Replacing.append(root, GuiFactory.premade(blocker)); if (blocker.hasConfigured(Keying) && config.focus) { Keying.focusIn(blocker); } if (!state.isBlocked()) { config.onBlock(component); } state.blockWith(() => Replacing.remove(root, blocker)); }; // Mark this component as unblocked, or not busy. This is a noop on a component // that isn't blocked. export const unblock: UnblockFn = (component, config, state) => { Attribute.remove(component.element, 'aria-busy'); if (state.isBlocked()) { config.onUnblock(component); } state.clear(); }; export const isBlocked = (component: AlloyComponent, blockingConfig: BlockingConfig, blockingState: BlockingState): boolean => blockingState.isBlocked();