@lyra/form-builder
Version:
Lyra form builder
378 lines (335 loc) • 11.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _isHotkey = require('is-hotkey');
var _activateOnFocus = require('part:@lyra/components/utilities/activate-on-focus');
var _activateOnFocus2 = _interopRequireDefault(_activateOnFocus);
var _portal = require('part:@lyra/components/utilities/portal');
var _stacked = require('part:@lyra/components/utilities/stacked');
var _stacked2 = _interopRequireDefault(_stacked);
var _escapable = require('part:@lyra/components/utilities/escapable');
var _escapable2 = _interopRequireDefault(_escapable);
var _default = require('part:@lyra/components/buttons/default');
var _default2 = _interopRequireDefault(_default);
var _EditNode = require('./EditNode');
var _EditNode2 = _interopRequireDefault(_EditNode);
var _Editor = require('./Editor');
var _Editor2 = _interopRequireDefault(_Editor);
var _Toolbar = require('./Toolbar/Toolbar');
var _Toolbar2 = _interopRequireDefault(_Toolbar);
var _BlockEditor = require('./styles/BlockEditor.css');
var _BlockEditor2 = _interopRequireDefault(_BlockEditor);
var _isMac = require('./utils/isMac');
var _isMac2 = _interopRequireDefault(_isMac);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*:: import type {Element as ReactElement} from 'react'*/
/*:: import type {
BlockContentFeatures,
SlateChange,
SlateValue,
Marker,
Type
} from './typeDefs'*/
/*:: type Props = {
blockContentFeatures: BlockContentFeatures,
editor: ReactElement<typeof Editor>,
editorValue: SlateValue,
fullscreen: boolean,
isActive: boolean,
focusPath: [],
markers: Marker[],
onPatch: (event: PatchEvent) => void,
onChange: (change: SlateChange) => void,
onBlur: (nextPath: []) => void,
onFocus: (nextPath: []) => void,
onToggleFullScreen: void => void,
readOnly?: boolean,
setFocus: void => void,
type: Type,
value: Block[]
}*/
function findEditNode(focusPath, editorValue) {
const focusBlockKey = focusPath[0]._key;
const focusInlineKey = focusPath[1] && focusPath[1] === 'children' && focusPath[2] && focusPath[2]._key;
const markDefKey = focusPath[2] && focusPath[1] === 'markDefs' && focusPath[2]._key;
let key;
if (markDefKey) {
const block = editorValue.document.getDescendant(focusBlockKey);
if (!block) {
return null;
}
const span = block.filterDescendants(desc => desc.type === 'span').find(node => {
const annotations = node.data.get('annotations') || {};
return Object.keys(annotations).find(aKey => annotations[aKey] && annotations[aKey]._key === markDefKey);
});
return span;
} else if (focusInlineKey) {
key = focusInlineKey;
} else {
key = focusBlockKey;
}
return editorValue.document.getDescendant(key);
}
class BlockEditor extends _react2.default.PureComponent /*:: <Props>*/ {
constructor(...args) {
var _temp;
return _temp = super(...args), this.state = {
preventScroll: false,
toolbarStyle: {}
}, this.setScrollContainer = element => {
this._scrollContainer = element;
}, this.setEditorWrapper = element => {
this._editorWrapper = element;
}, this.setRootElement = element => {
this._rootElement = element;
}, this.checkScrollHeight = () => {
if (this._scrollContainer && this._editorWrapper) {
this.setState({
preventScroll: this._scrollContainer.offsetHeight < this._editorWrapper.offsetHeight
});
}
}, this.handleScroll = event => {
if (!this.props.fullscreen) {
this.setState({
toolbarStyle: {}
});
}
const threshold = 100;
const scrollTop = event.target.scrollTop;
let ratio = scrollTop / threshold;
if (ratio >= 1) {
ratio = 1;
}
this.setState({
toolbarStyle: {
backgroundColor: `rgba(255, 255, 255, ${ratio * 0.95})`,
boxShadow: `0 2px ${5 * ratio}px rgba(0, 0, 0, ${ratio * 0.3})`
}
});
}, this.handleKeyDown = (event /*: SyntheticKeyboardEvent<*>*/) => {
const isFullscreenKey = (0, _isHotkey.isKeyHotkey)('mod+enter');
const onToggleFullScreen = this.props.onToggleFullScreen;
if (isFullscreenKey(event)) {
event.preventDefault();
event.stopPropagation();
onToggleFullScreen();
}
}, _temp;
}
componentDidMount() {
this.checkScrollHeight();
if (this._rootElement) {
this._rootElement.addEventListener('keydown', this.handleKeyDown, false);
}
if (this._scrollContainer) {
this._scrollContainer.addEventListener('scroll', this.handleScroll, {
passive: true
});
}
}
componentWillUnmount() {
if (this._rootElement) {
this._rootElement.removeEventListener('keydown', this.handleKeyDown, false);
}
if (this._scrollContainer) {
this._scrollContainer.removeEventListener('scroll', this.handleScroll, {
passive: true
});
}
}
componentWillReceiveProps(nextProps) {
if (nextProps !== this.props) {
this.checkScrollHeight();
}
}
renderNodeEditor() {
var _props = this.props;
const blockContentFeatures = _props.blockContentFeatures,
editorValue = _props.editorValue,
focusPath = _props.focusPath;
const slateNode = findEditNode(focusPath, editorValue);
if (!slateNode || slateNode.type === 'contentBlock') {
return null;
}
let value;
let type;
if (slateNode.type === 'span') {
const annotations = slateNode.data.get('annotations');
const focusedAnnotationName = Object.keys(annotations).find(key => annotations[key]._key === focusPath[2]._key);
if (!focusedAnnotationName) {
return null;
}
value = annotations[focusedAnnotationName];
type = blockContentFeatures.annotations.find(an => an.value === focusedAnnotationName).type;
return this.renderEditNode(value, type, [focusPath[0], 'markDefs', { _key: value._key }]);
}
value = slateNode.data.get('value');
const findType = obj => obj.name === value._type;
if (slateNode.object === 'inline') {
type = blockContentFeatures.types.inlineObjects.find(findType);
return this.renderEditNode(value, type, [focusPath[0], 'children', { _key: value._key }]);
}
type = blockContentFeatures.types.blockObjects.find(findType);
return this.renderEditNode(value, type, [{ _key: value._key }]);
}
renderEditNode(nodeValue, type, path) {
var _props2 = this.props;
const focusPath = _props2.focusPath,
readOnly = _props2.readOnly,
onBlur = _props2.onBlur,
onFocus = _props2.onFocus,
onPatch = _props2.onPatch,
markers = _props2.markers,
value = _props2.value;
return _react2.default.createElement(_EditNode2.default, {
focusPath: focusPath,
markers: markers,
onBlur: onBlur,
onPatch: onPatch,
onFocus: onFocus,
path: path,
type: type,
readOnly: readOnly,
nodeValue: nodeValue,
value: value
});
}
renderEditor() {
var _props3 = this.props;
const isActive = _props3.isActive,
blockContentFeatures = _props3.blockContentFeatures,
editorValue = _props3.editorValue,
editor = _props3.editor,
focusPath = _props3.focusPath,
fullscreen = _props3.fullscreen,
onChange = _props3.onChange,
onFocus = _props3.onFocus,
onToggleFullScreen = _props3.onToggleFullScreen,
readOnly = _props3.readOnly,
setFocus = _props3.setFocus,
type = _props3.type,
markers = _props3.markers;
if (readOnly) {
return _react2.default.createElement(
'div',
null,
editor
);
}
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(
'div',
{ className: _BlockEditor2.default.toolbar },
_react2.default.createElement(_Toolbar2.default, {
blockContentFeatures: blockContentFeatures,
editorValue: editorValue,
fullscreen: fullscreen,
focusPath: focusPath,
onChange: onChange,
onFocus: onFocus,
onToggleFullScreen: onToggleFullScreen,
style: fullscreen ? this.state.toolbarStyle : {},
markers: markers,
type: type
})
),
_react2.default.createElement(
_activateOnFocus2.default,
{
isActive: !this.state.preventScroll || fullscreen || isActive,
html: _react2.default.createElement(
'div',
{ className: _BlockEditor2.default.activeOnFocus },
_react2.default.createElement(
'h3',
null,
'Click to edit'
),
_react2.default.createElement(
'div',
null,
'or'
),
_react2.default.createElement(
'div',
null,
_react2.default.createElement(
_default2.default,
{ onClick: onToggleFullScreen, color: 'primary' },
'Open in fullscreen'
)
),
_react2.default.createElement(
'p',
{ className: _BlockEditor2.default.keyboardShortcut },
'Tip: ',
_react2.default.createElement('br', null),
_react2.default.createElement(
'strong',
null,
_isMac2.default ? '⌘' : 'ctrl',
'\xA0+\xA0enter'
),
' ',
'while editing to go in fullscreen'
)
),
onActivate: setFocus
},
_react2.default.createElement(
'div',
{
className: _BlockEditor2.default.scrollContainer,
ref: this.setScrollContainer,
onScroll: this.handleScroll
},
_react2.default.createElement(
'div',
{ className: _BlockEditor2.default.editorWrapper, ref: this.setEditorWrapper },
editor
)
)
)
);
}
render() {
var _props4 = this.props;
const focusPath = _props4.focusPath,
fullscreen = _props4.fullscreen,
onToggleFullScreen = _props4.onToggleFullScreen;
const isEditingNode = (focusPath || []).length > 1;
const isFocused = (focusPath || []).length;
return _react2.default.createElement(
'div',
{ className: _BlockEditor2.default.root, ref: this.setRootElement },
fullscreen && _react2.default.createElement(
_stacked2.default,
null,
isActive => {
return _react2.default.createElement(
_portal.Portal,
null,
isActive && _react2.default.createElement(_escapable2.default, { onEscape: onToggleFullScreen }),
_react2.default.createElement(
'div',
{ className: _BlockEditor2.default.fullscreen },
this.renderEditor()
)
);
}
),
!fullscreen && _react2.default.createElement(
'div',
{ className: isFocused ? _BlockEditor2.default.focus : '' },
this.renderEditor()
),
isEditingNode && this.renderNodeEditor()
);
}
}
exports.default = BlockEditor;