VulpesOS/gaia/tv_apps/smart-system/js/action_menu.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

247 lines
7.1 KiB
JavaScript
Executable file

/* global focusManager */
'use strict';
(function(exports) {
/**
* ActionMenu displays a list of user-selectable actions in an overlay.
* An example of this would be when the user selects a single activity form
* a list of several activities. Each ActionMenu instance maintains its own
* dom and event listeners.
* @class ActionMenu
* @param {Array} listItems An array of objects to display.
* @param {String} titleL10nId The l10n id of the header.
* @param {Function} successCb Called when the user selects an option.
* @param {Function} cancelCb Called when the menu is cancelled.
* @param {Boolean} preventFocusChange Set to true to prevent focus changing.
*/
function ActionMenu(listItems, titleL10nId, successCb, cancelCb,
preventFocusChange) {
this.onselected = successCb || function() {};
this.oncancel = cancelCb || function() {};
this.listItems = listItems;
this.titleL10nId = titleL10nId;
}
ActionMenu.prototype = {
/**
* Whether or not the ActionMenu is visible.
* @memberof ActionMenu.prototype
* @return {Boolean} The ActionMenu is visible.
*/
get visible() {
return this.container.classList.contains('visible');
},
/**
* Builds dom and adds event listeners
* @memberof ActionMenu.prototype
*/
start: function() {
// Create the structure
this.container = document.createElement('form');
this.container.dataset.type = 'action';
this.container.setAttribute('role', 'dialog');
this.container.setAttribute('data-z-index-level', 'action-menu');
// An action menu has a mandatory header
this.header = document.createElement('header');
if (this.titleL10nId !== undefined) {
this.header.setAttribute('data-l10n-id', this.titleL10nId);
}
this.container.appendChild(this.header);
// Following our paradigm we need a cancel
this.cancel = document.createElement('button');
this.cancel.dataset.action = 'cancel';
this.cancel.dataset.l10nId = 'cancel';
// We have a menu with all the options
this.menu = document.createElement('menu');
this.container.appendChild(this.menu);
this.container.classList.add('visible');
// We append to System app (actually to '#screen')
var screen = document.getElementById('screen');
screen.appendChild(this.container);
screen.classList.add('action-menu');
this.buildMenu(this.listItems);
this.container.addEventListener('submit', this);
this.menu.addEventListener('click', this);
window.addEventListener('attentionopened', this, true);
window.addEventListener('screenchange', this, true);
window.addEventListener('home', this);
window.addEventListener('holdhome', this);
window.addEventListener('sheets-gesture-begin', this);
focusManager.addUI(this);
focusManager.focus();
if (this.preventFocusChange) {
this.menu.addEventListener('mousedown', this.preventFocusChange);
}
},
/**
* Removes the dom and stops event listeners
* @memberof ActionMenu.prototype
*/
stop: function() {
var screen = document.getElementById('screen');
focusManager.removeUI(this);
screen.removeChild(this.container);
screen.classList.remove('action-menu');
window.removeEventListener('attentionopened', this, true);
window.removeEventListener('screenchange', this, true);
window.removeEventListener('home', this);
window.removeEventListener('holdhome', this);
window.removeEventListener('sheets-gesture-begin', this);
if (this.preventFocusChange) {
this.menu.removeEventListener('mousedown', this.preventFocusChange);
}
},
/**
* Builds the dom for the menu.
* @memberof ActionMenu.prototype
*/
buildMenu: function(items) {
this.menu.innerHTML = '';
items.forEach(function traveseItems(item) {
var action = document.createElement('button');
action.dataset.value = item.value;
action.textContent = item.label;
if (item.icon) {
action.classList.add(item.iconClass || 'icon');
action.style.backgroundImage = 'url(' + item.icon + ')';
}
this.menu.appendChild(action);
}, this);
this.cancel.setAttribute('data-l10n-id', 'cancel');
this.menu.appendChild(this.cancel);
},
/**
* Hides the ActionMenu.
* @memberof ActionMenu.prototype
* @param {Function} callback The callback to call after hiding.
*/
hide: function(callback) {
this.container.classList.remove('visible');
this.stop();
// focus back to the top most window/overlay.
focusManager.focus();
if (callback && typeof callback === 'function') {
setTimeout(callback);
}
},
/**
* When IME switcher shows, prevent the keyboard focus getting changed.
* @memberof ActionMenu.prototype
* @param {DOMEvent} evt The event.
*/
preventFocusChange: function(evt) {
evt.preventDefault();
},
/**
* General event handler interface.
* Handles submission and cancellation events.
* @memberof ActionMenu.prototype
* @param {DOMEvent} evt The event.
*/
handleEvent: function(evt) {
var target = evt.target;
var type = evt.type;
switch (type) {
case 'submit':
evt.preventDefault();
break;
case 'screenchange':
if (!this.visible) {
return;
}
if (!evt.detail.screenEnabled) {
this.hide();
this.oncancel();
}
break;
case 'click':
evt.preventDefault();
var action = target.dataset.action;
if (action && action === 'cancel') {
this.hide();
this.oncancel();
return;
}
var value = target.dataset.value;
if (!value) {
return;
}
value = parseInt(value);
this.hide(this.onselected.bind(this, value));
break;
case 'home':
case 'holdhome':
case 'sheets-gesture-begin':
if (!this.visible) {
return;
}
this.hide();
this.oncancel();
break;
case 'attentionopened':
this.hide();
this.oncancel();
break;
}
},
/**
* Whether or not the ActionMenu is visible.
* @memberof ActionMenu.prototype
* @return {Boolean} if the container is focusible, return true
*/
isFocusable: function() {
return this.container && this.visible;
},
/**
* Get the z-index value of container
* @memberof ActionMenu.prototype
* @return {HTMLElement} the container
*/
getElement: function() {
return this.container;
},
/**
* Focus cancel button in ActionMenu
* @memberof ActionMenu.prototype
*/
focus: function() {
if (this.cancel) {
document.activeElement.blur();
this.cancel.focus();
}
}
};
exports.ActionMenu = ActionMenu;
}(window));