@ucd-lib/theme-elements 0.0.1 → 0.0.5

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.
Files changed (49) hide show
  1. package/{ucd-theme-alert → brand/ucd-theme-alert}/ucd-theme-alert.js +0 -0
  2. package/{ucd-theme-alert → brand/ucd-theme-alert}/ucd-theme-alert.tpl.js +0 -0
  3. package/{ucd-theme-collapse → brand/ucd-theme-collapse}/ucd-theme-collapse.js +20 -21
  4. package/{ucd-theme-collapse → brand/ucd-theme-collapse}/ucd-theme-collapse.tpl.js +1 -1
  5. package/brand/ucd-theme-header/ucd-theme-header.js +268 -0
  6. package/brand/ucd-theme-header/ucd-theme-header.tpl.js +146 -0
  7. package/{ucd-theme-image-gallery → brand/ucd-theme-image-gallery}/ucd-theme-image-gallery.js +0 -0
  8. package/{ucd-theme-image-gallery → brand/ucd-theme-image-gallery}/ucd-theme-image-gallery.tpl.js +0 -0
  9. package/{ucd-theme-list-accordion → brand/ucd-theme-list-accordion}/ucd-theme-list-accordion.js +47 -44
  10. package/{ucd-theme-list-accordion → brand/ucd-theme-list-accordion}/ucd-theme-list-accordion.tpl.js +2 -2
  11. package/{ucd-theme-message-area → brand/ucd-theme-message-area}/ucd-theme-message-area.js +0 -0
  12. package/{ucd-theme-message-area → brand/ucd-theme-message-area}/ucd-theme-message-area.tpl.js +0 -0
  13. package/brand/ucd-theme-pagination/ucd-theme-pagination.js +284 -0
  14. package/brand/ucd-theme-pagination/ucd-theme-pagination.tpl.js +93 -0
  15. package/brand/ucd-theme-primary-nav/ucd-theme-primary-nav.js +589 -0
  16. package/brand/ucd-theme-primary-nav/ucd-theme-primary-nav.tpl.js +106 -0
  17. package/brand/ucd-theme-quick-links/ucd-theme-quick-links.js +269 -0
  18. package/brand/ucd-theme-quick-links/ucd-theme-quick-links.tpl.js +114 -0
  19. package/{ucd-theme-form-search/ucd-theme-form-search.js → brand/ucd-theme-search-form/ucd-theme-search-form.js} +14 -15
  20. package/{ucd-theme-form-search/ucd-theme-form-search.tpl.js → brand/ucd-theme-search-form/ucd-theme-search-form.tpl.js} +0 -0
  21. package/brand/ucd-theme-search-popup/ucd-theme-search-popup.js +91 -0
  22. package/{ucd-theme-header-search-popup/ucd-theme-header-search-popup.tpl.js → brand/ucd-theme-search-popup/ucd-theme-search-popup.tpl.js} +8 -1
  23. package/brand/ucd-theme-slim-select/ucd-theme-slim-select.js +58 -0
  24. package/brand/ucd-theme-slim-select/ucd-theme-slim-select.tpl.js +26 -0
  25. package/brand/ucd-theme-subnav/ucd-theme-subnav.js +196 -0
  26. package/brand/ucd-theme-subnav/ucd-theme-subnav.tpl.js +60 -0
  27. package/package.json +6 -4
  28. package/ucdlib/ucdlib-branding-bar/book.js +4 -0
  29. package/ucdlib/ucdlib-branding-bar/logo.js +67 -0
  30. package/ucdlib/ucdlib-branding-bar/ucdlib-branding-bar.js +101 -0
  31. package/ucdlib/ucdlib-branding-bar/ucdlib-branding-bar.tpl.js +102 -0
  32. package/ucdlib/ucdlib-icon/ucdlib-icon.js +138 -0
  33. package/ucdlib/ucdlib-icon/ucdlib-icon.tpl.js +22 -0
  34. package/ucdlib/ucdlib-icons/academic.js +154 -0
  35. package/ucdlib/ucdlib-icons/ucdlib-icons.js +78 -0
  36. package/ucdlib/ucdlib-icons/utils.js +29 -0
  37. package/ucdlib/ucdlib-iconset/ucdlib-iconset.js +170 -0
  38. package/ucdlib/ucdlib-pages/ucdlib-pages.js +150 -0
  39. package/utils/controllers/break-points.js +26 -0
  40. package/utils/controllers/index.js +11 -0
  41. package/utils/controllers/intersection-observer.js +58 -0
  42. package/utils/controllers/mutation-observer.js +52 -0
  43. package/utils/controllers/wait.js +43 -0
  44. package/utils/directives/motion-collapse.js +1 -1
  45. package/utils/mixins/index.js +8 -0
  46. package/utils/mixins/main-dom-element.js +23 -0
  47. package/utils/mixins/mixin.js +21 -0
  48. package/utils/mixins/nav-element.js +103 -0
  49. package/ucd-theme-header-search-popup/ucd-theme-header-search-popup.js +0 -40
