Menu refactor (#1277)

* Rename menuOpened event to menuOpen

* Rename menuClosed event to menuClose

* Rename close event

* Assign _isClosed

* Reuse event detail

* Expose PopupMenu.openMenus

* Rename and expose properties

* Add cancelable argument to close

* Update menuOpen detail

* Update menuClose detail
This commit is contained in:
toasted-nutbread 2021-01-19 20:52:57 -05:00 committed by GitHub
parent 3c51bf2a0b
commit ccf28ed055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 109 additions and 113 deletions

View File

@ -445,10 +445,10 @@ class AnkiCardController {
this._validateField(node, index); this._validateField(node, index);
} }
_onFieldMenuClosed({currentTarget: node, detail: {action, item}}) { _onFieldMenuClose({currentTarget: button, detail: {action, item}}) {
switch (action) { switch (action) {
case 'setFieldMarker': case 'setFieldMarker':
this._setFieldMarker(node, item.dataset.marker); this._setFieldMarker(button, item.dataset.marker);
break; break;
} }
} }
@ -547,7 +547,7 @@ class AnkiCardController {
} else { } else {
delete menuButton.dataset.menu; delete menuButton.dataset.menu;
} }
this._fieldEventListeners.addEventListener(menuButton, 'menuClosed', this._onFieldMenuClosed.bind(this), false); this._fieldEventListeners.addEventListener(menuButton, 'menuClose', this._onFieldMenuClose.bind(this), false);
} }
totalFragment.appendChild(content); totalFragment.appendChild(content);

View File

@ -69,7 +69,7 @@ class AnkiTemplatesController {
resetButton.addEventListener('click', this._onReset.bind(this), false); resetButton.addEventListener('click', this._onReset.bind(this), false);
resetConfirmButton.addEventListener('click', this._onResetConfirm.bind(this), false); resetConfirmButton.addEventListener('click', this._onResetConfirm.bind(this), false);
if (menuButton !== null) { if (menuButton !== null) {
menuButton.addEventListener('menuClosed', this._onFieldMenuClosed.bind(this), false); menuButton.addEventListener('menuClose', this._onFieldMenuClose.bind(this), false);
} }
this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
@ -138,10 +138,10 @@ class AnkiTemplatesController {
this._validate(infoNode, field, 'term-kanji', true, false); this._validate(infoNode, field, 'term-kanji', true, false);
} }
_onFieldMenuClosed({currentTarget: node, detail: {action, item}}) { _onFieldMenuClose({currentTarget: button, detail: {action, item}}) {
switch (action) { switch (action) {
case 'setFieldMarker': case 'setFieldMarker':
this._setFieldMarker(node, item.dataset.marker); this._setFieldMarker(button, item.dataset.marker);
break; break;
} }
} }

View File

