From 73989e09a94e0e19b9be68888c7713fa3efd4144 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 28 Jun 2021 10:55:42 +0200 Subject: [PATCH] Fixes concerning dropdowns --- src/shared/chat/emoji-dropdown.js | 96 +++++++++++++++++++++++++++ src/shared/chat/emoji-picker.js | 94 +------------------------- src/shared/components/dropdown.js | 78 +++------------------- src/shared/components/dropdownbase.js | 64 ++++++++++++++++++ 4 files changed, 171 insertions(+), 161 deletions(-) create mode 100644 src/shared/chat/emoji-dropdown.js create mode 100644 src/shared/components/dropdownbase.js diff --git a/src/shared/chat/emoji-dropdown.js b/src/shared/chat/emoji-dropdown.js new file mode 100644 index 000000000..055594aba --- /dev/null +++ b/src/shared/chat/emoji-dropdown.js @@ -0,0 +1,96 @@ +import DropdownBase from "shared/components/dropdown.js"; +import { __ } from 'i18n'; +import { _converse, api, converse } from "@converse/headless/core"; +import { html } from "lit"; +import { initStorage } from '@converse/headless/shared/utils.js'; +import { until } from 'lit/directives/until.js'; + +const u = converse.env.utils; + + +export default class EmojiDropdown extends DropdownBase { + + static get properties() { + return { + chatview: { type: Object } + }; + } + + constructor () { + super(); + // This is an optimization, we lazily render the emoji picker, otherwise tests slow to a crawl. + this.render_emojis = false; + } + + initModel () { + if (!this.init_promise) { + this.init_promise = (async () => { + await api.emojis.initialize() + const id = `converse.emoji-${_converse.bare_jid}-${this.chatview.model.get('jid')}`; + this.model = new _converse.EmojiPicker({'id': id}); + initStorage(this.model, id); + await new Promise(resolve => this.model.fetch({'success': resolve, 'error': resolve})); + // We never want still be in the autocompleting state upon page load + this.model.set({'autocompleting': null, 'ac_position': null}); + })(); + } + return this.init_promise; + } + + render() { + return html` +
+ + +
`; + } + + connectedCallback () { + super.connectedCallback(); + this.render_emojis = false; + } + + toggleMenu (ev) { + ev.stopPropagation(); + ev.preventDefault(); + if (u.hasClass('show', this.menu)) { + if (u.ancestor(ev.target, '.toggle-emojis')) { + this.hideMenu(); + } + } else { + this.showMenu(); + } + } + + async showMenu () { + await this.initModel(); + if (!this.render_emojis) { + // Trigger an update so that emojis are rendered + this.render_emojis = true; + await this.requestUpdate(); + } + super.showMenu(); + setTimeout(() => this.querySelector('.emoji-search')?.focus()); + } +} + +api.elements.define('converse-emoji-dropdown', EmojiDropdown); diff --git a/src/shared/chat/emoji-picker.js b/src/shared/chat/emoji-picker.js index 9dcc111a0..f8884bc0e 100644 --- a/src/shared/chat/emoji-picker.js +++ b/src/shared/chat/emoji-picker.js @@ -1,14 +1,10 @@ import "./emoji-picker-content.js"; +import './emoji-dropdown.js'; import DOMNavigator from "shared/dom-navigator"; import debounce from 'lodash-es/debounce'; -import { BaseDropdown } from "shared/components/dropdown.js"; import { CustomElement } from 'shared/components/element.js'; -import { __ } from 'i18n'; import { _converse, api, converse } from "@converse/headless/core"; -import { html } from "lit"; -import { initStorage } from '@converse/headless/shared/utils.js'; import { tpl_emoji_picker } from "./templates/emoji-picker.js"; -import { until } from 'lit/directives/until.js'; import './styles/emoji.scss'; @@ -30,6 +26,7 @@ export default class EmojiPicker extends CustomElement { } firstUpdated () { + super.firstUpdated(); this.listenTo(this.model, 'change', o => this.onModelChanged(o.changed)); this.initArrowNavigation(); } @@ -275,91 +272,4 @@ export default class EmojiPicker extends CustomElement { } } - -export class EmojiDropdown extends BaseDropdown { - - static get properties() { - return { - chatview: { type: Object } - }; - } - - constructor () { - super(); - // This is an optimization, we lazily render the emoji picker, otherwise tests slow to a crawl. - this.render_emojis = false; - } - - initModel () { - if (!this.init_promise) { - this.init_promise = (async () => { - await api.emojis.initialize() - const id = `converse.emoji-${_converse.bare_jid}-${this.chatview.model.get('jid')}`; - this.model = new _converse.EmojiPicker({'id': id}); - initStorage(this.model, id); - await new Promise(resolve => this.model.fetch({'success': resolve, 'error': resolve})); - // We never want still be in the autocompleting state upon page load - this.model.set({'autocompleting': null, 'ac_position': null}); - })(); - } - return this.init_promise; - } - - render() { - return html` -
- - -
`; - } - - connectedCallback () { - super.connectedCallback(); - this.render_emojis = false; - } - - toggleMenu (ev) { - ev.stopPropagation(); - ev.preventDefault(); - if (u.hasClass('show', this.menu)) { - if (u.ancestor(ev.target, '.toggle-emojis')) { - this.hideMenu(); - } - } else { - this.showMenu(); - } - } - - async showMenu () { - await this.initModel(); - if (!this.render_emojis) { - // Trigger an update so that emojis are rendered - this.render_emojis = true; - await this.requestUpdate(); - } - super.showMenu(); - setTimeout(() => this.querySelector('.emoji-search')?.focus()); - } -} - -api.elements.define('converse-emoji-dropdown', EmojiDropdown); api.elements.define('converse-emoji-picker', EmojiPicker); diff --git a/src/shared/components/dropdown.js b/src/shared/components/dropdown.js index 1671d11fd..8fb93c6cc 100644 --- a/src/shared/components/dropdown.js +++ b/src/shared/components/dropdown.js @@ -1,76 +1,11 @@ import DOMNavigator from "shared/dom-navigator.js"; -import { CustomElement } from './element.js'; import { converse, api } from "@converse/headless/core"; import { html } from 'lit'; import { until } from 'lit/directives/until.js'; - -const u = converse.env.utils; +import DropdownBase from 'shared/components/dropdownbase.js'; -export class BaseDropdown extends CustomElement { - - connectedCallback() { - super.connectedCallback(); - this.registerEvents(); - } - - registerEvents() { - this.clickOutside = this._clickOutside.bind(this); - document.addEventListener('click', this.clickOutside); - } - - firstUpdated () { - super.firstUpdated(); - this.initArrowNavigation(); - this.menu = this.querySelector('.dropdown-menu'); - this.dropdown = this.firstElementChild; - this.button = this.dropdown.querySelector('button'); - this.dropdown.addEventListener('click', ev => this.toggleMenu(ev)); - this.dropdown.addEventListener('keyup', ev => this.handleKeyUp(ev)); - } - - _clickOutside(ev) { - if (!this.contains(ev.composedPath()[0])) { - this.hideMenu(ev); - } - } - - hideMenu () { - u.removeClass('show', this.menu); - this.button?.setAttribute('aria-expanded', false); - this.button?.blur(); - } - - showMenu () { - u.addClass('show', this.menu); - this.button.setAttribute('aria-expanded', true); - } - - toggleMenu (ev) { - ev.preventDefault(); - if (u.hasClass('show', this.menu)) { - this.hideMenu(); - } else { - this.showMenu(); - } - } - - handleKeyUp (ev) { - if (ev.keyCode === converse.keycodes.ESCAPE) { - this.hideMenu(); - } else if (ev.keyCode === converse.keycodes.DOWN_ARROW && this.navigator && !this.navigator.enabled) { - this.enableArrowNavigation(ev); - } - } - - disconnectedCallback () { - document.removeEventListener('click', this.clickOutside); - super.disconnectedCallback(); - } -} - - -export default class DropdownList extends BaseDropdown { +export default class Dropdown extends DropdownBase { static get properties () { return { @@ -93,9 +28,14 @@ export default class DropdownList extends BaseDropdown { `; } + firstUpdated () { + super.firstUpdated(); + this.initArrowNavigation(); + } + hideMenu () { super.hideMenu(); - this.navigator.disable(); + this.navigator?.disable(); } initArrowNavigation () { @@ -125,4 +65,4 @@ export default class DropdownList extends BaseDropdown { } } -api.elements.define('converse-dropdown', DropdownList); +api.elements.define('converse-dropdown', Dropdown); diff --git a/src/shared/components/dropdownbase.js b/src/shared/components/dropdownbase.js new file mode 100644 index 000000000..e09ab993f --- /dev/null +++ b/src/shared/components/dropdownbase.js @@ -0,0 +1,64 @@ +import { CustomElement } from './element.js'; +import { converse } from "@converse/headless/core"; + +const u = converse.env.utils; + + +export default class DropdownBase extends CustomElement { + + connectedCallback() { + super.connectedCallback(); + this.registerEvents(); + } + + registerEvents() { + this.clickOutside = (ev) => this._clickOutside(ev); + document.addEventListener('click', this.clickOutside); + } + + firstUpdated () { + super.firstUpdated(); + this.menu = this.querySelector('.dropdown-menu'); + this.dropdown = this.firstElementChild; + this.button = this.dropdown.querySelector('button'); + this.dropdown.addEventListener('click', ev => this.toggleMenu(ev)); + this.dropdown.addEventListener('keyup', ev => this.handleKeyUp(ev)); + } + + _clickOutside(ev) { + if (!this.contains(ev.composedPath()[0])) { + this.hideMenu(ev); + } + } + + hideMenu () { + u.removeClass('show', this.menu); + this.button?.setAttribute('aria-expanded', false); + this.button?.blur(); + } + + showMenu () { + u.addClass('show', this.menu); + this.button.setAttribute('aria-expanded', true); + } + + toggleMenu (ev) { + ev.preventDefault(); + if (u.hasClass('show', this.menu)) { + this.hideMenu(); + } else { + this.showMenu(); + } + } + + handleKeyUp (ev) { + if (ev.keyCode === converse.keycodes.ESCAPE) { + this.hideMenu(); + } + } + + disconnectedCallback () { + document.removeEventListener('click', this.clickOutside); + super.disconnectedCallback(); + } +}