UNPKG

13.1 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 *
9 * @emails oncall+draft_js
10 *
11 * This file is a fork of DraftEditorBlock.react.js and DraftEditorContents.react.js
12 *
13 * This is unstable and not part of the public API and should not be used by
14 * production systems. This file may be update/removed without notice.
15 */
16'use strict';
17
18var _assign = require("object-assign");
19
20function _extends() { _extends = _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; }; return _extends.apply(this, arguments); }
21
22function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
23
24function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
25
26function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
27
28function _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; }
29
30var DraftEditorNode = require("./DraftEditorNode.react");
31
32var DraftOffsetKey = require("./DraftOffsetKey");
33
34var React = require("react");
35
36var Scroll = require("fbjs/lib/Scroll");
37
38var Style = require("fbjs/lib/Style");
39
40var getElementPosition = require("fbjs/lib/getElementPosition");
41
42var getScrollPosition = require("fbjs/lib/getScrollPosition");
43
44var getViewportDimensions = require("fbjs/lib/getViewportDimensions");
45
46var Immutable = require("immutable");
47
48var invariant = require("fbjs/lib/invariant");
49
50var isHTMLElement = require("./isHTMLElement");
51
52var SCROLL_BUFFER = 10;
53var List = Immutable.List; // we should harden up the bellow flow types to make them more strict
54
55/**
56 * Return whether a block overlaps with either edge of the `SelectionState`.
57 */
58var isBlockOnSelectionEdge = function isBlockOnSelectionEdge(selection, key) {
59 return selection.getAnchorKey() === key || selection.getFocusKey() === key;
60};
61/**
62 * We will use this helper to identify blocks that need to be wrapped but have siblings that
63 * also share the same wrapper element, this way we can do the wrapping once the last sibling
64 * is added.
65 */
66
67
68var shouldNotAddWrapperElement = function shouldNotAddWrapperElement(block, contentState) {
69 var nextSiblingKey = block.getNextSiblingKey();
70 return nextSiblingKey ? contentState.getBlockForKey(nextSiblingKey).getType() === block.getType() : false;
71};
72
73var applyWrapperElementToSiblings = function applyWrapperElementToSiblings(wrapperTemplate, Element, nodes) {
74 var wrappedSiblings = []; // we check back until we find a sibling that does not have same wrapper
75
76 var _iteratorNormalCompletion = true;
77 var _didIteratorError = false;
78 var _iteratorError = undefined;
79
80 try {
81 for (var _iterator = nodes.reverse()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
82 var sibling = _step.value;
83
84 if (sibling.type !== Element) {
85 break;
86 }
87
88 wrappedSiblings.push(sibling);
89 } // we now should remove from acc the wrappedSiblings and add them back under same wrap
90
91 } catch (err) {
92 _didIteratorError = true;
93 _iteratorError = err;
94 } finally {
95 try {
96 if (!_iteratorNormalCompletion && _iterator["return"] != null) {
97 _iterator["return"]();
98 }
99 } finally {
100 if (_didIteratorError) {
101 throw _iteratorError;
102 }
103 }
104 }
105
106 nodes.splice(nodes.indexOf(wrappedSiblings[0]), wrappedSiblings.length + 1);
107 var childrenIs = wrappedSiblings.reverse();
108 var key = childrenIs[0].key;
109 nodes.push(React.cloneElement(wrapperTemplate, {
110 key: "".concat(key, "-wrap"),
111 'data-offset-key': DraftOffsetKey.encode(key, 0, 0)
112 }, childrenIs));
113 return nodes;
114};
115
116var getDraftRenderConfig = function getDraftRenderConfig(block, blockRenderMap) {
117 var configForType = blockRenderMap.get(block.getType()) || blockRenderMap.get('unstyled');
118 var wrapperTemplate = configForType.wrapper;
119 var Element = configForType.element || blockRenderMap.get('unstyled').element;
120 return {
121 Element: Element,
122 wrapperTemplate: wrapperTemplate
123 };
124};
125
126var getCustomRenderConfig = function getCustomRenderConfig(block, blockRendererFn) {
127 var customRenderer = blockRendererFn(block);
128
129 if (!customRenderer) {
130 return {};
131 }
132
133 var CustomComponent = customRenderer.component,
134 customProps = customRenderer.props,
135 customEditable = customRenderer.editable;
136 return {
137 CustomComponent: CustomComponent,
138 customProps: customProps,
139 customEditable: customEditable
140 };
141};
142
143var getElementPropsConfig = function getElementPropsConfig(block, editorKey, offsetKey, blockStyleFn, customConfig, ref) {
144 var elementProps = {
145 'data-block': true,
146 'data-editor': editorKey,
147 'data-offset-key': offsetKey,
148 key: block.getKey(),
149 ref: ref
150 };
151 var customClass = blockStyleFn(block);
152
153 if (customClass) {
154 elementProps.className = customClass;
155 }
156
157 if (customConfig.customEditable !== undefined) {
158 elementProps = _objectSpread({}, elementProps, {
159 contentEditable: customConfig.customEditable,
160 suppressContentEditableWarning: true
161 });
162 }
163
164 return elementProps;
165};
166
167var DraftEditorBlockNode = /*#__PURE__*/function (_React$Component) {
168 _inheritsLoose(DraftEditorBlockNode, _React$Component);
169
170 function DraftEditorBlockNode() {
171 var _this;
172
173 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
174 args[_key] = arguments[_key];
175 }
176
177 _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
178
179 _defineProperty(_assertThisInitialized(_this), "wrapperRef", React.createRef());
180
181 return _this;
182 }
183
184 var _proto = DraftEditorBlockNode.prototype;
185
186 _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps) {
187 var _this$props = this.props,
188 block = _this$props.block,
189 direction = _this$props.direction,
190 tree = _this$props.tree;
191 var isContainerNode = !block.getChildKeys().isEmpty();
192 var blockHasChanged = block !== nextProps.block || tree !== nextProps.tree || direction !== nextProps.direction || isBlockOnSelectionEdge(nextProps.selection, nextProps.block.getKey()) && nextProps.forceSelection; // if we have children at this stage we always re-render container nodes
193 // else if its a root node we avoid re-rendering by checking for block updates
194
195 return isContainerNode || blockHasChanged;
196 }
197 /**
198 * When a block is mounted and overlaps the selection state, we need to make
199 * sure that the cursor is visible to match native behavior. This may not
200 * be the case if the user has pressed `RETURN` or pasted some content, since
201 * programatically creating these new blocks and setting the DOM selection
202 * will miss out on the browser natively scrolling to that position.
203 *
204 * To replicate native behavior, if the block overlaps the selection state
205 * on mount, force the scroll position. Check the scroll state of the scroll
206 * parent, and adjust it to align the entire block to the bottom of the
207 * scroll parent.
208 */
209 ;
210
211 _proto.componentDidMount = function componentDidMount() {
212 var selection = this.props.selection;
213 var endKey = selection.getEndKey();
214
215 if (!selection.getHasFocus() || endKey !== this.props.block.getKey()) {
216 return;
217 }
218
219 var blockNode = this.wrapperRef.current;
220
221 if (!blockNode) {
222 // This Block Node was rendered without a wrapper element.
223 return;
224 }
225
226 var scrollParent = Style.getScrollParent(blockNode);
227 var scrollPosition = getScrollPosition(scrollParent);
228 var scrollDelta;
229
230 if (scrollParent === window) {
231 var nodePosition = getElementPosition(blockNode);
232 var nodeBottom = nodePosition.y + nodePosition.height;
233 var viewportHeight = getViewportDimensions().height;
234 scrollDelta = nodeBottom - viewportHeight;
235
236 if (scrollDelta > 0) {
237 window.scrollTo(scrollPosition.x, scrollPosition.y + scrollDelta + SCROLL_BUFFER);
238 }
239 } else {
240 !isHTMLElement(blockNode) ? process.env.NODE_ENV !== "production" ? invariant(false, 'blockNode is not an HTMLElement') : invariant(false) : void 0;
241 var htmlBlockNode = blockNode;
242 var blockBottom = htmlBlockNode.offsetHeight + htmlBlockNode.offsetTop;
243 var scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
244 scrollDelta = blockBottom - scrollBottom;
245
246 if (scrollDelta > 0) {
247 Scroll.setTop(scrollParent, Scroll.getTop(scrollParent) + scrollDelta + SCROLL_BUFFER);
248 }
249 }
250 };
251
252 _proto.render = function render() {
253 var _this2 = this;
254
255 var _this$props2 = this.props,
256 block = _this$props2.block,
257 blockRenderMap = _this$props2.blockRenderMap,
258 blockRendererFn = _this$props2.blockRendererFn,
259 blockStyleFn = _this$props2.blockStyleFn,
260 contentState = _this$props2.contentState,
261 decorator = _this$props2.decorator,
262 editorKey = _this$props2.editorKey,
263 editorState = _this$props2.editorState,
264 customStyleFn = _this$props2.customStyleFn,
265 customStyleMap = _this$props2.customStyleMap,
266 direction = _this$props2.direction,
267 forceSelection = _this$props2.forceSelection,
268 selection = _this$props2.selection,
269 tree = _this$props2.tree;
270 var children = null;
271
272 if (block.children.size) {
273 children = block.children.reduce(function (acc, key) {
274 var offsetKey = DraftOffsetKey.encode(key, 0, 0);
275 var child = contentState.getBlockForKey(key);
276 var customConfig = getCustomRenderConfig(child, blockRendererFn);
277 var Component = customConfig.CustomComponent || DraftEditorBlockNode;
278
279 var _getDraftRenderConfig = getDraftRenderConfig(child, blockRenderMap),
280 Element = _getDraftRenderConfig.Element,
281 wrapperTemplate = _getDraftRenderConfig.wrapperTemplate;
282
283 var elementProps = getElementPropsConfig(child, editorKey, offsetKey, blockStyleFn, customConfig, null);
284
285 var childProps = _objectSpread({}, _this2.props, {
286 tree: editorState.getBlockTree(key),
287 blockProps: customConfig.customProps,
288 offsetKey: offsetKey,
289 block: child
290 });
291
292 acc.push(React.createElement(Element, elementProps, React.createElement(Component, childProps)));
293
294 if (!wrapperTemplate || shouldNotAddWrapperElement(child, contentState)) {
295 return acc;
296 } // if we are here it means we are the last block
297 // that has a wrapperTemplate so we should wrap itself
298 // and all other previous siblings that share the same wrapper
299
300
301 applyWrapperElementToSiblings(wrapperTemplate, Element, acc);
302 return acc;
303 }, []);
304 }
305
306 var blockKey = block.getKey();
307 var offsetKey = DraftOffsetKey.encode(blockKey, 0, 0);
308 var customConfig = getCustomRenderConfig(block, blockRendererFn);
309 var Component = customConfig.CustomComponent;
310 var blockNode = Component != null ? React.createElement(Component, _extends({}, this.props, {
311 tree: editorState.getBlockTree(blockKey),
312 blockProps: customConfig.customProps,
313 offsetKey: offsetKey,
314 block: block
315 })) : React.createElement(DraftEditorNode, {
316 block: block,
317 children: children,
318 contentState: contentState,
319 customStyleFn: customStyleFn,
320 customStyleMap: customStyleMap,
321 decorator: decorator,
322 direction: direction,
323 forceSelection: forceSelection,
324 hasSelection: isBlockOnSelectionEdge(selection, blockKey),
325 selection: selection,
326 tree: tree
327 });
328
329 if (block.getParentKey()) {
330 return blockNode;
331 }
332
333 var _getDraftRenderConfig2 = getDraftRenderConfig(block, blockRenderMap),
334 Element = _getDraftRenderConfig2.Element;
335
336 var elementProps = getElementPropsConfig(block, editorKey, offsetKey, blockStyleFn, customConfig, this.wrapperRef); // root block nodes needs to be wrapped
337
338 return React.createElement(Element, elementProps, blockNode);
339 };
340
341 return DraftEditorBlockNode;
342}(React.Component);
343
344module.exports = DraftEditorBlockNode;
\No newline at end of file