UNPKG

@lyra/form-builder

Version:
378 lines (335 loc) 11.6 kB
'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;