Fixes concerning dropdowns

This commit is contained in:
JC Brand 2021-06-28 10:55:42 +02:00
parent 46f567d0d1
commit 73989e09a9
4 changed files with 171 additions and 161 deletions

View File

@ -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`
<div class="dropup">
<button class="toggle-emojis"
title="${__('Insert emojis')}"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<converse-icon
class="fa fa-smile "
path-prefix="${api.settings.get('assets_path')}"
size="1em"></converse-icon>
</button>
<div class="dropdown-menu">
${until(this.initModel().then(() => html`
<converse-emoji-picker
.chatview=${this.chatview}
.model=${this.model}
?render_emojis=${this.render_emojis}
current_category="${this.model.get('current_category') || ''}"
current_skintone="${this.model.get('current_skintone') || ''}"
query="${this.model.get('query') || ''}"
></converse-emoji-picker>`), '')}
</div>
</div>`;
}
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);

View File

@ -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`
<div class="dropup">
<button class="toggle-emojis"
title="${__('Insert emojis')}"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<converse-icon
class="fa fa-smile "
path-prefix="${api.settings.get('assets_path')}"
size="1em"></converse-icon>
</button>
<div class="dropdown-menu">
${until(this.initModel().then(() => html`
<converse-emoji-picker
.chatview=${this.chatview}
.model=${this.model}
?render_emojis=${this.render_emojis}
current_category="${this.model.get('current_category') || ''}"
current_skintone="${this.model.get('current_skintone') || ''}"
query="${this.model.get('query') || ''}"
></converse-emoji-picker>`), '')}
</div>
</div>`;
}
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);

View File

@ -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);

View File

@ -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();
}
}