@ -168,7 +168,7 @@ class AudioController {
eventListeners.addEventListener(removeButton, 'click', this._onAudioSourceRemoveClicked.bind(this, entry), false); eventListeners.addEventListener(removeButton, 'click', this._onAudioSourceRemoveClicked.bind(this, entry), false);
} }
if (menuButton !== null) { if (menuButton !== null) {
eventListeners.addEventListener(menuButton, 'menuClosed', this._onMenuClosed.bind(this, entry), false); eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this, entry), false);
} }
this._audioSourceContainer.appendChild(container); this._audioSourceContainer.appendChild(container);
@ -224,9 +224,8 @@ class AudioController {
this._removeAudioSourceEntry(entry); this._removeAudioSourceEntry(entry);
} }
_onMenuClosed(entry, e) { _onMenuClose(entry, e) {
const {detail: {action}} = e; switch (e.detail.action) {
switch (action) {
case 'remove': case 'remove':
this._removeAudioSourceEntry(entry); this._removeAudioSourceEntry(entry);
break; break;

View File

@ -86,8 +86,8 @@ class DictionaryEntry {
this._eventListeners.addEventListener(deleteButton, 'click', this._onDeleteButtonClicked.bind(this), false); this._eventListeners.addEventListener(deleteButton, 'click', this._onDeleteButtonClicked.bind(this), false);
} }
if (menuButton !== null) { if (menuButton !== null) {
this._eventListeners.addEventListener(menuButton, 'menuOpened', this._onMenuOpened.bind(this), false); this._eventListeners.addEventListener(menuButton, 'menuOpen', this._onMenuOpen.bind(this), false);
this._eventListeners.addEventListener(menuButton, 'menuClosed', this._onMenuClosed.bind(this), false); this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this), false);
} }
if (detailsToggleLink !== null && this._detailsContainer !== null) { if (detailsToggleLink !== null && this._detailsContainer !== null) {
this._eventListeners.addEventListener(detailsToggleLink, 'click', this._onDetailsToggleLinkClicked.bind(this), false); this._eventListeners.addEventListener(detailsToggleLink, 'click', this._onDetailsToggleLinkClicked.bind(this), false);
@ -116,10 +116,10 @@ class DictionaryEntry {
this._delete(); this._delete();
} }
_onMenuOpened(e) { _onMenuOpen(e) {
const {detail: {menu}} = e; const node = e.detail.menu.node;
const showDetails = menu.querySelector('.popup-menu-item[data-menu-action="showDetails"]'); const showDetails = node.querySelector('.popup-menu-item[data-menu-action="showDetails"]');
const hideDetails = menu.querySelector('.popup-menu-item[data-menu-action="hideDetails"]'); const hideDetails = node.querySelector('.popup-menu-item[data-menu-action="hideDetails"]');
const hasDetails = (this._detailsContainer !== null); const hasDetails = (this._detailsContainer !== null);
const detailsVisible = (hasDetails && !this._detailsContainer.hidden); const detailsVisible = (hasDetails && !this._detailsContainer.hidden);
if (showDetails !== null) { if (showDetails !== null) {
@ -132,9 +132,8 @@ class DictionaryEntry {
} }
} }
_onMenuClosed(e) { _onMenuClose(e) {
const {detail: {action}} = e; switch (e.detail.action) {
switch (action) {
case 'delete': case 'delete':
this._delete(); this._delete();
break; break;

View File

@ -16,62 +16,72 @@
*/ */
class PopupMenu extends EventDispatcher { class PopupMenu extends EventDispatcher {
constructor(sourceElement, container) { constructor(sourceElement, containerNode) {
super(); super();
this._sourceElement = sourceElement; this._sourceElement = sourceElement;
this._container = container; this._containerNode = containerNode;
this._menu = container.querySelector('.popup-menu'); this._node = containerNode.querySelector('.popup-menu');
this._isClosed = false; this._isClosed = false;
this._eventListeners = new EventListenerCollection(); this._eventListeners = new EventListenerCollection();
} }
get sourceElement() {
return this._sourceElement;
}
get containerNode() {
return this._containerNode;
}
get node() {
return this._node;
}
get isClosed() { get isClosed() {
return this._isClosed; return this._isClosed;
} }
prepare() { prepare() {
const items = this._menu.querySelectorAll('.popup-menu-item'); const items = this._node.querySelectorAll('.popup-menu-item');
this._setPosition(items); this._setPosition(items);
this._container.focus(); this._containerNode.focus();
this._eventListeners.addEventListener(window, 'resize', this._onWindowResize.bind(this), false); this._eventListeners.addEventListener(window, 'resize', this._onWindowResize.bind(this), false);
this._eventListeners.addEventListener(this._container, 'click', this._onMenuContainerClick.bind(this), false); this._eventListeners.addEventListener(this._containerNode, 'click', this._onMenuContainerClick.bind(this), false);
const onMenuItemClick = this._onMenuItemClick.bind(this); const onMenuItemClick = this._onMenuItemClick.bind(this);
for (const item of items) { for (const item of items) {
this._eventListeners.addEventListener(item, 'click', onMenuItemClick, false); this._eventListeners.addEventListener(item, 'click', onMenuItemClick, false);
} }
this._sourceElement.dispatchEvent(new CustomEvent('menuOpened', { PopupMenu.openMenus.add(this);
this._sourceElement.dispatchEvent(new CustomEvent('menuOpen', {
bubbles: false, bubbles: false,
cancelable: false, cancelable: false,
detail: { detail: {menu: this}
popupMenu: this,
container: this._container,
menu: this._menu
}
})); }));
} }
close() { close(cancelable=true) {
return this._close(null, 'close'); return this._close(null, 'close', cancelable);
} }
// Private // Private
_onMenuContainerClick(e) { _onMenuContainerClick(e) {
if (e.currentTarget !== e.target) { return; } if (e.currentTarget !== e.target) { return; }
this._close(null, 'outside'); this._close(null, 'outside', true);
} }
_onMenuItemClick(e) { _onMenuItemClick(e) {
const item = e.currentTarget; const item = e.currentTarget;
if (item.disabled) { return; } if (item.disabled) { return; }
this._close(item, 'item'); this._close(item, 'item', true);
} }
_onWindowResize() { _onWindowResize() {
this._close(null, 'resize'); this._close(null, 'resize', true);
} }
_setPosition(items) { _setPosition(items) {
@ -122,8 +132,8 @@ class PopupMenu extends EventDispatcher {
} }
// Position // Position
const menu = this._menu; const menu = this._node;
const fullRect = this._container.getBoundingClientRect(); const fullRect = this._containerNode.getBoundingClientRect();
const sourceRect = this._sourceElement.getBoundingClientRect(); const sourceRect = this._sourceElement.getBoundingClientRect();
const menuRect = menu.getBoundingClientRect(); const menuRect = menu.getBoundingClientRect();
let top = menuRect.top; let top = menuRect.top;
@ -154,37 +164,35 @@ class PopupMenu extends EventDispatcher {
menu.style.top = `${y}px`; menu.style.top = `${y}px`;
} }
_close(item, cause) { _close(item, cause, cancelable) {
if (this._isClosed) { return true; } if (this._isClosed) { return true; }
const action = (item !== null ? item.dataset.menuAction : null); const action = (item !== null ? item.dataset.menuAction : null);
const result = this._sourceElement.dispatchEvent(new CustomEvent('menuClosed', { const detail = {
bubbles: false, menu: this,
cancelable: true,
detail: {
popupMenu: this,
container: this._container,
menu: this._menu,
item,
action,
cause
}
}));
if (!result) { return false; }
this._eventListeners.removeAllEventListeners();
if (this._container.parentNode !== null) {
this._container.parentNode.removeChild(this._container);
}
this.trigger('closed', {
popupMenu: this,
container: this._container,
menu: this._menu,
item, item,
action, action,
cause cause
}); };
const result = this._sourceElement.dispatchEvent(new CustomEvent('menuClose', {bubbles: false, cancelable, detail}));
if (cancelable && !result) { return false; }
PopupMenu.openMenus.delete(this);
this._isClosed = true;
this._eventListeners.removeAllEventListeners();
if (this._containerNode.parentNode !== null) {
this._containerNode.parentNode.removeChild(this._containerNode);
}
this.trigger('close', detail);
return true; return true;
} }
} }
Object.defineProperty(PopupMenu, 'openMenus', {
configurable: false,
enumerable: true,
writable: false,
value: new Set()
});

View File

@ -478,8 +478,8 @@ class ProfileConditionUI {
this._eventListeners.addEventListener(this._operatorInput, 'change', this._onOperatorChange.bind(this), false); this._eventListeners.addEventListener(this._operatorInput, 'change', this._onOperatorChange.bind(this), false);
if (this._removeButton !== null) { this._eventListeners.addEventListener(this._removeButton, 'click', this._onRemoveButtonClick.bind(this), false); } if (this._removeButton !== null) { this._eventListeners.addEventListener(this._removeButton, 'click', this._onRemoveButtonClick.bind(this), false); }
if (this._menuButton !== null) { if (this._menuButton !== null) {
this._eventListeners.addEventListener(this._menuButton, 'menuOpened', this._onMenuOpened.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuOpen', this._onMenuOpen.bind(this), false);
this._eventListeners.addEventListener(this._menuButton, 'menuClosed', this._onMenuClosed.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false);
} }
} }
@ -545,15 +545,16 @@ class ProfileConditionUI {
this._removeSelf(); this._removeSelf();
} }
_onMenuOpened({detail: {menu}}) { _onMenuOpen(e) {
const deleteGroup = menu.querySelector('.popup-menu-item[data-menu-action="deleteGroup"]'); const node = e.detail.menu.node;
const deleteGroup = node.querySelector('.popup-menu-item[data-menu-action="deleteGroup"]');
if (deleteGroup !== null) { if (deleteGroup !== null) {
deleteGroup.hidden = (this._parent.childCount <= 1); deleteGroup.hidden = (this._parent.childCount <= 1);
} }
} }
_onMenuClosed({detail: {action}}) { _onMenuClose(e) {
switch (action) { switch (e.detail.action) {
case 'delete': case 'delete':
this._removeSelf(); this._removeSelf();
break; break;

View File

@ -612,8 +612,8 @@ class ProfileEntry {
this._eventListeners.addEventListener(this._isDefaultRadio, 'change', this._onIsDefaultRadioChange.bind(this), false); this._eventListeners.addEventListener(this._isDefaultRadio, 'change', this._onIsDefaultRadioChange.bind(this), false);
this._eventListeners.addEventListener(this._nameInput, 'input', this._onNameInputInput.bind(this), false); this._eventListeners.addEventListener(this._nameInput, 'input', this._onNameInputInput.bind(this), false);
this._eventListeners.addEventListener(this._countLink, 'click', this._onConditionsCountLinkClick.bind(this), false); this._eventListeners.addEventListener(this._countLink, 'click', this._onConditionsCountLinkClick.bind(this), false);
this._eventListeners.addEventListener(this._menuButton, 'menuOpened', this._onMenuOpened.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuOpen', this._onMenuOpen.bind(this), false);
this._eventListeners.addEventListener(this._menuButton, 'menuClosed', this._onMenuClosed.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false);
} }
cleanup() { cleanup() {
@ -658,16 +658,17 @@ class ProfileEntry {
this._profileController.openProfileConditionsModal(this._index); this._profileController.openProfileConditionsModal(this._index);
} }
_onMenuOpened({detail: {menu}}) { _onMenuOpen(e) {
const node = e.detail.menu.node;
const count = this._profileController.profileCount; const count = this._profileController.profileCount;
this._setMenuActionEnabled(menu, 'moveUp', this._index > 0); this._setMenuActionEnabled(node, 'moveUp', this._index > 0);
this._setMenuActionEnabled(menu, 'moveDown', this._index < count - 1); this._setMenuActionEnabled(node, 'moveDown', this._index < count - 1);
this._setMenuActionEnabled(menu, 'copyFrom', count > 1); this._setMenuActionEnabled(node, 'copyFrom', count > 1);
this._setMenuActionEnabled(menu, 'delete', count > 1); this._setMenuActionEnabled(node, 'delete', count > 1);
} }
_onMenuClosed({detail: {action}}) { _onMenuClose(e) {
switch (action) { switch (e.detail.action) {
case 'moveUp': case 'moveUp':
this._profileController.moveProfile(this._index, -1); this._profileController.moveProfile(this._index, -1);
break; break;

View File

@ -208,8 +208,8 @@ class ScanInputField {
this._eventListeners.addEventListener(removeButton, 'click', this._onRemoveClick.bind(this)); this._eventListeners.addEventListener(removeButton, 'click', this._onRemoveClick.bind(this));
} }
if (menuButton !== null) { if (menuButton !== null) {
this._eventListeners.addEventListener(menuButton, 'menuOpened', this._onMenuOpened.bind(this)); this._eventListeners.addEventListener(menuButton, 'menuOpen', this._onMenuOpen.bind(this));
this._eventListeners.addEventListener(menuButton, 'menuClosed', this._onMenuClosed.bind(this)); this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this));
} }
this._updateDataSettingTargets(); this._updateDataSettingTargets();
@ -245,9 +245,10 @@ class ScanInputField {
this._removeSelf(); this._removeSelf();
} }
_onMenuOpened({detail: {menu}}) { _onMenuOpen(e) {
const showAdvanced = menu.querySelector('.popup-menu-item[data-menu-action="showAdvanced"]'); const node = e.detail.menu.node;
const hideAdvanced = menu.querySelector('.popup-menu-item[data-menu-action="hideAdvanced"]'); const showAdvanced = node.querySelector('.popup-menu-item[data-menu-action="showAdvanced"]');
const hideAdvanced = node.querySelector('.popup-menu-item[data-menu-action="hideAdvanced"]');
const advancedVisible = (this._node.dataset.showAdvanced === 'true'); const advancedVisible = (this._node.dataset.showAdvanced === 'true');
if (showAdvanced !== null) { if (showAdvanced !== null) {
showAdvanced.hidden = advancedVisible; showAdvanced.hidden = advancedVisible;
@ -257,8 +258,8 @@ class ScanInputField {
} }
} }
_onMenuClosed({detail: {action}}) { _onMenuClose(e) {
switch (action) { switch (e.detail.action) {
case 'remove': case 'remove':
this._removeSelf(); this._removeSelf();
break; break;

View File

@ -199,7 +199,7 @@ class KeyboardShortcutHotkeyEntry {
for (const scopeCheckbox of scopeCheckboxes) { for (const scopeCheckbox of scopeCheckboxes) {
this._eventListeners.addEventListener(scopeCheckbox, 'change', this._onScopeCheckboxChange.bind(this), false); this._eventListeners.addEventListener(scopeCheckbox, 'change', this._onScopeCheckboxChange.bind(this), false);
} }
this._eventListeners.addEventListener(menuButton, 'menuClosed', this._onMenuClosed.bind(this), false); this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this), false);
this._eventListeners.addEventListener(this._actionSelect, 'change', this._onActionSelectChange.bind(this), false); this._eventListeners.addEventListener(this._actionSelect, 'change', this._onActionSelectChange.bind(this), false);
this._eventListeners.on(this._inputField, 'change', this._onInputFieldChange.bind(this)); this._eventListeners.on(this._inputField, 'change', this._onInputFieldChange.bind(this));
} }
@ -214,9 +214,8 @@ class KeyboardShortcutHotkeyEntry {
// Private // Private
_onMenuClosed(e) { _onMenuClose(e) {
const {detail: {action}} = e; switch (e.detail.action) {
switch (action) {
case 'delete': case 'delete':
this._delete(); this._delete();
break; break;

View File

@ -182,7 +182,7 @@ class SentenceTerminationCharacterEntry {
this._eventListeners.addEventListener(typeSelect, 'change', this._onTypeSelectChange.bind(this), false); this._eventListeners.addEventListener(typeSelect, 'change', this._onTypeSelectChange.bind(this), false);
this._eventListeners.addEventListener(character1Input, 'change', this._onCharacterChange.bind(this, 1), false); this._eventListeners.addEventListener(character1Input, 'change', this._onCharacterChange.bind(this, 1), false);
this._eventListeners.addEventListener(character2Input, 'change', this._onCharacterChange.bind(this, 2), false); this._eventListeners.addEventListener(character2Input, 'change', this._onCharacterChange.bind(this, 2), false);
this._eventListeners.addEventListener(menuButton, 'menuClosed', this._onMenuClosed.bind(this), false); this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this), false);
} }
cleanup() { cleanup() {
@ -208,9 +208,8 @@ class SentenceTerminationCharacterEntry {
this._setCharacterValue(node, characterNumber, value); this._setCharacterValue(node, characterNumber, value);
} }
_onMenuClosed(e) { _onMenuClose(e) {
const {detail: {action}} = e; switch (e.detail.action) {
switch (action) {
case 'delete': case 'delete':
this._delete(); this._delete();
break; break;

View File

@ -26,7 +26,6 @@ class SettingsDisplayController {
this._modalController = modalController; this._modalController = modalController;
this._contentNode = null; this._contentNode = null;
this._menuContainer = null; this._menuContainer = null;
this._openPopupMenus = new Set();
this._onMoreToggleClickBind = null; this._onMoreToggleClickBind = null;
this._onMenuButtonClickBind = null; this._onMenuButtonClickBind = null;
} }
@ -198,11 +197,6 @@ class SettingsDisplayController {
return false; return false;
} }
_onClosePopupMenu({popupMenu, onClose}) {
this._openPopupMenus.delete(popupMenu);
popupMenu.off('closed', onClose);
}
_onInputTabActionKeyDown(e) { _onInputTabActionKeyDown(e) {
if (e.key !== 'Tab' || e.ctrlKey) { return; } if (e.key !== 'Tab' || e.ctrlKey) { return; }
@ -248,7 +242,7 @@ class SettingsDisplayController {
} }
_closeTopMenuOrModal() { _closeTopMenuOrModal() {
for (const popupMenu of this._openPopupMenus) { for (const popupMenu of PopupMenu.openMenus) {
popupMenu.close(); popupMenu.close();
return; return;
} }
@ -266,12 +260,6 @@ class SettingsDisplayController {
this._menuContainer.appendChild(menu); this._menuContainer.appendChild(menu);
const popupMenu = new PopupMenu(element, menu); const popupMenu = new PopupMenu(element, menu);
this._openPopupMenus.add(popupMenu);
const data = {popupMenu, onClose: null};
data.onClose = this._onClosePopupMenu.bind(this, data);
popupMenu.on('closed', data.onClose);
popupMenu.prepare(); popupMenu.prepare();
} }

View File

@ -158,8 +158,8 @@ class TranslationTextReplacementsEntry {
replacementInput.dataset.setting = `${pathBase}.replacement`; replacementInput.dataset.setting = `${pathBase}.replacement`;
ignoreCaseToggle.dataset.setting = `${pathBase}.ignoreCase`; ignoreCaseToggle.dataset.setting = `${pathBase}.ignoreCase`;
this._eventListeners.addEventListener(menuButton, 'menuOpened', this._onMenuOpened.bind(this), false); this._eventListeners.addEventListener(menuButton, 'menuOpen', this._onMenuOpen.bind(this), false);
this._eventListeners.addEventListener(menuButton, 'menuClosed', this._onMenuClosed.bind(this), false); this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this), false);
this._eventListeners.addEventListener(patternInput, 'settingChanged', this._onPatternChanged.bind(this), false); this._eventListeners.addEventListener(patternInput, 'settingChanged', this._onPatternChanged.bind(this), false);
this._eventListeners.addEventListener(ignoreCaseToggle, 'settingChanged', this._updateTestInput.bind(this), false); this._eventListeners.addEventListener(ignoreCaseToggle, 'settingChanged', this._updateTestInput.bind(this), false);
this._eventListeners.addEventListener(replacementInput, 'settingChanged', this._updateTestInput.bind(this), false); this._eventListeners.addEventListener(replacementInput, 'settingChanged', this._updateTestInput.bind(this), false);
@ -175,14 +175,15 @@ class TranslationTextReplacementsEntry {
// Private // Private
_onMenuOpened({detail: {menu}}) { _onMenuOpen(e) {
const node = e.detail.menu.node;
const testVisible = this._isTestVisible(); const testVisible = this._isTestVisible();
menu.querySelector('[data-menu-action=showTest]').hidden = testVisible; node.querySelector('[data-menu-action=showTest]').hidden = testVisible;
menu.querySelector('[data-menu-action=hideTest]').hidden = !testVisible; node.querySelector('[data-menu-action=hideTest]').hidden = !testVisible;
} }
_onMenuClosed({detail: {action}}) { _onMenuClose(e) {
switch (action) { switch (e.detail.action) {
case 'remove': case 'remove':
this._parent.deleteGroup(this._index); this._parent.deleteGroup(this._index);
break; break;