UNPKG

@workday/canvas-kit-popup-stack

Version:

Stack for managing popup UIs to coordinate global concerns like escape key handling and rendering order

172 lines 9.42 kB
/** * This type is purposely an interface so that it can be extended for a specific use-case. */ export interface PopupStackItem { /** * All items in the stack are identified by their DOM element reference. A DOM element is * framework agnostic. */ element: HTMLElement; /** * An owner is typically a trigger or anchor target. For example, it will be a HTMLButtonElement * that opened a dropdown menu. If an owner is provided, _and_ that owner element is part of * another stack item, it will be considered a "parent" of the provided stack item. This reference * helps in the following ways: * - Click outside detection typically will use `PopupStack.contains()` which includes this * element. If you wish to close a popup when the target is clicked, add a click handler to do * so. * - `PopupStack.bringToTop()` will also bring children to top as well using the `owner` reference * to map a "child" popup back to its parent. This is useful for "Window" or other persistent * popups that are brought to the front when clicked. This will prevent the "Window" from * rendering on top of child popups as they will be brought along also. * - Synthetic event systems like in React will bubble events through "portals". This is * inconsistent with DOM event bubbling. This reference helps normalize that behavior across * different frameworks. */ owner?: HTMLElement; } /** * Calculate the zIndex value of a given index in the stack. The range is 20 where 30 is the minimum * and 50 is the maximum. If there are more than 20 items in the stack, we'll have multiple zIndexes * of 30 at the bottom of the stack since the user probably can't tell the difference with that many * popups. */ export declare function getValue(index: number, length: number): number; interface Stack { items: PopupStackItem[]; /** * Returns the container of a stack given an optional element. */ container?: (element?: HTMLElement) => HTMLElement; zIndex: { min: number; max: number; getValue: typeof getValue; }; _adapter: Partial<typeof PopupStack>; } /** * The `PopupStack` is a framework agnostic first-in-last-out (FILO) stack that tracks all popups * ("floating UI" or any UI that renders on top of other content). It contains methods that interact * with the stack to support all coordinating behaviors of all popups on the page. The `PopupStack` * helps: * * - Render popups in the right order on the page * - Helps accessibility with the Escape key (topmost popup is closed) * - Handles transition to [Full * Screen](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API) * * The `PopupStack` supports adapters to work with existing popup systems. {@link createAdapter} is * exported to accept an adapter. Only a single adapter should be used per page. * * The `PopupStack` is designed to handle multiple versions of `PopupStack` on the page at once * while the internal FILO stack is shared between instances. You should not attempt to use the * internal FILO stack. If an adapter is used, the internal FILO stack may be empty. */ export declare const PopupStack: { /** * Create a HTMLElement as the container for the popup stack item. The returned element reference * will be the reference to be passed to all other methods. The Popup Stack will control when this * element is added and removed from the DOM as well as the `z-index` style property. Your content * should be added to this element. */ createContainer(): HTMLElement; /** * Adds a PopupStackItem to the stack. This should only be called when the item is rendered to the * page. Z-indexes are set when the item is added to the stack. If your application requires * popups to be registered initially, but rendered when the user triggers some event, call this * method when the event triggers. */ add(item: PopupStackItem): void; /** * Removes an item from a stack by its `HTMLElement` reference. This should be called when a popup * is "closed" or when the element is removed from the page entirely to ensure proper memory * cleanup. A popup will be removed from the stack it is a part of. This will not automatically be * called when the element is removed from the DOM. This method will reset z-index values of the * stack. */ remove(element: HTMLElement): void; /** * Returns true when the provided `element` is at the top of the stack. It will return false if it * is not the top of the stack or is not found in the stack. The `element` should be the same * reference that was passed to `add` */ isTopmost(element: HTMLElement): boolean; /** * Returns an array of elements defined by the `element` passed to `add`. This method return * elements in the order of lowest z-index to highest z-index. Some popup behaviors will need to * make decisions based on z-index order. */ getElements(stackOverride?: Stack): HTMLElement[]; /** * Bring the element to the top of the stack. This is useful for persistent popups to place them * on top of the stack when clicked. If an `owner` was provided to an item when it was added and * that owner is a DOM child of another item in the stack, that item will be considered a "parent" * to this item. If the previous are true, all "children" stack items will be brought to top as * well and will be on top of the element passed to `bringToTop`. This maintains stack item * "hierarchy" so that stack items like Popups and Tooltips don't get pushed behind elements they * are supposed to be on top of. * * This does not need to be called when a popup is added since added popups are already place on * the top of the stack. */ bringToTop(element: HTMLElement): void; /** * Compares a Popup by its element reference against the event target and the stack. An event * target is considered to be "contained" by an element under the following conditions: * - The `eventTarget` is a DOM child of the popup element * - The `eventTarget` is the `owner` element passed when it was added to the stack * - The `eventTarget` is a DOM child of the `owner` element * * This method should be used instead of `element.contains` so that clicking a popup target can * opt-in to toggling. Otherwise there is no way to opt-out of toggle behavior (because the target * is not inside `element`). */ contains(element: HTMLElement, eventTarget: HTMLElement): boolean; /** * Add a new stack context for popups. This method could be called with the same element multiple * times, but should only push a new stack context once. The most common use-case for calling * `pushStackContext` is when entering fullscreen, but multiple fullscreen listeners could be * pushing the same element which is very difficult to ensure only one stack is used. To mitigate, * this method filters out multiple calls to push the same element as a new stack context. */ pushStackContext(container: HTMLElement): void; /** * Remove the topmost stack context. The stack context will only be removed if the top stack * context container element matches to guard against accidental remove of other stack contexts * you don't own. */ popStackContext(container: HTMLElement): void; /** * Transfer the popup stack item into the current popup stack context. * * An example might be a popup * that is opened and an element goes into fullscreen. The default popup stack context is * `document.body`, but the [Fullscreen * API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API) will only render elements * that are children of the fullscreen element. If the popup isn't transferred to the current * popup stack context, the popup will remain open, but will no longer be rendered. This method * will transfer that popup to the fullscreen element so that it will render. Popups created while * in a fullscreen context that need to be transferred back when fullscreen is exited should also * call this method. While popups may still render when fullscreen is exited, popups will be * members of different popup stack contexts which will cause unspecified results (like the escape * key will choose the wrong popup as the "topmost"). */ transferToCurrentContext(item: PopupStackItem): void; }; /** * Reset all the items in the stack. This should only be used for testing or if the page doesn't * properly tear down each item in the stack when switching views. */ export declare function resetStack(): void; /** * An adapter is a custom implementation of the {@link PopupStack}. There is only ever a single * instance of an adapter on the page. It allows an adapter to intercept any `PopupStack` method. * This could bypass the internal FILO stack of the `PopupStack` and allows the FILO stack to be * handled by something else. * * @param adapter The parts of the PopupStack that we want to override */ export declare const createAdapter: (adapter: Partial<typeof PopupStack>) => void; export {}; //# sourceMappingURL=PopupStack.d.ts.map