mirror of https://gitee.com/godoos/godoos.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
15 KiB
297 lines
15 KiB
class MenuDropElement extends HTMLElement {
|
|
constructor(){
|
|
super();
|
|
this.defined = false;
|
|
}
|
|
connectedCallback(){
|
|
if (this.defined || !this.isConnected) return;
|
|
this.defined = true;
|
|
this.attachShadow({ mode: "open" });
|
|
this.shadowRoot.addEventListener("keydown",event => {
|
|
if (event.key == "ArrowDown"){
|
|
event.preventDefault();
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(".opener, .list, .option")){
|
|
var options = this.getOptions((event.target.matches(".option")) ? event.target.closest(".list") : (event.target.matches(".list")) ? event.target : undefined), index = options.indexOf(event.target) + 1, option = options[(index <= options.length - 1) ? index : 0];
|
|
option.focus();
|
|
}
|
|
if (event.target.matches(".sub-list[data-open] > .option")) this.close(event.target.closest(".sub-list"));
|
|
}
|
|
if (event.key == "ArrowUp"){
|
|
event.preventDefault();
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(".opener, .list, .option")){
|
|
var options = this.getOptions((event.target.matches(".option")) ? event.target.closest(".list") : (event.target.matches(".list")) ? event.target : undefined), index = options.indexOf(event.target) - 1, option = options[(index >= 0) ? index : options.length - 1];
|
|
option.focus();
|
|
}
|
|
if (event.target.matches(".sub-list[data-open] > .option")) this.close(event.target.closest(".sub-list"));
|
|
}
|
|
if (event.key == "ArrowRight" || event.key == "ArrowLeft"){
|
|
event.preventDefault();
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(".list")){
|
|
var options = this.getOptions(event.target), index = options.indexOf(event.target) + 1;
|
|
options[(index <= options.length - 1) ? index : 0].focus();
|
|
}
|
|
}
|
|
if (event.key == "ArrowRight"){
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(".sub-list > .option")){
|
|
var subList = event.target.closest(".sub-list"), option = this.getOptions(subList.querySelector(".list"))[0];
|
|
this.open(subList);
|
|
option.focus();
|
|
}
|
|
}
|
|
if (event.key == "ArrowLeft"){
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(".sub-list[data-open] > .option")) return this.close(event.target.closest(".sub-list"));
|
|
if (event.target.matches(".sub-list > .list .option")){
|
|
var subList = event.target.closest(".list").closest(".sub-list"), option = subList.querySelector(".option");
|
|
this.close(subList);
|
|
option.focus();
|
|
}
|
|
}
|
|
if (event.key == "Escape"){
|
|
if (!this.matches("[data-open]")) return;
|
|
event.preventDefault();
|
|
this.close((event.target.matches(".sub-list > .list .option")) ? event.target.closest(".list").closest(".sub-list") : undefined);
|
|
((event.target.matches(".sub-list > .list .option")) ? event.target.closest(".list").closest(".sub-list").querySelector(".option") : this.opener).focus();
|
|
}
|
|
if (event.key == "Tab"){
|
|
if (this.matches("[data-open]")) event.preventDefault();
|
|
}
|
|
if (event.key == "Enter"){
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(":not(a).option")){
|
|
event.preventDefault();
|
|
event.target.click();
|
|
}
|
|
if (event.target.matches(".sub-list > .option")) this.getOptions(event.target.closest(".sub-list").querySelector(".list"))[0].focus();
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("keyup",event => {
|
|
if (event.key == " "){
|
|
if (!this.matches("[data-open]")) return;
|
|
if (event.target.matches(".option")){
|
|
event.preventDefault();
|
|
event.target.click();
|
|
}
|
|
if (event.target.matches(".sub-list > .option")) this.getOptions(event.target.closest(".sub-list").querySelector(".list"))[0].focus();
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("pointerdown",event => {
|
|
this.shadowRoot.pointerType = event.pointerType;
|
|
if (event.pointerType != "mouse"){
|
|
if (event.target.matches(".opener") && this.matches("[data-alternate]")){
|
|
this.shadowRoot.alternateTimeout = window.setTimeout(() => {
|
|
if (event.target != this.shadowRoot.activeElement) event.target.focus();
|
|
this.toggle();
|
|
},500);
|
|
}
|
|
}
|
|
if (event.pointerType == "mouse"){
|
|
if ((event.button != 0 && !event.target.matches("a")) || event.target.matches(".opener, .list")) event.preventDefault();
|
|
if (event.target.matches(".opener") && !this.matches("[data-alternate]")){
|
|
if (event.target != this.shadowRoot.activeElement) event.target.focus();
|
|
if (event.button == 0) this.toggle();
|
|
}
|
|
if (event.target.matches(".list")){
|
|
event.target.querySelectorAll(":scope > li > .sub-list[data-open]").forEach(subList => this.close(subList));
|
|
event.target.focus();
|
|
}
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("pointermove",event => {
|
|
if (event.pointerType != "mouse"){
|
|
if (event.target.matches(".opener") && this.matches("[data-alternate]")){
|
|
if (!("alternateTimeout" in this.shadowRoot)) return;
|
|
window.clearTimeout(this.shadowRoot.alternateTimeout);
|
|
delete this.shadowRoot.alternateTimeout;
|
|
}
|
|
}
|
|
if (event.pointerType == "mouse"){
|
|
if (event.target == this.shadowRoot.activeElement) return;
|
|
if (event.target.matches(".option")) event.target.focus();
|
|
if (event.target.matches(".sub-list > .option")) this.open(event.target.closest(".sub-list"));
|
|
if (event.target.matches(":not(.sub-list) > .option")) event.target.closest(".list").querySelectorAll(":scope > li > .sub-list[data-open]").forEach(subList => this.close(subList));
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("pointerout",event => {
|
|
if (event.pointerType == "mouse") return;
|
|
if (event.target.matches(".opener") && this.matches("[data-alternate]")){
|
|
if (!("alternateTimeout" in this.shadowRoot)) return;
|
|
window.clearTimeout(this.shadowRoot.alternateTimeout);
|
|
delete this.shadowRoot.alternateTimeout;
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("pointerup",event => {
|
|
if (event.pointerType == "mouse") return;
|
|
if (event.target.matches(".list")){
|
|
event.target.querySelectorAll(":scope > li > .sub-list[data-open]").forEach(subList => this.close(subList));
|
|
event.target.focus();
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("click",event => {
|
|
if (!("pointerType" in event)) event.pointerType = this.shadowRoot.pointerType;
|
|
delete this.shadowRoot.pointerType;
|
|
if (event.target.matches(".opener") && !this.matches("[data-alternate]")){
|
|
if (event.pointerType == "mouse") return;
|
|
if (event.target != this.shadowRoot.activeElement) event.target.focus();
|
|
this.toggle();
|
|
}
|
|
if (event.target.matches(".sub-list > .option")) return this[(!event.pointerType == "mouse") ? "open" : "toggle"](event.target.closest(".sub-list"));
|
|
if (event.target.matches(".option")){
|
|
if (this.matches("[data-select]") && !event.target.matches("[data-no-select]") && !event.target.matches("[data-disabled]")) this.select(event.target);
|
|
this.close();
|
|
this.opener.focus();
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("contextmenu",event => {
|
|
if (!event.target.matches("a")) event.preventDefault();
|
|
if (event.target.matches(".opener") && this.matches("[data-alternate]")){
|
|
if (event.target != this.shadowRoot.activeElement) event.target.focus();
|
|
this.toggle();
|
|
}
|
|
});
|
|
this.shadowRoot.addEventListener("focusout",event => {
|
|
window.requestAnimationFrame(() => {
|
|
if (document.activeElement == this) return;
|
|
if (this.matches("[data-open]")) this.close();
|
|
});
|
|
});
|
|
window.requestAnimationFrame(() => {
|
|
this.container = document.createElement("div");
|
|
this.container.part.add("container");
|
|
this.container.classList.add("container");
|
|
this.container.setAttribute("ontouchstart","");
|
|
this.styles = document.createElement("link");
|
|
this.styles.rel = "stylesheet";
|
|
this.styles.href = "/text/css/styles.css";
|
|
this.opener = this.querySelector("button") || document.createElement("button");
|
|
this.opener.part.add("opener");
|
|
this.opener.classList.add("opener");
|
|
this.body = document.createElement("div");
|
|
this.body.part = "body";
|
|
this.body.classList.add("body");
|
|
this.main = this.querySelector("ul") || document.createElement("ul");
|
|
this.main.part.add("list");
|
|
this.main.part.add("main");
|
|
this.main.classList.add("list");
|
|
this.main.classList.add("main");
|
|
this.main.tabIndex = -1;
|
|
this.main.querySelectorAll("ul").forEach(list => {
|
|
var subList = document.createElement("div"), option = list.closest("li"), opener = document.createElement("span");
|
|
subList.part.add("sub-list");
|
|
subList.classList.add("sub-list");
|
|
opener.textContent = this.getTextNodes(option)[0].textContent;
|
|
this.getTextNodes(option)[0].textContent = "";
|
|
list.part.add("list");
|
|
list.classList.add("list");
|
|
list.tabIndex = -1;
|
|
list.parentElement.insertBefore(subList,list);
|
|
subList.appendChild(opener);
|
|
subList.appendChild(list);
|
|
});
|
|
this.main.querySelectorAll("li, a, .sub-list > span").forEach(option => {
|
|
if (option.querySelector(":scope > hr")) option.classList.add("pass-through");
|
|
if (option.querySelector(":scope > :is(a,hr,.sub-list)")) return;
|
|
option.part.add("option");
|
|
option.classList.add("option");
|
|
option.tabIndex = -1;
|
|
if (option.matches("[data-shortcuts]")){
|
|
var shortcuts = JSON.parse(option.getAttribute("data-shortcuts"));
|
|
if ("macOS" in shortcuts) shortcuts.macOS = shortcuts.macOS.replace(/Ctrl/g,"⌃").replace(/Option/g,"⌥").replace(/Shift/g,"⇧").replace(/Cmd/g,"⌘").replace(/Return/g,"↵").replace(/Enter/g,"⌤").replace(/\+/g,"");
|
|
var shortcut = document.createElement("span");
|
|
shortcut.part.add("shortcut");
|
|
shortcut.classList.add("shortcut");
|
|
shortcut.textContent = shortcuts[(/(macOS|Mac|iPhone|iPad|iPod)/i.test(("userAgentData" in navigator) ? navigator.userAgentData.platform : navigator.platform) && "macOS" in shortcuts) ? "macOS" : "default"];
|
|
option.appendChild(shortcut);
|
|
}
|
|
if (option.matches("[onclick]")) option.setAttribute("onclick",`window.setTimeout(${option.onclick});`);
|
|
if (option.querySelector(":scope > :is(img,svg)")){
|
|
var icon = option.querySelector(":scope > :is(img,svg)");
|
|
icon.part.add("icon");
|
|
icon.classList.add("icon");
|
|
icon.draggable = false;
|
|
}
|
|
});
|
|
this.main.querySelectorAll("hr").forEach(divider => {
|
|
divider.part.add("divider");
|
|
divider.classList.add("divider");
|
|
});
|
|
this.shadowRoot.appendChild(this.container);
|
|
this.container.appendChild(this.styles);
|
|
this.container.appendChild(this.opener);
|
|
this.container.appendChild(this.body);
|
|
this.body.appendChild(this.main);
|
|
this.innerHTML = "";
|
|
if (this.matches("[data-select]") && !this.matches("[data-select='no-appearance']")){
|
|
if (this.getAttribute("data-select") != "") this.setAttribute("data-select","");
|
|
new ResizeObserver(() => this.opener.style.minWidth = `${this.main.offsetWidth}px`).observe(this.main);
|
|
if (this.main.querySelector(".option[data-selected]")) this.opener.textContent = this.getTextNodes(this.main.querySelector(".option[data-selected]"))[0].textContent;
|
|
}
|
|
});
|
|
}
|
|
open(section = this){
|
|
var list = (section == this) ? this.main : section.querySelector(".list");
|
|
if (section == this){
|
|
var bounds = this.opener.getBoundingClientRect();
|
|
this.body.style.left = `calc(${bounds.left - parseInt(window.getComputedStyle(this).getPropertyValue("--safe-area-inset-left")) + ((CSS.supports("-webkit-touch-callout: none")) ? window.visualViewport.offsetLeft : 0)}px + var(--safe-area-inset-left))`;
|
|
this.body.style.top = `calc(${bounds.bottom - parseInt(window.getComputedStyle(this).getPropertyValue("--safe-area-inset-top")) + ((CSS.supports("-webkit-touch-callout: none")) ? window.visualViewport.offsetTop : 0)}px + var(--safe-area-inset-top))`;
|
|
this.body.style.width = `${bounds.width}px`;
|
|
}
|
|
Array.from(((section == this) ? this.main : section.closest(".list")).querySelectorAll(".sub-list[data-open]")).filter(subList => subList != list.closest(".sub-list")).forEach(subList => this.close(subList,false));
|
|
list.part.add((this.getVisibility(section)) ? "left" : "right");
|
|
list.classList.add((this.getVisibility(section)) ? "left" : "right");
|
|
section.setAttribute("data-open","");
|
|
list.part.add("open");
|
|
}
|
|
close(section = this,recursive = true){
|
|
var list = (section == this) ? this.main : section.querySelector(".list");
|
|
if (recursive) list.querySelectorAll(".sub-list[data-open]").forEach(subList => this.close(subList,false));
|
|
if (!section.matches("[data-open]")) return;
|
|
section.removeAttribute("data-open");
|
|
list.part.remove("open");
|
|
if (section == this) this.body.removeAttribute("style");
|
|
if (list.matches(".left")){
|
|
list.part.remove("left");
|
|
list.classList.remove("left");
|
|
}
|
|
if (list.matches(".right")){
|
|
list.part.remove("right");
|
|
list.classList.remove("right");
|
|
}
|
|
}
|
|
toggle(section = this){
|
|
(!section.matches("[data-open]")) ? this.open(section) : this.close(section);
|
|
}
|
|
select(option){
|
|
if (!this.matches("[data-select]")) return;
|
|
if (!option && option != 0) return;
|
|
if (typeof option == "number") option = this.getOptions()[option];
|
|
if (typeof option == "string") option = this.main.querySelector(`[data-value="${option}"]`);
|
|
if (!this.main.contains(option)) return;
|
|
this.getOptions(option.closest(".list")).filter(option => option.matches("[data-selected]")).forEach(option => option.removeAttribute("data-selected"));
|
|
option.setAttribute("data-selected","");
|
|
if (!this.matches("[data-select='no-appearance']")) this.opener.textContent = this.getTextNodes(option)[0].textContent;
|
|
return option;
|
|
}
|
|
getOptions(container = this.main){
|
|
var elements = container.querySelectorAll(":scope > .option, :scope > li > .option, :scope > li > .sub-list > .option");
|
|
return Array.from(elements).filter(element => ((window.getComputedStyle(element).getPropertyValue("display") != "none") && !element.matches("[data-disabled]")));
|
|
}
|
|
getVisibility(element = this.main){
|
|
var bounds = element.getBoundingClientRect();
|
|
return (bounds.left >= 0 && bounds.right <= window.innerWidth - bounds.width);
|
|
}
|
|
getTextNodes(element){
|
|
return Array.from(element.childNodes).filter(node => node.nodeType == Node.TEXT_NODE && node.textContent.replace(/\s/g,"").length);
|
|
}
|
|
focus({ preventScroll = false } = {}){
|
|
this.opener.focus({ preventScroll });
|
|
}
|
|
blur(){
|
|
this.shadowRoot.activeElement.blur();
|
|
}
|
|
}
|
|
window.customElements.define("menu-drop",MenuDropElement);
|