VulpesOS/gaia/tv_apps/smart-system/bower_components/smart-modal-dialog/script.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

353 lines
12 KiB
JavaScript
Executable file

/**
* SmartModalDialog creates a dialog with a group of buttons as select
* options. It also includes open/close animation and bubbling animation.
* The template is shown below:
* <smart-dialog class="modal-dialog">
* <div class="outer-container">
* <div class="container">
* <div class="modal-dialog-message-container">
* <div class="modal-dialog-message"></div>
* <div class="modal-dialog-custom-group"></div>
* </div>
* <div class="modal-dialog-button-group">
* <smart-button></smart-button>
* ...
* <smart-button></smart-button>
* </div>
* </div>
* </div>
* </smart-dialog>
*/
(function(exports) {
'use strict';
var DEFAULT_MARGIN = 44;
function SmartModalDialog(container, options) {
// Determine whether this dialog is opened or not
this.isOpened = false;
this._clickedIndex = null;
this.defaultFocusIndex = -1;
this._margin = options && options.margin ? options.margin : DEFAULT_MARGIN;
this._translateX = 0;
this.smartBubble = document.createElement('smart-bubbles');
// smart-dialog helps us to handle ESC key to close the dialog.
this.element = document.createElement('smart-dialog');
this.element.classList.add('modal-dialog');
// make dialog focusable in order to catch focus from mouse click or touch
// event.
this.element.setAttribute('tabIndex', '-1');
// in order to make vertical align, we need additional container
this.outerContainer = document.createElement('div');
this.outerContainer.classList.add('outer-container');
this.innerContainer = document.createElement('div');
this.innerContainer.classList.add('container');
this.messageElement = document.createElement('div');
this.messageElement.classList.add('modal-dialog-message');
this.customElementGroup = document.createElement('div');
this.customElementGroup.classList.add('modal-dialog-custom-group');
this.messageContainer = document.createElement('div');
this.messageContainer.classList.add('modal-dialog-message-container');
this.messageContainer.appendChild(this.messageElement);
this.messageContainer.appendChild(this.customElementGroup);
this.buttonGroup = document.createElement('div');
this.buttonGroup.classList.add('modal-dialog-button-group');
this.buttonGroup.setAttribute('smart-bubbles', 'true');
this.innerContainer.appendChild(this.messageContainer);
this.innerContainer.appendChild(this.buttonGroup);
this.outerContainer.appendChild(this.innerContainer);
this.element.appendChild(this.outerContainer);
this.container = container || document.body;
this.container.appendChild(this.element);
this.element.addEventListener('will-open', this);
this.element.addEventListener('will-close', this);
this.element.addEventListener('opened', this);
this.element.addEventListener('closed', this);
this.smartBubble.addEventListener('all-items-bubbled', this);
this._init();
}
var proto = Object.create(SmartDialog.prototype);
proto._init = function(container, options) {
// Put your custom init code here.
};
proto._setL10n = function(element, l10n) {
if ((typeof l10n) === 'string') {
element.setAttribute('data-l10n-id', l10n);
} else if (navigator.mozL10n) {
navigator.mozL10n.setAttributes(element, l10n.id, l10n.args);
}
};
proto._open = function(options) {
this.message = options.message || {};
var renderedCallback = options.onButtonRendered;
this.customSettings = options.customElementSettings || null;
if (this.customSettings) {
while (this.customElementGroup.firstChild) {
this.customElementGroup.removeChild(this.customElementGroup.firstChild);
}
this.customElementGroup.appendChild(this.customSettings.element);
this.customElementGroup.classList.remove('hidden');
} else {
this.customElementGroup.classList.add('hidden');
}
// onCancel is triggered when ESC key is pressed.
this.onCancel = options.onCancel || function() {};
this.buttonElements = [];
this.buttonSettings = options.buttonSettings || [];
if (!this.message.textRaw && !this.message.textL10nId) {
this.messageElement.classList.add('hidden');
} else {
this.messageElement.classList.remove('hidden');
this.messageElement.textContent = this.message.textRaw || '';
if (this.message.textL10nId) {
this._setL10n(this.messageElement, this.message.textL10nId);
}
}
this.buttonGroup.innerHTML = '';
// Set up every button
this.buttonSettings.forEach(function buildButton(buttonSetting, index) {
var button = document.createElement('smart-button');
button.setAttribute('type', buttonSetting.type || 'circle-text');
button.textContent = buttonSetting.textRaw || 'OK';
if (buttonSetting.textL10nId) {
this._setL10n(button, buttonSetting.textL10nId);
}
button.classList.add(buttonSetting.class || 'confirm');
if (buttonSetting.icon) {
button.style.backgroundImage = 'url(' + buttonSetting.icon + ')';
} else if (buttonSetting.iconFont) {
button.dataset.icon = buttonSetting.iconFont;
}
button.addEventListener('click', function() {
// Click action will be handled in closed event
this._clickedIndex = index;
this.element.close();
this.element.focus();
}.bind(this));
this.buttonElements.push(button);
this.buttonGroup.appendChild(button);
this._focusedIndex =
(buttonSetting.defaultFocus) ? index : this._focusedIndex;
if (renderedCallback) {
// We add render callback to let user to do more complex visual
// modifications.
renderedCallback(button, buttonSetting);
}
}.bind(this));
this.element.classList.add('visible');
this.element.open();
this.element.focus();
};
proto.open = function(options) {
if (this.isOpened) {
return;
}
this.isOpened = true;
this._focusedIndex = this.defaultFocusIndex;
// We should wait two frames for reflow.
window.requestAnimationFrame(function() {
window.requestAnimationFrame(this._open.bind(this, options));
}.bind(this));
};
proto.close = function() {
this.element.close();
this.element.focus();
};
proto.remove = function() {
this.container.removeChild(this.element);
};
proto.startKeyNavigation = function() {
this.element.addEventListener('keydown', this);
if (this.buttonElements.length > 0) {
if (this._focusedIndex === -1) {
this._focusedIndex = 0;
}
this.focus();
}
};
/**
* Scroll the list to the input element
*/
proto._scrollTo = function(element) {
var newTransition = this._getScrollOffset(element);
this._translateX = newTransition;
this.buttonGroup.style.transform = 'translateX(' + this._translateX +
'px)';
};
/**
* Get the offset of the node element
*/
proto._getScrollOffset = function(nodeElem) {
if (!this._viewWidth) {
this._viewWidth = this.innerContainer.clientWidth - 2 * this._margin;
}
var nodeLeft = nodeElem.offsetLeft;
var nodeWidth = nodeElem.offsetWidth;
var listWidth = this.buttonGroup.offsetWidth;
var newTranslate = this._translateX;
var lastElement = this.buttonElements[this.buttonElements.length - 1];
var firstElement = this.buttonElements[0];
if (listWidth < this._viewWidth) {
// align to horizontal center if list width is smaller than the container
return (this._viewWidth - listWidth) / 2 + this._margin;
} else if (nodeLeft + nodeWidth >
-this._translateX + this._viewWidth + this._margin) {
// scroll left if the node falls beyond the right edge of container
newTranslate = this._viewWidth - nodeLeft - nodeWidth + this._margin;
} else if (nodeLeft < -this._translateX + this._margin) {
// scroll right if the node falls beyond the left edge of container
newTranslate = -nodeLeft + this._margin;
}
// If the new scroll offset contains first/last node, we have to align the
// list to begin/end.
if (lastElement.offsetLeft + lastElement.offsetWidth <=
-newTranslate + this._viewWidth + this._margin) {
return this._viewWidth + this._margin - lastElement.offsetLeft -
lastElement.offsetWidth;
} else if (firstElement.offsetLeft >= -newTranslate + this._margin) {
return -firstElement.offsetLeft + this._margin;
}
return newTranslate;
};
proto._getFocusedElement = function() {
return this.buttonElements[this._focusedIndex];
};
proto.focus = function() {
if (this.element.classList.contains('opening') ||
this.element.classList.contains('closing')) {
this.element.focus();
} else {
var elem = this._getFocusedElement()
this._scrollTo(elem);
// move focus to smart dialog while transition running
if (elem.focus && (typeof elem.focus) === 'function') {
elem.focus();
}
}
};
proto.blur = function() {
var elem = this._getFocusedElement();
if (elem.blur && (typeof elem.blur === 'function')) {
elem.blur();
}
};
proto.stopKeyNavigation = function() {
this.element.removeEventListener('keydown', this);
};
proto.movePrevious = function() {
if (this._focusedIndex < 1) {
// Do nothing when focus index is at the first button element.
return;
}
this._focusedIndex--;
this.focus();
};
proto.moveNext = function() {
if (this._focusedIndex > this.buttonElements.length - 2) {
// Do nothing when focus index is at the last button element
return;
}
this._focusedIndex++;
this.focus();
};
proto._handleKeyEvent = function(e) {
if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
this.movePrevious();
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
this.moveNext();
}
};
proto.handleEvent = function(e) {
this._handleKeyEvent(e);
switch(e.target) {
case this.element:
if (e.type === 'will-open' || e.type === 'will-close') {
this.fireEvent('modal-dialog-' + e.type);
} else if (e.type === 'opened') {
this._scrollTo(this.buttonElements[0]);
// Play bubble animation when the smart-dialog is opened
this.smartBubble.play(this.buttonElements);
} else if (e.type === 'closed') {
// Do click actions when the smart-dialog is closed
this.element.classList.remove('visible');
if ((this._clickedIndex || this._clickedIndex === 0) &&
this.buttonSettings[this._clickedIndex].onClick) {
this.buttonSettings[this._clickedIndex].onClick(
this.buttonSettings[this._clickedIndex]);
this._clickedIndex = null;
} else if (this.onCancel) {
// ESC key enter
this.onCancel();
}
this.isOpened = false;
this.stopKeyNavigation();
this.element.classList.remove('modal-dialog-opened');
this.fireEvent('modal-dialog-closed');
}
break;
case this.smartBubble:
this.element.classList.add('modal-dialog-opened');
this.startKeyNavigation();
this.focus();
this.fireEvent('modal-dialog-opened');
break;
}
};
proto.fireEvent = function smd_fireEvent(event, detail) {
var evtObject = new CustomEvent(event, {
bubbles: true,
detail: detail || this
});
this.container.dispatchEvent(evtObject);
};
SmartModalDialog.prototype = proto;
exports.SmartModalDialog = SmartModalDialog;
})(window);