@brightspace-ui/core 3.142.2 → 3.143.1

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.
@@ -6,12 +6,25 @@ import { css, html, nothing } from 'lit';
6
6
  import { getComposedActiveElement, getFirstFocusableDescendant, getPreviousFocusableAncestor } from '../../helpers/focus.js';
7
7
  import { getComposedParent, isComposedAncestor } from '../../helpers/dom.js';
8
8
  import { _offscreenStyleDeclarations } from '../offscreen/offscreen.js';
9
+ import { classMap } from 'lit/directives/class-map.js';
9
10
  import { styleMap } from 'lit/directives/style-map.js';
10
11
  import { tryGetIfrauBackdropService } from '../../helpers/ifrauBackdropService.js';
11
12
 
13
+ export const positionLocations = Object.freeze({
14
+ blockEnd: 'block-end',
15
+ blockStart: 'block-start',
16
+ inlineEnd: 'inline-end',
17
+ inlineStart: 'inline-start'
18
+ });
19
+ export const positionSpans = Object.freeze({
20
+ all: 'all',
21
+ end: 'end',
22
+ start: 'start'
23
+ });
24
+
12
25
  const defaultPreferredPosition = {
13
- location: 'block-end', // block-start, block-end
14
- span: 'all', // start, end, all
26
+ location: positionLocations.blockEnd,
27
+ span: positionSpans.all,
15
28
  allowFlip: true
16
29
  };
17
30
  const minBackdropHeightMobile = 42;
@@ -20,6 +33,12 @@ const pointerLength = 16;
20
33
  const pointerRotatedLength = Math.SQRT2 * parseFloat(pointerLength);
21
34
  const isSupported = ('popover' in HTMLElement.prototype);
22
35
 
