Packages
noora
0.63.1
0.84.1
0.84.0
0.83.2
0.83.1
0.83.0
0.82.7
0.82.6
0.82.5
0.82.4
0.82.3
0.82.2
0.82.1
0.82.0
0.81.4
0.81.3
0.81.2
0.81.1
0.81.0
0.80.1
0.80.0
0.79.1
0.79.0
0.78.1
0.78.0
0.77.4
0.77.3
0.77.2
0.77.1
0.77.0
0.76.0
0.75.0
0.74.0
0.73.0
0.72.0
0.71.0
0.70.0
0.69.1
0.69.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.2
0.64.1
0.64.0
0.63.2
0.63.1
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.1
0.53.0
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.3
0.44.2
0.44.1
0.44.0
0.43.0
0.42.0
0.41.0
0.40.6
0.40.5
0.40.4
0.40.3
0.40.2
0.40.1
0.40.0
0.39.1
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.1
0.32.0
0.31.0
0.30.0
0.29.2
0.29.1
0.29.0
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.0
0.1.0
0.1.0-rc.2
0.1.0-rc.1
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
A component library for Phoenix LiveView applications
Current section
Files
Jump to
Current section
Files
js/Dropdown.js
import * as menu from "@zag-js/menu";
import {
normalizeProps,
spreadProps,
renderPart,
getBooleanOption,
getOption,
getPartSelector,
} from "./util.js";
import { Component } from "./component.js";
import { VanillaMachine } from "./machine.js";
class Menu extends Component {
initMachine(context) {
return new VanillaMachine(menu.machine, context);
}
initApi() {
return menu.connect(this.machine.service, normalizeProps);
}
render() {
const parts = [
"trigger",
"trigger:indicator",
"positioner",
"positioner:content",
];
for (const part of parts) renderPart(this.el, part, this.api);
this.renderItemGroupLabels();
this.renderItemGroups();
this.renderItems();
this.renderSeparators();
}
renderItemGroupLabels() {
for (const itemGroupLabel of this.el.querySelectorAll(
getPartSelector("positioner:content:item-group-label"),
)) {
const htmlFor = itemGroupLabel.getAttribute("for");
if (!htmlFor) {
console.error("Missing `for` attribute on item group label.");
return;
}
spreadProps(itemGroupLabel, this.api.getItemGroupLabelProps({ htmlFor }));
}
}
renderItemGroups() {
for (const itemGroup of this.el.querySelectorAll(
getPartSelector("positioner:content:item-group"),
)) {
const value = itemGroup.dataset.value;
if (!value) {
console.error("Missing `data-value` attribute on item group.");
return;
}
spreadProps(itemGroup, this.api.getItemGroupProps({ id: value }));
}
}
renderItems() {
const content = this.el.querySelector('[data-part="content"]');
if (!content) return;
for (const item of content.querySelectorAll('[data-part="item"]')) {
const value = item.dataset.value;
if (!value) {
console.error("Missing `data-value` attribute on item.");
return;
}
// Skip spreading props if item is inside breadcrumbs as that leads to issues on LiveView >= 1.1 due to conflicting state management between LiveView and JS layers.
if (item.closest(".noora-breadcrumbs")) {
continue;
}
spreadProps(item, this.api.getItemProps({ value }));
}
}
renderSeparators() {
for (const separator of this.el.querySelectorAll(
getPartSelector("positioner:content:separator"),
)) {
spreadProps(separator, this.api.getSeparatorProps());
}
}
}
/**
* Phoenix LiveView Hook for Menu component
*/
export default {
mounted() {
this.metadata = {};
for (const key in this.el.dataset) {
if (key.startsWith("meta")) {
const metaKey = key.charAt(4).toLowerCase() + key.slice(5);
this.metadata[metaKey] = this.el.dataset[key];
}
}
this.context = {
id: this.el.id,
loopFocus: getBooleanOption(this.el, "loopFocus"),
closeOnSelect: getBooleanOption(this.el, "closeOnSelect"),
typeahead: getBooleanOption(this.el, "typeahead"),
positioning: {
offset: { mainAxis: getOption(this.el, "positioningOffsetMainAxis") },
},
onOpenChange: (details) => {
if (this.el.dataset.onOpenChange) {
this.pushEvent(this.el.dataset.onOpenChange, details);
}
},
onHighlightChange: (details) => {
if (this.el.dataset.onHighlightChange) {
this.pushEvent(this.el.dataset.onHighlightChange, details);
}
},
onSelect: (details) => {
if (this.el.dataset.onSelect) {
this.pushEvent(this.el.dataset.onSelect, {
...this.metadata,
...details,
});
}
},
onEscapeKeyDown: (details) => {
if (this.el.dataset.onEscapeKeyDown) {
this.pushEvent(this.el.dataset.onEscapeKeyDown, details);
}
},
onPointerDownOutside: (details) => {
if (this.el.dataset.onPointerDownOutside) {
this.pushEvent(this.el.dataset.onPointerDownOutside, details);
}
},
onFocusOutside: (details) => {
if (this.el.dataset.onFocusOutside) {
this.pushEvent(this.el.dataset.onFocusOutside, details);
}
},
onInteractOutside: (details) => {
if (this.el.dataset.onInteractOutside) {
this.pushEvent(this.el.dataset.onInteractOutside, details);
}
},
};
this.menu = new Menu(this.el, this.context);
this.menu.init();
this.handleOpenDropdown = (event) => {
if (event.detail.id == this.el.id) {
this.menu.api.setOpen(true);
}
};
this.handleCloseDropdown = (event) => {
if (event.detail.all || event.detail.id === this.el.id) {
this.menu.api.setOpen(false);
}
};
window.addEventListener("phx:open-dropdown", this.handleOpenDropdown);
window.addEventListener("phx:close-dropdown", this.handleCloseDropdown);
},
updated() {
this.menu.render();
},
beforeDestroy() {
this.menu.destroy();
},
destroyed() {
window.removeEventListener("phx:open-dropdown", this.handleOpenDropdown);
window.removeEventListener("phx:close-dropdown", this.handleCloseDropdown);
},
};