@brightspace-ui/core 1.209.1 → 1.212.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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>