diff --git a/CHANGELOG.md b/CHANGELOG.md
index a78f1f1d1..56a4a96ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
The following is a curated list of changes in the Enact agate module, newest changes on the top.
+## [unreleased]
+
+### Added
+
+- `agate/DropManager` component
+
## [2.0.6] - 2023-06-08
No significant changes.
diff --git a/DropManager/DropManager.js b/DropManager/DropManager.js
index 1b2ddc739..0539da4ce 100644
--- a/DropManager/DropManager.js
+++ b/DropManager/DropManager.js
@@ -6,7 +6,7 @@
* @exports DropManager
* @exports Draggable
* @exports ResponsiveBox
- * @private
+ * @public
*/
import hoc from '@enact/core/hoc';
@@ -22,27 +22,69 @@ import Rearrangeable from '../Rearrangeable';
import css from './DropManager.module.less';
-// https://stackoverflow.com/questions/9907419/how-to-get-a-key-in-a-javascript-object-by-its-value
-// By: ZER0 - Mar 28 '12 at 12:51
const getKeyByValue = (obj, value) =>
Object.keys(obj).find(key => obj[key] === value);
// Establish the base container shape, to be shared with all components as a consistent starting point.
const containerShapePropTypes = PropTypes.shape({
+ /**
+ * Specifies if the draggable container takes all the available space up to all the edges of the screen.
+ *
+ * @type {Boolean}
+ * @default false
+ * @private
+ */
edge: PropTypes.bool,
+
+ /**
+ * Defines the position of the draggable container relative to screen edges.
+ *
+ * @type {Object}
+ * @private
+ */
edges: PropTypes.shape({
bottom: PropTypes.bool,
left: PropTypes.bool,
right: PropTypes.bool,
top: PropTypes.bool
}),
+
+ /**
+ * Specifies if the draggable container takes all the available space between the horizontal screen edges.
+ *
+ * @type {Boolean}
+ * @default false
+ * @private
+ */
horizontalEdge: PropTypes.bool,
+
+ /**
+ * The layout orientation of the components inside a draggable container.
+ *
+ * @type {String}
+ * @private
+ */
orientation: PropTypes.string,
+
+ /**
+ * The size of the draggable container relative to the screen.
+ *
+ * @type {Object}
+ * @private
+ */
size: PropTypes.shape({
relative: PropTypes.string // Relative size: small, medium, large, full
// proposed - height: PropTypes.number,
// proposed - width: PropTypes.number
}),
+
+ /**
+ * Specifies if the draggable container takes all the available space between the vertical screen edges.
+ *
+ * @type {Boolean}
+ * @default false
+ * @private
+ */
verticalEdge: PropTypes.bool
});
@@ -97,12 +139,12 @@ const fallbackArrangementProp = 'arrangement';
const DropManagerContext = createContext(defaultContainerShape);
/**
- * TBD.
+ * Applies Agate specific behaviors to {@link agate/DropManager.DropManagerBase|DropManagerBase} components.
*
* @class DropManager
* @hoc
* @memberof agate/DropManager
- * @private
+ * @public
*/
const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
const ArrangementState = Changeable({prop: configHoc.arrangementProp || fallbackArrangementProp});
@@ -112,7 +154,7 @@ const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
static propTypes = /** @lends agate/DropManager.DropManager.prototype */ {
/**
- * The ready-state to indicate that the contents are allowed to be rearranged
+ * The ready-state to indicate that the contents are allowed to be rearranged.
*
* @type {Boolean}
* @default false
@@ -225,8 +267,6 @@ const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
this.dragOriginNode.dataset.slot = dragDestination;
dragDropNode.dataset.slot = dragOrigin;
- // console.log('from:', dragOrigin, 'to:', dragDestination);
-
// We successfully completed the drag, blank-out the node.
this.dragOriginNode = null;
@@ -328,12 +368,6 @@ const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
this.setState({dragging: false});
};
- // handleDragEnd = () => {
- // if (this.props.onArrange) {
- // this.props.onArrange({arrangement: this.state.arrangement});
- // }
- // };
-
render () {
const {arrangement, arrangeable, className, ...rest} = {...this.props};
delete rest.onArrange;
@@ -343,7 +377,7 @@ const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
if (configHoc.arrangingProp) rest[configHoc.arrangingProp] = this.state.dragging;
if (arrangeable) {
- // Add all of the necessary events, but only if we're in edit mode
+ // Add all the necessary events, but only if we're in edit mode
rest.onDragStart = this.handleDragStart;
rest.onDragEnter = this.handleDragEnter;
rest.onDragLeave = this.handleDragLeave;
@@ -352,7 +386,6 @@ const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
rest.onTouchStart = this.handleTouchStart;
rest.onTouchMove = this.handleTouchMove;
rest.onTouchEnd = this.handleTouchEnd;
- // rest.onDragEnd = this.handleDragEnd;
}
return (
@@ -376,7 +409,6 @@ const DropManager = hoc(defaultConfig, (configHoc, Wrapped) => {
return ArrangementState(DropManagerBase);
});
-
// Draw conclusions from supplied shape information and auto-set some values based on logical
// conclusions to save time and improve consistency for consumers.
const logicallyPopulateContainerShape = (cs) => {
@@ -391,24 +423,51 @@ const logicallyPopulateContainerShape = (cs) => {
// return cs;
};
-
const DraggableContainerContext = createContext(null);
/**
- * TBD.
+ * Draggable passes props to support the dragging feature in DropManager.
*
* @class Draggable
* @memberof agate/DropManager
* @ui
- * @private
+ * @public
*/
const Draggable = (Wrapped) => kind({
name: 'Draggable',
propTypes: /** @lends agate/DropManager.Draggable.prototype */ {
+ /**
+ * Defines the shape of the draggable container relative to the screen edges.
+ *
+ * @type {Object}
+ * @public
+ */
containerShape: containerShapePropTypes,
+
+ /**
+ * Allows a container to be dragged.
+ *
+ * @type {Boolean}
+ * @default false
+ * @public
+ */
draggable: PropTypes.bool,
+
+ /**
+ * The draggable container's name.
+ *
+ * @type {String}
+ * @public
+ */
name: PropTypes.string,
+
+ /**
+ * Nodes to be inserted in a draggable container.
+ *
+ * @type {Node}
+ * @public
+ */
slot: PropTypes.string
},
@@ -444,12 +503,12 @@ const Draggable = (Wrapped) => kind({
});
/**
- * TBD.
+ * ResponsiveBox passes props to support the responsive layout feature in DropManager.
*
* @class ResponsiveBox
* @memberof agate/DropManager
* @ui
- * @private
+ * @public
*/
const ResponsiveBox = (Wrapped) => {
const Decorator = (props) => (
@@ -466,24 +525,16 @@ const ResponsiveBox = (Wrapped) => {
return Decorator;
};
-//
-//
-// DEV TO-DO LATER:
-//
-// Simplify draggable usage, maybe include a "hide with no content" prop
-//
-// Relocate data-slot junk to context child->parent relay of child identification information
-//
-//
-
// Consolidate usage pattern into a simple reusable piece
/**
- * TBD.
+ * Applies Agate specific behaviors to {@link agate/DropManager.DropManager|DropManager} components.
*
* @class Droppable
* @hoc
* @memberof agate/DropManager
- * @private
+ * @mixes ui/Slottable.Slottable
+ * @mixes agate/Rearrangeable.Rearrangeable
+ * @public
*/
const Droppable = hoc((configHoc, Wrapped) => {
const {arrangementProp, slots, ...rest} = configHoc;
diff --git a/samples/sampler/stories/default/DropManager.js b/samples/sampler/stories/default/DropManager.js
new file mode 100644
index 000000000..6007b7744
--- /dev/null
+++ b/samples/sampler/stories/default/DropManager.js
@@ -0,0 +1,100 @@
+import BodyText from '@enact/agate/BodyText';
+import Button from '@enact/agate/Button';
+import Droppable, {Draggable, DropManager, ResponsiveBox} from '@enact/agate/DropManager';
+import kind from '@enact/core/kind';
+import {mergeComponentMetadata} from '@enact/storybook-utils';
+import {action} from '@enact/storybook-utils/addons/actions';
+import {boolean, select} from '@enact/storybook-utils/addons/controls';
+import {Cell, Layout} from '@enact/ui/Layout';
+import ri from '@enact/ui/resolution';
+import PropTypes from 'prop-types';
+
+DropManager.displayName = 'DropManager';
+const Config = mergeComponentMetadata('DropManager', Draggable, DropManager, Droppable, ResponsiveBox);
+
+const prop = {
+ orientation: {
+ 'landscape': 'horizontal',
+ 'portrait': 'vertical'
+ }
+};
+
+const DraggableCell = Draggable(Cell);
+
+const styles = {
+ top: {backgroundColor: 'pink', borderRadius: ri.scaleToRem(6), marginBottom: ri.scaleToRem(9), padding: ri.scaleToRem(9)},
+ center: {backgroundColor: 'wheat', borderRadius: ri.scaleToRem(6), marginBottom: ri.scaleToRem(9), padding: ri.scaleToRem(9)},
+ bottom: {backgroundColor: 'lightblue', borderRadius: ri.scaleToRem(6), marginBottom: ri.scaleToRem(9), padding: ri.scaleToRem(9)},
+ text: {fontWeight: 'bold', marginBottom: 0}
+};
+
+const CustomLayoutBase = kind({
+ name: 'CustomLayoutBase',
+
+ propTypes: {
+ bottom: PropTypes.node,
+ center: PropTypes.node,
+ top: PropTypes.node
+ },
+
+ render: ({bottom, center, top, ...rest}) => {
+ return (
+
+ {top}
+ {center}
+ {bottom}
+
+ );
+ }
+});
+
+const CustomLayout = Droppable({slots: ['bottom', 'center', 'top']}, CustomLayoutBase);
+
+const ResponsiveLayout = ResponsiveBox(({containerShape, ...rest}) => {
+ const orientation = (containerShape.orientation === 'portrait') ? 'vertical' : 'horizontal';
+
+ return (
+
+ );
+});
+
+export default {
+ title: 'Agate/DropManager',
+ component: 'DropManager'
+};
+
+export const _DropManager = (args) => (
+
+
+
+ Top container with responsive layout
+ Use arrangeable control to drag and drop containers.
+
+
+
+
+ Center container with {args['orientation']} layout
+ Use orientation control to change layout.
+
+
+
+
+ Bottom container with unresponsive layout
+ Bottom content remains vertically aligned no matter the slot's position.
+
+
+);
+
+boolean('arrangeable', _DropManager, Config);
+select('orientation', _DropManager, prop.orientation, Config, 'vertical');
+
+_DropManager.storyName = 'DropManager';
+_DropManager.parameters = {
+ info: {
+ text: 'Basic usage of DropManager'
+ }
+};