@brightspace-ui/core 2.9.0 → 2.10.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.
@@ -10,23 +10,48 @@
10
10
  import '../tag-list-item.js';
11
11
  import '../test/tag-list-item-mixin-consumer.js';
12
12
  </script>
13
+ <style>
14
+ .actions {
15
+ margin-bottom: 1rem;
16
+ }
17
+ </style>
13
18
  </head>
14
19
  <body unresolved>
15
20
  <d2l-demo-page page-title="d2l-tag-list">
16
21
 
17
22
  <h2>Basic Tag List</h2>
23
+
24
+ <div class="actions">
25
+ <button id="add">add tag (first)</button>
26
+ <button id="remove">remove tag (first)</button>
27
+ </div>
28
+ <script>
29
+ let addIndex = 0;
30
+ document.querySelector('#add').addEventListener('click', () => {
31
+ const tag = document.createElement('d2l-tag-list-item');
32
+ tag.text = `Added Tag ${++addIndex}`;
33
+ document.querySelector('d2l-tag-list').insertBefore(tag, document.querySelector('d2l-tag-list').children[0]);
34
+ });
35
+
36
+ document.querySelector('#remove').addEventListener('click', () => {
37
+ const children = document.querySelector('d2l-tag-list').children;
38
+ if (children.length === 0) return;
39
+ document.querySelector('d2l-tag-list').removeChild(children[0]);
40
+ });
41
+
42
+ </script>
18
43
  <d2l-demo-snippet fullscreen>
19
44
  <d2l-tag-list description="A bunch of example tags">
20
45
  <d2l-tag-list-item text="Example Tag"></d2l-tag-list-item>
21
- <d2l-tag-list-item text="Longer Example Tag - much much much much much longer"></d2l-tag-list-item>
46
+ <d2l-tag-list-item text="Longer Example Tag - much much much much much much much much longer"></d2l-tag-list-item>
22
47
  <d2l-tag-list-item text="Another Example Tag"></d2l-tag-list-item>
23
48
  <d2l-tag-list-item-mixin-consumer name="Custom Tag List Item"></d2l-tag-list-item-mixin-consumer>
24
49
  <d2l-tag-list-item text="Example Tag 5"></d2l-tag-list-item>
25
50
  <d2l-tag-list-item text="Example Tag 6"></d2l-tag-list-item>
26
51
  <d2l-tag-list-item text="Example Tag 7"></d2l-tag-list-item>
27
- </d2l-tag-list>
52
+ </d2l-tag-list>
28
53
  </d2l-demo-snippet>
29
54
 
30
55
  </d2l-demo-page>
31
56
  </body>
32
- </html>
57
+ </html>
@@ -1,5 +1,7 @@
1
1
  import '../colors/colors.js';
2
+ import '../tooltip/tooltip.js';
2
3
  import { css, html } from 'lit';
4
+ import { getUniqueId } from '../../helpers/uniqueId.js';
3
5
  import { labelStyles } from '../typography/styles.js';
4
6
 
