@brightspace-ui/core 2.38.3 → 2.40.0

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.
@@ -1,6 +1,7 @@
1
1
  import '../button/button.js';
2
2
  import '../button/button-subtle.js';
3
3
  import { css, html, LitElement, nothing } from 'lit';
4
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
5
 
5
6
  /**
6
7
  * `d2l-empty-state-action-button` is an empty state action component that can be placed inside of the default slot of `empty-state-simple` or `empty-state-illustrated` to add a button action to the component.
@@ -70,6 +71,7 @@ class EmptyStateActionButton extends LitElement {
70
71
  : html`<d2l-button-subtle
71
72
  class="d2l-empty-state-action"
72
73
  @click=${this._handleActionClick}
74
+ h-align="${ifDefined(!this._illustrated ? 'text' : undefined)}"
73
75
  ?slim=${!this._illustrated}
74
76
  text=${this.text}>
75
77
  </d2l-button-subtle>`;
@@ -298,6 +298,10 @@ A tag-list allowing the user to see (and remove) the currently applied filters.
298
298
  | `label` | String | The text displayed in this component's label |
299
299
  <!-- docs: end hidden content -->
300
300
 
301
+ ## Filter Overflow Group [d2l-filter-overflow-group]
302
+
303
+ **Coming Soon!**
304
+
301
305
  ## Filter Dimension: Date [d2l-filter-dimension-date]
302
306
 
303
307
  **Coming Soon!**
@@ -0,0 +1,168 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <link rel="stylesheet" href="../../demo/styles.css" type="text/css">
6
+ <script type="module">
7
+ import '../../demo/demo-page.js';
8
+ import '../../filter/filter.js';
9
+ import '../../filter/filter-dimension-set.js';
10
+ import '../../filter/filter-dimension-set-value.js';
11
+ import '../../filter/filter-overflow-group.js';
12
+ import '../../filter/filter-tags.js';
13
+ </script>
14
+ <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1.0">
15
+ <meta charset="UTF-8">
16
+ <style>
17
+ d2l-filter-tags {
18
+ padding-top: 1rem;
19
+ }
20
+ </style>
21
+ </head>
22
+
23
+ <body unresolved>
24
+
25
+ <d2l-demo-page page-title="d2l-filter-overflow-group">
26
+
27
+ <h2>Filter Overflow Group</h2>
28
+ <d2l-demo-snippet>
29
+ <template>
30
+ <d2l-filter-overflow-group>
31
+ <d2l-filter>
32
+ <d2l-filter-dimension-set key="skill" text="Skill">
33
+ <d2l-filter-dimension-set-value key="communication" text="Fall"></d2l-filter-dimension-set-value>
34
+ <d2l-filter-dimension-set-value key="leadership" text="Winter"></d2l-filter-dimension-set-value>
35
+ <d2l-filter-dimension-set-value key="management" text="Spring"></d2l-filter-dimension-set-value>
36
+ <d2l-filter-dimension-set-value key="planning" text="Summer"></d2l-filter-dimension-set-value>
37
+ </d2l-filter-dimension-set>
38
+ </d2l-filter>
39
+ <d2l-filter>
40
+ <d2l-filter-dimension-set key="type" text="Type" selection-single>
41
+ <d2l-filter-dimension-set-value key="certificate" text="Certificate"></d2l-filter-dimension-set-value>
42
+ <d2l-filter-dimension-set-value key="degree" text="Degree"></d2l-filter-dimension-set-value>
43
+ <d2l-filter-dimension-set-value key="diploma" text="Diploma"></d2l-filter-dimension-set-value>
44
+ <d2l-filter-dimension-set-value key="course" text="Course"></d2l-filter-dimension-set-value>
45
+ </d2l-filter-dimension-set>
46
+ </d2l-filter>
47
+ <d2l-filter>
48
+ <d2l-filter-dimension-set key="provider" text="Semester3">
49
+ <d2l-filter-dimension-set-value key="mcmaster" text="McMaster"></d2l-filter-dimension-set-value>
50
+ <d2l-filter-dimension-set-value key="powered" text="PowerED"></d2l-filter-dimension-set-value>
51
+ <d2l-filter-dimension-set-value key="guelph" text="University of Guelph"></d2l-filter-dimension-set-value>
52
+ <d2l-filter-dimension-set-value key="manitoba" text="University of Manitoba"></d2l-filter-dimension-set-value>
53
+ </d2l-filter-dimension-set>
54
+ </d2l-filter>
55
+ <d2l-filter>
56
+ <d2l-filter-dimension-set key="format" text="Format">
57
+ <d2l-filter-dimension-set-value key="selfpaced" text="Self-Paced"></d2l-filter-dimension-set-value>
58
+ <d2l-filter-dimension-set-value key="instructor" text="Instructor Lead" selected></d2l-filter-dimension-set-value>
59
+ </d2l-filter-dimension-set>
60
+ </d2l-filter>
61
+ <d2l-filter>
62
+ <d2l-filter-dimension-set key="language" text="Language" selection-single>
63
+ <d2l-filter-dimension-set-value key="english" text="English"></d2l-filter-dimension-set-value>
64
+ <d2l-filter-dimension-set-value key="french" text="French"></d2l-filter-dimension-set-value>
65
+ <d2l-filter-dimension-set-value key="spanish" text="Spanish"></d2l-filter-dimension-set-value>
66
+ </d2l-filter-dimension-set>
67
+ </d2l-filter>
68
+ <d2l-filter>
69
+ <d2l-filter-dimension-set key="course" text="Course" select-all>
70
+ <d2l-filter-dimension-set-value key="art" text="Art"></d2l-filter-dimension-set-value>
71
+ <d2l-filter-dimension-set-value key="astronomy" text="Astronomy" selected></d2l-filter-dimension-set-value>
72
+ <d2l-filter-dimension-set-value key="biology" text="Biology"></d2l-filter-dimension-set-value>
73
+ <d2l-filter-dimension-set-value key="chemistry" text="Chemistry"></d2l-filter-dimension-set-value>
74
+ <d2l-filter-dimension-set-value key="drama" text="Drama"></d2l-filter-dimension-set-value>
75
+ <d2l-filter-dimension-set-value key="english" text="English"></d2l-filter-dimension-set-value>
76
+ <d2l-filter-dimension-set-value key="how-to" text="How To Write a How To Article With a Flashy Title"></d2l-filter-dimension-set-value>
77
+ <d2l-filter-dimension-set-value key="math" text="Math"></d2l-filter-dimension-set-value>
78
+ <d2l-filter-dimension-set-value key="physics" text="Physics"></d2l-filter-dimension-set-value>
79
+ <d2l-filter-dimension-set-value key="stats" text="Statistics"></d2l-filter-dimension-set-value>
80
+ <d2l-filter-dimension-set-value key="writerscraft" text="Writer's Craft"></d2l-filter-dimension-set-value>
81
+ </d2l-filter-dimension-set>
82
+ <d2l-filter-dimension-set key="duration" text="Duration">
83
+ <d2l-filter-dimension-set-value key="lessthanthree" text="< 3 months"></d2l-filter-dimension-set-value>
84
+ <d2l-filter-dimension-set-value key="threetosix" text="3-6 months"></d2l-filter-dimension-set-value>
85
+ <d2l-filter-dimension-set-value key="sixtotwelve" text="6-12 months"></d2l-filter-dimension-set-value>
86
+ </d2l-filter-dimension-set>
87
+ <d2l-filter-dimension-set key="hoursperweek" text="SemesterNested" selection-single>
88
+ <d2l-filter-dimension-set-value key="lessthanfive" text="< 5 hrs/week"></d2l-filter-dimension-set-value>
89
+ <d2l-filter-dimension-set-value key="fivetoten" text="5-10 hrs/week" selected></d2l-filter-dimension-set-value>
90
+ <d2l-filter-dimension-set-value key="tentotwenty" text="10-20 hrs/week"></d2l-filter-dimension-set-value>
91
+ </d2l-filter-dimension-set>
92
+ </d2l-filter>
93
+ </d2l-filter-overflow-group>
94
+ </template>
95
+ </d2l-demo-snippet>
96
+
97
+ <h2>Filter Overflow Group with Tags (external to d2l-filter-overflow-group)</h2>
98
+ <d2l-demo-snippet>
99
+ <template>
100
+ <d2l-filter-overflow-group>
101
+ <d2l-filter id="filter1">
102
+ <d2l-filter-dimension-set key="skill" text="Skill">
103
+ <d2l-filter-dimension-set-value key="communication" text="Fall"></d2l-filter-dimension-set-value>
104
+ <d2l-filter-dimension-set-value key="leadership" text="Winter"></d2l-filter-dimension-set-value>
105
+ <d2l-filter-dimension-set-value key="management" text="Spring"></d2l-filter-dimension-set-value>
106
+ <d2l-filter-dimension-set-value key="planning" text="Summer"></d2l-filter-dimension-set-value>
107
+ </d2l-filter-dimension-set>
108
+ </d2l-filter>
109
+ <d2l-filter id="filter2">
110
+ <d2l-filter-dimension-set key="type" text="Type" selection-single>
111
+ <d2l-filter-dimension-set-value key="certificate" text="Certificate"></d2l-filter-dimension-set-value>
112
+ <d2l-filter-dimension-set-value key="degree" text="Degree"></d2l-filter-dimension-set-value>
113
+ <d2l-filter-dimension-set-value key="diploma" text="Diploma"></d2l-filter-dimension-set-value>
114
+ <d2l-filter-dimension-set-value key="course" text="Course"></d2l-filter-dimension-set-value>
115
+ </d2l-filter-dimension-set>
116
+ </d2l-filter>
117
+ <d2l-filter id="filter3">
118
+ <d2l-filter-dimension-set key="provider" text="Semester3">
119
+ <d2l-filter-dimension-set-value key="mcmaster" text="McMaster"></d2l-filter-dimension-set-value>
120
+ <d2l-filter-dimension-set-value key="powered" text="PowerED"></d2l-filter-dimension-set-value>
121
+ <d2l-filter-dimension-set-value key="guelph" text="University of Guelph"></d2l-filter-dimension-set-value>
122
+ <d2l-filter-dimension-set-value key="manitoba" text="University of Manitoba"></d2l-filter-dimension-set-value>
123
+ </d2l-filter-dimension-set>
124
+ </d2l-filter>
125
+ <d2l-filter id="filter4">
126
+ <d2l-filter-dimension-set key="format" text="Format">
127
+ <d2l-filter-dimension-set-value key="selfpaced" text="Self-Paced"></d2l-filter-dimension-set-value>
128
+ <d2l-filter-dimension-set-value key="instructor" text="Instructor Lead" selected></d2l-filter-dimension-set-value>
129
+ </d2l-filter-dimension-set>
130
+ </d2l-filter>
131
+ <d2l-filter id="filter5">
132
+ <d2l-filter-dimension-set key="language" text="Language" selection-single>
133
+ <d2l-filter-dimension-set-value key="english" text="English"></d2l-filter-dimension-set-value>
134
+ <d2l-filter-dimension-set-value key="french" text="French"></d2l-filter-dimension-set-value>
135
+ <d2l-filter-dimension-set-value key="spanish" text="Spanish"></d2l-filter-dimension-set-value>
136
+ </d2l-filter-dimension-set>
137
+ </d2l-filter>
138
+ <d2l-filter id="filter6">
139
+ <d2l-filter-dimension-set key="course" text="Course" select-all>
140
+ <d2l-filter-dimension-set-value key="art" text="Art"></d2l-filter-dimension-set-value>
141
+ <d2l-filter-dimension-set-value key="astronomy" text="Astronomy" selected></d2l-filter-dimension-set-value>
142
+ <d2l-filter-dimension-set-value key="biology" text="Biology"></d2l-filter-dimension-set-value>
143
+ <d2l-filter-dimension-set-value key="chemistry" text="Chemistry"></d2l-filter-dimension-set-value>
144
+ <d2l-filter-dimension-set-value key="drama" text="Drama"></d2l-filter-dimension-set-value>
145
+ <d2l-filter-dimension-set-value key="english" text="English"></d2l-filter-dimension-set-value>
146
+ <d2l-filter-dimension-set-value key="how-to" text="How To Write a How To Article With a Flashy Title"></d2l-filter-dimension-set-value>
147
+ <d2l-filter-dimension-set-value key="math" text="Math"></d2l-filter-dimension-set-value>
148
+ <d2l-filter-dimension-set-value key="physics" text="Physics"></d2l-filter-dimension-set-value>
149
+ <d2l-filter-dimension-set-value key="stats" text="Statistics"></d2l-filter-dimension-set-value>
150
+ <d2l-filter-dimension-set-value key="writerscraft" text="Writer's Craft"></d2l-filter-dimension-set-value>
151
+ </d2l-filter-dimension-set>
152
+ <d2l-filter-dimension-set key="duration" text="Duration">
153
+ <d2l-filter-dimension-set-value key="lessthanthree" text="< 3 months"></d2l-filter-dimension-set-value>
154
+ <d2l-filter-dimension-set-value key="threetosix" text="3-6 months"></d2l-filter-dimension-set-value>
155
+ <d2l-filter-dimension-set-value key="sixtotwelve" text="6-12 months"></d2l-filter-dimension-set-value>
156
+ </d2l-filter-dimension-set>
157
+ <d2l-filter-dimension-set key="hoursperweek" text="SemesterNested" selection-single>
158
+ <d2l-filter-dimension-set-value key="lessthanfive" text="< 5 hrs/week"></d2l-filter-dimension-set-value>
159
+ <d2l-filter-dimension-set-value key="fivetoten" text="5-10 hrs/week" selected></d2l-filter-dimension-set-value>
160
+ <d2l-filter-dimension-set-value key="tentotwenty" text="10-20 hrs/week"></d2l-filter-dimension-set-value>
161
+ </d2l-filter-dimension-set>
162
+ </d2l-filter-overflow-group>
163
+ <d2l-filter-tags filter-ids="filter1 filter2 filter3 filter4 filter5 filter6" label="Applied Filters:"></d2l-filter-tags>
164
+ </template>
165
+ </d2l-demo-snippet>
166
+ </d2l-demo-page>
167
+ </body>
168
+ </html>
@@ -0,0 +1,28 @@
1
+ import { css, LitElement } from 'lit';
2
+ import { OverflowGroupMixin } from '../overflow-group/overflow-group-mixin.js';
3
+
4
+ /**
5
+ * A component that can be used to display a group of filters that will be put into an overflow filter when they no longer fit on the first line of their container
6
+ * @slot - d2l-filters to be added to the container
7
+ */
8
+ class FilterOverflowGroup extends OverflowGroupMixin(LitElement) {
9
+
10
+ static get styles() {
11
+ return [super.styles, css`
12
+ ::slotted(d2l-filter) {
13
+ margin-right: 0.3rem;
14
+ }
15
+ `];
16
+ }
17
+
18
+ convertToOverflowItem(node) {
19
+ const tagName = node.tagName.toLowerCase();
20
+ if (tagName !== 'd2l-filter') console.warn(`d2l-filter-overflow-group: ${tagName} is invalid in this group. This group should only contain d2l-filter direct child elements.`);
21
+ }
22
+
23
+ getOverflowContainer() {
24
+ }
25
+
26
+ }
27
+
28
+ customElements.define('d2l-filter-overflow-group', FilterOverflowGroup);
@@ -1,5 +1,5 @@
1
1
  # Overflow Groups
2
- The `d2l-overflow-group` element can be used to add responsiveness to a set of buttons, links or menus.
2
+ The `d2l-overflow-group` element can be used to add responsiveness to a set of buttons, links or menus. The `OverflowGroupMixin` allows for using the chomping logic without having to use those specific element types.
3
3
 
4
4
  <!-- docs: demo autoSize:false display:block size:medium -->
5
5
  ```html
@@ -70,3 +70,41 @@ Items added to this container element will no longer wrap onto a second line whe
70
70
 
71
71
  Looking for an enhancement not listed here? Create a GitHub issue!
72
72
  <!-- docs: end hidden content -->
73
+
74
+ ## OverflowGroupMixin
75
+ This mixin allows for creation of an overflow group that handles chomping when using elements that are not buttons, links, or menus, or when wanting an overflow container that is not a `d2l-dropdown`.
76
+
77
+ ### How to Use
78
+
79
+ **Import:**
80
+ ```javascript
81
+ import { OverflowGroupMixin } from '@brightspace-ui/core/components/overflow-group/overflow-group-mixin.js';
82
+
83
+ class OtherOverflowGroup extends OverflowGroupMixin(LitElement) {
84
+ ...
85
+ ```
86
+
87
+ **Styles:**
88
+
89
+ ```javascript
90
+ static get styles() {
91
+ return [ super.styles ];
92
+ }
93
+ ```
94
+
95
+ **Functionality:**
96
+
97
+ The functions `getOverflowContainer` and `convertToOverflowItem` need to be implemented by consumers of the mixin.
98
+
99
+ ```javascript
100
+ convertToOverflowItem(node) {
101
+ // return html of overflow item. For example:
102
+ return html`<d2l-menu-item text="${node.text}"></d2l-menu-item>`;
103
+ }
104
+
105
+ getOverflowContainer(overflowItems, mini) {
106
+ // return html of overflow menu. "mini" specifies if smaller menu option should be used, where applicable. For example:
107
+ if (mini) html`<d2l-dropdown-context-menu text="Overflow Menu"><d2l-dropdown-menu>${overflowItems}</d2l-dropdown-menu></d2l-dropdown-context-menu>`;
108
+ else return html`<d2l-dropdown-button text="Overflow Menu"><d2l-dropdown-menu>${overflowItems}</d2l-dropdown-menu></d2l-dropdown-button>`;
109
+ }
110
+ ```
@@ -0,0 +1,353 @@
1
+ import { css, html, nothing } from 'lit';
2
+ import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
3
+ import { offscreenStyles } from '../offscreen/offscreen.js';
4
+ import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
5
+
6
+ export const OVERFLOW_CLASS = 'd2l-overflow-container';
7
+ export const OVERFLOW_MINI_CLASS = 'd2l-overflow-container-mini';
8
+
9
+ const AUTO_SHOW_CLASS = 'd2l-button-group-show';
10
+ const AUTO_NO_SHOW_CLASS = 'd2l-button-group-no-show';
11
+
12
+ const OPENER_TYPE = {
13
+ DEFAULT: 'default',
14
+ ICON: 'icon'
15
+ };
16
+
17
+ async function filterAsync(arr, callback) {
18
+ const fail = Symbol();
19
+ const results = await Promise.all(arr.map(async item => {
20
+ const callbackResult = await callback(item);
21
+ return callbackResult ? item : fail;
22
+ }));
23
+ return results.filter(i => i !== fail);
24
+ }
25
+
26
+ export const OverflowGroupMixin = superclass => class extends LocalizeCoreElement(superclass) {
27
+
28
+ static get properties() {
29
+ return {
30
+ /**
31
+ * Use predefined classes on slot elements to set min and max slotted items to show
32
+ * @type {boolean}
33
+ */
34
+ autoShow: {
35
+ type: Boolean,
36
+ attribute: 'auto-show',
37
+ },
38
+ /**
39
+ * minimum amount of slotted items to show
40
+ * @type {number}
41
+ */
42
+ minToShow: {
43
+ type: Number,
44
+ reflect: true,
45
+ attribute: 'min-to-show',
46
+ },
47
+ /**
48
+ * maximum amount of slotted items to show
49
+ * @type {number}
50
+ */
51
+ maxToShow: {
52
+ type: Number,
53
+ reflect: true,
54
+ attribute: 'max-to-show',
55
+ },
56
+ /**
57
+ * @ignore
58
+ */
59
+ openerType: {
60
+ type: String,
61
+ attribute: 'opener-type'
62
+ },
63
+ _chompIndex: {
64
+ state: true
65
+ },
66
+ _mini: {
67
+ state: true
68
+ }
69
+ };
70
+ }
71
+
72
+ static get styles() {
73
+ return [offscreenStyles, css`
74
+ :host {
75
+ display: block;
76
+ }
77
+ :host([hidden]) {
78
+ display: none;
79
+ }
80
+ .d2l-overflow-group-container {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ justify-content: var(--d2l-overflow-group-justify-content, normal);
84
+ }
85
+ .d2l-overflow-group-container ::slotted([data-is-chomped]) {
86
+ display: none !important;
87
+ }
88
+ `];
89
+ }
90
+
91
+ constructor() {
92
+ super();
93
+
94
+ this._handleItemMutation = this._handleItemMutation.bind(this);
95
+ this._handleResize = this._handleResize.bind(this);
96
+ this._resizeObserver = new ResizeObserver((entries) => requestAnimationFrame(() => this._handleResize(entries)));
97
+
98
+ this._isObserving = false;
99
+ this._mini = this.openerType === OPENER_TYPE.ICON;
100
+ this._overflowContainerHidden = false;
101
+ this._slotItems = [];
102
+
103
+ this.autoShow = false;
104
+ this.maxToShow = -1;
105
+ this.minToShow = 1;
106
+ this.openerType = OPENER_TYPE.DEFAULT;
107
+ }
108
+
109
+ disconnectedCallback() {
110
+ super.disconnectedCallback();
111
+
112
+ if (this._isObserving) {
113
+ this._isObserving = false;
114
+ this._resizeObserver.disconnect();
115
+ }
116
+ }
117
+
118
+ render() {
119
+ const chompedOverflowItems = this._overflowItems ? this._overflowItems.slice(this._chompIndex) : [];
120
+ const overflowContainer = (!this._overflowContainerHidden && this._overflowItems)
121
+ ? this.getOverflowContainer(chompedOverflowItems, this._mini)
122
+ : nothing;
123
+
124
+ this._slotItems.forEach((element, index) => {
125
+ if (!this._overflowContainerHidden && index >= this._chompIndex) {
126
+ element.setAttribute('data-is-chomped', '');
127
+ } else {
128
+ element.removeAttribute('data-is-chomped');
129
+ }
130
+ });
131
+
132
+ return html`
133
+ <div class="d2l-overflow-group-container">
134
+ <slot @slotchange="${this._handleSlotChange}"></slot>
135
+ ${overflowContainer}
136
+ </div>
137
+ `;
138
+ }
139
+
140
+ update(changedProperties) {
141
+ super.update(changedProperties);
142
+
143
+ if (!this._isObserving) {
144
+ this._isObserving = true;
145
+ this._resizeObserver.observe(this.shadowRoot.querySelector('.d2l-overflow-group-container'));
146
+ }
147
+
148
+ if (changedProperties.has('autoShow') && this.autoShow) {
149
+ this._autoDetectBoundaries(this._slotItems);
150
+ }
151
+
152
+ if (changedProperties.has('minToShow')
153
+ || changedProperties.has('maxToShow')) {
154
+ this._chomp();
155
+ }
156
+
157
+ // Slight hack to get the overflow container width the first time it renders
158
+ if (!this._overflowContainerWidth) {
159
+ // this action needs to be deferred until first render of our overflow container
160
+ requestAnimationFrame(() => {
161
+ this._chomp();
162
+ });
163
+ }
164
+ }
165
+
166
+ convertToOverflowItem() {
167
+ throw new Error('OverflowGroupMixin.convertToOverflowItem must be overridden');
168
+ }
169
+
170
+ getOverflowContainer() {
171
+ throw new Error('OverflowGroupMixin.getOverflowContainer must be overridden');
172
+ }
173
+
174
+ _autoDetectBoundaries(items) {
175
+ if (!items) return;
176
+
177
+ let minToShow, maxToShow;
178
+ for (let i = 0; i < items.length; i++) {
179
+ if (!items[i].classList) continue;
180
+
181
+ if (items[i].classList.contains(AUTO_SHOW_CLASS)) {
182
+ minToShow = i + 1;
183
+ }
184
+ if (maxToShow === undefined && items[i].classList.contains(AUTO_NO_SHOW_CLASS)) {
185
+ maxToShow = i;
186
+ }
187
+ }
188
+
189
+ if (minToShow !== undefined) {
190
+ this.minToShow = minToShow;
191
+ }
192
+ if (maxToShow !== undefined) {
193
+ this.maxToShow = maxToShow;
194
+ }
195
+ }
196
+
197
+ _chomp() {
198
+ if (!this.shadowRoot || !this._itemLayouts) return;
199
+
200
+ this._overflowContainer = this.shadowRoot.querySelector(`.${OVERFLOW_CLASS}`);
201
+ this._overflowContainerMini = this.shadowRoot.querySelector(`.${OVERFLOW_MINI_CLASS}`);
202
+ if (this.openerType === OPENER_TYPE.ICON && this._overflowContainerMini) {
203
+ this._overflowContainerWidth = this._overflowContainerMini.offsetWidth;
204
+ } else if (this._overflowContainer) {
205
+ this._overflowContainerWidth = this._overflowContainer.offsetWidth;
206
+ }
207
+ this._overflowContainerWidth = this._overflowContainerWidth || 0;
208
+
209
+ const showing = {
210
+ count: 0,
211
+ width: 0
212
+ };
213
+
214
+ let isSoftOverflowing, isForcedOverflowing;
215
+ for (let i = 0; i < this._itemLayouts.length; i++) {
216
+ const itemLayout = this._itemLayouts[i];
217
+
218
+ // handle minimum items to show
219
+ if (showing.count < this.minToShow) {
220
+ showing.width += itemLayout.width;
221
+ showing.count += 1;
222
+ itemLayout.trigger = 'force-show';
223
+ itemLayout.isChomped = false;
224
+ continue;
225
+ }
226
+
227
+ // handle maximum items to show
228
+ if (this.maxToShow >= 0 && showing.count >= this.maxToShow) {
229
+ isForcedOverflowing = true;
230
+ itemLayout.isChomped = true;
231
+ itemLayout.trigger = 'force-hide';
232
+ continue;
233
+ }
234
+
235
+ // chomp or unchomp based on space available, and we've already handled min/max above
236
+ if (!isSoftOverflowing && showing.width + itemLayout.width < this._availableWidth) {
237
+ showing.width += itemLayout.width;
238
+ showing.count += 1;
239
+ itemLayout.isChomped = false;
240
+ itemLayout.trigger = 'soft-show';
241
+
242
+ } else {
243
+ isSoftOverflowing = true;
244
+ itemLayout.isChomped = true;
245
+ itemLayout.trigger = 'soft-hide';
246
+
247
+ }
248
+
249
+ }
250
+ // if there is at least one showing and no more to be hidden, enable collapsing overflow container to mini overflow container
251
+ this._overflowContainerHidden = this._itemLayouts.length === showing.count;
252
+ if (!this._overflowContainerHidden && (isSoftOverflowing || isForcedOverflowing)) {
253
+ for (let j = this._itemLayouts.length; j--;) {
254
+ if (showing.width + this._overflowContainerWidth < this._availableWidth) {
255
+ break;
256
+ }
257
+ const itemLayoutOverflowing = this._itemLayouts[j];
258
+ if (itemLayoutOverflowing.trigger !== 'soft-show') {
259
+ continue;
260
+ }
261
+ showing.width -= itemLayoutOverflowing.width;
262
+ showing.count -= 1;
263
+ isSoftOverflowing = true;
264
+ itemLayoutOverflowing.trigger = 'soft-hide';
265
+ itemLayoutOverflowing.isChomped = true;
266
+ }
267
+ }
268
+ const overflowOverflowing = (showing.width + this._overflowContainerWidth >= this._availableWidth);
269
+ const swapToMini = overflowOverflowing && !this._overflowContainerHidden;
270
+
271
+ this._mini = this.openerType === OPENER_TYPE.ICON || swapToMini;
272
+ this._chompIndex = this._overflowContainerHidden ? null : showing.count;
273
+
274
+ /** Dispatched when there is an update performed to the overflow group */
275
+ this.dispatchEvent(new CustomEvent('d2l-overflow-group-updated', { composed: false, bubbles: true }));
276
+ }
277
+
278
+ _getItemLayouts(filteredNodes) {
279
+ const items = filteredNodes.map((node) => {
280
+ const computedStyles = window.getComputedStyle(node);
281
+
282
+ return {
283
+ type: node.tagName.toLowerCase(),
284
+ isChomped: false,
285
+ isHidden: computedStyles.display === 'none',
286
+ width: Math.ceil(parseFloat(computedStyles.width) || 0)
287
+ + parseInt(computedStyles.marginRight) || 0
288
+ + parseInt(computedStyles.marginLeft) || 0,
289
+ node: node
290
+ };
291
+ });
292
+
293
+ return items.filter(({ isHidden }) => !isHidden);
294
+ }
295
+
296
+ async _getItems() {
297
+ // get the items from the slot
298
+ this._slotItems = await this._getSlotItems();
299
+ // convert them to layout items (calculate widths)
300
+ this._itemLayouts = this._getItemLayouts(this._slotItems);
301
+ // convert to overflow items (for overflow container)
302
+ this._overflowItems = this._slotItems.map((node) => this.convertToOverflowItem(node));
303
+ }
304
+
305
+ async _getSlotItems() {
306
+ const filteredNodes = await filterAsync(this.shadowRoot.querySelector('slot').assignedNodes({ flatten: true }), async(node) => {
307
+ if (node.nodeType !== Node.ELEMENT_NODE) return false;
308
+ if (node.updateComplete) await node.updateComplete;
309
+ return node.tagName.toLowerCase() !== 'template';
310
+ });
311
+
312
+ return filteredNodes;
313
+ }
314
+
315
+ _handleItemMutation(mutations) {
316
+ if (!mutations || mutations.length === 0) return;
317
+ if (this._updateOverflowItemsRequested) return;
318
+
319
+ this._updateOverflowItemsRequested = true;
320
+ setTimeout(() => {
321
+ this._overflowItems = this._slotItems.map(node => this.convertToOverflowItem(node));
322
+ this._updateOverflowItemsRequested = false;
323
+ this.requestUpdate();
324
+ }, 0);
325
+ }
326
+
327
+ _handleResize(entries) {
328
+ this._availableWidth = Math.ceil(entries[0].contentRect.width);
329
+ this._chomp();
330
+ }
331
+
332
+ _handleSlotChange() {
333
+ requestAnimationFrame(async() => {
334
+ await this._getItems();
335
+
336
+ this._slotItems.forEach(item => {
337
+ const observer = new MutationObserver(this._handleItemMutation);
338
+ observer.observe(item, {
339
+ attributes: true, /* required for legacy-Edge, otherwise attributeFilter throws a syntax error */
340
+ attributeFilter: ['disabled', 'text', 'selected'],
341
+ childList: false,
342
+ subtree: true
343
+ });
344
+ });
345
+
346
+ if (this.autoShow) {
347
+ this._autoDetectBoundaries(this._slotItems);
348
+ }
349
+
350
+ this._chomp();
351
+ });
352
+ }
353
+ };
@@ -11,20 +11,10 @@ import '../menu/menu-item.js';
11
11
  import '../menu/menu-item-separator.js';
12
12
  import '../menu/menu-item-link.js';
13
13
  import { css, html, LitElement } from 'lit';
14
+ import { OVERFLOW_CLASS, OVERFLOW_MINI_CLASS, OverflowGroupMixin } from './overflow-group-mixin.js';
14
15
  import { ifDefined } from 'lit/directives/if-defined.js';
15
- import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
16
- import { offscreenStyles } from '../offscreen/offscreen.js';
17
- import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
18
16
  import { RtlMixin } from '../../mixins/rtl-mixin.js';
19
17
 
20
- const AUTO_SHOW_CLASS = 'd2l-button-group-show';
21
- const AUTO_NO_SHOW_CLASS = 'd2l-button-group-no-show';
22
-
23
- const OPENER_TYPE = {
24
- DEFAULT: 'default',
25
- ICON: 'icon'
26
- };
27
-
28
18
  const OPENER_STYLE = {
29
19
  DEFAULT: 'default',
30
20
  SUBTLE: 'subtle',
@@ -61,140 +51,36 @@ function createMenuItemSeparator() {
61
51
  return html`<d2l-menu-item-separator></d2l-menu-item-separator>`;
62
52
  }
63
53
 
64
- function createMenuItemMenu(node) {
65
- const menuOpener =
66
- node.querySelector('d2l-dropdown-button')
67
- || node.querySelector('d2l-dropdown-button-subtle');
68
-
69
- const openerText = node.text || menuOpener.text;
70
- const disabled = !!node.disabled;
71
- const subMenu = node.querySelector('d2l-menu');
72
-
73
- const subItems = Array.from(subMenu.children).map((node) => convertToDropdownItem(node));
74
-
75
- return html`<d2l-menu-item
76
- ?disabled=${disabled}
77
- text="${openerText}">
78
- <d2l-menu>
79
- ${subItems}
80
- </d2l-menu>
81
- </d2l-menu-item>`;
82
- }
83
-
84
- function convertToDropdownItem(node) {
85
- const tagName = node.tagName.toLowerCase();
86
- switch (tagName) {
87
- case 'd2l-button':
88
- case 'd2l-button-subtle':
89
- case 'button':
90
- case 'd2l-button-icon':
91
- case 'd2l-selection-action':
92
- return createMenuItem(node);
93
- case 'a':
94
- case 'd2l-link':
95
- return createMenuItemLink(node);
96
- case 'd2l-menu':
97
- case 'd2l-dropdown':
98
- case 'd2l-dropdown-button':
99
- case 'd2l-dropdown-button-subtle':
100
- case 'd2l-dropdown-context-menu':
101
- case 'd2l-dropdown-more':
102
- case 'd2l-selection-action-dropdown':
103
- return createMenuItemMenu(node);
104
- case 'd2l-menu-item':
105
- case 'd2l-selection-action-menu-item':
106
- // if the menu item has children treat it as a menu item menu
107
- if (node.children.length > 0) {
108
- return createMenuItemMenu(node);
109
- } else {
110
- return createMenuItem(node);
111
- }
112
- }
113
- if (node.getAttribute('role') === 'separator') {
114
- return createMenuItemSeparator();
115
- }
116
- }
117
-
118
54
  /**
119
55
  *
120
56
  * A component that can be used to display a set of buttons, links or menus that will be put into a dropdown menu when they no longer fit on the first line of their container
121
57
  * @slot - Buttons, dropdown buttons, links or other items to be added to the container
122
- * @fires d2l-overflow-group-updated - Dispatched when there is an update performed to the overflow group
58
+ * @attr {'default'|'icon'} [opener-type="default"] - Set the opener type to 'icon' for a `...` menu icon instead of `More actions` text
123
59
  */
124
- class OverflowGroup extends RtlMixin(LocalizeCoreElement(LitElement)) {
60
+ class OverflowGroup extends OverflowGroupMixin(RtlMixin(LitElement)) {
125
61
 
126
62
  static get properties() {
127
63
  return {
128
- /**
129
- * Use predefined classes on slot elements to set min and max buttons to show
130
- * @type {boolean}
131
- */
132
- autoShow: {
133
- type: Boolean,
134
- attribute: 'auto-show',
135
- },
136
- /**
137
- * minimum amount of buttons to show
138
- * @type {number}
139
- */
140
- minToShow: {
141
- type: Number,
142
- reflect: true,
143
- attribute: 'min-to-show',
144
- },
145
- /**
146
- * maximum amount of buttons to show
147
- * @type {number}
148
- */
149
- maxToShow: {
150
- type: Number,
151
- reflect: true,
152
- attribute: 'max-to-show',
153
- },
154
- /**
155
- * Set the opener type to 'icon' for a `...` menu icon instead of `More actions` text
156
- * @type {'default'|'icon'}
157
- */
158
- openerType: {
159
- type: String,
160
- attribute: 'opener-type'
161
- },
162
64
  /**
163
65
  * Setting this property will change the style of the overflow menu opener
164
66
  * @type {'default'|'subtle'}
67
+ * @default "default"
165
68
  */
166
69
  openerStyle: {
167
70
  type: String,
168
71
  reflect: true,
169
72
  attribute: 'opener-style',
170
- },
171
- _mini: {
172
- type: Boolean,
173
- reflect: true
174
- },
175
- _chompIndex: {
176
- type: Number,
177
73
  }
178
74
  };
179
75
  }
180
76
 
181
77
  static get styles() {
182
- return [offscreenStyles, css`
183
- :host {
184
- display: block;
185
- }
186
- :host([hidden]) {
187
- display: none;
188
- }
78
+ return [super.styles, css`
189
79
  :host([opener-style="subtle"]) {
190
80
  --d2l-button-icon-fill-color: var(--d2l-color-celestine);
191
81
  --d2l-button-icon-fill-color-hover: var(--d2l-color-celestine-minus-1);
192
82
  }
193
- .d2l-overflow-group-container {
194
- display: flex;
195
- flex-wrap: wrap;
196
- justify-content: var(--d2l-overflow-group-justify-content, normal);
197
- }
83
+
198
84
  ::slotted(d2l-button),
199
85
  ::slotted(d2l-link),
200
86
  ::slotted(span),
@@ -229,291 +115,93 @@ class OverflowGroup extends RtlMixin(LocalizeCoreElement(LitElement)) {
229
115
  margin-left: 0.2rem;
230
116
  margin-right: 0;
231
117
  }
232
- .d2l-overflow-group-container ::slotted([data-is-chomped]) {
233
- display: none !important;
234
- }
235
118
  `];
236
119
  }
237
120
 
238
121
  constructor() {
239
122
  super();
240
- this._handleItemMutation = this._handleItemMutation.bind(this);
241
- this._handleResize = this._handleResize.bind(this);
242
-
243
- this._throttledResize = (entries) => requestAnimationFrame(() => this._handleResize(entries));
244
-
245
- this._overflowHidden = false;
246
- this.autoShow = false;
247
- this.maxToShow = -1;
248
- this.minToShow = 1;
249
123
  this.openerStyle = OPENER_STYLE.DEFAULT;
250
- this.openerType = OPENER_TYPE.DEFAULT;
251
- this._mini = this.openerType === OPENER_TYPE.ICON;
252
- this._resizeObserver = null;
253
- this._slotItems = [];
254
- }
255
-
256
- disconnectedCallback() {
257
- super.disconnectedCallback();
258
- if (this._resizeObserver) this._resizeObserver.disconnect();
259
124
  }
260
125
 
261
- async firstUpdated() {
262
- super.firstUpdated();
263
-
264
- // selected elements
265
- this._buttonSlot = this.shadowRoot.querySelector('slot');
266
-
267
- this._container = this.shadowRoot.querySelector('.d2l-overflow-group-container');
268
-
269
- this._resizeObserver = new ResizeObserver(this._throttledResize);
270
- this._resizeObserver.observe(this._container);
271
- }
272
-
273
- render() {
274
- const overflowMenu = this._getOverflowMenu();
275
-
276
- this._slotItems.forEach((element, index) => {
277
- if (!this._overflowMenuHidden && index >= this._chompIndex) {
278
- element.setAttribute('data-is-chomped', '');
279
- } else {
280
- element.removeAttribute('data-is-chomped');
281
- }
282
- });
283
-
284
- return html`
285
- <div class="d2l-overflow-group-container">
286
- <slot @slotchange="${this._handleSlotChange}"></slot>
287
- ${overflowMenu}
288
- </div>
289
- `;
290
- }
291
-
292
- update(changedProperties) {
293
- super.update(changedProperties);
294
-
295
- if (changedProperties.get('autoShow')) {
296
- this._getItemLayouts(this._slotItems);
297
- this._autoDetectBoundaries(this._itemLayouts);
298
- }
299
-
300
- if (changedProperties.get('minToShow')
301
- || changedProperties.get('maxToShow')) {
302
- this._chomp();
303
- }
304
-
305
- // Slight hack to get the overflow menu width the first time it renders
306
- if (!this._overflowMenuWidth) {
307
- // this action needs to be deferred until first render of our overflow button
308
- requestAnimationFrame(() => {
309
- this._chomp();
310
- });
311
- }
312
- }
313
-
314
- _autoDetectBoundaries(items) {
315
-
316
- let minToShow, maxToShow;
317
- for (let i = 0; i < items.length; i++) {
318
- if (items[i].classList.contains(AUTO_SHOW_CLASS)) {
319
- minToShow = i + 1;
320
- }
321
- if (maxToShow === undefined && items[i].classList.contains(AUTO_NO_SHOW_CLASS)) {
322
- maxToShow = i;
323
- }
324
- }
325
-
326
- if (minToShow !== undefined) {
327
- this.minToShow = minToShow;
328
- }
329
- if (maxToShow !== undefined) {
330
- this.maxToShow = maxToShow;
331
- }
332
- }
333
-
334
- _chomp() {
335
- if (!this.shadowRoot || !this._itemLayouts) return;
336
-
337
- this._overflowMenu = this.shadowRoot.querySelector('.d2l-overflow-dropdown');
338
- this._overflowMenuMini = this.shadowRoot.querySelector('.d2l-overflow-dropdown-mini');
339
- if (this.openerType === OPENER_TYPE.ICON && this._overflowMenuMini) {
340
- this._overflowMenuWidth = this._overflowMenuMini.offsetWidth;
341
- } else if (this._overflowMenu) {
342
- this._overflowMenuWidth = this._overflowMenu.offsetWidth;
343
- }
344
-
345
- const showing = {
346
- count: 0,
347
- width: 0
348
- };
349
-
350
- let isSoftOverflowing, isForcedOverflowing;
351
- for (let i = 0; i < this._itemLayouts.length; i++) {
352
- const itemLayout = this._itemLayouts[i];
353
-
354
- // handle minimum items to show
355
- if (showing.count < this.minToShow) {
356
- showing.width += itemLayout.width;
357
- showing.count += 1;
358
- itemLayout.trigger = 'force-show';
359
- itemLayout.isChomped = false;
360
- continue;
361
- }
362
-
363
- // handle maximum items to show
364
- if (this.maxToShow >= 0 && showing.count >= this.maxToShow) {
365
- isForcedOverflowing = true;
366
- itemLayout.isChomped = true;
367
- itemLayout.trigger = 'force-hide';
368
- continue;
369
- }
370
-
371
- // chomp or unchomp based on space available, and we've already handled min/max above
372
- if (!isSoftOverflowing && showing.width + itemLayout.width < this._availableWidth) {
373
- showing.width += itemLayout.width;
374
- showing.count += 1;
375
- itemLayout.isChomped = false;
376
- itemLayout.trigger = 'soft-show';
377
-
378
- } else {
379
- isSoftOverflowing = true;
380
- itemLayout.isChomped = true;
381
- itemLayout.trigger = 'soft-hide';
382
-
383
- }
384
-
385
- }
386
- // if there is at least one showing and no more to be hidden, enable collapsing more button to [...]
387
- this._overflowMenuHidden = this._itemLayouts.length === showing.count;
388
- if (!this._overflowMenuHidden && (isSoftOverflowing || isForcedOverflowing)) {
389
- for (let j = this._itemLayouts.length; j--;) {
390
- if (showing.width + this._overflowMenuWidth < this._availableWidth) {
391
- break;
392
- }
393
- const itemLayoutOverflowing = this._itemLayouts[j];
394
- if (itemLayoutOverflowing.trigger !== 'soft-show') {
395
- continue;
126
+ convertToOverflowItem(node) {
127
+ const tagName = node.tagName.toLowerCase();
128
+ switch (tagName) {
129
+ case 'd2l-button':
130
+ case 'd2l-button-subtle':
131
+ case 'button':
132
+ case 'd2l-button-icon':
133
+ case 'd2l-selection-action':
134
+ return createMenuItem(node);
135
+ case 'a':
136
+ case 'd2l-link':
137
+ return createMenuItemLink(node);
138
+ case 'd2l-menu':
139
+ case 'd2l-dropdown':
140
+ case 'd2l-dropdown-button':
141
+ case 'd2l-dropdown-button-subtle':
142
+ case 'd2l-dropdown-context-menu':
143
+ case 'd2l-dropdown-more':
144
+ case 'd2l-selection-action-dropdown':
145
+ return this._createMenuItemMenu(node);
146
+ case 'd2l-menu-item':
147
+ case 'd2l-selection-action-menu-item':
148
+ // if the menu item has children treat it as a menu item menu
149
+ if (node.children.length > 0) {
150
+ return this._createMenuItemMenu(node);
151
+ } else {
152
+ return createMenuItem(node);
396
153
  }
397
- showing.width -= itemLayoutOverflowing.width;
398
- showing.count -= 1;
399
- isSoftOverflowing = true;
400
- itemLayoutOverflowing.trigger = 'soft-hide';
401
- itemLayoutOverflowing.isChomped = true;
402
- }
403
154
  }
404
- const overflowDropdownOverflowing = (showing.width + this._overflowMenuWidth >= this._availableWidth);
405
- const swapToMiniButton = overflowDropdownOverflowing && !this._overflowMenuHidden;
406
-
407
- this._mini = this.openerType === OPENER_TYPE.ICON || swapToMiniButton;
408
- this._chompIndex = this._overflowMenuHidden ? null : showing.count;
409
-
410
- this.dispatchEvent(new CustomEvent('d2l-overflow-group-updated', { composed: false, bubbles: true }));
411
- }
412
-
413
- _getItemLayouts(filteredNodes) {
414
- const items = filteredNodes.map((node) => {
415
- const computedStyles = window.getComputedStyle(node);
416
-
417
- return {
418
- type: node.tagName.toLowerCase(),
419
- isChomped: false,
420
- isHidden: computedStyles.display === 'none',
421
- width: Math.ceil(parseFloat(computedStyles.width) || 0)
422
- + parseInt(computedStyles.marginRight) || 0
423
- + parseInt(computedStyles.marginLeft) || 0,
424
- node: node
425
- };
426
- });
427
-
428
- return items.filter(({ isHidden }) => !isHidden);
429
- }
430
-
431
- _getItems() {
432
- // get the items from the button slot
433
- this._slotItems = this._getSlotItems();
434
- // convert them to layout items (calculate widths)
435
- this._itemLayouts = this._getItemLayouts(this._slotItems);
436
- // convert to dropdown items (for overflow menu)
437
- this._dropdownItems = this._slotItems.map((node) => convertToDropdownItem(node));
155
+ if (node.getAttribute('role') === 'separator') {
156
+ return createMenuItemSeparator();
157
+ }
438
158
  }
439
159
 
440
- _getOverflowMenu() {
441
- if (this._overflowMenuHidden) {
442
- return;
443
- }
160
+ getOverflowContainer(overflowItems, mini) {
444
161
  const moreActionsText = this.localize('components.overflow-group.moreActions');
445
- const overflowItems = this._dropdownItems ? this._dropdownItems.slice(this._chompIndex) : [];
446
162
  const menu = html`<d2l-dropdown-menu>
447
163
  <d2l-menu label="${moreActionsText}">
448
164
  ${overflowItems}
449
165
  </d2l-menu>
450
166
  </d2l-dropdown-menu>`;
451
167
 
452
- if (this._mini) {
453
- return html`<d2l-dropdown-more class="d2l-overflow-dropdown-mini" text="${moreActionsText}">
168
+ if (mini) {
169
+ return html`<d2l-dropdown-more class="${OVERFLOW_MINI_CLASS} d2l-overflow-dropdown-mini" text="${moreActionsText}">
454
170
  ${menu}
455
171
  </d2l-dropdown-more>`;
456
172
  }
457
173
 
458
174
  if (this.openerStyle === OPENER_STYLE.SUBTLE) {
459
- return html`<d2l-dropdown-button-subtle class="d2l-overflow-dropdown" text="${moreActionsText}">
175
+ return html`<d2l-dropdown-button-subtle class="${OVERFLOW_CLASS} d2l-overflow-dropdown" text="${moreActionsText}">
460
176
  ${menu}
461
177
  </d2l-dropdown-button-subtle>`;
462
178
  }
463
179
 
464
- return html`<d2l-dropdown-button class="d2l-overflow-dropdown" text="${moreActionsText}">
180
+ return html`<d2l-dropdown-button class="${OVERFLOW_CLASS} d2l-overflow-dropdown" text="${moreActionsText}">
465
181
  ${menu}
466
182
  </d2l-dropdown-button>`;
467
183
  }
468
184
 
469
- _getSlotItems() {
470
- const nodes = this._buttonSlot.assignedNodes({ flatten: true });
471
- const filteredNodes = nodes.filter((node) => {
472
- const isNode = node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() !== 'template';
473
- return isNode;
474
- });
185
+ _createMenuItemMenu(node) {
186
+ const menuOpener =
187
+ node.querySelector('d2l-dropdown-button')
188
+ || node.querySelector('d2l-dropdown-button-subtle');
475
189
 
476
- return filteredNodes;
477
- }
478
-
479
- _handleItemMutation(mutations) {
480
- if (!mutations || mutations.length === 0) return;
481
- if (this._updateDropdownItemsRequested) return;
190
+ const openerText = node.text || menuOpener.text;
191
+ const disabled = !!node.disabled;
192
+ const subMenu = node.querySelector('d2l-menu');
482
193
 
483
- this._updateDropdownItemsRequested = true;
484
- setTimeout(() => {
485
- this._dropdownItems = this._slotItems.map(node => convertToDropdownItem(node));
486
- this._updateDropdownItemsRequested = false;
487
- this.requestUpdate();
488
- }, 0);
489
- }
194
+ const subItems = Array.from(subMenu.children).map((node) => this.convertToOverflowItem(node));
490
195
 
491
- _handleResize(entries) {
492
- this._availableWidth = Math.ceil(entries[0].contentRect.width);
493
- this._chomp();
196
+ return html`<d2l-menu-item
197
+ ?disabled=${disabled}
198
+ text="${openerText}">
199
+ <d2l-menu>
200
+ ${subItems}
201
+ </d2l-menu>
202
+ </d2l-menu-item>`;
494
203
  }
495
204
 
496
- _handleSlotChange() {
497
- requestAnimationFrame(() => {
498
- this._getItems();
499
-
500
- this._slotItems.forEach(item => {
501
- const observer = new MutationObserver(this._handleItemMutation);
502
- observer.observe(item, {
503
- attributes: true, /* required for legacy-Edge, otherwise attributeFilter throws a syntax error */
504
- attributeFilter: ['disabled', 'text'],
505
- childList: false,
506
- subtree: false
507
- });
508
- });
509
-
510
- if (this.autoShow) {
511
- this._autoDetectBoundaries(this._slotItems);
512
- }
513
-
514
- this._chomp();
515
- });
516
- }
517
205
  }
518
206
 
519
207
  customElements.define('d2l-overflow-group', OverflowGroup);
@@ -3359,6 +3359,71 @@
3359
3359
  }
3360
3360
  ]
3361
3361
  },
3362
+ {
3363
+ "name": "d2l-filter-overflow-group",
3364
+ "path": "./components/filter/filter-overflow-group.js",
3365
+ "description": "A component that can be used to display a group of filters that will be put into an overflow filter when they no longer fit on the first line of their container",
3366
+ "attributes": [
3367
+ {
3368
+ "name": "auto-show",
3369
+ "description": "Use predefined classes on slot elements to set min and max slotted items to show",
3370
+ "type": "boolean",
3371
+ "default": "false"
3372
+ },
3373
+ {
3374
+ "name": "max-to-show",
3375
+ "description": "maximum amount of slotted items to show",
3376
+ "type": "number",
3377
+ "default": "-1"
3378
+ },
3379
+ {
3380
+ "name": "min-to-show",
3381
+ "description": "minimum amount of slotted items to show",
3382
+ "type": "number",
3383
+ "default": "1"
3384
+ }
3385
+ ],
3386
+ "properties": [
3387
+ {
3388
+ "name": "autoShow",
3389
+ "attribute": "auto-show",
3390
+ "description": "Use predefined classes on slot elements to set min and max slotted items to show",
3391
+ "type": "boolean",
3392
+ "default": "false"
3393
+ },
3394
+ {
3395
+ "name": "maxToShow",
3396
+ "attribute": "max-to-show",
3397
+ "description": "maximum amount of slotted items to show",
3398
+ "type": "number",
3399
+ "default": "-1"
3400
+ },
3401
+ {
3402
+ "name": "minToShow",
3403
+ "attribute": "min-to-show",
3404
+ "description": "minimum amount of slotted items to show",
3405
+ "type": "number",
3406
+ "default": "1"
3407
+ },
3408
+ {
3409
+ "name": "openerType",
3410
+ "type": "string",
3411
+ "default": "\"DEFAULT\""
3412
+ }
3413
+ ],
3414
+ "events": [
3415
+ {
3416
+ "name": "d2l-overflow-group-updated",
3417
+ "description": "Dispatched when there is an update performed to the overflow group"
3418
+ }
3419
+ ],
3420
+ "slots": [
3421
+ {
3422
+ "name": "",
3423
+ "description": "d2l-filters to be added to the container"
3424
+ }
3425
+ ]
3426
+ },
3362
3427
  {
3363
3428
  "name": "d2l-filter-tags",
3364
3429
  "path": "./components/filter/filter-tags.js",
@@ -8760,71 +8825,69 @@
8760
8825
  "path": "./components/overflow-group/overflow-group.js",
8761
8826
  "description": "\nA component that can be used to display a set of buttons, links or menus that will be put into a dropdown menu when they no longer fit on the first line of their container",
8762
8827
  "attributes": [
8828
+ {
8829
+ "name": "opener-style",
8830
+ "description": "Setting this property will change the style of the overflow menu opener",
8831
+ "type": "'default'|'subtle'",
8832
+ "default": "\"\\\"default\\\"\""
8833
+ },
8763
8834
  {
8764
8835
  "name": "auto-show",
8765
- "description": "Use predefined classes on slot elements to set min and max buttons to show",
8836
+ "description": "Use predefined classes on slot elements to set min and max slotted items to show",
8766
8837
  "type": "boolean",
8767
8838
  "default": "false"
8768
8839
  },
8769
8840
  {
8770
8841
  "name": "max-to-show",
8771
- "description": "maximum amount of buttons to show",
8842
+ "description": "maximum amount of slotted items to show",
8772
8843
  "type": "number",
8773
8844
  "default": "-1"
8774
8845
  },
8775
8846
  {
8776
8847
  "name": "min-to-show",
8777
- "description": "minimum amount of buttons to show",
8848
+ "description": "minimum amount of slotted items to show",
8778
8849
  "type": "number",
8779
8850
  "default": "1"
8780
8851
  },
8781
- {
8782
- "name": "opener-style",
8783
- "description": "Setting this property will change the style of the overflow menu opener",
8784
- "type": "'default'|'subtle'",
8785
- "default": "\"DEFAULT\""
8786
- },
8787
8852
  {
8788
8853
  "name": "opener-type",
8789
8854
  "description": "Set the opener type to 'icon' for a `...` menu icon instead of `More actions` text",
8790
8855
  "type": "'default'|'icon'",
8791
- "default": "\"DEFAULT\""
8856
+ "default": "\"default\""
8792
8857
  }
8793
8858
  ],
8794
8859
  "properties": [
8860
+ {
8861
+ "name": "openerStyle",
8862
+ "attribute": "opener-style",
8863
+ "description": "Setting this property will change the style of the overflow menu opener",
8864
+ "type": "'default'|'subtle'",
8865
+ "default": "\"\\\"default\\\"\""
8866
+ },
8795
8867
  {
8796
8868
  "name": "autoShow",
8797
8869
  "attribute": "auto-show",
8798
- "description": "Use predefined classes on slot elements to set min and max buttons to show",
8870
+ "description": "Use predefined classes on slot elements to set min and max slotted items to show",
8799
8871
  "type": "boolean",
8800
8872
  "default": "false"
8801
8873
  },
8802
8874
  {
8803
8875
  "name": "maxToShow",
8804
8876
  "attribute": "max-to-show",
8805
- "description": "maximum amount of buttons to show",
8877
+ "description": "maximum amount of slotted items to show",
8806
8878
  "type": "number",
8807
8879
  "default": "-1"
8808
8880
  },
8809
8881
  {
8810
8882
  "name": "minToShow",
8811
8883
  "attribute": "min-to-show",
8812
- "description": "minimum amount of buttons to show",
8884
+ "description": "minimum amount of slotted items to show",
8813
8885
  "type": "number",
8814
8886
  "default": "1"
8815
8887
  },
8816
- {
8817
- "name": "openerStyle",
8818
- "attribute": "opener-style",
8819
- "description": "Setting this property will change the style of the overflow menu opener",
8820
- "type": "'default'|'subtle'",
8821
- "default": "\"DEFAULT\""
8822
- },
8823
8888
  {
8824
8889
  "name": "openerType",
8825
- "attribute": "opener-type",
8826
- "description": "Set the opener type to 'icon' for a `...` menu icon instead of `More actions` text",
8827
- "type": "'default'|'icon'",
8890
+ "type": "string",
8828
8891
  "default": "\"DEFAULT\""
8829
8892
  }
8830
8893
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "2.38.3",
3
+ "version": "2.40.0",
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",