From 667361e86a670e0a70dea662db232d12f79ba951 Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Wed, 28 Feb 2018 14:52:51 -0500 Subject: [PATCH 1/7] feat(top-app-bar): Add always collapsed feature to short top app bar --- demos/top-app-bar.html | 35 ++++++++++++++++++-------- packages/mdc-top-app-bar/README.md | 16 ++++++++++++ packages/mdc-top-app-bar/foundation.js | 16 ++++++++---- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/demos/top-app-bar.html b/demos/top-app-bar.html index 2b28c32e438..29f2b168187 100644 --- a/demos/top-app-bar.html +++ b/demos/top-app-bar.html @@ -159,17 +159,21 @@ <div class="demo-controls-container"> <h3>Demo Controls</h3> + <div> + <input type="checkbox" id="rtl-checkbox"/> + <label for="rtl-checkbox">RTL</label> + </div> + <div> + <input type="checkbox" id="short-checkbox"/> + <label for="short-checkbox">Short</label> + </div> + <div> + <input type="checkbox" id="no-action-item-checkbox"/> + <label for="no-action-item-checkbox">No Action Item</label> + </div> <div> - <input type="checkbox" id="rtl-checkbox"/> - <label for="rtl-checkbox">RTL</label> - </div> - <div> - <input type="checkbox" id="short-checkbox"/> - <label for="short-checkbox">Short</label> - </div> - <div> - <input type="checkbox" id="no-action-item-checkbox"/> - <label for="no-action-item-checkbox">No Action Item</label> + <input type="checkbox" id="always-closed-checkbox" disabled/> + <label for="always-closed-checkbox">Always Closed (Short Only)</label> </div> </div> @@ -188,6 +192,7 @@ <h3>Demo Controls</h3> var shortCheckbox = document.getElementById('short-checkbox'); var noActionItemCheckbox = document.getElementById('no-action-item-checkbox'); var rtlCheckbox = document.getElementById('rtl-checkbox'); + var alwaysClosedCheckbox = document.getElementById('always-closed-checkbox'); appBarEl.addEventListener('MDCTopAppBar:nav', function() { drawer.open = true; @@ -203,7 +208,17 @@ <h3>Demo Controls</h3> if (!this.checked) { appBarEl.classList.remove('mdc-top-app-bar--short-collapsed'); appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); + alwaysClosedCheckbox.checked = false; } + + alwaysClosedCheckbox.disabled = !this.checked; + }); + + alwaysClosedCheckbox.addEventListener('change', function() { + this.checked ? appBarEl.classList.add('mdc-top-app-bar--short-collapsed') : appBarEl.classList.remove('mdc-top-app-bar--short-collapsed'); + + appBar.destroy(); + appBar = mdc.topAppBar.MDCTopAppBar.attachTo(appBarEl); }); noActionItemCheckbox.addEventListener('change', function() { diff --git a/packages/mdc-top-app-bar/README.md b/packages/mdc-top-app-bar/README.md index 2326f5b72d0..0613ead595c 100644 --- a/packages/mdc-top-app-bar/README.md +++ b/packages/mdc-top-app-bar/README.md @@ -68,6 +68,22 @@ Short top app bars should only be used with one action item: </header> ``` +Short top app bars can be configured to stay collapsed: + +```html +<header class="mdc-top-app-bar mdc-top-app-bar--short mdc-top-app-bar--short-collapsed"> + <div class="mdc-top-app-bar__row"> + <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start"> + <a href="#" class="material-icons mdc-top-app-bar__navigation-icon">menu</a> + <span class="mdc-top-app-bar__title">Title</span> + </section> + <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end" role="top-app-bar"> + <a href="#" class="material-icons mdc-top-app-bar__icon" aria-label="Bookmark this page" alt="Bookmark this page">bookmark</a> + </section> + </div> +</header> +``` + ### JavaScript ```js diff --git a/packages/mdc-top-app-bar/foundation.js b/packages/mdc-top-app-bar/foundation.js index 99b6068ee95..363dde77289 100644 --- a/packages/mdc-top-app-bar/foundation.js +++ b/packages/mdc-top-app-bar/foundation.js @@ -67,14 +67,13 @@ class MDCTopAppBarFoundation extends MDCFoundation { } init() { - this.adapter_.registerNavigationIconInteractionHandler('click', this.navClickHandler_); - const isShortTopAppBar = this.adapter_.hasClass(cssClasses.SHORT_CLASS); if (isShortTopAppBar) { - this.adapter_.registerScrollHandler(this.scrollHandler_); - this.initShortAppBar_(); + this.initShortTopAppBar_(); } + + this.adapter_.registerNavigationIconInteractionHandler('click', this.navClickHandler_); } destroy() { @@ -85,10 +84,17 @@ class MDCTopAppBarFoundation extends MDCFoundation { /** * Used to set the initial style of the short top app bar */ - initShortAppBar_() { + initShortTopAppBar_() { + const isAlwaysCollapsed = this.adapter_.hasClass(cssClasses.SHORT_COLLAPSED_CLASS); + if (this.adapter_.getTotalActionItems() > 0) { this.adapter_.addClass(cssClasses.SHORT_HAS_ACTION_ITEM_CLASS); } + + if (!isAlwaysCollapsed) { + this.adapter_.registerScrollHandler(this.scrollHandler_); + this.shortAppBarScrollHandler_(); + } } /** From 8455cfb524870fe2b9b8cb97c7435570d73237bd Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Thu, 1 Mar 2018 11:01:15 -0500 Subject: [PATCH 2/7] feat(top-app-bar): Update tests/demo --- demos/top-app-bar.html | 26 ++++++++++---------- packages/mdc-top-app-bar/foundation.js | 2 +- test/unit/mdc-top-app-bar/foundation.test.js | 16 +++++++++++- test/unit/mdc-top-app-bar/util.test.js | 12 +++++++++ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/demos/top-app-bar.html b/demos/top-app-bar.html index 29f2b168187..fe5432e943e 100644 --- a/demos/top-app-bar.html +++ b/demos/top-app-bar.html @@ -159,21 +159,21 @@ <div class="demo-controls-container"> <h3>Demo Controls</h3> - <div> - <input type="checkbox" id="rtl-checkbox"/> - <label for="rtl-checkbox">RTL</label> - </div> - <div> - <input type="checkbox" id="short-checkbox"/> - <label for="short-checkbox">Short</label> - </div> - <div> - <input type="checkbox" id="no-action-item-checkbox"/> - <label for="no-action-item-checkbox">No Action Item</label> - </div> + <div> + <input type="checkbox" id="rtl-checkbox"/> + <label for="rtl-checkbox">RTL</label> + </div> + <div> + <input type="checkbox" id="short-checkbox"/> + <label for="short-checkbox">Short</label> + </div> + <div> + <input type="checkbox" id="no-action-item-checkbox"/> + <label for="no-action-item-checkbox">No Action Item</label> + </div> <div> <input type="checkbox" id="always-closed-checkbox" disabled/> - <label for="always-closed-checkbox">Always Closed (Short Only)</label> + <label for="always-closed-checkbox">Always Closed (Short Only)‎</label> </div> </div> diff --git a/packages/mdc-top-app-bar/foundation.js b/packages/mdc-top-app-bar/foundation.js index 363dde77289..0d9be0ccfab 100644 --- a/packages/mdc-top-app-bar/foundation.js +++ b/packages/mdc-top-app-bar/foundation.js @@ -98,7 +98,7 @@ class MDCTopAppBarFoundation extends MDCFoundation { } /** - * Scroll handler for applying/removing the closed modifier class + * Scroll handler for applying/removing the collapsed modifier class * on the short top app bar. */ shortAppBarScrollHandler_() { diff --git a/test/unit/mdc-top-app-bar/foundation.test.js b/test/unit/mdc-top-app-bar/foundation.test.js index 6109c777536..b49f3aa0494 100644 --- a/test/unit/mdc-top-app-bar/foundation.test.js +++ b/test/unit/mdc-top-app-bar/foundation.test.js @@ -93,15 +93,27 @@ test('short top app bar: scroll listener is removed on destroy', () => { td.verify(mockAdapter.deregisterScrollHandler(td.matchers.isA(Function)), {times: 1}); }); +test('short top app bar: scroll listener is not registered if collapsed class exists before init', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(MDCTopAppBarFoundation.cssClasses.SHORT_CLASS)).thenReturn(true); + td.when(mockAdapter.hasClass(MDCTopAppBarFoundation.cssClasses.SHORT_COLLAPSED_CLASS)).thenReturn(true); + td.when(mockAdapter.getTotalActionItems()).thenReturn(0); + foundation.init(); + td.verify(mockAdapter.registerScrollHandler(td.matchers.anything()), {times: 0}); +}); + test('short top app bar: class is added once when page is scrolled from the top', () => { const {foundation, mockAdapter} = setupTest(); const mockRaf = createMockRaf(); td.when(mockAdapter.hasClass(MDCTopAppBarFoundation.cssClasses.SHORT_CLASS)).thenReturn(true); + td.when(mockAdapter.hasClass(MDCTopAppBarFoundation.cssClasses.SHORT_COLLAPSED_CLASS)).thenReturn(false); + td.when(mockAdapter.getTotalActionItems()).thenReturn(0); + td.when(mockAdapter.getViewportScrollY()).thenReturn(0); const {scrollHandler} = createMockHandlers(foundation, mockAdapter, mockRaf); - td.when(mockAdapter.getViewportScrollY()).thenReturn(1); + scrollHandler(); scrollHandler(); @@ -113,6 +125,8 @@ test('short top app bar: class is removed once when page is scrolled to the top' const mockRaf = createMockRaf(); td.when(mockAdapter.hasClass(MDCTopAppBarFoundation.cssClasses.SHORT_CLASS)).thenReturn(true); + td.when(mockAdapter.hasClass(MDCTopAppBarFoundation.cssClasses.SHORT_COLLAPSED_CLASS)).thenReturn(false); + td.when(mockAdapter.getTotalActionItems()).thenReturn(0); const {scrollHandler} = createMockHandlers(foundation, mockAdapter, mockRaf); // Apply the closed class diff --git a/test/unit/mdc-top-app-bar/util.test.js b/test/unit/mdc-top-app-bar/util.test.js index 585f36460d9..f223ca61322 100644 --- a/test/unit/mdc-top-app-bar/util.test.js +++ b/test/unit/mdc-top-app-bar/util.test.js @@ -40,3 +40,15 @@ test('applyPassive returns false for browsers that do not support passive event }; assert.isFalse(util.applyPassive(mockWindow, true)); }); + +test('applyPassive returns previous value when called twice without refresh', () => { + const mockWindow = { + document: { + addEventListener: function(name, method, options) { + return options.passive; + }, + }, + }; + util.applyPassive(mockWindow, true); + assert.deepEqual(util.applyPassive(mockWindow, false), {passive: true}); +}); From 8a1a96141a2a20aef0eed7ec86f77ccaa3960958 Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Thu, 1 Mar 2018 11:02:45 -0500 Subject: [PATCH 3/7] feat(top-app-bar): Update Readme --- packages/mdc-top-app-bar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mdc-top-app-bar/README.md b/packages/mdc-top-app-bar/README.md index 0613ead595c..30b8c490bff 100644 --- a/packages/mdc-top-app-bar/README.md +++ b/packages/mdc-top-app-bar/README.md @@ -68,7 +68,7 @@ Short top app bars should only be used with one action item: </header> ``` -Short top app bars can be configured to stay collapsed: +Short top app bars can be configured to stay collapsed by applying the `mdc-top-app-bar--short-collapsed` before instantiating the component : ```html <header class="mdc-top-app-bar mdc-top-app-bar--short mdc-top-app-bar--short-collapsed"> From 99b066bda58f7768288179f6d33fea7959a658a3 Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Thu, 1 Mar 2018 11:25:26 -0500 Subject: [PATCH 4/7] feat(top-app-bar): Update demo names --- demos/top-app-bar.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/demos/top-app-bar.html b/demos/top-app-bar.html index fe5432e943e..86189f8556c 100644 --- a/demos/top-app-bar.html +++ b/demos/top-app-bar.html @@ -172,8 +172,8 @@ <h3>Demo Controls</h3> <label for="no-action-item-checkbox">No Action Item</label> </div> <div> - <input type="checkbox" id="always-closed-checkbox" disabled/> - <label for="always-closed-checkbox">Always Closed (Short Only)‎</label> + <input type="checkbox" id="always-collapsed-checkbox" disabled/> + <label for="always-collapsed-checkbox">Always Collapsed (Short Only)‎</label> </div> </div> @@ -192,7 +192,7 @@ <h3>Demo Controls</h3> var shortCheckbox = document.getElementById('short-checkbox'); var noActionItemCheckbox = document.getElementById('no-action-item-checkbox'); var rtlCheckbox = document.getElementById('rtl-checkbox'); - var alwaysClosedCheckbox = document.getElementById('always-closed-checkbox'); + var alwaysCollapsedCheckbox = document.getElementById('always-collapsed-checkbox'); appBarEl.addEventListener('MDCTopAppBar:nav', function() { drawer.open = true; @@ -208,13 +208,13 @@ <h3>Demo Controls</h3> if (!this.checked) { appBarEl.classList.remove('mdc-top-app-bar--short-collapsed'); appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); - alwaysClosedCheckbox.checked = false; + alwaysCollapsedCheckbox.checked = false; } - alwaysClosedCheckbox.disabled = !this.checked; + alwaysCollapsedCheckbox.disabled = !this.checked; }); - alwaysClosedCheckbox.addEventListener('change', function() { + alwaysCollapsedCheckbox.addEventListener('change', function() { this.checked ? appBarEl.classList.add('mdc-top-app-bar--short-collapsed') : appBarEl.classList.remove('mdc-top-app-bar--short-collapsed'); appBar.destroy(); From 47317a41f8ae5df365f0fe36eb9f263aef5e5a0e Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Thu, 1 Mar 2018 16:22:47 -0500 Subject: [PATCH 5/7] feat(top-app-bar): Update for comments --- demos/top-app-bar.html | 54 ++++++++++---------- demos/top-app-bar.scss | 4 ++ packages/mdc-top-app-bar/index.js | 2 +- test/unit/mdc-top-app-bar/foundation.test.js | 2 +- test/unit/mdc-top-app-bar/util.test.js | 12 ----- 5 files changed, 33 insertions(+), 41 deletions(-) diff --git a/demos/top-app-bar.html b/demos/top-app-bar.html index 86189f8556c..d0ca4ba2dfe 100644 --- a/demos/top-app-bar.html +++ b/demos/top-app-bar.html @@ -160,16 +160,16 @@ <div class="demo-controls-container"> <h3>Demo Controls</h3> <div> - <input type="checkbox" id="rtl-checkbox"/> - <label for="rtl-checkbox">RTL</label> + <input type="checkbox" id="rtl-checkbox"/> + <label for="rtl-checkbox">RTL</label> </div> <div> - <input type="checkbox" id="short-checkbox"/> - <label for="short-checkbox">Short</label> + <input type="checkbox" id="no-action-item-checkbox"/> + <label for="no-action-item-checkbox">No Action Item</label> </div> <div> - <input type="checkbox" id="no-action-item-checkbox"/> - <label for="no-action-item-checkbox">No Action Item</label> + <input type="checkbox" id="short-checkbox"/> + <label for="short-checkbox">Short</label> </div> <div> <input type="checkbox" id="always-collapsed-checkbox" disabled/> @@ -189,15 +189,33 @@ <h3>Demo Controls</h3> var drawer = new mdc.drawer.MDCTemporaryDrawer(drawerEl); var appBar = mdc.topAppBar.MDCTopAppBar.attachTo(appBarEl); - var shortCheckbox = document.getElementById('short-checkbox'); - var noActionItemCheckbox = document.getElementById('no-action-item-checkbox'); var rtlCheckbox = document.getElementById('rtl-checkbox'); + var noActionItemCheckbox = document.getElementById('no-action-item-checkbox'); + var shortCheckbox = document.getElementById('short-checkbox'); var alwaysCollapsedCheckbox = document.getElementById('always-collapsed-checkbox'); appBarEl.addEventListener('MDCTopAppBar:nav', function() { drawer.open = true; }); + rtlCheckbox.addEventListener('change', function() { + document.body[this.checked ? 'setAttribute' : 'removeAttribute']("dir", "rtl"); + appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); + + appBar.destroy(); + appBar = mdc.topAppBar.MDCTopAppBar.attachTo(appBarEl); + }); + + noActionItemCheckbox.addEventListener('change', function() { + if (this.checked) { + rightSection.removeChild(rightItemEl); + appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); + } else { + rightSection.appendChild(rightItemEl); + appBarEl.classList.add('mdc-top-app-bar--short-has-action-item'); + } + }); + shortCheckbox.addEventListener('change', function() { appBarEl.classList[this.checked ? 'add' : 'remove']('mdc-top-app-bar--short'); appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); @@ -215,29 +233,11 @@ <h3>Demo Controls</h3> }); alwaysCollapsedCheckbox.addEventListener('change', function() { - this.checked ? appBarEl.classList.add('mdc-top-app-bar--short-collapsed') : appBarEl.classList.remove('mdc-top-app-bar--short-collapsed'); + appBarEl.classList[this.checked ? 'add' : 'remove']('mdc-top-app-bar--short-collapsed'); appBar.destroy(); appBar = mdc.topAppBar.MDCTopAppBar.attachTo(appBarEl); }); - - noActionItemCheckbox.addEventListener('change', function() { - if (this.checked) { - rightSection.removeChild(rightItemEl); - appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); - } else { - rightSection.appendChild(rightItemEl); - appBarEl.classList.add('mdc-top-app-bar--short-has-action-item'); - } - }); - - rtlCheckbox.addEventListener('change', function() { - document.body[this.checked ? 'setAttribute' : 'removeAttribute']("dir", "rtl"); - appBarEl.classList.remove('mdc-top-app-bar--short-has-action-item'); - - appBar.destroy(); - appBar = mdc.topAppBar.MDCTopAppBar.attachTo(appBarEl); - }) }); </script> </body> diff --git a/demos/top-app-bar.scss b/demos/top-app-bar.scss index c2153aa88c3..e82df46a5cd 100644 --- a/demos/top-app-bar.scss +++ b/demos/top-app-bar.scss @@ -51,3 +51,7 @@ background-color: white; padding: 10px; } + +input[type="checkbox"]:disabled + label { + opacity: .4; +} diff --git a/packages/mdc-top-app-bar/index.js b/packages/mdc-top-app-bar/index.js index 0bd9bbdf226..05b77183228 100644 --- a/packages/mdc-top-app-bar/index.js +++ b/packages/mdc-top-app-bar/index.js @@ -89,7 +89,7 @@ class MDCTopAppBar extends MDCComponent { this.emit(strings.NAVIGATION_EVENT, {}); }, registerScrollHandler: (handler) => window.addEventListener('scroll', handler, util.applyPassive()), - deregisterScrollHandler: (handler) => window.removeEventListener('scroll', handler), + deregisterScrollHandler: (handler) => window.removeEventListener('scroll', handler, util.applyPassive()), getViewportScrollY: () => window.pageYOffset, getTotalActionItems: () => this.root_.querySelectorAll(strings.ACTION_ITEM_SELECTOR).length, diff --git a/test/unit/mdc-top-app-bar/foundation.test.js b/test/unit/mdc-top-app-bar/foundation.test.js index b49f3aa0494..f6c5d8ee1d5 100644 --- a/test/unit/mdc-top-app-bar/foundation.test.js +++ b/test/unit/mdc-top-app-bar/foundation.test.js @@ -129,7 +129,7 @@ test('short top app bar: class is removed once when page is scrolled to the top' td.when(mockAdapter.getTotalActionItems()).thenReturn(0); const {scrollHandler} = createMockHandlers(foundation, mockAdapter, mockRaf); - // Apply the closed class + // Apply the collapsed class td.when(mockAdapter.getViewportScrollY()).thenReturn(1); scrollHandler(); diff --git a/test/unit/mdc-top-app-bar/util.test.js b/test/unit/mdc-top-app-bar/util.test.js index f223ca61322..585f36460d9 100644 --- a/test/unit/mdc-top-app-bar/util.test.js +++ b/test/unit/mdc-top-app-bar/util.test.js @@ -40,15 +40,3 @@ test('applyPassive returns false for browsers that do not support passive event }; assert.isFalse(util.applyPassive(mockWindow, true)); }); - -test('applyPassive returns previous value when called twice without refresh', () => { - const mockWindow = { - document: { - addEventListener: function(name, method, options) { - return options.passive; - }, - }, - }; - util.applyPassive(mockWindow, true); - assert.deepEqual(util.applyPassive(mockWindow, false), {passive: true}); -}); From 7593d0b189fc29d3f8a4f136cd6bcda9a6cbe069 Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Thu, 1 Mar 2018 16:24:12 -0500 Subject: [PATCH 6/7] feat(top-app-bar): Update readme. --- packages/mdc-top-app-bar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mdc-top-app-bar/README.md b/packages/mdc-top-app-bar/README.md index 30b8c490bff..2b2a70e22aa 100644 --- a/packages/mdc-top-app-bar/README.md +++ b/packages/mdc-top-app-bar/README.md @@ -68,7 +68,7 @@ Short top app bars should only be used with one action item: </header> ``` -Short top app bars can be configured to stay collapsed by applying the `mdc-top-app-bar--short-collapsed` before instantiating the component : +Short top app bars can be configured to always appear collapsed by applying the `mdc-top-app-bar--short-collapsed` before instantiating the component : ```html <header class="mdc-top-app-bar mdc-top-app-bar--short mdc-top-app-bar--short-collapsed"> From 12528ab1609dd72fa97c991ae2c29ef257cbc175 Mon Sep 17 00:00:00 2001 From: williamernest <williamernest@google.com> Date: Fri, 2 Mar 2018 12:57:13 -0500 Subject: [PATCH 7/7] feat(top-app-bar): Update for comments --- packages/mdc-top-app-bar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mdc-top-app-bar/index.js b/packages/mdc-top-app-bar/index.js index 05b77183228..0bd9bbdf226 100644 --- a/packages/mdc-top-app-bar/index.js +++ b/packages/mdc-top-app-bar/index.js @@ -89,7 +89,7 @@ class MDCTopAppBar extends MDCComponent { this.emit(strings.NAVIGATION_EVENT, {}); }, registerScrollHandler: (handler) => window.addEventListener('scroll', handler, util.applyPassive()), - deregisterScrollHandler: (handler) => window.removeEventListener('scroll', handler, util.applyPassive()), + deregisterScrollHandler: (handler) => window.removeEventListener('scroll', handler), getViewportScrollY: () => window.pageYOffset, getTotalActionItems: () => this.root_.querySelectorAll(strings.ACTION_ITEM_SELECTOR).length,