@brightspace-ui/core 3.21.2 → 3.21.4

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.
@@ -70,7 +70,7 @@ class FilterDimensionSetDateTimeRangeValue extends LocalizeCoreElement(LitElemen
70
70
  this._dispatchFilterChangeEvent = false;
71
71
  this._enforceSingleSelection = true;
72
72
  this._filterSetValue = true;
73
- this._minWidth = 375;
73
+ this._minWidth = 390;
74
74
  this._noSearchSupport = true;
75
75
 
76
76
  this._handleDateChange = this._handleDateChange.bind(this);
@@ -149,6 +149,9 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
149
149
  margin-inline-start: -2rem;
150
150
  padding-block: 0.5rem;
151
151
  }
152
+ d2l-list-item.expanding-content {
153
+ overflow-y: hidden;
154
+ }
152
155
 
153
156
  .d2l-filter-dimension-set-value-text {
154
157
  -webkit-box-orient: vertical;
@@ -605,7 +608,10 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
605
608
  : nothing}
606
609
  </div>
607
610
  ${item.additionalContent ? html`
608
- <d2l-expand-collapse-content ?expanded="${item.selected}">
611
+ <d2l-expand-collapse-content
612
+ ?expanded="${item.selected}"
613
+ @d2l-expand-collapse-content-collapse="${this._handleExpandCollapse}"
614
+ @d2l-expand-collapse-content-expand="${this._handleExpandCollapse}">
609
615
  ${item.additionalContent()}
610
616
  </d2l-expand-collapse-content>
611
617
  ` : nothing}
@@ -841,6 +847,15 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
841
847
  ));
842
848
  }
843
849
 
850
+ async _handleExpandCollapse(e) {
851
+ const eventPromise = e.target.expanded ? e.detail.expandComplete : e.detail.collapseComplete;
852
+ const parentListItem = e.target.closest('d2l-list-item');
853
+ parentListItem.classList.add('expanding-content');
854
+
855
+ await eventPromise;
856
+ parentListItem.classList.remove('expanding-content');
857
+ }
858
+
844
859
  _handleSearch(e) {
845
860
  const dimension = this._getActiveDimension();
846
861
  const searchValue = e.detail.value.trim();
@@ -41,65 +41,48 @@ class InputDateTimeRangeTo extends SkeletonMixin(LocalizeCoreElement(LitElement)
41
41
  :host([top-margin]) {
42
42
  margin-top: calc(0.9rem - 7px);
43
43
  }
44
+ :host(:not([display-to])) .d2l-input-date-time-range-to-to {
45
+ display: none;
46
+ }
44
47
 
48
+ /* flex case (not wrapped) */
45
49
  .d2l-input-date-time-range-to-container {
50
+ column-gap: 1.5rem;
46
51
  display: flex;
47
52
  flex-wrap: wrap;
48
- margin-bottom: -1.2rem;
49
53
  }
54
+ :host([display-to]) div:not(.d2l-input-date-time-range-to-container-block).d2l-input-date-time-range-to-container {
55
+ column-gap: 0.9rem;
56
+ }
57
+ .d2l-input-date-time-range-end-container {
58
+ display: flex;
59
+ }
60
+ .d2l-input-date-time-range-end-container ::slotted(*) {
61
+ align-self: flex-end;
62
+ }
63
+
64
+ /* block case (wrapped) */
50
65
  .d2l-input-date-time-range-to-container-block.d2l-input-date-time-range-to-container {
51
66
  display: block;
67
+ margin-bottom: -1.2rem;
52
68
  }
53
-
54
- .d2l-input-date-time-range-start-container {
69
+ .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-start-container {
55
70
  margin-bottom: 1.2rem;
56
- margin-right: 1.5rem;
57
- }
58
- :host([dir="rtl"]) .d2l-input-date-time-range-start-container {
59
- margin-left: 1.5rem;
60
- margin-right: 0;
71
+ margin-inline: 0;
61
72
  }
62
- :host([display-to]) .d2l-input-date-time-range-start-container {
73
+ :host([display-to]) .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-start-container {
63
74
  margin-bottom: 0.6rem;
64
75
  }
65
- :host([display-to]) div:not(.d2l-input-date-time-range-to-container-block) > .d2l-input-date-time-range-start-container {
66
- margin-right: 0.9rem;
67
- }
68
- :host([display-to][dir="rtl"]) div:not(.d2l-input-date-time-range-to-container-block) > .d2l-input-date-time-range-start-container {
69
- margin-left: 0.9rem;
70
- margin-right: 0;
71
- }
72
- .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-start-container,
73
- :host([dir="rtl"]) .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-start-container {
74
- margin-left: 0;
75
- margin-right: 0;
76
- }
77
-
78
- :host(:not([display-to])) .d2l-input-date-time-range-to-to {
79
- display: none;
76
+ .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-end-container {
77
+ display: block;
78
+ margin-bottom: 1.2rem;
80
79
  }
81
- .d2l-input-date-time-range-to-to {
80
+ .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-to-to {
82
81
  display: inline-block;
83
82
  margin-bottom: 0.6rem;
84
- margin-right: 0.9rem;
85
83
  margin-top: auto;
86
84
  vertical-align: top;
87
85
  }
88
- :host([dir="rtl"]) .d2l-input-date-time-range-to-to {
89
- margin-left: 0.9rem;
90
- margin-right: 0;
91
- }
92
-
93
- .d2l-input-date-time-range-end-container {
94
- display: flex;
95
- margin-bottom: 1.2rem;
96
- }
97
- .d2l-input-date-time-range-to-container-block .d2l-input-date-time-range-end-container {
98
- display: block;
99
- }
100
- .d2l-input-date-time-range-end-container ::slotted(*) {
101
- align-self: flex-end;
102
- }
103
86
  `];
104
87
  }
105
88
 
@@ -131,10 +114,10 @@ class InputDateTimeRangeTo extends SkeletonMixin(LocalizeCoreElement(LitElement)
131
114
  <div class="d2l-input-date-time-range-start-container">
132
115
  <slot name="left"></slot>
133
116
  </div>
134
- <div class="d2l-input-date-time-range-end-container">
135
117
  <div class="d2l-body-small d2l-skeletize d2l-input-date-time-range-to-to">
136
118
  ${this.localize('components.input-date-time-range-to.to')}
137
119
  </div>
120
+ <div class="d2l-input-date-time-range-end-container">
138
121
  <slot name="right"></slot>
139
122
  </div>
140
123
  </div>
@@ -25,9 +25,9 @@
25
25
  <d2l-button-subtle id="close1" text="Close"></d2l-button-subtle>
26
26
  </d2l-test-popover>
27
27
  <script>
28
- const popover = document.querySelector('#popover1');
29
- document.querySelector('#open1').addEventListener('click', () => popover.opened = true);
30
- document.querySelector('#close1').addEventListener('click', () => popover.opened = false);
28
+ const popover1 = document.querySelector('#popover1');
29
+ document.querySelector('#open1').addEventListener('click', () => popover1.opened = !popover1.opened);
30
+ document.querySelector('#close1').addEventListener('click', () => popover1.opened = false);
31
31
  </script>
32
32
  </template>
33
33
  </d2l-demo-snippet>
@@ -1,5 +1,8 @@
1
1
  import '../colors/colors.js';
2
+ import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
2
3
  import { css, html } from 'lit';
4
+ import { getComposedActiveElement, getPreviousFocusableAncestor } from '../../helpers/focus.js';
5
+ import { isComposedAncestor } from '../../helpers/dom.js';
3
6
 
4
7
  const isSupported = ('popover' in HTMLElement.prototype);
5
8
 
@@ -15,7 +18,12 @@ export const PopoverMixin = superclass => class extends superclass {
15
18
  * @type {boolean}
16
19
  */
17
20
  opened: { type: Boolean, reflect: true },
18
- _useNativePopover: { type: Boolean, reflect: true, attribute: 'popover' }
21
+ /**
22
+ * Whether to disable auto-close/light-dismiss
23
+ * @type {boolean}
24
+ */
25
+ noAutoClose: { type: Boolean, reflect: true, attribute: 'no-auto-close' },
26
+ _useNativePopover: { type: String, reflect: true, attribute: 'popover' }
19
27
  };
20
28
  }
21
29
 
@@ -62,34 +70,110 @@ export const PopoverMixin = superclass => class extends superclass {
62
70
 
63
71
  constructor() {
64
72
  super();
73
+ this.noAutoClose = false;
65
74
  this.opened = false;
66
- this._useNativePopover = isSupported;
75
+ this._useNativePopover = isSupported ? 'manual' : undefined;
76
+ this._handleAutoCloseClick = this._handleAutoCloseClick.bind(this);
77
+ this._handleAutoCloseFocus = this._handleAutoCloseFocus.bind(this);
67
78
  }
68
79
 
69
80
  connectedCallback() {
70
81
  super.connectedCallback();
82
+ if (this.opened) this._addAutoCloseHandlers();
71
83
  }
72
84
 
73
85
  disconnectedCallback() {
74
86
  super.disconnectedCallback();
87
+ this._removeAutoCloseHandlers();
88
+ this._clearDismissible();
75
89
  }
76
90
 
77
91
  updated(changedProperties) {
78
92
  super.updated(changedProperties);
79
93
  if (changedProperties.has('opened')) {
80
94
 
95
+ if (this._useNativePopover) {
96
+ if (this.opened) this.showPopover();
97
+ else this.hidePopover();
98
+ }
99
+
100
+ this._previousFocusableAncestor = this.opened ? getPreviousFocusableAncestor(this, false, false) : null;
101
+
81
102
  if (this.opened) {
103
+ this._opener = getComposedActiveElement();
104
+ this._addAutoCloseHandlers();
105
+ this._dismissibleId = setDismissible(() => this._close());
82
106
  this.dispatchEvent(new CustomEvent('d2l-popover-open', { bubbles: true, composed: true }));
83
107
  } else if (changedProperties.get('opened') !== undefined) {
108
+ this._removeAutoCloseHandlers();
109
+ this._clearDismissible();
84
110
  this.dispatchEvent(new CustomEvent('d2l-popover-close', { bubbles: true, composed: true }));
85
111
  }
86
112
 
87
- if (this._useNativePopover) {
88
- if (this.opened) this.showPopover();
89
- else this.hidePopover();
90
- }
113
+ }
114
+ }
115
+
116
+ _addAutoCloseHandlers() {
117
+ this.addEventListener('blur', this._handleAutoCloseFocus, { capture: true });
118
+ document.body.addEventListener('focus', this._handleAutoCloseFocus, { capture: true });
119
+ document.addEventListener('click', this._handleAutoCloseClick, { capture: true });
120
+ }
121
+
122
+ _clearDismissible() {
123
+ if (!this._dismissibleId) return;
124
+ clearDismissible(this._dismissibleId);
125
+ this._dismissibleId = null;
126
+ }
127
+
128
+ _close() {
129
+ const hide = () => {
130
+ this.opened = false;
131
+ };
132
+
133
+ hide();
134
+ }
91
135
 
136
+ _handleAutoCloseClick(e) {
137
+
138
+ if (!this.opened || this.noAutoClose) return;
139
+
140
+ const rootTarget = e.composedPath()[0];
141
+ if (isComposedAncestor(this.shadowRoot.querySelector('.content'), rootTarget)
142
+ || (this._opener !== document.body && isComposedAncestor(this._opener, rootTarget))) {
143
+ return;
92
144
  }
145
+
146
+ this._close();
147
+ }
148
+
149
+ _handleAutoCloseFocus() {
150
+
151
+ // todo: try to use relatedTarget instead - this logic is largely copied as-is from dropdown simply to mitigate risk of this fragile code
152
+ setTimeout(() => {
153
+ // we ignore focusable ancestors othrwise the popover will close when user clicks empty space inside the popover
154
+ if (!this.opened
155
+ || this.noAutoClose
156
+ || !document.activeElement
157
+ || document.activeElement === this._previousFocusableAncestor
158
+ || document.activeElement === document.body) {
159
+ return;
160
+ }
161
+
162
+ const activeElement = getComposedActiveElement();
163
+ if (isComposedAncestor(this.shadowRoot.querySelector('.content'), activeElement)
164
+ || activeElement === this._opener) {
165
+ return;
166
+ }
167
+
168
+ this._close();
169
+ }, 0);
170
+
171
+ }
172
+
173
+ _removeAutoCloseHandlers() {
174
+ this.removeEventListener('blur', this._handleAutoCloseFocus, { capture: true });
175
+ document.body?.removeEventListener('focus', this._handleAutoCloseFocus, { capture: true }); // DE41322: document.body can be null in some scenarios
176
+ document.removeEventListener('click', this._handleAutoCloseClick, { capture: true });
93
177
  }
94
178
 
95
179
  _renderPopover() {
@@ -10717,6 +10717,12 @@
10717
10717
  "name": "d2l-test-popover",
10718
10718
  "path": "./components/popover/test/popover.js",
10719
10719
  "attributes": [
10720
+ {
10721
+ "name": "no-auto-close",
10722
+ "description": "Whether to disable auto-close/light-dismiss",
10723
+ "type": "boolean",
10724
+ "default": "false"
10725
+ },
10720
10726
  {
10721
10727
  "name": "opened",
10722
10728
  "description": "Whether the popover is open or not",
@@ -10725,6 +10731,13 @@
10725
10731
  }
10726
10732
  ],
10727
10733
  "properties": [
10734
+ {
10735
+ "name": "noAutoClose",
10736
+ "attribute": "no-auto-close",
10737
+ "description": "Whether to disable auto-close/light-dismiss",
10738
+ "type": "boolean",
10739
+ "default": "false"
10740
+ },
10728
10741
  {
10729
10742
  "name": "opened",
10730
10743
  "attribute": "opened",
package/lang/en.js CHANGED
@@ -29,8 +29,8 @@ export default {
29
29
  "components.filter-dimension-set-date-text-value.textDays": "{num, plural, =0 {Today} one {Last {num} day} other {Last {num} days}}",
30
30
  "components.filter-dimension-set-date-text-value.textMonths": "Last {num} months",
31
31
  "components.filter-dimension-set-date-time-range-value.valueTextRange": "{startValue} to {endValue}",
32
- "components.filter-dimension-set-date-time-range-value.valueTextRangeStartOnly": "Start: {startValue}",
33
- "components.filter-dimension-set-date-time-range-value.valueTextRangeEndOnly": "End: {endValue}",
32
+ "components.filter-dimension-set-date-time-range-value.valueTextRangeStartOnly": "Start Date: {startValue}",
33
+ "components.filter-dimension-set-date-time-range-value.valueTextRangeEndOnly": "End Date: {endValue}",
34
34
  "components.filter-dimension-set-date-time-range-value.text": "Custom date range",
35
35
  "components.form-element.defaultError": "{label} is invalid.",
36
36
  "components.form-element.defaultFieldLabel": "Field",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.21.2",
3
+ "version": "3.21.4",
4
4
  "description": "A collection of accessible, free, open-source web components for building Brightspace applications",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/BrightspaceUI/core.git",