@brightspace-ui/labs 2.11.6 → 2.13.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.
package/package.json CHANGED
@@ -5,6 +5,8 @@
5
5
  "repository": "https://github.com/BrightspaceUI/labs.git",
6
6
  "exports": {
7
7
  "./components/accessibility-disability-simulator.js": "./src/components/accessibility-disability-simulator/accessibility-disability-simulator.js",
8
+ "./components/accordion.js": "./src/components/accordion/accordion.js",
9
+ "./components/accordion-collapse.js": "./src/components/accordion/accordion-collapse.js",
8
10
  "./components/attribute-picker.js": "./src/components/attribute-picker/attribute-picker.js",
9
11
  "./components/checkbox-drawer.js": "./src/components/checkbox-drawer/checkbox-drawer.js",
10
12
  "./components/card-overlay.js": "./src/components/card-overlay/card-overlay.js",
@@ -79,5 +81,5 @@
79
81
  "@lit/context": "^1.1.3",
80
82
  "lit": "^3"
81
83
  },
82
- "version": "2.11.6"
84
+ "version": "2.13.0"
83
85
  }
@@ -0,0 +1,109 @@
1
+ # d2l-labs-accordion
2
+
3
+ Lit-based widget that displays a list of collapsible components. When collapsible component is clicked - it expands or collapses the associated content.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <script type="module">
9
+ import '@brightspace-ui/labs/components/accordion.js';
10
+ import '@brightspace-ui/labs/components/accordion-collapse.js';
11
+ </script>
12
+ <d2l-labs-accordion>
13
+ <d2l-labs-accordion-collapse></d2l-labs-accordion-collapse>
14
+ </d2l-labs-accordion>
15
+ ```
16
+
17
+ ## Lit components:
18
+ ### **d2l-labs-accordion** - accordion panel.
19
+ #### Attributes:
20
+ * multi - allows multiple collapsibles to be opened at the same time
21
+ * selected - used only if `multi` is disabled. sets item index to be opened by default
22
+ * selectedValue - used only if `multi` is set. Sets array of indexes for the items to be opened by default
23
+ * autoClose - expanding any **d2l-labs-accordion-collapse** child (except those that are disabled) will automatically close other opened children.
24
+ ### **d2l-labs-accordion-collapse** - accordion component.
25
+ #### Attributes:
26
+ * flex - adjust component to the parent width
27
+ * noIcons - hide the expand/collapse icon
28
+ * opened - container is opened by default. Do not use this attribute when inside the **d2l-labs-accordion** as the **d2l-labs-accordion** does not monitor opened state of the items at the start. In this case, use `selected` or `selectedValue` **d2l-labs-accordion** attributes instead.
29
+ * disabled - container cannot be expanded or collapsed
30
+ * disable-default-trigger-focus - disables the default outline added by browsers on trigger focus so custom styles can be added to the component on focus
31
+ * headerBorder - show a border between the header and the summary/content
32
+ * icon-has-padding - adds padding on one side of the icon.
33
+ * When used with 'flex' attribute, the padding will be to the right. (Opposite for RTL)
34
+ * Without 'flex' attribute, the padding will be on the left. (Opposite for RTL)
35
+ * header-has-interactive-content - adjusts the html to allow interactive (e.g. clickable) elements in the header to function properly
36
+ (especially with screen readers)
37
+ * screen-reader-header-text - text that is visually hidden and only used for a screen reader to read text
38
+ from the header
39
+
40
+ #### Slots:
41
+ * header - content to display under the title
42
+ * summary - content that summarizes the data inside the accordion
43
+
44
+ Example 1:
45
+ ```html
46
+ <d2l-labs-accordion selected="1">
47
+ <d2l-labs-accordion-collapse title="Item 1" flex>
48
+ Text 1
49
+ </d2l-labs-accordion-collapse>
50
+ <d2l-labs-accordion-collapse title="Item 2" flex>
51
+ Item 3
52
+ </d2l-labs-accordion-collapse>
53
+ <d2l-labs-accordion-collapse title="Item 3" flex>
54
+ Text 3
55
+ </d2l-labs-accordion-collapse>
56
+ </d2l-labs-accordion>
57
+ ```
58
+
59
+ Example 2:
60
+ ```html
61
+ <d2l-labs-accordion multi selectedValue="[1,2]">
62
+ <d2l-labs-accordion-collapse title="Item 1" flex>
63
+ Text 1
64
+ </d2l-labs-accordion-collapse>
65
+ <d2l-labs-accordion-collapse title="Item 2" flex>
66
+ Item 3
67
+ </d2l-labs-accordion-collapse>
68
+ <d2l-labs-accordion-collapse title="Item 3" flex>
69
+ Text 3
70
+ </d2l-labs-accordion-collapse>
71
+ </d2l-labs-accordion>
72
+ ```
73
+
74
+ Example 3:
75
+ ```html
76
+ <d2l-labs-accordion-collapse title="Opened By Default (Flex)" opened flex>
77
+ Text 1
78
+ </d2l-labs-accordion-collapse>
79
+ ```
80
+
81
+ Example 4:
82
+ ```html
83
+ <d2l-labs-accordion-collapse title="Opened By Default (Regular)" opened>
84
+ Text 1
85
+ </d2l-labs-accordion-collapse>
86
+ ```
87
+
88
+ Example 5:
89
+ ```html
90
+ <d2l-labs-accordion-collapse flex header-border>
91
+ <h2 slot="header">Custom header, summary, border and flex 💪</h2>
92
+ <ul slot="summary" style="list-style-type: none; padding-left: 0px;">
93
+ <li>Availability starts 4/13/2020 and ends 4/23/2020</li>
94
+ <li>One release condition</li>
95
+ <li>Special access</li>
96
+ </ul>
97
+ <p>Stuff inside of the accordion goes here</p>
98
+ </d2l-labs-accordion-collapse>
99
+ ```
100
+
101
+ Example 6:
102
+ ```html
103
+ <d2l-labs-accordion-collapse header-has-interactive-content screen-reader-header-text="Go to D2L">
104
+ <span slot="header">
105
+ Go to
106
+ <a href="https://www.d2l.com/">D2L</a>
107
+ </span>
108
+ </d2l-labs-accordion-collapse>
109
+ ```
@@ -0,0 +1,378 @@
1
+ import '@brightspace-ui/core/components/expand-collapse/expand-collapse-content.js';
2
+ import '@brightspace-ui/core/components/colors/colors.js';
3
+ import '@brightspace-ui/core/components/icons/icon.js';
4
+ import { css, html, LitElement, nothing } from 'lit';
5
+ import { findComposedAncestor, isComposedAncestor } from '@brightspace-ui/core/helpers/dom.js';
6
+ import { offscreenStyles } from '@brightspace-ui/core/components/offscreen/offscreen.js';
7
+
8
+ class LabsAccordionCollapse extends LitElement {
9
+ static get properties() {
10
+ return {
11
+ /**
12
+ * Label. Does not apply title to entire accordion
13
+ */
14
+ label: { type: String },
15
+ /**
16
+ * Header text hidden on the screen, but to be read by a screen reader
17
+ */
18
+ screenReaderHeaderText: { type: String, attribute: 'screen-reader-header-text' },
19
+ /**
20
+ * Corresponds to the iron-collapse's noAnimation property.
21
+ */
22
+ noAnimation: { type: Boolean, attribute: 'no-animation' },
23
+ /**
24
+ * Whether currently expanded.
25
+ */
26
+ opened: { type: Boolean, reflect: true },
27
+ /**
28
+ * The icon when collapsed.
29
+ */
30
+ expandIcon: { type: String, attribute: 'expand-icon' },
31
+ /**
32
+ * The icon when expanded.
33
+ */
34
+ collapseIcon: { type: String, attribute: 'collapse-icon' },
35
+ /**
36
+ * Whether to hide the expand/collapse icon.
37
+ */
38
+ noIcons: { type: Boolean, attribute: 'no-icons' },
39
+ /**
40
+ * Whether or not to use flex layout.
41
+ */
42
+ flex: { type: Boolean, reflect: true },
43
+ /**
44
+ * Whether or not to add extra padding for icon.
45
+ */
46
+ iconHasPadding: { type: Boolean, attribute: 'icon-has-padding', reflect: true },
47
+ /**
48
+ * Whether or not to add a border between the header and the content.
49
+ */
50
+ headerBorder: { type: Boolean, attribute: 'header-border' },
51
+ /**
52
+ * Whether the accordion's expand/collapse function is disabled.
53
+ */
54
+ disabled: { type: Boolean, reflect: true },
55
+ /**
56
+ * Whether or not to disable default focus styles
57
+ */
58
+ disableDefaultTriggerFocus: { type: Boolean, attribute: 'disable-default-trigger-focus', reflect: true },
59
+ /**
60
+ * Whether or not the header contains an interactive element inside it (e.g. clickable)
61
+ */
62
+ headerHasInteractiveContent: { type: Boolean, attribute: 'header-has-interactive-content', reflect: true },
63
+ /**
64
+ * Listener for state changes.
65
+ */
66
+ _boundListener: { type: Function, attribute: '_bound-listener' },
67
+ /**
68
+ * The current state of the accordion (opened, closed)
69
+ */
70
+ _state: { type: String, reflect: true }
71
+ };
72
+ }
73
+
74
+ static get styles() {
75
+ return [offscreenStyles, css`
76
+ :host {
77
+ display: block;
78
+ }
79
+ #interactive-header-content {
80
+ align-items: center;
81
+ display: flex;
82
+ }
83
+ #trigger {
84
+ align-items: center;
85
+ display: flex;
86
+ text-decoration: none;
87
+ }
88
+ #trigger:focus-visible {
89
+ outline-offset: 3px;
90
+ }
91
+ #trigger[data-border] {
92
+ border-bottom: solid 1px var(--d2l-color-corundum);
93
+ margin-bottom: 0.4rem;
94
+ padding-bottom: 0.4rem;
95
+ }
96
+ #trigger, #trigger:visited, #trigger:hover, #trigger:active {
97
+ color: inherit;
98
+ }
99
+ :host([flex]) .collapse-title {
100
+ flex: 1;
101
+ flex-basis: 0.000000001px;
102
+ overflow: hidden;
103
+ }
104
+ :host([icon-has-padding]) d2l-icon {
105
+ padding-left: 1.25rem;
106
+ padding-right: 0;
107
+ }
108
+ :host([icon-has-padding][dir="rtl"]) d2l-icon {
109
+ padding-left: 0;
110
+ padding-right: 1.25rem;
111
+ }
112
+ :host([flex][icon-has-padding]) d2l-icon {
113
+ padding-left: 0;
114
+ padding-right: 1.25rem;
115
+ }
116
+ :host([flex][icon-has-padding][dir="rtl"]) d2l-icon {
117
+ padding-left: 1.25rem;
118
+ padding-right: 0;
119
+ }
120
+ .content {
121
+ height: auto;
122
+ margin: -1px;
123
+ padding: 1px;
124
+ position: relative;
125
+ }
126
+ .summary {
127
+ transition: opacity 500ms ease;
128
+ }
129
+ :host([_state="closing"]) .content,
130
+ :host([_state="opening"]) .content {
131
+ overflow: hidden;
132
+ }
133
+ :host([_state="closed"]) .summary,
134
+ :host([_state="closing"]) .summary {
135
+ transition-delay: 500ms;
136
+ }
137
+ :host([_state="opening"]) .summary,
138
+ :host([_state="opened"]) .summary {
139
+ opacity: 0;
140
+ }
141
+ :host([_state="closing"]) .summary,
142
+ :host([_state="opening"]) .summary,
143
+ :host([_state="opened"]) .summary {
144
+ pointer-events: none;
145
+ position: absolute;
146
+ }
147
+ :host([_state="closing"]) .summary,
148
+ :host([_state="opened"]) .summary {
149
+ transition-property: none;
150
+ }
151
+ :host([disabled]) a {
152
+ cursor: default;
153
+ }
154
+ :host([disabled]) d2l-icon {
155
+ color: var(--d2l-color-chromite);
156
+ }
157
+ :host([disable-default-trigger-focus]) #trigger:focus {
158
+ outline: none;
159
+ }
160
+
161
+ :host([header-has-interactive-content]) #header-container {
162
+ display: grid;
163
+ grid-template-columns: auto;
164
+ grid-template-rows: auto;
165
+ }
166
+ :host([header-has-interactive-content]) .header-grid-item {
167
+ grid-column: 1;
168
+ grid-row: 1;
169
+ }
170
+ :host([header-has-interactive-content]) #interactive-header-content {
171
+ cursor: pointer;
172
+ }
173
+ `];
174
+ }
175
+
176
+ constructor() {
177
+ super();
178
+ this.title = '';
179
+ this.label = '';
180
+ this.screenReaderHeaderText = '';
181
+ this.noAnimation = false;
182
+ this.opened = false;
183
+ this.expandIcon = 'd2l-tier1:arrow-expand';
184
+ this.collapseIcon = 'd2l-tier1:arrow-collapse';
185
+ this.noIcons = false;
186
+ this.flex = false;
187
+ this.iconHasPadding = false;
188
+ this.headerBorder = false;
189
+ this.disabled = false;
190
+ this.disableDefaultTriggerFocus = false;
191
+ this.headerHasInteractiveContent = false;
192
+ this._state = 'closed';
193
+
194
+ this._boundListener = this._onStateChanged.bind(this);
195
+ }
196
+ connectedCallback() {
197
+ super.connectedCallback();
198
+ if (this.disabled) {
199
+ return;
200
+ }
201
+ window.addEventListener('d2l-labs-accordion-collapse-state-changed', this._boundListener);
202
+ if (!this.#resizeObserver) {
203
+ this.#resizeObserver = new ResizeObserver(() => this._fireAccordionResizeEvent());
204
+ this.#resizeObserver.observe(this);
205
+ }
206
+ }
207
+ disconnectedCallback() {
208
+ super.disconnectedCallback();
209
+ if (this.disabled) {
210
+ return;
211
+ }
212
+ window.removeEventListener('d2l-labs-accordion-collapse-state-changed', this._boundListener);
213
+ if (this.#resizeObserver) {
214
+ this.#resizeObserver.disconnect();
215
+ this.#resizeObserver = null;
216
+ }
217
+ }
218
+ render() {
219
+ return html`
220
+ <div id="header-container">
221
+ <a href="javascript:void(0)" id="trigger" ?aria-expanded=${this.opened} class="header-grid-item" aria-controls="collapse" role="button" ?data-border="${this.headerBorder}" @blur=${this._triggerBlur} @click=${this.toggle} @focus=${this._triggerFocus}>
222
+ ${!this.headerHasInteractiveContent ? html`
223
+ <div class="collapse-title" title="${this.label}">${this.title}${this.label}<slot name="header"></slot>
224
+ </div>
225
+ ${!this.noIcons ? html`
226
+ <d2l-icon icon="${this.opened ? this.collapseIcon : this.expandIcon}"></d2l-icon>
227
+ ` : nothing}
228
+ ` : html`
229
+ <span class="d2l-offscreen">${this.screenReaderHeaderText}</span>
230
+ `}
231
+ </a>
232
+ ${this.headerHasInteractiveContent ? html`
233
+ <div id="interactive-header-content" class="header-grid-item" @click=${this.toggle}>
234
+ <div class="collapse-title" title="${this.label}">${this.title}${this.label}<slot name="header"></slot>
235
+ </div>
236
+ ${!this.noIcons ? html`
237
+ <d2l-icon icon="${this.opened ? this.collapseIcon : this.expandIcon}"></d2l-icon>
238
+ ` : nothing}
239
+ </div>
240
+ ` : nothing}
241
+ </div>
242
+
243
+ <div class="content">
244
+ <d2l-expand-collapse-content ?expanded=${this.opened} @d2l-expand-collapse-content-expand=${this._handleExpand} @d2l-expand-collapse-content-collapse=${this._handleCollapse}>
245
+ <slot></slot>
246
+ </d2l-expand-collapse-content>
247
+ <div class="summary">
248
+ <slot name="summary"></slot>
249
+ </div>
250
+ </div>
251
+
252
+ `;
253
+ }
254
+ updated(changedProperties) {
255
+ super.updated(changedProperties);
256
+ if (changedProperties.has('_state')) {
257
+ const content = this.shadowRoot.querySelector('.content');
258
+ if (this._state === 'opening') {
259
+ content.style.minHeight = `${content.offsetHeight - 2}px`;
260
+ }
261
+ if (this._state === 'closed') {
262
+ content.style.removeProperty('min-height');
263
+ }
264
+ }
265
+ }
266
+ willUpdate(changedProperties) {
267
+ super.willUpdate(changedProperties);
268
+ if (changedProperties.has('opened')) {
269
+ this.dispatchEvent(new CustomEvent('opened-changed',
270
+ { bubbles: true, composed: true, detail: { value: this.opened } }
271
+ ));
272
+ this._notifyStateChanged(this.opened);
273
+ }
274
+
275
+ }
276
+ close() {
277
+ if (this.disabled) {
278
+ return;
279
+ }
280
+ this.opened = false;
281
+ this._notifyStateChanged();
282
+ }
283
+ open() {
284
+ if (this.disabled) {
285
+ return;
286
+ }
287
+ this.opened = true;
288
+ this._notifyStateChanged();
289
+ }
290
+ toggle() {
291
+ this.dispatchEvent(new CustomEvent('d2l-labs-accordion-collapse-clicked'));
292
+ if (this.disabled) {
293
+ return;
294
+ }
295
+ if (this.opened) {
296
+ this.close();
297
+ } else {
298
+ this.open();
299
+ }
300
+ }
301
+ #resizeObserver;
302
+
303
+ _fireAccordionResizeEvent() {
304
+ const event = new CustomEvent('d2l-labs-accordion-collapse-resize', {
305
+ bubbles: true
306
+ });
307
+ window.dispatchEvent(event);
308
+ }
309
+
310
+ _handleCollapse(e) {
311
+ this._state = 'closing';
312
+ e.detail.collapseComplete.then(() => this._state = 'closed');
313
+ }
314
+
315
+ _handleExpand(e) {
316
+ this._state = 'opening';
317
+ e.detail.expandComplete.then(() => this._state = 'opened');
318
+ }
319
+
320
+ _haveSharedAutoCloseAccordionAncestor(node1, node2) {
321
+ const accordionAncestor = findComposedAncestor(node1, (elem) => {
322
+ if (elem.isAccordion && elem.autoClose) {
323
+ return true;
324
+ }
325
+ });
326
+ if (!accordionAncestor) {
327
+ return false;
328
+ }
329
+ if (!isComposedAncestor(accordionAncestor, node2)) {
330
+ return false;
331
+ }
332
+ return true;
333
+ }
334
+
335
+ _notifyStateChanged() {
336
+ if (this.opened) {
337
+ this.dispatchEvent(new CustomEvent('d2l-labs-accordion-collapse-state-opened'));
338
+ }
339
+ this.dispatchEvent(new CustomEvent(
340
+ 'd2l-labs-accordion-collapse-state-changed', {
341
+ bubbles: true,
342
+ composed: true,
343
+ detail: { opened: this.opened, el: this }
344
+ }
345
+ ));
346
+ }
347
+
348
+ _onStateChanged(event) {
349
+ if (this.opened
350
+ && event.detail.el !== this
351
+ && this._haveSharedAutoCloseAccordionAncestor(this, event.detail.el)
352
+ ) {
353
+ /* close an opened child */
354
+ if (!event.detail.opened
355
+ && isComposedAncestor(event.detail.el, this)
356
+ ) {
357
+ this.opened = false;
358
+ }
359
+ /* close an opened sibling */
360
+ if (event.detail.opened
361
+ && !isComposedAncestor(event.detail.el, this)
362
+ && !isComposedAncestor(this, event.detail.el)
363
+ ) {
364
+ this.opened = false;
365
+ }
366
+ }
367
+ }
368
+
369
+ _triggerBlur() {
370
+ this.dispatchEvent(new CustomEvent('d2l-labs-accordion-collapse-toggle-blur'));
371
+ }
372
+
373
+ _triggerFocus() {
374
+ this.dispatchEvent(new CustomEvent('d2l-labs-accordion-collapse-toggle-focus'));
375
+ }
376
+ }
377
+
378
+ customElements.define('d2l-labs-accordion-collapse', LabsAccordionCollapse);
@@ -0,0 +1,80 @@
1
+ import './accordion-collapse.js';
2
+ import { css, html, LitElement } from 'lit';
3
+
4
+ class LabsAccordion extends LitElement {
5
+ static get properties() {
6
+ return {
7
+ /**
8
+ * Whether to automatically close other opened branches
9
+ */
10
+ autoClose: { type: Boolean, attribute: 'auto-close' },
11
+ _selected: { type: Number }
12
+ };
13
+ }
14
+
15
+ static get styles() {
16
+ return css`
17
+ :host {
18
+ box-sizing: border-box;
19
+ display: block;
20
+ }
21
+ `;
22
+ }
23
+
24
+ constructor() {
25
+ super();
26
+ this._handleAccordionOpened = this._handleAccordionOpened.bind(this);
27
+ this.autoClose = false;
28
+ }
29
+
30
+ get selected() {
31
+ return this._selected;
32
+ }
33
+
34
+ set selected(val) {
35
+ this.select(val);
36
+ }
37
+
38
+ get isAccordion() {
39
+ return true;
40
+ }
41
+
42
+ get items() {
43
+ return [...this.querySelectorAll('d2l-labs-accordion-collapse')];
44
+ }
45
+
46
+ connectedCallback() {
47
+ super.connectedCallback();
48
+ this.addEventListener('d2l-labs-accordion-collapse-state-opened', this._handleAccordionOpened);
49
+ }
50
+
51
+ disconnectedCallback() {
52
+ super.disconnectedCallback();
53
+ this.removeEventListener('d2l-labs-accordion-collapse-state-opened', this._handleAccordionOpened);
54
+ }
55
+
56
+ render() {
57
+ return html`
58
+ <slot></slot>
59
+ `;
60
+ }
61
+
62
+ select(selected) {
63
+ this._selected = selected;
64
+ const items = this.items;
65
+
66
+ for (const i in items) {
67
+ items[i].opened = Number(i) === selected;
68
+ }
69
+ }
70
+
71
+ _handleAccordionOpened(e) {
72
+ this.select(this.items.indexOf(e.target));
73
+ }
74
+
75
+ _setisAccordion(val) {
76
+ this._isAccordion = val;
77
+ }
78
+ }
79
+
80
+ customElements.define('d2l-labs-accordion', LabsAccordion);