@brightspace-ui/core 3.219.5 → 3.219.7

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.
@@ -325,1790 +325,6 @@ border: 1px solid #cd2026;
325
325
  </tbody>
326
326
  </table>
327
327
 
328
- <h2 class="d2l-heading-3">Large Samples</h2>
329
-
330
- <pre class="d2l-code d2l-code-dark line-numbers"><code class="language-markup">&lt;!DOCTYPE html&gt;
331
- &lt;html lang="en"&gt;
332
- &lt;head&gt;
333
- &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
334
- &lt;meta charset="UTF-8"&gt;
335
- &lt;link rel="stylesheet" href="../../demo/styles.css" type="text/css"&gt;
336
- &lt;script type="module"&gt;
337
- import '../../demo/demo-page.js';
338
- import '../../button/button.js';
339
- import '../../button/button-icon.js';
340
- import '../../dropdown/dropdown-more.js';
341
- import '../../dropdown/dropdown-content.js';
342
- import '../../status-indicator/status-indicator.js';
343
- import '../../tooltip/tooltip.js';
344
- import '../card.js';
345
- import '../card-loading-shimmer.js';
346
- import '../card-content-meta.js';
347
- import '../card-content-title.js';
348
- import '../card-footer-link.js';
349
- &lt;/script&gt;
350
- &lt;style&gt;
351
- d2l-card {
352
- vertical-align: top;
353
- }
354
- .subtle-demo {
355
- background-color: #f6f7f8;
356
- padding: 20px;
357
- }
358
- .badge {
359
- text-align: center;
360
- }
361
- .badge &gt; img {
362
- background-color: white;
363
- border: 1px solid #202122; /* ferrite */
364
- border-radius: 6px;
365
- height: 70px;
366
- object-fit: cover;
367
- object-position: center;
368
- }
369
- .badge-status {
370
- background-color: white;
371
- border: 1px solid white;
372
- border-radius: 0.6rem;
373
- display: inline-block;
374
- }
375
- div[slot="footer"] {
376
- display: inline-block;
377
- }
378
- #toggleLoading {
379
- margin-top: 20px;
380
- }
381
- &lt;/style&gt;
382
- &lt;/head&gt;
383
- &lt;body unresolved&gt;
384
-
385
- &lt;d2l-demo-page page-title="d2l-card"&gt;
386
-
387
- &lt;h2&gt;Subtle Card (badges, no-link)&lt;/h2&gt;
388
-
389
- &lt;d2l-demo-snippet&gt;
390
- &lt;template&gt;
391
- &lt;div class="subtle-demo"&gt;
392
-
393
- &lt;d2l-card subtle align-center text="Image Badge" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 280px; width: 245px;"&gt;
394
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
395
- &lt;div slot="badge" class="badge"&gt;
396
- &lt;img alt="" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Stick_Figure.svg/340px-Stick_Figure.svg.png"&gt;
397
- &lt;/div&gt;
398
- &lt;div slot="content"&gt;Image Badge&lt;/div&gt;
399
- &lt;/d2l-card&gt;
400
-
401
- &lt;d2l-card subtle align-center text="Status Badge" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 280px; width: 245px;"&gt;
402
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
403
- &lt;div class="badge-status" slot="badge"&gt;
404
- &lt;d2l-status-indicator text="Success" state="success"&gt;&lt;/d2l-status-indicator&gt;
405
- &lt;/div&gt;
406
- &lt;div slot="content"&gt;Status Badge&lt;/div&gt;
407
- &lt;/d2l-card&gt;
408
-
409
- &lt;d2l-card subtle align-center text="No Link" style="height: 280px; width: 245px;"&gt;
410
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
411
- &lt;div slot="content"&gt;No Link&lt;/div&gt;
412
- &lt;/d2l-card&gt;
413
-
414
- &lt;/div&gt;
415
- &lt;/template&gt;
416
- &lt;/d2l-demo-snippet&gt;
417
-
418
- &lt;h2&gt;Subtle Card (header actions, meta-content, footer links)&lt;/h2&gt;
419
-
420
- &lt;d2l-demo-snippet&gt;
421
- &lt;template&gt;
422
- &lt;div class="subtle-demo"&gt;
423
-
424
- &lt;d2l-card subtle align-center text="Hydrology" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 300px; width: 245px;"&gt;
425
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
426
- &lt;d2l-dropdown-more slot="actions" translucent visible-on-ancestor text="Open!"&gt;
427
- &lt;d2l-dropdown-content&gt;
428
- &lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;
429
- &lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;
430
- &lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;
431
- &lt;/d2l-dropdown-content&gt;
432
- &lt;/d2l-dropdown-more&gt;
433
- &lt;d2l-button-icon slot="actions" translucent text="unpin" icon="tier1:pin-filled"&gt;&lt;/d2l-button-icon&gt;
434
- &lt;div slot="content"&gt;&lt;div&gt;Hydrology&lt;/div&gt;&lt;d2l-card-content-meta&gt;This is some extra meta data that will fill the content slot of the card.&lt;/d2l-card-content-meta&gt;&lt;/div&gt;
435
- &lt;div slot="footer"&gt;
436
- &lt;d2l-card-footer-link id="googleDriveLink1" icon="tier1:google-drive" text="Google Drive" secondary-count="100" href="https://www.google.ca/drive/"&gt;
437
- &lt;d2l-tooltip slot="tooltip" for="googleDriveLink1"&gt;Go to Google Drive&lt;/d2l-tooltip&gt;
438
- &lt;/d2l-card-footer-link&gt;
439
- &lt;d2l-card-footer-link id="rssFeedLink1" icon="tier1:rss" text="RSS Feed" secondary-count="1"&gt;
440
- &lt;d2l-tooltip slot="tooltip" for="rssFeedLink1"&gt;RSS Feed&lt;/d2l-tooltip&gt;
441
- &lt;/d2l-card-footer-link&gt;
442
- &lt;d2l-card-footer-link id="outcomesLink1" icon="tier1:outcomes" text="Outcomes" secondary-count="5"&gt;
443
- &lt;d2l-tooltip slot="tooltip" for="outcomesLink1"&gt;Outcomes&lt;/d2l-tooltip&gt;
444
- &lt;/d2l-card-footer-link&gt;
445
- &lt;d2l-card-footer-link id="assignmentsLink1" icon="tier1:assignments" text="Assignments" secondary-count="3"&gt;
446
- &lt;d2l-tooltip slot="tooltip" position="top" style="width: 100%;" for="assignmentsLink1"&gt;You have 3 assignments due tomorrow.&lt;/d2l-tooltip&gt;
447
- &lt;/d2l-card-footer-link&gt;
448
- &lt;/div&gt;
449
- &lt;/d2l-card&gt;
450
-
451
- &lt;d2l-card subtle align-center text="Grade 2" href="https://en.wikipedia.org/wiki/Second_grade" style="height: 300px; width: 245px;"&gt;
452
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-low-density-max-size.jpg"&gt;
453
- &lt;d2l-dropdown-more slot="actions" translucent visible-on-ancestor text="Open!"&gt;
454
- &lt;d2l-dropdown-content&gt;&lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;&lt;/d2l-dropdown-content&gt;
455
- &lt;/d2l-dropdown-more&gt;
456
- &lt;d2l-button-icon slot="actions" translucent text="unpin" icon="tier1:pin-filled"&gt;&lt;/d2l-button-icon&gt;
457
- &lt;div slot="content"&gt;Grade 2&lt;/div&gt;
458
- &lt;div slot="footer"&gt;
459
- &lt;d2l-card-footer-link id="googleDriveLink2" icon="tier1:google-drive" text="Google Drive" secondary-count="100" href="https://www.google.ca/drive/"&gt;
460
- &lt;d2l-tooltip slot="tooltip" for="googleDriveLink2"&gt;Go to Google Drive&lt;/d2l-tooltip&gt;
461
- &lt;/d2l-card-footer-link&gt;
462
- &lt;d2l-card-footer-link id="rssFeedLink2" icon="tier1:rss" text="RSS Feed" secondary-count="1"&gt;
463
- &lt;d2l-tooltip slot="tooltip" for="rssFeedLink2"&gt;RSS Feed&lt;/d2l-tooltip&gt;
464
- &lt;/d2l-card-footer-link&gt;
465
- &lt;d2l-card-footer-link id="outcomesLink2" icon="tier1:outcomes" text="Outcomes" secondary-count="5"&gt;
466
- &lt;d2l-tooltip slot="tooltip" for="outcomesLink2"&gt;Outcomes&lt;/d2l-tooltip&gt;
467
- &lt;/d2l-card-footer-link&gt;
468
- &lt;/div&gt;
469
- &lt;/d2l-card&gt;
470
-
471
- &lt;d2l-card subtle align-center text="Painting" href="https://en.wikipedia.org/wiki/Painting" style="height: 300px; width: 245px;"&gt;
472
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-low-density-max-size.jpg"&gt;
473
- &lt;d2l-dropdown-more slot="actions" translucent visible-on-ancestor text="Open!"&gt;
474
- &lt;d2l-dropdown-content&gt;&lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;&lt;/d2l-dropdown-content&gt;
475
- &lt;/d2l-dropdown-more&gt;
476
- &lt;d2l-button-icon slot="actions" translucent text="unpin" icon="tier1:pin-filled"&gt;&lt;/d2l-button-icon&gt;
477
- &lt;div slot="content"&gt;Painting&lt;/div&gt;
478
- &lt;d2l-button slot="footer" style="width: 100%;"&gt;Shiny Button&lt;/d2l-button&gt;
479
- &lt;/d2l-card&gt;
480
-
481
- &lt;/div&gt;
482
- &lt;/template&gt;
483
- &lt;/d2l-demo-snippet&gt;
484
-
485
- &lt;h2&gt;Card (badges, no-link)&lt;/h2&gt;
486
-
487
- &lt;d2l-demo-snippet&gt;
488
- &lt;template&gt;
489
-
490
- &lt;d2l-card align-center text="Image Badge" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 280px; width: 245px;"&gt;
491
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
492
- &lt;div slot="badge" class="badge"&gt;
493
- &lt;img alt="" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Stick_Figure.svg/340px-Stick_Figure.svg.png"&gt;
494
- &lt;/div&gt;
495
- &lt;div slot="content"&gt;Image Badge&lt;/div&gt;
496
- &lt;/d2l-card&gt;
497
-
498
- &lt;d2l-card align-center text="Status Badge" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 280px; width: 245px;"&gt;
499
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
500
- &lt;div class="badge-status" slot="badge"&gt;
501
- &lt;d2l-status-indicator text="Success" state="success"&gt;&lt;/d2l-status-indicator&gt;
502
- &lt;/div&gt;
503
- &lt;div slot="content"&gt;Status Badge&lt;/div&gt;
504
- &lt;/d2l-card&gt;
505
-
506
- &lt;d2l-card align-center text="No Link" style="height: 280px; width: 245px;"&gt;
507
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
508
- &lt;div slot="content"&gt;No Link&lt;/div&gt;
509
- &lt;/d2l-card&gt;
510
-
511
- &lt;/template&gt;
512
- &lt;/d2l-demo-snippet&gt;
513
-
514
- &lt;h2&gt;Card (header actions, meta-content, footer links)&lt;/h2&gt;
515
-
516
- &lt;d2l-demo-snippet&gt;
517
- &lt;template&gt;
518
-
519
- &lt;d2l-card align-center text="Hydrology" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 300px; width: 245px;"&gt;
520
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
521
- &lt;d2l-dropdown-more slot="actions" translucent visible-on-ancestor text="Open!"&gt;
522
- &lt;d2l-dropdown-content&gt;
523
- &lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;
524
- &lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;
525
- &lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;
526
- &lt;/d2l-dropdown-content&gt;
527
- &lt;/d2l-dropdown-more&gt;
528
- &lt;d2l-button-icon slot="actions" translucent text="unpin" icon="tier1:pin-filled"&gt;&lt;/d2l-button-icon&gt;
529
- &lt;div slot="content"&gt;&lt;div&gt;Hydrology&lt;/div&gt;&lt;d2l-card-content-meta&gt;This is some extra meta data that will fill the content slot of the card.&lt;/d2l-card-content-meta&gt;&lt;/div&gt;
530
- &lt;div slot="footer"&gt;
531
- &lt;d2l-card-footer-link id="googleDriveLink3" icon="tier1:google-drive" text="Google Drive" secondary-count="100" href="https://www.google.ca/drive/"&gt;
532
- &lt;d2l-tooltip slot="tooltip" for="googleDriveLink3"&gt;Go to Google Drive&lt;/d2l-tooltip&gt;
533
- &lt;/d2l-card-footer-link&gt;
534
- &lt;d2l-card-footer-link id="rssFeedLink3" icon="tier1:rss" text="RSS Feed" secondary-count="1"&gt;
535
- &lt;d2l-tooltip slot="tooltip" for="rssFeedLink3"&gt;RSS Feed&lt;/d2l-tooltip&gt;
536
- &lt;/d2l-card-footer-link&gt;
537
- &lt;d2l-card-footer-link id="outcomesLink3" icon="tier1:outcomes" text="Outcomes" secondary-count="5"&gt;
538
- &lt;d2l-tooltip slot="tooltip" for="outcomesLink3"&gt;Outcomes&lt;/d2l-tooltip&gt;
539
- &lt;/d2l-card-footer-link&gt;
540
- &lt;d2l-card-footer-link id="assignmentsLink3" icon="tier1:assignments" text="Assignments" secondary-count="3"&gt;
541
- &lt;d2l-tooltip slot="tooltip" position="top" style="width: 100%;" for="assignmentsLink3"&gt;You have 3 assignments due tomorrow.&lt;/d2l-tooltip&gt;
542
- &lt;/d2l-card-footer-link&gt;
543
- &lt;/div&gt;
544
- &lt;/d2l-card&gt;
545
-
546
- &lt;d2l-card align-center text="Painting" href="https://en.wikipedia.org/wiki/Painting" style="height: 300px; width: 245px;"&gt;
547
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-low-density-max-size.jpg"&gt;
548
- &lt;d2l-dropdown-more slot="actions" translucent visible-on-ancestor text="Open!"&gt;
549
- &lt;d2l-dropdown-content&gt;&lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;&lt;/d2l-dropdown-content&gt;
550
- &lt;/d2l-dropdown-more&gt;
551
- &lt;d2l-button-icon slot="actions" translucent text="unpin" icon="tier1:pin-filled"&gt;&lt;/d2l-button-icon&gt;
552
- &lt;div slot="content"&gt;Painting&lt;/div&gt;
553
- &lt;d2l-button slot="footer" style="width: 100%;"&gt;Shiny Button&lt;/d2l-button&gt;
554
- &lt;/d2l-card&gt;
555
-
556
- &lt;d2l-card align-center text="Grade 2" href="https://en.wikipedia.org/wiki/Second_grade" style="height: 300px; width: 245px;"&gt;
557
- &lt;img slot="header" alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-low-density-max-size.jpg"&gt;
558
- &lt;d2l-dropdown-more slot="actions" translucent visible-on-ancestor text="Open!"&gt;
559
- &lt;d2l-dropdown-content&gt;&lt;div&gt;This is where you could put the super cool features for your card!&lt;/div&gt;&lt;br&gt;&lt;div&gt;As with all dropdowns, you can choose between a generic dropdown container, or a menu specific one.&lt;/div&gt;&lt;/d2l-dropdown-content&gt;
560
- &lt;/d2l-dropdown-more&gt;
561
- &lt;d2l-button-icon slot="actions" translucent text="unpin" icon="tier1:pin-filled"&gt;&lt;/d2l-button-icon&gt;
562
- &lt;div slot="content"&gt;Grade 2&lt;/div&gt;
563
- &lt;div slot="footer"&gt;
564
- &lt;d2l-card-footer-link id="googleDriveLink4" icon="tier1:google-drive" text="Google Drive" secondary-count-type="count" secondary-count="100" href="https://www.google.ca/drive/"&gt;
565
- &lt;d2l-tooltip slot="tooltip" for="googleDriveLink4"&gt;Go to Google Drive&lt;/d2l-tooltip&gt;
566
- &lt;/d2l-card-footer-link&gt;
567
- &lt;d2l-card-footer-link id="rssFeedLink4" icon="tier1:rss" text="RSS Feed" secondary-count-type="count" secondary-count="1"&gt;
568
- &lt;d2l-tooltip slot="tooltip" for="rssFeedLink4"&gt;RSS Feed&lt;/d2l-tooltip&gt;
569
- &lt;/d2l-card-footer-link&gt;
570
- &lt;d2l-card-footer-link id="outcomesLink4" icon="tier1:outcomes" text="Outcomes" secondary-count-type="count" secondary-count="5"&gt;
571
- &lt;d2l-tooltip slot="tooltip" for="outcomesLink4"&gt;Outcomes&lt;/d2l-tooltip&gt;
572
- &lt;/d2l-card-footer-link&gt;
573
- &lt;/div&gt;
574
- &lt;/d2l-card&gt;
575
-
576
- &lt;/template&gt;
577
- &lt;/d2l-demo-snippet&gt;
578
-
579
- &lt;h2&gt;Card (with header loading)&lt;/h2&gt;
580
-
581
- &lt;d2l-demo-snippet&gt;
582
- &lt;template&gt;
583
-
584
- &lt;d2l-card align-center text="Hydrology" href="https://en.wikipedia.org/wiki/Hydrology" style="height: 300px; width: 245px;"&gt;
585
- &lt;d2l-card-loading-shimmer slot="header" loading style="display: block; height: 103.5px; width: 100%;"&gt;
586
- &lt;img alt="" style="display: block; width: 100%;" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg"&gt;
587
- &lt;/d2l-card-loading-shimmer&gt;
588
- &lt;div slot="content"&gt;&lt;div&gt;Hydrology&lt;/div&gt;&lt;d2l-card-content-meta&gt;This is some extra meta data that will fill the content slot of the card.&lt;/d2l-card-content-meta&gt;&lt;/div&gt;
589
- &lt;/d2l-card&gt;
590
-
591
- &lt;div&gt;
592
- &lt;d2l-button id="toggleLoading"&gt;Toggle Loading State&lt;/d2l-button&gt;
593
- &lt;/div&gt;
594
-
595
- &lt;script&gt;
596
- document.querySelector('#toggleLoading').addEventListener('click', () =&gt; {
597
- const loadingContainer = document.querySelector('d2l-card-loading-shimmer');
598
- loadingContainer.loading = !loadingContainer.loading;
599
- });
600
- &lt;/script&gt;
601
- &lt;/template&gt;
602
- &lt;/d2l-demo-snippet&gt;
603
-
604
- &lt;/d2l-demo-page&gt;
605
- &lt;/body&gt;
606
- &lt;/html&gt;
607
- </code></pre>
608
-
609
- <pre class="d2l-code d2l-code-dark line-numbers"><code class="language-javascript">import '../backdrop/backdrop.js';
610
- import '../button/button.js';
611
- import '../focus-trap/focus-trap.js';
612
- import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
613
- import { findComposedAncestor, getBoundingAncestor, isComposedAncestor, isVisible } from '../../helpers/dom.js';
614
- import { getComposedActiveElement, getFirstFocusableDescendant, getPreviousFocusableAncestor } from '../../helpers/focus.js';
615
- import { classMap } from 'lit/directives/class-map.js';
616
- import { html } from 'lit';
617
- import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
618
- import { styleMap } from 'lit/directives/style-map.js';
619
- import { tryGetIfrauBackdropService } from '../../helpers/ifrauBackdropService.js';
620
-
621
- const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
622
- const minBackdropHeightMobile = 42;
623
- const minBackdropWidthMobile = 30;
624
- const outerMarginTopBottom = 18;
625
- const defaultVerticalOffset = 16;
626
-
627
- export const DropdownContentMixin = superclass =&gt; class extends LocalizeCoreElement(superclass) {
628
-
629
- static get properties() {
630
- return {
631
- /**
632
- * Optionally align dropdown to either start or end. If not set, the dropdown will attempt be centred.
633
- * @type {'start'|'end'}
634
- */
635
- align: {
636
- type: String,
637
- reflect: true
638
- },
639
- /**
640
- * Optionally provide boundaries to where the dropdown will appear. Valid properties are "above", "below", "left", and "right".
641
- * @type {object}
642
- */
643
- boundary: {
644
- type: Object,
645
- },
646
- /**
647
- * Override default max-width (undefined). Specify a number that would be the px value.
648
- * @type {number}
649
- */
650
- maxWidth: {
651
- type: Number,
652
- reflect: true,
653
- attribute: 'max-width'
654
- },
655
- /**
656
- * Override default min-width (undefined). Specify a number that would be the px value.
657
- * @type {number}
658
- */
659
- minWidth: {
660
- type: Number,
661
- reflect: true,
662
- attribute: 'min-width'
663
- },
664
- /**
665
- * Override max-height. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.
666
- * @type {number}
667
- */
668
- maxHeight: {
669
- type: Number,
670
- attribute: 'max-height'
671
- },
672
- /**
673
- * Override the breakpoint at which mobile styling is used. Defaults to 616px.
674
- * @type {number}
675
- */
676
- mobileBreakpointOverride: {
677
- type: Number,
678
- attribute: 'mobile-breakpoint'
679
- },
680
- /**
681
- * Override default height used for required space when `no-auto-fit` is true. Specify a number that would be the px value. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.
682
- * @type {number}
683
- */
684
- minHeight: {
685
- type: Number,
686
- reflect: true,
687
- attribute: 'min-height'
688
- },
689
- /**
690
- * Opt-out of showing a close button in the footer of tray-style mobile dropdowns.
691
- * @type {boolean}
692
- */
693
- noMobileCloseButton: {
694
- type: Boolean,
695
- reflect: true,
696
- attribute: 'no-mobile-close-button'
697
- },
698
- /**
699
- * Mobile dropdown style.
700
- * @type {'left'|'right'|'bottom'}
701
- */
702
- mobileTray: {
703
- type: String,
704
- reflect: true,
705
- attribute: 'mobile-tray'
706
- },
707
- /**
708
- * Opt out of automatically closing on focus or click outside of the dropdown content
709
- * @type {boolean}
710
- */
711
- noAutoClose: {
712
- type: Boolean,
713
- reflect: true,
714
- attribute: 'no-auto-close'
715
- },
716
- /**
717
- * Opt out of auto-sizing
718
- * @type {boolean}
719
- */
720
- noAutoFit: {
721
- type: Boolean,
722
- reflect: true,
723
- attribute: 'no-auto-fit'
724
- },
725
- /**
726
- * Opt out of focus being automatically moved to the first focusable element in the dropdown when opened
727
- * @type {boolean}
728
- */
729
- noAutoFocus: {
730
- type: Boolean,
731
- reflect: true,
732
- attribute: 'no-auto-focus'
733
- },
734
- /**
735
- * Render with no padding
736
- * @type {boolean}
737
- */
738
- noPadding: {
739
- type: Boolean,
740
- reflect: true,
741
- attribute: 'no-padding'
742
- },
743
- /**
744
- * Render the footer with no padding (if it has content)
745
- * @type {boolean}
746
- */
747
- noPaddingFooter: {
748
- type: Boolean,
749
- reflect: true,
750
- attribute: 'no-padding-footer'
751
- },
752
- /**
753
- * Render the header with no padding (if it has content)
754
- * @type {boolean}
755
- */
756
- noPaddingHeader: {
757
- type: Boolean,
758
- reflect: true,
759
- attribute: 'no-padding-header'
760
- },
761
- /**
762
- * Render without a pointer
763
- * @type {boolean}
764
- */
765
- noPointer: {
766
- type: Boolean,
767
- reflect: true,
768
- attribute: 'no-pointer'
769
- },
770
- /**
771
- * Whether the dropdown is open or not
772
- * @type {boolean}
773
- */
774
- opened: {
775
- type: Boolean,
776
- reflect: true
777
- },
778
- /**
779
- * Private.
780
- * @ignore
781
- */
782
- openedAbove: {
783
- type: Boolean,
784
- reflect: true,
785
- attribute: 'opened-above'
786
- },
787
- /**
788
- * Optionally render a d2l-focus-trap around the dropdown content
789
- * @type {boolean}
790
- */
791
- trapFocus: {
792
- type: Boolean,
793
- reflect: true,
794
- attribute: 'trap-focus'
795
- },
796
- /**
797
- * Provide custom offset, positive or negative
798
- * @type {string}
799
- */
800
- verticalOffset: {
801
- type: String,
802
- attribute: 'vertical-offset'
803
- },
804
- _bottomOverflow: {
805
- type: Boolean
806
- },
807
- _closing: {
808
- type: Boolean
809
- },
810
- _contentOverflow: {
811
- type: Boolean
812
- },
813
- _dropdownContent: {
814
- type: Boolean,
815
- attribute: 'dropdown-content',
816
- reflect: true
817
- },
818
- _useMobileStyling: {
819
- type: Boolean,
820
- attribute: 'data-mobile',
821
- reflect: true
822
- },
823
- _hasHeader: {
824
- type: Boolean
825
- },
826
- _hasFooter: {
827
- type: Boolean
828
- },
829
- _contentHeight: {
830
- type: Number
831
- },
832
- _position: {
833
- type: Number
834
- },
835
- _showBackdrop: {
836
- type: Boolean
837
- },
838
- _topOverflow: {
839
- type: Boolean
840
- },
841
- _width: {
842
- type: Number
843
- }
844
- };
845
- }
846
-
847
- constructor() {
848
- super();
849
-
850
- this.noAutoClose = false;
851
- this.noAutoFit = false;
852
- this.noAutoFocus = false;
853
- this.noMobileCloseButton = false;
854
- this.noPadding = false;
855
- this.noPaddingFooter = false;
856
- this.noPaddingHeader = false;
857
- this.noPointer = false;
858
- this.mobileBreakpointOverride = 616;
859
- this.trapFocus = false;
860
- this._useMobileStyling = false;
861
-
862
- this.__opened = false;
863
- this.__content = null;
864
- this.__previousFocusableAncestor = null;
865
- this.__applyFocus = true;
866
- this.__dismissibleId = null;
867
-
868
- this._dropdownContent = true;
869
- this._bottomOverflow = false;
870
- this._topOverflow = false;
871
- this._closing = false;
872
- this._contentOverflow = false;
873
- this._hasHeader = false;
874
- this._hasFooter = false;
875
- this._showBackdrop = false;
876
- this._verticalOffset = defaultVerticalOffset;
877
-
878
- this.__onResize = this.__onResize.bind(this);
879
- this.__onAutoCloseFocus = this.__onAutoCloseFocus.bind(this);
880
- this.__onAutoCloseClick = this.__onAutoCloseClick.bind(this);
881
- this.__toggleScrollStyles = this.__toggleScrollStyles.bind(this);
882
- this._handleMobileResize = this._handleMobileResize.bind(this);
883
- this.__disconnectResizeObserver = this.__disconnectResizeObserver.bind(this);
884
- }
885
-
886
- get opened() {
887
- return this.__opened;
888
- }
889
-
890
- set opened(val) {
891
- const oldVal = this.__opened;
892
- if (oldVal !== val) {
893
- this.__opened = val;
894
- this.requestUpdate('opened', oldVal);
895
- this.__openedChanged(val);
896
- }
897
- }
898
-
899
- connectedCallback() {
900
- super.connectedCallback();
901
-
902
- window.addEventListener('resize', this.__onResize);
903
- this.addEventListener('blur', this.__onAutoCloseFocus, true);
904
- document.body.addEventListener('focus', this.__onAutoCloseFocus, true);
905
- document.body.addEventListener('click', this.__onAutoCloseClick, true);
906
- this.mediaQueryList = window.matchMedia(`(max-width: ${this.mobileBreakpointOverride - 1}px)`);
907
- this._useMobileStyling = this.mediaQueryList.matches;
908
- if (this.mediaQueryList.addEventListener) this.mediaQueryList.addEventListener('change', this._handleMobileResize);
909
- }
910
-
911
- disconnectedCallback() {
912
- super.disconnectedCallback();
913
- if (this.mediaQueryList.removeEventListener) this.mediaQueryList.removeEventListener('change', this._handleMobileResize);
914
- this.removeEventListener('blur', this.__onAutoCloseFocus);
915
- window.removeEventListener('resize', this.__onResize);
916
- if (document.body) {
917
- // DE41322: document.body can be null in some scenarios
918
- document.body.removeEventListener('focus', this.__onAutoCloseFocus, true);
919
- document.body.removeEventListener('click', this.__onAutoCloseClick, true);
920
- }
921
- clearDismissible(this.__dismissibleId);
922
- this.__dismissibleId = null;
923
-
924
- if (this.__resizeObserver) this.__resizeObserver.disconnect();
925
- }
926
-
927
- firstUpdated(changedProperties) {
928
- super.firstUpdated(changedProperties);
929
-
930
- this.__content = this.getContentContainer();
931
- this.addEventListener('d2l-dropdown-close', this.__onClose);
932
- this.addEventListener('d2l-dropdown-position', this.__toggleScrollStyles);
933
- }
934
-
935
- updated(changedProperties) {
936
- changedProperties.forEach((_, propName) =&gt; {
937
- if (propName === 'verticalOffset') {
938
- let newVerticalOffset = parseInt(this.verticalOffset);
939
- if (isNaN(newVerticalOffset)) {
940
- newVerticalOffset = defaultVerticalOffset;
941
- }
942
- this.style.setProperty('--d2l-dropdown-verticaloffset', `${newVerticalOffset}px`);
943
- this._verticalOffset = newVerticalOffset;
944
- }
945
- });
946
- }
947
-
948
- close() {
949
- const hide = () =&gt; {
950
- this._closing = false;
951
- this._showBackdrop = false;
952
- this.opened = false;
953
- };
954
-
955
- if (!reduceMotion && this._useMobileStyling && this.mobileTray && isVisible(this)) {
956
- if (this.shadowRoot) this.shadowRoot.querySelector('.d2l-dropdown-content-width')
957
- .addEventListener('animationend', hide, { once: true });
958
- this._closing = true;
959
- this._showBackdrop = false;
960
- } else {
961
- hide();
962
- }
963
- }
964
-
965
- /**
966
- * forceRender is no longer necessary, this is left as a stub so that
967
- * places calling it will not break. It will be removed once the Polymer
968
- * dropdown is swapped over to use this and all instances of
969
- * forceRender are removed.
970
- */
971
- forceRender() {}
972
-
973
- getContentContainer() {
974
- return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-container');
975
- }
976
-
977
- /**
978
- * Private.
979
- */
980
- height() {
981
- return this.__content && this.__content.offsetHeight;
982
- }
983
-
984
- async open(applyFocus) {
985
- this.__applyFocus = applyFocus !== undefined ? applyFocus : true;
986
- this.opened = true;
987
- await this.updateComplete;
988
- this._showBackdrop = this._useMobileStyling && this.mobileTray;
989
- }
990
-
991
- /**
992
- * Waits for the next resize when elem has a height &gt; 0px,
993
- * then calls the __position function.
994
- */
995
- requestRepositionNextResize(elem) {
996
- if (!elem) return;
997
- if (this.__resizeObserver) this.__resizeObserver.disconnect();
998
- this.__resizeObserver = new ResizeObserver(this.__disconnectResizeObserver);
999
- this.__resizeObserver.observe(elem);
1000
- }
1001
-
1002
- async resize() {
1003
- if (!this.opened) {
1004
- return;
1005
- }
1006
- this._showBackdrop = this._useMobileStyling && this.mobileTray;
1007
- await this.__position();
1008
- }
1009
-
1010
- /**
1011
- * Private.
1012
- */
1013
- scrollTo(scrollTop) {
1014
- const content = this.__content;
1015
- if (content) {
1016
- if (typeof scrollTop === 'number') {
1017
- content.scrollTop = scrollTop;
1018
- }
1019
- return content.scrollTop;
1020
- }
1021
- }
1022
-
1023
- toggleOpen(applyFocus) {
1024
- if (this.opened) {
1025
- this.close();
1026
- } else {
1027
- this.open(!this.noAutoFocus && applyFocus);
1028
- }
1029
- }
1030
-
1031
- __disconnectResizeObserver(entries) {
1032
- for (let i = 0; i &lt; entries.length; i++) {
1033
- const entry = entries[i];
1034
- if (this.__resizeObserver && entry.contentRect.height !== 0) {
1035
- this.__resizeObserver.disconnect();
1036
- // wrap in rAF for Firefox
1037
- requestAnimationFrame(() =&gt; {
1038
- if (this.opened) this.__position();
1039
- });
1040
- break;
1041
- }
1042
- }
1043
- }
1044
-
1045
- __getContentBottom() {
1046
- return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-bottom');
1047
- }
1048
-
1049
- __getContentTop() {
1050
- return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-top');
1051
- }
1052
-
1053
- __getOpener() {
1054
- const opener = findComposedAncestor(this, (elem) =&gt; {
1055
- if (elem.dropdownOpener) {
1056
- return true;
1057
- }
1058
- });
1059
- return opener;
1060
- }
1061
-
1062
- __getPositionContainer() {
1063
- return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-position');
1064
- }
1065
-
1066
- __getWidthContainer() {
1067
- return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-width');
1068
- }
1069
-
1070
- __handleFooterSlotChange(e) {
1071
- this._hasFooter = e.target.assignedNodes().length !== 0;
1072
- }
1073
-
1074
- __handleHeaderSlotChange(e) {
1075
- this._hasHeader = e.target.assignedNodes().length !== 0;
1076
- }
1077
-
1078
- __onAutoCloseClick(e) {
1079
- if (!this.opened || this.noAutoClose) {
1080
- return;
1081
- }
1082
- const rootTarget = e.composedPath()[0];
1083
- const clickInside = isComposedAncestor(this.getContentContainer(), rootTarget) ||
1084
- isComposedAncestor(this.__getContentTop(), rootTarget) ||
1085
- isComposedAncestor(this.__getContentBottom(), rootTarget);
1086
- if (clickInside) {
1087
- return;
1088
- }
1089
- const opener = this.__getOpener();
1090
- if (isComposedAncestor(opener.getOpenerElement(), rootTarget)) {
1091
- return;
1092
- }
1093
-
1094
- this.close();
1095
- }
1096
-
1097
- __onAutoCloseFocus() {
1098
-
1099
- /* timeout needed to work around lack of support for relatedTarget */
1100
- setTimeout(() =&gt; {
1101
- if (!this.opened
1102
- || this.noAutoClose
1103
- || !document.activeElement
1104
- || document.activeElement === this.__previousFocusableAncestor
1105
- || document.activeElement === document.body) {
1106
- return;
1107
- }
1108
-
1109
- const activeElement = getComposedActiveElement();
1110
-
1111
- if (isComposedAncestor(this, activeElement)
1112
- || isComposedAncestor(this.__getOpener(), activeElement)) {
1113
- return;
1114
- }
1115
- this.close();
1116
- }, 0);
1117
- }
1118
-
1119
- __onClose(e) {
1120
-
1121
- if (e.target !== this || !document.activeElement) {
1122
- return;
1123
- }
1124
-
1125
- const activeElement = getComposedActiveElement();
1126
-
1127
- if (!isComposedAncestor(this, activeElement)) {
1128
- return;
1129
- }
1130
-
1131
- const opener = this.__getOpener();
1132
- opener.getOpenerElement().focus();
1133
-
1134
- }
1135
-
1136
- __onResize() {
1137
- this.resize();
1138
- }
1139
-
1140
- async __openedChanged(newValue) {
1141
-
1142
- // DE44538: wait for dropdown content to fully render,
1143
- // otherwise this.getContentContainer() can return null.
1144
- await this.updateComplete;
1145
-
1146
- this.__previousFocusableAncestor =
1147
- newValue === true
1148
- ? getPreviousFocusableAncestor(this, false, false)
1149
- : null;
1150
-
1151
- const doOpen = async() =&gt; {
1152
-
1153
- const content = this.getContentContainer();
1154
-
1155
- if (!this.noAutoFit) {
1156
- content.scrollTop = 0;
1157
- }
1158
-
1159
- await this.__position();
1160
- this._showBackdrop = this._useMobileStyling && this.mobileTray;
1161
- if (!this.noAutoFocus && this.__applyFocus) {
1162
- const focusable = getFirstFocusableDescendant(this);
1163
- if (focusable) {
1164
- // Removing the rAF call can allow infinite focus looping to happen in content using a focus trap
1165
- requestAnimationFrame(() =&gt; focusable.focus());
1166
- } else {
1167
- content.setAttribute('tabindex', '-1');
1168
- content.focus();
1169
- }
1170
- }
1171
-
1172
- setTimeout(() =&gt;
1173
- this.dispatchEvent(new CustomEvent('d2l-dropdown-open', { bubbles: true, composed: true })), 0
1174
- );
1175
-
1176
- this.__dismissibleId = setDismissible(() =&gt; {
1177
- this.close();
1178
- });
1179
- };
1180
-
1181
- const ifrauBackdropService = await tryGetIfrauBackdropService();
1182
-
1183
- if (newValue) {
1184
-
1185
- if (ifrauBackdropService && this.mobileTray && this._useMobileStyling) {
1186
- this._ifrauContextInfo = await ifrauBackdropService.showBackdrop();
1187
- }
1188
-
1189
- await doOpen();
1190
-
1191
- } else {
1192
-
1193
- if (this.__dismissibleId) {
1194
- clearDismissible(this.__dismissibleId);
1195
- this.__dismissibleId = null;
1196
- }
1197
- if (ifrauBackdropService && this.mobileTray && this._useMobileStyling) {
1198
- ifrauBackdropService.hideBackdrop();
1199
- this._ifrauContextInfo = null;
1200
- }
1201
- this._showBackdrop = false;
1202
- await this.updateComplete;
1203
-
1204
- /** Dispatched when the dropdown is closed */
1205
- this.dispatchEvent(new CustomEvent('d2l-dropdown-close', { bubbles: true, composed: true }));
1206
-
1207
- }
1208
- }
1209
-
1210
- async __position(ignoreVertical, contentRect) {
1211
-
1212
- const opener = this.__getOpener();
1213
- if (!opener) {
1214
- return;
1215
- }
1216
- const target = opener.getOpenerElement();
1217
- if (!target) {
1218
- return;
1219
- }
1220
-
1221
- const content = this.getContentContainer();
1222
- const header = this.__getContentTop();
1223
- const footer = this.__getContentBottom();
1224
-
1225
- if (!this.noAutoFit) {
1226
- this._contentHeight = null;
1227
- }
1228
-
1229
- /* don't let dropdown content horizontally overflow viewport */
1230
- this._width = null;
1231
-
1232
- const openerPosition = window.getComputedStyle(opener, null).getPropertyValue('position');
1233
- const boundingContainer = getBoundingAncestor(target.parentNode);
1234
- const boundingContainerRect = boundingContainer.getBoundingClientRect();
1235
- const scrollHeight = boundingContainer.scrollHeight;
1236
-
1237
- await this.updateComplete;
1238
-
1239
- // position check in case consuming app (LMS) has overriden position to make content absolute wrt document
1240
- const bounded = (openerPosition === 'relative' && boundingContainer !== document.documentElement);
1241
-
1242
- const adjustPosition = async() =&gt; {
1243
-
1244
- const targetRect = target.getBoundingClientRect();
1245
- contentRect = contentRect ? contentRect : content.getBoundingClientRect();
1246
- const headerFooterHeight = header.getBoundingClientRect().height + footer.getBoundingClientRect().height;
1247
-
1248
- const height = this.minHeight ? this.minHeight : Math.min(this.maxHeight ? this.maxHeight : Number.MAX_VALUE, contentRect.height + headerFooterHeight);
1249
- const spaceRequired = {
1250
- height: height + 10,
1251
- width: contentRect.width
1252
- };
1253
- let spaceAround;
1254
- let spaceAroundScroll;
1255
- if (bounded) {
1256
- spaceAround = this._constrainSpaceAround({
1257
- // allow for target offset + outer margin
1258
- above: targetRect.top - boundingContainerRect.top - this._verticalOffset - outerMarginTopBottom,
1259
- // allow for target offset + outer margin
1260
- below: boundingContainerRect.bottom - targetRect.bottom - this._verticalOffset - outerMarginTopBottom,
1261
- // allow for outer margin
1262
- left: targetRect.left - boundingContainerRect.left - 20,
1263
- // allow for outer margin
1264
- right: boundingContainerRect.right - targetRect.right - 20
1265
- }, spaceRequired, targetRect);
1266
- spaceAroundScroll = this._constrainSpaceAround({
1267
- above: targetRect.top - boundingContainerRect.top + boundingContainer.scrollTop,
1268
- below: scrollHeight - targetRect.bottom + boundingContainerRect.top - boundingContainer.scrollTop
1269
- }, spaceRequired, targetRect);
1270
- } else {
1271
- spaceAround = this._constrainSpaceAround({
1272
- // allow for target offset + outer margin
1273
- above: targetRect.top - this._verticalOffset - outerMarginTopBottom,
1274
- // allow for target offset + outer margin
1275
- below: window.innerHeight - targetRect.bottom - this._verticalOffset - outerMarginTopBottom,
1276
- // allow for outer margin
1277
- left: targetRect.left - 20,
1278
- // allow for outer margin
1279
- right: document.documentElement.clientWidth - targetRect.right - 15
1280
- }, spaceRequired, targetRect);
1281
- spaceAroundScroll = this._constrainSpaceAround({
1282
- above: targetRect.top + document.documentElement.scrollTop,
1283
- below: scrollHeight - targetRect.bottom - document.documentElement.scrollTop
1284
- }, spaceRequired, targetRect);
1285
- }
1286
-
1287
- if (!ignoreVertical) {
1288
- this.openedAbove = this._getOpenedAbove(spaceAround, spaceAroundScroll, spaceRequired);
1289
- }
1290
-
1291
- const centerDelta = contentRect.width - targetRect.width;
1292
- const position = this._getPosition(spaceAround, centerDelta);
1293
- if (position !== null) {
1294
- this._position = position;
1295
- }
1296
-
1297
- //Calculate height available to the dropdown contents for overflow because that is the only area capable of scrolling
1298
- const availableHeight = this.openedAbove ? spaceAround.above : spaceAround.below;
1299
- if (!this.noAutoFit && availableHeight && availableHeight &gt; 0) {
1300
- //Only apply maximum if it's less than space available and the header/footer alone won't exceed it (content must be visible)
1301
- this._contentHeight = this.maxHeight !== null
1302
- && availableHeight &gt; this.maxHeight
1303
- && headerFooterHeight &lt; this.maxHeight
1304
- ? this.maxHeight - headerFooterHeight - 2
1305
- : availableHeight - headerFooterHeight;
1306
- this.__toggleOverflowY(contentRect.height + headerFooterHeight &gt; availableHeight);
1307
-
1308
- // ensure the content height has updated when the __toggleScrollStyles event handler runs
1309
- await this.updateComplete;
1310
- }
1311
-
1312
- /** Dispatched when the dropdown position finishes adjusting */
1313
- this.dispatchEvent(new CustomEvent('d2l-dropdown-position', { bubbles: true, composed: true }));
1314
- };
1315
-
1316
- const scrollWidth = Math.max(header.scrollWidth, content.scrollWidth, footer.scrollWidth);
1317
- const availableWidth = (bounded ? boundingContainerRect.width - 60 : window.innerWidth - 40);
1318
- this._width = (availableWidth &gt; scrollWidth ? scrollWidth : availableWidth) ;
1319
-
1320
- await this.updateComplete;
1321
-
1322
- await adjustPosition();
1323
- }
1324
-
1325
- __toggleOverflowY(isOverflowing) {
1326
- if (!this.__content) {
1327
- return;
1328
- }
1329
- if (!this._contentHeight) {
1330
- return;
1331
- }
1332
- this._contentOverflow = isOverflowing || this.__content.scrollHeight &gt; this._contentHeight;
1333
- }
1334
-
1335
- __toggleScrollStyles() {
1336
- /* scrollHeight incorrect in IE by 4px second time opened */
1337
- this._bottomOverflow = this.__content.scrollHeight - (this.__content.scrollTop + this.__content.clientHeight) &gt;= 5;
1338
- this._topOverflow = this.__content.scrollTop !== 0;
1339
- }
1340
-
1341
- _constrainSpaceAround(spaceAround, spaceRequired, targetRect) {
1342
- const constrained = { ...spaceAround };
1343
- if (this.boundary) {
1344
- constrained.above = this.boundary.above &gt;= 0 ? Math.min(spaceAround.above, this.boundary.above) : spaceAround.above;
1345
- constrained.below = this.boundary.below &gt;= 0 ? Math.min(spaceAround.below, this.boundary.below) : spaceAround.below;
1346
- constrained.left = this.boundary.left &gt;= 0 ? Math.min(spaceAround.left, this.boundary.left) : spaceAround.left;
1347
- constrained.right = this.boundary.right &gt;= 0 ? Math.min(spaceAround.right, this.boundary.right) : spaceAround.right;
1348
- }
1349
- const isRTL = this.getAttribute('dir') === 'rtl';
1350
- if ((this.align === 'start' && !isRTL) || (this.align === 'end' && isRTL)) {
1351
- constrained.left = Math.max(0, spaceRequired.width - (targetRect.width + spaceAround.right));
1352
- } else if ((this.align === 'start' && isRTL) || (this.align === 'end' && !isRTL)) {
1353
- constrained.right = Math.max(0, spaceRequired.width - (targetRect.width + spaceAround.left));
1354
- }
1355
- return constrained;
1356
- }
1357
-
1358
- _getBottomTrayStyling() {
1359
-
1360
- let maxHeightOverride;
1361
- let availableHeight = Math.min(window.innerHeight, window.screen.height);
1362
- if (this._ifrauContextInfo) availableHeight = this._ifrauContextInfo.availableHeight;
1363
- // default maximum height for bottom tray (42px margin)
1364
- const mobileTrayMaxHeightDefault = availableHeight - minBackdropHeightMobile;
1365
- if (this.maxHeight) {
1366
- // if maxWidth provided is smaller, use the maxWidth
1367
- maxHeightOverride = Math.min(mobileTrayMaxHeightDefault, this.maxHeight);
1368
- } else {
1369
- maxHeightOverride = mobileTrayMaxHeightDefault;
1370
- }
1371
- maxHeightOverride = `${maxHeightOverride}px`;
1372
-
1373
- let bottomOverride;
1374
- if (this._ifrauContextInfo) {
1375
- // Bottom override is measured as
1376
- // the distance from the bottom of the screen
1377
- const screenHeight =
1378
- window.innerHeight
1379
- - this._ifrauContextInfo.availableHeight
1380
- + Math.min(this._ifrauContextInfo.top, 0);
1381
- bottomOverride = `${screenHeight}px`;
1382
- }
1383
-
1384
- const widthOverride = '100vw';
1385
-
1386
- const widthStyle = {
1387
- minWidth: widthOverride,
1388
- width: widthOverride,
1389
- maxHeight: maxHeightOverride,
1390
- bottom: bottomOverride
1391
- };
1392
-
1393
- const contentWidthStyle = {
1394
- /* set width of content in addition to width container so header and footer borders are full width */
1395
- width: widthOverride
1396
- };
1397
-
1398
- const headerStyle = {
1399
- ...contentWidthStyle,
1400
- minHeight: this._hasHeader ? 'auto' : '5px'
1401
- };
1402
-
1403
- const footerStyle = {
1404
- ...contentWidthStyle,
1405
- minHeight: this._hasFooter || !this.noMobileCloseButton ? 'auto' : '5px'
1406
- };
1407
-
1408
- const contentStyle = {
1409
- ...contentWidthStyle,
1410
- maxHeight: maxHeightOverride,
1411
- overflowY: this._contentOverflow ? 'auto' : 'hidden'
1412
- };
1413
-
1414
- const closeButtonStyles = {
1415
- display: !this.noMobileCloseButton ? 'inline-block' : 'none',
1416
- width: this._getTrayFooterWidth(),
1417
- padding: this._hasFooter && !this.noPaddingFooter ? '12px 0 0 0' : '12px',
1418
- margin: this._getTrayFooterMargin()
1419
- };
1420
-
1421
- return {
1422
- 'width' : widthStyle,
1423
- 'header' : headerStyle,
1424
- 'footer' : footerStyle,
1425
- 'content' : contentStyle,
1426
- 'close' : closeButtonStyles
1427
- };
1428
- }
1429
-
1430
- _getDropdownStyling() {
1431
- const widthStyle = {
1432
- maxWidth: this.maxWidth ? `${this.maxWidth}px` : '',
1433
- minWidth: this.minWidth ? `${this.minWidth}px` : '',
1434
- /* add 2 to content width since scrollWidth does not include border */
1435
- width: this._width ? `${this._width + 20}px` : ''
1436
- };
1437
-
1438
- const contentWidthStyle = {
1439
- minWidth: this.minWidth ? `${this.minWidth}px` : '',
1440
- /* set width of content in addition to width container so header and footer borders are full width */
1441
- width: this._width ? `${this._width + 18}px` : '',
1442
- };
1443
-
1444
- const contentStyle = {
1445
- ...contentWidthStyle,
1446
- maxHeight: this._contentHeight ? `${this._contentHeight}px` : '',
1447
- overflowY: this._contentOverflow ? 'auto' : 'hidden'
1448
- };
1449
-
1450
- const closeButtonStyle = {
1451
- display: 'none',
1452
- };
1453
-
1454
- return {
1455
- 'width' : widthStyle,
1456
- 'content' : contentStyle,
1457
- 'close' : closeButtonStyle,
1458
- 'header' : contentWidthStyle,
1459
- 'footer' : contentWidthStyle
1460
- };
1461
- }
1462
-
1463
- _getLeftRightTrayStyling() {
1464
-
1465
- let maxWidthOverride = this.maxWidth;
1466
- let availableWidth = Math.min(window.innerWidth, window.screen.width);
1467
- if (this._ifrauContextInfo) availableWidth = this._ifrauContextInfo.availableWidth;
1468
- // default maximum width for tray (30px margin)
1469
- const mobileTrayMaxWidthDefault = Math.min(availableWidth - minBackdropWidthMobile, 420);
1470
- if (maxWidthOverride) {
1471
- // if maxWidth provided is smaller, use the maxWidth
1472
- maxWidthOverride = Math.min(mobileTrayMaxWidthDefault, maxWidthOverride);
1473
- } else {
1474
- maxWidthOverride = mobileTrayMaxWidthDefault;
1475
- }
1476
-
1477
- let minWidthOverride = this.minWidth;
1478
- // minimum size - 285px
1479
- const mobileTrayMinWidthDefault = 285;
1480
- if (minWidthOverride) {
1481
- // if minWidth provided is smaller, use the minumum width for tray
1482
- minWidthOverride = Math.max(mobileTrayMinWidthDefault, minWidthOverride);
1483
- } else {
1484
- minWidthOverride = mobileTrayMinWidthDefault;
1485
- }
1486
-
1487
- // if no width property set, automatically size to maximum width
1488
- let widthOverride = this._width ? this._width : maxWidthOverride;
1489
- // ensure width is between minWidth and maxWidth
1490
- if (widthOverride && maxWidthOverride && widthOverride &gt; (maxWidthOverride - 20)) widthOverride = maxWidthOverride - 20;
1491
- if (widthOverride && minWidthOverride && widthOverride &lt; (minWidthOverride - 20)) widthOverride = minWidthOverride - 20;
1492
-
1493
- maxWidthOverride = `${maxWidthOverride}px`;
1494
- minWidthOverride = `${minWidthOverride}px`;
1495
- const contentWidth = `${widthOverride + 18}px`;
1496
- /* add 2 to content width since scrollWidth does not include border */
1497
- const containerWidth = `${widthOverride + 20}px`;
1498
-
1499
- let maxHeightOverride = '';
1500
- if (this._ifrauContextInfo) maxHeightOverride = `${this._ifrauContextInfo.availableHeight}px`;
1501
-
1502
- let topOverride;
1503
- if (this._ifrauContextInfo) {
1504
- // if inside iframe, use ifrauContext top as top of screen
1505
- topOverride = `${this._ifrauContextInfo.top &lt; 0 ? -this._ifrauContextInfo.top : 0}px`;
1506
- } else if (window.innerHeight &gt; window.screen.height) {
1507
- // non-responsive page, manually override top to scroll distance
1508
- topOverride = window.pageYOffset;
1509
- }
1510
-
1511
- let rightOverride;
1512
- let leftOverride;
1513
- if (this.mobileTray === 'right') {
1514
- // On non-responsive pages, the innerWidth may be wider than the screen,
1515
- // override right to stick to right of viewport
1516
- rightOverride = `${Math.max(window.innerWidth - window.screen.width, 0)}px`;
1517
- }
1518
- if (this.mobileTray === 'left') {
1519
- // On non-responsive pages, the innerWidth may be wider than the screen,
1520
- // override left to stick to left of viewport
1521
- leftOverride = `${Math.max(window.innerWidth - window.screen.width, 0)}px`;
1522
- }
1523
-
1524
- const widthStyle = {
1525
- maxWidth: maxWidthOverride,
1526
- minWidth: minWidthOverride,
1527
- width: containerWidth,
1528
- maxHeight: maxHeightOverride,
1529
- top: topOverride,
1530
- right: rightOverride,
1531
- left: leftOverride,
1532
- };
1533
-
1534
- const contentWidthStyle = {
1535
- minWidth: minWidthOverride,
1536
- /* set width of content in addition to width container so header and footer borders are full width */
1537
- width: contentWidth,
1538
- };
1539
-
1540
- const headerStyle = {
1541
- ...contentWidthStyle,
1542
- minHeight: this._hasHeader ? 'auto' : '5px'
1543
- };
1544
-
1545
- const footerStyle = {
1546
- ...contentWidthStyle,
1547
- minHeight: this._hasFooter || !this.noMobileCloseButton ? 'auto' : '5px'
1548
- };
1549
-
1550
- const contentStyle = {
1551
- ...contentWidthStyle,
1552
- maxHeight: maxHeightOverride,
1553
- overflowY: this._contentOverflow ? 'auto' : 'hidden'
1554
- };
1555
-
1556
- const closeButtonStyles = {
1557
- display: !this.noMobileCloseButton ? 'inline-block' : 'none',
1558
- width: this._getTrayFooterWidth(),
1559
- padding: this._hasFooter && !this.noPaddingFooter ? '12px 0 0 0' : '12px',
1560
- margin: this._getTrayFooterMargin()
1561
- };
1562
-
1563
- return {
1564
- 'width' : widthStyle,
1565
- 'header' : headerStyle,
1566
- 'footer' : footerStyle,
1567
- 'content' : contentStyle,
1568
- 'close' : closeButtonStyles
1569
- };
1570
- }
1571
-
1572
- _getOpenedAbove(spaceAround, spaceAroundScroll, spaceRequired) {
1573
- if (spaceAround.below &gt;= spaceRequired.height) {
1574
- return false;
1575
- }
1576
- if (spaceAround.above &gt;= spaceRequired.height) {
1577
- return true;
1578
- }
1579
- if (!this.noAutoFit) {
1580
- // if auto-fit is enabled, scroll will be enabled for the
1581
- // inner content so it will always fit in the available space
1582
- // so pick the largest space it can be displayed in
1583
- return spaceAround.above &gt; spaceAround.below;
1584
- }
1585
- if (spaceAroundScroll.below &gt;= spaceRequired.height) {
1586
- return false;
1587
- }
1588
- if (spaceAroundScroll.above &gt;= spaceRequired.height) {
1589
- return true;
1590
- }
1591
- // if auto-fit is disabled and it doesn't fit in the scrollable space
1592
- // above or below, always open down because it can add scrollable space
1593
- return false;
1594
- }
1595
-
1596
- _getPosition(spaceAround, centerDelta) {
1597
-
1598
- const contentXAdjustment = centerDelta / 2;
1599
- if (centerDelta &lt;= 0) {
1600
- return contentXAdjustment * -1;
1601
- }
1602
- if (spaceAround.left &gt; contentXAdjustment && spaceAround.right &gt; contentXAdjustment) {
1603
- // center with target
1604
- return contentXAdjustment * -1;
1605
- }
1606
- const isRTL = this.getAttribute('dir') === 'rtl';
1607
- if (!isRTL) {
1608
- if (spaceAround.left &lt; contentXAdjustment) {
1609
- // slide content right (not enough space to center)
1610
- return spaceAround.left * -1;
1611
- } else if (spaceAround.right &lt; contentXAdjustment) {
1612
- // slide content left (not enough space to center)
1613
- return (centerDelta * -1) + spaceAround.right;
1614
- }
1615
- } else {
1616
- if (spaceAround.left &lt; contentXAdjustment) {
1617
- // slide content right (not enough space to center)
1618
- return (centerDelta * -1) + spaceAround.left;
1619
- } else if (spaceAround.right &lt; contentXAdjustment) {
1620
- // slide content left (not enough space to center)
1621
- return spaceAround.right * -1;
1622
- }
1623
- }
1624
- return null;
1625
- }
1626
-
1627
- _getTrayFooterMargin() {
1628
- let footerMargin;
1629
- if (this._hasFooter) {
1630
- footerMargin = '0';
1631
- } else if (this.getAttribute('dir') === 'rtl') {
1632
- footerMargin = '-20px -20px -20px 0px';
1633
- } else {
1634
- footerMargin = '-20px 0 -20px -20px';
1635
- }
1636
- return footerMargin;
1637
- }
1638
-
1639
- _getTrayFooterWidth() {
1640
- let footerWidth;
1641
- if (this.noPaddingFooter) {
1642
- footerWidth = 'calc(100% - 24px)';
1643
- } else if (this._hasFooter) {
1644
- footerWidth = '100%';
1645
- } else {
1646
- footerWidth = 'calc(100% + 16px)';
1647
- }
1648
- return footerWidth;
1649
- }
1650
-
1651
- _handleFocusTrapEnter() {
1652
- if (this.__applyFocus && !this.noAutoFocus) {
1653
- const content = this.getContentContainer();
1654
- const focusable = getFirstFocusableDescendant(content);
1655
- if (focusable) {
1656
- // Removing the rAF call can allow infinite focus looping to happen in content using a focus trap
1657
- requestAnimationFrame(() =&gt; focusable.focus());
1658
- } else {
1659
- content.setAttribute('tabindex', '-1');
1660
- content.focus();
1661
- }
1662
- }
1663
- /** Dispatched when user focus enters the dropdown content (trap-focus option only) */
1664
- this.dispatchEvent(new CustomEvent('d2l-dropdown-focus-enter', { detail:{ applyFocus: this.__applyFocus } }));
1665
- }
1666
-
1667
- async _handleMobileResize() {
1668
- this._useMobileStyling = this.mediaQueryList.matches;
1669
- if (this.opened) this._showBackdrop = this._useMobileStyling && this.mobileTray;
1670
- if (this.opened) await this.__position();
1671
- }
1672
-
1673
- _renderContent() {
1674
- const positionStyle = {};
1675
- const isRTL = this.getAttribute('dir') === 'rtl';
1676
- if (this._position) {
1677
- if (!isRTL) {
1678
- positionStyle.left = `${this._position}px`;
1679
- } else {
1680
- positionStyle.right = `${this._position}px`;
1681
- }
1682
- }
1683
-
1684
- const mobileTrayRightLeft = this._useMobileStyling && (this.mobileTray === 'right' || this.mobileTray === 'left');
1685
- const mobileTrayBottom = this._useMobileStyling && (this.mobileTray === 'bottom');
1686
-
1687
- let stylesMap;
1688
- if (mobileTrayBottom) {
1689
- stylesMap = this._getBottomTrayStyling();
1690
- } else if (mobileTrayRightLeft) {
1691
- stylesMap = this._getLeftRightTrayStyling();
1692
- } else {
1693
- stylesMap = this._getDropdownStyling();
1694
- }
1695
- const widthStyle = stylesMap['width'];
1696
- const headerStyle = stylesMap['header'];
1697
- const footerStyle = stylesMap['footer'];
1698
- const contentStyle = stylesMap['content'];
1699
- const closeButtonStyles = stylesMap['close'];
1700
-
1701
- const topClasses = {
1702
- 'd2l-dropdown-content-top': true,
1703
- 'd2l-dropdown-content-top-scroll': this._topOverflow,
1704
- 'd2l-dropdown-content-header': this._hasHeader
1705
- };
1706
- const bottomClasses = {
1707
- 'd2l-dropdown-content-bottom': true,
1708
- 'd2l-dropdown-content-bottom-scroll': this._bottomOverflow,
1709
- 'd2l-dropdown-content-footer': this._hasFooter || (this._useMobileStyling && this.mobileTray && !this.noMobileCloseButton)
1710
- };
1711
-
1712
- let dropdownContentSlots = html`
1713
- &lt;div
1714
- id="d2l-dropdown-wrapper"
1715
- class="d2l-dropdown-content-width"
1716
- style=${styleMap(widthStyle)}
1717
- ?data-closing="${this._closing}"&gt;
1718
- &lt;div class=${classMap(topClasses)} style=${styleMap(headerStyle)}&gt;
1719
- &lt;slot name="header" @slotchange="${this.__handleHeaderSlotChange}"&gt;&lt;/slot&gt;
1720
- &lt;/div&gt;
1721
- &lt;div
1722
- class="d2l-dropdown-content-container"
1723
- style=${styleMap(contentStyle)}
1724
- @scroll=${this.__toggleScrollStyles}&gt;
1725
- &lt;slot class="d2l-dropdown-content-slot"&gt;&lt;/slot&gt;
1726
- &lt;/div&gt;
1727
- &lt;div class=${classMap(bottomClasses)} style=${styleMap(footerStyle)}&gt;
1728
- &lt;slot name="footer" @slotchange="${this.__handleFooterSlotChange}"&gt;&lt;/slot&gt;
1729
- &lt;d2l-button
1730
- class="dropdown-close-btn"
1731
- style=${styleMap(closeButtonStyles)}
1732
- @click=${this.close}&gt;
1733
- ${this.localize('components.dropdown.close')}
1734
- &lt;/d2l-button&gt;
1735
- &lt;/div&gt;
1736
- &lt;/div&gt;
1737
- `;
1738
-
1739
- if (this.trapFocus) {
1740
- dropdownContentSlots = html`
1741
- &lt;d2l-focus-trap
1742
- @d2l-focus-trap-enter="${this._handleFocusTrapEnter}"
1743
- ?trap="${this.opened}"&gt;
1744
- ${dropdownContentSlots}
1745
- &lt;/d2l-focus-trap&gt;`;
1746
- }
1747
-
1748
- const dropdown = html`
1749
- &lt;div class="d2l-dropdown-content-position" style=${styleMap(positionStyle)}&gt;
1750
- ${dropdownContentSlots}
1751
- &lt;/div&gt;
1752
- `;
1753
-
1754
- return (this.mobileTray) ? html`
1755
- ${dropdown}
1756
- &lt;d2l-backdrop
1757
- for-target="d2l-dropdown-wrapper"
1758
- ?shown="${this._showBackdrop}" &gt;
1759
- &lt;/d2l-backdrop&gt;`
1760
- : html`${dropdown}`;
1761
- }
1762
-
1763
- };
1764
- </code></pre>
1765
-
1766
-
1767
- <pre class="d2l-code d2l-code-dark line-numbers"><code class="language-javascript">import '../colors/colors.js';
1768
- import { css, html, LitElement } from 'lit';
1769
- import { classMap } from 'lit/directives/class-map.js';
1770
- import { FocusMixin } from '../../mixins/focus/focus-mixin.js';
1771
- import { ifDefined } from 'lit/directives/if-defined.js';
1772
- import { offscreenStyles } from '../offscreen/offscreen.js';
1773
- import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
1774
- import { styleMap } from 'lit/directives/style-map.js';
1775
-
1776
- /**
1777
- * A container element that provides specific layout using several slots.
1778
- * @slot content - Slot for primary content such as title and supplementary info (no actionable elements)
1779
- * @slot actions - Slot for buttons and dropdown openers to be placed in top right corner of header
1780
- * @slot badge - Slot for badge content, such as a profile image or status indicator
1781
- * @slot footer - Slot for footer content, such secondary actions
1782
- * @slot header - Slot for header content, such as course image (no actionable elements)
1783
- */
1784
- class Card extends FocusMixin(LitElement) {
1785
-
1786
- static get properties() {
1787
- return {
1788
- /**
1789
- * Style the card's content and footer as centered horizontally
1790
- * @type {boolean}
1791
- */
1792
- alignCenter: { type: Boolean, attribute: 'align-center', reflect: true },
1793
- /**
1794
- * Download a URL instead of navigating to it
1795
- * @type {boolean}
1796
- */
1797
- download: { type: Boolean, reflect: true },
1798
- /**
1799
- * Location for the primary action/navigation
1800
- * @type {string}
1801
- */
1802
- href: { type: String, reflect: true },
1803
- /**
1804
- * Indicates the human language of the linked resource; purely advisory, with no built-in functionality
1805
- * @type {string}
1806
- */
1807
- hreflang: { type: String, reflect: true },
1808
- /**
1809
- * Specifies the relationship of the target object to the link object
1810
- * @type {string}
1811
- */
1812
- rel: { type: String, reflect: true },
1813
- /**
1814
- * Subtle aesthetic on non-white backgrounds
1815
- * @type {boolean}
1816
- */
1817
- subtle: { type: Boolean, reflect: true },
1818
- /**
1819
- * Where to display the linked URL
1820
- * @type {string}
1821
- */
1822
- target: { type: String, reflect: true },
1823
- /**
1824
- * Accessible text for the card (will be announced when AT user focuses)
1825
- * @type {string}
1826
- */
1827
- text: { type: String, reflect: true },
1828
- /**
1829
- * Specifies the media type in the form of a MIME type for the linked URL; purely advisory, with no built-in functionality
1830
- * @type {string}
1831
- */
1832
- type: { type: String, reflect: true },
1833
- _active: { type: Boolean, reflect: true },
1834
- _dropdownActionOpen: { type: Boolean, attribute: '_dropdown-action-open', reflect: true },
1835
- _hover: { type: Boolean },
1836
- _badgeMarginTop: { type: String },
1837
- _footerHidden: { type: Boolean },
1838
- _tooltipShowing: { type: Boolean, attribute: '_tooltip_showing', reflect: true }
1839
- };
1840
- }
1841
-
1842
- static get styles() {
1843
- return [offscreenStyles, css`
1844
- :host {
1845
- background-color: #ffffff;
1846
- border: 1px solid var(--d2l-color-gypsum);
1847
- border-radius: 6px;
1848
- box-sizing: border-box;
1849
- display: inline-block;
1850
- position: relative;
1851
- z-index: 0;
1852
- }
1853
- .d2l-card-container {
1854
- align-items: flex-start; /* required so that footer will not stretch to 100% width */
1855
- display: flex;
1856
- flex-direction: column;
1857
- height: 100%;
1858
- position: relative;
1859
- }
1860
- .d2l-card-link-container {
1861
- flex-basis: auto;
1862
- flex-grow: 1;
1863
- flex-shrink: 1;
1864
- width: 100%; /* required for Legacy-Edge and FF when align-items: flex-start is specified */
1865
- }
1866
- .d2l-card-link-text {
1867
- display: inline-block;
1868
- }
1869
- .d2l-card-header {
1870
- border-start-end-radius: 6px;
1871
- border-start-start-radius: 6px;
1872
- overflow: hidden;
1873
- }
1874
-
1875
- a {
1876
- bottom: -1px;
1877
- display: block;
1878
- left: -1px;
1879
- outline: none;
1880
- position: absolute;
1881
- right: -1px;
1882
- top: -1px;
1883
- z-index: 1;
1884
- }
1885
- :host([subtle]) a {
1886
- bottom: 0;
1887
- left: 0;
1888
- right: 0;
1889
- top: 0;
1890
- }
1891
-
1892
- :host(:hover) a {
1893
- bottom: -5px;
1894
- }
1895
- :host([subtle]:hover) a {
1896
- bottom: -4px;
1897
- }
1898
-
1899
- .d2l-card-content {
1900
- padding: 1.2rem 0.8rem 0 0.8rem;
1901
- }
1902
- :host([align-center]) .d2l-card-content {
1903
- text-align: center;
1904
- }
1905
-
1906
- .d2l-card-footer-hidden .d2l-card-content {
1907
- padding-bottom: 1.2rem;
1908
- }
1909
- .d2l-card-actions {
1910
- inset-inline-end: 0.6rem;
1911
- position: absolute;
1912
- top: 0.6rem;
1913
- /* this must be higher than footer z-index so dropdowns will be on top */
1914
- z-index: 3;
1915
- }
1916
- .d2l-card-actions ::slotted(*) {
1917
- margin-inline-start: 0.3rem;
1918
- }
1919
- .d2l-card-badge {
1920
- line-height: 0;
1921
- padding: 0 0.8rem;
1922
- }
1923
- .d2l-card-footer {
1924
- box-sizing: border-box;
1925
- flex: none;
1926
- padding: 1.2rem 0.8rem 0.6rem 0.8rem;
1927
- pointer-events: none;
1928
- width: 100%;
1929
- z-index: 2;
1930
- }
1931
- :host([align-center]) .d2l-card-footer {
1932
- text-align: center;
1933
- }
1934
-
1935
- .d2l-card-footer ::slotted([slot="footer"]) {
1936
- pointer-events: all;
1937
- }
1938
-
1939
- .d2l-card-footer-hidden .d2l-card-footer {
1940
- box-sizing: content-box;
1941
- height: auto;
1942
- }
1943
-
1944
- :host([subtle]) {
1945
- border: none;
1946
- }
1947
- :host([subtle][href]) {
1948
- box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.03);
1949
- }
1950
- :host([href]:not([_active]):hover) {
1951
- box-shadow: 0 2px 14px 1px rgba(0, 0, 0, 0.06);
1952
- }
1953
- :host([subtle][href]:not([_active]):hover) {
1954
- box-shadow: 0 4px 18px 2px rgba(0, 0, 0, 0.06);
1955
- }
1956
- ${getFocusRingStyles(() => ':host([_active])', {'extraStyles': css`border-color: transparent;`})}
1957
- /* .d2l-card-link-container-hover is used to only color/underline when
1958
- hovering the anchor; these styles are not applied when hovering actions */
1959
- :host([href]) .d2l-card-link-container-hover,
1960
- :host([href][_active]) .d2l-card-content {
1961
- color: var(--d2l-color-celestine);
1962
- text-decoration: underline;
1963
- }
1964
- /* this is needed to ensure tooltip is not be clipped by adjacent cards */
1965
- :host([_tooltip_showing]) {
1966
- z-index: 1;
1967
- }
1968
- /* this is needed to ensure open menu will be ontop of adjacent cards */
1969
- :host([_dropdown-action-open]) {
1970
- z-index: 2;
1971
- }
1972
- @media (prefers-reduced-motion: no-preference) {
1973
- :host {
1974
- transition: box-shadow 0.2s;
1975
- }
1976
- }
1977
- @media (prefers-contrast: more) {
1978
- :host([subtle]) {
1979
- border: 1px solid var(--d2l-color-gypsum);;
1980
- }
1981
- }
1982
- `];
1983
- }
1984
-
1985
- constructor() {
1986
- super();
1987
- this.alignCenter = false;
1988
- this.download = false;
1989
- this.subtle = false;
1990
- this._active = false;
1991
- this._dropdownActionOpen = false;
1992
- this._footerHidden = true;
1993
- this._hover = false;
1994
- this._tooltipShowing = false;
1995
- this._onBadgeResize = this._onBadgeResize.bind(this);
1996
- this._onFooterResize = this._onFooterResize.bind(this);
1997
- }
1998
-
1999
- static get focusElementSelector() {
2000
- return 'a';
2001
- }
2002
-
2003
- firstUpdated(changedProperties) {
2004
- super.firstUpdated(changedProperties);
2005
- const badgeObserver = new ResizeObserver(this._onBadgeResize);
2006
- badgeObserver.observe(this.shadowRoot.querySelector('.d2l-card-badge'));
2007
- const footerObserver = new ResizeObserver(this._onFooterResize);
2008
- footerObserver.observe(this.shadowRoot.querySelector('.d2l-card-footer'));
2009
- }
2010
-
2011
- render() {
2012
-
2013
- const containerClass = {
2014
- 'd2l-card-container': true,
2015
- 'd2l-visible-on-ancestor-target': true,
2016
- 'd2l-card-footer-hidden': this._footerHidden
2017
- };
2018
-
2019
- const linkContainerClass = {
2020
- 'd2l-card-link-container': true,
2021
- 'd2l-card-link-container-hover': this._hover
2022
- };
2023
-
2024
- const badgeStyle = {};
2025
- if (this._badgeMarginTop) badgeStyle.marginTop = this._badgeMarginTop;
2026
-
2027
- const footerClass = {
2028
- 'd2l-card-footer': true,
2029
- 'd2l-offscreen': this._footerHidden
2030
- };
2031
-
2032
- return html`
2033
- &lt;div class="${classMap(containerClass)}"
2034
- @d2l-dropdown-open="${this._onDropdownOpen}"
2035
- @d2l-dropdown-close="${this._onDropdownClose}"
2036
- @d2l-tooltip-show="${this._onTooltipShow}"
2037
- @d2l-tooltip-hide="${this._onTooltipHide}"&gt;
2038
- &lt;a @blur="${this._onLinkBlur}"
2039
- ?download="${this.download}"
2040
- @focus="${this._onLinkFocus}"
2041
- href="${ifDefined(this.href ? this.href : undefined)}"
2042
- hreflang="${ifDefined(this.hreflang)}"
2043
- @mouseenter="${this._onLinkMouseEnter}"
2044
- @mouseleave="${this._onLinkMouseLeave}"
2045
- rel="${ifDefined(this.rel)}"
2046
- target="${ifDefined(this.target)}"
2047
- type="${ifDefined(this.type)}"&gt;
2048
- &lt;span class="d2l-card-link-text d2l-offscreen"&gt;${this.text}&lt;/span&gt;
2049
- &lt;/a&gt;
2050
- &lt;div class="${classMap(linkContainerClass)}"&gt;
2051
- &lt;div class="d2l-card-header"&gt;&lt;slot name="header"&gt;&lt;/slot&gt;&lt;/div&gt;
2052
- &lt;div class="d2l-card-badge" style="${styleMap(badgeStyle)}"&gt;&lt;slot name="badge"&gt;&lt;/slot&gt;&lt;/div&gt;
2053
- &lt;div class="d2l-card-content"&gt;&lt;slot name="content"&gt;&lt;/slot&gt;&lt;/div&gt;
2054
- &lt;/div&gt;
2055
- &lt;div class="d2l-card-actions"&gt;&lt;slot name="actions"&gt;&lt;/slot&gt;&lt;/div&gt;
2056
- &lt;div class="${classMap(footerClass)}"&gt;&lt;slot name="footer"&gt;&lt;/slot&gt;&lt;/div&gt;
2057
- &lt;/div&gt;
2058
- `;
2059
- }
2060
-
2061
- _onBadgeResize(entries) {
2062
- if (!entries || entries.length === 0) return;
2063
- const entry = entries[0];
2064
- this._badgeMarginTop = `${-0.5 * entry.contentRect.height}px`;
2065
- }
2066
-
2067
- _onDropdownClose() {
2068
- this._dropdownActionOpen = false;
2069
- }
2070
-
2071
- _onDropdownOpen() {
2072
- this._dropdownActionOpen = true;
2073
- }
2074
-
2075
- _onFooterResize(entries) {
2076
- if (!entries || entries.length === 0) return;
2077
- const entry = entries[0];
2078
- // firefox has a rounding error when calculating the height of the contentRect
2079
- // with `box-sizing: border-box;` so check for numbers which are close to 0 as well
2080
- this._footerHidden = (entry.contentRect.height &lt; 1);
2081
- }
2082
-
2083
- _onLinkBlur() {
2084
- this._active = false;
2085
- }
2086
-
2087
- _onLinkFocus() {
2088
- this._active = true;
2089
- }
2090
-
2091
- _onLinkMouseEnter() {
2092
- this._hover = true;
2093
- }
2094
-
2095
- _onLinkMouseLeave() {
2096
- this._hover = false;
2097
- }
2098
-
2099
- _onTooltipHide() {
2100
- this._tooltipShowing = false;
2101
- }
2102
-
2103
- _onTooltipShow() {
2104
- this._tooltipShowing = true;
2105
- }
2106
-
2107
- }
2108
-
2109
- customElements.define('d2l-card', Card);
2110
- </code></pre>
2111
-
2112
328
  <h2 class="d2l-heading-3">Language Samples</h2>
2113
329
 
2114
330
  <pre class="d2l-code d2l-code-dark line-numbers"><code class="language-arduino">// digital pin 2 has a pushbutton attached to it. Give it a name: