diff --git a/CHANGELOG.md b/CHANGELOG.md index f7b9878..5dc3bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Version 2.0.0 (2021-03-06) + +- Migrate to null safety. + ## Version 1.4.3 (2019-12-10) - Remove unnecessary new keyword. diff --git a/README.md b/README.md index 00069a0..24c7eb1 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ a `dnd-retarget` attribute to the host: The _Dart Drag and Drop_ library is inspired by -- [jQuery UI Draggable](http://jqueryui.com/draggable/) +- [jQuery UI Draggable](https://jqueryui.com/draggable/) - [Draggabilly](https://draggabilly.desandro.com/) Thank you for your work! diff --git a/analysis_options.yaml b/analysis_options.yaml deleted file mode 100644 index 338ed79..0000000 --- a/analysis_options.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Lint rules and documentation, see http://dart-lang.github.io/linter/lints -linter: - rules: - - cancel_subscriptions - - hash_and_equals - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - test_types_in_equals - - unrelated_type_equality_checks - - valid_regexps diff --git a/example/custom_acceptor/example.dart b/example/custom_acceptor/example.dart index 8bec813..dd21431 100644 --- a/example/custom_acceptor/example.dart +++ b/example/custom_acceptor/example.dart @@ -31,7 +31,8 @@ class MyAcceptor extends Acceptor { @override bool accepts( Element draggableElement, int draggableId, Element dropzoneElement) { - InputElement input = draggableElement.querySelector('input'); + InputElement? input = + draggableElement.querySelector('input') as InputElement?; return input != null && input.value == 'acceptme'; } } diff --git a/example/custom_avatar/example.dart b/example/custom_avatar/example.dart index a689f77..9fb9e36 100644 --- a/example/custom_avatar/example.dart +++ b/example/custom_avatar/example.dart @@ -13,7 +13,7 @@ main() { avatarHandler: MyAvatarHandler()); // Install dropzone (trash). - Element trash = querySelector('.trash'); + Element trash = querySelector('.trash') as Element; Dropzone dropzone = Dropzone(trash); // Keep track if we're over the trash. @@ -27,7 +27,7 @@ main() { // Change the drag avatar during the drag. draggable.onDrag.listen((DraggableEvent event) { - MyAvatarHandler handler = event.avatarHandler; + MyAvatarHandler handler = event.avatarHandler as MyAvatarHandler; if (overTrash) { // Set to last image if over trash. @@ -79,10 +79,10 @@ class MyAvatarHandler extends AvatarHandler { setLeftTop(startPosition + OFFSET); // Ensure avatar has an absolute position. - avatar.style.position = 'absolute'; + avatar!.style.position = 'absolute'; // Add the drag avatar to the body element. - document.body.append(avatar); + document.body!.append(avatar!); } @override @@ -92,7 +92,9 @@ class MyAvatarHandler extends AvatarHandler { @override void dragEnd(Point startPosition, Point position) { - avatar.remove(); + if (avatar != null) { + avatar!.remove(); + } } /// Updates the image to [imageNumber]. diff --git a/example/detection_only/example.dart b/example/detection_only/example.dart index 51578b2..623fb1a 100644 --- a/example/detection_only/example.dart +++ b/example/detection_only/example.dart @@ -10,7 +10,7 @@ main() { Draggable draggable = Draggable(querySelector('.draggable')); // Paragraph. - Element p = querySelector('.draggable p'); + Element p = querySelector('.draggable p') as Element; // Listen to drag start. draggable.onDragStart.listen((DraggableEvent event) { diff --git a/example/nested_dropzones/example.dart b/example/nested_dropzones/example.dart index 38b2ade..ca05ade 100644 --- a/example/nested_dropzones/example.dart +++ b/example/nested_dropzones/example.dart @@ -15,9 +15,9 @@ main() { Dropzone innerDropzone = Dropzone(querySelector('.dropzone-inner')); // The text elements. - Element draggableText = querySelector('.draggable > p'); - Element outerText = querySelector('.dropzone-outer > span'); - Element innerText = querySelector('.dropzone-inner > span'); + Element draggableText = querySelector('.draggable > p') as Element; + Element outerText = querySelector('.dropzone-outer > span') as Element; + Element innerText = querySelector('.dropzone-inner > span') as Element; // Dropped flags to help for displaying the correct text in dragLeave // (dragLeave is fired after a drop). diff --git a/example/nested_elements/example.dart b/example/nested_elements/example.dart index 7864108..a36a86c 100644 --- a/example/nested_elements/example.dart +++ b/example/nested_elements/example.dart @@ -12,7 +12,7 @@ main() { Dropzone dropzone = Dropzone(querySelector('.dropzone')); // Text element - Element text = querySelector('.dropzone > span'); + Element text = querySelector('.dropzone > span') as Element; // Listen to dragEnter. dropzone.onDragEnter.listen((DropzoneEvent event) { diff --git a/example/simple_sortable/example.dart b/example/simple_sortable/example.dart index 0820cc3..f754209 100644 --- a/example/simple_sortable/example.dart +++ b/example/simple_sortable/example.dart @@ -18,9 +18,9 @@ main() { /// Simple function to swap two elements. void swapElements(Element elm1, Element elm2) { - var parent1 = elm1.parent; + var parent1 = elm1.parent as Element; var next1 = elm1.nextElementSibling; - var parent2 = elm2.parent; + var parent2 = elm2.parent as Element; var next2 = elm2.nextElementSibling; parent1.insertBefore(elm2, next1); diff --git a/lib/src/draggable.dart b/lib/src/draggable.dart index 109a149..2d10923 100644 --- a/lib/src/draggable.dart +++ b/lib/src/draggable.dart @@ -4,7 +4,7 @@ part of dnd; /// all [Draggable]s know when a drag operation is already handled. /// Is also used by the [Dropzone] because we can't pass an [Element] through /// the [CustomEvent]s. -_DragInfo _currentDrag; +_DragInfo? _currentDrag; /// The [Draggable] detects drag operations for touch and mouse interactions and /// optionally creates a drag avatar for visual feedback of the drag. Event @@ -27,7 +27,7 @@ class Draggable { // -------------- /// [avatarHandler] is a function to create a [DragAvatar] for this [Draggable]. /// See [Draggable] constructor. - AvatarHandler avatarHandler; + AvatarHandler? avatarHandler; /// When set to true, only horizontal dragging is tracked. This enables /// vertical touch dragging to be used for scrolling. @@ -39,7 +39,7 @@ class Draggable { /// Restricts dragging from starting to the [handle]. /// See [Draggable] constructor. - String handle; + String? handle; /// Prevents dragging from starting on specified elements. /// See [Draggable] constructor. @@ -60,7 +60,7 @@ class Draggable { /// The minimum distance for a drag to prevent click events. /// DEPRECATED: Please use [minDragStartDistance] instead. @deprecated - get clickSuppression => minDragStartDistance; + int get clickSuppression => minDragStartDistance; /// DEPRECATED: Please use [minDragStartDistance] instead. @deprecated @@ -69,9 +69,9 @@ class Draggable { // ------------------- // Events // ------------------- - StreamController _onDragStart; - StreamController _onDrag; - StreamController _onDragEnd; + StreamController? _onDragStart; + StreamController? _onDrag; + StreamController? _onDragEnd; /// Fired when the user starts dragging. /// @@ -83,7 +83,7 @@ class Draggable { _onDragStart = StreamController.broadcast( sync: true, onCancel: () => _onDragStart = null); } - return _onDragStart.stream; + return _onDragStart!.stream; } /// Fired periodically throughout the drag operation. @@ -92,7 +92,7 @@ class Draggable { _onDrag = StreamController.broadcast( sync: true, onCancel: () => _onDrag = null); } - return _onDrag.stream; + return _onDrag!.stream; } /// Fired when the user ends the dragging. @@ -102,12 +102,12 @@ class Draggable { _onDragEnd = StreamController.broadcast( sync: true, onCancel: () => _onDragEnd = null); } - return _onDragEnd.stream; + return _onDragEnd!.stream; } /// Abort the current drag void abort() { - if (_currentDrag != null && _currentDrag.draggableId == this.id) { + if (_currentDrag != null && _currentDrag!.draggableId == this.id) { _handleDragEnd(null, null, cancelled: true); } } @@ -116,7 +116,7 @@ class Draggable { // Private Properties // ------------------- /// The list of [Element]s on which a drag is detected. - List _elements; + late List _elements; /// Managers for browser events. final List<_EventManager> _eventManagers = []; @@ -172,7 +172,7 @@ class Draggable { this.draggingClassBody = 'dnd-drag-occurring', this.minDragStartDistance = 4}) { // Wrap in a List if it is not a list but a single Element. - _elements = elementOrElementList is List + _elements = elementOrElementList is List ? elementOrElementList : [elementOrElementList]; @@ -194,29 +194,25 @@ class Draggable { /// Handles the drag start. The [moveEvent] might either be a /// [TouchEvent] or a [MouseEvent]. - void _handleDragStart(UIEvent moveEvent) { + void _handleDragStart(Event moveEvent) { // Set the drag started flag. - _currentDrag.started = true; + _currentDrag!.started = true; // Pass event to AvatarHandler. if (avatarHandler != null) { - avatarHandler._handleDragStart( - _currentDrag.element, _currentDrag.position); + avatarHandler! + ._handleDragStart(_currentDrag!.element, _currentDrag!.position); } // Fire the drag start event with start position. if (_onDragStart != null) { // The dragStart has the same for startPosition and current position. - _onDragStart.add(DraggableEvent._(moveEvent, _currentDrag)); + _onDragStart!.add(DraggableEvent._(moveEvent, _currentDrag!)); } // Add the css classes during the drag operation. - if (draggingClass != null) { - _currentDrag.element.classes.add(draggingClass); - } - if (draggingClassBody != null) { - document.body.classes.add(draggingClassBody); - } + _currentDrag!.element.classes.add(draggingClass); + document.body!.classes.add(draggingClassBody); // Text selections should not be a problem, but it seems better usability // to remove text selection when dragging something. @@ -227,11 +223,11 @@ class Draggable { /// [MouseEvent]. /// /// The [target] is the actual target receiving the event. - void _handleDrag(UIEvent moveEvent, EventTarget target) { + void _handleDrag(Event moveEvent, EventTarget? target) { // Pass event to AvatarHandler. if (avatarHandler != null) { - avatarHandler._handleDrag( - _currentDrag.startPosition, _currentDrag.position); + avatarHandler! + ._handleDrag(_currentDrag!.startPosition, _currentDrag!.position); } // Dispatch internal drag enter, over, or leave event. @@ -239,7 +235,7 @@ class Draggable { // Fire the drag event. if (_onDrag != null) { - _onDrag.add(DraggableEvent._(moveEvent, _currentDrag)); + _onDrag!.add(DraggableEvent._(moveEvent, _currentDrag!)); } } @@ -252,13 +248,13 @@ class Draggable { /// /// Set [cancelled] to true to indicate that this drag ended through a /// cancel oparation like hitting the `esc` key. - void _handleDragEnd(Event event, EventTarget target, {cancelled = false}) { + void _handleDragEnd(Event? event, EventTarget? target, {cancelled = false}) { // Only handle drag end if the user actually did drag and not just clicked. - if (_currentDrag.started) { + if (_currentDrag != null && _currentDrag!.started) { // Pass event to AvatarHandler. if (avatarHandler != null) { - avatarHandler._handleDragEnd( - _currentDrag.startPosition, _currentDrag.position); + avatarHandler!._handleDragEnd( + _currentDrag!.startPosition, _currentDrag!.position); } // Dispatch internal drop event if drag was not cancelled. @@ -268,8 +264,8 @@ class Draggable { // Fire dragEnd event. if (_onDragEnd != null) { - _onDragEnd - .add(DraggableEvent._(event, _currentDrag, cancelled: cancelled)); + _onDragEnd! + .add(DraggableEvent._(event, _currentDrag!, cancelled: cancelled)); } // Prevent TouchEvent from emulating a click after touchEnd event. @@ -279,16 +275,12 @@ class Draggable { // Prevent MouseEvent from firing a click after mouseUp event. if (event is MouseEvent) { - _suppressClickEvent(_currentDrag.element); + _suppressClickEvent(_currentDrag!.element); } // Remove the css classes. - if (draggingClass != null) { - _currentDrag.element.classes.remove(draggingClass); - } - if (draggingClassBody != null) { - document.body.classes.remove(draggingClassBody); - } + _currentDrag!.element.classes.remove(draggingClass); + document.body!.classes.remove(draggingClassBody); } // Reset. @@ -320,8 +312,8 @@ class Draggable { // Destroy all managers with their listeners. _eventManagers.forEach((m) => m.destroy()); _eventManagers.clear(); - if (avatarHandler != null && avatarHandler.avatar != null) { - avatarHandler.avatar.remove(); + if (avatarHandler != null && avatarHandler!.avatar != null) { + avatarHandler!.avatar!.remove(); } } @@ -341,7 +333,7 @@ class Draggable { /// in active textarea or active input element. void _clearTextSelections() { // Remove selection. - window.getSelection().removeAllRanges(); + window.getSelection()?.removeAllRanges(); // Try to remove selection from textarea or input. try { @@ -364,7 +356,7 @@ class DraggableEvent { final Element draggableElement; /// The [AvatarHandler] or null if there is none. - final AvatarHandler avatarHandler; + final AvatarHandler? avatarHandler; /// The original event which is either ... /// * a [MouseEvent], @@ -372,7 +364,7 @@ class DraggableEvent { /// * a [KeyboardEvent] when the user clicks the esc-key, /// * a normal [Event] when the window loses focus (blur event), or /// * null if the drag was programmatically aborted. - final Event originalEvent; + final Event? originalEvent; /// Position where the drag started, relative to the whole document (page /// position). @@ -408,11 +400,11 @@ class _DragInfo { final Point startPosition; /// The [AvatarHandler] or null if there is none. - final AvatarHandler avatarHandler; + final AvatarHandler? avatarHandler; /// The current position of the mouse or touch. This position is constrained /// by the horizontal/vertical axis. - Point _position; + late Point _position; /// Flag indicating if the drag started. bool started = false; diff --git a/lib/src/draggable_avatar.dart b/lib/src/draggable_avatar.dart index a5621a9..7f392a4 100644 --- a/lib/src/draggable_avatar.dart +++ b/lib/src/draggable_avatar.dart @@ -7,13 +7,13 @@ abstract class AvatarHandler { /// Returns the [avatar] element during a drag operation. /// /// If there is no drag operation going on, [avatar] will be null. - Element avatar; + Element? avatar; /// The cached top margin of [avatar]. - num _marginTop; + num? _marginTop; /// Returns the (cached) top margin of [avatar]. - num get marginTop { + num? get marginTop { if (_marginTop == null) { cacheMargins(); } @@ -21,10 +21,10 @@ abstract class AvatarHandler { } /// The cached left margin of [avatar]. - num _marginLeft; + num? _marginLeft; /// Returns the (cached) left margin of [avatar]. - num get marginLeft { + num? get marginLeft { if (_marginLeft == null) { cacheMargins(); } @@ -32,7 +32,7 @@ abstract class AvatarHandler { } /// Saved pointer events style before the drag operation. - String _pointerEventsBeforeDrag; + String? _pointerEventsBeforeDrag; /// Default constructor. AvatarHandler(); @@ -59,8 +59,10 @@ abstract class AvatarHandler { // Sets the pointer-events CSS property of avatar to 'none' which enables // mouse and touch events to go trough to the element under the avatar. - _pointerEventsBeforeDrag = avatar.style.pointerEvents; - avatar.style.pointerEvents = 'none'; + if (avatar != null) { + _pointerEventsBeforeDrag = avatar!.style.pointerEvents; + avatar!.style.pointerEvents = 'none'; + } } /// Handles the drag. @@ -73,7 +75,9 @@ abstract class AvatarHandler { dragEnd(startPosition, position); // Reset the pointer-events CSS property to its original value. - avatar.style.pointerEvents = _pointerEventsBeforeDrag; + if (avatar != null) { + avatar!.style.pointerEvents = _pointerEventsBeforeDrag ?? ''; + } _pointerEventsBeforeDrag = null; // Reset avatar. @@ -117,7 +121,7 @@ abstract class AvatarHandler { AnimationHelper.requestUpdate(() { // Unsing `translate3d` to activate GPU hardware-acceleration (a bit of a hack). if (avatar != null) { - avatar.style.transform = + avatar!.style.transform = 'translate3d(${position.x}px, ${position.y}px, 0)'; } }); @@ -127,7 +131,9 @@ abstract class AvatarHandler { /// from [setTranslate]. void removeTranslate() { AnimationHelper.stop(); - avatar.style.transform = null; + if (avatar != null) { + avatar!.style.transform = ''; + } } /// Sets the CSS left/top values of [avatar]. Takes care of any left/top @@ -136,8 +142,10 @@ abstract class AvatarHandler { /// Note: The [avatar] must already be in the DOM for the margins to be /// calculated correctly. void setLeftTop(Point position) { - avatar.style.left = '${position.x - marginLeft}px'; - avatar.style.top = '${position.y - marginTop}px'; + if (avatar != null) { + avatar!.style.left = '${position.x - (marginLeft ?? 0)}px'; + avatar!.style.top = '${position.y - (marginTop ?? 0)}px'; + } } /// Caches the [marginLeft] and [marginTop] of [avatar]. @@ -146,33 +154,37 @@ abstract class AvatarHandler { /// operation. void cacheMargins() { // Calculate margins. - var computedStyles = avatar.getComputedStyle(); - _marginLeft = - num.tryParse(computedStyles.marginLeft.replaceFirst('px', '')) ?? 0; - _marginTop = - num.tryParse(computedStyles.marginTop.replaceFirst('px', '')) ?? 0; + if (avatar != null) { + var computedStyles = avatar!.getComputedStyle(); + _marginLeft = + num.tryParse(computedStyles.marginLeft.replaceFirst('px', '')) ?? 0; + _marginTop = + num.tryParse(computedStyles.marginTop.replaceFirst('px', '')) ?? 0; + } } } /// The [OriginalAvatarHandler] uses the draggable element itself as drag /// avatar. It uses absolute positioning of the avatar. class OriginalAvatarHandler extends AvatarHandler { - Point _draggableStartOffset; + Point? _draggableStartOffset; @override void dragStart(Element draggable, Point startPosition) { // Use the draggable itself as avatar. avatar = draggable; + // Ensure avatar has an absolute position. + if (avatar != null) { + avatar!.style.position = 'absolute'; + } + // Get the start offset of the draggable (relative to the closest positioned // ancestor). _draggableStartOffset = draggable.offset.topLeft; - // Ensure avatar has an absolute position. - avatar.style.position = 'absolute'; - // Set the initial position of the original. - setLeftTop(_draggableStartOffset); + setLeftTop(_draggableStartOffset!); } @override @@ -190,7 +202,7 @@ class OriginalAvatarHandler extends AvatarHandler { Point constrainedPosition = Point(math.max(1, position.x), math.max(1, position.y)); - setLeftTop(constrainedPosition - startPosition + _draggableStartOffset); + setLeftTop(constrainedPosition - startPosition + _draggableStartOffset!); } } @@ -205,11 +217,11 @@ class CloneAvatarHandler extends AvatarHandler { ..style.cursor = 'inherit'; // Ensure avatar has an absolute position. - avatar.style.position = 'absolute'; - avatar.style.zIndex = '100'; + avatar!.style.position = 'absolute'; + avatar!.style.zIndex = '100'; // Add the drag avatar to the parent element. - draggable.parentNode.append(avatar); + draggable.parentNode!.append(avatar!); // Set the initial position of avatar (relative to the closest positioned // ancestor). @@ -223,13 +235,15 @@ class CloneAvatarHandler extends AvatarHandler { @override void dragEnd(Point startPosition, Point position) { - avatar.remove(); + if (avatar != null) { + avatar!.remove(); + } } } /// Simple helper class to speed up animation with requestAnimationFrame. class AnimationHelper { - static Function _lastUpdateFunction; + static Function? _lastUpdateFunction; static bool _updating = false; /// Requests that the [updateFunction] be called. When the animation frame is @@ -252,7 +266,9 @@ class AnimationHelper { static void _update() { // Test if it wasn't stopped. if (_updating) { - _lastUpdateFunction(); + if (_lastUpdateFunction != null) { + _lastUpdateFunction!(); + } _updating = false; } } diff --git a/lib/src/draggable_dispatch.dart b/lib/src/draggable_dispatch.dart index d36b48a..628e8b9 100644 --- a/lib/src/draggable_dispatch.dart +++ b/lib/src/draggable_dispatch.dart @@ -36,13 +36,13 @@ class _DragEventDispatcher { EventStreamProvider(CUSTOM_DROP); /// Keeps track of the previous target to be able to fire dragLeave events on it. - static EventTarget previousTarget; + static EventTarget? previousTarget; /// Dispatches dragEnter, dragOver, and dragLeave events. /// /// The [draggable] is the [Draggable] that is dispatching the event. /// The [target] is the element that the event will be dispatched on. - static void dispatchEnterOverLeave(Draggable draggable, EventTarget target) { + static void dispatchEnterOverLeave(Draggable draggable, EventTarget? target) { // Sometimes the target is null (e.g. when user drags over buttons on // android). Ignore it. if (target == null) { @@ -63,7 +63,7 @@ class _DragEventDispatcher { if (previousTarget != null) { MouseEvent dragLeaveEvent = MouseEvent(CUSTOM_DRAG_LEAVE, relatedTarget: target); - previousTarget.dispatchEvent(dragLeaveEvent); + previousTarget!.dispatchEvent(dragLeaveEvent); } // Also fire the first dragOver event for the new element. @@ -78,7 +78,7 @@ class _DragEventDispatcher { /// /// The [draggable] is the [Draggable] that is dispatching the event. /// The [target] is the element that the event will be dispatched on. - static void dispatchDrop(Draggable draggable, EventTarget target) { + static void dispatchDrop(Draggable draggable, EventTarget? target) { // Sometimes the target is null (e.g. when user drags over buttons on // android). Ignore it. if (target == null) { @@ -96,7 +96,7 @@ class _DragEventDispatcher { // Fire a last dragLeave. if (previousTarget != null) { MouseEvent dragLeaveEvent = MouseEvent(CUSTOM_DRAG_LEAVE); - previousTarget.dispatchEvent(dragLeaveEvent); + previousTarget!.dispatchEvent(dragLeaveEvent); previousTarget = null; } } diff --git a/lib/src/draggable_manager.dart b/lib/src/draggable_manager.dart index 261a305..f353022 100644 --- a/lib/src/draggable_manager.dart +++ b/lib/src/draggable_manager.dart @@ -69,7 +69,7 @@ abstract class _EventManager { void handleStart(Event event, Point position) { // Initialize the drag info. // Note: the drag is not started on touchStart but after a first valid move. - _currentDrag = _DragInfo(drg.id, event.currentTarget, position, + _currentDrag = _DragInfo(drg.id, event.currentTarget as Element, position, avatarHandler: drg.avatarHandler, horizontalOnly: drg.horizontalOnly, verticalOnly: drg.verticalOnly); @@ -84,11 +84,11 @@ abstract class _EventManager { /// Handles a move event (touchMove, mouseMove, etc.). void handleMove(Event event, Point position, Point clientPosition) { // Set the current position. - _currentDrag.position = position; + _currentDrag!.position = position; - if (!_currentDrag.started) { + if (!_currentDrag!.started) { // Test if drag has moved far enough to start drag. - if (_currentDrag.startPosition.distanceTo(_currentDrag.position) >= + if (_currentDrag!.startPosition.distanceTo(_currentDrag!.position) >= drg.minDragStartDistance) { // Drag starts now. drg._handleDragStart(event); @@ -101,13 +101,17 @@ abstract class _EventManager { } /// Handles all end events (touchEnd, mouseUp, and pointerUp). - void handleEnd( - Event event, EventTarget target, Point position, Point clientPosition) { + void handleEnd(Event event, EventTarget? target, Point? position, + Point? clientPosition) { // Set the current position. - _currentDrag.position = position; + if (position != null) { + _currentDrag!.position = position; + } - EventTarget realTarget = _getRealTarget(clientPosition, target: target); - drg._handleDragEnd(event, realTarget); + if (clientPosition != null) { + EventTarget realTarget = _getRealTarget(clientPosition, target: target); + drg._handleDragEnd(event, realTarget); + } } /// Handles all cancel events (touchCancel and pointerCancel). @@ -133,16 +137,16 @@ abstract class _EventManager { startSubs.clear(); // Reset the touch action property. - drg._elements.forEach((el) => el.style.touchAction = null); + drg._elements.forEach((el) => el.style.touchAction = ''); } /// Determine a target using `document.elementFromPoint` via the provided [clientPosition]. /// /// Falls back to `document.body` if no element is found at the provided [clientPosition]. - EventTarget _getRealTargetFromPoint(Point clientPosition) { + Element _getRealTargetFromPoint(Point clientPosition) { return document.elementFromPoint( clientPosition.x.round(), clientPosition.y.round()) ?? - document.body; + document.body!; } /// Determine the actual target that should receive the event because @@ -151,20 +155,20 @@ abstract class _EventManager { /// If a [target] is provided it is tested to see if is already the correct /// target or if it is the drag avatar and thus must be replaced by the /// element underneath. - EventTarget _getRealTarget(Point clientPosition, {EventTarget target}) { + Element _getRealTarget(Point clientPosition, {EventTarget? target}) { // If no target was provided get it. - if (target == null) { + if (target == null || target is! Element) { target = _getRealTargetFromPoint(clientPosition); } // Test if target is the drag avatar. if (drg.avatarHandler != null && - drg.avatarHandler.avatar != null && - drg.avatarHandler.avatar.contains(target)) { + drg.avatarHandler!.avatar != null && + drg.avatarHandler!.avatar!.contains(target)) { // Target is the drag avatar, get element underneath. - drg.avatarHandler.avatar.style.visibility = 'hidden'; + drg.avatarHandler!.avatar!.style.visibility = 'hidden'; target = _getRealTargetFromPoint(clientPosition); - drg.avatarHandler.avatar.style.visibility = 'visible'; + drg.avatarHandler!.avatar!.style.visibility = 'visible'; } target = _recursiveShadowDomTarget(clientPosition, target); @@ -174,18 +178,18 @@ abstract class _EventManager { /// Recursively searches for the real target inside the Shadow DOM for all /// Shadow hosts with the attribute [SHADOW_DOM_RETARGET_ATTRIBUTE]. - EventTarget _recursiveShadowDomTarget( - Point clientPosition, EventTarget target) { + Element _recursiveShadowDomTarget(Point clientPosition, Element target) { // Retarget if target is a shadow host and has the specific attribute. if (target is Element && target.shadowRoot != null && target.attributes.containsKey(SHADOW_DOM_RETARGET_ATTRIBUTE)) { - Element newTarget = (target as Element) - .shadowRoot + Element? newTarget = target.shadowRoot! .elementFromPoint(clientPosition.x.round(), clientPosition.y.round()); // Recursive call for nested shadow DOM trees. - target = _recursiveShadowDomTarget(clientPosition, newTarget); + if (newTarget != null) { + target = _recursiveShadowDomTarget(clientPosition, newTarget); + } } return target; @@ -196,9 +200,7 @@ abstract class _EventManager { /// provided, drag cannot be started on those elements. bool _isValidDragStartTarget(EventTarget target) { // Test if a drag was started on a cancel element. - if (drg.cancel != null && - target is Element && - target.matchesWithAncestors(drg.cancel)) { + if (target is Element && target.matchesWithAncestors(drg.cancel)) { return false; } @@ -206,12 +208,12 @@ abstract class _EventManager { if (drg.handle != null) { if (target is Element) { // 1. The target must match the handle query String. - if (!target.matchesWithAncestors(drg.handle)) { + if (!target.matchesWithAncestors(drg.handle!)) { return false; } // 2. The target must be a child of the drag element(s). - if (drg._elements.firstWhere((el) => el.contains(target)) != null) { + if (drg._elements.any((el) => el.contains(target))) { return true; } } @@ -238,16 +240,19 @@ class _TouchManager extends _EventManager { } // Ignore multi-touch events. - if (event.touches.length > 1) { + if (event.touches != null && event.touches!.length > 1) { return; } // Ensure the drag started on a valid target. - if (!_isValidDragStartTarget(event.touches[0].target)) { + if (event.touches != null && + !_isValidDragStartTarget(event.touches![0].target!)) { return; } - handleStart(event, event.touches[0].page); + if (event.touches != null) { + handleStart(event, event.touches![0].page); + } })); }); } @@ -256,20 +261,23 @@ class _TouchManager extends _EventManager { void installMove() { dragSubs.add(document.onTouchMove.listen((TouchEvent event) { // Stop and cancel subscriptions on multi-touch. - if (event.touches.length > 1) { + if (event.touches != null && event.touches!.length > 1) { handleCancel(event); return; } // Do a scrolling test if this is the first drag. - if (!_currentDrag.started && isScrolling(event.changedTouches[0].page)) { - // The user is scrolling --> Stop tracking current drag. - handleCancel(event); - return; - } + if (event.changedTouches != null) { + if (!_currentDrag!.started && + isScrolling(event.changedTouches![0].page)) { + // The user is scrolling --> Stop tracking current drag. + handleCancel(event); + return; + } - handleMove( - event, event.changedTouches[0].page, event.changedTouches[0].client); + handleMove(event, event.changedTouches![0].page, + event.changedTouches![0].client); + } // Prevent touch scrolling. event.preventDefault(); @@ -279,8 +287,8 @@ class _TouchManager extends _EventManager { @override void installEnd() { dragSubs.add(document.onTouchEnd.listen((TouchEvent event) { - handleEnd(event, null, event.changedTouches[0].page, - event.changedTouches[0].client); + handleEnd(event, null, event.changedTouches?[0].page, + event.changedTouches?[0].client); })); } @@ -293,7 +301,7 @@ class _TouchManager extends _EventManager { /// Returns true if there was scrolling activity instead of dragging. bool isScrolling(Point currentPosition) { - Point delta = currentPosition - _currentDrag.startPosition; + Point delta = currentPosition - _currentDrag!.startPosition; // If horizontalOnly test for vertical movement. if (drg.horizontalOnly && delta.y.abs() > delta.x.abs()) { @@ -331,7 +339,7 @@ class _MouseManager extends _EventManager { } // Ensure the drag started on a valid target. - if (!_isValidDragStartTarget(event.target)) { + if (event.target != null && !_isValidDragStartTarget(event.target!)) { return; } @@ -343,7 +351,7 @@ class _MouseManager extends _EventManager { // * SelectElement would not show a dropdown. // * InputElement and TextAreaElement would not get focus. // * ButtonElement and OptionElement - don't know if this is needed?? - Element target = event.target; + EventTarget? target = event.target; if (!(target is SelectElement || target is InputElement || target is TextAreaElement || @@ -398,7 +406,7 @@ class _PointerManager extends _EventManager { } // Ensure the drag started on a valid target. - if (!_isValidDragStartTarget(event.target)) { + if (event.target != null && !_isValidDragStartTarget(event.target!)) { return; } @@ -410,7 +418,7 @@ class _PointerManager extends _EventManager { // * SelectElement would not show a dropdown. // * InputElement and TextAreaElement would not get focus. // * ButtonElement and OptionElement - don't know if this is needed?? - Element target = event.target; + EventTarget? target = event.target; if (!(target is SelectElement || target is InputElement || target is TextAreaElement || diff --git a/lib/src/dropzone.dart b/lib/src/dropzone.dart index f18df08..031ed7e 100644 --- a/lib/src/dropzone.dart +++ b/lib/src/dropzone.dart @@ -18,7 +18,7 @@ class Dropzone { // -------------- /// The [Acceptor] used to determine which [Draggable]s will be accepted by /// this [Dropzone]. If none is specified, all [Draggable]s will be accepted. - Acceptor acceptor; + Acceptor? acceptor; /// CSS class set to the [Dropzone] element when an accepted [Draggable] is /// dragged over it. See [Dropzone] constructor. @@ -31,10 +31,10 @@ class Dropzone { // ------------------- // Events // ------------------- - StreamController _onDragEnter; - StreamController _onDragOver; - StreamController _onDragLeave; - StreamController _onDrop; + StreamController? _onDragEnter; + StreamController? _onDragOver; + StreamController? _onDragLeave; + StreamController? _onDrop; /// Fired when a [Draggable] enters this [Dropzone]. Stream get onDragEnter { @@ -42,7 +42,7 @@ class Dropzone { _onDragEnter = StreamController.broadcast( sync: true, onCancel: () => _onDragEnter = null); } - return _onDragEnter.stream; + return _onDragEnter!.stream; } /// Fired periodically while a [Draggable] is moved over a [Dropzone]. @@ -51,7 +51,7 @@ class Dropzone { _onDragOver = StreamController.broadcast( sync: true, onCancel: () => _onDragOver = null); } - return _onDragOver.stream; + return _onDragOver!.stream; } /// Fired when a [Draggable] leaves this [Dropzone]. @@ -60,7 +60,7 @@ class Dropzone { _onDragLeave = StreamController.broadcast( sync: true, onCancel: () => _onDragLeave = null); } - return _onDragLeave.stream; + return _onDragLeave!.stream; } /// Fired at the end of the drag operation when the [Draggable] is dropped @@ -70,14 +70,14 @@ class Dropzone { _onDrop = StreamController.broadcast( sync: true, onCancel: () => _onDrop = null); } - return _onDrop.stream; + return _onDrop!.stream; } // ------------------- // Private Properties // ------------------- /// The list of [Element]s for the dropzone. - List _elements; + late List _elements; /// Tracks subscriptions. List _subs = []; @@ -102,7 +102,7 @@ class Dropzone { this.overClass = 'dnd-over', this.invalidClass = 'dnd-invalid'}) { // Wrap in a List if it is not a list but a single Element. - _elements = elementOrElementList is List + _elements = elementOrElementList is List ? elementOrElementList : [elementOrElementList]; @@ -130,30 +130,28 @@ class Dropzone { void _handleDragEnter(MouseEvent event) { // Only handle dragEnter if user moved from outside of element into the // element. That means we ignore it if user is coming from a child element. - if (event.relatedTarget != null && - (event.currentTarget as Element).contains(event.relatedTarget)) { + if (event.relatedTarget is Element && + (event.currentTarget as Element) + .contains(event.relatedTarget as Element)) { return; } // Test if the current draggable is accepted by this dropzone. If there is // no accepter all are accepted. if (acceptor == null || - acceptor.accepts(_currentDrag.element, _currentDrag.draggableId, - event.currentTarget)) { + acceptor!.accepts(_currentDrag!.element, _currentDrag!.draggableId, + event.currentTarget as Element)) { // Fire dragEnter event. if (_onDragEnter != null) { - _onDragEnter.add(DropzoneEvent._(event.currentTarget, _currentDrag)); + _onDragEnter!.add( + DropzoneEvent._(event.currentTarget as Element, _currentDrag!)); } // Add the css class to indicate drag over. - if (overClass != null) { - (event.currentTarget as Element).classes.add(overClass); - } + (event.currentTarget as Element).classes.add(overClass); } else { // Add the css class to indicate invalid drag over. - if (invalidClass != null) { - (event.currentTarget as Element).classes.add(invalidClass); - } + (event.currentTarget as Element).classes.add(invalidClass); } } @@ -162,11 +160,12 @@ class Dropzone { // Test if the current draggable is accepted by this dropzone. If there is // no accepter all are accepted. if (acceptor == null || - acceptor.accepts(_currentDrag.element, _currentDrag.draggableId, - event.currentTarget)) { + acceptor!.accepts(_currentDrag!.element, _currentDrag!.draggableId, + event.currentTarget as Element)) { // Fire dragOver event. if (_onDragOver != null) { - _onDragOver.add(DropzoneEvent._(event.currentTarget, _currentDrag)); + _onDragOver!.add( + DropzoneEvent._(event.currentTarget as Element, _currentDrag!)); } } } @@ -175,30 +174,28 @@ class Dropzone { void _handleDragLeave(MouseEvent event) { // Only handle dragLeave if user moved from inside of element to the // outside. That means we ignore it if user is moving to a child element. - if (event.relatedTarget != null && - (event.currentTarget as Element).contains(event.relatedTarget)) { + if (event.relatedTarget is Element && + (event.currentTarget as Element) + .contains(event.relatedTarget as Element)) { return; } // Test if the current draggable is accepted by this dropzone. If there is // no accepter all are accepted. if (acceptor == null || - acceptor.accepts(_currentDrag.element, _currentDrag.draggableId, - event.currentTarget)) { + acceptor!.accepts(_currentDrag!.element, _currentDrag!.draggableId, + event.currentTarget as Element)) { // Fire dragLeave event. if (_onDragLeave != null) { - _onDragLeave.add(DropzoneEvent._(event.currentTarget, _currentDrag)); + _onDragLeave!.add( + DropzoneEvent._(event.currentTarget as Element, _currentDrag!)); } // Remove the css class. - if (overClass != null) { - (event.currentTarget as Element).classes.remove(overClass); - } + (event.currentTarget as Element).classes.remove(overClass); } else { // Remove the invalid drag css class. - if (invalidClass != null) { - (event.currentTarget as Element).classes.remove(invalidClass); - } + (event.currentTarget as Element).classes.remove(invalidClass); } } @@ -207,11 +204,12 @@ class Dropzone { // Test if the current draggable is accepted by this dropzone. If there is // no accepter all are accepted. if (acceptor == null || - acceptor.accepts(_currentDrag.element, _currentDrag.draggableId, - event.currentTarget)) { + acceptor!.accepts(_currentDrag!.element, _currentDrag!.draggableId, + event.currentTarget as Element)) { // Fire drop event. if (_onDrop != null) { - _onDrop.add(DropzoneEvent._(event.currentTarget, _currentDrag)); + _onDrop!.add( + DropzoneEvent._(event.currentTarget as Element, _currentDrag!)); } } } @@ -232,7 +230,7 @@ class DropzoneEvent { final Element draggableElement; /// The [AvatarHandler] or null if there is none. - final AvatarHandler avatarHandler; + final AvatarHandler? avatarHandler; /// The current mouse/touch position, relative to the whole document (page /// position). diff --git a/pubspec.yaml b/pubspec.yaml index 978d59a..38b94d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: dnd -version: 1.4.3 +version: 2.0.0 author: Marco Jakob description: Drag and Drop for Dart web apps with mouse and touch support. repository: https://github.com/marcojakob/dart-dnd homepage: https://code.makery.ch/library/dart-drag-and-drop/ environment: - sdk: '>=2.0.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dev_dependencies: build_runner: ^1.2.5 - build_web_compilers: ^1.1.0 + build_web_compilers: ^2.16.4