@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
TypeScript
/**
* 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