From 1980fc819b472e973769d5a23bd974f133976a5c Mon Sep 17 00:00:00 2001 From: Rex Lee Date: Thu, 5 Feb 2015 18:27:07 +0800 Subject: [PATCH] Bug 1129386 - [Stingray][System] Send keyboard events to only focused context menu --- tv_apps/smart-system/js/app_modal_dialog.js | 16 +- .../smart-system/js/browser_context_menu.js | 47 +++--- .../js/interactive_notifications.js | 3 +- .../action_menu/action_menu_extended.css | 137 +++++++++--------- .../tv_shared/js/key_navigation_adapter.js | 11 +- tv_apps/tv_shared/js/simple_key_navigation.js | 11 +- 6 files changed, 111 insertions(+), 114 deletions(-) diff --git a/tv_apps/smart-system/js/app_modal_dialog.js b/tv_apps/smart-system/js/app_modal_dialog.js index 655bea268c6c..b97660ef1817 100644 --- a/tv_apps/smart-system/js/app_modal_dialog.js +++ b/tv_apps/smart-system/js/app_modal_dialog.js @@ -267,7 +267,9 @@ } this.simpleKeyNavigation.start( - [elements.alertOk], SimpleKeyNavigation.DIRECTION.HORIZONTAL); + [elements.alertOk], + SimpleKeyNavigation.DIRECTION.HORIZONTAL, + {target: elements.alert}); // XXX: Focusing smart-button fails at the second time popup if we don't // postpone it. We need to find the root cause. @@ -300,12 +302,13 @@ document.activeElement.blur(); var horizontalButtonNavigation = new SimpleKeyNavigation(); horizontalButtonNavigation.start( - [elements.promptCancel, elements.promptOk], - SimpleKeyNavigation.DIRECTION.HORIZONTAL, - true); + [elements.promptCancel, elements.promptOk], + SimpleKeyNavigation.DIRECTION.HORIZONTAL, + {isChild: true}); this.simpleKeyNavigation.start( [elements.promptInput, horizontalButtonNavigation], - SimpleKeyNavigation.DIRECTION.VERTICAL); + SimpleKeyNavigation.DIRECTION.VERTICAL, + {target: elements.prompt}); elements.promptInput.select(); break; @@ -331,7 +334,8 @@ document.activeElement.blur(); this.simpleKeyNavigation.start( [elements.confirmCancel, elements.confirmOk], - SimpleKeyNavigation.DIRECTION.HORIZONTAL); + SimpleKeyNavigation.DIRECTION.HORIZONTAL, + {target: elements.confirm}); // XXX: Focusing smart-button fails at the second time popup if we don't // postpone it. We need to find the root cause. diff --git a/tv_apps/smart-system/js/browser_context_menu.js b/tv_apps/smart-system/js/browser_context_menu.js index fb5d22f56195..ac937128fb1e 100644 --- a/tv_apps/smart-system/js/browser_context_menu.js +++ b/tv_apps/smart-system/js/browser_context_menu.js @@ -23,15 +23,11 @@ this.keyNavigationAdapter = new KeyNavigationAdapter(); this.keyNavigationAdapter.on('move', function(key) { - this.scrollable.move(key); - }.bind(this)); - // All behaviors which no need to have multple events while holding the - // key should use keyup. - this.keyNavigationAdapter.on('enter-keyup', function() { - this.scrollable.currentItem.click(); + this.scrollable && this.scrollable.move(key); }.bind(this)); + // All behaviors which no need to have multple events while holding the - // key should use keyup + // key should use keyup this.keyNavigationAdapter.on('esc-keyup', this.hide.bind(this)); this.circleAnimation = Animations @@ -44,17 +40,10 @@ BrowserContextMenu.prototype.handleFocus = function(scrollable, elem) { if (elem.nodeName) { - elem.classList.add('hover'); elem.focus(); } }; - BrowserContextMenu.prototype.handleUnfocus = function(scrollable, elem) { - if (elem.nodeName) { - elem.classList.remove('hover'); - } - }; - BrowserContextMenu.prototype.CLASS_NAME = 'BrowserContextMenu'; BrowserContextMenu.prototype.ELEMENT_PREFIX = 'contextmenu-'; @@ -116,8 +105,6 @@ BrowserContextMenu.prototype.show = function(evt) { var detail = evt.detail; - this.keyNavigationAdapter.init(); - var hasContextMenu = detail.contextmenu && detail.contextmenu.items.length > 0; var hasSystemTargets = detail.systemTargets && @@ -145,6 +132,9 @@ evt.preventDefault(); evt.stopPropagation(); this.showMenu(items); + + // We need to init this after showMenu for fetching this.element + this.keyNavigationAdapter.init(this.element); }; BrowserContextMenu.prototype.hasMenuVisible = function() { @@ -162,12 +152,20 @@ } this._injected = true; this.buildMenu(menu); - document.activeElement.blur(); - this.scrollable.catchFocus(); + + this._createCloseMenuHandler(); this.circleAnimation.play({type: 'grow'}, function() { this.element.classList.add('visible'); Animations.doBubbleAnimation( this.contextFrame, '.' + this.ELEMENT_PREFIX + 'button', 100); + // XXX: We need to wait a short interval before we can focus on the + // button element. (This is NOT related to bubble animation above) + // so we temporarily use setTimeout here. + // This may need to be fixed from Gecko. + setTimeout(function() { + document.activeElement.blur(); + this.scrollable.catchFocus(); + }.bind(this), 100); }.bind(this)); }, @@ -175,17 +173,19 @@ var self = this; var container = document.createElement('div'); - var action = document.createElement('button'); + var action = document.createElement('smart-button'); var icon = document.createElement('div'); + action.dataset.id = item.id; action.dataset.value = item.value; var l10nPayload = item.labelL10nId ? item.labelL10nId : {raw: item.label}; SharedUtils.localizeElement(action, l10nPayload); action.className = self.ELEMENT_PREFIX + 'button'; + action.setAttribute('type', 'contextmenu'); + icon.classList.add('icon'); if (item.icon) { - icon.classList.add(item.iconClass || 'icon'); icon.style.backgroundImage = 'url(' + item.icon + ')'; } @@ -200,7 +200,7 @@ this.elements.list.appendChild(container); }, - BrowserContextMenu.prototype._createTransitionHandler = function() { + BrowserContextMenu.prototype._createCloseMenuHandler = function() { var self = this; var onFrameDisappear = function onFrameDisappear(evt) { if (evt.propertyName === 'opacity' && @@ -229,16 +229,13 @@ this.elements.list.innerHTML = ''; items.forEach(this._createElement, this); - this._createTransitionHandler(); - this.scrollable = new XScrollable({ frameElem: this.elements.listFrame, listElem: this.elements.list, itemClassName: self.ELEMENT_PREFIX + 'button', - margin: 8.2 + spacing: 8.2 }); this.scrollable.on('focus', this.handleFocus.bind(this)); - this.scrollable.on('unfocus', this.handleUnfocus.bind(this)); }; BrowserContextMenu.prototype._listItems = function(detail) { diff --git a/tv_apps/smart-system/js/interactive_notifications.js b/tv_apps/smart-system/js/interactive_notifications.js index 012154547d92..d3483b04ba31 100644 --- a/tv_apps/smart-system/js/interactive_notifications.js +++ b/tv_apps/smart-system/js/interactive_notifications.js @@ -189,7 +189,8 @@ // focus() for us when the focus switches to another button. this._keyNavigator.start([$('notification-button-0'), $('notification-button-1')], - SimpleKeyNavigation.DIRECTION.HORIZONTAL); + SimpleKeyNavigation.DIRECTION.HORIZONTAL, + {target: this._banner}); } else { document.activeElement.blur(); } diff --git a/tv_apps/smart-system/style/action_menu/action_menu_extended.css b/tv_apps/smart-system/style/action_menu/action_menu_extended.css index 2743591c6869..128729f688e9 100644 --- a/tv_apps/smart-system/style/action_menu/action_menu_extended.css +++ b/tv_apps/smart-system/style/action_menu/action_menu_extended.css @@ -7,10 +7,10 @@ height: calc(100% - 7rem); } -/* Extending action menu with icon capability & transition*/ +/* Extending action menu with icon capability & transition */ -form[role="dialog"][data-type="action"] > div > menu > div > button .icon, -form[role="dialog"][data-type="object"] > div > menu > div > button .icon { +smart-button[type="contextmenu"] > .icon { + content: attr(data-icon); background-position: center top 4.8rem; background-size: 10rem; background-repeat: no-repeat; @@ -21,8 +21,7 @@ form[role="dialog"][data-type="object"] > div > menu > div > button .icon { height: 100%; } -form[role="dialog"][data-type="action"] > div > menu > div > button.hover .icon, -form[role="dialog"][data-type="object"] > div > menu > div > button.hover .icon { +smart-button[type="contextmenu"].focused:not(.pressed) > .icon { filter: url(invert.svg#invert); opacity: 0.824; } @@ -40,77 +39,71 @@ form[role="dialog"][data-type="object"].visible { transform: translateY(0); } -@media (min-width: 768px) { - form[role="dialog"][data-type="action"], - form[role="dialog"][data-type="object"] { - -moz-box-sizing: border-box; - background-color: rgba(0,0,0,0.9); - } - - form[role="dialog"][data-type="action"] > header, - form[role="dialog"][data-type="object"] > header { - top: 5rem; - left: 7.6rem; - font-size: 5.4rem; - font-style: italic; - height: 10rem; - color: white; - border: none; - padding: 0; - } +form[role="dialog"][data-type="action"], +form[role="dialog"][data-type="object"] { + -moz-box-sizing: border-box; + background-color: rgba(0,0,0,0.9); +} - form[role="dialog"][data-type="action"] > div, - form[role="dialog"][data-type="object"] > div { - margin: 0 auto; - width: 100%; - height: 30rem; - padding-top: 3rem; - top: calc(50% - 15rem); - left: 0; - text-align: center; - overflow: hidden; - position: absolute; - } +form[role="dialog"][data-type="action"] > header, +form[role="dialog"][data-type="object"] > header { + top: 5rem; + left: 7.6rem; + font-size: 5.4rem; + font-style: italic; + height: 10rem; + color: white; + border: none; + padding: 0; +} - form[role="dialog"][data-type="action"] > div > menu { - transition: transform 0.2s ease; - } +form[role="dialog"][data-type="action"] > div, +form[role="dialog"][data-type="object"] > div { + margin: 0 auto; + width: 100%; + height: 30rem; + padding-top: 3rem; + top: calc(50% - 15rem); + left: 0; + text-align: center; + overflow: hidden; + position: absolute; +} - form[role="dialog"][data-type="action"] > div > menu > div, - form[role="dialog"][data-type="action"] > div > menu > div { - position: absolute; - left: 0; - margin: 0 3rem; - } +form[role="dialog"][data-type="action"] > div > menu { + transition: transform 0.2s ease; +} - form[role="dialog"][data-type="action"] > div > menu > div > button, - form[role="dialog"][data-type="object"] > div > menu > div > button { - width: 24rem; - height: 24rem; - border-radius: 12rem; - background-color: #5f5f5f; - padding-top: 14.8rem; - padding-bottom: 5rem; - font-size: 2rem; - font-style: italic; - text-align: center; - color: white; - transition: transform 0.2s ease, background-color 0.2s ease; - } +form[role="dialog"][data-type="action"] > div > menu > div, +form[role="dialog"][data-type="action"] > div > menu > div { + position: absolute; + left: 0; + margin: 0 3rem; +} - form[role="dialog"][data-type="action"] > div > menu > div > button.hover, - form[role="dialog"][data-type="object"] > div > menu > div > button.hover { - transform: scale(1.2); - background-color: white; - color: #2d2d2d; - } +smart-button[type="contextmenu"] { + width: 24rem; + height: 24rem; + border-radius: 12rem; + background-color: #5f5f5f; + padding-top: 14.8rem; + padding-bottom: 5rem; + font-size: 2rem; + font-style: italic; + text-align: center; + color: white; + line-height: 2.8rem; + box-sizing: border-box; +} - form[role="dialog"][data-type="action"] > div > menu > div > button.pressed, - form[role="dialog"][data-type="object"] > div > menu > div > button.pressed { - background-color: #00caf2; - color: white; - } +smart-button[type="contextmenu"].focused { + transform: scale(1.2); + color: #2d2d2d; + background-color: white; +} - [role="dialog"][data-type="action"] > div > menu > div:last-child > button:before { - display: none; - } +smart-button[type="contextmenu"].pressed { + transform: scale(1); + background-color: #00caf2; + color: white; +} diff --git a/tv_apps/tv_shared/js/key_navigation_adapter.js b/tv_apps/tv_shared/js/key_navigation_adapter.js index 076dfd20802a..4ea4a9de4e82 100644 --- a/tv_apps/tv_shared/js/key_navigation_adapter.js +++ b/tv_apps/tv_shared/js/key_navigation_adapter.js @@ -8,13 +8,14 @@ } KeyNavigationAdapter.prototype = evt({ - init: function kna_init() { - window.addEventListener('keydown', this); - window.addEventListener('keyup', this); + init: function kna_init(targetElement) { + this._targetElement = targetElement || window; + this._targetElement.addEventListener('keydown', this); + this._targetElement.addEventListener('keyup', this); }, uninit: function kna_uninit() { - window.removeEventListener('keydown', this); - window.removeEventListener('keyup', this); + this._targetElement.removeEventListener('keydown', this); + this._targetElement.removeEventListener('keyup', this); }, handleEvent: function kna_handleEvent(evt) { diff --git a/tv_apps/tv_shared/js/simple_key_navigation.js b/tv_apps/tv_shared/js/simple_key_navigation.js index 43f5c242dad3..02ae4f28e705 100644 --- a/tv_apps/tv_shared/js/simple_key_navigation.js +++ b/tv_apps/tv_shared/js/simple_key_navigation.js @@ -19,17 +19,18 @@ 'VERTICAL': 'vertical' }); - proto.start = function skn_start(list, direction, isChild) { + proto.start = function skn_start(list, direction, options) { this.direction = direction; this.updateList(list); - this.isChild = !!isChild; - if (!isChild) { - window.addEventListener('keydown', this); + this.isChild = !!options.isChild; + this.target = options.target || window; + if (!this.isChild) { + this.target.addEventListener('keydown', this); } }; proto.stop = function skn_stop() { - window.removeEventListener('keydown', this); + this.target.removeEventListener('keydown', this); }; proto.updateList = function skn_updateList(list) {