wj-elements
Version:
WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.
350 lines (349 loc) • 17.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import WJElement from "./wje-element.js";
const styles = "/*\n[ WJ Tree Item ]\n*/\n\n:host {\n .native-tree-item {\n position: relative;\n display: flex;\n align-items: stretch;\n flex-direction: column;\n &.multiple {\n .item {\n border-radius: 0 !important;\n }\n }\n .item {\n display: flex;\n align-items: center;\n padding-inline: var(--wje-tree-item-padding-inline);\n padding-block: var(--wje-tree-item-padding-block);\n border-radius: var(--wje-tree-item-border-radius);\n background-color: var(--wje-tree-item-background);\n color: var(--wje-tree-item-color);\n\n &:hover {\n background-color: var(--wje-tree-item-background-hover);\n color: var(--wje-tree-item-color-hover);\n }\n\n .indent {\n display: block;\n width: calc(var(--wje-tree-item-indent, var(--wje-spacing-medium)) * var(--wje-tree-item-depth, 0));\n flex-shrink: 0;\n }\n\n .toggle {\n font-size: var(--wje-tree-item-font-size);\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: content-box;\n width: var(--wje-tree-item-indent);\n height: var(--wje-tree-item-indent);\n flex-shrink: 0;\n }\n\n wje-checkbox {\n font-size: var(--wje-tree-item-font-size);\n margin: 0;\n }\n\n .content {\n font-size: var(--wje-tree-item-font-size);\n }\n\n slot {\n display: flex;\n align-items: center;\n }\n\n slot:not([name])::slotted(wje-icon) {\n margin-right: var(--wje-spacing-2x-small);\n }\n\n slot[name='end'] {\n margin-inline-start: auto;\n }\n }\n .children {\n display: none;\n &.open {\n font-size: var(--wje-tree-item-font-size);\n display: block;\n\n ::before {\n content: '';\n position: absolute;\n top: var(--wje-tree-item-indent);\n bottom: 5px;\n left: calc(\n (var(--wje-tree-item-indent, var(--wje-spacing-medium)) * var(--wje-tree-item-depth, 0)) +\n 1em -\n (var(--wje-spacing-3x-small) * 2) -\n (var(--wje-tree-item-indent-guid-width) / 2)\n );\n border-inline-end: var(--wje-tree-item-indent-guid-width) solid var(--wje-border-color);\n z-index: 1;\n }\n }\n }\n }\n\n .native-tree-item.expanded .item slot[name='expand'],\n .native-tree-item:not(.expanded) slot[name='collapse'] {\n display: none;\n }\n}\n\n:host([selected]) {\n .item {\n background-color: var(--wje-tree-item-background-selected);\n color: var(--wje-tree-item-color-selected);\n }\n}\n:host([slot-hover-visible]) {\n .item {\n &:hover {\n slot[name='start'], slot[name='end'] {\n visibility: visible;\n }\n }\n slot[name='start'], slot[name='end'] {\n visibility: hidden;\n }\n }\n}\n";
class TreeItem extends WJElement {
/**
* Creates an instance of Toast.
*/
constructor() {
super();
/**
* The class name for the component.
* @type {string}
*/
__publicField(this, "className", "TreeItem");
this._selection = "single";
}
/**
* Sets the expanded state of the element. When set to a truthy value,
* the 'expanded' attribute will be added to the element. When set to a falsy
* value, the 'expanded' attribute will be removed.
* @param {boolean} value A boolean value indicating whether the
* element should be expanded (true) or collapsed (false).
*/
set expanded(value) {
if (value) {
this.setAttribute("expanded", "");
} else {
this.removeAttribute("expanded");
}
}
/**
* Retrieves the value of the 'expanded' state for the current element.
* This getter checks whether the 'expanded' attribute is present on the element.
* If the attribute exists, it returns true, representing that the element is expanded.
* Otherwise, it returns false, indicating that the element is not expanded.
* @returns {boolean} True if the 'expanded' attribute is present, false otherwise.
*/
get expanded() {
return this.hasAttribute("expanded");
}
/**
* Sets the 'selected' attribute of the element. Removes the attribute if the provided value is falsy; otherwise, sets it.
* @param {boolean} value The value indicating whether the element should have the 'selected' attribute.
*/
set selected(value) {
this.removeAttribute("selected");
if (value) this.setAttribute("selected", "");
}
/**
* Getter method for determining if the 'selected' attribute is present on the element.
* @returns {boolean} Returns true if the 'selected' attribute is present, otherwise false.
*/
get selected() {
return this.hasAttribute("selected");
}
/**
* Sets the selection mode for the component.
* @param {string} value The selection mode to apply. Defaults to 'single'
* if no value is provided. Possible options may be
* specific to the implementation of the component
* (e.g., 'single', 'multiple').
*/
set selection(value) {
this._selection = value || "single";
}
/**
* Retrieves the current selection.
* @returns {*} The value of the current selection.
*/
get selection() {
return this._selection;
}
/**
* Sets or removes the 'indeterminate' attribute based on the provided value.
* This can be used to visually indicate an indeterminate state for elements like checkboxes.
* @param {boolean} value A boolean indicating whether to set the element to an indeterminate state.
* If true, the 'indeterminate' attribute is added to the element; if false, the attribute is removed.
*/
set indeterminate(value) {
this.removeAttribute("indeterminate");
if (value) this.setAttribute("indeterminate", "");
}
/**
* Retrieves the state of the indeterminate attribute.
* @returns {boolean} True if the indeterminate attribute is present, otherwise false.
*/
get indeterminate() {
return this.hasAttribute("indeterminate");
}
/**
* Returns the CSS stylesheet for the component.
* @static
* @returns {CSSStyleSheet} The CSS stylesheet
*/
static get cssStyleSheet() {
return styles;
}
/**
* Setup attributes for the Button element.
*/
setupAttributes() {
this.isShadowRoot = "open";
this.syncAria();
}
connectedCallback() {
var _a;
(_a = super.connectedCallback) == null ? void 0 : _a.call(this);
this.syncNestingState();
}
/**
* Returns the list of attributes to observe for changes.
* @static
* @returns {Array<string>}
*/
static get observedAttributes() {
return ["selected", "indeterminate"];
}
/**
* Handles updates when observed attributes of the element are changed.
* Updates the checkbox state based on changes to the "selected" or "indeterminate" attributes.
* @param {string} name The name of the attribute that was changed.
* @param {string|null} oldValue The previous value of the attribute before the change.
* @param {string|null} newValue The new value of the attribute after the change.
* @returns {void}
*/
attributeChangedCallback(name, oldValue, newValue) {
if (!this.checkbox) {
this.syncAria();
return;
}
if (name === "selected") {
if (this.selected) {
this.checkbox.checked = true;
} else {
this.checkbox.checked = false;
}
}
if (name === "indeterminate" && !this.selected) {
this.checkbox.removeAttribute("indeterminate");
this.checkbox.removeAttribute("checked");
if (this.indeterminate) this.checkbox.setAttribute("indeterminate", "");
}
this.syncAria();
}
/**
* Custom logic executed before the draw process begins.
* Determines and sets the appropriate slot if the current item is nested.
* @returns {void} No return value.
*/
beforeDraw() {
var _a;
this.syncNestingState();
if ((_a = this.closest("wje-tree")) == null ? void 0 : _a.hasAttribute("slot-hover-visible"))
this.setAttribute("slot-hover-visible", "");
}
/**
* Synchronizes the nesting-related state on the host element.
* Sets the "slot" to children for nested items and updates
* the nesting depth used to compute visual indentation.
* @returns {void}
*/
syncNestingState() {
const depth = this.getNestingDepth();
this.style.setProperty("--wje-tree-item-depth", String(depth));
if (depth > 0) {
this.slot = "children";
} else if (this.getAttribute("slot") === "children") {
this.removeAttribute("slot");
}
}
/**
* Creates and returns a document fragment representing the structure of a tree item component.
* The method constructs the DOM elements including the native container, indentation, toggle button,
* selection checkbox, label, and children container, along with their respective slots and attributes.
* It dynamically handles the creation of expand and collapse icons, as well as appending slots for
* child components.
* @returns {DocumentFragment} A fragment containing the complete tree item structure to be rendered.
*/
draw() {
let fragment = document.createDocumentFragment();
let native = document.createElement("div");
native.setAttribute("part", "native");
native.classList.add("native-tree-item", this.selection === "multiple" ? "multiple" : "single");
let slotStart = document.createElement("slot");
slotStart.setAttribute("name", "start");
let item = document.createElement("div");
item.setAttribute("part", "item");
item.classList.add("item");
let indent = document.createElement("div");
indent.classList.add("indent");
let button = document.createElement("div");
button.classList.add("toggle");
let checkbox = document.createElement("wje-checkbox");
if (this.selected) checkbox.setAttribute("checked", "");
let label = document.createElement("div");
label.setAttribute("part", "label");
label.classList.add("content");
let slotElement = document.createElement("slot");
let children = document.createElement("div");
children.classList.add("children");
let slot = document.createElement("slot");
slot.setAttribute("name", "children");
children.appendChild(slot);
let slotEnd = document.createElement("slot");
slotEnd.setAttribute("name", "end");
item.appendChild(slotStart);
item.appendChild(indent);
if (this.querySelectorAll(":scope > wje-tree-item").length > 0) {
if (this.querySelectorAll('[slot="expand"]').length < 1) {
let expandIcon = document.createElement("wje-icon");
expandIcon.setAttribute("name", "chevron-right");
expandIcon.setAttribute("slot", "expand");
this.appendChild(expandIcon);
}
if (this.querySelectorAll('[slot="collapse"]').length < 1) {
let collapseIcon = document.createElement("wje-icon");
collapseIcon.setAttribute("name", "chevron-down");
collapseIcon.setAttribute("slot", "collapse");
this.appendChild(collapseIcon);
}
let expandSlot = document.createElement("slot");
expandSlot.setAttribute("name", "expand");
let collapseSlot = document.createElement("slot");
collapseSlot.setAttribute("name", "collapse");
button.appendChild(expandSlot);
button.appendChild(collapseSlot);
}
item.appendChild(button);
if (this.selection === "multiple") item.appendChild(checkbox);
label.appendChild(slotElement);
item.appendChild(label);
item.appendChild(slotEnd);
native.appendChild(item);
native.appendChild(children);
fragment.appendChild(native);
this.checkbox = checkbox;
this.native = native;
this.button = button;
this.childrenElement = children;
this.childrenSlot = slot;
return fragment;
}
/**
* Executes operations to be performed after the draw action is completed.
* If the state indicates it is expanded, toggles its children.
* Additionally, sets up an event listener on the button element to handle toggling children upon click.
* @returns {void} Does not return a value.
*/
afterDraw() {
if (this.expanded) this.toggleChildren();
this.button.addEventListener("click", this.toggleChildren.bind(this));
this.setAttribute("tabindex", "0");
this.syncAria();
}
/**
* Determines if the current item is a nested item within a tree structure.
* Checks if the item's parent element exists and is also a tree item.
* @returns {boolean} Returns true if the current item is a nested tree item; otherwise, false.
*/
isNestedItem() {
const parent = this.parentElement;
return !!parent && this.isTreeItem(parent);
}
/**
* Calculates nesting depth based on ancestor tree items.
* Root items have depth 0, direct children depth 1, etc.
* @returns {number}
*/
getNestingDepth() {
let depth = 0;
let current = this.parentElement;
while (current) {
if (this.isTreeItem(current)) depth += 1;
current = current.parentElement;
}
return depth;
}
/**
* Checks whether the given node is a tree item.
* @param {object} node The node to check.
* @returns {boolean} Returns true if the node is an Element and has a class name of 'TreeItem', otherwise false.
*/
isTreeItem(node) {
return node instanceof Element && node.className === "TreeItem";
}
/**
* Toggles the visibility state of the children element and updates the class of the parent element.
* The method toggles the 'open' class on the children elements and the 'expanded' class on the parent element,
* effectively showing or hiding the children and indicating the expanded state.
* @returns {void} Does not return a value.
*/
toggleChildren() {
this.childrenElement.classList.toggle("open");
this.native.classList.toggle("expanded");
this.syncAria();
}
/**
* Syncs ARIA attributes on the host element.
*/
syncAria() {
var _a, _b;
const hasChildren = this.querySelectorAll(":scope > wje-tree-item").length > 0;
const expanded = hasChildren ? (_b = (_a = this.native) == null ? void 0 : _a.classList) == null ? void 0 : _b.contains("expanded") : void 0;
if (this.selection === "multiple") {
const checked = this.indeterminate ? "mixed" : this.selected ? "true" : "false";
this.setAriaState({
role: "treeitem",
checked,
expanded
});
} else {
this.setAriaState({
role: "treeitem",
selected: this.selected,
expanded
});
}
}
/**
* Retrieves the child items from the `childrenSlot` that match specific criteria.
* @param {object} [options] Configuration options.
* @param {boolean} [options.includeDisabled] Determines whether disabled items should be included in the result. Defaults to true.
* @returns {Array} An array of child items that are valid tree items and meet the criteria specified in the options.
*/
getChildrenItems(options = {}) {
const includeDisabled = options.includeDisabled ?? true;
const assigned = this.childrenSlot ? this.childrenSlot.assignedElements({ flatten: true }) : [];
const direct = assigned.length ? assigned : Array.from(this.querySelectorAll(":scope > wje-tree-item"));
return direct.filter(
(item) => this.isTreeItem(item) && (includeDisabled || !item.disabled)
);
}
/**
* Retrieves all descendant children of the current object in a flattened array structure.
* @param {object} [options] An optional object specifying filters or configurations for retrieving children.
* @returns {Array} An array containing all children and their descendants in a flat structure.
*/
getAllChildrenFlat(options = {}) {
const directChildren = this.getChildrenItems(options);
return directChildren.flatMap((child) => [child, ...child.getAllChildrenFlat(options)]);
}
}
TreeItem.define("wje-tree-item", TreeItem);
export {
TreeItem as default
};
//# sourceMappingURL=wje-tree-item.js.map