@brightspace-ui/core 2.75.3 → 2.75.5

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.
@@ -0,0 +1,25 @@
1
+ import { LitElement } from 'lit';
2
+
3
+ /**
4
+ * Creates a Lit component that mirrors properties of another, and passes its properties through to
5
+ * a specific rendered element.
6
+ *
7
+ * @param superclass The Lit class to mirror (will copy all its properties).
8
+ * @param { String } target The element name or other selector string of the element to pass properties to.
9
+ */
10
+ export const DemoPassthroughMixin = (superclass, target) => class extends LitElement {
11
+ static get properties() {
12
+ return Object.fromEntries(superclass.elementProperties);
13
+ }
14
+
15
+ firstUpdated() {
16
+ this.target = this.shadowRoot.querySelector(target);
17
+ }
18
+
19
+ updated(changedProperties) {
20
+ const propertyDefinitions = superclass.elementProperties;
21
+ changedProperties.forEach((_value, key) => {
22
+ if (propertyDefinitions.get(key)?.attribute !== false) this.target[key] = this[key];
23
+ });
24
+ }
25
+ };
@@ -29,7 +29,7 @@ Tables are used to display tabular data in rows and columns. They can allow user
29
29
  ## Responsive Behavior
30
30
  If the browser window is too narrow to display the table’s contents, a scroll button appears. This alerts users to overflowing content and provides a way for users to scroll horizontally. The scroll button sticks to the top of the screen so that it's available as long as the table is in the viewport.
31
31
 
32
- <!-- docs: demo name:d2l-test-table size:large -->
32
+ <!-- docs: demo size:large -->
33
33
  ```html
34
34
  <script type="module">
35
35
  import '@brightspace-ui/core/components/table/demo/table-test.js';
@@ -45,7 +45,7 @@ If the viewport is very narrow — for example, on a mobile device — it may be
45
45
 
46
46
  The `d2l-table-wrapper` element can be combined with table styles to apply default/light styling, row selection styles, overflow scrolling and sticky headers to native `<table>` elements within your Lit components.
47
47
 
48
- <!-- docs: demo live name:d2l-test-table display:block -->
48
+ <!-- docs: demo live name:d2l-table-wrapper display:block -->
49
49
  ```html
50
50
  <script type="module">
51
51
  import { html, LitElement } from 'lit';
@@ -63,15 +63,7 @@ The `d2l-table-wrapper` element can be combined with table styles to apply defau
63
63
  { name: 'Japan', fruit: { 'apples': 8534, 'oranges': 1325, 'bananas': 78382756 }, selected: false }
64
64
  ];
65
65
 
