@brightspace-ui/core 1.210.1 → 1.211.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.
@@ -5,7 +5,8 @@ import { html, LitElement } from 'lit-element/lit-element.js';
5
5
  import { ifDefined } from 'lit-html/directives/if-defined.js';
6
6
  import { repeat } from 'lit-html/directives/repeat.js';
7
7
 
8
- class ListDemoDragAndDropUsage extends LitElement {
8
+ class ListDemoDragAndDropPosition extends LitElement {
9
+
9
10
  static get properties() {
10
11
  return {
11
12
  list: { type: Array },
@@ -92,4 +93,4 @@ class ListDemoDragAndDropUsage extends LitElement {
92
93
  }
93
94
  }
94
95
 
95
- customElements.define('d2l-list-demo-drag-and-drop-usage', ListDemoDragAndDropUsage);
96
+ customElements.define('d2l-demo-list-drag-and-drop-position', ListDemoDragAndDropPosition);
@@ -6,45 +6,38 @@
6
6
  <link rel="stylesheet" href="../../demo/styles.css" type="text/css">
7
7
  <script type="module">
8
8
  import '../../demo/demo-page.js';
9
- import './list-demo-drag-and-drop-usage.js';
9
+ import './list-drag-and-drop-position.js';
10
+ import './list-drag-and-drop.js';
10
11
  </script>
11
12
  </head>
12
13
  <body unresolved>
13
14
 
14
15
  <d2l-demo-page page-title="d2l-list (with drag & drop)">
15
16
 
16
- <h2>Draggable</h2>
17
+ <h2>Position Change Event</h2>
17
18
 
18
19
  <d2l-demo-snippet>
19
20
  <template>
20
- <d2l-list-demo-drag-and-drop-usage></d2l-list-demo-drag-and-drop-usage>
21
+ <d2l-demo-list-drag-and-drop-position grid selectable hrefs></d2l-demo-list-drag-and-drop-position>
21
22
  </template>
22
23
  </d2l-demo-snippet>
23
24
 
24
- <h2>Draggable and Selectable</h2>
25
+ <h2>Move Event</h2>
25
26
 
26
27
  <d2l-demo-snippet>
27
28
  <template>
28
- <d2l-list-demo-drag-and-drop-usage selectable></d2l-list-demo-drag-and-drop-usage>
29
+ <d2l-demo-list-drag-and-drop></d2l-demo-list-drag-and-drop>
29
30
  </template>
30
31
  </d2l-demo-snippet>
31
32
 
32
- <h2>Draggable with Grid and Selectable</h2>
33
33
 
34
- <d2l-demo-snippet>
35
- <template>
36
- <d2l-list-demo-drag-and-drop-usage grid selectable></d2l-list-demo-drag-and-drop-usage>
37
- </template>
38
- </d2l-demo-snippet>
39
-
40
- <h2>All the Fixins (grid, draggable, selectable, hrefs)</h2>
34
+ </d2l-demo-page>
41
35
 
42
- <d2l-demo-snippet>
43
- <template>
44
- <d2l-list-demo-drag-and-drop-usage grid selectable hrefs></d2l-list-demo-drag-and-drop-usage>
45
- </template>
46
- </d2l-demo-snippet>
36
+ <script>
37
+ document.body.addEventListener('d2l-list-items-move', e => {
38
+ console.log('d2l-list-items-move', e.detail);
39
+ });
40
+ </script>
47
41
 
48
- </d2l-demo-page>
49
42
  </body>
50
43
  </html>
@@ -0,0 +1,171 @@
1
+ import '../list-item-content.js';
2
+ import '../list-item.js';
3
+ import '../list.js';
4
+ import { html, LitElement } from 'lit-element/lit-element.js';
5
+ import { ifDefined } from 'lit-html/directives/if-defined.js';
6
+ import { moveLocations } from '../list-item-drag-drop-mixin.js';
7
+ import { repeat } from 'lit-html/directives/repeat.js';
8
+
9
+ class ListDemoDragAndDrop extends LitElement {
10
+
11
+ static get properties() {
12
+ return {
13
+ items: { type: Array }
14
+ };
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this.items = [{
20
+ key: '1',
21
+ primaryText: 'Introductory Earth Sciences',
22
+ supportingText: 'This course explores the geological processes of the Earth\'s interior and surface. These include volcanism, earthquakes, mountain building, glaciation and weathering.',
23
+ imgSrc: 'https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-high-density-max-size.jpg',
24
+ items: [{
25
+ key: '1-1',
26
+ primaryText: 'Glaciation',
27
+ supportingText: 'Supporting Info',
28
+ imgSrc: '',
29
+ items: []
30
+ }, {
31
+ key: '1-2',
32
+ primaryText: 'Weathering',
33
+ supportingText: 'Supporting Info',
34
+ imgSrc: '',
35
+ items: []
36
+ }, {
37
+ key: '1-3',
38
+ primaryText: 'Volcanism',
39
+ supportingText: 'Supporting Info',
40
+ imgSrc: '',
41
+ items: []
42
+ }]
43
+ }, {
44
+ key: '2',
45
+ primaryText: 'Flow and Transport Through Fractured Rocks',
46
+ supportingText: 'Fractures are ubiquitous in geologic media and important in disciplines such as physical and contaminant hydrogeology, geotechnical engineering, civil and environmental engineering, petroleum engineering among other areas.',
47
+ imgSrc: 'https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-high-density-max-size.jpg',
48
+ items: [{
49
+ key: '2-1',
50
+ primaryText: 'Contaminant Transport',
51
+ supportingText: 'Supporting Info',
52
+ imgSrc: '',
53
+ items: []
54
+ }, {
55
+ key: '2-2',
56
+ primaryText: 'Modelling Flow in Fractured Media',
57
+ supportingText: 'Supporting Info',
58
+ imgSrc: '',
59
+ items: []
60
+ }]
61
+ }, {
62
+ key: '3',
63
+ primaryText: 'Applied Wetland Science',
64
+ supportingText: 'Advanced concepts on wetland ecosystems in the context of regional and global earth systems processes such as carbon and nitrogen cycling and climate change, applications of wetland paleoecology, use of isotopes and other geochemical tools in wetland science, and wetland engineering in landscape rehabilitation and ecotechnology.',
65
+ imgSrc: 'https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg',
66
+ items: [{
67
+ key: '3-1',
68
+ primaryText: 'Carbon & Nitrogen Cycling',
69
+ supportingText: 'Supporting Info',
70
+ imgSrc: '',
71
+ items: []
72
+ }, {
73
+ key: '3-2',
74
+ primaryText: 'Wetland Engineering',
75
+ supportingText: 'Supporting Info',
76
+ imgSrc: '',
77
+ items: []
78
+ }]
79
+ }];
80
+ }
81
+
82
+ render() {
83
+ const renderList = (items, nested) => {
84
+ return html`
85
+ <d2l-list grid slot="${ifDefined(nested ? 'nested' : undefined)}">
86
+ ${repeat(items, item => item.key, item => html`
87
+ <d2l-list-item
88
+ action-href="http://www.d2l.com"
89
+ draggable
90
+ drag-handle-text="${item.primaryText}"
91
+ drop-nested
92
+ key="${item.key}"
93
+ label="${item.primaryText}"
94
+ selectable>
95
+ ${nested ? null : html`<img slot="illustration" src="${item.imgSrc}">`}
96
+ <d2l-list-item-content>
97
+ <div>${item.primaryText}</div>
98
+ <div slot="supporting-info">${item.supportingText}</div>
99
+ </d2l-list-item-content>
100
+ ${item.items.length > 0 ? renderList(item.items, true) : null}
101
+ </d2l-list-item>
102
+ `)}
103
+ </d2l-list>
104
+ `;
105
+ };
106
+
107
+ return html`
108
+ <div @d2l-list-items-move="${this._handleListItemsMove}">
109
+ ${renderList(this.items, false)}
110
+ </div>
111
+ `;
112
+ }
113
+
114
+ async _handleListItemsMove(e) {
115
+
116
+ const sourceListItems = e.detail.sourceItems;
117
+ const target = e.detail.target;
118
+
119
+ // helper that gets the array containing item data, the item data, and the index within the array
120
+ const getItemInfo = (items, key) => {
121
+ for (let i = 0; i < items.length; i++) {
122
+ if (items[i].key === key) {
123
+ return { owner: items, item: items[i], index: i };
124
+ }
125
+ if (items[i].items && items[i].items.length > 0) {
126
+ const tempItemData = getItemInfo(items[i].items, key);
127
+ if (tempItemData) return tempItemData;
128
+ }
129
+ }
130
+ };
131
+
132
+ const dataToMove = [];
133
+
134
+ // remove data elements from original locations
135
+ sourceListItems.forEach(sourceListItem => {
136
+ const info = getItemInfo(this.items, sourceListItem.key);
137
+ info.owner.splice(info.index, 1);
138
+ dataToMove.push(info.item);
139
+ });
140
+
141
+ // append data elements to new location
142
+ const targetInfo = getItemInfo(this.items, target.item.key);
143
+ let targetItems;
144
+ let targetIndex;
145
+ if (target.location === moveLocations.nest) {
146
+ if (!targetInfo.item.items) targetInfo.item.items = [];
147
+ targetItems = targetInfo.item.items;
148
+ targetIndex = targetItems.length;
149
+ } else {
150
+ targetItems = targetInfo.owner;
151
+ if (target.location === moveLocations.above) targetIndex = targetInfo.index;
152
+ else if (target.location === moveLocations.below) targetIndex = targetInfo.index + 1;
153
+ }
154
+ for (let i = dataToMove.length - 1; i >= 0; i--) {
155
+ targetItems.splice(targetIndex, 0, dataToMove[i]);
156
+ }
157
+
158
+ await this.requestUpdate();
159
+
160
+ if (e.detail.keyboardActive) {
161
+ requestAnimationFrame(() => {
162
+ const newItem = this.shadowRoot.querySelector('d2l-list').getListItemByKey(sourceListItems[0].key);
163
+ newItem.activateDragHandle();
164
+ });
165
+ }
166
+
167
+ }
168
+
169
+ }
170
+
171
+ customElements.define('d2l-demo-list-drag-and-drop', ListDemoDragAndDrop);
@@ -113,16 +113,6 @@ export const ListItemCheckboxMixin = superclass => class extends SkeletonMixin(L
113
113
  }));
114
114
  }
