UNPKG

@agnos-ui/core

Version:

Framework-agnostic headless component library.

378 lines (377 loc) 11.6 kB
"use strict"; const tansu = require("@amadeus-it-group/tansu"); const dom = require("@floating-ui/dom"); const services_floatingUI = require("./services/floatingUI.cjs"); const services_focustrack = require("./services/focustrack.cjs"); const services_navManager = require("./services/navManager.cjs"); const utils_directive = require("./dom-CuBx1JPZ.cjs"); const utils_func = require("./utils/func.cjs"); const utils_stores = require("./utils/stores.cjs"); const utils_widget = require("./utils/widget.cjs"); const defaultConfig = { id: void 0, ariaLabel: "Select", open: false, disabled: false, items: [], filterText: "", loading: false, selected: [], navSelector: (node) => node.querySelectorAll(".au-select-badge,input"), itemIdFn: (item) => "" + item, onOpenChange: utils_func.noop, onFilterTextChange: utils_func.noop, onSelectedChange: utils_func.noop, allowedPlacements: ["bottom-start", "top-start", "bottom-end", "top-end"], className: "", menuClassName: "", menuItemClassName: "", badgeClassName: "" }; function getSelectDefaultConfig() { return { ...defaultConfig }; } const createSelect = utils_widget.createWidgetFactory("select", (config) => { const [ { id$: _dirtyId$, open$: _dirtyOpen$, filterText$: _dirtyFilterText$, items$, itemIdFn$, onOpenChange$, onFilterTextChange$, onSelectedChange$, allowedPlacements$, navSelector$, className$, badgeClassName$, ariaLabel$, menuClassName$, ...stateProps }, patch ] = utils_stores.writablesForProps(defaultConfig, config); const { selected$ } = stateProps; const id$ = tansu.computed(() => _dirtyId$() ?? utils_directive.generateId()); const filterText$ = utils_stores.bindableProp(_dirtyFilterText$, onFilterTextChange$); const { hasFocus$, directive: hasFocusDirective } = services_focustrack.createHasFocus(); const open$ = utils_stores.bindableDerived(onOpenChange$, [_dirtyOpen$, hasFocus$], ([_dirtyOpen, hasFocus]) => _dirtyOpen && hasFocus); const selectedContextsMap$ = tansu.computed(() => { const selectedItemsContext = /* @__PURE__ */ new Map(); const itemIdFn = itemIdFn$(); for (const item of selected$()) { const id = itemIdFn(item); selectedItemsContext.set(id, { item, id: itemIdFn(item), selected: true }); } return selectedItemsContext; }); const selectedContexts$ = tansu.computed(() => [...selectedContextsMap$().values()]); const highlightedIndex$ = function() { const store = tansu.writable(0); return tansu.asWritable(store, (index) => { const { length } = visibleItems$(); if (index != void 0) { if (!length) { index = void 0; } else if (index < 0) { index = length - 1; } else if (index >= length) { index = 0; } } store.set(index); }); }(); const itemContexts$ = tansu.computed(() => { const itemContexts = /* @__PURE__ */ new Map(); if (open$()) { const selectedContextsMap = selectedContextsMap$(); const itemIdFn = itemIdFn$(); for (const item of items$()) { const id = itemIdFn(item); itemContexts.set(id, { item, id, selected: selectedContextsMap.has(id) }); } } return itemContexts; }); const visibleItems$ = tansu.computed(() => open$() ? [...itemContexts$().values()] : []); const highlighted$ = tansu.computed(() => { const visibleItems = visibleItems$(); const highlightedIndex = highlightedIndex$(); return visibleItems.length && highlightedIndex != void 0 ? visibleItems[highlightedIndex] : void 0; }); const { directives: { floatingDirective, referenceDirective }, stores: { placement$ } } = services_floatingUI.createFloatingUI({ props: { computePositionOptions: tansu.asWritable( tansu.computed(() => ({ middleware: [ dom.offset(5), dom.autoPlacement({ allowedPlacements: allowedPlacements$() }), dom.size() ] })) ) } }); const { directive: navDirective, refreshElements, focusFirst, focusLast, focusLeft, focusRight } = services_navManager.createNavManager(); const navManagerConfig$ = tansu.computed( () => ({ keys: { Home: focusFirst, End: focusLast, ArrowLeft: focusLeft, ArrowRight: focusRight }, selector: navSelector$() }) ); const onRemoveBadge = (event, item) => { const referenceElement = event.target; refreshElements(); widget.api.unselect(item); if (referenceElement instanceof HTMLElement) { setTimeout(() => { if (!focusLeft({ event, referenceElement })) { focusRight({ event, referenceElement }); } }); } event.preventDefault(); }; const inputContainerAttributesDirective = utils_directive.createAttributesDirective(() => ({ attributes: { role: tansu.readable("combobox"), "aria-haspopup": tansu.readable("listbox"), "aria-expanded": tansu.computed(() => `${open$()}`), "aria-controls": tansu.computed(() => `${id$()}-menu`) } })); const badgeAttributesDirective = utils_directive.createAttributesDirective((itemContext$) => ({ attributes: { tabindex: tansu.readable(-1), class: badgeClassName$ }, classNames: { "au-select-badge": utils_stores.true$ }, events: { keydown: (event) => { let keyManaged = false; switch (event.key) { case "Backspace": case "Delete": { onRemoveBadge(event, itemContext$().item); keyManaged = true; break; } } if (keyManaged) { event.preventDefault(); } } } })); const menuAttributesDirective = utils_directive.createAttributesDirective(() => ({ attributes: { role: tansu.readable("listbox"), id: tansu.computed(() => `${id$()}-menu`), "data-popper-placement": placement$, class: menuClassName$ }, events: { mousedown: (e) => e.preventDefault() } })); const itemAttributesDirective = utils_directive.createAttributesDirective((itemContext$) => ({ attributes: { role: tansu.readable("option"), "aria-selected": tansu.computed(() => `${itemContext$().selected}`), style: tansu.readable("cursor: pointer") }, classNames: { "au-select-item": utils_stores.true$, selected: tansu.computed(() => itemContext$().selected) }, events: { click: () => widget.api.toggleItem(itemContext$().item) } })); const inputDirective = utils_directive.createAttributesDirective(() => ({ attributes: { id: id$, type: tansu.readable("text"), "aria-label": ariaLabel$, "aria-autocomplete": tansu.readable("list"), autocorrect: tansu.readable("off"), autocapitalize: tansu.readable("none"), autocomplete: tansu.readable("off") }, classNames: { "au-select-input": utils_stores.true$ }, events: { input: (event) => { const value = event.target.value; tansu.batch(() => { open$.set(value != null && value !== ""); filterText$.set(value); }); }, keydown: ({ ctrlKey, key, preventDefault }) => { let keyManaged = true; switch (key) { case "ArrowDown": { const isOpen = open$(); if (isOpen) { if (ctrlKey) { widget.api.highlightLast(); } else { widget.api.highlightNext(); } } else { widget.api.open(); widget.api.highlightFirst(); } break; } case "ArrowUp": if (ctrlKey) { widget.api.highlightFirst(); } else { widget.api.highlightPrevious(); } break; case "Enter": { const itemCtx = highlighted$(); if (itemCtx) { widget.api.toggleItem(itemCtx.item); } break; } case "Escape": open$.set(false); break; default: keyManaged = false; } if (keyManaged) { preventDefault(); } } } })); const badgeCloseButtonDirective = utils_directive.createAttributesDirective((itemContext$) => ({ events: { click: (event) => { onRemoveBadge(event, itemContext$().item); } } })); const widget = { ...utils_stores.stateStores({ id$, visibleItems$, highlighted$, open$, selectedContexts$, filterText$, placement$, className$, badgeClassName$, ariaLabel$, menuClassName$, ...stateProps }), patch, api: { clear() { selected$.set([]); }, select(item) { widget.api.toggleItem(item, true); }, unselect(item) { widget.api.toggleItem(item, false); }, toggleItem(item, selected) { const itemIdFn = itemIdFn$(); const itemId = itemIdFn(item); const selectedContextsMap = selectedContextsMap$(); const isInSelected = selectedContextsMap.has(itemId); if (selected == null) { selected = !isInSelected; } if (selected && !itemContexts$().has(itemId) || !selected && !isInSelected) { return; } selected$.update((selectedItems) => { var _a; selectedItems = [...selectedItems]; if (selected && !isInSelected) { selectedItems.push(item); } else if (!selected && isInSelected) { const index = selectedItems.findIndex((item2) => itemIdFn(item2) === itemId); selectedItems.splice(index, 1); } (_a = onSelectedChange$()) == null ? void 0 : _a(selectedItems); return selectedItems; }); }, clearText() { }, highlight(item) { const index = visibleItems$().findIndex((itemCtx) => itemCtx.item === item); highlightedIndex$.set(index === -1 ? void 0 : index); }, highlightFirst() { highlightedIndex$.set(0); }, highlightPrevious() { highlightedIndex$.update((highlightedIndex) => { return highlightedIndex != null ? highlightedIndex - 1 : -1; }); }, highlightNext() { highlightedIndex$.update((highlightedIndex) => { return highlightedIndex != null ? highlightedIndex + 1 : Infinity; }); }, highlightLast() { highlightedIndex$.set(-1); }, open: () => widget.api.toggle(true), close: () => widget.api.toggle(false), toggle(isOpen) { open$.update((value) => isOpen != null ? isOpen : !value); } }, directives: { hasFocusDirective, floatingDirective, referenceDirective, inputContainerDirective: utils_directive.mergeDirectives(utils_directive.bindDirective(navDirective, navManagerConfig$), inputContainerAttributesDirective), badgeAttributesDirective, menuAttributesDirective, itemAttributesDirective, inputDirective, badgeCloseButtonDirective } }; return widget; }); exports.createSelect = createSelect; exports.getSelectDefaultConfig = getSelectDefaultConfig;