@ucd-lib/theme-elements 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brand/ucd-theme-header/ucd-theme-header.js +4 -16
- package/brand/ucd-theme-primary-nav/ucd-theme-primary-nav.js +15 -91
- package/brand/ucd-theme-quick-links/ucd-theme-quick-links.js +5 -32
- package/brand/ucd-theme-subnav/ucd-theme-subnav.js +192 -0
- package/brand/ucd-theme-subnav/ucd-theme-subnav.tpl.js +60 -0
- package/package.json +1 -1
- package/ucdlib/ucdlib-icons/utils.js +1 -1
- package/utils/index.js +11 -3
- package/utils/nav-element.js +101 -0
- package/utils/wait.js +65 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
2
|
import {render, styles} from "./ucd-theme-header.tpl.js";
|
|
3
3
|
|
|
4
|
-
import { Mixin, MutationObserverElement, BreakPoints } from "../../utils/index.js";
|
|
4
|
+
import { Mixin, MutationObserverElement, BreakPoints, Wait } from "../../utils/index.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @class UcdThemeHeader
|
|
@@ -36,7 +36,7 @@ import { Mixin, MutationObserverElement, BreakPoints } from "../../utils/index.j
|
|
|
36
36
|
*
|
|
37
37
|
*/
|
|
38
38
|
export default class UcdThemeHeader extends Mixin(LitElement)
|
|
39
|
-
.with(MutationObserverElement, BreakPoints) {
|
|
39
|
+
.with(MutationObserverElement, BreakPoints, Wait) {
|
|
40
40
|
|
|
41
41
|
static get properties() {
|
|
42
42
|
return {
|
|
@@ -105,7 +105,7 @@ export default class UcdThemeHeader extends Mixin(LitElement)
|
|
|
105
105
|
|
|
106
106
|
this.opened = true;
|
|
107
107
|
this._transitioning = true;
|
|
108
|
-
await this.
|
|
108
|
+
await this.waitForAnimation();
|
|
109
109
|
this._transitioning = false;
|
|
110
110
|
return true;
|
|
111
111
|
|
|
@@ -121,7 +121,7 @@ export default class UcdThemeHeader extends Mixin(LitElement)
|
|
|
121
121
|
|
|
122
122
|
this.opened = false;
|
|
123
123
|
this._transitioning = true;
|
|
124
|
-
await this.
|
|
124
|
+
await this.waitForAnimation();
|
|
125
125
|
this._transitioning = false;
|
|
126
126
|
return true;
|
|
127
127
|
|
|
@@ -215,18 +215,6 @@ export default class UcdThemeHeader extends Mixin(LitElement)
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
/**
|
|
219
|
-
* @method _waitForAnimation
|
|
220
|
-
* @private
|
|
221
|
-
* @description Wait for time designated for open/close animation
|
|
222
|
-
* @returns {Promise}
|
|
223
|
-
*/
|
|
224
|
-
async _waitForAnimation() {
|
|
225
|
-
return new Promise(resolve => {
|
|
226
|
-
setTimeout(resolve, this._animationDuration);
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
218
|
}
|
|
231
219
|
|
|
232
220
|
customElements.define('ucd-theme-header', UcdThemeHeader);
|
|
@@ -4,7 +4,7 @@ import { styleMap } from 'lit/directives/style-map.js';
|
|
|
4
4
|
import { classMap } from 'lit/directives/class-map.js';
|
|
5
5
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
6
6
|
|
|
7
|
-
import { Mixin, MutationObserverElement, BreakPoints } from "../../utils/index.js";
|
|
7
|
+
import { Mixin, MutationObserverElement, BreakPoints, NavElement } from "../../utils/index.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @class UcdThemePrimaryNav
|
|
@@ -33,7 +33,7 @@ import { Mixin, MutationObserverElement, BreakPoints } from "../../utils/index.j
|
|
|
33
33
|
* </ucd-theme-primary-nav>
|
|
34
34
|
*/
|
|
35
35
|
export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
36
|
-
.with(MutationObserverElement, BreakPoints) {
|
|
36
|
+
.with(NavElement, MutationObserverElement, BreakPoints) {
|
|
37
37
|
|
|
38
38
|
static get properties() {
|
|
39
39
|
return {
|
|
@@ -58,8 +58,6 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
58
58
|
this.styleModifiers = "";
|
|
59
59
|
this.hoverDelay = 300;
|
|
60
60
|
this.animationDuration = 300;
|
|
61
|
-
this.navItems = [];
|
|
62
|
-
this.maxDepth = 2;
|
|
63
61
|
|
|
64
62
|
this._classPrefix = "primary-nav";
|
|
65
63
|
this._acceptedNavTypes = ['superfish', 'mega'];
|
|
@@ -108,14 +106,14 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
108
106
|
navItem.isTransitioning = true;
|
|
109
107
|
|
|
110
108
|
// Get expanded height
|
|
111
|
-
navItem.
|
|
112
|
-
navItem.
|
|
109
|
+
navItem.inlineStyles.display = "block";
|
|
110
|
+
navItem.inlineStyles.height = 0 + "px";
|
|
113
111
|
this.requestUpdate();
|
|
114
112
|
await this.updateComplete;
|
|
115
113
|
const expandedHeight = ul.scrollHeight + "px";
|
|
116
114
|
|
|
117
115
|
// Set expanded height
|
|
118
|
-
navItem.
|
|
116
|
+
navItem.inlineStyles.height = expandedHeight;
|
|
119
117
|
this.requestUpdate();
|
|
120
118
|
await this.updateComplete;
|
|
121
119
|
|
|
@@ -131,7 +129,7 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
131
129
|
return;
|
|
132
130
|
}
|
|
133
131
|
|
|
134
|
-
this.
|
|
132
|
+
this.clearItemInlineStyles(navItem);
|
|
135
133
|
if ( navItem.isClosing ) {
|
|
136
134
|
navItem.isClosing = false;
|
|
137
135
|
this.requestUpdate();
|
|
@@ -171,15 +169,15 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
171
169
|
navItem.isTransitioning = true;
|
|
172
170
|
|
|
173
171
|
// Set expanded height
|
|
174
|
-
navItem.
|
|
175
|
-
navItem.
|
|
172
|
+
navItem.inlineStyles.height = ul.scrollHeight + "px";
|
|
173
|
+
navItem.inlineStyles.display = "block";
|
|
176
174
|
this.requestUpdate();
|
|
177
175
|
await this.updateComplete;
|
|
178
176
|
|
|
179
177
|
// Set height to 0 by requesting all of the animation frames :-(
|
|
180
178
|
requestAnimationFrame(() => {
|
|
181
179
|
requestAnimationFrame(() => {
|
|
182
|
-
navItem.
|
|
180
|
+
navItem.inlineStyles.height = "0px";
|
|
183
181
|
this.requestUpdate();
|
|
184
182
|
|
|
185
183
|
requestAnimationFrame(() => {
|
|
@@ -200,7 +198,7 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
200
198
|
}
|
|
201
199
|
|
|
202
200
|
|
|
203
|
-
this.
|
|
201
|
+
this.clearItemInlineStyles(navItem);
|
|
204
202
|
if ( navItem.timeout ) clearTimeout(navItem.timeout);
|
|
205
203
|
if ( !navItem.isOpen ) return;
|
|
206
204
|
|
|
@@ -234,22 +232,6 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
234
232
|
});
|
|
235
233
|
}
|
|
236
234
|
|
|
237
|
-
/**
|
|
238
|
-
* @method clearMobileAnimationStyles
|
|
239
|
-
* @description Removes inline styles on a nav element (used for mobile transition animation)
|
|
240
|
-
* @param {Object} navItem - Member of the this.navItems array
|
|
241
|
-
*/
|
|
242
|
-
clearMobileAnimationStyles(navItem){
|
|
243
|
-
if (
|
|
244
|
-
navItem &&
|
|
245
|
-
navItem.mobileStyles &&
|
|
246
|
-
Object.keys(navItem.mobileStyles).length > 0
|
|
247
|
-
) {
|
|
248
|
-
navItem.mobileStyles = {};
|
|
249
|
-
this.requestUpdate();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
235
|
/**
|
|
254
236
|
* @method isMegaMenu
|
|
255
237
|
* @description Does this element use the mega menu?
|
|
@@ -260,20 +242,6 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
260
242
|
return false;
|
|
261
243
|
}
|
|
262
244
|
|
|
263
|
-
/**
|
|
264
|
-
* @method getNavItem
|
|
265
|
-
* @description Retrieves an item from the navItems array.
|
|
266
|
-
* @param {Array} location - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
|
|
267
|
-
* @returns {Object}
|
|
268
|
-
*/
|
|
269
|
-
getNavItem(location){
|
|
270
|
-
let accessor = "this.navItems";
|
|
271
|
-
if ( location && location.length > 0) {
|
|
272
|
-
accessor += "[" + location.join("].subItems[") + "]";
|
|
273
|
-
}
|
|
274
|
-
return eval(accessor);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
245
|
/**
|
|
278
246
|
* @method _getNavClasses
|
|
279
247
|
* @private
|
|
@@ -299,42 +267,10 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
299
267
|
* Sets the 'navItems' property.
|
|
300
268
|
*/
|
|
301
269
|
_onChildListMutation(){
|
|
302
|
-
|
|
303
|
-
let navItems = children.map((child) => this._makeNavItemTree(child)).filter(navItem => navItem.linkText);
|
|
270
|
+
let navItems = this.parseNavChildren();
|
|
304
271
|
if ( navItems.length ) this.navItems = navItems;
|
|
305
272
|
}
|
|
306
273
|
|
|
307
|
-
/**
|
|
308
|
-
* @method _makeNavItemTree
|
|
309
|
-
* @private
|
|
310
|
-
* @description Extracts menu item data from DOM Element
|
|
311
|
-
* @param {Element} ele - Element
|
|
312
|
-
* @returns {Object} Formatted object describing the menu item and its children
|
|
313
|
-
*/
|
|
314
|
-
_makeNavItemTree(ele){
|
|
315
|
-
let linkText, href, subItems = [], isOpen=false, mobileStyles={};
|
|
316
|
-
if ( ele.tagName === 'LI' && ele.children.length > 0) ele = ele.children[0];
|
|
317
|
-
|
|
318
|
-
if ( ele.tagName === 'A' ) {
|
|
319
|
-
linkText = ele.innerText;
|
|
320
|
-
href = ele.href;
|
|
321
|
-
} else if ( ele.tagName === 'LI' ) {
|
|
322
|
-
linkText = ele.innerText;
|
|
323
|
-
} else if ( ele.tagName === 'OL' || ele.tagName === 'UL' ) {
|
|
324
|
-
linkText = ele.getAttribute('link-text');
|
|
325
|
-
href = ele.getAttribute('href');
|
|
326
|
-
|
|
327
|
-
for (const child of Array.from(ele.children)) {
|
|
328
|
-
let childItem = this._makeNavItemTree(child);
|
|
329
|
-
if ( childItem.linkText ) subItems.push(childItem);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if ( linkText ) linkText = linkText.trim();
|
|
334
|
-
return {linkText, href, subItems, isOpen, mobileStyles};
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
274
|
/**
|
|
339
275
|
* @method _renderNavItem
|
|
340
276
|
* @private
|
|
@@ -347,7 +283,7 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
347
283
|
const depth = location.length - 1;
|
|
348
284
|
|
|
349
285
|
// Render item and its subnav
|
|
350
|
-
if ( this.
|
|
286
|
+
if ( this.itemHasSubNav(navItem) && depth < this.maxDepth) {
|
|
351
287
|
return html`
|
|
352
288
|
<li
|
|
353
289
|
id="nav--${location.join("-")}"
|
|
@@ -561,7 +497,7 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
561
497
|
*/
|
|
562
498
|
_completeMobileTransition(navItem){
|
|
563
499
|
navItem.timeout = setTimeout(() => {
|
|
564
|
-
navItem.
|
|
500
|
+
navItem.inlineStyles = {};
|
|
565
501
|
navItem.isOpen = !navItem.isOpen;
|
|
566
502
|
navItem.isTransitioning = false;
|
|
567
503
|
this.requestUpdate();
|
|
@@ -630,18 +566,6 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
630
566
|
|
|
631
567
|
}
|
|
632
568
|
|
|
633
|
-
/**
|
|
634
|
-
* @method _hasSubNav
|
|
635
|
-
* @private
|
|
636
|
-
* @description Utility function for determining if a menu has subitems
|
|
637
|
-
* @param {Object} navItem - A member of the navItems array.
|
|
638
|
-
* @returns {Boolean}
|
|
639
|
-
*/
|
|
640
|
-
_hasSubNav(navItem){
|
|
641
|
-
if ( navItem && navItem.subItems && navItem.subItems.length) return true;
|
|
642
|
-
return false;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
569
|
/**
|
|
646
570
|
* @method _getItemMobileStyles
|
|
647
571
|
* @private
|
|
@@ -652,8 +576,8 @@ export default class UcdThemePrimaryNav extends Mixin(LitElement)
|
|
|
652
576
|
_getItemMobileStyles(location) {
|
|
653
577
|
if ( this.isDesktop() ) return {};
|
|
654
578
|
let navItem = this.getNavItem(location);
|
|
655
|
-
if ( !navItem.
|
|
656
|
-
return navItem.
|
|
579
|
+
if ( !navItem.inlineStyles ) return {};
|
|
580
|
+
return navItem.inlineStyles;
|
|
657
581
|
}
|
|
658
582
|
|
|
659
583
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LitElement, html } from 'lit';
|
|
2
2
|
import {render, styles} from "./ucd-theme-quick-links.tpl.js";
|
|
3
3
|
|
|
4
|
-
import { Mixin, BreakPoints } from "../../utils/index.js";
|
|
4
|
+
import { Mixin, MutationObserverElement, BreakPoints, Wait } from "../../utils/index.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @class UcdThemeQuickLinks
|
|
@@ -18,7 +18,7 @@ import { Mixin, BreakPoints } from "../../utils/index.js";
|
|
|
18
18
|
* @property {Number} animationDuration - Length of animation when opening/closing menu
|
|
19
19
|
*/
|
|
20
20
|
export default class UcdThemeQuickLinks extends Mixin(LitElement)
|
|
21
|
-
.with(BreakPoints) {
|
|
21
|
+
.with(MutationObserverElement, BreakPoints, Wait) {
|
|
22
22
|
|
|
23
23
|
static get properties() {
|
|
24
24
|
return {
|
|
@@ -67,7 +67,7 @@ export default class UcdThemeQuickLinks extends Mixin(LitElement)
|
|
|
67
67
|
this._openedHeight = this.renderRoot.getElementById('menu').scrollHeight + "px";
|
|
68
68
|
await this.updateComplete;
|
|
69
69
|
|
|
70
|
-
await this.
|
|
70
|
+
await this.waitForAnimation();
|
|
71
71
|
this._transitioning = false;
|
|
72
72
|
this.opened = true;
|
|
73
73
|
return true;
|
|
@@ -84,11 +84,11 @@ export default class UcdThemeQuickLinks extends Mixin(LitElement)
|
|
|
84
84
|
|
|
85
85
|
this._openedHeight = this.renderRoot.getElementById('menu').scrollHeight + "px";
|
|
86
86
|
await this.updateComplete;
|
|
87
|
-
await this.
|
|
87
|
+
await this.waitForFrames(2);
|
|
88
88
|
this._openedHeight = 0;
|
|
89
89
|
await this.updateComplete;
|
|
90
90
|
|
|
91
|
-
await this.
|
|
91
|
+
await this.waitForAnimation();
|
|
92
92
|
|
|
93
93
|
this._transitioning = false;
|
|
94
94
|
this.opened = false;
|
|
@@ -262,33 +262,6 @@ export default class UcdThemeQuickLinks extends Mixin(LitElement)
|
|
|
262
262
|
return html``;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
/**
|
|
266
|
-
* @method _waitForAnimation
|
|
267
|
-
* @private
|
|
268
|
-
* @description Wait for time designated for open/close animation
|
|
269
|
-
* @returns {Promise}
|
|
270
|
-
*/
|
|
271
|
-
async _waitForAnimation() {
|
|
272
|
-
return new Promise(resolve => {
|
|
273
|
-
setTimeout(resolve, this.animationDuration);
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* @method _waitForFrames
|
|
279
|
-
* @private
|
|
280
|
-
* @description Wait for specified number of animation frames
|
|
281
|
-
* @param {Number} ct Number of frames
|
|
282
|
-
*/
|
|
283
|
-
async _waitForFrames(ct=1) {
|
|
284
|
-
for (let i = 0; i < ct; i++) {
|
|
285
|
-
await new Promise(resolve => {
|
|
286
|
-
requestAnimationFrame(resolve);
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
265
|
}
|
|
293
266
|
|
|
294
267
|
customElements.define('ucd-theme-quick-links', UcdThemeQuickLinks);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { LitElement, html } from 'lit';
|
|
2
|
+
import {render, styles} from "./ucd-theme-subnav.tpl.js";
|
|
3
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
4
|
+
|
|
5
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
6
|
+
|
|
7
|
+
import {Mixin, MutationObserverElement, NavElement, Wait} from "../../utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class UcdThemeSubnav
|
|
11
|
+
* @classdesc Component class for displaying a subnav
|
|
12
|
+
*
|
|
13
|
+
* Patternlab url:
|
|
14
|
+
* - http://dev.webstyleguide.ucdavis.edu/redesign/?p=molecules-sub-nav
|
|
15
|
+
* - http://dev.webstyleguide.ucdavis.edu/redesign/?p=molecules-sub-nav-linked-title
|
|
16
|
+
*
|
|
17
|
+
* @property {String} navTitle - Subnav header text
|
|
18
|
+
* @property {String} titleHref - Link for subnav header (optional)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <ucd-theme-subnav nav-title="A subnav">
|
|
22
|
+
* <li><a href="#">Link 1</a></li>
|
|
23
|
+
* <li><a href="#">Link 2</a></li>
|
|
24
|
+
* <ul link-text="Link with Children" href="#">
|
|
25
|
+
* <li><a href="#">Child 1</a></li>
|
|
26
|
+
* <li><a href="#">Child 2</a></li>
|
|
27
|
+
* <li><a href="#">Child 3</a></li>
|
|
28
|
+
* </ul>
|
|
29
|
+
* </ucd-theme-subnav>
|
|
30
|
+
*/
|
|
31
|
+
export default class UcdThemeSubnav extends Mixin(LitElement)
|
|
32
|
+
.with(NavElement, MutationObserverElement, Wait) {
|
|
33
|
+
|
|
34
|
+
static get properties() {
|
|
35
|
+
return {
|
|
36
|
+
navTitle: {type: String, attribute: "nav-title"},
|
|
37
|
+
titleHref: {type: String, attribute: "title-href"},
|
|
38
|
+
navItems: {type: Array},
|
|
39
|
+
animationDuration: {type: Number, attribute: "animation-duration"}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static get styles() {
|
|
44
|
+
return styles();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
super();
|
|
49
|
+
this.render = render.bind(this);
|
|
50
|
+
|
|
51
|
+
this.navTitle = "";
|
|
52
|
+
this.titleHref = "";
|
|
53
|
+
this.animationDuration = 300;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @method openNavItem
|
|
58
|
+
* @description Shows children of a nav item (if applicable)
|
|
59
|
+
* @param {Array} navLocation - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
|
|
60
|
+
* @returns {Boolean}
|
|
61
|
+
*/
|
|
62
|
+
async openNavItem(navLocation){
|
|
63
|
+
let navItem = this.getNavItem(navLocation);
|
|
64
|
+
if ( !navItem || navItem.isTransitioning ) return false;
|
|
65
|
+
|
|
66
|
+
let navEle = this.renderRoot.getElementById(`nav--${navLocation.join("-")}`);
|
|
67
|
+
if ( !navEle ) return false;
|
|
68
|
+
let navUL = navEle.querySelector('ul');
|
|
69
|
+
if ( !navUL ) return false;
|
|
70
|
+
navItem.isTransitioning = true;
|
|
71
|
+
|
|
72
|
+
// Get expanded height
|
|
73
|
+
navItem.inlineStyles.display = "block";
|
|
74
|
+
navItem.inlineStyles.height = "0px";
|
|
75
|
+
await this.waitForUpdate();
|
|
76
|
+
const expandedHeight = navUL.scrollHeight + "px";
|
|
77
|
+
|
|
78
|
+
// Set expanded height
|
|
79
|
+
navItem.inlineStyles.height = expandedHeight;
|
|
80
|
+
await this.waitForUpdate();
|
|
81
|
+
|
|
82
|
+
// Complete animation
|
|
83
|
+
await this.waitForAnimation();
|
|
84
|
+
navItem.inlineStyles = {};
|
|
85
|
+
navItem.isOpen = true;
|
|
86
|
+
navItem.isTransitioning = false;
|
|
87
|
+
this.requestUpdate();
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @method closeNavItem
|
|
94
|
+
* @description Hides children of a nav item (if applicable)
|
|
95
|
+
* @param {Array} navLocation - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
|
|
96
|
+
* @returns {Boolean}
|
|
97
|
+
*/
|
|
98
|
+
async closeNavItem(navLocation){
|
|
99
|
+
let navItem = this.getNavItem(navLocation);
|
|
100
|
+
if ( !navItem || navItem.isTransitioning ) return false;
|
|
101
|
+
|
|
102
|
+
let navEle = this.renderRoot.getElementById(`nav--${navLocation.join("-")}`);
|
|
103
|
+
if ( !navEle ) return false;
|
|
104
|
+
let navUL = navEle.querySelector('ul');
|
|
105
|
+
if ( !navUL ) return false;
|
|
106
|
+
navItem.isTransitioning = true;
|
|
107
|
+
|
|
108
|
+
// Set expanded height
|
|
109
|
+
navItem.inlineStyles.height = navUL.scrollHeight + "px";
|
|
110
|
+
navItem.inlineStyles.display = "block";
|
|
111
|
+
await this.waitForUpdate();
|
|
112
|
+
|
|
113
|
+
// Set height to zero
|
|
114
|
+
await this.waitForFrames(2);
|
|
115
|
+
navItem.inlineStyles.height = "0px";
|
|
116
|
+
await this.waitForUpdate();
|
|
117
|
+
|
|
118
|
+
// Complete animation
|
|
119
|
+
await this.waitForAnimation();
|
|
120
|
+
navItem.inlineStyles = {};
|
|
121
|
+
navItem.isOpen = false;
|
|
122
|
+
navItem.isTransitioning = false;
|
|
123
|
+
this.requestUpdate();
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @method _onChildListMutation
|
|
131
|
+
* @private
|
|
132
|
+
* @description Fires when light dom child list changes. Injected by MutationObserverElement mixin.
|
|
133
|
+
* Sets the 'navItems' property.
|
|
134
|
+
*/
|
|
135
|
+
_onChildListMutation(){
|
|
136
|
+
let navItems = this.parseNavChildren();
|
|
137
|
+
if ( navItems.length ) this.navItems = navItems;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @method _renderNavItem
|
|
142
|
+
* @private
|
|
143
|
+
* @description Renders a menu item and all its children to the specified max depth
|
|
144
|
+
* @param {Object} item - An item from the 'navItems' element property
|
|
145
|
+
* @param {Array} location - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4]
|
|
146
|
+
* @returns {TemplateResult}
|
|
147
|
+
*/
|
|
148
|
+
_renderNavItem(item, location){
|
|
149
|
+
const depth = location.length - 1;
|
|
150
|
+
|
|
151
|
+
if ( this.itemHasSubNav(item) && depth < this.maxDepth) {
|
|
152
|
+
return html`
|
|
153
|
+
<li id="nav--${location.join("-")}">
|
|
154
|
+
<div class="submenu-toggle__wrapper">
|
|
155
|
+
<a href=${ifDefined(item.href ? item.href : null)}>${item.linkText}</a>
|
|
156
|
+
<button
|
|
157
|
+
@click=${() => this._toggleItemMenu(location)}
|
|
158
|
+
class="submenu-toggle ${item.isOpen ? "submenu-toggle--open" : ""}"
|
|
159
|
+
?disabled=${item.isTransitioning}
|
|
160
|
+
aria-label="Toggle Submenu">
|
|
161
|
+
<span class="submenu-toggle__icon"></span>
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
<ul style=${styleMap(item.inlineStyles)} class="${item.isOpen ? "is-open": "" }">
|
|
165
|
+
${item.subItems.map((subItem, i) => this._renderNavItem(subItem, location.concat([i])))}
|
|
166
|
+
</ul>
|
|
167
|
+
</li>
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
return html`
|
|
171
|
+
<li id="nav--${location.join("-")}"><a href=${item.href}>${item.linkText}</a></li>
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @method _toggleItemMenu
|
|
177
|
+
* @private
|
|
178
|
+
* @description Attached to nav item button click. Shows/hides children.
|
|
179
|
+
* @param {Array} navLocation - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4]
|
|
180
|
+
*/
|
|
181
|
+
_toggleItemMenu(navLocation){
|
|
182
|
+
let navItem = this.getNavItem(navLocation);
|
|
183
|
+
if ( navItem.isOpen ) {
|
|
184
|
+
this.closeNavItem(navLocation);
|
|
185
|
+
} else {
|
|
186
|
+
this.openNavItem(navLocation);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
customElements.define('ucd-theme-subnav', UcdThemeSubnav);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
import normalizeStyles from "@ucd-lib/theme-sass/normalize.css.js";
|
|
4
|
+
import headerStyles from "@ucd-lib/theme-sass/1_base_html/_headings.css.js";
|
|
5
|
+
import formStyles from "@ucd-lib/theme-sass/1_base_html/_forms.css.js";
|
|
6
|
+
import menuStyles from "@ucd-lib/theme-sass/2_base_class/_misc.css.js";
|
|
7
|
+
import subNavStyles from "@ucd-lib/theme-sass/4_component/_nav-sub.css.js";
|
|
8
|
+
import subNavToggleStyles from "@ucd-lib/theme-sass/4_component/_submenu-toggle.css.js";
|
|
9
|
+
|
|
10
|
+
export function styles() {
|
|
11
|
+
const elementStyles = css`
|
|
12
|
+
:host {
|
|
13
|
+
display: block;
|
|
14
|
+
}
|
|
15
|
+
ul.sub-nav__menu ul {
|
|
16
|
+
display: none;
|
|
17
|
+
overflow-y: hidden;
|
|
18
|
+
visibility: visible;
|
|
19
|
+
height: auto;
|
|
20
|
+
border-top-width: 0px;
|
|
21
|
+
border-bottom-width: 0px;
|
|
22
|
+
padding-top: 0px;
|
|
23
|
+
padding-bottom: 0px;
|
|
24
|
+
}
|
|
25
|
+
ul.sub-nav__menu ul.is-open {
|
|
26
|
+
display: block;
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
normalizeStyles,
|
|
32
|
+
headerStyles,
|
|
33
|
+
formStyles,
|
|
34
|
+
menuStyles,
|
|
35
|
+
subNavStyles,
|
|
36
|
+
subNavToggleStyles,
|
|
37
|
+
elementStyles
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function render() {
|
|
42
|
+
return html`
|
|
43
|
+
<style>
|
|
44
|
+
ul.sub-nav__menu ul {
|
|
45
|
+
transition: height ${this.animationDuration + "ms"};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
</style>
|
|
49
|
+
<nav class="sub-nav">
|
|
50
|
+
${this.navTitle ? html`
|
|
51
|
+
<h2 class="sub-nav__title${this.titleHref ? "-linked" : ""}">
|
|
52
|
+
${this.titleHref ? html`<a href=${this.titleHref}>${this.navTitle}</a>` : this.navTitle}
|
|
53
|
+
</h2>
|
|
54
|
+
` : html``}
|
|
55
|
+
<ul class="sub-nav__menu">
|
|
56
|
+
${this.navItems.map((item, i) => this._renderNavItem(item, [i]))}
|
|
57
|
+
</ul>
|
|
58
|
+
</nav>
|
|
59
|
+
|
|
60
|
+
`;}
|
package/package.json
CHANGED
package/utils/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import Mixin from './mixin.js';
|
|
2
|
-
import {MutationObserverElement} from './mutation-observer.js';
|
|
3
|
-
import {MainDomElement} from './main-dom-element.js';
|
|
2
|
+
import { MutationObserverElement } from './mutation-observer.js';
|
|
3
|
+
import { MainDomElement } from './main-dom-element.js';
|
|
4
4
|
import { BreakPoints } from './break-points.js';
|
|
5
|
+
import { NavElement } from './nav-element.js';
|
|
6
|
+
import { Wait } from './wait.js';
|
|
5
7
|
|
|
6
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
Mixin,
|
|
10
|
+
MutationObserverElement,
|
|
11
|
+
MainDomElement,
|
|
12
|
+
BreakPoints,
|
|
13
|
+
Wait,
|
|
14
|
+
NavElement};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @function NavElement
|
|
3
|
+
* @param {Class} superClass - LitElement or child class.
|
|
4
|
+
* @description Adds utilities for navigation to a LitElement
|
|
5
|
+
*
|
|
6
|
+
* @returns {Class} LitElement with Nav utilities attached
|
|
7
|
+
*/
|
|
8
|
+
const NavElement = (superClass) => class extends superClass {
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.navItems = [];
|
|
13
|
+
this.maxDepth = 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @method parseChildren
|
|
18
|
+
* @description Creates a tree-like nav Array structure from element children
|
|
19
|
+
* @param {HTMLCollection} children - Element children (non-shadow)
|
|
20
|
+
* @returns {Array}
|
|
21
|
+
*/
|
|
22
|
+
parseNavChildren( children=this.children ){
|
|
23
|
+
if ( !children ) return [];
|
|
24
|
+
children = Array.from(this.children);
|
|
25
|
+
let navItems = children.map((child) => this._makeNavItemTree(child)).filter(navItem => navItem.linkText);
|
|
26
|
+
return navItems;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @method _makeNavItemTree
|
|
31
|
+
* @private
|
|
32
|
+
* @description Extracts menu item data from DOM Element
|
|
33
|
+
* @param {Element} ele - Element
|
|
34
|
+
* @returns {Object} Formatted object describing the menu item and its children
|
|
35
|
+
*/
|
|
36
|
+
_makeNavItemTree(ele){
|
|
37
|
+
let linkText, href, subItems = [], isOpen=false, inlineStyles={};
|
|
38
|
+
if ( ele.tagName === 'LI' && ele.children.length > 0) ele = ele.children[0];
|
|
39
|
+
|
|
40
|
+
if ( ele.tagName === 'A' ) {
|
|
41
|
+
linkText = ele.innerText;
|
|
42
|
+
href = ele.href;
|
|
43
|
+
} else if ( ele.tagName === 'LI' ) {
|
|
44
|
+
linkText = ele.innerText;
|
|
45
|
+
} else if ( ele.tagName === 'OL' || ele.tagName === 'UL' ) {
|
|
46
|
+
linkText = ele.getAttribute('link-text');
|
|
47
|
+
href = ele.getAttribute('href');
|
|
48
|
+
|
|
49
|
+
for (const child of Array.from(ele.children)) {
|
|
50
|
+
let childItem = this._makeNavItemTree(child);
|
|
51
|
+
if ( childItem.linkText ) subItems.push(childItem);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if ( linkText ) linkText = linkText.trim();
|
|
56
|
+
return {linkText, href, subItems, isOpen, inlineStyles};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @method getNavItem
|
|
61
|
+
* @description Retrieves an item from the navItems array.
|
|
62
|
+
* @param {Array} location - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
|
|
63
|
+
* @returns {Object}
|
|
64
|
+
*/
|
|
65
|
+
getNavItem(location){
|
|
66
|
+
let accessor = "this.navItems";
|
|
67
|
+
if ( location && location.length > 0) {
|
|
68
|
+
accessor += "[" + location.join("].subItems[") + "]";
|
|
69
|
+
}
|
|
70
|
+
return eval(accessor);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @method itemHasSubNav
|
|
75
|
+
* @description Utility function for determining if a menu has subitems
|
|
76
|
+
* @param {Object} navItem - A member of the navItems array.
|
|
77
|
+
* @returns {Boolean}
|
|
78
|
+
*/
|
|
79
|
+
itemHasSubNav(navItem){
|
|
80
|
+
if ( navItem && navItem.subItems && navItem.subItems.length) return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @method clearMobileAnimationStyles
|
|
86
|
+
* @description Removes inline styles on a nav element (used for mobile transition animation)
|
|
87
|
+
* @param {Object} navItem - Member of the this.navItems array
|
|
88
|
+
*/
|
|
89
|
+
clearItemInlineStyles(navItem){
|
|
90
|
+
if (
|
|
91
|
+
navItem &&
|
|
92
|
+
navItem.inlineStyles &&
|
|
93
|
+
Object.keys(navItem.inlineStyles).length > 0
|
|
94
|
+
) {
|
|
95
|
+
navItem.inlineStyles = {};
|
|
96
|
+
this.requestUpdate();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export {NavElement};
|
package/utils/wait.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @function Wait
|
|
3
|
+
* @param {Class} superClass - LitElement or child class.
|
|
4
|
+
* @description Adds wait methods to Lit element
|
|
5
|
+
*
|
|
6
|
+
* @returns {Class} LitElement with Wait methods
|
|
7
|
+
*/
|
|
8
|
+
const Wait = (superClass) => class extends superClass {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @method wait
|
|
12
|
+
* @description Wait for the specified amount of time
|
|
13
|
+
* @param {Number} time - Time to wait (ms)
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
async wait(time){
|
|
17
|
+
return new Promise(resolve => {
|
|
18
|
+
setTimeout(resolve, time);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @method waitForUpdate
|
|
24
|
+
* @description Requests and waits for Lit update.
|
|
25
|
+
*/
|
|
26
|
+
async waitForUpdate(){
|
|
27
|
+
this.requestUpdate();
|
|
28
|
+
await this.updateComplete;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @method waitForFrames
|
|
33
|
+
* @description Wait for specified number of animation frames
|
|
34
|
+
* @param {Number} ct Number of frames
|
|
35
|
+
*/
|
|
36
|
+
async waitForFrames(ct=1) {
|
|
37
|
+
for (let i = 0; i < ct; i++) {
|
|
38
|
+
await new Promise(resolve => {
|
|
39
|
+
requestAnimationFrame(resolve);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @method waitForAnimation
|
|
46
|
+
* @description Wait for animation time designated by element
|
|
47
|
+
* @returns {Promise}
|
|
48
|
+
*/
|
|
49
|
+
async waitForAnimation() {
|
|
50
|
+
|
|
51
|
+
let animationDuration = 0;
|
|
52
|
+
if ( this.animationDuration ) {
|
|
53
|
+
animationDuration = this.animationDuration;
|
|
54
|
+
} else if (this._animationDuration) {
|
|
55
|
+
animationDuration = this._animationDuration;
|
|
56
|
+
} else {
|
|
57
|
+
console.warn("animationDuration property not set!");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new Promise(resolve => {
|
|
61
|
+
setTimeout(resolve, animationDuration);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
export {Wait};
|