@lyra/form-builder
Version:
Lyra form builder
447 lines (361 loc) • 14.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _slateBase64Serializer = require('slate-base64-serializer');
var _slateBase64Serializer2 = _interopRequireDefault(_slateBase64Serializer);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _slate = require('slate');
var _slateReact = require('slate-react');
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _router = require('part:@lyra/base/router');
var _blockExtras = require('part:@lyra/form-builder/input/block-editor/block-extras');
var _blockExtras2 = _interopRequireDefault(_blockExtras);
var _linkIcon = require('part:@lyra/base/link-icon');
var _linkIcon2 = _interopRequireDefault(_linkIcon);
var _buttonCollection = require('part:@lyra/components/buttons/button-collection');
var _buttonCollection2 = _interopRequireDefault(_buttonCollection);
var _PatchEvent = require('../../../PatchEvent');
var _pathUtils = require('../../../utils/pathUtils');
var _resolveTypeName = require('../../../utils/resolveTypeName');
var _createRange = require('../utils/createRange');
var _createRange2 = _interopRequireDefault(_createRange);
var _InvalidValueInput = require('../../InvalidValueInput');
var _InvalidValueInput2 = _interopRequireDefault(_InvalidValueInput);
var _Preview = require('../../../Preview');
var _Preview2 = _interopRequireDefault(_Preview);
var _DeleteButton = require('../DeleteButton');
var _DeleteButton2 = _interopRequireDefault(_DeleteButton);
var _EditButton = require('../EditButton');
var _EditButton2 = _interopRequireDefault(_EditButton);
var _ViewButton = require('../ViewButton');
var _ViewButton2 = _interopRequireDefault(_ViewButton);
var _BlockObject = require('./styles/BlockObject.css');
var _BlockObject2 = _interopRequireDefault(_BlockObject);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*:: import type {Node} from 'react'*/
/*:: import type {
BlockContentFeatures,
SlateValue,
Type,
SlateChange,
Marker
} from '../typeDefs'*/
/*:: type Props = {
attributes: {},
blockContentFeatures: BlockContentFeatures,
children: Node,
editor: Editor,
editorValue: SlateValue,
hasFormBuilderFocus: boolean,
isSelected: boolean,
markers: Marker[],
node: Block,
onChange: (change: SlateChange) => void,
onFocus: (nextPath: []) => void,
onHideBlockDragMarker: void => void,
onPatch: (event: PatchEvent) => void,
onShowBlockDragMarker: (pos: string, node: HTMLDivElement) => void,
readOnly: ?boolean,
blockActions?: React.Node,
renderCustomMarkers?: (Marker[]) => React.Node,
type: ?Type
}*/
/*:: type State = {
isDragging: boolean,
isEditing: boolean,
isSelected: boolean
}*/
class BlockObject extends _react2.default.Component /*:: <Props, State>*/ {
constructor(...args) {
var _temp;
return _temp = super(...args), this.rootElement = null, this.state = {
isDragging: false
}, this.handleDragStart = event => {
const node = this.props.node;
this.setState({ isDragging: true });
this.addDragHandlers();
// const element = ReactDOM.findDOMNode(this.previewContainer)
const encoded = _slateBase64Serializer2.default.serializeNode(node, { preserveKeys: true });
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.dropEffect = 'none';
(0, _slateReact.setEventTransfer)(event, 'node', encoded);
// event.dataTransfer.setDragImage(element, element.clientWidth / 2, element.clientHeight)
}, this.handleDragLeave = event => {
event.dataTransfer.dropEffect = 'none';
this.props.onHideBlockDragMarker();
if (event.target === this._editorNode) {
this.resetDropTarget();
}
}, this.handleDragOverOtherNode = event => {
const node = this.props.node;
if (!this.state.isDragging) {
return;
}
const targetDOMNode = event.target;
// As the event is registered on the editor parent node
// ignore the event if it is coming from from the editor node itself
if (targetDOMNode === this._editorNode) {
return;
}
const editorValue = this.props.editorValue;
const targetNode = (0, _slateReact.findNode)(targetDOMNode, editorValue);
if (!targetNode) {
return;
}
const block = targetNode.object === 'block' ? targetNode : editorValue.document.getClosestBlock(targetNode.key);
// If no or same block reset and return
if (!block || block.key === node.key) {
event.dataTransfer.dropEffect = 'none';
this.resetDropTarget();
return;
}
const range = (0, _createRange2.default)(event);
if (range === null) {
return;
}
const rangeIsAtStart = range.rangeIsAtStart,
rangeOffset = range.rangeOffset;
// If the block in the nearest vincinity (same position target), reset and return
let nearestNeighbour = false;
if (rangeIsAtStart) {
const nextBlock = editorValue.document.getNextBlock(node.key);
nearestNeighbour = nextBlock && nextBlock.key === block.key;
} else {
const previousBlock = editorValue.document.getPreviousBlock(node.key);
nearestNeighbour = previousBlock && previousBlock.key === block.key;
}
if (nearestNeighbour) {
event.dataTransfer.dropEffect = 'none';
this.resetDropTarget();
return;
}
const domNode = (0, _slateReact.findDOMNode)(block); // eslint-disable-line react/no-find-dom-node
if (rangeIsAtStart) {
this.props.onShowBlockDragMarker('before', domNode);
} else {
this.props.onShowBlockDragMarker('after', domNode);
}
event.dataTransfer.dropEffect = 'move';
this._dropTarget = {
node: block,
isAtStart: rangeIsAtStart,
offset: rangeOffset
};
}, this.handleDragEnd = event => {
event.preventDefault();
this.setState({ isDragging: false });
this.removeDragHandlers();
var _props = this.props;
const onChange = _props.onChange,
node = _props.node,
editorValue = _props.editorValue;
const target = this._dropTarget;
// Return if this is our node
if (!target || target.node.key === node.key) {
this.resetDropTarget();
return;
}
let nextChange = editorValue.change().removeNodeByKey(node.key);
nextChange = nextChange[target.isAtStart ? 'collapseToStartOf' : 'collapseToEndOf'](target.node).insertBlock(node).collapseToEndOf(node).focus();
onChange(nextChange);
this.resetDropTarget();
}, this.handleCancelEvent = event => {
event.preventDefault();
event.stopPropagation();
}, this.handleFocus = event => {
event.stopPropagation();
var _props2 = this.props;
const node = _props2.node,
onFocus = _props2.onFocus;
onFocus([{ _key: node.key }, _pathUtils.FOCUS_TERMINATOR]);
}, this.handleEditStart = event => {
event.stopPropagation();
var _props3 = this.props;
const node = _props3.node,
onFocus = _props3.onFocus,
onChange = _props3.onChange,
editorValue = _props3.editorValue;
const change = editorValue.change().collapseToEndOf(node).focus().blur();
onChange(change, () => onFocus([{ _key: node.key }, _pathUtils.FOCUS_TERMINATOR]));
}, this.handleClose = () => {
var _props4 = this.props;
const node = _props4.node,
onFocus = _props4.onFocus;
onFocus([{ _key: node.key }]);
}, this.refFormBuilderBlock = formBuilderBlock => {
this.formBuilderBlock = formBuilderBlock;
}, this.refPreview = previewContainer => {
this.previewContainer = previewContainer;
}, this.handleInvalidValue = (event /*: PatchEvent*/) => {
const onPatch = this.props.onPatch;
const value = this.getValue();
onPatch(event.prefixAll({ _key: value._key }), value);
}, this.handleRemoveValue = event => {
event.preventDefault();
event.stopPropagation();
var _props5 = this.props;
const editorValue = _props5.editorValue,
node = _props5.node,
onChange = _props5.onChange;
const change = editorValue.change();
onChange(change.removeNodeByKey(node.key).focus());
}, _temp;
}
componentDidMount() {
const editor = this.props.editor;
this._editorNode = _reactDom2.default.findDOMNode(editor);
}
componentWillUnmount() {
this.removeDragHandlers();
}
addDragHandlers() {
const readOnly = this.props.readOnly;
if (readOnly) {
return;
}
this._editorNode.addEventListener('dragover', this.handleDragOverOtherNode);
this._editorNode.addEventListener('dragleave', this.handleDragLeave);
}
removeDragHandlers() {
this._editorNode.removeEventListener('dragover', this.handleDragOverOtherNode);
this._editorNode.removeEventListener('dragleave', this.handleDragLeave);
}
// Remove the drop target if we leave the editors nodes
resetDropTarget() {
this._dropTarget = null;
this.props.onHideBlockDragMarker();
}
// eslint-disable-next-line complexity
getValue() {
return this.props.node.data.get('value');
}
renderPreview(value) {
var _props6 = this.props;
const type = _props6.type,
readOnly = _props6.readOnly;
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(
'div',
{ className: _BlockObject2.default.header },
_react2.default.createElement(
'div',
{ className: _BlockObject2.default.type },
type.title || type.name || 'Unknown'
),
_react2.default.createElement(
_buttonCollection2.default,
{ align: 'end', className: _BlockObject2.default.functions },
value._ref && _react2.default.createElement(
_router.IntentLink,
{
className: _BlockObject2.default.linkToReference,
intent: 'edit',
params: { id: value._ref }
},
_react2.default.createElement(_linkIcon2.default, null)
),
readOnly ? _react2.default.createElement(
'div',
null,
_react2.default.createElement(_ViewButton2.default, {
title: 'View this block',
onClick: this.handleFocus
})
) : _react2.default.createElement(
'div',
null,
_react2.default.createElement(_EditButton2.default, {
title: 'Edit this block',
onClick: this.handleEditStart
})
),
!readOnly && _react2.default.createElement(
'div',
null,
_react2.default.createElement(_DeleteButton2.default, {
title: 'Remove this block',
onClick: this.handleRemoveValue
})
)
)
),
_react2.default.createElement(_Preview2.default, { type: type, value: value, layout: 'block' })
);
}
render() {
var _props7 = this.props;
const attributes = _props7.attributes,
blockContentFeatures = _props7.blockContentFeatures,
editorValue = _props7.editorValue,
isSelected = _props7.isSelected,
markers = _props7.markers,
node = _props7.node,
onChange = _props7.onChange,
onFocus = _props7.onFocus,
readOnly = _props7.readOnly,
blockActions = _props7.blockActions,
renderCustomMarkers = _props7.renderCustomMarkers;
const value = this.getValue();
const valueType = (0, _resolveTypeName.resolveTypeName)(value);
const validTypes = blockContentFeatures.types.blockObjects.map(objType => objType.name).concat('block');
if (!validTypes.includes(valueType)) {
return _react2.default.createElement(
'div',
{ onClick: this.handleCancelEvent },
_react2.default.createElement(_InvalidValueInput2.default, {
validTypes: validTypes,
actualType: valueType,
value: value,
onChange: this.handleInvalidValue
})
);
}
const validation = markers.filter(marker => marker.type === 'validation');
const errors = validation.filter(marker => marker.level === 'error');
const highlightedMarker = markers.find(mrk => mrk.highlighted);
const highlightedId = highlightedMarker ? highlightedMarker.highlighted : null;
const classname = (0, _classnames2.default)([_BlockObject2.default.root, editorValue.selection.hasFocusIn(node) && _BlockObject2.default.focused, isSelected && _BlockObject2.default.selected, errors.length > 0 && _BlockObject2.default.hasErrors]);
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(
'div',
_extends({}, attributes, {
onDragStart: this.handleDragStart,
onDragEnd: this.handleDragEnd,
onDragEnter: this.handleCancelEvent,
onDragLeave: this.handleCancelEvent,
onDrop: this.handleCancelEvent,
onDoubleClick: this.handleEditStart,
draggable: !readOnly,
ref: this.refFormBuilderBlock,
className: classname
}),
_react2.default.createElement(
'div',
{ ref: this.refPreview, className: _BlockObject2.default.previewContainer },
this.renderPreview(value)
)
),
(markers.length > 0 || blockActions) && _react2.default.createElement(_blockExtras2.default, {
highlightedMarkerId: highlightedId,
markers: markers,
onFocus: onFocus,
onChange: onChange,
editorValue: editorValue,
block: value,
blockActions: blockActions,
renderCustomMarkers: renderCustomMarkers
})
);
}
}
exports.default = BlockObject;