115
115
 
116
- _getNestedList() {
117
- const nestedSlot = this.shadowRoot.querySelector('slot[name="nested"]');
118
- let nestedNodes = nestedSlot.assignedNodes();
119
- if (nestedNodes.length === 0) {
120
- nestedNodes = [...nestedSlot.childNodes];
121
- }
122
-
123
- return nestedNodes.find(node => (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'D2L-LIST'));
124
- }
125
-
126
116
  _onCheckboxActionClick(event) {
127
117
  event.preventDefault();
128
118
  if (this.disabled) return;
@@ -140,6 +130,10 @@ export const ListItemCheckboxMixin = superclass => class extends SkeletonMixin(L
140
130
  }
141
131
  }
142
132
 
133
+ _onNestedSlotChange() {
134
+ this._updateNestedSelectionProvider();
135
+ }
136
+
143
137
  _onSelectionProviderConnected(e) {
144
138
  e.stopPropagation();
145
139
  this._updateNestedSelectionProvider();
@@ -1,23 +1,27 @@
1
1
  import { css, html } from 'lit-element/lit-element.js';
2
+ import { findComposedAncestor, isComposedAncestor } from '../../helpers/dom.js';
2
3
  import { announce } from '../../helpers/announce.js';
3
4
  import { classMap } from 'lit-html/directives/class-map.js';
4
5
  import { dragActions } from './list-item-drag-handle.js';
5
- import { findComposedAncestor } from '../../helpers/dom.js';
6
6
  import { getUniqueId } from '../../helpers/uniqueId.js';
7
7
  import { ifDefined } from 'lit-html/directives/if-defined.js';
8
8
  import { nothing } from 'lit-html';
9
+ import { SelectionInfo } from '../selection/selection-mixin.js';
9
10
 
10
- export const dropLocation = Object.freeze({
11
+ export const moveLocations = Object.freeze({
11
12
  above: 1,
12
13
  below: 2,
13
14
  first: 3,
14
15
  last: 4,
15
16
  shiftDown: 5,
16
17
  shiftUp: 6,
18
+ nest: 7,
17
19
  void: 0
18
20
  });
19
21
 
20
- const dropTargetLeaveDelay = 1000; //ms
22
+ export const dropLocation = moveLocations; // backwards compatibility
23
+
24
+ const dropTargetLeaveDelay = 1000; // ms
21
25
  const touchHoldDuration = 400; // length of time user needs to hold down touch before dragging occurs
22
26
  const scrollSensitivity = 150; // pixels between top/bottom of viewport to scroll for mobile
23
27
 
@@ -38,20 +42,16 @@ const isDragSupported = () => {
38
42
  };
39
43
 
40
44
  class DragState {
41
- constructor(dragTarget) {
42
- this._dragTarget = dragTarget;
45
+ constructor(dragTargets) {
46
+ this._dragTargets = dragTargets;
43
47
  this._activeDropTarget = null;
44
48
  this._dropTargets = new Map();
45
49
  this._dropLocation = dropLocation.void;
46
50
  this._time = 0;
47
51
  }
48
52
 
49
- get dragTarget() {
50
- return this._dragTarget;
51
- }
52
-
53
- get dragTargetKey() {
54
- return this._dragTarget && this._dragTarget.key;
53
+ get dragTargets() {
54
+ return this._dragTargets;
55
55
  }
56
56
 
57
57
  get dropLocation() {
@@ -117,9 +117,9 @@ class DragState {
117
117
 
118
118
  let dragState = null;
119
119
 
120
- function createDragState(target) {
120
+ function createDragState(targets) {
121
121
  clearDragState();
122
- dragState = new DragState(target);
122
+ dragState = new DragState(targets ? targets : []);
123
123
  return dragState;
124
124
  }
125
125
 
@@ -263,6 +263,11 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
263
263
  * @type {string}
264
264
  */
265
265
  dragHandleText: { type: String, attribute: 'drag-handle-text' },
266
+ /**
267
+ * **Drag & drop:** Whether the items can be dropped as nested children
268
+ * @type {boolean}
269
+ */
270
+ dropNested: { type: Boolean, attribute: 'drop-nested' },
266
271
  /**
267
272
  * **Drag & drop:** Text to drag and drop
268
273
  * @type {string}
@@ -274,7 +279,7 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
274
279
  */
275
280
  key: { type: String, reflect: true },
276
281
  _draggingOver: { type: Boolean },
277
- _dropLocation: { type: Number },
282
+ _dropLocation: { type: Number, reflect: true, attribute: '_drop-location' },
278
283
  _focusingDragHandle: { type: Boolean },
279
284
  _hovering: { type: Boolean },
280
285
  _keyboardActive: { type: Boolean },
@@ -293,6 +298,7 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
293
298
  }
294
299
  .d2l-list-item-drag-bottom-marker,
295
300
  .d2l-list-item-drag-top-marker {
301
+ pointer-events: none;
296
302
  position: absolute;
297
303
  width: 100%;
298
304
  z-index: 1;
@@ -311,16 +317,16 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
311
317
  display: grid;
312
318
  grid-template-columns: 100%;
313
319
  grid-template-rows: 1rem 1fr 1fr 1rem;
314
- height: 100%;
315
- position: absolute;
316
- top: 0;
317
- width: 100%;
318
- z-index: 100;
320
+ }
321
+ :host([_drop-location="7"]) d2l-list-item-generic-layout {
322
+ border-radius: 6px;
323
+ outline: 2px solid var(--d2l-color-celestine);
319
324
  }
320
325
  @media only screen and (hover: hover), only screen and (pointer: fine) {
321
326
  d2l-list-item-drag-handle {
322
327
  opacity: 0;
323
328
  }
329
+ :host([selected]) d2l-list-item-drag-handle,
324
330
  d2l-list-item-drag-handle.d2l-hovering,
325
331
  d2l-list-item-drag-handle.d2l-focusing {
326
332
  opacity: 1;
@@ -335,8 +341,10 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
335
341
  constructor() {
336
342
  super();
337
343
  this._itemDragId = getUniqueId();
344
+ this.draggable = false;
338
345
  /** @ignore */
339
346
  this.dragging = false;
347
+ this.dropNested = false;
340
348
  }
341
349
 
342
350
  connectedCallback() {
@@ -351,6 +359,10 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
351
359
  super.firstUpdated(changedProperties);
352
360
  }
353
361
 
362
+ activateDragHandle() {
363
+ this.shadowRoot.querySelector(`#${this._itemDragId}`).activateKeyboardMode();
364
+ }
365
+
354
366
  _annoucePositionChange(dragTargetKey, dropTargetKey, dropLocation) {
355
367
  /** Dispatched when a draggable list item's position changes in the list. See [Event Details: d2l-list-item-position-change](#event-details%3A-d2l-list-item-position-change). */
356
368
  this.dispatchEvent(new CustomEvent('d2l-list-item-position-change', {
@@ -359,6 +371,84 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
359
371
  }));
360
372
  }
361
373
 
374
+ _dispatchListItemsMove(sourceItems, targetItem, moveLocation, keyboardActive) {
375
+ if (!keyboardActive) keyboardActive = false;
376
+ const rootList = this._getRootList();
377
+ rootList.dispatchEvent(new CustomEvent('d2l-list-items-move', {
378
+ detail: {
379
+ keyboardActive: keyboardActive,
380
+ sourceItems: sourceItems,
381
+ target: {
382
+ item: targetItem,
383
+ location: moveLocation
384
+ }
385
+ },
386
+ bubbles: true
387
+ }));
388
+ }
389
+
390
+ _dispatchMoveListItemFirst(moveToRoot) {
391
+ const list = (moveToRoot ? this._getRootList() : findComposedAncestor(this, node => node.tagName === 'D2L-LIST'));
392
+ const items = list.getItems();
393
+ this._dispatchListItemsMove([this], items[0], moveLocations.above, true);
394
+ }
395
+
396
+ _dispatchMoveListItemLast(moveToRoot) {
397
+ const list = (moveToRoot ? this._getRootList() : findComposedAncestor(this, node => node.tagName === 'D2L-LIST'));
398
+ const items = list.getItems();
399
+ this._dispatchListItemsMove([this], items[items.length - 1], moveLocations.below, true);
400
+ }
401
+
402
+ _dispatchMoveListItemNest() {
403
+ const listItem = this._getPreviousListItemSibling();
404
+ if (listItem) {
405
+ this._dispatchListItemsMove([this], listItem, moveLocations.nest, true);
406
+ }
407
+ }
408
+
409
+ _dispatchMoveListItemNext() {
410
+ const listItem = this._getNextListItemSibling();
411
+ if (listItem) {
412
+ const nestedList = listItem._getNestedList();
413
+ const items = (nestedList ? nestedList.getItems() : []);
414
+ if (items.length > 0) {
415
+ this._dispatchListItemsMove([this], items[0], moveLocations.above, true);
416
+ } else {
417
+ this._dispatchListItemsMove([this], listItem, moveLocations.below, true);
418
+ }
419
+ } else {
420
+ const parentListItem = this._getParentListItem();
421
+ if (parentListItem) {
422
+ this._dispatchListItemsMove([this], parentListItem, moveLocations.below, true);
423
+ }
424
+ }
425
+ }
426
+
427
+ _dispatchMoveListItemPrevious() {
428
+ const listItem = this._getPreviousListItemSibling();
429
+ if (listItem) {
430
+ const nestedList = listItem._getNestedList();
431
+ const items = (nestedList ? nestedList.getItems() : []);
432
+ if (items.length > 0) {
433
+ this._dispatchListItemsMove([this], items[items.length - 1], moveLocations.below, true);
434
+ } else {
435
+ this._dispatchListItemsMove([this], listItem, moveLocations.above, true);
436
+ }
437
+ } else {
438
+ const parentListItem = this._getParentListItem();
439
+ if (parentListItem) {
440
+ this._dispatchListItemsMove([this], parentListItem, moveLocations.above, true);
441
+ }
442
+ }
443
+ }
444
+
445
+ _dispatchMoveListItemUnnest() {
446
+ const listItem = this._getParentListItem();
447
+ if (listItem) {
448
+ this._dispatchListItemsMove([this], listItem, moveLocations.below, true);
449
+ }
450
+ }
451
+
362
452
  _findListItemFromCoordinates(x, y) {
363
453
  const listNode = findComposedAncestor(this.parentNode, (node) => node && node.tagName === 'D2L-LIST');
364
454
  return listNode.shadowRoot.elementFromPoint(x, y);
@@ -380,11 +470,27 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
380
470
  }
381
471
 
382
472
  _onDragEnd(e) {
473
+
383
474
  const dragState = getDragState();
384
475
  this.dragging = false;
385
- if (dragState.shouldDrop(e.timeStamp)) {
386
- this._annoucePositionChange(dragState.dragTargetKey, dragState.dropTargetKey, dragState.dropLocation);
476
+
477
+ // check the dropEffect in case the user cancelled by Escape while dragging ('none' set by browser)
478
+ if (e.dataTransfer.dropEffect !== 'none' && dragState.shouldDrop(e.timeStamp)) {
479
+
480
+ const dropTargetList = findComposedAncestor(dragState.dropTarget, node => node.tagName === 'D2L-LIST');
481
+ const shouldDispatchPositionChange = !dragState.dragTargets.find(dragTarget => {
482
+ const dragTargetList = findComposedAncestor(dragTarget, node => node.tagName === 'D2L-LIST');
483
+ return dragTargetList !== dropTargetList;
484
+ });
485
+
486
+ if (shouldDispatchPositionChange && dragState.dragTargets.length === 1) {
487
+ this._annoucePositionChange(dragState.dragTargets[0].key, dragState.dropTargetKey, dragState.dropLocation);
488
+ }
489
+
490
+ this._dispatchListItemsMove(dragState.dragTargets, dragState.dropTarget, dragState.dropLocation, false);
491
+
387
492
  }
493
+
388
494
  clearDragState();
389
495
  }
390
496
 
@@ -399,15 +505,31 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
399
505
  break;
400
506
  case dragActions.up:
401
507
  this._annoucePositionChange(this.key, null, dropLocation.shiftUp);
508
+ this._dispatchMoveListItemPrevious();
402
509
  break;
403
510
  case dragActions.down:
404
511
  this._annoucePositionChange(this.key, null, dropLocation.shiftDown);
512
+ this._dispatchMoveListItemNext();
513
+ break;
514
+ case dragActions.nest:
515
+ this._dispatchMoveListItemNest();
516
+ break;
517
+ case dragActions.unnest:
518
+ this._dispatchMoveListItemUnnest();
405
519
  break;
406
520
  case dragActions.first:
407
521
  this._annoucePositionChange(this.key, null, dropLocation.first);
522
+ this._dispatchMoveListItemFirst();
523
+ break;
524
+ case dragActions.rootFirst:
525
+ this._dispatchMoveListItemFirst(true);
408
526
  break;
409
527
  case dragActions.last:
410
528
  this._annoucePositionChange(this.key, null, dropLocation.last);
529
+ this._dispatchMoveListItemLast();
530
+ break;
531
+ case dragActions.rootLast:
532
+ this._dispatchMoveListItemLast(true);
411
533
  break;
412
534
  default:
413
535
  break;
@@ -427,13 +549,32 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
427
549
  e.dataTransfer.setData('text/plain', `${this.dropText}`);
428
550
  }
429
551
 
430
- // Legacy-Edge doesn't support setDragImage. Experience is not degraded for Legacy-Edge by doing this fix.
431
- if (e.dataTransfer.setDragImage) {
432
- const nodeImage = this.shadowRoot.querySelector('.d2l-list-item-drag-image') || this;
433
- e.dataTransfer.setDragImage(nodeImage, 50, 50);
434
- }
552
+ const nodeImage = this.shadowRoot.querySelector('.d2l-list-item-drag-image') || this;
553
+ e.dataTransfer.setDragImage(nodeImage, 50, 50);
554
+
555
+ const rootList = this._getRootList(this);
435
556
 
436
- createDragState(this);
557
+ // getSelectionInfo(false) is fast so we can quickly check the state
558
+ if (!rootList.dragMultiple || rootList.getSelectionInfo(false).state === SelectionInfo.states.none) {
559
+ createDragState([this]);
560
+ } else {
561
+
562
+ // get the seelcted items, but do not include selected items of selected items
563
+ const getDragTargets = list => {
564
+ let dragTargets = [];
565
+ list.getItems().forEach(item => {
566
+ if (item.selected || item === this) {
567
+ dragTargets.push(item);
568
+ } else if (item._selectionProvider) {
569
+ dragTargets = [...dragTargets, ...getDragTargets(item._selectionProvider)];
570
+ }
571
+ });
572
+ return dragTargets;
573
+ };
574
+ const dragTargets = getDragTargets(rootList);
575
+
576
+ createDragState(dragTargets);
577
+ }
437
578
 
438
579
  setTimeout(() => {
439
580
  this.dragging = true;
@@ -461,35 +602,47 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
461
602
  dragState.setActiveDropTarget(this, dragState.dropLocation);
462
603
  }
463
604
 
464
- _onDropTargetBottomDrag(e) {
605
+ _onDropTargetBottomDragEnter(e) {
465
606
  e.dataTransfer.dropEffect = 'move';
466
607
  const dragState = getDragState();
467
608
  dragState.setActiveDropTarget(this, dropLocation.below);
468
609
  this._inBottomArea = true;
469
610
  }
470
611
 
471
- _onDropTargetDragEnter(e) {
472
- e.dataTransfer.dropEffect = 'move';
473
- const dragState = getDragState();
474
- dragState.setActiveDropTarget(this, dropLocation.above);
475
- this._inTopArea = true;
476
- }
477
-
478
612
  _onDropTargetLowerDragEnter(e) {
479
613
  e.dataTransfer.dropEffect = 'move';
480
- if (this._inBottomArea) {
481
- const dragState = getDragState();
482
- dragState.setActiveDropTarget(this, dropLocation.above);
614
+ if (this.dropNested) {
483
615
  this._inBottomArea = false;
616
+ const dragState = getDragState();
617
+ dragState.setActiveDropTarget(this, moveLocations.nest);
618
+ } else {
619
+ if (this._inBottomArea) {
620
+ const dragState = getDragState();
621
+ dragState.setActiveDropTarget(this, dropLocation.above);
622
+ this._inBottomArea = false;
623
+ }
484
624
  }
485
625
  }
486
626
 
627
+ _onDropTargetTopDragEnter(e) {
628
+ e.dataTransfer.dropEffect = 'move';
629
+ const dragState = getDragState();
630
+ dragState.setActiveDropTarget(this, dropLocation.above);
631
+ this._inTopArea = true;
632
+ }
633
+
487
634
  _onDropTargetUpperDragEnter(e) {
488
635
  e.dataTransfer.dropEffect = 'move';
489
- if (this._inTopArea) {
490
- const dragState = getDragState();
491
- dragState.setActiveDropTarget(this, dropLocation.below);
636
+ if (this.dropNested) {
492
637
  this._inTopArea = false;
638
+ const dragState = getDragState();
639
+ dragState.setActiveDropTarget(this, moveLocations.nest);
640
+ } else {
641
+ if (this._inTopArea) {
642
+ const dragState = getDragState();
643
+ dragState.setActiveDropTarget(this, dropLocation.below);
644
+ this._inTopArea = false;
645
+ }
493
646
  }
494
647
  }
495
648
 
@@ -504,9 +657,14 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
504
657
 
505
658
  _onHostDragEnter(e) {
506
659
  const dragState = getDragState();
507
- if (this === dragState.dragTarget) {
508
- return;
509
- }
660
+ if (this === dragState.dragTarget) return;
661
+
662
+ // check if any of the drag targets are ancestors of the drop target
663
+ const invalidDropTarget = dragState.dragTargets.find(dragTarget => {
664
+ return isComposedAncestor(dragTarget, this);
665
+ });
666
+ if (invalidDropTarget) return;
667
+
510
668
  dragState.addDropTarget(this);
511
669
  this._draggingOver = true;
512
670
  e.dataTransfer.dropEffect = 'move';
@@ -635,11 +793,11 @@ export const ListItemDragDropMixin = superclass => class extends superclass {
635
793
  _renderDropTarget(templateMethod) {
636
794
  templateMethod = templateMethod || (DropTarget => DropTarget);
637
795
  return this.draggable && this._draggingOver ? templateMethod(html`
638
- <div class="d2l-list-item-drag-drop-grid" @drop="${this._onDrop}" @dragover="${this._onDragOver}">
639
- <div @dragenter="${this._onDropTargetDragEnter}"></div>
796
+ <div class="d2l-list-item-drag-drop-grid" slot="drop-target" @drop="${this._onDrop}" @dragover="${this._onDragOver}">
797
+ <div @dragenter="${this._onDropTargetTopDragEnter}"></div>
640
798
  <div @dragenter="${this._onDropTargetUpperDragEnter}"></div>
641
799
  <div @dragenter="${this._onDropTargetLowerDragEnter}"></div>
642
- <div @dragenter="${this._onDropTargetBottomDrag}"></div>
800
+ <div @dragenter="${this._onDropTargetBottomDragEnter}"></div>
643
801
  </div>
644
802
  `) : nothing;
645
803
  }
@@ -1,4 +1,3 @@
1
-
2
1
  import '../button/button-icon.js';
3
2
  import '../icons/icon.js';
4
3
  import { css, html, LitElement } from 'lit-element/lit-element.js';
@@ -6,6 +5,7 @@ import { buttonStyles } from '../button/button-styles.js';
6
5
  import { findComposedAncestor } from '../../helpers/dom.js';
7
6
  import { getFirstFocusableDescendant } from '../../helpers/focus.js';
8
7
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
8
+ import { RtlMixin } from '../../mixins/rtl-mixin.js';
9
9
 
10
10
  const keyCodes = Object.freeze({
11
11
  DOWN: 40,
@@ -26,16 +26,20 @@ export const dragActions = Object.freeze({
26
26
  down: 'down',
27
27
  first: 'first',
28
28
  last: 'last',
29
+ nest: 'nest',
29
30
  nextElement: 'next-element',
30
31
  previousElement: 'previous-element',
32
+ rootFirst: 'rootFirst',
33
+ rootLast: 'rootLast',
31
34
  save: 'keyboard-deactivate-save',
35
+ unnest: 'unnest',
32
36
  up: 'up'
33
37
  });
34
38
 
35
39
  /**
36
40
  * @fires d2l-list-item-drag-handle-action - Dispatched when an action performed on the drag handle
37
41
  */
38
- class ListItemDragHandle extends LocalizeCoreElement(LitElement) {
42
+ class ListItemDragHandle extends LocalizeCoreElement(RtlMixin(LitElement)) {
39
43
 
40
44
  static get properties() {
41
45
  return {
@@ -176,11 +180,11 @@ class ListItemDragHandle extends LocalizeCoreElement(LitElement) {
176
180
  break;
177
181
  case keyCodes.HOME:
178
182
  this._movingElement = true;
179
- action = dragActions.first;
183
+ action = (e.ctrlKey ? dragActions.rootFirst : dragActions.first);
180
184
  break;
181
185
  case keyCodes.END:
182
186
  this._movingElement = true;
183
- action = dragActions.last;
187
+ action = (e.ctrlKey ? dragActions.rootLast : dragActions.last);
184
188
  break;
185
189
  case keyCodes.TAB:
186
190
  action = e.shiftKey ? dragActions.previousElement : dragActions.nextElement;
@@ -189,9 +193,16 @@ class ListItemDragHandle extends LocalizeCoreElement(LitElement) {
189
193
  action = dragActions.cancel;
190
194
  this.updateComplete.then(() => this._keyboardActive = false);
191
195
  break;
196
+ case keyCodes.RIGHT:
197
+ this._movingElement = true;
198
+ action = (this.dir === 'rtl' ? dragActions.unnest : dragActions.nest);
199
+ break;
200
+ case keyCodes.LEFT:
201
+ this._movingElement = true;
202
+ action = (this.dir === 'rtl' ? dragActions.nest : dragActions.unnest) ;
203
+ break;
192
204
  case keyCodes.ENTER:
193
205
  case keyCodes.SPACE:
194
- case keyCodes.RIGHT:
195
206
  action = dragActions.save;
196
207
  this.updateComplete.then(() => this._keyboardActive = false);
197
208
  break;
@@ -221,7 +232,7 @@ class ListItemDragHandle extends LocalizeCoreElement(LitElement) {
221
232
  }
222
233
 
223
234
  _onInactiveKeyboard(e) {
224
- if (e.type === 'click' || e.keyCode === keyCodes.ENTER || e.keyCode === keyCodes.SPACE || e.keyCode === keyCodes.LEFT) {
235
+ if (e.type === 'click' || e.keyCode === keyCodes.ENTER || e.keyCode === keyCodes.SPACE) {
225
236
  this._dispatchAction(dragActions.active);
226
237
  this._keyboardActive = true;
227
238
  e.preventDefault();
@@ -229,7 +240,7 @@ class ListItemDragHandle extends LocalizeCoreElement(LitElement) {
229
240
  }
230
241
 
231
242
  _onInactiveKeyDown(e) {
232
- if (e.type === 'click' || e.keyCode === keyCodes.ENTER || e.keyCode === keyCodes.SPACE || e.keyCode === keyCodes.LEFT) {
243
+ if (e.type === 'click' || e.keyCode === keyCodes.ENTER || e.keyCode === keyCodes.SPACE) {
233
244
  e.preventDefault();
234
245
  }
235
246
  }
@@ -67,6 +67,17 @@ class ListItemGenericLayout extends RtlMixin(LitElement) {
67
67
  grid-column: content-start / end;
68
68
  grid-row: nested-start / nested-end;
69
69
  }
70
+ :host(.d2l-dragging-over) ::slotted([slot="nested"]) {
71
+ z-index: 6;
72
+ }
73
+
74
+ ::slotted([slot="drop-target"]) {
75
+ height: 100%;
76
+ position: absolute;
77
+ top: 0;
78
+ width: 100%;
79
+ z-index: 5;
80
+ }
70
81
 
71
82
  ::slotted([slot="outside-control"]),
72
83
  ::slotted([slot="control"]),
@@ -115,7 +126,6 @@ class ListItemGenericLayout extends RtlMixin(LitElement) {
115
126
  grid-column: content-start / end;
116
127
  z-index: 3;
117
128
  }
118
-
119
129
  `;
120
130
  }
121
131
 
@@ -148,6 +158,7 @@ class ListItemGenericLayout extends RtlMixin(LitElement) {
148
158
 
149
159
  render() {
150
160
  return html`
161
+ <slot name="drop-target"></slot>
151
162
  <slot name="content-action" class="d2l-cell" data-cell-num="5"></slot>
152
163
  <slot name="outside-control-action" class="d2l-cell" data-cell-num="1"></slot>
153
164
  <slot name="outside-control" class="d2l-cell" data-cell-num="2"></slot>
@@ -2,6 +2,7 @@ import '../colors/colors.js';
2
2
  import './list-item-generic-layout.js';
3
3
  import './list-item-placement-marker.js';
4
4
  import { css, html } from 'lit-element/lit-element.js';
5
+ import { findComposedAncestor, getComposedParent } from '../../helpers/dom.js';
5
6
  import { classMap } from 'lit-html/directives/class-map.js';
6
7
  import { getFirstFocusableDescendant } from '../../helpers/focus.js';
7
8
  import { getUniqueId } from '../../helpers/uniqueId.js';
@@ -301,6 +302,52 @@ export const ListItemMixin = superclass => class extends ListItemDragDropMixin(L
301
302
  });
302
303
  }
303
304
 
305
+ _getNestedList() {
306
+ const nestedSlot = this.shadowRoot.querySelector('slot[name="nested"]');
307
+ let nestedNodes = nestedSlot.assignedNodes();
308
+ if (nestedNodes.length === 0) {
309
+ nestedNodes = [...nestedSlot.childNodes];
310
+ }
311
+
312
+ return nestedNodes.find(node => (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'D2L-LIST'));
313
+ }
314
+
315
+ _getNextListItemSibling() {
316
+ let nextElement = this.nextElementSibling;
317
+ while (nextElement) {
318
+ if (this._isListItem(nextElement)) return nextElement;
319
+ nextElement = nextElement.nextElementSibling;
320
+ }
321
+ }
322
+
323
+ _getParentListItem() {
324
+ const parentListItem = findComposedAncestor(this.parentNode, node => this._isListItem(node));
325
+ return parentListItem;
326
+ }
327
+
328
+ _getPreviousListItemSibling() {
329
+ let previousElement = this.previousElementSibling;
330
+ while (previousElement) {
331
+ if (this._isListItem(previousElement)) return previousElement;
332
+ previousElement = previousElement.previousElementSibling;
333
+ }
334
+ }
335
+
336
+ _getRootList(node) {
337
+ if (!node) node = this;
338
+ let rootList;
339
+ while (node) {
340
+ if (node.tagName === 'D2L-LIST') rootList = node;
341
+ node = getComposedParent(node);
342
+ }
343
+ return rootList;
344
+ }
345
+
346
+ _isListItem(node) {
347
+ if (!node) node = this;
348
+ return node.role === 'rowgroup' || node.role === 'listitem';
349
+ }
350
+
304
351
  _onFocusIn() {
305
352
  this._focusing = true;
306
353
  }
@@ -341,18 +388,18 @@ export const ListItemMixin = superclass => class extends ListItemDragDropMixin(L
341
388
  'd2l-list-item-content-extend-separators': this._extendSeparators,
342
389
  'd2l-focusing': this._focusing,
343
390
  'd2l-hovering': this._hovering,
391
+ 'd2l-dragging-over': this._draggingOver
344
392
  };
345
393
  const contentClasses = {
346
394
  'd2l-list-item-content': true,
347
395
  'd2l-hovering': this._hoveringPrimaryAction,
348
- 'd2l-focusing': this._focusingPrimaryAction,
396
+ 'd2l-focusing': this._focusingPrimaryAction
349
397
  };
350
398
 
351
399
  const primaryAction = this._renderPrimaryAction ? this._renderPrimaryAction(this._contentId) : null;
352
400
 
353
401
  return html`
354
402
  ${this._renderTopPlacementMarker(html`<d2l-list-item-placement-marker></d2l-list-item-placement-marker>`)}
355
- ${this._renderDropTarget()}
356
403
  <div class="d2l-list-item-drag-image">
357
404
  <d2l-list-item-generic-layout
358
405
  @focusin="${this._onFocusIn}"
@@ -361,6 +408,7 @@ export const ListItemMixin = superclass => class extends ListItemDragDropMixin(L
361
408
  data-breakpoint="${this._breakpoint}"
362
409
  data-separators="${ifDefined(this._separators)}"
363
410
  ?grid-active="${this.role === 'rowgroup'}">
411
+ ${this._renderDropTarget()}
364
412
  ${this._renderDragHandle(this._renderOutsideControl)}
365
413
  ${this._renderDragTarget(this._renderOutsideControlAction)}
366
414
  ${this.selectable ? html`
@@ -391,7 +439,7 @@ export const ListItemMixin = superclass => class extends ListItemDragDropMixin(L
391
439
  <slot name="actions" class="d2l-list-item-actions">${actions}</slot>
392
440
  </div>
393
441
  <div slot="nested" @d2l-selection-provider-connected="${this._onSelectionProviderConnected}">
394
- <slot name="nested">${nested}</slot>
442
+ <slot name="nested" @slotchange="${this._onNestedSlotChange}">${nested}</slot>
395
443
  </div>
396
444
  </d2l-list-item-generic-layout>
397
445
  <div class="d2l-list-item-active-border"></div>
@@ -16,6 +16,11 @@ class List extends SelectionMixin(LitElement) {
16
16
 
17
17
  static get properties() {
18
18
  return {
19
+ /**
20
+ * Whether to the user can drag multiple items
21
+ * @type {boolean}
22
+ */
23
+ dragMultiple: { type: Boolean, attribute: 'drag-multiple' },
19
24
  /**
20
25
  * Whether to extend the separators beyond the content's edge
21
26
  * @type {boolean}
@@ -48,6 +53,7 @@ class List extends SelectionMixin(LitElement) {
48
53
 
49
54
  constructor() {
50
55
  super();
56
+ this.dragMultiple = false;
51
57
  this.extendSeparators = false;
52
58
  this.grid = false;
53
59
  this._listItemChanges = [];
@@ -91,17 +97,37 @@ class List extends SelectionMixin(LitElement) {
91
97
  `;
92
98
  }
93
99
 
100
+ getItems() {
101
+ const slot = this.shadowRoot.querySelector('slot:not([name])');
102
+ if (!slot) return [];
103
+ return slot.assignedNodes({ flatten: true }).filter((node) => {
104
+ return node.nodeType === Node.ELEMENT_NODE && (node.role === 'rowgroup' || node.role === 'listitem');
105
+ });
106
+ }
107
+
108
+ getListItemByKey(key) {
109
+ const items = this.getItems();
110
+ for (let i = 0; i < items.length; i++) {
111
+ if (items[i].key === key) return items[i];
112
+ if (items[i]._selectionProvider) {
113
+ const tempItem = items[i]._selectionProvider.getListItemByKey(key);
114
+ if (tempItem) return tempItem;
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+
94
120
  getListItemCount() {
95
- return this._getItems().length;
121
+ return this.getItems().length;
96
122
  }
97
123
 
98
124
  getListItemIndex(item) {
99
- return this._getItems().indexOf(item);
125
+ return this.getItems().indexOf(item);
100
126
  }
101
127
 
102
128
  getSelectedListItems(includeNested) {
103
129
  let selectedItems = [];
104
- this._getItems().forEach(item => {
130
+ this.getItems().forEach(item => {
105
131
  if (item.selected) selectedItems.push(item);
106
132
  if (includeNested && item._selectionProvider) {
107
133
  selectedItems = [...selectedItems, ...item._selectionProvider.getSelectedListItems(includeNested)];
@@ -116,7 +142,7 @@ class List extends SelectionMixin(LitElement) {
116
142
 
117
143
  let keys = selectionInfo.keys;
118
144
 
119
- this._getItems().forEach(item => {
145
+ this.getItems().forEach(item => {
120
146
  if (item._selectionProvider) {
121
147
  keys = [...keys, ...item._selectionProvider.getSelectionInfo(true).keys];
122
148
  }
@@ -125,14 +151,6 @@ class List extends SelectionMixin(LitElement) {
125
151
  return new SelectionInfo(keys, selectionInfo.state);
126
152
  }
127
153
 
128
- _getItems() {
129
- const slot = this.shadowRoot.querySelector('slot:not([name])');
130
- if (!slot) return [];
131
- return slot.assignedNodes({ flatten: true }).filter((node) => {
132
- return node.nodeType === Node.ELEMENT_NODE && (node.role === 'listitem' || node.tagName.includes('LIST-ITEM'));
133
- });
134
- }
135
-
136
154
  _handleKeyDown(e) {
137
155
  if (!this.grid || this.slot === 'nested' || e.keyCode !== keyCodes.TAB) return;
138
156
  e.preventDefault();
@@ -6208,8 +6208,8 @@
6208
6208
  ]
6209
6209
  },
6210
6210
  {
6211
- "name": "d2l-list-demo-drag-and-drop-usage",
6212
- "path": "./components/list/demo/list-demo-drag-and-drop-usage.js",
6211
+ "name": "d2l-demo-list-drag-and-drop-position",
6212
+ "path": "./components/list/demo/list-drag-and-drop-position.js",
6213
6213
  "attributes": [
6214
6214
  {
6215
6215
  "name": "grid",
@@ -6253,6 +6253,25 @@
6253
6253
  }
6254
6254
  ]
6255
6255
  },
6256
+ {
6257
+ "name": "d2l-demo-list-drag-and-drop",
6258
+ "path": "./components/list/demo/list-drag-and-drop.js",
6259
+ "attributes": [
6260
+ {
6261
+ "name": "items",
6262
+ "type": "array",
6263
+ "default": "[{\"key\":\"1\",\"primaryText\":\"Introductory Earth Sciences\",\"supportingText\":\"This course explores the geological processes of the Earth's interior and surface. These include volcanism, earthquakes, mountain building, glaciation and weathering.\",\"imgSrc\":\"https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-high-density-max-size.jpg\",\"items\":[{\"key\":\"1-1\",\"primaryText\":\"Glaciation\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"1-2\",\"primaryText\":\"Weathering\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"1-3\",\"primaryText\":\"Volcanism\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]}]},{\"key\":\"2\",\"primaryText\":\"Flow and Transport Through Fractured Rocks\",\"supportingText\":\"Fractures are ubiquitous in geologic media and important in disciplines such as physical and contaminant hydrogeology, geotechnical engineering, civil and environmental engineering, petroleum engineering among other areas.\",\"imgSrc\":\"https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-high-density-max-size.jpg\",\"items\":[{\"key\":\"2-1\",\"primaryText\":\"Contaminant Transport\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"2-2\",\"primaryText\":\"Modelling Flow in Fractured Media\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]}]},{\"key\":\"3\",\"primaryText\":\"Applied Wetland Science\",\"supportingText\":\"Advanced concepts on wetland ecosystems in the context of regional and global earth systems processes such as carbon and nitrogen cycling and climate change, applications of wetland paleoecology, use of isotopes and other geochemical tools in wetland science, and wetland engineering in landscape rehabilitation and ecotechnology.\",\"imgSrc\":\"https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg\",\"items\":[{\"key\":\"3-1\",\"primaryText\":\"Carbon & Nitrogen Cycling\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"3-2\",\"primaryText\":\"Wetland Engineering\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]}]}]"
6264
+ }
6265
+ ],
6266
+ "properties": [
6267
+ {
6268
+ "name": "items",
6269
+ "attribute": "items",
6270
+ "type": "array",
6271
+ "default": "[{\"key\":\"1\",\"primaryText\":\"Introductory Earth Sciences\",\"supportingText\":\"This course explores the geological processes of the Earth's interior and surface. These include volcanism, earthquakes, mountain building, glaciation and weathering.\",\"imgSrc\":\"https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-high-density-max-size.jpg\",\"items\":[{\"key\":\"1-1\",\"primaryText\":\"Glaciation\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"1-2\",\"primaryText\":\"Weathering\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"1-3\",\"primaryText\":\"Volcanism\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]}]},{\"key\":\"2\",\"primaryText\":\"Flow and Transport Through Fractured Rocks\",\"supportingText\":\"Fractures are ubiquitous in geologic media and important in disciplines such as physical and contaminant hydrogeology, geotechnical engineering, civil and environmental engineering, petroleum engineering among other areas.\",\"imgSrc\":\"https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-high-density-max-size.jpg\",\"items\":[{\"key\":\"2-1\",\"primaryText\":\"Contaminant Transport\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"2-2\",\"primaryText\":\"Modelling Flow in Fractured Media\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]}]},{\"key\":\"3\",\"primaryText\":\"Applied Wetland Science\",\"supportingText\":\"Advanced concepts on wetland ecosystems in the context of regional and global earth systems processes such as carbon and nitrogen cycling and climate change, applications of wetland paleoecology, use of isotopes and other geochemical tools in wetland science, and wetland engineering in landscape rehabilitation and ecotechnology.\",\"imgSrc\":\"https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg\",\"items\":[{\"key\":\"3-1\",\"primaryText\":\"Carbon & Nitrogen Cycling\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]},{\"key\":\"3-2\",\"primaryText\":\"Wetland Engineering\",\"supportingText\":\"Supporting Info\",\"imgSrc\":\"\",\"items\":[]}]}]"
6272
+ }
6273
+ ]
6274
+ },
6256
6275
  {
6257
6276
  "name": "d2l-demo-list-item-custom",
6258
6277
  "path": "./components/list/demo/list-item-custom.js",
@@ -6269,11 +6288,6 @@
6269
6288
  "type": "boolean",
6270
6289
  "default": "false"
6271
6290
  },
6272
- {
6273
- "name": "draggable",
6274
- "description": "**Drag & drop:** Whether the item is draggable",
6275
- "type": "boolean"
6276
- },
6277
6291
  {
6278
6292
  "name": "drag-handle-text",
6279
6293
  "description": "**Drag & drop:** The drag-handle label for assistive technology. If implementing drag & drop, you should change this to dynamically announce what the drag-handle is moving for assistive technology in keyboard mode.",
@@ -6284,6 +6298,18 @@
6284
6298
  "description": "**Drag & drop:** Text to drag and drop",
6285
6299
  "type": "string"
6286
6300
  },
6301
+ {
6302
+ "name": "draggable",
6303
+ "description": "**Drag & drop:** Whether the item is draggable",
6304
+ "type": "boolean",
6305
+ "default": "false"
6306
+ },
6307
+ {
6308
+ "name": "drop-nested",
6309
+ "description": "**Drag & drop:** Whether the items can be dropped as nested children",
6310
+ "type": "boolean",
6311
+ "default": "false"
6312
+ },
6287
6313
  {
6288
6314
  "name": "disabled",
6289
6315
  "description": "**Selection:** Disables the input",
@@ -6338,12 +6364,6 @@
6338
6364
  "type": "boolean",
6339
6365
  "default": "false"
6340
6366
  },
6341
- {
6342
- "name": "draggable",
6343
- "attribute": "draggable",
6344
- "description": "**Drag & drop:** Whether the item is draggable",
6345
- "type": "boolean"
6346
- },
6347
6367
  {
6348
6368
  "name": "dragHandleText",
6349
6369
  "attribute": "drag-handle-text",
@@ -6356,6 +6376,20 @@
6356
6376
  "description": "**Drag & drop:** Text to drag and drop",
6357
6377
  "type": "string"
6358
6378
  },
6379
+ {
6380
+ "name": "draggable",
6381
+ "attribute": "draggable",
6382
+ "description": "**Drag & drop:** Whether the item is draggable",
6383
+ "type": "boolean",
6384
+ "default": "false"
6385
+ },
6386
+ {
6387
+ "name": "dropNested",
6388
+ "attribute": "drop-nested",
6389
+ "description": "**Drag & drop:** Whether the items can be dropped as nested children",
6390
+ "type": "boolean",
6391
+ "default": "false"
6392
+ },
6359
6393
  {
6360
6394
  "name": "disabled",
6361
6395
  "attribute": "disabled",
@@ -6415,6 +6449,9 @@
6415
6449
  "name": "d2l-list-item-position-change",
6416
6450
  "description": "Dispatched when a draggable list item's position changes in the list. See [Event Details: d2l-list-item-position-change](#event-details%3A-d2l-list-item-position-change)."
6417
6451
  },
6452
+ {
6453
+ "name": "d2l-list-items-move"
6454
+ },
6418
6455
  {
6419
6456
  "name": "d2l-list-item-selected",
6420
6457
  "description": "Dispatched when the component item is selected"
@@ -6460,11 +6497,6 @@
6460
6497
  "type": "boolean",
6461
6498
  "default": "false"
6462
6499
  },
6463
- {
6464
- "name": "draggable",
6465
- "description": "**Drag & drop:** Whether the item is draggable",
6466
- "type": "boolean"
6467
- },
6468
6500
  {
6469
6501
  "name": "drag-handle-text",
6470
6502
  "description": "**Drag & drop:** The drag-handle label for assistive technology. If implementing drag & drop, you should change this to dynamically announce what the drag-handle is moving for assistive technology in keyboard mode.",
@@ -6475,6 +6507,18 @@
6475
6507
  "description": "**Drag & drop:** Text to drag and drop",
6476
6508
  "type": "string"
6477
6509
  },
6510
+ {
6511
+ "name": "draggable",
6512
+ "description": "**Drag & drop:** Whether the item is draggable",
6513
+ "type": "boolean",
6514
+ "default": "false"
6515
+ },
6516
+ {
6517
+ "name": "drop-nested",
6518
+ "description": "**Drag & drop:** Whether the items can be dropped as nested children",
6519
+ "type": "boolean",
6520
+ "default": "false"
6521
+ },
6478
6522
  {
6479
6523
  "name": "disabled",
6480
6524
  "description": "**Selection:** Disables the input",
@@ -6528,12 +6572,6 @@
6528
6572
  "type": "boolean",
6529
6573
  "default": "false"
6530
6574
  },
6531
- {
6532
- "name": "draggable",
6533
- "attribute": "draggable",
6534
- "description": "**Drag & drop:** Whether the item is draggable",
6535
- "type": "boolean"
6536
- },
6537
6575
  {
6538
6576
  "name": "dragHandleText",
6539
6577
  "attribute": "drag-handle-text",
@@ -6546,6 +6584,20 @@
6546
6584
  "description": "**Drag & drop:** Text to drag and drop",
6547
6585
  "type": "string"
6548
6586
  },
6587
+ {
6588
+ "name": "draggable",
6589
+ "attribute": "draggable",
6590
+ "description": "**Drag & drop:** Whether the item is draggable",
6591
+ "type": "boolean",
6592
+ "default": "false"
6593
+ },
6594
+ {
6595
+ "name": "dropNested",
6596
+ "attribute": "drop-nested",
6597
+ "description": "**Drag & drop:** Whether the items can be dropped as nested children",
6598
+ "type": "boolean",
6599
+ "default": "false"
6600
+ },
6549
6601
  {
6550
6602
  "name": "disabled",
6551
6603
  "attribute": "disabled",
@@ -6608,6 +6660,9 @@
6608
6660
  "name": "d2l-list-item-position-change",
6609
6661
  "description": "Dispatched when a draggable list item's position changes in the list. See [Event Details: d2l-list-item-position-change](#event-details%3A-d2l-list-item-position-change)."
6610
6662
  },
6663
+ {
6664
+ "name": "d2l-list-items-move"
6665
+ },
6611
6666
  {
6612
6667
  "name": "d2l-list-item-selected",
6613
6668
  "description": "Dispatched when the component item is selected"
@@ -6777,11 +6832,6 @@
6777
6832
  "type": "boolean",
6778
6833
  "default": "false"
6779
6834
  },
6780
- {
6781
- "name": "draggable",
6782
- "description": "**Drag & drop:** Whether the item is draggable",
6783
- "type": "boolean"
6784
- },
6785
6835
  {
6786
6836
  "name": "drag-handle-text",
6787
6837
  "description": "**Drag & drop:** The drag-handle label for assistive technology. If implementing drag & drop, you should change this to dynamically announce what the drag-handle is moving for assistive technology in keyboard mode.",
@@ -6792,6 +6842,18 @@
6792
6842
  "description": "**Drag & drop:** Text to drag and drop",
6793
6843
  "type": "string"
6794
6844
  },
6845
+ {
6846
+ "name": "draggable",
6847
+ "description": "**Drag & drop:** Whether the item is draggable",
6848
+ "type": "boolean",
6849
+ "default": "false"
6850
+ },
6851
+ {
6852
+ "name": "drop-nested",
6853
+ "description": "**Drag & drop:** Whether the items can be dropped as nested children",
6854
+ "type": "boolean",
6855
+ "default": "false"
6856
+ },
6795
6857
  {
6796
6858
  "name": "disabled",
6797
6859
  "description": "**Selection:** Disables the input",
@@ -6857,12 +6919,6 @@
6857
6919
  "type": "boolean",
6858
6920
  "default": "false"
6859
6921
  },
6860
- {
6861
- "name": "draggable",
6862
- "attribute": "draggable",
6863
- "description": "**Drag & drop:** Whether the item is draggable",
6864
- "type": "boolean"
6865
- },
6866
6922
  {
6867
6923
  "name": "dragHandleText",
6868
6924
  "attribute": "drag-handle-text",
@@ -6875,6 +6931,20 @@
6875
6931
  "description": "**Drag & drop:** Text to drag and drop",
6876
6932
  "type": "string"
6877
6933
  },
6934
+ {
6935
+ "name": "draggable",
6936
+ "attribute": "draggable",
6937
+ "description": "**Drag & drop:** Whether the item is draggable",
6938
+ "type": "boolean",
6939
+ "default": "false"
6940
+ },
6941
+ {
6942
+ "name": "dropNested",
6943
+ "attribute": "drop-nested",
6944
+ "description": "**Drag & drop:** Whether the items can be dropped as nested children",
6945
+ "type": "boolean",
6946
+ "default": "false"
6947
+ },
6878
6948
  {
6879
6949
  "name": "disabled",
6880
6950
  "attribute": "disabled",
@@ -6937,6 +7007,9 @@
6937
7007
  "name": "d2l-list-item-position-change",
6938
7008
  "description": "Dispatched when a draggable list item's position changes in the list. See [Event Details: d2l-list-item-position-change](#event-details%3A-d2l-list-item-position-change)."
6939
7009
  },
7010
+ {
7011
+ "name": "d2l-list-items-move"
7012
+ },
6940
7013
  {
6941
7014
  "name": "d2l-list-item-selected",
6942
7015
  "description": "Dispatched when the component item is selected"
@@ -6968,6 +7041,12 @@
6968
7041
  "type": "'all'|'between'|'none'",
6969
7042
  "default": "\"\\\"all\\\"\""
6970
7043
  },
7044
+ {
7045
+ "name": "drag-multiple",
7046
+ "description": "Whether to the user can drag multiple items",
7047
+ "type": "boolean",
7048
+ "default": "false"
7049
+ },
6971
7050
  {
6972
7051
  "name": "extend-separators",
6973
7052
  "description": "Whether to extend the separators beyond the content's edge",
@@ -6995,6 +7074,13 @@
6995
7074
  "type": "'all'|'between'|'none'",
6996
7075
  "default": "\"\\\"all\\\"\""
6997
7076
  },
7077
+ {
7078
+ "name": "dragMultiple",
7079
+ "attribute": "drag-multiple",
7080
+ "description": "Whether to the user can drag multiple items",
7081
+ "type": "boolean",
7082
+ "default": "false"
7083
+ },
6998
7084
  {
6999
7085
  "name": "extendSeparators",
7000
7086
  "attribute": "extend-separators",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "1.210.1",
3
+ "version": "1.211.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",