@@ -0,0 +1,589 @@
1
+ import { LitElement, html } from 'lit';
2
+ import {render, styles} from "./ucd-theme-primary-nav.tpl.js";
3
+ import { styleMap } from 'lit/directives/style-map.js';
4
+ import { classMap } from 'lit/directives/class-map.js';
5
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
+
7
+ import { Mixin, NavElement } from "../../utils/mixins";
8
+ import { MutationObserverController, BreakPointsController } from '../../utils/controllers';
9
+
10
+ /**
11
+ * @class UcdThemePrimaryNav
12
+ * @classdesc Component class for displaying a primary site nav
13
+ *
14
+ * Pattern Lab Url:
15
+ * - http://dev.webstyleguide.ucdavis.edu/redesign/patterns/molecules-navigation-00-primary-nav/molecules-navigation-00-primary-nav.rendered.html
16
+ * - http://dev.webstyleguide.ucdavis.edu/redesign/patterns/molecules-navigation-00-primary-nav-megamenu/molecules-navigation-00-primary-nav-megamenu.rendered.html
17
+ *
18
+ * @property {String} navType - The primary style type of the nav:
19
+ * 'superfish' - The default
20
+ * 'mega' - Hovering over any top-level link opens a single nav with all subnav links
21
+ * @property {String} styleModifiers - Apply alternate styles with a space-separated list.
22
+ * e.g. 'justify' for 'primary-nav--justify'
23
+ * @property {Number} hoverDelay - How long (ms) after hover will menu open/close
24
+ * @property {Number} animationDuration - How long (ms) for a menu to fade in/out
25
+ * @property {Number} maxDepth - Maximum number of submenus to show
26
+ *
27
+ * @example
28
+ * <ucd-theme-primary-nav>
29
+ * <a href="#">link 1</a>
30
+ * <a href="#">link 2</a>
31
+ * <ul link-title="link with subnav" href="#">
32
+ * <li><a href="#">subnav link 1</a></li>
33
+ * </ul>
34
+ * </ucd-theme-primary-nav>
35
+ */
36
+ export default class UcdThemePrimaryNav extends Mixin(LitElement)
37
+ .with(NavElement) {
38
+
39
+ mutationObserver = new MutationObserverController(this, {subtree: true, childList: true});
40
+ breakPoints = new BreakPointsController(this);
41
+
42
+ static get properties() {
43
+ return {
44
+ navType: {type: String, attribute: "nav-type"},
45
+ styleModifiers: {type: String, attribute: "style-modifiers"},
46
+ hoverDelay: {type: Number, attribute: "hover-delay"},
47
+ animationDuration: {type: Number, attribute: "animation-duration"},
48
+ navItems: {type: Array},
49
+ maxDepth: {type: Number, attribute: "max-depth"},
50
+ _megaIsOpen: {type: Boolean, state: true}
51
+ };
52
+ }
53
+
54
+ static get styles() {
55
+ return styles();
56
+ }
57
+
58
+ constructor() {
59
+ super();
60
+ this.render = render.bind(this);
61
+ this.navType = "superfish";
62
+ this.styleModifiers = "";
63
+ this.hoverDelay = 300;
64
+ this.animationDuration = 300;
65
+
66
+ this._classPrefix = "primary-nav";
67
+ this._acceptedNavTypes = ['superfish', 'mega'];
68
+ this._megaIsOpen = false;
69
+ }
70
+
71
+ /**
72
+ * @method openMegaNav
73
+ * @description Opens the meganav menu
74
+ */
75
+ openMegaNav() {
76
+ this._megaIsOpen = true;
77
+ }
78
+
79
+ /**
80
+ * @method closeMegaNav
81
+ * @description Closes the meganav menu
82
+ */
83
+ closeMegaNav(){
84
+ this._megaIsOpen = false;
85
+ }
86
+
87
+ /**
88
+ * @method openSubNav
89
+ * @description Opens the specified subnav
90
+ * @param {Array} navLocation - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
91
+ */
92
+ async openSubNav(navLocation){
93
+
94
+ // non-mega menu
95
+ if (
96
+ typeof navLocation !== 'object' ||
97
+ !Array.isArray(navLocation) ||
98
+ navLocation.length === 0
99
+ ) return;
100
+ let navItem = this.getNavItem(navLocation);
101
+ if ( !navItem ) return;
102
+
103
+ // Open on mobile
104
+ if ( this.breakPoints.isMobile() ) {
105
+ let nav = this.renderRoot.getElementById(`nav--${navLocation.join("-")}`);
106
+ if ( !nav ) return;
107
+ let ul = nav.querySelector('ul');
108
+ if ( !ul ) return;
109
+ if ( navItem.isTransitioning ) return;
110
+ navItem.isTransitioning = true;
111
+
112
+ // Get expanded height
113
+ navItem.inlineStyles.display = "block";
114
+ navItem.inlineStyles.height = 0 + "px";
115
+ this.requestUpdate();
116
+ await this.updateComplete;
117
+ const expandedHeight = ul.scrollHeight + "px";
118
+
119
+ // Set expanded height
120
+ navItem.inlineStyles.height = expandedHeight;
121
+ this.requestUpdate();
122
+ await this.updateComplete;
123
+
124
+ // Remove transition state after animation duration
125
+ this._completeMobileTransition(navItem);
126
+
127
+
128
+ // Open on desktop
129
+ } else {
130
+
131
+ // mega menu
132
+ if ( this.isMegaMenu() ){
133
+ return;
134
+ }
135
+
136
+ this.clearItemInlineStyles(navItem);
137
+ if ( navItem.isClosing ) {
138
+ navItem.isClosing = false;
139
+ this.requestUpdate();
140
+ }
141
+ if ( navItem.timeout ) clearTimeout(navItem.timeout);
142
+ if ( navItem.isOpen ) return;
143
+
144
+ navItem.timeout = setTimeout(() => {
145
+ navItem.isOpen = true;
146
+ this.requestUpdate();
147
+ }, this.hoverDelay);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * @method closeSubNav
153
+ * @description Closes a subnav given its coordinates
154
+ * @param {Array} navLocation - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
155
+ */
156
+ async closeSubNav(navLocation){
157
+
158
+ if (
159
+ typeof navLocation !== 'object' ||
160
+ !Array.isArray(navLocation) ||
161
+ navLocation.length === 0
162
+ ) return;
163
+ let navItem = this.getNavItem(navLocation);
164
+ if ( !navItem ) return;
165
+
166
+ // close on mobile
167
+ if ( this.breakPoints.isMobile() ) {
168
+ let nav = this.renderRoot.getElementById(`nav--${navLocation.join("-")}`);
169
+ if ( !nav ) return;
170
+ let ul = nav.querySelector('ul');
171
+ if ( !ul ) return;
172
+ if ( navItem.isTransitioning ) return;
173
+ navItem.isTransitioning = true;
174
+
175
+ // Set expanded height
176
+ navItem.inlineStyles.height = ul.scrollHeight + "px";
177
+ navItem.inlineStyles.display = "block";
178
+ this.requestUpdate();
179
+ await this.updateComplete;
180
+
181
+ // Set height to 0 by requesting all of the animation frames :-(
182
+ requestAnimationFrame(() => {
183
+ requestAnimationFrame(() => {
184
+ navItem.inlineStyles.height = "0px";
185
+ this.requestUpdate();
186
+
187
+ requestAnimationFrame(() => {
188
+ // Remove transition state after animation duration
189
+ this._completeMobileTransition(navItem);
190
+ });
191
+
192
+ });
193
+ });
194
+
195
+
196
+ // close on desktop
197
+ } else {
198
+
199
+ // mega menu
200
+ if ( this.isMegaMenu() ){
201
+ return;
202
+ }
203
+
204
+
205
+ this.clearItemInlineStyles(navItem);
206
+ if ( navItem.timeout ) clearTimeout(navItem.timeout);
207
+ if ( !navItem.isOpen ) return;
208
+
209
+ navItem.isClosing = true;
210
+ this.requestUpdate();
211
+ navItem.timeout = setTimeout(() => {
212
+ navItem.isOpen = false;
213
+ navItem.isClosing = false;
214
+ this.requestUpdate();
215
+ }, this.hoverDelay + this.animationDuration);
216
+ }
217
+
218
+ }
219
+
220
+ /**
221
+ * @method closeAllSubNavs
222
+ * @description Recursively closes all nav submenus within specified menu.
223
+ * @param {Array} navItems - The subItems property of any object within the 'navItems' element property.
224
+ * @param {Boolean} requestUpdate - Should an update be requested after each subnav closing?
225
+ */
226
+ closeAllSubNavs(navItems, requestUpdate=true){
227
+ if ( !navItems ) navItems = this.navItems;
228
+ navItems.forEach((navItem) => {
229
+ if ( navItem.isOpen ) {
230
+ navItem.isOpen = false;
231
+ if ( requestUpdate ) this.requestUpdate();
232
+ }
233
+ if ( navItem.subItems ) {
234
+ this.closeAllSubNavs(navItem.subItems);
235
+ }
236
+ });
237
+ }
238
+
239
+ /**
240
+ * @method isMegaMenu
241
+ * @description Does this element use the mega menu?
242
+ * @returns {Boolean}
243
+ */
244
+ isMegaMenu(){
245
+ if ( this.navType.toLowerCase().trim() === 'mega') return true;
246
+ return false;
247
+ }
248
+
249
+ /**
250
+ * @method _getNavClasses
251
+ * @private
252
+ * @description Get classes to be applied to the top-level 'nav' element
253
+ * @returns {String}
254
+ */
255
+ _getNavClasses(){
256
+ let navType = this._acceptedNavTypes[0];
257
+ if ( this._acceptedNavTypes.includes(this.navType.toLowerCase()) ) navType = this.navType;
258
+
259
+ let styleModifiers = "";
260
+ if ( this.styleModifiers ) {
261
+ styleModifiers = this.styleModifiers.split(" ").map(mod => `${this._classPrefix}--${mod}`).join(" ");
262
+ }
263
+ let megaIsOpen = this.isMegaMenu() && this._megaIsOpen ? 'is-hover' : '';
264
+ return `${this._classPrefix} ${this._classPrefix}--${navType} ${styleModifiers} ${megaIsOpen}`;
265
+ }
266
+
267
+ /**
268
+ * @method _onChildListMutation
269
+ * @private
270
+ * @description Fires when light dom child list changes. Injected by MutationObserverController.
271
+ * Sets the 'navItems' property.
272
+ */
273
+ _onChildListMutation(){
274
+ let navItems = this.parseNavChildren();
275
+ if ( navItems.length ) this.navItems = navItems;
276
+ }
277
+
278
+ /**
279
+ * @method _renderNavItem
280
+ * @private
281
+ * @description Renders a menu item and all its children to the specified max depth
282
+ * @param {Object} navItem - An item from the 'navItems' element property
283
+ * @param {Array} location - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4]
284
+ * @returns {TemplateResult}
285
+ */
286
+ _renderNavItem(navItem, location){
287
+ const depth = location.length - 1;
288
+
289
+ // Render item and its subnav
290
+ if ( this.itemHasSubNav(navItem) && depth < this.maxDepth) {
291
+ return html`
292
+ <li
293
+ id="nav--${location.join("-")}"
294
+ .key=${location}
295
+ .hasnav=${true}
296
+ @mouseenter=${this._onItemMouseenter}
297
+ @mouseleave=${this._onItemMouseleave}
298
+ class=${classMap(this._makeLiClassMap(navItem, depth))}>
299
+ <div class="submenu-toggle__wrapper ${depth === 0 ? `${this._classPrefix}__top-link` : ''}">
300
+ <a
301
+ href=${ifDefined(navItem.href ? navItem.href : null)}
302
+ tabindex=${this._setTabIndex(depth)}
303
+ @focus=${this._onItemFocus}>
304
+ ${navItem.linkText}<span class="${this._classPrefix}__submenu-indicator"></span>
305
+ </a>
306
+ <button
307
+ @click=${() => this._toggleMobileMenu(location)}
308
+ class="submenu-toggle ${navItem.isOpen ? 'submenu-toggle--open' : ''}"
309
+ ?disabled=${navItem.isTransitioning}
310
+ aria-label="Toggle Submenu">
311
+ <span class="submenu-toggle__icon"></span>
312
+ </button>
313
+ </div>
314
+ <ul class="menu ${navItem.isOpen ? "menu--open" : ""}" style=${styleMap(this._getItemMobileStyles(location))}>
315
+ ${navItem.subItems.map((subItem, i) => this._renderNavItem(subItem, location.concat([i])))}
316
+ </ul>
317
+ </li>
318
+ `;
319
+ }
320
+
321
+ // render as normal link
322
+ return html`
323
+ <li id="nav--${location.join("-")}" .key=${location} class=${classMap(this._makeLiClassMap(navItem, depth))}>
324
+ <div class="${depth === 0 ? `${this._classPrefix}__top-link`: '' }">
325
+ ${navItem.href ? html`
326
+ <a
327
+ href=${navItem.href}
328
+ @focus=${this._onItemFocus}
329
+ tabindex=${this._setTabIndex(depth)}>
330
+ ${navItem.linkText}</a>
331
+ ` : html`
332
+ <span class="${this._classPrefix}__nolink">${navItem.linkText}</span>
333
+ `}
334
+ </div>
335
+ </li>
336
+ `;
337
+ }
338
+
339
+ /**
340
+ * @method _setTabIndex
341
+ * @private
342
+ * @description Sets the tab index of menu links
343
+ * @param {Number} depth - Level of the menu link
344
+ * @returns {Number}
345
+ */
346
+ _setTabIndex(depth=0){
347
+ let i = 0;
348
+ if (
349
+ this.isMegaMenu() &&
350
+ depth > 0 &&
351
+ !this._megaIsOpen &&
352
+ this.breakPoints.isDesktop()
353
+ ) i = -1;
354
+
355
+ return i;
356
+ }
357
+
358
+ /**
359
+ * @method _makeLiClassMap
360
+ * @private
361
+ * @description Classes to be assigned to each LI element in the nav.
362
+ * @param {Object} navItem - An item in the navItems property.
363
+ * @param {Number} depth - Depth of the navItem
364
+ * @returns {Object}
365
+ */
366
+ _makeLiClassMap(navItem, depth=0){
367
+ let classes = {};
368
+ classes[`depth-${depth}`] = true;
369
+ if ( navItem.isOpen ) classes['sf--hover'] = true;
370
+ if ( navItem.isClosing ) classes.closing = true;
371
+ if (navItem.megaFocus) classes['mega-focus'] = true;
372
+ return classes;
373
+ }
374
+
375
+ /**
376
+ * @method _toggleMobileMenu
377
+ * @private
378
+ * @description Expands/collapses mobile subnavs with animation on user click.
379
+ * @param {Array} navLocation - Array coordinates of corresponding nav item
380
+ */
381
+ async _toggleMobileMenu(navLocation){
382
+ if ( this.breakPoints.isDesktop() ) return;
383
+ let navItem = this.getNavItem(navLocation);
384
+ if ( navItem.isOpen ) {
385
+ this.closeSubNav(navLocation);
386
+ } else {
387
+ this.openSubNav(navLocation);
388
+ }
389
+ }
390
+
391
+ /**
392
+ * @method _onNavMouseenter
393
+ * @private
394
+ * @description Attached to top-level nav element. Opens mega menu in desktop view
395
+ */
396
+ _onNavMouseenter(){
397
+ if (
398
+ this.breakPoints.isMobile() ||
399
+ !this.isMegaMenu() )
400
+ return;
401
+
402
+ if ( this._megaTimeout ) clearTimeout(this._megaTimeout);
403
+ this._megaTimeout = setTimeout(() => {
404
+ this.openMegaNav();
405
+ }, this.hoverDelay);
406
+ }
407
+
408
+ /**
409
+ * @method _onNavMouseleave
410
+ * @private
411
+ * @description Attached to top-level nav element. Closes mega menu in desktop view
412
+ */
413
+ _onNavMouseleave(){
414
+ if (
415
+ this.breakPoints.isMobile() ||
416
+ !this.isMegaMenu() )
417
+ return;
418
+
419
+ if ( this._megaTimeout ) clearTimeout(this._megaTimeout);
420
+
421
+ this._megaTimeout = setTimeout(() => {
422
+ this.closeMegaNav();
423
+ }, this.hoverDelay);
424
+ }
425
+
426
+ /**
427
+ * @method _onNavFocusin
428
+ * @private
429
+ * @description Fires when focus enters the main nav element. Used to open the meganav
430
+ */
431
+ _onNavFocusin(){
432
+ if (
433
+ this.breakPoints.isMobile() ||
434
+ !this.isMegaMenu() )
435
+ return;
436
+
437
+ if ( this._megaIsOpen ) return;
438
+ if ( this._megaTimeout ) clearTimeout(this._megaTimeout);
439
+
440
+ this._megaTimeout = setTimeout(() => {
441
+ this.openMegaNav();
442
+ }, this.hoverDelay);
443
+
444
+ }
445
+
446
+
447
+ /**
448
+ * @method _onItemMouseenter
449
+ * @private
450
+ * @description Bound to nav li items with a subnav
451
+ * @param {Event} e
452
+ */
453
+ _onItemMouseenter(e){
454
+ if ( this.breakPoints.isMobile() ) return;
455
+ this.openSubNav(e.target.key);
456
+ }
457
+
458
+ /**
459
+ * @method _onItemFocus
460
+ * @private
461
+ * @description Bound to nav a elements
462
+ * @param {Event} e
463
+ */
464
+ _onItemFocus(e){
465
+ if ( this.breakPoints.isMobile() ) return;
466
+ const LI = e.target.parentElement.parentElement;
467
+
468
+ if (LI.hasnav) {
469
+ this.openSubNav(LI.key);
470
+ }
471
+
472
+ if (this.isMegaMenu() && this._megaIsOpen) {
473
+ this._setMegaFocus(LI.key);
474
+ }
475
+ }
476
+
477
+ /**
478
+ * @method _setMegaFocus
479
+ * @private
480
+ * @description Displays custom styling to meganav item when focused to fix bug in sitefarm code.
481
+ * @param {Array} navLocation - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
482
+ */
483
+ _setMegaFocus(navLocation){
484
+ this.navItems.forEach((nav) => nav.megaFocus = false);
485
+ if (
486
+ typeof navLocation !== 'object' ||
487
+ !Array.isArray(navLocation) ||
488
+ navLocation.length < 1
489
+ ) return;
490
+ let navItem = this.getNavItem([navLocation[0]]);
491
+ navItem.megaFocus = true;
492
+ this.requestUpdate();
493
+
494
+ }
495
+
496
+ /**
497
+ * @method _completeMobileTransition
498
+ * @private
499
+ * @description Sets timeout to remove animation styles from mobile transition
500
+ * @param {Object} navItem - Member 'navItems' element property.
501
+ */
502
+ _completeMobileTransition(navItem){
503
+ navItem.timeout = setTimeout(() => {
504
+ navItem.inlineStyles = {};
505
+ navItem.isOpen = !navItem.isOpen;
506
+ navItem.isTransitioning = false;
507
+ this.requestUpdate();
508
+ }, this.animationDuration);
509
+ }
510
+
511
+ /**
512
+ * @method _onItemMouseleave
513
+ * @private
514
+ * @description Bound to nav li items with a subnav
515
+ * @param {Event} e
516
+ */
517
+ _onItemMouseleave(e){
518
+ if ( this.breakPoints.isMobile() || this.isMegaMenu() ) return;
519
+ this.closeSubNav(e.target.key);
520
+ }
521
+
522
+ /**
523
+ * @method _onNavFocusout
524
+ * @private
525
+ * @description Attached to the top-level nav element. Closes subnav if it doesn't contain focused link.
526
+ */
527
+ _onNavFocusout(){
528
+ if ( this.breakPoints.isMobile() ) return;
529
+ if ( this.isMegaMenu() ) {
530
+ if ( this._megaTimeout ) clearTimeout(this._megaTimeout);
531
+ requestAnimationFrame(() => {
532
+ const focusedEle = this.renderRoot.activeElement;
533
+ if ( focusedEle ) return;
534
+ this._megaTimeout = setTimeout(() => {
535
+ this.navItems.forEach((nav) => nav.megaFocus = false);
536
+ this.closeMegaNav();
537
+ }, this.hoverDelay);
538
+ });
539
+
540
+ } else {
541
+ requestAnimationFrame(() => {
542
+ const focusedEle = this.renderRoot.activeElement;
543
+ if ( !focusedEle ) {
544
+ this.closeAllSubNavs();
545
+ return;
546
+ }
547
+
548
+ let ele = focusedEle;
549
+ while (
550
+ ele &&
551
+ ele.tagName !== this.tagName &&
552
+ !Array.isArray(ele.key)
553
+ ){
554
+ ele = ele.parentElement;
555
+ }
556
+ if ( !ele.key ) return;
557
+ let navLocation = [...ele.key];
558
+ let currentIndex = navLocation.pop();
559
+ let navSiblings = navLocation.length == 0 ? this.navItems : this.getNavItem(navLocation).subItems;
560
+ navSiblings.forEach((sibling, i) => {
561
+ if ( i !== currentIndex) {
562
+ sibling.isOpen = false;
563
+ this.closeAllSubNavs(sibling.subItems, false);
564
+ }
565
+ });
566
+ this.requestUpdate();
567
+ });
568
+
569
+ }
570
+
571
+ }
572
+
573
+ /**
574
+ * @method _getItemMobileStyles
575
+ * @private
576
+ * @description Returns inline styles on a nav element (used for mobile transition animation)
577
+ * @param {Array} location - Coordinates of the item in the 'navItems' array. i.e. [0, 1, 4].
578
+ * @returns {Object} - Style map
579
+ */
580
+ _getItemMobileStyles(location) {
581
+ if ( this.breakPoints.isDesktop() ) return {};
582
+ let navItem = this.getNavItem(location);
583
+ if ( !navItem.inlineStyles ) return {};
584
+ return navItem.inlineStyles;
585
+ }
586
+
587
+ }
588
+
589
+ customElements.define('ucd-theme-primary-nav', UcdThemePrimaryNav);
@@ -0,0 +1,106 @@
1
+ import { html, css } from 'lit';
2
+
3
+ import normalizeStyles from "@ucd-lib/theme-sass/normalize.css.js";
4
+ import formStyles from "@ucd-lib/theme-sass/1_base_html/_forms.css.js";
5
+ import menuStyles from "@ucd-lib/theme-sass/2_base_class/_misc.css.js";
6
+ import primaryNavStyles from "@ucd-lib/theme-sass/4_component/_nav-primary.css.js";
7
+ import subNavToggleStyles from "@ucd-lib/theme-sass/4_component/_submenu-toggle.css.js";
8
+
9
+ export function styles() {
10
+ const elementStyles = css`
11
+ :host {
12
+ display: block;
13
+ }
14
+ .submenu-toggle * {
15
+ pointer-events: none;
16
+ }
17
+ button[disabled] {
18
+ pointer-events: none;
19
+ }
20
+ @media (min-width: 992px) {
21
+ nav.primary-nav--mega li.depth-0 > ul.menu {
22
+ opacity: 1;
23
+ display: block;
24
+ }
25
+
26
+ ul.menu ul.menu {
27
+ opacity: 0;
28
+ display: none;
29
+ }
30
+ ul.menu li.sf--hover > ul.menu {
31
+ display: block;
32
+ opacity: 1;
33
+ }
34
+ ul.menu li.closing > ul.menu {
35
+ display: block;
36
+ opacity: 0;
37
+ }
38
+ .mega-focus .primary-nav__top-link a,
39
+ .mega-focus .primary-nav__top-link a::before, .mega-focus
40
+ .primary-nav__top-link a::after {
41
+ background-color: rgb(255, 223, 128);
42
+ }
43
+ .mega-focus .primary-nav__top-link a:focus,
44
+ .mega-focus .primary-nav__top-link a:focus::before,
45
+ .mega-focus .primary-nav__top-link a:focus::after {
46
+ background-color: rgb(255, 191, 0);
47
+ }
48
+ .mega-focus > ul {
49
+ background-color: rgb(255, 251, 237);
50
+ }
51
+
52
+ }
53
+
54
+ @media (max-width: 991px) {
55
+ ul.menu ul.menu {
56
+ display: none;
57
+ overflow-y: hidden;
58
+ visibility: visible;
59
+ height: auto;
60
+ border-top-width: 0px;
61
+ border-bottom-width: 0px;
62
+ padding-top: 0px;
63
+ padding-bottom: 0px;
64
+ }
65
+
66
+ ul.menu ul.menu.menu--open {
67
+ display: block;
68
+ }
69
+
70
+ }
71
+ `;
72
+
73
+ return [
74
+ normalizeStyles,
75
+ formStyles,
76
+ menuStyles,
77
+ primaryNavStyles,
78
+ subNavToggleStyles,
79
+ elementStyles
80
+ ];
81
+ }
82
+
83
+ export function render() {
84
+ return html`
85
+ <style>
86
+ ul.menu ul.menu {
87
+ transition: opacity ${this.animationDuration + "ms"}, height ${this.animationDuration + "ms"};
88
+ }
89
+ ul.menu li.sf--hover > ul.menu {
90
+ transition: opacity ${this.animationDuration + "ms"} ${this.hoverDelay + "ms"}, height ${this.animationDuration + "ms"};
91
+ }
92
+
93
+ </style>
94
+ <nav
95
+ id=${this._classPrefix}
96
+ class="${this._getNavClasses()}"
97
+ @mouseenter=${this._onNavMouseenter}
98
+ @mouseleave=${this._onNavMouseleave}
99
+ @focusout=${this._onNavFocusout}
100
+ @focusin=${this._onNavFocusin}
101
+ aria-label="Main Menu">
102
+ <ul class="menu">
103
+ ${this.navItems.map((navItem, i) => this._renderNavItem(navItem, [i]))}
104
+ </ul>
105
+ </nav>
106
+ `;}