36
+ const getScrollbarWidth = () => {
37
+ const width = window.innerWidth - document.documentElement.clientWidth;
38
+ if (width > 0) return width + 1; // 16 when present, but can be 0 even if visible (ex. MacOS depending on settings)
39
+ else return 0;
40
+ };
41
+
23
42
  export const PopoverMixin = superclass => class extends superclass {
24
43
 
25
44
  static get properties() {
@@ -127,6 +146,17 @@ export const PopoverMixin = superclass => class extends superclass {
127
146
  position: absolute;
128
147
  z-index: 1;
129
148
  }
149
+ :host([_location="block-start"]) .pointer {
150
+ clip: rect(9px, 21px, 22px, -3px);
151
+ }
152
+ :host([_location="inline-start"]) .pointer,
153
+ :host([_location="inline-end"]) .pointer.pointer-mirror {
154
+ clip: rect(-3px, 21px, 21px, 10px);
155
+ }
156
+ :host([_location="inline-end"]) .pointer,
157
+ :host([_location="inline-start"]) .pointer.pointer-mirror {
158
+ clip: rect(-3px, 8px, 21px, -3px);
159
+ }
130
160
 
131
161
  .pointer > div {
132
162
  background-color: var(--d2l-popover-background-color, var(--d2l-popover-default-background-color));
@@ -138,10 +168,6 @@ export const PopoverMixin = superclass => class extends superclass {
138
168
  width: ${pointerLength}px;
139
169
  }
140
170
 
141
- :host([_location="block-start"]) .pointer {
142
- clip: rect(9px, 21px, 22px, -3px);
143
- }
144
-
145
171
  :host([_location="block-start"]) .pointer > div {
146
172
  box-shadow: 4px 4px 12px -5px rgba(32, 33, 34, 0.2); /* ferrite */
147
173
  }
@@ -278,7 +304,10 @@ export const PopoverMixin = superclass => class extends superclass {
278
304
  }
279
305
 
280
306
  configure(properties) {
281
- this._margin = properties?.margin ?? 18;
307
+ if (properties?.margin) this._margin = properties.margin;
308
+ else if (properties?.position?.location === positionLocations.inlineStart
309
+ || properties?.position?.location === positionLocations.inlineEnd) this._margin = 0;
310
+ else this._margin = 18;
282
311
  this._maxHeight = properties?.maxHeight;
283
312
  this._maxWidth = properties?.maxWidth;
284
313
  this._minHeight = properties?.minHeight;
@@ -296,8 +325,8 @@ export const PopoverMixin = superclass => class extends superclass {
296
325
  || this._preferredPosition?.span !== properties.position?.span
297
326
  || this._preferredPosition?.allowFlip !== properties.position?.allowFlip) {
298
327
  this._preferredPosition = {
299
- location: properties?.position?.location ?? 'block-end',
300
- span: properties?.position?.span ?? 'all',
328
+ location: properties?.position?.location ?? positionLocations.blockEnd,
329
+ span: properties?.position?.span ?? positionSpans.all,
301
330
  allowFlip: properties?.position?.allowFlip ?? true
302
331
  };
303
332
  }
@@ -371,11 +400,12 @@ export const PopoverMixin = superclass => class extends superclass {
371
400
  };
372
401
 
373
402
  // space in viewport
403
+ const prefersInline = this._preferredPosition.location === positionLocations.inlineStart || this._preferredPosition.location === positionLocations.inlineEnd;
374
404
  const spaceAround = this.#constrainSpaceAround({
375
405
  // allow for opener offset + outer margin
376
- above: openerRect.top - this._offset - this._margin,
406
+ above: openerRect.top - (prefersInline ? 0 : this._offset) - this._margin,
377
407
  // allow for opener offset + outer margin
378
- below: window.innerHeight - openerRect.bottom - this._offset - this._margin,
408
+ below: window.innerHeight - openerRect.bottom - (prefersInline ? 0 : this._offset) - this._margin,
379
409
  // allow for outer margin
380
410
  left: openerRect.left - 20,
381
411
  // allow for outer margin
@@ -398,7 +428,9 @@ export const PopoverMixin = superclass => class extends superclass {
398
428
  if (options.updateHeight) {
399
429
 
400
430
  // calculate height available to the popover contents for overflow because that is the only area capable of scrolling
401
- const availableHeight = (this._location === 'block-start') ? spaceAround.above : spaceAround.below;
431
+ const availableHeight = (this._location === positionLocations.inlineStart || this._location === positionLocations.inlineEnd)
432
+ ? (spaceAround.above + spaceAround.below + openerRect.height)
433
+ : (this._location === positionLocations.blockStart ? spaceAround.above : spaceAround.below);
402
434
 
403
435
  if (!this._noAutoFit && availableHeight && availableHeight > 0) {
404
436
  // only apply maximum if it's less than space available and the header/footer alone won't exceed it (content must be visible)
@@ -409,8 +441,6 @@ export const PopoverMixin = superclass => class extends superclass {
409
441
  await this.updateComplete;
410
442
  }
411
443
 
412
- // todo: handle inline-start and inline-end locations
413
-
414
444
  }
415
445
 
416
446
  /** @ignore */
@@ -434,9 +464,9 @@ export const PopoverMixin = superclass => class extends superclass {
434
464
  const mobileTrayLocation = this._mobile ? this._mobileTrayLocation : null;
435
465
 
436
466
  let stylesMap;
437
- if (mobileTrayLocation === 'block-end') {
467
+ if (mobileTrayLocation === positionLocations.blockEnd) {
438
468
  stylesMap = this.#getMobileTrayBlockStyleMaps();
439
- } else if (mobileTrayLocation === 'inline-start' || mobileTrayLocation === 'inline-end') {
469
+ } else if (mobileTrayLocation === positionLocations.inlineStart || mobileTrayLocation === positionLocations.inlineEnd) {
440
470
  stylesMap = this.#getMobileTrayInlineStyleMaps();
441
471
  } else {
442
472
  stylesMap = this.#getStyleMaps();
@@ -478,8 +508,12 @@ export const PopoverMixin = superclass => class extends superclass {
478
508
  }
479
509
  }
480
510
 
511
+ const pointerClasses = {
512
+ 'pointer': true,
513
+ 'pointer-mirror': this._rtl
514
+ };
481
515
  const pointer = !this._noPointer ? html`
482
- <div class="pointer" style="${styleMap(pointerPositionStyles)}">
516
+ <div class="${classMap(pointerClasses)}" style="${styleMap(pointerPositionStyles)}">
483
517
  <div></div>
484
518
  </div>
485
519
  ` : nothing;
@@ -580,9 +614,9 @@ export const PopoverMixin = superclass => class extends superclass {
580
614
  #constrainSpaceAround(spaceAround, spaceRequired, openerRect) {
581
615
  const constrained = { ...spaceAround };
582
616
 
583
- if ((this._preferredPosition.span === 'end' && !this._rtl) || (this._preferredPosition.span === 'start' && this._rtl)) {
617
+ if ((this._preferredPosition.span === positionSpans.end && !this._rtl) || (this._preferredPosition.span === positionSpans.start && this._rtl)) {
584
618
  constrained.left = Math.max(0, spaceRequired.width - (openerRect.width + spaceAround.right));
585
- } else if ((this._preferredPosition.span === 'end' && this._rtl) || (this._preferredPosition.span === 'start' && !this._rtl)) {
619
+ } else if ((this._preferredPosition.span === positionSpans.end && this._rtl) || (this._preferredPosition.span === positionSpans.start && !this._rtl)) {
586
620
  constrained.right = Math.max(0, spaceRequired.width - (openerRect.width + spaceAround.left));
587
621
  }
588
622
 
@@ -621,28 +655,46 @@ export const PopoverMixin = superclass => class extends superclass {
621
655
  return preferred.location;
622
656
  }
623
657
 
624
- if (preferred.location === 'block-end') {
625
- if (spaceAround.below >= spaceRequired.height) return 'block-end';
626
- if (spaceAround.above >= spaceRequired.height) return 'block-start';
658
+ if (preferred.location === positionLocations.blockEnd) {
659
+ if (spaceAround.below >= spaceRequired.height) return positionLocations.blockEnd;
660
+ if (spaceAround.above >= spaceRequired.height) return positionLocations.blockStart;
627
661
  // if auto-fit is enabled, scroll will be enabled for the inner content so it will always fit in the available space so pick the largest space it can be displayed in
628
- if (!this.noAutoFit) return spaceAround.above > spaceAround.below ? 'block-start' : 'block-end';
629
- if (spaceAroundScroll.below >= spaceRequired.height) return 'block-end';
630
- if (spaceAroundScroll.above >= spaceRequired.height) return 'block-start';
662
+ if (!this.noAutoFit) return spaceAround.above > spaceAround.below ? positionLocations.blockStart : positionLocations.blockEnd;
663
+ if (spaceAroundScroll.below >= spaceRequired.height) return positionLocations.blockEnd;
664
+ if (spaceAroundScroll.above >= spaceRequired.height) return positionLocations.blockStart;
631
665
  }
632
666
 
633
- if (preferred.location === 'block-start') {
634
- if (spaceAround.above >= spaceRequired.height) return 'block-start';
635
- if (spaceAround.below >= spaceRequired.height) return 'block-end';
667
+ if (preferred.location === positionLocations.blockStart) {
668
+ if (spaceAround.above >= spaceRequired.height) return positionLocations.blockStart;
669
+ if (spaceAround.below >= spaceRequired.height) return positionLocations.blockEnd;
636
670
  // if auto-fit is enabled, scroll will be enabled for the inner content so it will always fit in the available space so pick the largest space it can be displayed in
637
- if (!this.noAutoFit) return spaceAround.above > spaceAround.below ? 'block-start' : 'block-end';
638
- if (spaceAroundScroll.above >= spaceRequired.height) return 'block-start';
639
- if (spaceAroundScroll.below >= spaceRequired.height) return 'block-end';
671
+ if (!this.noAutoFit) return spaceAround.above > spaceAround.below ? positionLocations.blockStart : positionLocations.blockEnd;
672
+ if (spaceAroundScroll.above >= spaceRequired.height) return positionLocations.blockStart;
673
+ if (spaceAroundScroll.below >= spaceRequired.height) return positionLocations.blockEnd;
640
674
  }
641
675
 
642
- // todo: add location order for inline-start and inline-end
676
+ if (preferred.location === positionLocations.inlineEnd) {
677
+ if (this._rtl) {
678
+ if (spaceAround.left >= spaceRequired.width) return positionLocations.inlineEnd;
679
+ if (spaceAround.right >= spaceRequired.width) return positionLocations.inlineStart;
680
+ } else {
681
+ if (spaceAround.right >= spaceRequired.width) return positionLocations.inlineEnd;
682
+ if (spaceAround.left >= spaceRequired.width) return positionLocations.inlineStart;
683
+ }
684
+ }
685
+
686
+ if (preferred.location === positionLocations.inlineStart) {
687
+ if (this._rtl) {
688
+ if (spaceAround.right >= spaceRequired.width) return positionLocations.inlineStart;
689
+ if (spaceAround.left >= spaceRequired.width) return positionLocations.inlineEnd;
690
+ } else {
691
+ if (spaceAround.left >= spaceRequired.width) return positionLocations.inlineStart;
692
+ if (spaceAround.right >= spaceRequired.width) return positionLocations.inlineEnd;
693
+ }
694
+ }
643
695
 
644
696
  // if auto-fit is disabled and it doesn't fit in the scrollable space above or below, always open down because it can add scrollable space
645
- return 'block-end';
697
+ return positionLocations.blockEnd;
646
698
  }
647
699
 
648
700
  #getMobileTrayBlockStyleMaps() {
@@ -743,11 +795,11 @@ export const PopoverMixin = superclass => class extends superclass {
743
795
 
744
796
  let inlineEndOverride;
745
797
  let inlineStartOverride;
746
- if (this._mobileTrayLocation === 'inline-end') {
798
+ if (this._mobileTrayLocation === positionLocations.inlineEnd) {
747
799
  // On non-responsive pages, the innerWidth may be wider than the screen,
748
800
  // override right to stick to right of viewport
749
801
  inlineEndOverride = `${Math.max(window.innerWidth - window.screen.width, 0)}px`;
750
- } else if (this._mobileTrayLocation === 'inline-start') {
802
+ } else if (this._mobileTrayLocation === positionLocations.inlineStart) {
751
803
  // On non-responsive pages, the innerWidth may be wider than the screen,
752
804
  // override left to stick to left of viewport
753
805
  inlineStartOverride = `${Math.max(window.innerWidth - window.screen.width, 0)}px`;
@@ -794,33 +846,55 @@ export const PopoverMixin = superclass => class extends superclass {
794
846
 
795
847
  const pointerRect = pointer.getBoundingClientRect();
796
848
 
797
- if (this._preferredPosition.span !== 'all') {
798
- const xAdjustment = Math.min(20 + ((pointerRotatedLength - pointerLength) / 2), (openerRect.width - pointerLength) / 2);
799
- if (!this._rtl) {
800
- if (this._preferredPosition.span === 'end') {
801
- position.left = openerRect.left + xAdjustment;
849
+ if (this._location === positionLocations.blockEnd || this._location === positionLocations.blockStart) {
850
+
851
+ if (this._preferredPosition.span !== positionSpans.all) {
852
+ const xAdjustment = Math.min(20 + ((pointerRotatedLength - pointerLength) / 2), (openerRect.width - pointerLength) / 2);
853
+ if (!this._rtl) {
854
+ if (this._preferredPosition.span === positionSpans.end) {
855
+ position.left = openerRect.left + xAdjustment;
856
+ } else {
857
+ position.right = (openerRect.right * -1) + xAdjustment;
858
+ }
802
859
  } else {
803
- position.right = (openerRect.right * -1) + xAdjustment;
860
+ if (this._preferredPosition.span === positionSpans.end) {
861
+ position.right = window.innerWidth - openerRect.right + xAdjustment - getScrollbarWidth();
862
+ } else {
863
+ position.left = (window.innerWidth - openerRect.left - xAdjustment - getScrollbarWidth()) * -1;
864
+ }
804
865
  }
805
866
  } else {
806
- if (this._preferredPosition.span === 'end') {
807
- position.right = window.innerWidth - openerRect.right + xAdjustment;
867
+ if (!this._rtl) {
868
+ position.left = openerRect.left + ((openerRect.width - pointerRect.width) / 2);
808
869
  } else {
809
- position.left = (window.innerWidth - openerRect.left - xAdjustment) * -1;
870
+ position.right = window.innerWidth - openerRect.left - ((openerRect.width + pointerRect.width) / 2) - getScrollbarWidth();
810
871
  }
811
872
  }
812
- } else {
813
- if (!this._rtl) {
814
- position.left = openerRect.left + ((openerRect.width - pointerRect.width) / 2);
873
+
874
+ if (this._location === positionLocations.blockStart) {
875
+ position.bottom = window.innerHeight - openerRect.top + this._offset - 8; // 8 minor adjustment to position pointer at edge of content
815
876
  } else {
816
- position.right = window.innerWidth - openerRect.left - ((openerRect.width + pointerRect.width) / 2);
877
+ position.top = openerRect.top + openerRect.height + this._offset - 7; // 7 minor adjustment to position pointer at edge of content
878
+ }
879
+
880
+ } else if (this._location === positionLocations.inlineEnd || this._location === positionLocations.inlineStart) {
881
+
882
+ position.top = openerRect.top + (openerRect.height / 2) - (pointerLength / 2);
883
+
884
+ if (this._location === positionLocations.inlineStart) {
885
+ if (!this._rtl) {
886
+ position.right = (openerRect.left - this._offset + 7) * -1; // 7 minor adjustment to position pointer at edge of content
887
+ } else {
888
+ position.left = (window.innerWidth - openerRect.right + 7 - this._offset - getScrollbarWidth()) * -1; // 7 minor adjustment to position pointer at edge of content
889
+ }
890
+ } else {
891
+ if (!this._rtl) {
892
+ position.left = openerRect.left + openerRect.width + this._offset - 7; // 7 minor adjustment to position pointer at edge of content
893
+ } else {
894
+ position.right = window.innerWidth - openerRect.left - 7 + this._offset - getScrollbarWidth(); // 7 minor adjustment to position pointer at edge of content
895
+ }
817
896
  }
818
- }
819
897
 
820
- if (this._location === 'block-start') {
821
- position.bottom = window.innerHeight - openerRect.top + this._offset - 8;
822
- } else {
823
- position.top = openerRect.top + openerRect.height + this._offset - 7;
824
898
  }
825
899
 
826
900
  return position;
@@ -829,79 +903,124 @@ export const PopoverMixin = superclass => class extends superclass {
829
903
  #getPosition(spaceAround, openerRect, contentRect) {
830
904
  const position = {};
831
905
 
832
- if (this._location === 'block-end' || this._location === 'block-start') {
906
+ if (this._location === positionLocations.blockEnd || this._location === positionLocations.blockStart) {
833
907
 
834
908
  const xAdjustment = this.#getPositionXAdjustment(spaceAround, openerRect, contentRect);
835
909
  if (xAdjustment !== null) {
836
910
  if (!this._rtl) {
837
911
  position.left = openerRect.left + xAdjustment;
838
912
  } else {
839
- position.right = window.innerWidth - openerRect.left - openerRect.width + xAdjustment;
913
+ position.right = window.innerWidth - openerRect.left - openerRect.width + xAdjustment - getScrollbarWidth();
840
914
  }
841
915
  }
842
916
 
843
- if (this._location === 'block-start') {
917
+ if (this._location === positionLocations.blockStart) {
844
918
  position.bottom = window.innerHeight - openerRect.top + this._offset;
845
919
  } else {
846
920
  position.top = openerRect.top + openerRect.height + this._offset;
847
921
  }
848
922
 
849
- }
923
+ } else if (this._location === positionLocations.inlineEnd || this._location === positionLocations.inlineStart) {
850
924
 
851
- // todo: add position styles for inline-start and inline-end
925
+ const yAdjustment = this.#getPositionYAdjustment(spaceAround, openerRect, contentRect);
926
+ if (yAdjustment !== null) {
927
+ position.top = openerRect.top + yAdjustment;
928
+ }
929
+
930
+ if (this._location === positionLocations.inlineStart) {
931
+ if (!this._rtl) {
932
+ position.right = (openerRect.left - this._offset) * -1;
933
+ } else {
934
+ position.left = (window.innerWidth - openerRect.right - this._offset - getScrollbarWidth()) * -1;
935
+ }
936
+ } else {
937
+ if (!this._rtl) {
938
+ position.left = openerRect.left + openerRect.width + this._offset;
939
+ } else {
940
+ position.right = window.innerWidth - openerRect.left + this._offset - getScrollbarWidth();
941
+ }
942
+ }
943
+
944
+ }
852
945
 
853
946
  return position;
854
947
  }
855
948
 
856
949
  #getPositionXAdjustment(spaceAround, openerRect, contentRect) {
857
950
 
858
- if (this._location === 'block-end' || this._location === 'block-start') {
951
+ if (this._location !== positionLocations.blockEnd && this._location !== positionLocations.blockStart) return null;
952
+
953
+ const centerDelta = contentRect.width - openerRect.width;
954
+ const contentXAdjustment = centerDelta / 2;
859
955
 
860
- const centerDelta = contentRect.width - openerRect.width;
861
- const contentXAdjustment = centerDelta / 2;
956
+ if (this._preferredPosition.span === positionSpans.all && centerDelta <= 0) {
957
+ // center with target (opener wider than content)
958
+ return contentXAdjustment * -1;
959
+ }
960
+ if (this._preferredPosition.span === positionSpans.all && spaceAround.left > contentXAdjustment && spaceAround.right > contentXAdjustment) {
961
+ // center with target (content wider than opener and enough space around)
962
+ return contentXAdjustment * -1;
963
+ }
862
964
 
863
- if (this._preferredPosition.span === 'all' && centerDelta <= 0) {
864
- // center with target (opener wider than content)
865
- return contentXAdjustment * -1;
965
+ if (!this._rtl) {
966
+ if (spaceAround.left < contentXAdjustment) {
967
+ // slide content right (not enough space to center)
968
+ return spaceAround.left * -1;
969
+ } else if (spaceAround.right < contentXAdjustment) {
970
+ // slide content left (not enough space to center)
971
+ return (centerDelta * -1) + spaceAround.right;
866
972
  }
867
- if (this._preferredPosition.span === 'all' && spaceAround.left > contentXAdjustment && spaceAround.right > contentXAdjustment) {
868
- // center with target (content wider than opener and enough space around)
869
- return contentXAdjustment * -1;
973
+ } else {
974
+ if (spaceAround.left < contentXAdjustment) {
975
+ // slide content right (not enough space to center)
976
+ return (centerDelta * -1) + spaceAround.left;
977
+ } else if (spaceAround.right < contentXAdjustment) {
978
+ // slide content left (not enough space to center)
979
+ return spaceAround.right * -1;
870
980
  }
981
+ }
871
982
 
872
- if (!this._rtl) {
873
- if (spaceAround.left < contentXAdjustment) {
874
- // slide content right (not enough space to center)
875
- return spaceAround.left * -1;
876
- } else if (spaceAround.right < contentXAdjustment) {
877
- // slide content left (not enough space to center)
878
- return (centerDelta * -1) + spaceAround.right;
879
- }
983
+ if (this._preferredPosition.span !== positionSpans.all) {
984
+ // shift it (not enough space to align as requested)
985
+ const shift = Math.min((openerRect.width / 2) - (20 + pointerLength / 2), 0); // 20 ~= 1rem
986
+ if (this._preferredPosition.span === positionSpans.end) {
987
+ return shift;
880
988
  } else {
881
- if (spaceAround.left < contentXAdjustment) {
882
- // slide content right (not enough space to center)
883
- return (centerDelta * -1) + spaceAround.left;
884
- } else if (spaceAround.right < contentXAdjustment) {
885
- // slide content left (not enough space to center)
886
- return spaceAround.right * -1;
887
- }
989
+ return openerRect.width - contentRect.width - shift;
888
990
  }
991
+ }
889
992
 
890
- if (this._preferredPosition.span !== 'all') {
891
- // shift it (not enough space to align as requested)
892
- const shift = Math.min((openerRect.width / 2) - (20 + pointerLength / 2), 0); // 20 ~= 1rem
893
- if (this._preferredPosition.span === 'end') {
894
- return shift;
895
- } else {
896
- return openerRect.width - contentRect.width - shift;
897
- }
898
- }
993
+ return null;
899
994
 
995
+ }
996
+
997
+ #getPositionYAdjustment(spaceAround, openerRect, contentRect) {
998
+
999
+ if (this._location !== positionLocations.inlineEnd && this._location !== positionLocations.inlineStart) return null;
1000
+
1001
+ const centerDelta = contentRect.height - openerRect.height;
1002
+ const contentYAdjustment = centerDelta / 2;
1003
+
1004
+ if (this._preferredPosition.span === positionSpans.all && centerDelta <= 0) {
1005
+ // center with target (opener taller than content)
1006
+ return contentYAdjustment * -1;
900
1007
  }
901
1008
 
902
- // todo: add position styles for inline-start and inline-end
1009
+ if (this._preferredPosition.span === positionSpans.all && spaceAround.above > contentYAdjustment && spaceAround.below > contentYAdjustment) {
1010
+ // center with target (content wider than opener and enough space around)
1011
+ return contentYAdjustment * -1;
1012
+ }
1013
+
1014
+ if (spaceAround.above < contentYAdjustment) {
1015
+ // slide content down (not enough space to center)
1016
+ return spaceAround.above * -1;
1017
+ } else if (spaceAround.below < contentYAdjustment) {
1018
+ // slide content up (not enough space to center)
1019
+ return (centerDelta * -1) + spaceAround.below;
1020
+ }
903
1021
 
904
1022
  return null;
1023
+
905
1024
  }
906
1025
 
907
1026
  #getStyleMaps() {
@@ -1,7 +1,7 @@
1
1
  import '../colors/colors.js';
2
2
  import '../icons/icon.js';
3
- import { css, html, LitElement, unsafeCSS } from 'lit';
4
- import { getFocusPseudoClass } from '../../helpers/focus.js';
3
+ import { css, html, LitElement } from 'lit';
4
+ import { getFocusRingStyles } from '../../helpers/focus.js';
5
5
  import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
6
6
  import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
7
7
 
@@ -13,11 +13,10 @@ let focusStyleSheet;
13
13
  function getFocusStyleSheet() {
14
14
  if (!focusStyleSheet) {
15
15
  focusStyleSheet = new CSSStyleSheet();
16
- focusStyleSheet.replaceSync(css`
17
- .d2l-scroll-wrapper-focus:${unsafeCSS(getFocusPseudoClass())} {
18
- box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine), 0 2px 12px 0 rgba(0, 0, 0, 0.15);
19
- outline: none;
20
- }`);
16
+ focusStyleSheet.replaceSync(getFocusRingStyles(
17
+ '.d2l-scroll-wrapper-focus',
18
+ { extraStyles: css`box-shadow: 0 0 0 2px #ffffff, 0 2px 12px 0 rgba(0, 0, 0, 0.15);` }
19
+ ));
21
20
  }
22
21
  return focusStyleSheet;
23
22
  }
@@ -150,8 +150,9 @@ export const SwitchMixin = superclass => class extends FocusMixin(RtlMixin(super
150
150
  display: none;
151
151
  }
152
152
  .d2l-switch-inner:hover, .switch-hover {
153
- border-color: var(--d2l-color-celestine);
154
- box-shadow: 0 0 0 1px var(--d2l-color-celestine) inset;
153
+ border-color: transparent;
154
+ outline: 2px solid var(--d2l-color-celestine);
155
+ outline-offset: -2px;
155
156
  }
156
157
  @media (prefers-reduced-motion: reduce) {
157
158
  .d2l-switch-toggle,
@@ -3,10 +3,10 @@ import '../dropdown/dropdown.js';
3
3
  import '../dropdown/dropdown-menu.js';
4
4
  import '../icons/icon.js';
5
5
  import '../menu/menu.js';
6
- import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
6
+ import { css, html, LitElement, nothing } from 'lit';
7
7
  import { classMap } from 'lit/directives/class-map.js';
8
8
  import { FocusMixin } from '../../mixins/focus/focus-mixin.js';
9
- import { getFocusPseudoClass } from '../../helpers/focus.js';
9
+ import { getFocusRingStyles } from '../../helpers/focus.js';
10
10
  import { getUniqueId } from '../../helpers/uniqueId.js';
11
11
  import { ifDefined } from 'lit/directives/if-defined.js';
12
12
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
@@ -116,11 +116,7 @@ export class TableColSortButton extends LocalizeCoreElement(FocusMixin(LitElemen
116
116
  button:hover {
117
117
  background-color: var(--d2l-color-gypsum);
118
118
  }
119
- button:focus-visible,
120
- button:${unsafeCSS(getFocusPseudoClass())} {
121
- box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine);
122
- outline-style: none;
123
- }
119
+ ${getFocusRingStyles('button', { extraStyles: css`box-shadow: 0 0 0 2px #ffffff;` })}
124
120
  d2l-icon {
125
121
  margin-inline-start: 0.6rem;
126
122
  }
@@ -18,7 +18,7 @@ class TabCustom extends TabMixin(LitElement) {
18
18
  }
19
19
  ${getFocusRingStyles(
20
20
  pseudoClass => `:host(:${pseudoClass}) .d2l-tab-custom-content`,
21
- { extraStyles: 'border-radius: 0.3rem; color: var(--d2l-color-celestine);' }
21
+ { extraStyles: css`border-radius: 0.3rem; color: var(--d2l-color-celestine);` }
22
22
  )}
23
23
  `];
24
24
 
@@ -1,7 +1,7 @@
1
1
  import '../colors/colors.js';
2
- import { css, html, LitElement, unsafeCSS } from 'lit';
2
+ import { css, html, LitElement } from 'lit';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
- import { getFocusPseudoClass } from '../../helpers/focus.js';
4
+ import { getFocusRingStyles } from '../../helpers/focus.js';
5
5
  import { SkeletonMixin } from '../skeleton/skeleton-mixin.js';
6
6
 
7
7
  const keyCodes = {
@@ -32,6 +32,7 @@ class Tab extends SkeletonMixin(LitElement) {
32
32
  vertical-align: middle;
33
33
  }
34
34
  .d2l-tab-text {
35
+ --d2l-focus-ring-offset: 0;
35
36
  margin: 0.5rem;
36
37
  overflow: hidden;
37
38
  padding: 0.1rem;
@@ -67,11 +68,7 @@ class Tab extends SkeletonMixin(LitElement) {
67
68
  margin-inline-start: 0;
68
69
  width: calc(100% - 0.6rem);
69
70
  }
70
- :host(:${unsafeCSS(getFocusPseudoClass())}) > .d2l-tab-text {
71
- border-radius: 0.3rem;
72
- box-shadow: 0 0 0 2px var(--d2l-color-celestine);
73
- color: var(--d2l-color-celestine);
74
- }
71
+ ${getFocusRingStyles(pseudoClass => `:host(:${pseudoClass}) > .d2l-tab-text`, { extraStyles: css`border-radius: 0.3rem; color: var(--d2l-color-celestine);` })}
75
72
  :host([aria-selected="true"]:focus) {
76
73
  text-decoration: none;
77
74
  }
@@ -4,10 +4,10 @@ import '../../helpers/queueMicrotask.js';
4
4
  import './tab-internal.js';
5
5
  import { css, html, LitElement, unsafeCSS } from 'lit';
6
6
  import { cssEscape, findComposedAncestor, getOffsetParent } from '../../helpers/dom.js';
7
+ import { getFocusPseudoClass, getFocusRingStyles } from '../../helpers/focus.js';
7
8
  import { ArrowKeysMixin } from '../../mixins/arrow-keys/arrow-keys-mixin.js';
8
9
  import { bodyCompactStyles } from '../typography/styles.js';
9
10
  import { classMap } from 'lit/directives/class-map.js';
10
- import { getFocusPseudoClass } from '../../helpers/focus.js';
11
11
  import { ifDefined } from 'lit/directives/if-defined.js';
12
12
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
13
13
  import { repeat } from 'lit/directives/repeat.js';
@@ -159,9 +159,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
159
159
  .d2l-tabs-scroll-button:${unsafeCSS(getFocusPseudoClass())} {
160
160
  background-color: var(--d2l-color-gypsum);
161
161
  }
162
- .d2l-tabs-scroll-button:${unsafeCSS(getFocusPseudoClass())} {
163
- box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine);
164
- }
162
+ ${getFocusRingStyles('.d2l-tabs-scroll-button')}
165
163
  :host([skeleton]) .d2l-tabs-scroll-button {
166
164
  visibility: hidden;
167
165
  }
@@ -11519,6 +11519,11 @@
11519
11519
  "name": "d2l-test-popover",
11520
11520
  "path": "./components/popover/test/popover.js",
11521
11521
  "attributes": [
11522
+ {
11523
+ "name": "margin",
11524
+ "description": "Margin to include when computing space available.",
11525
+ "type": "number"
11526
+ },
11522
11527
  {
11523
11528
  "name": "max-height",
11524
11529
  "description": "Max-height. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.",
@@ -11580,6 +11585,12 @@
11580
11585
  }
11581
11586
  ],
11582
11587
  "properties": [
11588
+ {
11589
+ "name": "margin",
11590
+ "attribute": "margin",
11591
+ "description": "Margin to include when computing space available.",
11592
+ "type": "number"
11593
+ },
11583
11594
  {
11584
11595
  "name": "maxHeight",
11585
11596
  "attribute": "max-height",