{"version":3,"sources":["https://lively-kernel.org/lively4/lively4-portal/src/components/widgets/lively-menu.js"],"names":["Morph","pt","html","Entry","fromDescription","desc","Array","isArray","fromArray","String","name","callbackOrChildren","right","icon","options","onSelect","onDeselect","onClick","entry","Function","callback","Promise","children","selectHandler","deselectHandler","asItem","menu","replace","innerHTML","item","appendChild","addEventListener","evt","matchingItems","includes","selectItem","matchesFilter","filter","HTMLElement","textContent","toLowerCase","selected","deselected","FILTER_KEY_BLACKLIST","LivelyMenu","initialize","setAttribute","registerKeys","moveInsideWindow","w","window","innerWidth","innerHeight","b","lively","getGlobalBounds","original","topLeft","bottom","y","x","left","top","delta","subPt","parentMenu","getExtent","moveBy","topLevelMenu","_filter","value","onKeyDown","key","get","items","forEach","classList","remove","nonMatchingItems","add","currentItem","length","from","querySelectorAll","matchFilter","onSpaceDown","warn","onUpDown","selectUpOrDown","onDownDown","onEscDown","closeWindow","offset","targetIndex","indexOf","onLeftDown","focusWithoutScroll","onRightDown","enterSubmenu","onTabDown","shiftKey","submenu","onEnterUp","openOn","optEvt","optPos","container","console","log","resolve","ea","setPosition","focus","subitems","document","createElement","components","openIn","bounds","getBoundingClientRect","menuBounds"],"mappings":"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","file":"lively-menu.js","sourcesContent":["/*MD # Lively Menu\n\n![](lively-menu.png){height=200}\n\n\nMD*/\n\nimport Morph from 'src/components/widgets/lively-morph.js';\nimport { pt } from 'src/client/graphics.js';\nimport html from 'src/client/html.js';\n\nclass Entry {\n  static fromDescription(desc) {\n    if (Array.isArray(desc)) {\n      return this.fromArray(desc);\n    } else if (desc instanceof String) {\n      // #TODO: convert the String '---' into a <hl />\n    } else {\n      // #TODO: Object\n    }\n  }\n  \n  static fromArray([name, callbackOrChildren, right, icon, options = {}]) {\n    const { onSelect, onDeselect, onClick } = options;\n    const entry = new Entry();\n\n    entry.name = name;\n    entry.callbackOrChildren = callbackOrChildren;\n    if (callbackOrChildren instanceof Function) {\n      entry.callback = callbackOrChildren;\n    } else if(callbackOrChildren instanceof Array || callbackOrChildren instanceof Promise) {\n      entry.children = callbackOrChildren;\n    }\n    if(onClick) {\n      entry.callback = onClick;\n    }\n    entry.right = right;\n    entry.icon = icon;\n    entry.selectHandler = onSelect;\n    entry.deselectHandler = onDeselect;\n\n    return entry;\n  }\n\n  asItem(menu) {\n    const right = <label>{this.right ? this.right.replace ? this.right.replace(\"CMD\", \"Ctrl\") : this.right : \"\"}\n      <span class=\"submenuindicator\">{this.children ? <span>►</span> : \" \"}</span>\n    </label>;\n    const icon = <div class='icon'></div>;\n    icon.innerHTML = this.icon ||  \"\"\n    const item = <li>{icon}{this.name}</li>;\n    item.entry = this;\n    item.appendChild(right);\n\n    if (this.callback) {\n      item.addEventListener(\"click\", evt => this.callback(evt, item));\n    }\n\n    item.addEventListener(\"mouseenter\", async evt => {\n      if (menu.matchingItems.includes(item)) {\n        menu.selectItem(item);\n      }\n    });\n\n    return item;\n  }\n\n  matchesFilter(filter) {\n    if (this.name instanceof HTMLElement) {\n      return this.name.textContent.toLowerCase().includes(filter.toLowerCase());\n    }\n    return typeof this.name === 'string' && this.name.toLowerCase().includes(filter.toLowerCase());\n  }\n\n  selected() {\n    if (this.selectHandler) {\n      this.selectHandler();\n    }\n  }\n  deselected() {\n    if (this.deselectHandler) {\n      this.deselectHandler();\n    }\n  }\n}\n\nconst FILTER_KEY_BLACKLIST = ['Control', 'Shift', 'Capslock', 'Alt', ' ', 'Enter', 'Escape', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'ArrowLeft', 'Tab'];\n\nexport default class LivelyMenu extends Morph {\n  initialize() {\n    this.setAttribute(\"tabindex\", 0 // we want keyboard events\n    );html.registerKeys(this, \"Menu\", this, true);\n  }\n\n  moveInsideWindow() {\n    var w = pt(window.innerWidth - 12, window.innerHeight - 12);\n    var b = lively.getGlobalBounds(this);\n    var original = b.topLeft();\n\n    if (b.bottom() > w.y) {\n      b.y -= b.bottom() - w.y;\n    }\n    if (b.right() > w.x) {\n      b.x -= b.right() - w.x;\n    }\n    if (b.left() < 0) {\n      b.x -= b.left();\n    }\n    if (b.top() < 0) {\n      b.y -= b.top();\n    }\n\n    var delta = b.topLeft().subPt(original\n    // lively.moveBy(this.topLevelMenu(), delta)\n    );if (this.parentMenu) {\n      if (delta.x < 0) {\n        delta.x -= lively.getExtent(this.parentMenu).x;\n      }\n    }\n    lively.moveBy(this, delta);\n\n    return delta;\n  }\n\n  topLevelMenu() {\n    if (!this.parentMenu) {\n      return this;\n    } else {\n      return this.parentMenu.topLevelMenu();\n    }\n  }\n\n  // lazy filter property\n  get filter() {\n    return this._filter = this._filter || '';\n  }\n  set filter(value) {\n    return this._filter = value;\n  }\n\n  onKeyDown(evt) {\n    if (FILTER_KEY_BLACKLIST.includes(evt.key)) {\n      return;\n    }\n\n    if (['Backspace', 'Delete'].includes(evt.key)) {\n      this.filter = '';\n    } else {\n      this.filter += evt.key;\n    }\n\n    this.get('#filter-hint').innerHTML = this.filter;\n\n    // lively.warn(evt.key, this.filter)\n\n    this.items.forEach(item => item.classList.remove('filtered-out'));\n    this.nonMatchingItems.forEach(item => item.classList.add('filtered-out'));\n\n    // lively.notify(this.matchingItems.length, this.nonMatchingItems.length)\n    if (!this.currentItem || this.nonMatchingItems.includes(this.currentItem) && this.matchingItems.length > 0) {\n      this.selectItem(this.matchingItems[0]);\n    }\n  }\n\n  get items() {\n    return Array.from(this.get(\".container\").querySelectorAll(\"li\"));\n  }\n\n  matchFilter(item) {\n    return item && item.entry && item.entry.matchesFilter(this.filter);\n  }\n\n  get matchingItems() {\n    return this.items.filter(item => this.matchFilter(item));\n  }\n\n  get nonMatchingItems() {\n    return this.items.filter(item => !this.matchFilter(item));\n  }\n\n  onSpaceDown(evt) {\n    lively.warn('should toggle binary Preferences');\n  }\n\n  onUpDown(evt) {\n    this.selectUpOrDown(evt, -1);\n  }\n\n  onDownDown(evt) {\n    this.selectUpOrDown(evt, 1);\n  }\n\n  onEscDown(evt) {\n    // #TODO: check if we are in a submenu\n    if (this.parentMenu) {\n      this.parentMenu.onEscDown(evt);\n    }\n    this.closeWindow();\n  }\n\n  selectUpOrDown(evt, offset = 0) {\n    if (!this.currentItem) {\n      this.selectItem(this.items[0]);\n    } else {\n      var matchingItems = this.matchingItems;\n      var targetIndex = (matchingItems.indexOf(this.currentItem) + offset + matchingItems.length) % matchingItems.length; //cycling through menu items\n      this.selectItem(matchingItems[targetIndex]);\n    }\n  }\n\n  onLeftDown(evt) {\n    if (this.parentMenu) {\n      lively.focusWithoutScroll(this.parentMenu);\n      this.parentMenu.selectUpOrDown(evt);\n    }\n  }\n\n  async onRightDown(evt) {\n    if (!this.currentItem) {\n      return;\n    }\n\n    var entry = this.currentItem.entry;\n\n    if ((await entry.children) instanceof Array) {\n      this.enterSubmenu(evt);\n    }\n  }\n\n  onTabDown(evt) {\n    if (evt.shiftKey) {\n      this.onLeftDown(evt);\n    } else {\n      this.onRightDown(evt);\n    }\n  }\n\n  enterSubmenu(evt) {\n    lively.focusWithoutScroll(this.submenu);\n    this.submenu.selectUpOrDown(evt);\n  }\n\n  async onEnterUp(evt) {\n    if (!this.currentItem) return;\n\n    var entry = this.currentItem.entry;\n    if (entry.callback) {\n      entry.callback(evt, this.currentItem);\n    } else if ((await entry.children) instanceof Array) {\n      this.enterSubmenu(evt);\n    }\n  }\n\n  async openOn(items, optEvt, optPos) {\n    var container = this.get(\".container\");\n    container.innerHTML = \"\"; // clear\n    // create a radio button for each tool\n    if (!items) {\n      console.log(\"WARNING: no items to open\");\n      return Promise.resolve();\n    }\n    for (let ea of items) {\n      const entry = Entry.fromDescription(ea);\n      const item = entry.asItem(this);\n      container.appendChild(item);\n    }\n    if (optPos) lively.setPosition(this, optPos);\n    this.moveInsideWindow();\n\n    return Promise.resolve(container);\n  }\n  \n  closeWindow() {\n    if(this.currentItem) {\n      this.currentItem.entry.deselected();\n    }\n    this.remove();\n  }\n\n  async selectItem(item) {\n    if (this.currentItem) {\n      this.currentItem.classList.remove(\"current\");\n      this.currentItem.entry.deselected();\n    }\n    if (!item) return;\n    // lively.showElement(item)\n    item.classList.add(\"current\");\n    this.currentItem = item;\n\n    // scroll item into view\n    item.setAttribute('tabindex', 0);\n    item.focus();\n\n    var ea = item.entry;\n    var menu = this.get(\".container\");\n    if (this.submenu) this.submenu.closeWindow();\n    item.entry.selected();\n    const subitems = await ea.children; // resolve Promise\n    if (subitems instanceof Array) {\n      this.submenu = document.createElement(\"lively-menu\");\n      this.submenu.parentMenu = this;\n      await lively.components.openIn(menu, this.submenu);\n      var bounds = item.getBoundingClientRect();\n      var menuBounds = menu.getBoundingClientRect();\n      this.submenu.openOn(subitems, null, pt(bounds.right, bounds.top).subPt(pt(menuBounds.left, menuBounds.top\n      // lively.moveBy(this, delta)\n      )));\n    }\n  }\n\n}"]}