UNPKG

@agnos-ui/core

Version:

Framework-agnostic headless component library.

228 lines (227 loc) 6.89 kB
import { writable, computed, readable } from "@amadeus-it-group/tansu"; import { createNavManager } from "./services/navManager.js"; import { k as mergeDirectives, n as createAttributesDirective, b as browserDirective, a as bindDirective } from "./dom-gfxqXJpK.js"; import { noop } from "./utils/func.js"; import { writablesForProps, stateStores, true$ } from "./utils/stores.js"; import { g as typeFunction, i as typeArray, e as typeString } from "./writables-CgpOQ4hA.js"; import { createWidgetFactory } from "./utils/widget.js"; function getTreeDefaultConfig() { return { ...defaultTreeConfig }; } const defaultTreeConfig = { className: "", nodes: [], onExpandToggle: noop, navSelector: (node) => node.querySelectorAll("button"), ariaLabelToggleFn: (label) => `Toggle ${label}` }; const configValidator = { className: typeString, nodes: typeArray, onExpandToggle: typeFunction, navSelector: typeFunction, ariaLabelToggleFn: typeFunction }; const createTree = createWidgetFactory("tree", (config) => { const [{ nodes$, onExpandToggle$, navSelector$, ariaLabelToggleFn$, ...stateProps }, patch] = writablesForProps( defaultTreeConfig, config, configValidator ); const treeMap = /* @__PURE__ */ new Map(); const _expandedMap = { get(item) { return item.isExpanded; } }; const _toggleChange$ = writable({}); const expandedMap$ = computed(() => { normalizedNodes$(); _toggleChange$(); return _expandedMap; }); const { elementsInDomOrder$, directive: navDirective, refreshElements, focusIndex, focusPrevious, focusNext, focusFirst, focusLast } = createNavManager(); const navManagerConfig$ = computed(() => ({ keys: { ArrowUp: focusPrevious, ArrowDown: focusNext, Home: focusFirst, End: focusLast }, selector: navSelector$() })); const traverseTree = (node, level, parent) => { var _a; const copyNode = { ...node, ariaLabel: node.ariaLabel ?? node.label, level, children: [], isExpanded: ((_a = node.children) == null ? void 0 : _a.length) ? node.isExpanded ?? false : void 0 }; treeMap.set(copyNode, { parent }); if (node.children) { copyNode.children = node.children.map((child) => traverseTree(child, level + 1, copyNode)); } return copyNode; }; const normalizedNodes$ = computed(() => { treeMap.clear(); return nodes$().map((node) => traverseTree(node, 0, void 0)); }); const _lastFocusedTreeItem$ = writable(normalizedNodes$().find((node) => node.isExpanded !== void 0)); const getTreeItemInfo = (item) => { const treeItem = treeMap.get(item); if (!treeItem) { console.error(`Node ${item.label} doesn't exist in the map`); } return treeItem; }; const treeItemElementDirective = browserDirective( (toggleItem, args) => { let treeItemInfo; const destroy = () => { if (treeItemInfo && treeItemInfo.htmlElement === toggleItem) { treeItemInfo.htmlElement = void 0; } treeItemInfo = void 0; }; const update = (args2) => { destroy(); treeItemInfo = getTreeItemInfo(args2.item); if (treeItemInfo) { if (treeItemInfo.htmlElement) { console.warn(`The tree item directive should be used once per element`); } treeItemInfo.htmlElement = toggleItem; } }; update(args); return { update, destroy }; } ); const focusElementIfExists = (itemToFocus) => { var _a; if (itemToFocus) { const mapItemHtml = (_a = getTreeItemInfo(itemToFocus)) == null ? void 0 : _a.htmlElement; if (mapItemHtml) { const index = elementsInDomOrder$().indexOf(mapItemHtml); focusIndex(index, 0); } } }; const itemToggleAttributesDirective = createAttributesDirective((treeItemContext$) => ({ events: { focus: () => { const { item } = treeItemContext$(); _lastFocusedTreeItem$.set(item); }, click: () => { const { item } = treeItemContext$(); toggleExpanded(item); }, keydown: (event) => { var _a, _b; const { key } = event; const { item } = treeItemContext$(); const isExpanded = item.isExpanded; refreshElements(); switch (key) { case "Enter": case " ": toggleExpanded(item); break; case "ArrowLeft": if (isExpanded) { toggleExpanded(item); } else { focusElementIfExists((_a = getTreeItemInfo(item)) == null ? void 0 : _a.parent); } break; case "ArrowRight": if (!isExpanded) { toggleExpanded(item); } else { focusElementIfExists((_b = item.children) == null ? void 0 : _b[0]); } break; default: return; } event.preventDefault(); event.stopPropagation(); } }, attributes: { "aria-label": computed(() => { const { item } = treeItemContext$(); return ariaLabelToggleFn$()(item.ariaLabel); }), tabindex: computed(() => { const { item } = treeItemContext$(); return item === _lastFocusedTreeItem$() ? "0" : "-1"; }), type: readable("button") }, classNames: { "au-tree-expand-icon": true$, "au-tree-expand-icon-expanded": computed(() => { _toggleChange$(); const { item } = treeItemContext$(); return item.isExpanded ?? false; }) } })); const toggleExpanded = (node) => { const treeItemInfo = getTreeItemInfo(node); if (treeItemInfo === void 0 || node.isExpanded === void 0) { return; } node.isExpanded = !node.isExpanded; _toggleChange$.set({}); onExpandToggle$()(node); }; const widget = { ...stateStores({ normalizedNodes$, expandedMap$, ...stateProps }), patch, api: {}, directives: { navigationDirective: bindDirective(navDirective, navManagerConfig$), itemToggleDirective: mergeDirectives(treeItemElementDirective, itemToggleAttributesDirective), itemAttributesDirective: createAttributesDirective((treeItemContext$) => ({ attributes: { role: readable("treeitem"), "aria-selected": readable("false"), // TODO: adapt aria-selected to the actual selected state "aria-expanded": computed(() => { var _a; const { item } = treeItemContext$(); _toggleChange$(); return (_a = item.isExpanded) == null ? void 0 : _a.toString(); }) } })) } }; return widget; }); export { createTree as c, getTreeDefaultConfig as g };