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

293 lines
9.8 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._clickedIndex = null;
this.defaultFocusIndex = -1;
this._margin = options && options.margin ? options.margin : DEFAULT_MARGIN;
this._translateX = 0;
BaseModalDialog.call(this, container);
}
var proto = Object.create(BaseModalDialog.prototype);
proto._init = function() {
this._render();
this.smartBubble = document.createElement('smart-bubbles');
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);
};
proto._render = function() {
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);
};
proto._renderMessage = function(options) {
this.message = options.message || {};
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');
}
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);
}
}
};
proto._renderButtons = function(options) {
var renderedCallback = options.onButtonRendered;
this.buttonElements = [];
this.buttonSettings = options.buttonSettings || [];
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));
};
proto._open = function(options) {
this._focusedIndex = this.defaultFocusIndex;
this._renderMessage(options);
this._renderButtons(options);
BaseModalDialog.prototype._open.call(this, options);
};
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._focusContent = function() {
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._blurContent = 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;
}
};
SmartModalDialog.prototype = proto;
exports.SmartModalDialog = SmartModalDialog;
})(window);