66
- class TestTable extends LitElement {
67
-
68
- static get properties() {
69
- return {
70
- noColumnBorder: { attribute: 'no-column-border', type: Boolean },
71
- type: { type: String },
72
- stickyHeaders: { attribute: 'sticky-headers', type: Boolean }
73
- };
74
- }
66
+ class SampleTable extends LitElement {
75
67
 
76
68
  static get styles() {
77
69
  return tableStyles;
@@ -81,7 +73,7 @@ The `d2l-table-wrapper` element can be combined with table styles to apply defau
81
73
  const type = this.type === 'light' ? 'light' : 'default';
82
74
 
83
75
  return html`
84
- <d2l-table-wrapper ?no-column-border="${this.noColumnBorder}" ?sticky-headers="${this.stickyHeaders}" type="${type}">
76
+ <d2l-table-wrapper>
85
77
  <table class="d2l-table">
86
78
  <thead>
87
79
  <tr>
@@ -103,9 +95,9 @@ The `d2l-table-wrapper` element can be combined with table styles to apply defau
103
95
  }
104
96
 
105
97
  }
106
- customElements.define('d2l-test-table', TestTable);
98
+ customElements.define('d2l-sample-table', SampleTable);
107
99
  </script>
108
- <d2l-test-table></d2l-test-table>
100
+ <d2l-sample-table></d2l-sample-table>
109
101
  ```
110
102
 
111
103
  <!-- docs: start hidden content -->
@@ -304,7 +296,7 @@ If your table supports row selection, apply the `selected` attribute to `<tr>` r
304
296
 
305
297
  The `d2l-table-header` component can be placed in the `d2l-table-wrapper`'s `header` slot to provide a selection summary, a slot for `d2l-selection-action`s, and overflow-group behaviour.
306
298
 
307
- <!-- docs: demo live name:d2l-table-header -->
299
+ <!-- docs: demo live name:d2l-table-header display:block -->
308
300
  ```html
309
301
  <script type="module">
310
302
  import '@brightspace-ui/core/components/selection/selection-action.js';
@@ -314,7 +306,7 @@ The `d2l-table-header` component can be placed in the `d2l-table-wrapper`'s `hea
314
306
  import { html, LitElement } from 'lit';
315
307
  import { tableStyles } from '@brightspace-ui/core/components/table/table-wrapper.js';
316
308
 
317
- class MyTableWithHeaderElem extends LitElement {
309
+ class SampleTableWithHeader extends LitElement {
318
310
 
319
311
  static get properties() {
320
312
  return {
@@ -369,9 +361,9 @@ The `d2l-table-header` component can be placed in the `d2l-table-wrapper`'s `hea
369
361
  }
370
362
 
371
363
  }
372
- customElements.define('d2l-my-table-with-header-elem', MyTableWithHeaderElem);
364
+ customElements.define('d2l-sample-table-with-header', SampleTableWithHeader);
373
365
  </script>
374
- <d2l-my-table-with-header-elem></d2l-my-table-with-header-elem>
366
+ <d2l-sample-table-with-header></d2l-sample-table-with-header>
375
367
  ```
376
368
 
377
369
  <!-- docs: start hidden content -->
@@ -9,9 +9,10 @@ import '../../selection/selection-action-dropdown.js';
9
9
  import '../../selection/selection-action-menu-item.js';
10
10
  import '../../selection/selection-input.js';
11
11
 
12
- import { css, html, LitElement } from 'lit';
12
+ import { css, html } from 'lit';
13
+ import { tableStyles, TableWrapper } from '../table-wrapper.js';
14
+ import { DemoPassthroughMixin } from '../../demo/demo-passthrough-mixin.js';
13
15
  import { RtlMixin } from '../../../mixins/rtl-mixin.js';
14
- import { tableStyles } from '../table-wrapper.js';
15
16
 
16
17
  const fruits = ['Apples', 'Oranges', 'Bananas'];
17
18
 
@@ -27,25 +28,10 @@ const data = () => [
27
28
 
28
29
  const formatter = new Intl.NumberFormat('en-US');
29
30
 
30
- class TestTable extends RtlMixin(LitElement) {
31
+ class TestTable extends RtlMixin(DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')) {
31
32
 
32
33
  static get properties() {
33
34
  return {
34
- /**
35
- * Hides the column borders on "default" table type
36
- * @type {boolean}
37
- */
38
- noColumnBorder: { attribute: 'no-column-border', type: Boolean },
39
- /**
40
- * Type of table style to apply
41
- * @type {'default'|'light'}
42
- */
43
- type: { type: String },
44
- /**
45
- * Whether header row is sticky
46
- * @type {boolean}
47
- */
48
- stickyHeaders: { attribute: 'sticky-headers', type: Boolean },
49
35
  _data: { state: true },
50
36
  _sortField: { attribute: false, type: String },
51
37
  _sortDesc: { attribute: false, type: Boolean }
@@ -62,15 +48,10 @@ class TestTable extends RtlMixin(LitElement) {
62
48
 
63
49
  constructor() {
64
50
  super();
65
- this.noColumnBorder = false;
66
- this.sortDesc = false;
67
- this.stickyHeaders = false;
68
- this.type = 'default';
69
51
  this._data = data();
70
52
  }
71
53
 
72
54
  render() {
73
- const type = this.type === 'light' ? 'light' : 'default';
74
55
  const sorted = this._data.sort((a, b) => {
75
56
  if (this._sortDesc) {
76
57
  return b.fruit[this._sortField] - a.fruit[this._sortField];
@@ -78,7 +59,7 @@ class TestTable extends RtlMixin(LitElement) {
78
59
  return a.fruit[this._sortField] - b.fruit[this._sortField];
79
60
  });
80
61
  return html`
81
- <d2l-table-wrapper ?no-column-border="${this.noColumnBorder}" ?sticky-headers="${this.stickyHeaders}" type="${type}">
62
+ <d2l-table-wrapper>
82
63
  <d2l-table-header slot="header" no-sticky>
83
64
  <d2l-selection-action icon="tier1:plus-default" text="Add" @d2l-selection-action-click="${this._handleAddItem}"></d2l-selection-action>
84
65
  <d2l-selection-action-dropdown text="Move To" requires-selection>
@@ -7,6 +7,16 @@ import { SelectionHeader } from '../selection/selection-header.js';
7
7
  * A header for table components containing a selection summary and selection actions.
8
8
  */
9
9
  class TableHeader extends SelectionHeader {
10
+ static get properties() {
11
+ return {
12
+ /**
13
+ * Whether to render the selection summary
14
+ * @type {boolean}
15
+ */
16
+ noSelection: { type: Boolean, attribute: 'no-selection' }
17
+ };
18
+ }
19
+
10
20
  _renderSelection() {
11
21
  return html`
12
22
  <d2l-selection-summary
@@ -10601,15 +10601,32 @@
10601
10601
  },
10602
10602
  {
10603
10603
  "name": "sticky-headers",
10604
- "description": "Whether header row is sticky",
10604
+ "description": "Whether the header row is sticky. Useful for long tables to \"stick\" the header row in place as the user scrolls.",
10605
10605
  "type": "boolean",
10606
10606
  "default": "false"
10607
10607
  },
10608
10608
  {
10609
10609
  "name": "type",
10610
- "description": "Type of table style to apply",
10610
+ "description": "Type of table style to apply. The \"light\" style has fewer borders and tighter padding.",
10611
10611
  "type": "'default'|'light'",
10612
10612
  "default": "\"default\""
10613
+ },
10614
+ {
10615
+ "name": "selection-count-override",
10616
+ "description": "ADVANCED: Temporary optional parameter used to override existing count. Will be removed soon, use with caution.",
10617
+ "type": "number"
10618
+ },
10619
+ {
10620
+ "name": "item-count",
10621
+ "description": "Total number of items. Required when selecting all pages is allowed.",
10622
+ "type": "number",
10623
+ "default": "0"
10624
+ },
10625
+ {
10626
+ "name": "selection-single",
10627
+ "description": "Whether to render with single selection behaviour. If `selection-single` is specified, the nested `d2l-selection-input` elements will render radios instead of checkboxes, and the selection component will maintain a single selected item.",
10628
+ "type": "boolean",
10629
+ "default": "false"
10613
10630
  }
10614
10631
  ],
10615
10632
  "properties": [
@@ -10620,24 +10637,49 @@
10620
10637
  "type": "boolean",
10621
10638
  "default": "false"
10622
10639
  },
10623
- {
10624
- "name": "sortDesc",
10625
- "type": "boolean",
10626
- "default": "false"
10627
- },
10628
10640
  {
10629
10641
  "name": "stickyHeaders",
10630
10642
  "attribute": "sticky-headers",
10631
- "description": "Whether header row is sticky",
10643
+ "description": "Whether the header row is sticky. Useful for long tables to \"stick\" the header row in place as the user scrolls.",
10632
10644
  "type": "boolean",
10633
10645
  "default": "false"
10634
10646
  },
10635
10647
  {
10636
10648
  "name": "type",
10637
10649
  "attribute": "type",
10638
- "description": "Type of table style to apply",
10650
+ "description": "Type of table style to apply. The \"light\" style has fewer borders and tighter padding.",
10639
10651
  "type": "'default'|'light'",
10640
10652
  "default": "\"default\""
10653
+ },
10654
+ {
10655
+ "name": "selectionCountOverride",
10656
+ "attribute": "selection-count-override",
10657
+ "description": "ADVANCED: Temporary optional parameter used to override existing count. Will be removed soon, use with caution.",
10658
+ "type": "number"
10659
+ },
10660
+ {
10661
+ "name": "itemCount",
10662
+ "attribute": "item-count",
10663
+ "description": "Total number of items. Required when selecting all pages is allowed.",
10664
+ "type": "number",
10665
+ "default": "0"
10666
+ },
10667
+ {
10668
+ "name": "selectionSingle",
10669
+ "attribute": "selection-single",
10670
+ "description": "Whether to render with single selection behaviour. If `selection-single` is specified, the nested `d2l-selection-input` elements will render radios instead of checkboxes, and the selection component will maintain a single selected item.",
10671
+ "type": "boolean",
10672
+ "default": "false"
10673
+ }
10674
+ ],
10675
+ "slots": [
10676
+ {
10677
+ "name": "",
10678
+ "description": "Content to wrap"
10679
+ },
10680
+ {
10681
+ "name": "header",
10682
+ "description": "Slot for `d2l-table-header` to be rendered above the table"
10641
10683
  }
10642
10684
  ]
10643
10685
  },
@@ -10689,7 +10731,7 @@
10689
10731
  "attributes": [
10690
10732
  {
10691
10733
  "name": "no-selection",
10692
- "description": "Whether to render select-all and selection summary",
10734
+ "description": "Whether to render the selection summary",
10693
10735
  "type": "boolean",
10694
10736
  "default": "false"
10695
10737
  },
@@ -10715,7 +10757,7 @@
10715
10757
  {
10716
10758
  "name": "noSelection",
10717
10759
  "attribute": "no-selection",
10718
- "description": "Whether to render select-all and selection summary",
10760
+ "description": "Whether to render the selection summary",
10719
10761
  "type": "boolean",
10720
10762
  "default": "false"
10721
10763
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "2.75.3",
3
+ "version": "2.75.5",
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",
@@ -46,16 +46,22 @@ class Resizer {
46
46
  constructor() {
47
47
  this.contentRect = null;
48
48
  this.contentBounds = null;
49
+ this.isCollapsed = false;
49
50
  this.isMobile = false;
50
- this.panelSize = 0;
51
51
  this.isRtl = false;
52
+ this.panelSize = 0;
52
53
  this.secondaryFirst = false;
54
+ this._wasCollapsed = false;
53
55
  }
54
56
 
55
57
  clampHeight(height) {
56
58
  return clamp(height, this.contentBounds.minHeight, this.contentBounds.maxHeight);
57
59
  }
58
60
 
61
+ clampMaxHeight(height) {
62
+ return Math.min(height, this.contentBounds.maxHeight);
63
+ }
64
+
59
65
  clampWidth(width) {
60
66
  return clamp(width, this.contentBounds.minWidth, this.contentBounds.maxWidth);
61
67
  }
@@ -155,17 +161,18 @@ class DesktopMouseResizer extends Resizer {
155
161
  this._onMouseDown = this._onMouseDown.bind(this);
156
162
  this._onTouchMove = this._onTouchMove.bind(this);
157
163
  this._onMouseMove = this._onMouseMove.bind(this);
158
- this._onResizeEnd = this._onResizeEnd.bind(this);
164
+ this._onMouseUp = this._onMouseUp.bind(this);
165
+ this._onTouchEnd = this._onTouchEnd.bind(this);
159
166
  this._target = null;
160
167
  }
161
168
 
162
169
  connect(target) {
163
170
  target.addEventListener('touchstart', this._onTouchStart);
164
171
  target.addEventListener('touchmove', this._onTouchMove);
165
- target.addEventListener('touchend', this._onResizeEnd);
172
+ target.addEventListener('touchend', this._onTouchEnd);
166
173
  target.addEventListener('mousedown', this._onMouseDown);
167
174
  window.addEventListener('mousemove', this._onMouseMove);
168
- window.addEventListener('mouseup', this._onResizeEnd);
175
+ window.addEventListener('mouseup', this._onMouseUp);
169
176
  this._target = target;
170
177
  }
171
178
 
@@ -173,14 +180,18 @@ class DesktopMouseResizer extends Resizer {
173
180
  if (this._target) {
174
181
  this._target.removeEventListener('touchstart', this._onTouchStart);
175
182
  this._target.removeEventListener('touchmove', this._onTouchMove);
176
- this._target.removeEventListener('touchend', this._onResizeEnd);
183
+ this._target.removeEventListener('touchend', this._onTouchEnd);
177
184
  this._target.removeEventListener('mousedown', this._onMouseDown);
178
185
  }
179
186
  window.removeEventListener('mousemove', this._onMouseMove);
180
- window.removeEventListener('mouseup', this._onResizeEnd);
187
+ window.removeEventListener('mouseup', this._onMouseUp);
181
188
  this._target = null;
182
189
  }
183
190
 
191
+ _clampMaxWidth(width) {
192
+ return Math.min(width, this.contentBounds.maxWidth);
193
+ }
194
+
184
195
  _computeContentX(clientX) {
185
196
  const x = clientX - this.contentRect.left;
186
197
  // The direction of the container is flipped if exactly one of isRtl and secondaryFirst is true
@@ -190,6 +201,7 @@ class DesktopMouseResizer extends Resizer {
190
201
  _onMouseDown(e) {
191
202
  if (!this.isMobile) {
192
203
  e.preventDefault();
204
+ this._wasCollapsed = this.isCollapsed;
193
205
  this._resizeStart(e.clientX);
194
206
  }
195
207
  }
@@ -201,11 +213,19 @@ class DesktopMouseResizer extends Resizer {
201
213
  this._resize(e.clientX);
202
214
  }
203
215
 
204
- _onResizeEnd() {
205
- if (this._isResizing) {
206
- this._isResizing = false;
207
- this.dispatchResizeEnd();
216
+ _onMouseUp(e) {
217
+ if (!this._isResizing) {
218
+ return;
208
219
  }
220
+ this._resizeEnd(e.clientX);
221
+ }
222
+
223
+ _onTouchEnd(e) {
224
+ if (!this._isResizing) {
225
+ return;
226
+ }
227
+ const touch = e.changedTouches[0].clientX;
228
+ this._resizeEnd(touch);
209
229
  }
210
230
 
211
231
  _onTouchMove(e) {
@@ -215,27 +235,39 @@ class DesktopMouseResizer extends Resizer {
215
235
  const touch = e.touches[0];
216
236
  this._resize(touch.clientX);
217
237
  }
218
-
219
238
  _onTouchStart(e) {
220
239
  if (!this.isMobile) {
221
- e.preventDefault();
240
+ if (e.cancelable) e.preventDefault();
241
+ this._wasCollapsed = this.isCollapsed;
222
242
  const touch = e.touches[0];
223
243
  this._resizeStart(touch.clientX);
224
244
  }
225
245
  }
226
-
227
246
  _resize(clientX) {
228
- let actualSecondaryWidth;
229
247
  const x = this._computeContentX(clientX);
230
- const collapseThreshold = this.contentBounds.minWidth / 2;
248
+ const secondaryWidth = x + this._offset;
249
+ this.dispatchResize(this._clampMaxWidth(secondaryWidth), false);
250
+ }
251
+
252
+ _resizeEnd(clientX) {
253
+ if (!this._isResizing) {
254
+ return;
255
+ }
256
+ const expandedCollapseThreshold = this.contentBounds.minWidth * 0.75;
257
+ const collapsedCollapseThreshold = this.contentBounds.minWidth * 0.1;
258
+ const x = this._computeContentX(clientX);
231
259
  const desiredSecondaryWidth = x + this._offset;
232
- if (desiredSecondaryWidth < collapseThreshold) {
233
- actualSecondaryWidth = 0;
234
- } else {
235
- actualSecondaryWidth = this.clampWidth(desiredSecondaryWidth);
260
+ if (
261
+ (this._wasCollapsed && desiredSecondaryWidth < collapsedCollapseThreshold)
262
+ || (!this._wasCollapsed && desiredSecondaryWidth < expandedCollapseThreshold)
263
+ ) {
264
+ this.dispatchResize(0, true);
265
+ }
266
+ else if (desiredSecondaryWidth < this.contentBounds.minWidth) {
267
+ this.dispatchResize(this.contentBounds.minWidth, true);
236
268
  }
237
- const animateResize = desiredSecondaryWidth < actualSecondaryWidth || actualSecondaryWidth === 0;
238
- this.dispatchResize(actualSecondaryWidth, animateResize);
269
+ this._isResizing = false;
270
+ this.dispatchResizeEnd();
239
271
  }
240
272
 
241
273
  _resizeStart(clientX) {
@@ -326,6 +358,7 @@ class MobileMouseResizer extends Resizer {
326
358
 
327
359
  _onMouseDown(e) {
328
360
  if (this.isMobile) {
361
+ this._wasCollapsed = this.isCollapsed;
329
362
  this.dispatchResizeStart();
330
363
  e.preventDefault();
331
364
  const y = e.clientY - this.contentRect.top;
@@ -340,24 +373,29 @@ class MobileMouseResizer extends Resizer {
340
373
  return;
341
374
  }
342
375
  const y = e.clientY - this.contentRect.top;
376
+ const secondaryHeight = this.clampMaxHeight(this.contentRect.height - y + this._offset);
377
+ this.dispatchResize(secondaryHeight, false);
378
+ }
343
379
 
344
- let actualSecondaryHeight;
345
- const collapseThreshold = this.contentBounds.minHeight / 2;
380
+ _onMouseUp(e) {
381
+ if (!this._isResizing) {
382
+ return;
383
+ }
384
+ const expandedCollapseThreshold = this.contentBounds.minHeight * 0.75;
385
+ const collapsedCollapseThreshold = this.contentBounds.minHeight * 0.1;
386
+ const y = e.clientY - this.contentRect.top;
346
387
  const desiredSecondaryHeight = this.contentRect.height - y + this._offset;
347
- if (desiredSecondaryHeight < collapseThreshold) {
348
- actualSecondaryHeight = 0;
349
- } else {
350
- actualSecondaryHeight = this.clampHeight(desiredSecondaryHeight);
388
+ if (
389
+ (this._wasCollapsed && desiredSecondaryHeight < collapsedCollapseThreshold)
390
+ || (!this._wasCollapsed && desiredSecondaryHeight < expandedCollapseThreshold)
391
+ ) {
392
+ if (desiredSecondaryHeight > 0) this.dispatchResize(0, true);
351
393
  }
352
- const animateResize = desiredSecondaryHeight < actualSecondaryHeight || actualSecondaryHeight === 0;
353
- this.dispatchResize(actualSecondaryHeight, animateResize);
354
- }
355
-
356
- _onMouseUp() {
357
- if (this._isResizing) {
358
- this._isResizing = false;
359
- this.dispatchResizeEnd();
394
+ else if (desiredSecondaryHeight < this.contentBounds.minHeight) {
395
+ this.dispatchResize(this.contentBounds.minHeight, true);
360
396
  }
397
+ this._isResizing = false;
398
+ this.dispatchResizeEnd();
361
399
  }
362
400
 
363
401
  }
@@ -365,52 +403,72 @@ class MobileMouseResizer extends Resizer {
365
403
  class MobileTouchResizer extends Resizer {
366
404
  constructor() {
367
405
  super();
406
+ this.isExpanded = false;
407
+ this._wasExpanded = false;
368
408
  this._onResizeStart = this._onResizeStart.bind(this);
369
409
  this._onTouchMove = this._onTouchMove.bind(this);
370
410
  this._onResizeEnd = this._onResizeEnd.bind(this);
371
- this._target = null;
411
+ this._targetDivider = null;
412
+ this._targetSecondary = null;
372
413
  }
373
414
 
374
- connect(target) {
375
- target.addEventListener('touchstart', this._onResizeStart);
376
- target.addEventListener('touchmove', this._onTouchMove);
377
- target.addEventListener('touchend', this._onResizeEnd);
378
- this._target = target;
415
+ connect(targetDivider, targetSecondary) {
416
+ targetDivider.addEventListener('touchstart', this._onResizeStart);
417
+ targetDivider.addEventListener('touchmove', this._onTouchMove);
418
+ targetDivider.addEventListener('touchend', this._onResizeEnd);
419
+ this._targetDivider = targetDivider;
420
+
421
+ targetSecondary.addEventListener('touchstart', this._onResizeStart);
422
+ targetSecondary.addEventListener('touchmove', this._onTouchMove);
423
+ targetSecondary.addEventListener('touchend', this._onResizeEnd);
424
+ this._targetSecondary = targetSecondary;
379
425
  }
380
426
 
381
427
  disconnect() {
382
- if (this._target) {
383
- this._target.removeEventListener('touchstart', this._onResizeStart);
384
- this._target.removeEventListener('touchmove', this._onTouchMove);
385
- this._target.removeEventListener('touchend', this._onResizeEnd);
428
+ if (this._targetDivider) {
429
+ this._targetDivider.removeEventListener('touchstart', this._onResizeStart);
430
+ this._targetDivider.removeEventListener('touchmove', this._onTouchMove);
431
+ this._targetDivider.removeEventListener('touchend', this._onResizeEnd);
386
432
  }
387
- this._target = null;
388
- }
433
+ this._targetDivider = null;
389
434
 
390
- _computeTouchDirection() {
391
- const oldest = this._touches[0];
392
- const newest = this._touches[this._touches.length - 1];
393
- if (oldest === newest) {
394
- return 0;
435
+ if (this._targetSecondary) {
436
+ this._targetSecondary.removeEventListener('touchstart', this._onResizeStart);
437
+ this._targetSecondary.removeEventListener('touchmove', this._onTouchMove);
438
+ this._targetSecondary.removeEventListener('touchend', this._onResizeEnd);
395
439
  }
396
- return newest - oldest;
440
+ this._targetSecondary = null;
397
441
  }
398
442
 
399
443
  _onResizeEnd() {
400
- if (this._isResizing) {
401
- if (this.panelSize > this.contentBounds.minHeight && this.panelSize < this.contentBounds.maxHeight) {
402
- let secondaryHeight;
403
- const touchDirection = this._computeTouchDirection();
404
- if (touchDirection >= 0) {
405
- secondaryHeight = this.contentBounds.minHeight;
406
- } else {
407
- secondaryHeight = this.contentBounds.maxHeight;
408
- }
409
- this.dispatchResize(secondaryHeight, true);
410
- }
411
- this._isResizing = false;
412
- this.dispatchResizeEnd();
444
+ if (!this._isResizing) {
445
+ return;
446
+ }
447
+ let secondaryHeight;
448
+ const expandedShrinkThreshold = this.contentBounds.minHeight + (this.contentBounds.maxHeight - this.contentBounds.minHeight) * 0.9;
449
+ const expandThreshold = this.contentBounds.minHeight + (this.contentBounds.maxHeight - this.contentBounds.minHeight) * 0.1;
450
+ const collapseThreshold = this.contentBounds.minHeight * 0.9;
451
+ const collapsedExpandThreshold = this.contentBounds.minHeight * 0.1;
452
+ if (
453
+ (!this._wasCollapsed && this.panelSize < collapseThreshold)
454
+ || (this._wasCollapsed && this.panelSize < collapsedExpandThreshold)
455
+ ) {
456
+ secondaryHeight = 0;
457
+ }
458
+ else if (
459
+ (this._wasExpanded && this.panelSize < expandedShrinkThreshold)
460
+ || (this._wasCollapsed && this.panelSize < expandThreshold)
461
+ || (!this._wasExpanded && !this._wasCollapsed && this.panelSize < expandThreshold && this.panelSize > collapseThreshold)
462
+ ) {
463
+ secondaryHeight = this.contentBounds.minHeight;
413
464
  }
465
+ else {
466
+ secondaryHeight = this.contentBounds.maxHeight;
467
+ }
468
+ this.dispatchResize(secondaryHeight, true);
469
+
470
+ this._isResizing = false;
471
+ this.dispatchResizeEnd();
414
472
  }
415
473
 
416
474
  _onResizeStart(e) {
@@ -421,6 +479,8 @@ class MobileTouchResizer extends Resizer {
421
479
  this._isResizing = true;
422
480
  this._touches = [];
423
481
  this._trackTouch(touch);
482
+ this._wasCollapsed = this.isCollapsed;
483
+ this._wasExpanded = this.isExpanded;
424
484
  }
425
485
  }
426
486
 
@@ -431,18 +491,19 @@ class MobileTouchResizer extends Resizer {
431
491
  const touch = e.touches[0];
432
492
  const curTouch = touch.screenY;
433
493
  const delta = curTouch - this._prevTouch;
434
- const curScroll = this._target.scrollTop;
494
+ const isScrollingDivider = this._targetDivider.contains(e.target);
495
+ const curScroll = this._targetSecondary.scrollTop;
435
496
  this._trackTouch(touch);
436
497
 
437
498
  let isScrollable;
438
499
  let secondaryHeight = this.panelSize;
439
500
  if (delta > 0) {
440
- if (curScroll === 0) {
441
- secondaryHeight = this.clampHeight(this.panelSize - delta);
501
+ if (isScrollingDivider || curScroll === 0) {
502
+ secondaryHeight = this.clampMaxHeight(this.panelSize - delta);
442
503
  }
443
504
  isScrollable = curScroll > 0;
444
505
  } else if (delta < 0) {
445
- secondaryHeight = this.clampHeight(this.panelSize - delta);
506
+ secondaryHeight = this.clampMaxHeight(this.panelSize - delta);
446
507
  isScrollable = secondaryHeight === this.contentBounds.maxHeight;
447
508
  }
448
509
  if (!isScrollable && e.cancelable) {
@@ -594,7 +655,15 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
594
655
  background-color: var(--d2l-color-gypsum);
595
656
  }
596
657
  :host([resizable]) [data-is-collapsed] aside {
597
- display: none;
658
+ visibility: hidden;
659
+ }
660
+ :host([resizable]:not([dir="rtl"]):not([secondary-first])) aside,
661
+ :host([resizable][dir="rtl"][secondary-first]) aside {
662
+ float: left;
663
+ }
664
+ :host([resizable][dir="rtl"]:not([secondary-first])) aside,
665
+ :host([resizable]:not([dir="rtl"])[secondary-first]) aside {
666
+ float: right;
598
667
  }
599
668
  .d2l-template-primary-secondary-divider {
600
669
  background-color: var(--d2l-color-mica);
@@ -672,10 +741,12 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
672
741
  .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-left {
673
742
  display: block;
674
743
  }
675
- :host(:not([dir="rtl"])) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-left {
744
+ :host(:not([dir="rtl"]):not([secondary-first])) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-left,
745
+ :host([dir="rtl"][secondary-first]) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-left {
676
746
  display: none;
677
747
  }
678
- :host([dir="rtl"]) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-right {
748
+ :host(:not([dir="rtl"])[secondary-first]) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-right,
749
+ :host([dir="rtl"]:not([secondary-first])) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-right {
679
750
  display: none;
680
751
  }
681
752
  d2l-icon {
@@ -974,7 +1045,7 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
974
1045
  </d2l-icon-custom>
975
1046
  </div>
976
1047
  <div class="d2l-template-primary-secondary-divider-handle-mobile">
977
- ${size === 0 ? html`<d2l-icon icon="tier1:chevron-up"></d2l-icon>` : html`<d2l-icon icon="tier1:chevron-down"></d2l-icon>`}
1048
+ <d2l-icon icon=${size === 0 ? 'tier1:chevron-up' : 'tier1:chevron-down'}></d2l-icon>
978
1049
  </div>
979
1050
  </div>
980
1051
  </div>
@@ -989,6 +1060,14 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
989
1060
 
990
1061
  updated(changedProperties) {
991
1062
  super.updated(changedProperties);
1063
+ if (changedProperties.has('_isCollapsed')) {
1064
+ this._desktopMouseResizer.isCollapsed = this._isCollapsed;
1065
+ this._mobileMouseResizer.isCollapsed = this._isCollapsed;
1066
+ this._mobileTouchResizer.isCollapsed = this._isCollapsed;
1067
+ }
1068
+ if (changedProperties.has('_isExpanded')) {
1069
+ this._mobileTouchResizer.isExpanded = this._isExpanded;
1070
+ }
992
1071
  if (changedProperties.has('_size')) {
993
1072
  if (this.storageKey) {
994
1073
  const key = computeSizeKey(this.storageKey);
@@ -1003,16 +1082,20 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
1003
1082
  this._desktopKeyboardResizer.secondaryFirst = this.secondaryFirst;
1004
1083
  this._desktopMouseResizer.secondaryFirst = this.secondaryFirst;
1005
1084
  }
1006
- if (!this._secondary) {
1007
- this._secondary = this.shadowRoot.querySelector('aside');
1085
+ if (!this._divider) {
1008
1086
  this._divider = this.shadowRoot.querySelector('.d2l-template-primary-secondary-divider');
1009
1087
  }
1088
+ if (changedProperties.has('_isMobile') && this._isMobile) {
1089
+ this._secondary = this.shadowRoot.querySelector('aside');
1090
+ if (this._divider.isConnected && this._secondary.isConnected) {
1091
+ this._mobileTouchResizer.connect(this._divider, this._secondary);
1092
+ }
1093
+ }
1010
1094
  if (this._divider.isConnected && !this._hasConnectedResizers) {
1011
1095
  this._desktopKeyboardResizer.connect(this._divider);
1012
1096
  this._desktopMouseResizer.connect(this._divider);
1013
1097
  this._mobileKeyboardResizer.connect(this._divider);
1014
1098
  this._mobileMouseResizer.connect(this._divider);
1015
- this._mobileTouchResizer.connect(this._secondary);
1016
1099
  this._hasConnectedResizers = true;
1017
1100
  }
1018
1101
  }