5
7
  export const TagListItemMixin = superclass => class extends superclass {
@@ -17,19 +19,12 @@ export const TagListItemMixin = superclass => class extends superclass {
17
19
  return [labelStyles, css`
18
20
  :host {
19
21
  display: grid;
22
+ max-width: 100%;
20
23
  outline: none;
21
24
  }
22
25
  :host([hidden]) {
23
26
  display: none;
24
27
  }
25
- .tag-list-item-content {
26
- height: 1rem;
27
- margin: auto;
28
- min-width: 0;
29
- overflow: hidden;
30
- text-overflow: ellipsis;
31
- white-space: nowrap;
32
- }
33
28
  .tag-list-item-container {
34
29
  background-color: var(--d2l-color-regolith);
35
30
  border-radius: 6px;
@@ -37,21 +32,26 @@ export const TagListItemMixin = superclass => class extends superclass {
37
32
  box-sizing: border-box;
38
33
  color: var(--d2l-color-ferrite);
39
34
  cursor: pointer;
40
- display: flex;
35
+ max-width: 320px;
41
36
  min-width: 0;
37
+ outline: none;
38
+ overflow: hidden;
42
39
  padding: 0.25rem 0.6rem;
40
+ text-overflow: ellipsis;
43
41
  transition: background-color 0.2s ease-out, box-shadow 0.2s ease-out;
42
+ white-space: nowrap;
43
+ }
44
+ .tag-list-item-container:focus,
45
+ :host(:hover) .tag-list-item-container:focus {
46
+ box-shadow: inset 0 0 0 2px var(--d2l-color-celestine), 0 2px 4px rgba(0, 0, 0, 0.03);
44
47
  }
45
48
  :host(:hover) .tag-list-item-container,
46
- :host(:focus) .tag-list-item-container {
49
+ .tag-list-item-container:focus {
47
50
  background-color: var(--d2l-color-sylvite);
48
51
  }
49
52
  :host(:hover) .tag-list-item-container {
50
53
  box-shadow: inset 0 0 0 1px var(--d2l-color-mica), 0 2px 4px rgba(0, 0, 0, 0.03);
51
54
  }
52
- :host(:focus) .tag-list-item-container {
53
- box-shadow: inset 0 0 0 2px var(--d2l-color-celestine), 0 2px 4px rgba(0, 0, 0, 0.03);
54
- }
55
55
 
56
56
  @media (prefers-reduced-motion: reduce) {
57
57
  .tag-list-item-container {
@@ -65,12 +65,27 @@ export const TagListItemMixin = superclass => class extends superclass {
65
65
  super();
66
66
  /** @ignore */
67
67
  this.role = 'listitem';
68
+ this._id = getUniqueId();
69
+ }
70
+
71
+ firstUpdated(changedProperties) {
72
+ super.firstUpdated(changedProperties);
73
+
74
+ const container = this.shadowRoot.querySelector('.tag-list-item-container');
75
+ this.addEventListener('focus', () => container.focus());
76
+ this.addEventListener('blur', () => container.blur());
68
77
  }
69
78
 
70
- _renderTag(tagContent) {
79
+ _renderTag(tagContent, hasTruncationTooltip) {
80
+ const tooltip = hasTruncationTooltip ? html`
81
+ <d2l-tooltip for="${this._id}" show-truncated-only>
82
+ ${tagContent}
83
+ </d2l-tooltip>
84
+ ` : null;
71
85
  return html`
72
- <div class="tag-list-item-container d2l-label-text">
73
- <div class="tag-list-item-content">${tagContent}</div>
86
+ ${tooltip}
87
+ <div class="tag-list-item-container d2l-label-text" id="${this._id}" tabindex="-1">
88
+ ${tagContent}
74
89
  </div>
75
90
  `;
76
91
  }
@@ -14,7 +14,7 @@ class TagListItem extends TagListItemMixin(LitElement) {
14
14
  }
15
15
 
16
16
  render() {
17
- return this._renderTag(this.text);
17
+ return this._renderTag(this.text, true);
18
18
  }
19
19
  }
20
20
 
@@ -14,7 +14,7 @@ const PAGE_SIZE_LINES = {
14
14
  medium: 2,
15
15
  small: 3
16
16
  };
17
- const MARGIN_TOP_HEIGHT = 6;
17
+ const MARGIN_TOP_RIGHT = 6;
18
18
 
19
19
  class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
20
20
 
@@ -44,7 +44,6 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
44
44
  flex-wrap: wrap;
45
45
  margin: -6px -6px 0 0;
46
46
  padding: 0;
47
- position: relative;
48
47
  }
49
48
  ::slotted(*),
50
49
  d2l-button-subtle {
@@ -65,7 +64,7 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
65
64
  /** @ignore */
66
65
  this.arrowKeysDirection = 'leftrightupdown';
67
66
  this._chompIndex = 10000;
68
- this._items = [];
67
+ this._hasResized = false;
69
68
  this._resizeObserver = null;
70
69
  this._showHiddenTags = false;
71
70
  }
@@ -73,11 +72,18 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
73
72
  disconnectedCallback() {
74
73
  super.disconnectedCallback();
75
74
  if (this._resizeObserver) this._resizeObserver.disconnect();
75
+ if (this._subtleButtonResizeObserver) this._subtleButtonResizeObserver.disconnect();
76
76
  }
77
77
 
78
78
  firstUpdated(changedProperties) {
79
79
  super.firstUpdated(changedProperties);
80
80
 
81
+ const subtleButton = this.shadowRoot.querySelector('.d2l-tag-list-hidden-button');
82
+ this._subtleButtonResizeObserver = new ResizeObserver(() => {
83
+ this._subtleButtonWidth = Math.ceil(parseFloat(getComputedStyle(subtleButton).getPropertyValue('width')));
84
+ });
85
+ this._subtleButtonResizeObserver.observe(subtleButton);
86
+
81
87
  const container = this.shadowRoot.querySelector('.tag-list-outer-container');
82
88
  this._resizeObserver = new ResizeObserver((e) => requestAnimationFrame(() => this._handleResize(e)));
83
89
  this._resizeObserver.observe(container);
@@ -86,15 +92,17 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
86
92
  render() {
87
93
  let hiddenCount = 0;
88
94
  let hasHiddenTags = false;
89
- this._items.forEach((element, index) => {
90
- if (index >= this._chompIndex) hasHiddenTags = true;
91
- if (!this._showHiddenTags && index >= this._chompIndex) {
92
- hiddenCount++;
93
- element.setAttribute('data-is-chomped', '');
94
- } else {
95
- element.removeAttribute('data-is-chomped');
96
- }
97
- });
95
+ if (this._items) {
96
+ this._items.forEach((element, index) => {
97
+ if (index >= this._chompIndex) hasHiddenTags = true;
98
+ if (!this._showHiddenTags && index >= this._chompIndex) {
99
+ hiddenCount++;
100
+ element.setAttribute('data-is-chomped', '');
101
+ } else {
102
+ element.removeAttribute('data-is-chomped');
103
+ }
104
+ });
105
+ }
98
106
 
99
107
  let button = null;
100
108
  if (hasHiddenTags) {
@@ -123,7 +131,7 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
123
131
  `;
124
132
 
125
133
  const outerContainerStyles = {
126
- maxHeight: (this._showHiddenTags || !this._lines) ? undefined : `${(this._itemHeight + MARGIN_TOP_HEIGHT) * this._lines}px`
134
+ maxHeight: (this._showHiddenTags || !this._lines) ? undefined : `${(this._itemHeight + MARGIN_TOP_RIGHT) * this._lines}px`
127
135
  };
128
136
 
129
137
  return html`
@@ -140,15 +148,12 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
140
148
  }
141
149
 
142
150
  focus() {
143
- if (this._items.length > 0) this._items[0].focus();
151
+ if (this._items && this._items.length > 0) this._items[0].focus();
144
152
  }
145
153
 
146
154
  _chomp() {
147
155
  if (!this.shadowRoot || !this._lines || !this._itemLayouts) return;
148
156
 
149
- const subtleButton = this.shadowRoot.querySelector('.d2l-tag-list-hidden-button');
150
- const subtleButtonWidth = Math.ceil(parseFloat(getComputedStyle(subtleButton).getPropertyValue('width')));
151
-
152
157
  const showing = {
153
158
  count: 0,
154
159
  width: 0
@@ -166,9 +171,10 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
166
171
 
167
172
  for (let i = overflowingIndex; i < this._itemLayouts.length; i++) {
168
173
  const itemLayout = this._itemLayouts[i];
174
+ const itemWidth = Math.min(itemLayout.width, this._availableWidth);
169
175
 
170
- if (!isOverflowing && showing.width + itemLayout.width < this._availableWidth) {
171
- showing.width += itemLayout.width;
176
+ if (!isOverflowing && ((showing.width + itemWidth) <= (this._availableWidth + MARGIN_TOP_RIGHT))) {
177
+ showing.width += itemWidth;
172
178
  showing.count += 1;
173
179
  itemLayout.trigger = 'soft-show';
174
180
  } else if (k < this._lines) {
@@ -179,7 +185,6 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
179
185
  itemLayout.trigger = 'soft-hide';
180
186
  }
181
187
  }
182
-
183
188
  }
184
189
 
185
190
  if (!isOverflowing) {
@@ -189,7 +194,7 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
189
194
 
190
195
  // calculate if additional item(s) should be hidden due to subtle button needing space
191
196
  for (let j = this._itemLayouts.length; j--;) {
192
- if ((showing.width + subtleButtonWidth) < this._availableWidth) {
197
+ if ((showing.width + this._subtleButtonWidth) < this._availableWidth) {
193
198
  break;
194
199
  }
195
200
  const itemLayoutOverflowing = this._itemLayouts[j];
@@ -223,29 +228,39 @@ class TagList extends LocalizeCoreElement(ArrowKeysMixin(LitElement)) {
223
228
  return slot.assignedNodes({ flatten: true }).filter((node) => {
224
229
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
225
230
  const role = node.getAttribute('role');
231
+ node.removeAttribute('data-is-chomped');
226
232
  return (role === 'listitem');
227
233
  });
228
234
  }
229
235
 
230
236
  _handleResize(entries) {
231
- this._availableWidth = Math.ceil(entries[0].contentRect.width);
237
+ this._availableWidth = Math.floor(entries[0].contentRect.width);
232
238
  if (this._availableWidth >= PAGE_SIZE.large) this._lines = PAGE_SIZE_LINES.large;
233
239
  else if (this._availableWidth < PAGE_SIZE.large && this._availableWidth >= PAGE_SIZE.medium) this._lines = PAGE_SIZE_LINES.medium;
234
240
  else this._lines = PAGE_SIZE_LINES.small;
241
+ if (!this._hasResized) {
242
+ this._hasResized = true;
243
+ this._handleSlotChange();
244
+ }
235
245
  this._chomp();
236
246
  }
237
247
 
238
248
  _handleSlotChange() {
239
- requestAnimationFrame(() => {
249
+ if (!this._hasResized) return;
250
+
251
+ requestAnimationFrame(async() => {
240
252
  this._items = this._getTagListItems();
241
- this._itemLayouts = this._getItemLayouts(this._items);
253
+ if (!this._items || this._items.length === 0) return;
242
254
 
243
- if (this._items.length === 0) return;
255
+ await Promise.all(this._items.map(item => item.updateComplete));
256
+
257
+ this._itemLayouts = this._getItemLayouts(this._items);
244
258
  this._itemHeight = this._items[0].offsetHeight;
245
259
  this._items.forEach((item, index) => {
246
260
  item.setAttribute('tabIndex', index === 0 ? 0 : -1);
247
261
  });
248
262
  this._chomp();
263
+ this.requestUpdate();
249
264
  });
250
265
  }
251
266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "2.9.0",
3
+ "version": "2.10.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",