Greasy Fork is available in English.

lib:menu

none

// ==UserScript==
// @name         lib:menu
// @version      3
// @description  none
// @run-at       document-start
// @author       You
// @license      GPLv3
// @match        *://*/*
// @exclude      /livereload.net\/files\/ffopen\/index.html$/
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAHJQTFRFAAAAEIijAo2yAI60BYyuF4WaFIifAY6zBI2wB4usGIaZEYigIoiZCIyrE4igG4iYD4mjEomhFoedCoqpDIqnDomlBYyvE4efEYmiDYqlA42xBoytD4mkCYqqGYSUFYidC4qoC4upAo6yCoupDYqmCYur4zowOQAAACZ0Uk5TAO////9vr////1+/D/+/L+/Pf/////+f3///////H4////////+5G91rAAACgUlEQVR4nM2Y22KjIBCGidg1264liZqDadK03X3/V2wNKHMC7MpF/xthHD5mgERAqZhWhfYqH6K+Qf2qNNf625hCoFj9/gblMUi5q5jLkXLCKudgyiRm0FMK82cWJp1fLbV5VmvJbCIc0GCYaFqqlDJgADdBjncqAXYobm1xh72aFMflbysteFfdy2Yi1XGOm5HGBzQ1dq7TzEoxjeNTjQZb7VA3e1c7+ImgasAgQ9+xusNVNZIo5xmOMgihIS2PbCQIiHEUdTvhxCcS/kPomfFI2zHy2PkWmA6aNatIJpKFJyekyy02xh5Y3DI9T4aOT6VhIUrsNTFp1pf79Z4SIIVDegl6IJO6cHiL/GimIZDhgTu/BlYWCQzHMl0zBWT/T3KAhtxOuUB9FtBrpsz0RV4xsjHmW+UCaffcSy/5viMGer0/6HdFNMZBq/vjJL38H9Dqx4Fuy0Em12DbZy+9pGtiDijbglwAehyj11n0tRD3WUBm+lwulE/8h4BuA+iWAQQnteg2Xm63WQLTpnMnpjdge0Mgu/GRPsV4xdjQ94Lfi624fabhDkfUqIKNrM64Q837v8yL0prasepCgrtvw1sJpoqanGEX7b5mQboNW8eawXaWXTMfMGxub472hzWzHSn6Sg2G9+6TAyRruE71s+zAzjWaknoyJCQzwxrghH2k5FDT4eqWunuNxyN9QCGcxVod5oADbYnIUkDTGZEf1xDJnSFteQ3KdsT8zYDMQXcHxsevcLH1TrsABzkNPyA/L7b0jg704viMMlpQI96WsHknCt/3YH0kOEo9zcGkwrFK39ck72rmoehmKqo2RKlilzSy/nJKEV45CT38myJp456fezktHjN5aeMAAAAASUVORK5CYII=
// @grant        none
// @namespace https://greasyfork.org/users/1184528
// ==/UserScript==
JSON.tryparse ??= function (json) {
  try {
    return JSON.parse(json)
  } catch (e) {
    console.warn("JSON.tryparse error", e)
    return undefined
  }
}
// hint button style useroption
;(() => {
  function checkformatofformat(obj) {
    with (loadlib("strict")) {
      obj.forEach((obj) =>
        testformat(obj, {
          default: setformat({ type: ["any", "none"] }),
          round: condfunc(({ type }) =>
            setformat({
              type: type == "number" ? ["boolean", "none"] : ["none"],
            })
          ),
          text: condfunc(({ key, type }) =>
            setformat({
              type:
                type == "groupend"
                  ? ["none"]
                  : key
                  ? ["string", "none"]
                  : ["string"],
            })
          ),
          key: condfunc(({ text, type }) =>
            setformat({
              type:
                type == "button" || type == "groupend" || type == "groupstart"
                  ? ["undefined"]
                  : text
                  ? ["string", "none"]
                  : ["string"],
            })
          ),
          ignorekeycase: condfunc(({ type }) =>
            setformat({
              type: type == "key" ? ["boolean", "none"] : ["undefined"],
            })
          ),
          type: setformat({
            value: [
              "button",
              "boolean",
              "text",
              "number",
              "key",
              "none",
              "select",
              "range",
              "groupstart",
              "groupend",
              //               "color",
              //               "array",
            ],
          }),
          forcelower: condfunc((obj) =>
            setformat({
              value: ["text", "key"].includes(obj.type)
                ? obj.forceupper
                  ? [false, undefined]
                  : [true, false, undefined]
                : [undefined],
            })
          ),
          forceupper: condfunc((obj) =>
            setformat({
              value: ["text", "key"].includes(obj.type)
                ? obj.forcelower
                  ? [false, undefined]
                  : [true, false, undefined]
                : [undefined],
            })
          ),
          tab: optional(setformat({ type: "number" })),
          innerstyle: optional(
            setformat(
              {},
              { allowextras: true, extrasformat: { type: ["string"] } }
            )
          ),
          textstyle: optional(
            setformat(
              {},
              { allowextras: true, extrasformat: { type: ["string"] } }
            )
          ),
          buttontext: condfunc((obj) =>
            setformat({
              type: obj.type == "button" ? ["string"] : ["none"],
            })
          ),
          hint: optional(setformat({ type: ["string"] })),
          onclick: condfunc((obj) =>
            setformat({
              type:
                obj.type == "button" ? ["function", "asyncfunction"] : ["none"],
            })
          ),
          options: condfunc((obj) =>
            setformat({
              type: obj.type == "select" ? ["array"] : ["none"],
            })
          ),
          oncontextmenu: condfunc((obj) =>
            setformat({
              type:
                obj.type == "button"
                  ? ["function", "asyncfunction", "none"]
                  : ["none"],
            })
          ),
          min: condfunc(({ type }) =>
            setformat({
              type:
                type == "range"
                  ? ["number"]
                  : type == "number"
                  ? ["number", "none"]
                  : ["none"],
            })
          ),
          step: condfunc(({ type }) =>
            setformat({
              type:
                type == "range"
                  ? ["number"]
                  : type == "number"
                  ? ["number", "none"]
                  : ["none"],
            })
          ),
          max: condfunc(({ type }) =>
            setformat({
              type:
                type == "range"
                  ? ["number"]
                  : type == "number"
                  ? ["number", "none"]
                  : ["none"],
            })
          ),
          hinttype: condfunc((obj) =>
            setformat({
              value: "hint" in obj ? ["text", "html"] : [undefined],
            })
          ),
          allowmodifiers: condfunc((obj) =>
            setformat({
              value: obj.type == "key" ? [true, false] : [undefined],
            })
          ),
          keyreturntype: condfunc((obj) =>
            setformat({
              value: obj.type == "key" ? ["object", "string"] : [undefined],
            })
          ),
          allowanynumber: condfunc(({ type }) =>
            setformat({
              type: type == "range" ? ["boolean", "none"] : ["undefined"],
            })
          ),
        })
      )
    }
  }
  function blockevent(e) {
    if (!e) return
    e.preventDefault?.()
    e.stopPropagation?.()
    e.stopImmediatePropagation?.()
  }
  const a = loadlib("allfuncs")
  const e = loadlib("newelem")
  const userliboptions = loadlib("libloader").savelib(
    "menu",
    class menu {
      #options
      #globalopts
      #format
      #main
      constructor(...args) {
        this.#globalopts = {}
        this.update(...args)
      }
      update(options, format, globalopts = {}) {
        this.#options = options || this.#options
        this.#format = [...(format || this.#format)].map((e) => {
          return { ...e }
        })
        checkformatofformat(this.#format)
        const { setformat, testformat } = loadlib("strict")
        testformat(
          { ...this.#globalopts, ...globalopts },
          {
            noclosebutton: setformat({ type: ["boolean", "undefined"] }),
            onchange: setformat({ type: ["function", "undefined"] }),
            onhide: setformat({ type: ["function", "undefined"] }),
            onshow: setformat({ type: ["function", "undefined"] }),
            position: setformat({
              value: [
                "unset",
                "full screen",
                "top left",
                "top center",
                "top right",
                "center left",
                "center",
                "center right",
                "bottom left",
                "bottom center",
                "bottom right",
              ],
            }),
            dimmeropacity: setformat({ type: ["number", "none"] }),
            cursor: setformat({ type: ["string", "none"] }),
          },
          { functionname: "lib:menu" }
        )
        Object.assign(this.#globalopts, globalopts)
        if (this.#main) {
          this.hide()
          this.show()
        }
      }
      async show() {
        var groupparent = [],
          groupcount = 0
        this.#globalopts.onshow?.()

        if (!document.body) await a().bodyload()
        var options = this.#options
        var format = this.#format
        var onsave = this.#globalopts.onchange
        const globalopts = this.#globalopts
        if (this.#main) this.#main?.remove?.()
        this.#main = undefined
        const closethis = this.hide.bind(this)
        var main = a(document.body).createelem("div", {
          position: "fixed",
          top: "0",
          right: "0",
          zIndex: 99999,
          colorScheme: "dark",
        }).val
        var shadow = a(
          userliboptions.noshadows ? main : main.attachShadow({ mode: "open" })
        )
        if (this.#globalopts.cursor)
          a(shadow).createelem("style", {
            innerHTML: `
            * {
              cursor: ${this.#globalopts.cursor};
            }
            input[type="number"]::-webkit-inner-spin-button,
            input[type="number"]::-webkit-outer-spin-button{
              cursor:  ${this.#globalopts.cursor};
            }
            `,
          })
        const menupos =
          globalopts.position == "unset"
            ? userliboptions.menuposition1 == "full screen"
              ? "full screen"
              : userliboptions.menuposition1 +
                " " +
                userliboptions.menuposition2
            : globalopts.position
        var dimmeropacity =
          this.#globalopts.dimmeropacity ?? userliboptions.dimmeropacity
        shadow = shadow
          .createelem("div", {
            backgroundColor: this.#globalopts.noclosebutton
              ? "#0000"
              : `rgba(60, 60, 60, ${dimmeropacity})`,
            position: "fixed",
            top: 0,
            left: 0,
            width: "100vw",
            height: "100vh",
            maxHeight: "100vh",
            overflow: "scroll",
            pointerEvents:
              dimmeropacity == 0 || this.#globalopts.noclosebutton
                ? "none"
                : "",
            onclick({ target }) {
              if (this !== target) return
              if (userliboptions.clickoffclosesmenu) closethis()
            },
          })
          .createelem("div", {
            position: "fixed",
            backgroundColor: "#888",
            padding: "10px",
            border: "10px solid #333",
            borderRadius: "10px",
            color: "#ddd",
            pointerEvents: "all",
            width: "fit-content",
            height: "fit-content",
            maxHeight: "calc(100vh - 40px)",
            overflow: "scroll",

            ...(() => {
              if (menupos == "full screen")
                return {
                  top: 0,
                  left: 0,
                  width: "calc(100vw - 40px)",
                  height: "calc(100vh - 40px)",
                }
              var pos = menupos.split(" ")
              var obj = {}
              if (pos.includes("center")) {
                pos = pos.filter((e) => e && e !== "center")
                if (["left", "right"].includes(pos[0])) {
                  obj.top = "50%"
                  obj.translate = "0 -50%"
                } else if (["top", "bottom"].includes(pos[0])) {
                  obj.left = "50%"
                  obj.translate = "-50% 0"
                } else
                  return {
                    top: "50vh",
                    left: "50vw",
                    translate: "-50% -50%",
                  }
              }
              pos.forEach((p, i) => {
                obj[p] = 0
              })
              return obj
            })(),
          })

        var temp = shadow.createelem("div", {
          width: "100%",
          height: "fit-content",
          display: "flex",
          justifyContent: "flex-end",
        })

        if (!this.#globalopts.noclosebutton)
          temp.createelem("button", {
            innerHTML: "x",
            width: "22px",
            height: "22px",
            onclick: closethis,
            color: "#ddd",
          })

        shadow = shadow.val

        format.forEach((f) => {
          var resettodefaultbutton
          var canrunonsavefunction = false
          function modify(f, val) {
            options[f.key] = f.func ? f.func(val) : val
            if (canrunonsavefunction && onsave) onsave(f.key, val)
            if (resettodefaultbutton)
              resettodefaultbutton.style.visibility =
                val == f.default ? "hidden" : "visible"
          }
          if (f.type == "groupend") {
            groupparent.pop()
            groupcount--
            return
          }
          f.text ??= f.key
          f.key ??= f.text
          f.innerstyle ??= {}
          if (!("default" in f)) f.default = options[f.key]
          f.textstyle ??= {}
          //           if (f.type == "groupstart") groupparent = undefined
          //           if (groupparent) {
          f.tab ??= 0
          f.tab += groupcount
          //           }
          var test = (() => {
            if (f.type !== "none" && !f.text)
              throw new Error("object is missing properties text and key")
            if (!f.type) throw new Error("object is missing property key")
            switch (f.type) {
              case "button":
                return [
                  e("button", {
                    marginLeft: "4px",
                    innerHTML: f.buttontext,
                    onclick: f.onclick,
                    settovalue: false,
                    oncontextmenu: f.oncontextmenu,
                    ...f.innerstyle,
                    color: "#ddd",
                  }),
                ]
              case "boolean":
                return [
                  e("input", {
                    type: "checkbox",
                    settovalue(val) {
                      modify(f, (this.checked = val))
                    },
                    checked: options[f.key],
                    onclick() {
                      modify(f, this.checked)
                    },
                    ...f.innerstyle,
                  }),
                ]
              case "text":
                return [
                  e("input", {
                    marginLeft: "4px",
                    value: options[f.key],
                    color: "#ddd",
                    settovalue(val) {
                      this.value = val
                      this.onchange()
                    },
                    onchange() {
                      if (f.forcelower) this.value = this.value.toLowerCase()
                      if (f.forceupper) this.value = this.value.toUpperCase()
                      modify(f, this.value)
                    },
                    ...f.innerstyle,
                  }),
                ]
              case "number":
                return [
                  e("input", {
                    marginLeft: "4px",
                    //                     marginLeft: "auto",

                    //                     translate: f.hint ? "" : "-17px",
                    type: "number",
                    value: options[f.key],
                    step: f.step,
                    min: f.min,
                    max: f.max,
                    settovalue(val) {
                      this.value = val
                      this.onchange()
                    },
                    onchange() {
                      var num = Number(this.value)
                      if (f.round) num = Math.round(num)
                      modify(f, num)
                    },
                    ...f.innerstyle,
                  }),
                ]
              case "key":
                function pressed(e = "UNBOUND") {
                  if (a(e).gettype("string").val) {
                    e = e.split("+")
                    var key = e.pop() ?? "+"
                    e = e.map((e) => e.toLowerCase())
                    e = {
                      key,
                      shiftKey: e.includes("shift"),
                      altKey: e.includes("alt"),
                      metaKey: e.includes("meta"),
                      ctrlKey: e.includes("ctrl"),
                    }
                  }
                  var { shiftKey, ctrlKey, metaKey, altKey, key } = e
                  e.key ??= "UNBOUND"
                  if (key == "Unidentified")
                    throw new Error("key is Unidentified", error(e))
                  if (key == " ") key = "Space"
                  blockevent(e)
                  if (f.allowmodifiers) {
                    this.value = ""
                    if (ctrlKey) this.value += "Ctrl+"
                    if (altKey) this.value += "Alt+"
                    if (shiftKey) this.value += "Shift+"
                    if (metaKey) this.value += "Meta+"
                    this.value +=
                      f.ignorekeycase && key.length == 1
                        ? key.toUpperCase()
                        : key
                  } else
                    this.value =
                      f.ignorekeycase && key.length == 1
                        ? key.toUpperCase()
                        : key
                  switch (f.keyreturntype) {
                    case "string":
                      if (f.allowmodifiers) key = this.value
                      modify(f, f.ignorekeycase ? key.toLowerCase() : key)
                      break
                    case "object":
                      if (f.allowmodifiers)
                        modify(f, {
                          shiftKey,
                          ctrlKey,
                          metaKey,
                          altKey,
                          key: f.ignorekeycase ? key.toLowerCase() : key,
                        })
                      else
                        modify(f, {
                          key: f.ignorekeycase ? key.toLowerCase() : key,
                        })
                      break
                    default:
                      throw new Error("invalid key return type")
                  }
                  this.blur()
                }
                var textholder
                return [
                  e(
                    "div",
                    {
                      width: "100px",
                      height: "20px",
                      backgroundColor: "#444",
                      marginLeft: "4px",
                      settovalue(val) {
                        pressed.call(this.onmousedown(), val)
                      },
                      borderRadius: "10px",
                      color: "#ccc",
                      display: "inline-block",
                      overflow: "scroll",
                      textAlign: "center",
                      display: "flex",
                      justifyContent: "center",
                      alignItems: "center",
                      onmousedown(e) {
                        var lastmd = this.onmousedown
                        this.onmousedown = undefined
                        var _this = this
                        blockevent(e)
                        var mouselisteners = [
                          a(window).listen("mousedown", (e) => {
                            _this.onmousedown = lastmd
                            e.preventDefault()
                            blockevent(e)
                            e.key = "mouse" + e.button
                            pressed.call(temp, e)
                          }),
                          a(window).listen("mouseup", blockevent),
                          a(window).listen("click", blockevent),
                          a(window).listen("contextmenu", blockevent),
                        ]
                        textholder.lastvals = {
                          text: textholder.innerHTML,
                          size: textholder.style.fontSize,
                        }
                        textholder.style.fontSize = "100%"
                        textholder.innerHTML = "press any key"
                        if (e) e.preventDefault()
                        var temp = a(this).createelem("input", {
                          onkeypress: pressed,
                          onkeyup: pressed,
                          onkeydown(e) {
                            e.preventDefault()
                          },
                          width: "0px",
                          height: "0px",
                          opacity: 0,
                          position: "absolute",
                          onblur() {
                            _this.onmousedown = lastmd
                            setTimeout((e) => {
                              mouselisteners.forEach((mouselistener) =>
                                a(mouselistener).unlisten()
                              )
                            })
                            textholder.style.fontSize = textholder.lastvals.size
                            textholder.innerHTML = textholder.lastvals.text
                            if (
                              this.value.toLowerCase() == "escape" &&
                              userliboptions.escapeunbindskeypicker
                            )
                              this.value = "UNBOUND"
                            else if (
                              !this.value ||
                              (this.value.toLowerCase() == "escape" &&
                                userliboptions.escapecancelskeypicker)
                            )
                              return this.remove()

                            textholder.style.fontSize =
                              this.value.length > 12
                                ? this.value.length > 16
                                  ? this.value.length > 21
                                    ? "40%"
                                    : "60%"
                                  : "80%"
                                : "100%"
                            textholder.textContent = this.value
                            this.remove()
                          },
                        }).val
                        temp.select()
                        return temp
                      },
                      display: "inline-block",
                    },
                    [
                      (textholder = e("div", {
                        marginLeft: "auto",
                        marginRight: "auto",
                        height: "fit-content",
                        position: "relative",
                        top: "50%",
                        translate: "0 -50%",
                      })),
                    ]
                  ),
                ]
              case "select":
                return [
                  e("select", {
                    marginLeft: "4px",
                    value: options[f.key],
                    options: f.options,
                    settovalue(val) {
                      modify(f, (this.value = val))
                    },
                    onchange() {
                      modify(f, this.value)
                      this.blur()
                    },
                    ...f.innerstyle,
                  }),
                ]
              case "range":
                var textwidth = 12
                // var ts = String(f.max).length * textwidth
                // if (String(f.step).includes(".")) {
                //   ts +=
                //     String(f.step).match(/(?<=\.)\d+$/)[0].length * textwidth
                //   if (
                //     String(f.max).length !==
                //     String(f.max - f.step / f.step).length
                //   ) {
                //     ts -= textwidth
                //   }
                // }
                // if (f.allowanynumber) ts += 10
                var numinp = e("input", {
                  type: f.allowanynumber ? "number" : "text",
                  width: "60px",
                  onfocus() {
                    this.select()
                  },
                  readOnly: !f.allowanynumber,
                  step: f.step ? (f.step < 1 ? f.step : 1) : 1,
                  oninput() {
                    modify(f, Number((rangeinp.value = this.value)))
                  },
                  settovalue(val) {
                    this.value = val
                  },
                })
                var rangeinp = e("input", {
                  marginLeft: "4px",
                  value: options[f.key],
                  type: "range",
                  min: f.min,
                  onfocus() {
                    this.blur()
                  },
                  tabIndex: -1,
                  step: f.step ?? 1,
                  max: f.max,
                  settovalue(val) {
                    numinp.value = this.value
                    modify(f, Number((this.value = val)))
                  },
                  onchange() {
                    numinp.value = this.value
                    modify(f, Number(this.value))
                  },
                  onmouseout() {
                    if (this.display) this.display.style.display = "none"
                  },
                  onmousemove(event) {
                    var rect = event.target.getBoundingClientRect()
                    //                       var x = event.clientX - rect.left
                    //                       var y = event.clientY - rect.top
                    var { w, x, y } = a(this).rect().val
                    var mousex = event.clientX - x
                    if (!this.display)
                      this.display = this.parentElement.appendChild(
                        e("div", {
                          innerHTML: "loading",
                          position: "fixed",
                          pointerEvents: "none",
                          backgroundColor: "#444",
                          borderRadius: "6px",
                          padding: "2px",
                          translate: userliboptions.showrangenumberbelowrange
                            ? "-50% 100%"
                            : "-50% -100%",
                        })
                      )
                    if (userliboptions.keepnumberdisplaysamesize) {
                      var val = String(this.value)
                      while (val.length < String(f.max).length) val = "0" + val
                      this.display.innerHTML = val
                    } else this.display.innerHTML = this.value
                    Object.assign(this.display.style, {
                      top: y + "px",
                      left: x + Math.min(Math.max(mousex, 0), w) + "px",
                      display: "",
                    })
                  },
                  ...f.innerstyle,
                })
                return [rangeinp, numinp]
              case "none":
                return []
              case "groupstart":
                groupcount++
                groupparent.push(
                  e("div", {
                    display: "flex",
                    width: "100%",
                    flexDirection: "column",
                    height: "20px",
                    minHeight: "20px",
                    overflow: "hidden",
                    marginTop: "2px",
                    marginBottom: "2px",
                    //                   marginBottom: "10px",
                    //                   marginTop: "10px",
                    ...f.innerstyle,
                  })
                )
                return []
              default:
                return [
                  e("span", {
                    marginLeft: "4px",
                    innerHTML: `error type:${f.type}, text:${f.text}`,
                  }),
                ]
            }
          })()
          test.unshift(
            e("span", {
              flexGrow: 1,
            })
          )
          var thisrow = e("div", {
            display: "flex",
            alignItems: "center",
          })
          const prevgroupparent = groupparent[groupparent.length - 2]
          const currentgroupparent = groupparent[groupparent.length - 1]
          if (currentgroupparent) {
            ;(prevgroupparent || shadow).appendChild(currentgroupparent)
            currentgroupparent.appendChild(thisrow)
            //             if (f.type !== "groupstart") thisrow.style.display = "none"
          } else shadow.appendChild(thisrow)
          thisrow.appendChild(
            e(
              "label",
              {
                border: "1px solid #aaa",
                borderRadius: "5px",
                paddingLeft: "5px",
                paddingRight: "5px",
                marginTop: "2px",
                marginBottom: "2px",
                innerHTML: f.type == "groupstart" ? `\u02c3 ${f.text}` : f.text,
                marginLeft: (f.tab ?? 0) * userliboptions.tabsize + "px",
                currentgroupparent,
                width: "100%",
                color: f.type == "groupstart" ? "#222" : "#ddd",
                ...f.textstyle,
                onclick:
                  f.type == "groupstart"
                    ? function () {
                        if (
                          this.currentgroupparent.style.height == "fit-content"
                        ) {
                          this.currentgroupparent.style.height = "20px"
                          this.innerHTML = `\u02c3 ${f.text}`
                        } else {
                          this.currentgroupparent.style.height = "fit-content"
                          this.innerHTML = `\u02c5 ${f.text}`
                        }
                      }
                    : undefined,
              },
              ["boolean"].includes(f.type) ? test : []
            )
          )

          if (!["boolean"].includes(f.type)) {
            test.forEach(thisrow.appendChild.bind(thisrow))
          }

          if (test && f.hint) {
            f.hint = f.hint.trim()
            var showhint = e("div", {
              border: "2px solid #444",
              borderRadius: "100%",
              marginLeft: "4px",
              innerHTML: "?",
              title: f.hint.length < 60 ? f.hint : "show hint",
              width: "10px",
              height: "10px",
              color: "black",
              fontWeight: "bold",
              display: "inline-block",
              textAlign: "center",
              fontSize: "60%",
              onmouseup(e) {
                e.preventDefault()
                var hintbg = a(this.parentElement).createelem("div", {
                  backgroundColor: "#333",
                  position: "fixed",
                  zIndex: 99,
                  backgroundColor: `rgba(60, 60, 60, ${userliboptions.popupdimmeropacity})`,
                  top: 0,
                  left: 0,
                  width: "100vw",
                  height: "100vh",
                  onclick() {
                    this.remove()
                  },
                }).val
                hintbg.focus()
                var closehintbutton = a(
                  userliboptions.noshadows
                    ? hintbg
                    : hintbg.attachShadow({ mode: "open" })
                ).createelem("div", {
                  color: "#000",
                  fontWeight: "normal",
                  marginLeft: "auto",
                  marginRight: "4px",
                  width: "fit-content",
                  padding: "7px",
                  marginRight: "auto",
                  position: "relative",
                  border: "10px solid #444",
                  backgroundColor: "#777",
                  top: "50%",
                  translate: "0 -50%",
                  ...(f.hintcss ?? {}),
                  onclick: blockevent,
                }).val
                a(closehintbutton).createelem("button", {
                  width: "20px",
                  height: "20px",
                  innerHTML: "x",
                })
                switch (f.hinttype) {
                  case "html":
                    closehintbutton.innerHTML = f.hint
                    break
                  case "text":
                    closehintbutton.innerText = f.hint
                    break
                  default:
                    throw new Error("invalid hint type", {
                      hinttype: f.hinttype,
                      obj,
                      format: f,
                    })
                }
                closehintbutton.innerHTML =
                  '<button style="width:20px; height:20px">x</button><br>' +
                  closehintbutton.innerHTML
                a("button").qs(closehintbutton).val.onclick = () =>
                  hintbg.remove()
              },
            })
            thisrow.appendChild(showhint)
          }
          if (userliboptions.alligninputs) test.shift()
          if (test?.[0] && test[0]?.settovalue !== false && f.type !== "none") {
            resettodefaultbutton = e("div", {
              marginLeft: "4px",
              marginRight: "4px",
              innerHTML: "\u21B6" ?? "\u21B0",
              title: "reset to default",
              width: "10px",
              height: "10px",
              color: "black",
              fontWeight: "bold",
              display: "inline-block",
              visiblity: "hidden",
              textAlign: "center",
              fontSize: "100%",
              translate: "0 -50%",
              ...JSON.tryparse(userliboptions.resettodefaultbuttonstyle),
              onclick() {
                test.map((e) => e.settovalue(f.default))
              },
            })
            test.forEach((e) => {
              if (e.settovalue) {
                e.settovalue(f.key in options ? options[f.key] : f.default)
                if (canrunonsavefunction) onsave()
              } else warn(e)
            })
            canrunonsavefunction = true
            thisrow.appendChild(resettodefaultbutton)
          }
        })
        this.#main = main
      }
      hide() {
        this.#globalopts.onhide?.()
        if (this.#main) this.#main.remove()
        this.#main = undefined
      }
    },
    {
      showrangenumberbelowrange: {
        type: "boolean",
        text: "show range value below the range instead of above it",
        default: false,
      },
      keepnumberdisplaysamesize: {
        type: "boolean",
        text: "force number display to allways have the same number of digits",
        default: false,
      },
      tabsize: { type: "number", default: 10, text: "tab size" },
      escapecancelskeypicker: {
        default: false,
        type: "boolean",
        text: "escape cancels key picker",
      },
      noshadows: {
        default: false,
        type: "boolean",
        text: "don't create shadow doms",
        hint: "only useful to allow eruda to see it",
        hinttype: "text",
      },
      escapeunbindskeypicker: {
        default: true,
        type: "boolean",
        text: "escape unbinds key in key picker",
      },
      resettodefaultbuttonstyle: {
        default: "{}",
        type: "text",
        text: "reset to default button style",
      },
      dimmeropacity: {
        text: "dimmer opacity",
        default: 0.8,
        type: "range",
        min: 0,
        max: 1,
        step: 0.05,
      },
      popupdimmeropacity: {
        text: "popup dimmer opacity",
        default: 0.8,
        type: "range",
        min: 0,
        max: 1,
        step: 0.05,
      },
      menuposition1: {
        text: "menu position",
        default: "top",
        type: "select",
        options: ["top", "center", "bottom", "full screen"],
      },
      menuposition2: {
        text: "menu position",
        default: "right",
        type: "select",
        options: ["left", "center", "right"],
      },
      // aligninputs: {
      //   text: "align inputs",
      //   default: true,
      //   type: "boolean",
      // },
      clickoffclosesmenu: {
        text: "clicking off the menu closes it",
        default: true,
        type: "boolean",
      },
      alligninputs: {
        text: "allign inputs",
        default: true,
        type: "boolean",
      },
    }
  )
})()