@zag-js/dismissable
Version:
Dismissable layer utilities for the DOM
132 lines (131 loc) • 4.33 kB
JavaScript
// src/layer-stack.ts
import { contains, nextTick } from "@zag-js/dom-query";
var LAYER_REQUEST_DISMISS_EVENT = "layer:request-dismiss";
var layerStack = {
layers: [],
branches: [],
recentlyRemoved: /* @__PURE__ */ new Set(),
count() {
return this.layers.length;
},
pointerBlockingLayers() {
return this.layers.filter((layer) => layer.pointerBlocking);
},
topMostPointerBlockingLayer() {
return [...this.pointerBlockingLayers()].slice(-1)[0];
},
hasPointerBlockingLayer() {
return this.pointerBlockingLayers().length > 0;
},
isBelowPointerBlockingLayer(node) {
const index = this.indexOf(node);
const highestBlockingIndex = this.topMostPointerBlockingLayer() ? this.indexOf(this.topMostPointerBlockingLayer()?.node) : -1;
return index < highestBlockingIndex;
},
isTopMost(node) {
const layer = this.layers[this.count() - 1];
return layer?.node === node;
},
getNestedLayers(node) {
return Array.from(this.layers).slice(this.indexOf(node) + 1);
},
getLayersByType(type) {
return this.layers.filter((layer) => layer.type === type);
},
getNestedLayersByType(node, type) {
const index = this.indexOf(node);
if (index === -1) return [];
return this.layers.slice(index + 1).filter((layer) => layer.type === type);
},
getParentLayerOfType(node, type) {
const index = this.indexOf(node);
if (index <= 0) return void 0;
return this.layers.slice(0, index).reverse().find((layer) => layer.type === type);
},
countNestedLayersOfType(node, type) {
return this.getNestedLayersByType(node, type).length;
},
isInNestedLayer(node, target) {
const inNested = this.getNestedLayers(node).some((layer) => contains(layer.node, target));
if (inNested) return true;
if (this.recentlyRemoved.size > 0) return true;
return false;
},
isInBranch(target) {
return Array.from(this.branches).some((branch) => contains(branch, target));
},
add(layer) {
this.layers.push(layer);
this.syncLayers();
},
addBranch(node) {
this.branches.push(node);
},
remove(node) {
const index = this.indexOf(node);
if (index < 0) return;
this.recentlyRemoved.add(node);
nextTick(() => this.recentlyRemoved.delete(node));
if (index < this.count() - 1) {
const _layers = this.getNestedLayers(node);
_layers.forEach((layer) => layerStack.dismiss(layer.node, node));
}
this.layers.splice(index, 1);
this.syncLayers();
},
removeBranch(node) {
const index = this.branches.indexOf(node);
if (index >= 0) this.branches.splice(index, 1);
},
syncLayers() {
this.layers.forEach((layer, index) => {
layer.node.style.setProperty("--layer-index", `${index}`);
layer.node.removeAttribute("data-nested");
layer.node.removeAttribute("data-has-nested");
const parentOfSameType = this.getParentLayerOfType(layer.node, layer.type);
if (parentOfSameType) {
layer.node.setAttribute("data-nested", layer.type);
}
const nestedCount = this.countNestedLayersOfType(layer.node, layer.type);
if (nestedCount > 0) {
layer.node.setAttribute("data-has-nested", layer.type);
}
layer.node.style.setProperty("--nested-layer-count", `${nestedCount}`);
});
},
indexOf(node) {
return this.layers.findIndex((layer) => layer.node === node);
},
dismiss(node, parent) {
const index = this.indexOf(node);
if (index === -1) return;
const layer = this.layers[index];
addListenerOnce(node, LAYER_REQUEST_DISMISS_EVENT, (event) => {
layer.requestDismiss?.(event);
if (!event.defaultPrevented) {
layer?.dismiss();
}
});
fireCustomEvent(node, LAYER_REQUEST_DISMISS_EVENT, {
originalLayer: node,
targetLayer: parent,
originalIndex: index,
targetIndex: parent ? this.indexOf(parent) : -1
});
this.syncLayers();
},
clear() {
this.remove(this.layers[0].node);
}
};
function fireCustomEvent(el, type, detail) {
const win = el.ownerDocument.defaultView || window;
const event = new win.CustomEvent(type, { cancelable: true, bubbles: true, detail });
return el.dispatchEvent(event);
}
function addListenerOnce(el, type, callback) {
el.addEventListener(type, callback, { once: true });
}
export {
layerStack
};