"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ResponsiveBox = exports.Draggable = exports.DropManager = exports.Droppable = exports["default"] = void 0;

var _jsxRuntime = require("react/jsx-runtime");

var _hoc = _interopRequireDefault(require("@enact/core/hoc"));

var _kind = _interopRequireDefault(require("@enact/core/kind"));

var _Changeable = _interopRequireDefault(require("@enact/ui/Changeable"));

var _Slottable = _interopRequireDefault(require("@enact/ui/Slottable"));

var _classnames = _interopRequireDefault(require("classnames"));

var _propTypes = _interopRequireDefault(require("prop-types"));

var _ramda = require("ramda");

var _react = require("react");

var _recompose = require("recompose");

var _Rearrangeable = _interopRequireDefault(require("../Rearrangeable"));

var _DropManagerModule = _interopRequireDefault(require("./DropManager.module.css"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

// 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
var getKeyByValue = function getKeyByValue(obj, value) {
  return Object.keys(obj).find(function (key) {
    return obj[key] === value;
  });
}; // Establish the base container shape, to be shared with all components as a consistent starting point.


var containerShapePropTypes = _propTypes["default"].shape({
  edge: _propTypes["default"].bool,
  edges: _propTypes["default"].shape({
    bottom: _propTypes["default"].bool,
    left: _propTypes["default"].bool,
    right: _propTypes["default"].bool,
    top: _propTypes["default"].bool
  }),
  horizontalEdge: _propTypes["default"].bool,
  orientation: _propTypes["default"].string,
  size: _propTypes["default"].shape({
    relative: _propTypes["default"].string // Relative size: small, medium, large, full
    // proposed - height: PropTypes.number,
    // proposed - width: PropTypes.number

  }),
  verticalEdge: _propTypes["default"].bool
});

var defaultContainerShape = {
  edge: false,
  edges: {
    top: false,
    right: false,
    bottom: false,
    left: false
  },
  horizontalEdge: false,
  verticalEdge: false,
  orientation: null,
  size: {
    relative: null // Relative size: small, medium, large, full
    // proposed - height: 0,
    // proposed - width: 0

  }
};
/**
 * Default config for DropManager.
 *
 * @memberof agate/DropManager.DropManager
 * @hocconfig
 */

var defaultConfig = {
  // The prop name for the boolean send to the Wrapped component that indicates whether we are
  // able to arrange things. Useful in case we need to show some boxes on the screen or
  // something. The default/common value for this is "arrangeable".
  arrangeableProp: null,
  // The prop name for the boolean send to the Wrapped component that indicates whether we are
  // currently arranging things. Useful in case we need to add an overlay or hide content during
  // dragging. The default/common value for this is "arranging".
  arrangingProp: null,
  // The prop name for the object sent to the Wrapped component containing the arrangement object.
  // This also applies to the incoming arrangement prop name.
  // This will typically directly feed into Rearrangeable.
  // The default/common value for this is "arrangement".
  arrangementProp: null
}; // The arrangementProp above is allowed to be empty. When it's empty, though, and the user doesn't
// want the prop delivered to their Wrapped component, we still need to have some prop name to
// communicate to Rearrangeable. That's where this comes in; it's used if the user didn't specify a
// prop, so we can send an arrangement to Rearrangeable, exclusively.

var fallbackArrangementProp = 'arrangement';
var DropManagerContext = /*#__PURE__*/(0, _react.createContext)(defaultContainerShape);
/**
 * TBD.
 *
 * @class DropManager
 * @hoc
 * @memberof agate/DropManager
 * @private
 */

var DropManager = (0, _hoc["default"])(defaultConfig, function (configHoc, Wrapped) {
  var _class, _temp;

  var ArrangementState = (0, _Changeable["default"])({
    prop: configHoc.arrangementProp || fallbackArrangementProp
  });
  var DropManagerBase = (_temp = _class = /*#__PURE__*/function (_Component) {
    _inherits(DropManagerBase, _Component);

    var _super = _createSuper(DropManagerBase);

    function DropManagerBase() {
      var _this;

      _classCallCheck(this, DropManagerBase);

      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      _this = _super.call.apply(_super, [this].concat(args));
      _this.state = {
        dragging: false
      };

      _this.addDropTarget = function (target) {
        target.classList.add('dropTarget');
      };

      _this.removeDropTarget = function (target) {
        target.classList.remove('dropTarget');
      };

      _this.handleDragStart = function (ev) {
        ev.dataTransfer.setData('text/plain', ev.target.dataset.slot);
        ev.dataTransfer.effectAllowed = 'move';
        _this.dragOriginNode = ev.target;

        _this.setState({
          dragging: true
        });
      };

      _this.handleDragEnter = function (ev) {
        _this.addDropTarget(ev.target);
      };

      _this.handleDragLeave = function (ev) {
        _this.removeDropTarget(ev.target);
      };

      _this.handleDragOver = function (ev) {
        ev.preventDefault(); // Set the dropEffect to move

        ev.dataTransfer.dropEffect = 'move';
      };

      _this.handleDrop = function (ev) {
        ev.preventDefault();
        ev.dataTransfer.clearData(); // Bail early if the drag started from some unknown location.

        if (!_this.dragOriginNode) {
          _this.setState({
            dragging: false
          });

          return;
        } // Get the id of the target and add the moved element to the target's DOM
        // const dragOrigin = ev.dataTransfer.getData('text/plain');


        var dragOrigin = _this.dragOriginNode.dataset.slot;
        var dragDropNode; // If we dropped directly on an element with a slot defined, just use that directly

        if (ev.target.dataset.slot) {
          dragDropNode = ev.target;
        } else {
          // If we dropped on a child of a slotted element (like an icon or a div or other
          // nested component), find the closest ancestor with a slot and use that as the drop element.
          var closestSlot = ev.target.closest('[data-slot]');

          if (closestSlot && closestSlot.dataset.slot) {
            dragDropNode = closestSlot;
          } else {
            // We didn't actually find anything, just bail out. The component was dropped in
            // a place that is unknown.
            _this.setState({
              dragging: false
            });

            return;
          }
        }

        _this.removeDropTarget(dragDropNode); // Get the destination element's slot value, or find its ancestor that has one (in case we drop this on a child or grandchild of the slotted item).
        // const dragDestination = ev.target.dataset.slot || (ev.target.closest('[data-slot]') && ev.target.closest('[data-slot]').dataset.slot);


        var dragDestination = dragDropNode.dataset.slot; // If the dragged element was dropped back on itself, do nothing and exit.

        if (dragDestination === dragOrigin) {
          _this.setState({
            dragging: false
          });

          return;
        }

        _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;
        var _this$props = _this.props,
            arrangement = _this$props.arrangement,
            onArrange = _this$props.onArrange;
        var oldD = getKeyByValue(arrangement, dragDestination);
        var oldO = getKeyByValue(arrangement, dragOrigin);
        arrangement[oldD || dragDestination] = dragOrigin;
        arrangement[oldO || dragOrigin] = dragDestination;

        if (onArrange) {
          onArrange({
            arrangement: arrangement
          });
        }

        _this.setState({
          dragging: false
        });
      };

      return _this;
    }

    _createClass(DropManagerBase, [{
      key: "render",
      // handleDragEnd = () => {
      // 	if (this.props.onArrange) {
      // 		this.props.onArrange({arrangement: this.state.arrangement});
      // 	}
      // };
      value: function render() {
        var _this$props2 = _objectSpread({}, this.props),
            arrangement = _this$props2.arrangement,
            arrangeable = _this$props2.arrangeable,
            className = _this$props2.className,
            rest = _objectWithoutProperties(_this$props2, ["arrangement", "arrangeable", "className"]);

        delete rest.onArrange;
        rest[configHoc.arrangementProp || fallbackArrangementProp] = arrangement;
        if (configHoc.arrangeableProp) rest[configHoc.arrangeableProp] = arrangeable;
        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
          rest.onDragStart = this.handleDragStart;
          rest.onDragEnter = this.handleDragEnter;
          rest.onDragLeave = this.handleDragLeave;
          rest.onDragOver = this.handleDragOver;
          rest.onDrop = this.handleDrop; // rest.onDragEnd = this.handleDragEnd;
        }

        return /*#__PURE__*/(0, _jsxRuntime.jsx)(DropManagerContext.Provider, {
          value: {
            arrangement: arrangement,
            arranging: this.state.dragging,
            arrangeable: arrangeable
          },
          children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapped, _objectSpread(_objectSpread({}, rest), {}, {
            className: (0, _classnames["default"])(className, _DropManagerModule["default"].dropManager, arrangeable ? _DropManagerModule["default"].arrangeable : '', {
              dragging: this.state.dragging
            }) // draggable="true"

          }))
        });
      }
    }]);

    return DropManagerBase;
  }(_react.Component), _class.displayName = 'DropManager', _class.propTypes =
  /** @lends agate/DropManager.DropManager.prototype */
  {
    /**
     * The ready-state to indicate that the contents are allowed to be rearranged
     *
     * @type {Boolean}
     * @public
     */
    arrangeable: _propTypes["default"].bool,

    /**
     * The arrangement object, consumable by `Rearrangeable`. This represents the initial
     * arrangement and will be forwarded to `Rearrangeable` as well as the `Draggable`
     * children. This is ingested using `Changeable`.
     *
     * @type {Object}
     * @public
     */
    arrangement: _propTypes["default"].object,

    /**
     * Executed when an arranging (dragging) action has completed. Included in the payload
     * is a new arrangement object, which can be saved for later arrangement recall.
     *
     * @type {Function}
     * @public
     */
    onArrange: _propTypes["default"].func
  }, _class.defaultProps = {
    arrangeable: false
  }, _temp);
  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.

exports.DropManager = DropManager;

var logicallyPopulateContainerShape = function logicallyPopulateContainerShape(cs) {
  if (cs.edges.left || cs.edges.right) {
    cs.horizontalEdge = true;
    cs.edge = true;
  }

  if (cs.edges.top || cs.edges.bottom) {
    cs.verticalEdge = true;
    cs.edge = true;
  } // return cs;

};

var DraggableContainerContext = /*#__PURE__*/(0, _react.createContext)(null);
/**
 * TBD.
 *
 * @class Draggable
 * @memberof agate/DropManager
 * @ui
 * @private
 */

var Draggable = function Draggable(Wrapped) {
  return (0, _kind["default"])({
    name: 'Draggable',
    propTypes:
    /** @lends agate/DropManager.Draggable.prototype */
    {
      containerShape: containerShapePropTypes,
      draggable: _propTypes["default"].bool,
      name: _propTypes["default"].string,
      slot: _propTypes["default"].string
    },
    // If something is marked explicitly as *not* draggable (everything using this is normally)
    // don't assign it a data-slot, so it can't be dragged(A) or dropped on(B).
    render: function render(_ref) {
      var _ref$containerShape = _ref.containerShape,
          containerShape = _ref$containerShape === void 0 ? {} : _ref$containerShape,
          _ref$draggable = _ref.draggable,
          draggable = _ref$draggable === void 0 ? true : _ref$draggable,
          name = _ref.name,
          slot = _ref.slot,
          rest = _objectWithoutProperties(_ref, ["containerShape", "draggable", "name", "slot"]);

      // Apply a consistent (predictable) set of object keys for consumers
      // defaultProps is a shallow merge only, so we aren't using it :(
      containerShape = (0, _ramda.mergeDeepRight)(defaultContainerShape, containerShape); // Draw conclusions from supplied shape information and auto-set some values.

      logicallyPopulateContainerShape(containerShape);
      Object.freeze(containerShape);
      return /*#__PURE__*/(0, _jsxRuntime.jsx)(DropManagerContext.Consumer, {
        children: function children(_ref2) {
          var arrangement = _ref2.arrangement,
              arrangeable = _ref2.arrangeable;
          return /*#__PURE__*/(0, _jsxRuntime.jsx)(DraggableContainerContext.Provider, {
            value: {
              containerShape: containerShape
            },
            children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapped, _objectSpread(_objectSpread({}, rest), {}, {
              draggable: arrangeable && draggable,
              "data-slot": draggable ? arrangement && (arrangement[name] || arrangement[slot]) || name || slot : null,
              "data-slot-name": slot
            }))
          });
        }
      });
    }
  });
};
/**
 * TBD.
 *
 * @class ResponsiveBox
 * @memberof agate/DropManager
 * @ui
 * @private
 */


exports.Draggable = Draggable;

var ResponsiveBox = function ResponsiveBox(Wrapped) {
  return (0, _recompose.setDisplayName)('ResponsiveBox')(function (props) {
    return /*#__PURE__*/(0, _jsxRuntime.jsx)(DraggableContainerContext.Consumer, {
      children: function children(ctx) {
        var containerShape = ctx ? ctx.containerShape : defaultContainerShape;
        return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapped, _objectSpread({
          containerShape: containerShape
        }, props));
      }
    });
  });
}; //
//
// 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.
 *
 * @class Droppable
 * @hoc
 * @memberof agate/DropManager
 * @private
 */


exports.ResponsiveBox = ResponsiveBox;
var Droppable = (0, _hoc["default"])(function (configHoc, Wrapped) {
  var arrangementProp = configHoc.arrangementProp,
      slots = configHoc.slots,
      rest = _objectWithoutProperties(configHoc, ["arrangementProp", "slots"]);

  return DropManager(_objectSpread({
    arrangementProp: arrangementProp
  }, rest), (0, _Slottable["default"])({
    slots: slots
  }, (0, _Rearrangeable["default"])({
    arrangementProp: arrangementProp,
    slots: slots
  }, Wrapped)));
});
exports.Droppable = Droppable;
var _default = Droppable;
exports["default"] = _default;