Skip to content
This repository was archived by the owner on May 20, 2023. It is now read-only.

Commit 567c648

Browse files
Googlernshahan
authored andcommitted
[reorder-list] Add ability to specify a "handle" element in a complex component
- Only the handle receives drag'n'drop events instead of the whole component - Prevents accidental dragging when interacting with the rest of the component, e.g. when selecting text PiperOrigin-RevId: 204038226
1 parent 40f5ce8 commit 567c648

File tree

1 file changed

+65
-17
lines changed

1 file changed

+65
-17
lines changed

lib/reorder_list/reorder_list.dart

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ export 'reorder_events.dart';
1919
/// marked with `reorderPlaceholder` attribute as a placeholder when moving
2020
/// items.
2121
///
22+
/// `reorderItem`s should be direct children of the `reorder-list` and, by
23+
/// default, the entire element is a handle and may be dragged and dropped
24+
/// to perfom reorder operations.
25+
///
26+
/// If a `reorderItem` component implements [ReorderHandleProvider], the child
27+
/// element marked with `reorderHandle` will be draggable for reorder
28+
/// operations. This allows complex components to be reordered by a handle and
29+
/// not have the entire component be listening to drag and drop events.
30+
///
2231
/// __Attention:__ This component is not an implementation of the Material List
2332
/// reorder control from the Material Spec. There is currently no support for
2433
/// interactions on a mobile device.
@@ -77,8 +86,8 @@ class ReorderListComponent implements OnDestroy {
7786
@Input()
7887
bool multiSelect = false;
7988

80-
// Reorderable items in the list
81-
List<ReorderItemDirective> _items;
89+
// Reorderable items in the list; keys are the handles, values are the items
90+
Map<HtmlElement, HtmlElement> _items;
8291
// Map of active drag&drop event subscriptions.
8392
Map<HtmlElement, List<StreamSubscription>> _subscriptions;
8493
// Map of active onDrag subscriptions,
@@ -111,12 +120,13 @@ class ReorderListComponent implements OnDestroy {
111120

112121
@ContentChildren(ReorderItemDirective)
113122
set items(List<ReorderItemDirective> value) {
114-
_items = value;
123+
_items = new Map.fromIterable(value,
124+
key: (e) => e.handleElement, value: (e) => e.element);
115125
_refreshItems();
116126
}
117127

118128
void _refreshItems() {
119-
Set<HtmlElement> newElements = _items.map((e) => e.element).toSet();
129+
final newElements = _handleElements.toSet();
120130
Set<HtmlElement> currentlyTracked = new Set.from(_subscriptions.keys);
121131
for (HtmlElement tracked in currentlyTracked) {
122132
if (!newElements.contains(tracked)) {
@@ -204,22 +214,23 @@ class ReorderListComponent implements OnDestroy {
204214
}
205215
}
206216

217+
final draggedElement = _items[_dragSourceElement];
207218
if (verticalItems) {
208219
placeholder.style
209-
..height = "${_dragSourceElement.borderEdge.height}px"
210-
..width = "${_dragSourceElement.borderEdge.width}px"
220+
..height = "${draggedElement.borderEdge.height}px"
221+
..width = "${draggedElement.borderEdge.width}px"
211222
..top = "${upperStackSize}px";
212223
} else {
213224
HtmlElement e = contents[toIndex];
214225
// If e move right, take its left as placeholder's left. Else, take
215226
// its right minus drag source width as placeholder's left.
216227
var left = moveRight
217228
? e.offset.left
218-
: e.offset.right - _dragSourceElement.borderEdge.width;
229+
: e.offset.right - draggedElement.borderEdge.width;
219230

220231
placeholder.style
221-
..height = "${_dragSourceElement.borderEdge.height}px"
222-
..width = "${_dragSourceElement.borderEdge.width}px"
232+
..height = "${draggedElement.borderEdge.height}px"
233+
..width = "${draggedElement.borderEdge.width}px"
223234
..top = "${e.offset.top}px"
224235
..left = "${left}px";
225236
}
@@ -297,7 +308,8 @@ class ReorderListComponent implements OnDestroy {
297308
_dragSubscriptions.remove(element);
298309
}
299310

300-
get _reorderElements => _items.map((x) => x.element).toList();
311+
List<HtmlElement> get _handleElements => _items.keys.toList();
312+
List<HtmlElement> get _reorderElements => _items.values.toList();
301313

302314
void _onDragStart(MouseEvent e) {
303315
// If multiSelect is enabled, clear the selection and replace with the
@@ -315,7 +327,7 @@ class ReorderListComponent implements OnDestroy {
315327
// Initialize all transforms.
316328
var contents = _reorderElements;
317329
int childCount = contents.length;
318-
_moveSourceIndex = contents.indexOf(_dragSourceElement);
330+
_moveSourceIndex = _handleElements.indexOf(_dragSourceElement);
319331
_curTransformY = new List<int>.filled(childCount, 0);
320332
_itemSizes = new List<int>(childCount);
321333
for (int i = 0; i < childCount; i++) {
@@ -550,7 +562,7 @@ class ReorderListComponent implements OnDestroy {
550562
}
551563

552564
int _getIndex(HtmlElement element) {
553-
List contents = _reorderElements;
565+
List contents = _handleElements;
554566
int childCount = contents.length;
555567
for (int i = 0; i < childCount; i++) {
556568
if (element == contents[i]) {
@@ -601,23 +613,59 @@ class ReorderListComponent implements OnDestroy {
601613

602614
typedef void ReorderListHandler(int sourceIndex, int destIndex);
603615

604-
/// Indicates that a child will participate in reorder operation inside a
605-
/// reorder-list component. See ReorderListComponent for usage.
606-
// TODO(google): Add back in host value once attribute bug is fixed.
616+
/// Indicates that the element is a list item in the containing `reorder-list`
617+
/// component. See [ReorderListComponent] for usage.
607618
@Directive(
608619
selector: '[reorderItem]',
609620
)
610621
class ReorderItemDirective {
611622
@HostBinding('attr.draggable')
612-
static const hostDraggable = 'true';
623+
String get hostDraggable => _reorderHandle == null ? 'true' : null;
613624

614625
@HostBinding('attr.role')
615626
static const hostRole = 'listitem';
616627

617628
@HostBinding('tabIndex')
618629
static const hostTabIndex = 0;
619630

631+
final HtmlElement element;
632+
final ReorderHandleProvider _handleProvider;
633+
HtmlElement _handleElement;
634+
635+
HtmlElement get _reorderHandle =>
636+
_handleElement ?? _handleProvider?.reorderHandle?.element;
637+
638+
/// The [HtmlElement] to be used as the drag handle.
639+
///
640+
/// Optional. If not specified, the host element for this directive will also
641+
/// be the handle.
642+
@Input()
643+
set reorderHandle(HtmlElement element) {
644+
_handleElement = element;
645+
}
646+
647+
HtmlElement get handleElement => _reorderHandle ?? element;
648+
649+
ReorderItemDirective(this.element, @Optional() this._handleProvider);
650+
}
651+
652+
/// Interface that will return a [ReorderHandleDirective] for use as a handle of
653+
/// a component marked as a `reorderItem` inside a reorder-list.
654+
abstract class ReorderHandleProvider {
655+
ReorderHandleDirective get reorderHandle;
656+
}
657+
658+
/// Indicates the element with this directive is the drag handle for the
659+
/// containing [reorderItem].
660+
@Directive(
661+
selector: '[reorderHandle]',
662+
exportAs: 'handle',
663+
)
664+
class ReorderHandleDirective {
665+
@HostBinding('attr.draggable')
666+
static const hostDraggable = 'true';
667+
620668
final HtmlElement element;
621669

622-
ReorderItemDirective(this.element);
670+
ReorderHandleDirective(this.element);
623671
}

0 commit comments

Comments
 (0)