@planningcenter/react-beautiful-dnd 13.2.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.
- package/CHANGELOG.md +35 -0
- package/LICENSE +13 -0
- package/README.md +178 -0
- package/dist/react-beautiful-dnd.cjs.js +8728 -0
- package/dist/react-beautiful-dnd.cjs.js.flow +3 -0
- package/dist/react-beautiful-dnd.esm.js +8715 -0
- package/dist/react-beautiful-dnd.js +11726 -0
- package/dist/react-beautiful-dnd.min.js +1 -0
- package/package.json +155 -0
- package/src/animation.js +75 -0
- package/src/debug/middleware/action-timing-average.js +52 -0
- package/src/debug/middleware/action-timing.js +16 -0
- package/src/debug/middleware/log.js +26 -0
- package/src/debug/middleware/user-timing.js +16 -0
- package/src/debug/timings.js +86 -0
- package/src/dev-warning.js +50 -0
- package/src/empty.js +6 -0
- package/src/index.js +67 -0
- package/src/invariant.js +33 -0
- package/src/native-with-fallback.js +69 -0
- package/src/screen-reader-message-preset.js +134 -0
- package/src/state/action-creators.js +328 -0
- package/src/state/auto-scroller/auto-scroller-types.js +8 -0
- package/src/state/auto-scroller/can-scroll.js +160 -0
- package/src/state/auto-scroller/fluid-scroller/config.js +25 -0
- package/src/state/auto-scroller/fluid-scroller/did-start-in-scrollable-area.js +1 -0
- package/src/state/auto-scroller/fluid-scroller/get-best-scrollable-droppable.js +77 -0
- package/src/state/auto-scroller/fluid-scroller/get-droppable-scroll-change.js +50 -0
- package/src/state/auto-scroller/fluid-scroller/get-percentage.js +25 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/adjust-for-size-limits.js +30 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/buffer-thresholds/calc-axis-scroll-conditions.js +68 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/buffer-thresholds/get-scroll-conditions.js +56 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/buffer-thresholds/index.js +66 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/dampen-value-by-time.js +48 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/get-distance-thresholds.js +31 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/get-value-from-distance.js +67 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/get-value.js +51 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/index.js +50 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/min-scroll.js +4 -0
- package/src/state/auto-scroller/fluid-scroller/get-scroll/index.js +139 -0
- package/src/state/auto-scroller/fluid-scroller/get-window-scroll-change.js +43 -0
- package/src/state/auto-scroller/fluid-scroller/index.js +99 -0
- package/src/state/auto-scroller/fluid-scroller/scroll.js +80 -0
- package/src/state/auto-scroller/index.js +59 -0
- package/src/state/auto-scroller/jump-scroller.js +139 -0
- package/src/state/axis.js +26 -0
- package/src/state/calculate-drag-impact/calculate-reorder-impact.js +136 -0
- package/src/state/can-start-drag.js +29 -0
- package/src/state/create-store.js +97 -0
- package/src/state/did-start-after-critical.js +9 -0
- package/src/state/dimension-marshal/dimension-marshal-types.js +46 -0
- package/src/state/dimension-marshal/dimension-marshal.js +218 -0
- package/src/state/dimension-marshal/get-initial-publish.js +66 -0
- package/src/state/dimension-marshal/while-dragging-publisher.js +146 -0
- package/src/state/dimension-structures.js +35 -0
- package/src/state/droppable/get-droppable.js +101 -0
- package/src/state/droppable/is-home-of.js +7 -0
- package/src/state/droppable/scroll-droppable.js +53 -0
- package/src/state/droppable/should-use-placeholder.js +7 -0
- package/src/state/droppable/util/clip.js +17 -0
- package/src/state/droppable/util/get-subject.js +63 -0
- package/src/state/droppable/what-is-dragged-over-from-result.js +16 -0
- package/src/state/droppable/what-is-dragged-over.js +16 -0
- package/src/state/droppable/with-placeholder.js +174 -0
- package/src/state/get-center-from-impact/get-client-border-box-center/get-client-from-page-border-box-center.js +29 -0
- package/src/state/get-center-from-impact/get-client-border-box-center/index.js +44 -0
- package/src/state/get-center-from-impact/get-page-border-box-center/index.js +70 -0
- package/src/state/get-center-from-impact/get-page-border-box-center/when-combining.js +39 -0
- package/src/state/get-center-from-impact/get-page-border-box-center/when-reordering.js +112 -0
- package/src/state/get-center-from-impact/move-relative-to.js +66 -0
- package/src/state/get-combined-item-displacement.js +34 -0
- package/src/state/get-displaced-by.js +17 -0
- package/src/state/get-displacement-groups.js +130 -0
- package/src/state/get-drag-impact/get-combine-impact.js +130 -0
- package/src/state/get-drag-impact/get-reorder-impact.js +143 -0
- package/src/state/get-drag-impact/index.js +93 -0
- package/src/state/get-draggables-inside-droppable.js +31 -0
- package/src/state/get-droppable-over.js +158 -0
- package/src/state/get-frame.js +10 -0
- package/src/state/get-home-location.js +7 -0
- package/src/state/get-impact-location.js +16 -0
- package/src/state/get-is-displaced.js +11 -0
- package/src/state/get-lift-effect.js +82 -0
- package/src/state/get-max-scroll.js +30 -0
- package/src/state/is-movement-allowed.js +6 -0
- package/src/state/is-within.js +9 -0
- package/src/state/middleware/auto-scroll.js +38 -0
- package/src/state/middleware/dimension-marshal-stopper.js +20 -0
- package/src/state/middleware/drop/drop-animation-finish-middleware.js +21 -0
- package/src/state/middleware/drop/drop-animation-flush-on-scroll-middleware.js +63 -0
- package/src/state/middleware/drop/drop-middleware.js +146 -0
- package/src/state/middleware/drop/get-drop-duration.js +47 -0
- package/src/state/middleware/drop/get-drop-impact.js +77 -0
- package/src/state/middleware/drop/get-new-home-client-offset.js +54 -0
- package/src/state/middleware/drop/index.js +2 -0
- package/src/state/middleware/focus.js +42 -0
- package/src/state/middleware/lift.js +66 -0
- package/src/state/middleware/pending-drop.js +37 -0
- package/src/state/middleware/responders/async-marshal.js +55 -0
- package/src/state/middleware/responders/expiring-announce.js +44 -0
- package/src/state/middleware/responders/index.js +2 -0
- package/src/state/middleware/responders/is-equal.js +57 -0
- package/src/state/middleware/responders/publisher.js +253 -0
- package/src/state/middleware/responders/responders-middleware.js +75 -0
- package/src/state/middleware/scroll-listener.js +31 -0
- package/src/state/middleware/style.js +22 -0
- package/src/state/middleware/util/validate-dimensions.js +71 -0
- package/src/state/move-in-direction/index.js +84 -0
- package/src/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.js +168 -0
- package/src/state/move-in-direction/move-cross-axis/get-closest-draggable.js +79 -0
- package/src/state/move-in-direction/move-cross-axis/index.js +109 -0
- package/src/state/move-in-direction/move-cross-axis/move-to-new-droppable.js +121 -0
- package/src/state/move-in-direction/move-cross-axis/without-starting-displacement.js +31 -0
- package/src/state/move-in-direction/move-in-direction-types.js +9 -0
- package/src/state/move-in-direction/move-to-next-place/index.js +132 -0
- package/src/state/move-in-direction/move-to-next-place/is-totally-visible-in-new-location.js +52 -0
- package/src/state/move-in-direction/move-to-next-place/move-to-next-combine/index.js +97 -0
- package/src/state/move-in-direction/move-to-next-place/move-to-next-index/from-combine.js +52 -0
- package/src/state/move-in-direction/move-to-next-place/move-to-next-index/from-reorder.js +43 -0
- package/src/state/move-in-direction/move-to-next-place/move-to-next-index/index.js +86 -0
- package/src/state/no-impact.js +33 -0
- package/src/state/patch-dimension-map.js +11 -0
- package/src/state/patch-droppable-map.js +10 -0
- package/src/state/position.js +58 -0
- package/src/state/post-reducer/when-moving/refresh-snap.js +67 -0
- package/src/state/post-reducer/when-moving/update.js +120 -0
- package/src/state/publish-while-dragging-in-virtual/adjust-additions-for-scroll-changes.js +58 -0
- package/src/state/publish-while-dragging-in-virtual/index.js +158 -0
- package/src/state/publish-while-dragging-in-virtual/offset-draggable.js +35 -0
- package/src/state/recompute-placeholders.js +99 -0
- package/src/state/rect.js +7 -0
- package/src/state/reducer.js +457 -0
- package/src/state/registry/create-registry.js +162 -0
- package/src/state/registry/registry-types.js +98 -0
- package/src/state/registry/use-registry.js +20 -0
- package/src/state/remove-draggable-from-list.js +13 -0
- package/src/state/scroll-viewport.js +34 -0
- package/src/state/spacing.js +45 -0
- package/src/state/store-types.js +16 -0
- package/src/state/update-displacement-visibility/recompute.js +54 -0
- package/src/state/update-displacement-visibility/speculatively-increase.js +111 -0
- package/src/state/visibility/is-partially-visible-through-frame.js +60 -0
- package/src/state/visibility/is-position-in-frame.js +12 -0
- package/src/state/visibility/is-totally-visible-through-frame-on-axis.js +19 -0
- package/src/state/visibility/is-totally-visible-through-frame.js +18 -0
- package/src/state/visibility/is-visible.js +102 -0
- package/src/state/with-scroll-change/with-all-displacement.js +15 -0
- package/src/state/with-scroll-change/with-droppable-displacement.js +13 -0
- package/src/state/with-scroll-change/with-droppable-scroll.js +13 -0
- package/src/state/with-scroll-change/with-viewport-displacement.js +7 -0
- package/src/types.js +542 -0
- package/src/view/animate-in-out/animate-in-out.jsx +95 -0
- package/src/view/animate-in-out/index.js +2 -0
- package/src/view/check-is-valid-inner-ref.js +15 -0
- package/src/view/context/app-context.js +19 -0
- package/src/view/context/droppable-context.js +11 -0
- package/src/view/context/store-context.js +5 -0
- package/src/view/data-attributes.js +37 -0
- package/src/view/drag-drop-context/app.jsx +273 -0
- package/src/view/drag-drop-context/check-doctype.js +39 -0
- package/src/view/drag-drop-context/check-react-version.js +71 -0
- package/src/view/drag-drop-context/drag-drop-context-types.js +7 -0
- package/src/view/drag-drop-context/drag-drop-context.jsx +68 -0
- package/src/view/drag-drop-context/error-boundary.jsx +88 -0
- package/src/view/drag-drop-context/index.js +2 -0
- package/src/view/drag-drop-context/use-startup-validation.js +13 -0
- package/src/view/drag-drop-context/use-unique-context-id.js +13 -0
- package/src/view/draggable/connected-draggable.js +372 -0
- package/src/view/draggable/draggable-api.jsx +48 -0
- package/src/view/draggable/draggable-types.js +191 -0
- package/src/view/draggable/draggable.jsx +171 -0
- package/src/view/draggable/get-style.js +109 -0
- package/src/view/draggable/index.js +2 -0
- package/src/view/draggable/use-validation.js +70 -0
- package/src/view/droppable/connected-droppable.js +280 -0
- package/src/view/droppable/droppable-types.js +91 -0
- package/src/view/droppable/droppable.jsx +167 -0
- package/src/view/droppable/index.js +2 -0
- package/src/view/droppable/use-validation.js +101 -0
- package/src/view/event-bindings/bind-events.js +39 -0
- package/src/view/event-bindings/event-types.js +14 -0
- package/src/view/get-body-element.js +8 -0
- package/src/view/get-border-box-center-position.js +5 -0
- package/src/view/get-document-element.js +8 -0
- package/src/view/get-elements/find-drag-handle.js +38 -0
- package/src/view/get-elements/find-draggable.js +30 -0
- package/src/view/is-strict-equal.js +2 -0
- package/src/view/is-type-of-element/is-element.js +6 -0
- package/src/view/is-type-of-element/is-html-element.js +6 -0
- package/src/view/is-type-of-element/is-svg-element.js +12 -0
- package/src/view/key-codes.js +13 -0
- package/src/view/placeholder/index.js +2 -0
- package/src/view/placeholder/placeholder-types.js +16 -0
- package/src/view/placeholder/placeholder.jsx +198 -0
- package/src/view/scroll-listener.js +72 -0
- package/src/view/throw-if-invalid-inner-ref.js +15 -0
- package/src/view/use-announcer/index.js +2 -0
- package/src/view/use-announcer/use-announcer.js +80 -0
- package/src/view/use-dev-setup-warning.js +22 -0
- package/src/view/use-dev.js +9 -0
- package/src/view/use-draggable-publisher/get-dimension.js +44 -0
- package/src/view/use-draggable-publisher/index.js +2 -0
- package/src/view/use-draggable-publisher/use-draggable-publisher.js +87 -0
- package/src/view/use-droppable-publisher/check-for-nested-scroll-container.js +27 -0
- package/src/view/use-droppable-publisher/get-closest-scrollable.js +95 -0
- package/src/view/use-droppable-publisher/get-dimension.js +139 -0
- package/src/view/use-droppable-publisher/get-env.js +31 -0
- package/src/view/use-droppable-publisher/get-listener-options.js +12 -0
- package/src/view/use-droppable-publisher/get-scroll.js +7 -0
- package/src/view/use-droppable-publisher/index.js +2 -0
- package/src/view/use-droppable-publisher/is-in-fixed-container.js +21 -0
- package/src/view/use-droppable-publisher/use-droppable-publisher.js +283 -0
- package/src/view/use-focus-marshal/focus-marshal-types.js +13 -0
- package/src/view/use-focus-marshal/index.js +2 -0
- package/src/view/use-focus-marshal/use-focus-marshal.js +129 -0
- package/src/view/use-hidden-text-element/index.js +2 -0
- package/src/view/use-hidden-text-element/use-hidden-text-element.js +60 -0
- package/src/view/use-isomorphic-layout-effect.js +18 -0
- package/src/view/use-previous-ref.js +14 -0
- package/src/view/use-required-context.js +9 -0
- package/src/view/use-sensor-marshal/closest.js +50 -0
- package/src/view/use-sensor-marshal/find-closest-draggable-id-from-event.js +50 -0
- package/src/view/use-sensor-marshal/index.js +5 -0
- package/src/view/use-sensor-marshal/is-event-in-interactive-element.js +66 -0
- package/src/view/use-sensor-marshal/lock.js +48 -0
- package/src/view/use-sensor-marshal/sensors/use-keyboard-sensor.js +243 -0
- package/src/view/use-sensor-marshal/sensors/use-mouse-sensor.js +386 -0
- package/src/view/use-sensor-marshal/sensors/use-touch-sensor.js +461 -0
- package/src/view/use-sensor-marshal/sensors/util/prevent-standard-key-events.js +19 -0
- package/src/view/use-sensor-marshal/sensors/util/supported-page-visibility-event-name.js +29 -0
- package/src/view/use-sensor-marshal/use-sensor-marshal.js +495 -0
- package/src/view/use-sensor-marshal/use-validate-sensor-hooks.js +20 -0
- package/src/view/use-style-marshal/get-styles.js +170 -0
- package/src/view/use-style-marshal/index.js +2 -0
- package/src/view/use-style-marshal/style-marshal-types.js +8 -0
- package/src/view/use-style-marshal/use-style-marshal.js +126 -0
- package/src/view/use-unique-id.js +25 -0
- package/src/view/visually-hidden-style.js +16 -0
- package/src/view/window/get-max-window-scroll.js +19 -0
- package/src/view/window/get-viewport.js +49 -0
- package/src/view/window/get-window-from-el.js +3 -0
- package/src/view/window/get-window-scroll.js +30 -0
- package/src/view/window/scroll-window.js +7 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { DragImpact, DraggableLocation, Combine } from '../types';
|
|
3
|
+
|
|
4
|
+
export function tryGetDestination(impact: DragImpact): ?DraggableLocation {
|
|
5
|
+
if (impact.at && impact.at.type === 'REORDER') {
|
|
6
|
+
return impact.at.destination;
|
|
7
|
+
}
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function tryGetCombine(impact: DragImpact): ?Combine {
|
|
12
|
+
if (impact.at && impact.at.type === 'COMBINE') {
|
|
13
|
+
return impact.at.combine;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { DisplacementGroups, DraggableId } from '../types';
|
|
3
|
+
|
|
4
|
+
type Args = {|
|
|
5
|
+
displaced: DisplacementGroups,
|
|
6
|
+
id: DraggableId,
|
|
7
|
+
|};
|
|
8
|
+
|
|
9
|
+
export default function getIsDisplaced({ displaced, id }: Args): boolean {
|
|
10
|
+
return Boolean(displaced.visible[id] || displaced.invisible[id]);
|
|
11
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import { invariant } from '../invariant';
|
|
3
|
+
import getHomeLocation from './get-home-location';
|
|
4
|
+
import type {
|
|
5
|
+
DraggableDimension,
|
|
6
|
+
DroppableDimension,
|
|
7
|
+
DraggableDimensionMap,
|
|
8
|
+
DragImpact,
|
|
9
|
+
DisplacedBy,
|
|
10
|
+
Viewport,
|
|
11
|
+
DraggableIdMap,
|
|
12
|
+
DisplacementGroups,
|
|
13
|
+
LiftEffect,
|
|
14
|
+
} from '../types';
|
|
15
|
+
import getDraggablesInsideDroppable from './get-draggables-inside-droppable';
|
|
16
|
+
import getDisplacedBy from './get-displaced-by';
|
|
17
|
+
import getDisplacementGroups from './get-displacement-groups';
|
|
18
|
+
|
|
19
|
+
type Args = {|
|
|
20
|
+
draggable: DraggableDimension,
|
|
21
|
+
home: DroppableDimension,
|
|
22
|
+
draggables: DraggableDimensionMap,
|
|
23
|
+
viewport: Viewport,
|
|
24
|
+
|};
|
|
25
|
+
|
|
26
|
+
type Result = {|
|
|
27
|
+
afterCritical: LiftEffect,
|
|
28
|
+
impact: DragImpact,
|
|
29
|
+
|};
|
|
30
|
+
|
|
31
|
+
export default ({ draggable, home, draggables, viewport }: Args): Result => {
|
|
32
|
+
const displacedBy: DisplacedBy = getDisplacedBy(
|
|
33
|
+
home.axis,
|
|
34
|
+
draggable.displaceBy,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const insideHome: DraggableDimension[] = getDraggablesInsideDroppable(
|
|
38
|
+
home.descriptor.id,
|
|
39
|
+
draggables,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// in a list that does not start at 0 the descriptor.index might be different from the index in the list
|
|
43
|
+
// eg a list could be: [2,3,4]. A descriptor.index of '2' would actually be in index '0' of the list
|
|
44
|
+
const rawIndex: number = insideHome.indexOf(draggable);
|
|
45
|
+
invariant(rawIndex !== -1, 'Expected draggable to be inside home list');
|
|
46
|
+
|
|
47
|
+
const afterDragging: DraggableDimension[] = insideHome.slice(rawIndex + 1);
|
|
48
|
+
const effected: DraggableIdMap = afterDragging.reduce(
|
|
49
|
+
(previous: DraggableIdMap, item: DraggableDimension): DraggableIdMap => {
|
|
50
|
+
previous[item.descriptor.id] = true;
|
|
51
|
+
return previous;
|
|
52
|
+
},
|
|
53
|
+
{},
|
|
54
|
+
);
|
|
55
|
+
const afterCritical: LiftEffect = {
|
|
56
|
+
inVirtualList: home.descriptor.mode === 'virtual',
|
|
57
|
+
displacedBy,
|
|
58
|
+
effected,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const displaced: DisplacementGroups = getDisplacementGroups({
|
|
62
|
+
afterDragging,
|
|
63
|
+
destination: home,
|
|
64
|
+
displacedBy,
|
|
65
|
+
last: null,
|
|
66
|
+
viewport: viewport.frame,
|
|
67
|
+
// originally we do not want any animation as we want
|
|
68
|
+
// everything to be fixed in the same position that
|
|
69
|
+
// it started in
|
|
70
|
+
forceShouldAnimate: false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const impact: DragImpact = {
|
|
74
|
+
displaced,
|
|
75
|
+
displacedBy,
|
|
76
|
+
at: {
|
|
77
|
+
type: 'REORDER',
|
|
78
|
+
destination: getHomeLocation(draggable.descriptor),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
return { impact, afterCritical };
|
|
82
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import { type Position } from 'css-box-model';
|
|
3
|
+
import { subtract } from './position';
|
|
4
|
+
|
|
5
|
+
type Args = {|
|
|
6
|
+
scrollHeight: number,
|
|
7
|
+
scrollWidth: number,
|
|
8
|
+
height: number,
|
|
9
|
+
width: number,
|
|
10
|
+
|};
|
|
11
|
+
export default ({
|
|
12
|
+
scrollHeight,
|
|
13
|
+
scrollWidth,
|
|
14
|
+
height,
|
|
15
|
+
width,
|
|
16
|
+
}: Args): Position => {
|
|
17
|
+
const maxScroll: Position = subtract(
|
|
18
|
+
// full size
|
|
19
|
+
{ x: scrollWidth, y: scrollHeight },
|
|
20
|
+
// viewport size
|
|
21
|
+
{ x: width, y: height },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const adjustedMaxScroll: Position = {
|
|
25
|
+
x: Math.max(0, maxScroll.x),
|
|
26
|
+
y: Math.max(0, maxScroll.y),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return adjustedMaxScroll;
|
|
30
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { State } from '../types';
|
|
3
|
+
// Using function declaration as arrow function does not play well with the %checks syntax
|
|
4
|
+
export default function isMovementAllowed(state: State): boolean %checks {
|
|
5
|
+
return state.phase === 'DRAGGING' || state.phase === 'COLLECTING';
|
|
6
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import { invariant } from '../../invariant';
|
|
3
|
+
import type { AutoScroller } from '../auto-scroller/auto-scroller-types';
|
|
4
|
+
import type { Action, Dispatch, MiddlewareStore } from '../store-types';
|
|
5
|
+
import type { State } from '../../types';
|
|
6
|
+
|
|
7
|
+
const shouldStop = (action: Action): boolean =>
|
|
8
|
+
action.type === 'DROP_COMPLETE' ||
|
|
9
|
+
action.type === 'DROP_ANIMATE' ||
|
|
10
|
+
action.type === 'FLUSH';
|
|
11
|
+
|
|
12
|
+
export default (autoScroller: AutoScroller) => (store: MiddlewareStore) => (
|
|
13
|
+
next: Dispatch,
|
|
14
|
+
) => (action: Action): any => {
|
|
15
|
+
if (shouldStop(action)) {
|
|
16
|
+
autoScroller.stop();
|
|
17
|
+
next(action);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (action.type === 'INITIAL_PUBLISH') {
|
|
22
|
+
// letting the action go first to hydrate the state
|
|
23
|
+
next(action);
|
|
24
|
+
const state: State = store.getState();
|
|
25
|
+
invariant(
|
|
26
|
+
state.phase === 'DRAGGING',
|
|
27
|
+
'Expected phase to be DRAGGING after INITIAL_PUBLISH',
|
|
28
|
+
);
|
|
29
|
+
autoScroller.start(state);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// auto scroll happens in response to state changes
|
|
34
|
+
// releasing all actions to the reducer first
|
|
35
|
+
next(action);
|
|
36
|
+
|
|
37
|
+
autoScroller.scroll(store.getState());
|
|
38
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { Action, Dispatch } from '../store-types';
|
|
3
|
+
import type { DimensionMarshal } from '../dimension-marshal/dimension-marshal-types';
|
|
4
|
+
|
|
5
|
+
export default (marshal: DimensionMarshal) => () => (next: Dispatch) => (
|
|
6
|
+
action: Action,
|
|
7
|
+
): any => {
|
|
8
|
+
// Not stopping a collection on a 'DROP' as we want a collection to continue
|
|
9
|
+
if (
|
|
10
|
+
// drag is finished
|
|
11
|
+
action.type === 'DROP_COMPLETE' ||
|
|
12
|
+
action.type === 'FLUSH' ||
|
|
13
|
+
// no longer accepting changes once the drop has started
|
|
14
|
+
action.type === 'DROP_ANIMATE'
|
|
15
|
+
) {
|
|
16
|
+
marshal.stopPublishing();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next(action);
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import { invariant } from '../../../invariant';
|
|
3
|
+
import { completeDrop } from '../../action-creators';
|
|
4
|
+
import type { State } from '../../../types';
|
|
5
|
+
import type { MiddlewareStore, Action, Dispatch } from '../../store-types';
|
|
6
|
+
|
|
7
|
+
export default (store: MiddlewareStore) => (next: Dispatch) => (
|
|
8
|
+
action: Action,
|
|
9
|
+
): any => {
|
|
10
|
+
if (action.type !== 'DROP_ANIMATION_FINISHED') {
|
|
11
|
+
next(action);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const state: State = store.getState();
|
|
16
|
+
invariant(
|
|
17
|
+
state.phase === 'DROP_ANIMATING',
|
|
18
|
+
'Cannot finish a drop animating when no drop is occurring',
|
|
19
|
+
);
|
|
20
|
+
store.dispatch(completeDrop({ completed: state.completed }));
|
|
21
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import { dropAnimationFinished } from '../../action-creators';
|
|
3
|
+
import type { State } from '../../../types';
|
|
4
|
+
import type { MiddlewareStore, Action, Dispatch } from '../../store-types';
|
|
5
|
+
import type { EventBinding } from '../../../view/event-bindings/event-types';
|
|
6
|
+
import bindEvents from '../../../view/event-bindings/bind-events';
|
|
7
|
+
|
|
8
|
+
export default (store: MiddlewareStore) => {
|
|
9
|
+
let unbind: ?() => void = null;
|
|
10
|
+
let frameId: ?AnimationFrameID = null;
|
|
11
|
+
|
|
12
|
+
function clear() {
|
|
13
|
+
if (frameId) {
|
|
14
|
+
cancelAnimationFrame(frameId);
|
|
15
|
+
frameId = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (unbind) {
|
|
19
|
+
unbind();
|
|
20
|
+
unbind = null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (next: Dispatch) => (action: Action): any => {
|
|
25
|
+
if (
|
|
26
|
+
action.type === 'FLUSH' ||
|
|
27
|
+
action.type === 'DROP_COMPLETE' ||
|
|
28
|
+
action.type === 'DROP_ANIMATION_FINISHED'
|
|
29
|
+
) {
|
|
30
|
+
clear();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
next(action);
|
|
34
|
+
|
|
35
|
+
if (action.type !== 'DROP_ANIMATE') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const binding: EventBinding = {
|
|
40
|
+
eventName: 'scroll',
|
|
41
|
+
// capture: true will catch all scroll events, event from scroll containers
|
|
42
|
+
// once: just in case, we only want to ever fire one
|
|
43
|
+
options: { capture: true, passive: false, once: true },
|
|
44
|
+
fn: function flushDropAnimation() {
|
|
45
|
+
const state: State = store.getState();
|
|
46
|
+
if (state.phase === 'DROP_ANIMATING') {
|
|
47
|
+
store.dispatch(dropAnimationFinished());
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// The browser can batch a few scroll events in a single frame
|
|
53
|
+
// including the one that ended the drag.
|
|
54
|
+
// Binding after a requestAnimationFrame ensures that any scrolls caused
|
|
55
|
+
// by the auto scroller are finished
|
|
56
|
+
// TODO: why is a second window scroll being fired?
|
|
57
|
+
// It leads to funny drop positions :(
|
|
58
|
+
frameId = requestAnimationFrame(() => {
|
|
59
|
+
frameId = null;
|
|
60
|
+
unbind = bindEvents(window, [binding]);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { Position } from 'css-box-model';
|
|
3
|
+
import { invariant } from '../../../invariant';
|
|
4
|
+
import type {
|
|
5
|
+
State,
|
|
6
|
+
DropReason,
|
|
7
|
+
Critical,
|
|
8
|
+
DraggableLocation,
|
|
9
|
+
DropResult,
|
|
10
|
+
CompletedDrag,
|
|
11
|
+
Combine,
|
|
12
|
+
DimensionMap,
|
|
13
|
+
DraggableDimension,
|
|
14
|
+
} from '../../../types';
|
|
15
|
+
import type { MiddlewareStore, Dispatch, Action } from '../../store-types';
|
|
16
|
+
import {
|
|
17
|
+
animateDrop,
|
|
18
|
+
completeDrop,
|
|
19
|
+
dropPending,
|
|
20
|
+
type AnimateDropArgs,
|
|
21
|
+
} from '../../action-creators';
|
|
22
|
+
import { isEqual } from '../../position';
|
|
23
|
+
import getDropDuration from './get-drop-duration';
|
|
24
|
+
import getNewHomeClientOffset from './get-new-home-client-offset';
|
|
25
|
+
import getDropImpact, { type Result } from './get-drop-impact';
|
|
26
|
+
import { tryGetCombine, tryGetDestination } from '../../get-impact-location';
|
|
27
|
+
|
|
28
|
+
export default ({ getState, dispatch }: MiddlewareStore) => (
|
|
29
|
+
next: Dispatch,
|
|
30
|
+
) => (action: Action): any => {
|
|
31
|
+
if (action.type !== 'DROP') {
|
|
32
|
+
next(action);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const state: State = getState();
|
|
37
|
+
const reason: DropReason = action.payload.reason;
|
|
38
|
+
|
|
39
|
+
// Still waiting for a bulk collection to publish
|
|
40
|
+
// We are now shifting the application into the 'DROP_PENDING' phase
|
|
41
|
+
if (state.phase === 'COLLECTING') {
|
|
42
|
+
dispatch(dropPending({ reason }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Could have occurred in response to an error
|
|
47
|
+
if (state.phase === 'IDLE') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Still waiting for our drop pending to end
|
|
52
|
+
// TODO: should this throw?
|
|
53
|
+
const isWaitingForDrop: boolean =
|
|
54
|
+
state.phase === 'DROP_PENDING' && state.isWaiting;
|
|
55
|
+
invariant(
|
|
56
|
+
!isWaitingForDrop,
|
|
57
|
+
'A DROP action occurred while DROP_PENDING and still waiting',
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
invariant(
|
|
61
|
+
state.phase === 'DRAGGING' || state.phase === 'DROP_PENDING',
|
|
62
|
+
`Cannot drop in phase: ${state.phase}`,
|
|
63
|
+
);
|
|
64
|
+
// We are now in the DRAGGING or DROP_PENDING phase
|
|
65
|
+
|
|
66
|
+
const critical: Critical = state.critical;
|
|
67
|
+
const dimensions: DimensionMap = state.dimensions;
|
|
68
|
+
const draggable: DraggableDimension =
|
|
69
|
+
dimensions.draggables[state.critical.draggable.id];
|
|
70
|
+
// Only keeping impact when doing a user drop - otherwise we are cancelling
|
|
71
|
+
|
|
72
|
+
const { impact, didDropInsideDroppable }: Result = getDropImpact({
|
|
73
|
+
reason,
|
|
74
|
+
lastImpact: state.impact,
|
|
75
|
+
afterCritical: state.afterCritical,
|
|
76
|
+
onLiftImpact: state.onLiftImpact,
|
|
77
|
+
home: state.dimensions.droppables[state.critical.droppable.id],
|
|
78
|
+
viewport: state.viewport,
|
|
79
|
+
draggables: state.dimensions.draggables,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// only populating destination / combine if 'didDropInsideDroppable' is true
|
|
83
|
+
const destination: ?DraggableLocation = didDropInsideDroppable
|
|
84
|
+
? tryGetDestination(impact)
|
|
85
|
+
: null;
|
|
86
|
+
const combine: ?Combine = didDropInsideDroppable
|
|
87
|
+
? tryGetCombine(impact)
|
|
88
|
+
: null;
|
|
89
|
+
|
|
90
|
+
const source: DraggableLocation = {
|
|
91
|
+
index: critical.draggable.index,
|
|
92
|
+
droppableId: critical.droppable.id,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const result: DropResult = {
|
|
96
|
+
draggableId: draggable.descriptor.id,
|
|
97
|
+
type: draggable.descriptor.type,
|
|
98
|
+
source,
|
|
99
|
+
reason,
|
|
100
|
+
mode: state.movementMode,
|
|
101
|
+
// destination / combine will be null if didDropInsideDroppable is true
|
|
102
|
+
destination,
|
|
103
|
+
combine,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const newHomeClientOffset: Position = getNewHomeClientOffset({
|
|
107
|
+
impact,
|
|
108
|
+
draggable,
|
|
109
|
+
dimensions,
|
|
110
|
+
viewport: state.viewport,
|
|
111
|
+
afterCritical: state.afterCritical,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const completed: CompletedDrag = {
|
|
115
|
+
critical: state.critical,
|
|
116
|
+
afterCritical: state.afterCritical,
|
|
117
|
+
result,
|
|
118
|
+
impact,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const isAnimationRequired: boolean =
|
|
122
|
+
// 1. not already in the right spot
|
|
123
|
+
!isEqual(state.current.client.offset, newHomeClientOffset) ||
|
|
124
|
+
// 2. doing a combine (we still want to animate the scale and opacity fade)
|
|
125
|
+
// looking at the result and not the impact as the combine impact is cleared
|
|
126
|
+
Boolean(result.combine);
|
|
127
|
+
|
|
128
|
+
if (!isAnimationRequired) {
|
|
129
|
+
dispatch(completeDrop({ completed }));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const dropDuration: number = getDropDuration({
|
|
134
|
+
current: state.current.client.offset,
|
|
135
|
+
destination: newHomeClientOffset,
|
|
136
|
+
reason,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const args: AnimateDropArgs = {
|
|
140
|
+
newHomeClientOffset,
|
|
141
|
+
dropDuration,
|
|
142
|
+
completed,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
dispatch(animateDrop(args));
|
|
146
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { Position } from 'css-box-model';
|
|
3
|
+
import { distance as getDistance } from '../../position';
|
|
4
|
+
import { timings } from '../../../animation';
|
|
5
|
+
import type { DropReason } from '../../../types';
|
|
6
|
+
|
|
7
|
+
type GetDropDurationArgs = {|
|
|
8
|
+
current: Position,
|
|
9
|
+
destination: Position,
|
|
10
|
+
reason: DropReason,
|
|
11
|
+
|};
|
|
12
|
+
|
|
13
|
+
const { minDropTime, maxDropTime } = timings;
|
|
14
|
+
const dropTimeRange: number = maxDropTime - minDropTime;
|
|
15
|
+
const maxDropTimeAtDistance: number = 1500;
|
|
16
|
+
// will bring a time lower - which makes it faster
|
|
17
|
+
const cancelDropModifier: number = 0.6;
|
|
18
|
+
|
|
19
|
+
export default ({
|
|
20
|
+
current,
|
|
21
|
+
destination,
|
|
22
|
+
reason,
|
|
23
|
+
}: GetDropDurationArgs): number => {
|
|
24
|
+
const distance: number = getDistance(current, destination);
|
|
25
|
+
// even if there is no distance to travel, we might still need to animate opacity
|
|
26
|
+
if (distance <= 0) {
|
|
27
|
+
return minDropTime;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (distance >= maxDropTimeAtDistance) {
|
|
31
|
+
return maxDropTime;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// * range from:
|
|
35
|
+
// 0px = 0.33s
|
|
36
|
+
// 1500px and over = 0.55s
|
|
37
|
+
// * If reason === 'CANCEL' then speeding up the animation
|
|
38
|
+
// * round to 2 decimal points
|
|
39
|
+
|
|
40
|
+
const percentage: number = distance / maxDropTimeAtDistance;
|
|
41
|
+
const duration: number = minDropTime + dropTimeRange * percentage;
|
|
42
|
+
|
|
43
|
+
const withDuration: number =
|
|
44
|
+
reason === 'CANCEL' ? duration * cancelDropModifier : duration;
|
|
45
|
+
// To two decimal points by converting to string and back
|
|
46
|
+
return Number(withDuration.toFixed(2));
|
|
47
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {
|
|
3
|
+
DropReason,
|
|
4
|
+
DragImpact,
|
|
5
|
+
Viewport,
|
|
6
|
+
DroppableDimension,
|
|
7
|
+
DraggableDimensionMap,
|
|
8
|
+
LiftEffect,
|
|
9
|
+
} from '../../../types';
|
|
10
|
+
import recompute from '../../update-displacement-visibility/recompute';
|
|
11
|
+
import { emptyGroups } from '../../no-impact';
|
|
12
|
+
|
|
13
|
+
type Args = {|
|
|
14
|
+
draggables: DraggableDimensionMap,
|
|
15
|
+
home: DroppableDimension,
|
|
16
|
+
reason: DropReason,
|
|
17
|
+
lastImpact: DragImpact,
|
|
18
|
+
onLiftImpact: DragImpact,
|
|
19
|
+
viewport: Viewport,
|
|
20
|
+
afterCritical: LiftEffect,
|
|
21
|
+
|};
|
|
22
|
+
|
|
23
|
+
export type Result = {|
|
|
24
|
+
impact: DragImpact,
|
|
25
|
+
didDropInsideDroppable: boolean,
|
|
26
|
+
|};
|
|
27
|
+
|
|
28
|
+
export default ({
|
|
29
|
+
draggables,
|
|
30
|
+
reason,
|
|
31
|
+
lastImpact,
|
|
32
|
+
home,
|
|
33
|
+
viewport,
|
|
34
|
+
onLiftImpact,
|
|
35
|
+
}: Args): Result => {
|
|
36
|
+
if (!lastImpact.at || reason !== 'DROP') {
|
|
37
|
+
// Dropping outside of a list or the drag was cancelled
|
|
38
|
+
|
|
39
|
+
// Going to use the on lift impact
|
|
40
|
+
// Need to recompute the visibility of the original impact
|
|
41
|
+
// What is visible can be different to when the drag started
|
|
42
|
+
|
|
43
|
+
const recomputedHomeImpact: DragImpact = recompute({
|
|
44
|
+
draggables,
|
|
45
|
+
impact: onLiftImpact,
|
|
46
|
+
destination: home,
|
|
47
|
+
viewport,
|
|
48
|
+
// We need the draggables to animate back to their positions
|
|
49
|
+
forceShouldAnimate: true,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
impact: recomputedHomeImpact,
|
|
54
|
+
didDropInsideDroppable: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// use the existing impact
|
|
59
|
+
if (lastImpact.at.type === 'REORDER') {
|
|
60
|
+
return {
|
|
61
|
+
impact: lastImpact,
|
|
62
|
+
didDropInsideDroppable: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// When merging we remove the movement so that everything
|
|
67
|
+
// will animate closed
|
|
68
|
+
const withoutMovement: DragImpact = {
|
|
69
|
+
...lastImpact,
|
|
70
|
+
displaced: emptyGroups,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
impact: withoutMovement,
|
|
75
|
+
didDropInsideDroppable: true,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { Position } from 'css-box-model';
|
|
3
|
+
import type {
|
|
4
|
+
DroppableDimension,
|
|
5
|
+
Viewport,
|
|
6
|
+
DragImpact,
|
|
7
|
+
DimensionMap,
|
|
8
|
+
DraggableDimension,
|
|
9
|
+
DroppableId,
|
|
10
|
+
LiftEffect,
|
|
11
|
+
} from '../../../types';
|
|
12
|
+
import whatIsDraggedOver from '../../droppable/what-is-dragged-over';
|
|
13
|
+
import { subtract } from '../../position';
|
|
14
|
+
import getClientBorderBoxCenter from '../../get-center-from-impact/get-client-border-box-center';
|
|
15
|
+
|
|
16
|
+
type Args = {|
|
|
17
|
+
impact: DragImpact,
|
|
18
|
+
draggable: DraggableDimension,
|
|
19
|
+
dimensions: DimensionMap,
|
|
20
|
+
viewport: Viewport,
|
|
21
|
+
afterCritical: LiftEffect,
|
|
22
|
+
|};
|
|
23
|
+
|
|
24
|
+
export default ({
|
|
25
|
+
impact,
|
|
26
|
+
draggable,
|
|
27
|
+
dimensions,
|
|
28
|
+
viewport,
|
|
29
|
+
afterCritical,
|
|
30
|
+
}: Args): Position => {
|
|
31
|
+
const { draggables, droppables } = dimensions;
|
|
32
|
+
const droppableId: ?DroppableId = whatIsDraggedOver(impact);
|
|
33
|
+
const destination: ?DroppableDimension = droppableId
|
|
34
|
+
? droppables[droppableId]
|
|
35
|
+
: null;
|
|
36
|
+
const home: DroppableDimension = droppables[draggable.descriptor.droppableId];
|
|
37
|
+
|
|
38
|
+
const newClientCenter: Position = getClientBorderBoxCenter({
|
|
39
|
+
impact,
|
|
40
|
+
draggable,
|
|
41
|
+
draggables,
|
|
42
|
+
// if there is no destination, then we will be dropping back into the home
|
|
43
|
+
afterCritical,
|
|
44
|
+
droppable: destination || home,
|
|
45
|
+
viewport,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const offset: Position = subtract(
|
|
49
|
+
newClientCenter,
|
|
50
|
+
draggable.client.borderBox.center,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return offset;
|
|
54
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { DropResult } from '../../types';
|
|
3
|
+
import type { Action, Dispatch } from '../store-types';
|
|
4
|
+
import type { FocusMarshal } from '../../view/use-focus-marshal/focus-marshal-types';
|
|
5
|
+
|
|
6
|
+
export default (marshal: FocusMarshal) => {
|
|
7
|
+
let isWatching: boolean = false;
|
|
8
|
+
|
|
9
|
+
return () => (next: Dispatch) => (action: Action): any => {
|
|
10
|
+
if (action.type === 'INITIAL_PUBLISH') {
|
|
11
|
+
isWatching = true;
|
|
12
|
+
|
|
13
|
+
marshal.tryRecordFocus(action.payload.critical.draggable.id);
|
|
14
|
+
next(action);
|
|
15
|
+
marshal.tryRestoreFocusRecorded();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next(action);
|
|
20
|
+
|
|
21
|
+
if (!isWatching) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (action.type === 'FLUSH') {
|
|
26
|
+
isWatching = false;
|
|
27
|
+
marshal.tryRestoreFocusRecorded();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (action.type === 'DROP_COMPLETE') {
|
|
32
|
+
isWatching = false;
|
|
33
|
+
const result: DropResult = action.payload.completed.result;
|
|
34
|
+
|
|
35
|
+
// give focus to the combine target when combining
|
|
36
|
+
if (result.combine) {
|
|
37
|
+
marshal.tryShiftRecord(result.draggableId, result.combine.draggableId);
|
|
38
|
+
}
|
|
39
|
+
marshal.tryRestoreFocusRecorded();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|