@atlaskit/renderer 144.0.3 → 144.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/react/index.js +8 -5
  3. package/dist/cjs/react/nodes/inline.js +46 -3
  4. package/dist/cjs/react/nodes/paragraph.js +5 -2
  5. package/dist/cjs/react/nodes/text-wrapper.js +2 -1
  6. package/dist/cjs/react/utils/render-text-segments.js +13 -3
  7. package/dist/cjs/react/utils/render-text.js +23 -0
  8. package/dist/cjs/ui/Renderer/index.js +1 -1
  9. package/dist/cjs/ui/annotations/draft/component.js +15 -14
  10. package/dist/es2019/react/index.js +8 -5
  11. package/dist/es2019/react/nodes/inline.js +33 -3
  12. package/dist/es2019/react/nodes/paragraph.js +5 -2
  13. package/dist/es2019/react/nodes/text-wrapper.js +2 -1
  14. package/dist/es2019/react/utils/render-text-segments.js +9 -2
  15. package/dist/es2019/react/utils/render-text.js +18 -0
  16. package/dist/es2019/ui/Renderer/index.js +1 -1
  17. package/dist/es2019/ui/annotations/draft/component.js +13 -14
  18. package/dist/esm/react/index.js +8 -5
  19. package/dist/esm/react/nodes/inline.js +45 -3
  20. package/dist/esm/react/nodes/paragraph.js +5 -2
  21. package/dist/esm/react/nodes/text-wrapper.js +2 -1
  22. package/dist/esm/react/utils/render-text-segments.js +13 -3
  23. package/dist/esm/react/utils/render-text.js +18 -0
  24. package/dist/esm/ui/Renderer/index.js +1 -1
  25. package/dist/esm/ui/annotations/draft/component.js +15 -14
  26. package/dist/types/react/index.d.ts +1 -0
  27. package/dist/types/react/nodes/inline.d.ts +16 -0
  28. package/dist/types/react/nodes/paragraph.d.ts +1 -1
  29. package/dist/types/react/nodes/text-wrapper.d.ts +1 -0
  30. package/dist/types/react/types.d.ts +5 -0
  31. package/dist/types/react/utils/render-text-segments.d.ts +1 -1
  32. package/dist/types/react/utils/render-text.d.ts +12 -0
  33. package/dist/types/ui/annotations/draft/component.d.ts +4 -2
  34. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @atlaskit/renderer
2
2
 
3
+ ## 144.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`778fd05c90b9a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/778fd05c90b9a) -
8
+ Reduce per-text-node and per-paragraph allocations in the React serializer (plain-text fast path,
9
+ non-allocating empty-paragraph check) behind the new `platform_renderer_text_paragraph_fast_path`
10
+ experiment. No visual or DOM change.
11
+
3
12
  ## 144.0.3
4
13
 
5
14
  ### Patch Changes
@@ -16,6 +16,7 @@ var _utils = require("@atlaskit/editor-common/utils");
16
16
  var _validator = require("@atlaskit/editor-common/validator");
17
17
  var _model = require("@atlaskit/editor-prosemirror/model");
18
18
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
19
+ var _expVal = require("@atlaskit/platform-feature-experiments/exp-val");
19
20
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
20
21
  var _fg = require("@atlaskit/platform-feature-flags/fg");
21
22
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
@@ -30,8 +31,7 @@ var _contentGetter = require("./utils/content-getter");
30
31
  var _getStandaloneBackgroundColorMarks = require("./utils/getStandaloneBackgroundColorMarks");
31
32
  var _links = require("./utils/links");
32
33
  var _markBlockAsInline = require("./utils/markBlockAsInline");
33
- var _renderTextSegments = require("./utils/render-text-segments");
34
- var _segmentText = require("./utils/segment-text");
34
+ var _renderText = require("./utils/render-text");
35
35
  var _excluded = ["key"];
36
36
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
37
37
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -66,6 +66,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
66
66
  (0, _defineProperty2.default)(this, "allowPlaceholderText", true);
67
67
  (0, _defineProperty2.default)(this, "allowCustomPanels", false);
68
68
  (0, _defineProperty2.default)(this, "surroundTextNodesWithTextWrapper", false);
69
+ (0, _defineProperty2.default)(this, "textFastPath", false);
69
70
  (0, _defineProperty2.default)(this, "allowAnnotations", false);
70
71
  (0, _defineProperty2.default)(this, "standaloneBackgroundColorMarks", []);
71
72
  (0, _defineProperty2.default)(this, "inlinePositions", new Set());
@@ -209,6 +210,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
209
210
  this.allowMediaLinking = init.allowMediaLinking;
210
211
  this.allowAnnotations = Boolean(init.allowAnnotations);
211
212
  this.surroundTextNodesWithTextWrapper = Boolean(init.surroundTextNodesWithTextWrapper);
213
+ this.textFastPath = (0, _expVal.expVal)('platform_renderer_text_paragraph_fast_path', 'isEnabled', false);
212
214
  this.media = init.media;
213
215
  this.mentionNodeDataProvider = init.mentionNodeDataProvider;
214
216
  this.emojiProviderLookupOrder = init.emojiProviderLookupOrder;
@@ -386,11 +388,11 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
386
388
  startPos: startPos + parentDepth,
387
389
  endPos: endPos + parentDepth,
388
390
  textHighlighter: this.textHighlighter,
389
- marks: mark.marks
391
+ marks: mark.marks,
392
+ plainTextFastPath: this.textFastPath
390
393
  }, mark.text);
391
394
  }
392
- var segments = (0, _segmentText.segmentText)(mark.text, this.textHighlighter);
393
- return (0, _renderTextSegments.renderTextSegments)(segments, this.textHighlighter, mark.marks, startPos);
395
+ return (0, _renderText.renderText)(mark.text, this.textHighlighter, mark.marks, startPos, this.textFastPath);
394
396
  }
395
397
  }, {
396
398
  key: "renderNode",
@@ -671,6 +673,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
671
673
  var startPos = this.startPos + path.length;
672
674
  return _objectSpread({
673
675
  asInline: this.inlinePositions.has(startPos) ? 'on' : undefined,
676
+ plainTextFastPath: this.textFastPath,
674
677
  text: node.text,
675
678
  providers: this.providers,
676
679
  eventHandlers: this.eventHandlers,
@@ -5,13 +5,56 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = Inline;
8
+ exports.hasRenderableChild = hasRenderableChild;
8
9
  var _react = _interopRequireDefault(require("react"));
10
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
11
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
12
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13
+ /**
14
+ * Allocation-free equivalent of `React.Children.toArray(children).length > 0`.
15
+ *
16
+ * Mirrors `toArray` semantics: `null`, `undefined` and booleans are dropped; arrays are flattened;
17
+ * everything else (strings including `''`, numbers, elements, fragments, portals) counts as a child.
18
+ */
19
+ function hasRenderableChild(children) {
20
+ if (children == null || typeof children === 'boolean') {
21
+ return false;
22
+ }
23
+ if (Array.isArray(children)) {
24
+ var _iterator = _createForOfIteratorHelper(children),
25
+ _step;
26
+ try {
27
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
28
+ var child = _step.value;
29
+ if (hasRenderableChild(child)) {
30
+ return true;
31
+ }
32
+ }
33
+ } catch (err) {
34
+ _iterator.e(err);
35
+ } finally {
36
+ _iterator.f();
37
+ }
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+
43
+ /**
44
+ * Renders inline content, substituting a non-breaking space when there is nothing to render so
45
+ * an empty paragraph keeps its height.
46
+ *
47
+ * `plainTextFastPath` is threaded down from `ReactSerializer`, which evaluates the experiment
48
+ * once per instance. This component renders once per paragraph, so it must not evaluate the
49
+ * experiment itself. When the prop is absent the original behaviour is used.
50
+ */
9
51
  // Ignored via go/ees005
10
52
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
53
  function Inline(props) {
12
- var children = props.children;
13
- var childCount = _react.default.Children.toArray(children).length;
14
- if (!childCount) {
54
+ var children = props.children,
55
+ plainTextFastPath = props.plainTextFastPath;
56
+ var isEmpty = plainTextFastPath ? !hasRenderableChild(children) : _react.default.Children.toArray(children).length === 0;
57
+ if (isEmpty) {
15
58
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "\xA0");
16
59
  }
17
60
  return children;
@@ -12,7 +12,8 @@ function Paragraph(_ref) {
12
12
  var children = _ref.children,
13
13
  dataAttributes = _ref.dataAttributes,
14
14
  localId = _ref.localId,
15
- asInline = _ref.asInline;
15
+ asInline = _ref.asInline,
16
+ plainTextFastPath = _ref.plainTextFastPath;
16
17
  return (
17
18
  /*#__PURE__*/
18
19
  // ignore the eslint warning Text primitive does not support dataAttributes
@@ -22,6 +23,8 @@ function Paragraph(_ref) {
22
23
  _react.default.createElement("p", (0, _extends2.default)({}, dataAttributes, {
23
24
  "data-local-id": localId,
24
25
  "data-as-inline": asInline
25
- }), /*#__PURE__*/_react.default.createElement(_inline.default, null, children))
26
+ }), /*#__PURE__*/_react.default.createElement(_inline.default, {
27
+ plainTextFastPath: plainTextFastPath
28
+ }, children))
26
29
  );
27
30
  }
@@ -18,7 +18,8 @@ var TextWrapper = function TextWrapper(props) {
18
18
  startPos: startPos,
19
19
  endPos: endPos,
20
20
  textHighlighter: props.textHighlighter,
21
- marks: props.marks
21
+ marks: props.marks,
22
+ plainTextFastPath: props.plainTextFastPath
22
23
  }, children);
23
24
  };
24
25
  var _default = exports.default = TextWrapper;
@@ -6,16 +6,26 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.renderTextSegments = renderTextSegments;
8
8
  var _react = _interopRequireDefault(require("react"));
9
- function renderTextSegments(segments, textHighlighter, marksList, startPos) {
10
- var Component = textHighlighter === null || textHighlighter === void 0 ? void 0 : textHighlighter.component;
11
- var marks = new Set(marksList.map(function (m) {
9
+ /** Collects the mark type names a highlighter component receives for a text node. */
10
+ var toMarkNames = function toMarkNames(marksList) {
11
+ return new Set(marksList.map(function (m) {
12
12
  return m.type.name;
13
13
  }));
14
+ };
15
+ function renderTextSegments(segments, textHighlighter, marksList, startPos) {
16
+ var lazyMarks = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
17
+ var Component = textHighlighter === null || textHighlighter === void 0 ? void 0 : textHighlighter.component;
18
+ // With `lazyMarks`, the Set is only built once a highlighted segment is actually rendered, so
19
+ // a highlighter with no matches on this text allocates nothing extra.
20
+ var marks = lazyMarks ? undefined : toMarkNames(marksList);
14
21
  function renderSegment(segment) {
15
22
  var idx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
16
23
  if (segment.type === 'plain' || !Component) {
17
24
  return segment.text;
18
25
  }
26
+ if (!marks) {
27
+ marks = toMarkNames(marksList);
28
+ }
19
29
  return /*#__PURE__*/_react.default.createElement("span", {
20
30
  "data-highlighted": true,
21
31
  "data-vc": "highlighted-text",
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.renderText = renderText;
7
+ var _renderTextSegments = require("./render-text-segments");
8
+ var _segmentText = require("./segment-text");
9
+ /**
10
+ * Renders a text node's string, applying the text highlighter when one is configured.
11
+ *
12
+ * When `fastPath` is enabled and there is no highlighter (or no text), the input string is
13
+ * returned directly. This is output-identical to the segmented path, which for a single plain
14
+ * segment also returns the bare string, but skips the per-text-node segment array/object and
15
+ * marks Set allocations.
16
+ */
17
+ function renderText(text, textHighlighter, marks, startPos, fastPath) {
18
+ if (fastPath && (!textHighlighter || !text)) {
19
+ return text !== null && text !== void 0 ? text : '';
20
+ }
21
+ var segments = (0, _segmentText.segmentText)(text, textHighlighter);
22
+ return (0, _renderTextSegments.renderTextSegments)(segments, textHighlighter, marks, startPos, fastPath);
23
+ }
@@ -71,7 +71,7 @@ var DEGRADED_SEVERITY_THRESHOLD = exports.DEGRADED_SEVERITY_THRESHOLD = 3000;
71
71
  var TABLE_INFO_TIMEOUT = 10000;
72
72
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
73
73
  var packageName = "@atlaskit/renderer";
74
- var packageVersion = "144.0.2";
74
+ var packageVersion = "144.0.3";
75
75
  var setAsQueryContainerStyles = (0, _react2.css)({
76
76
  containerName: 'ak-renderer-wrapper',
77
77
  containerType: 'inline-size'
@@ -9,8 +9,7 @@ exports.getAnnotationIndex = exports.applyAnnotationOnText = exports.TextWithAnn
9
9
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _react2 = require("@emotion/react");
12
- var _renderTextSegments = require("../../../react/utils/render-text-segments");
13
- var _segmentText = require("../../../react/utils/segment-text");
12
+ var _renderText = require("../../../react/utils/render-text");
14
13
  var _AnnotationManagerContext = require("../contexts/AnnotationManagerContext");
15
14
  var _AnnotationRangeContext = require("../contexts/AnnotationRangeContext");
16
15
  var _types = require("../types");
@@ -85,10 +84,12 @@ var applyAnnotationOnText = exports.applyAnnotationOnText = function applyAnnota
85
84
  shouldApplyAnnotationAt = _ref2.shouldApplyAnnotationAt,
86
85
  draftPosition = _ref2.draftPosition,
87
86
  textHighlighter = _ref2.textHighlighter,
88
- marks = _ref2.marks;
87
+ marks = _ref2.marks,
88
+ _ref2$plainTextFastPa = _ref2.plainTextFastPath,
89
+ plainTextFastPath = _ref2$plainTextFastPa === void 0 ? false : _ref2$plainTextFastPa;
89
90
  var annotateIndex = getAnnotationIndex(shouldApplyAnnotationAt, texts.length);
90
91
  return texts.map(function (value, index) {
91
- var segments = (0, _segmentText.segmentText)(value, textHighlighter);
92
+ var rendered = (0, _renderText.renderText)(value, textHighlighter, marks || [], draftPosition.from, plainTextFastPath);
92
93
  if (annotateIndex === index) {
93
94
  return (
94
95
  // Ignored via go/ees005
@@ -96,7 +97,7 @@ var applyAnnotationOnText = exports.applyAnnotationOnText = function applyAnnota
96
97
  (0, _react2.jsx)(AnnotationDraft, {
97
98
  key: index,
98
99
  draftPosition: draftPosition
99
- }, (0, _renderTextSegments.renderTextSegments)(segments, textHighlighter, marks || [], draftPosition.from))
100
+ }, rendered)
100
101
  );
101
102
  }
102
103
  return (
@@ -104,7 +105,7 @@ var applyAnnotationOnText = exports.applyAnnotationOnText = function applyAnnota
104
105
  // eslint-disable-next-line react/no-array-index-key
105
106
  (0, _react2.jsx)(_react.default.Fragment, {
106
107
  key: index
107
- }, (0, _renderTextSegments.renderTextSegments)(segments, textHighlighter, marks || [], draftPosition.from))
108
+ }, rendered)
108
109
  );
109
110
  });
110
111
  };
@@ -113,7 +114,9 @@ var TextWithAnnotationDraft = exports.TextWithAnnotationDraft = function TextWit
113
114
  endPos = _ref3.endPos,
114
115
  children = _ref3.children,
115
116
  textHighlighter = _ref3.textHighlighter,
116
- marks = _ref3.marks;
117
+ marks = _ref3.marks,
118
+ _ref3$plainTextFastPa = _ref3.plainTextFastPath,
119
+ plainTextFastPath = _ref3$plainTextFastPa === void 0 ? false : _ref3$plainTextFastPa;
117
120
  var textPosition = _react.default.useMemo(function () {
118
121
  return {
119
122
  start: startPos,
@@ -133,28 +136,26 @@ var TextWithAnnotationDraft = exports.TextWithAnnotationDraft = function TextWit
133
136
  return (0, _react2.jsx)(_react.Fragment, null, children);
134
137
  }
135
138
  if (shouldApplyAnnotationAt === false || !nextDraftPosition) {
136
- var segments = (0, _segmentText.segmentText)(textString, textHighlighter);
137
- return (0, _react2.jsx)(_react.Fragment, null, (0, _renderTextSegments.renderTextSegments)(segments, textHighlighter, marks || [], startPos));
139
+ return (0, _react2.jsx)(_react.Fragment, null, (0, _renderText.renderText)(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
138
140
  }
139
141
  if (shouldApplyAnnotationAt === _types.InsertDraftPosition.AROUND_TEXT) {
140
- var _segments = (0, _segmentText.segmentText)(textString, textHighlighter);
141
142
  return (0, _react2.jsx)(AnnotationDraft, {
142
143
  key: 0,
143
144
  draftPosition: nextDraftPosition
144
- }, (0, _renderTextSegments.renderTextSegments)(_segments, textHighlighter, marks || [], startPos));
145
+ }, (0, _renderText.renderText)(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
145
146
  }
146
147
  var offsets = (0, _text.calcTextSplitOffset)(nextDraftPosition, textPosition, textString);
147
148
  var texts = (0, _text.splitText)(textString, offsets);
148
149
  if (!texts) {
149
- var _segments2 = (0, _segmentText.segmentText)(textString, textHighlighter);
150
- return (0, _react2.jsx)(_react.Fragment, null, (0, _renderTextSegments.renderTextSegments)(_segments2, textHighlighter, marks || [], startPos));
150
+ return (0, _react2.jsx)(_react.Fragment, null, (0, _renderText.renderText)(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
151
151
  }
152
152
  var components = applyAnnotationOnText({
153
153
  texts: texts,
154
154
  shouldApplyAnnotationAt: shouldApplyAnnotationAt,
155
155
  draftPosition: nextDraftPosition,
156
156
  textHighlighter: textHighlighter,
157
- marks: marks
157
+ marks: marks,
158
+ plainTextFastPath: plainTextFastPath
158
159
  });
159
160
  return (0, _react2.jsx)(_react.Fragment, null, components);
160
161
  };
@@ -5,6 +5,7 @@ import { getColumnWidths } from '@atlaskit/editor-common/utils';
5
5
  import { getMarksByOrder, isSameMark } from '@atlaskit/editor-common/validator';
6
6
  import { MarkType } from '@atlaskit/editor-prosemirror/model';
7
7
  import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
8
+ import { expVal } from '@atlaskit/platform-feature-experiments/exp-val';
8
9
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
9
10
  import { fg } from '@atlaskit/platform-feature-flags/fg';
10
11
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
@@ -19,8 +20,7 @@ import { createContentGetter } from './utils/content-getter';
19
20
  import { getStandaloneBackgroundColorMarks } from './utils/getStandaloneBackgroundColorMarks';
20
21
  import { isNestedHeaderLinksEnabled } from './utils/links';
21
22
  import { markBlockAsInline } from './utils/markBlockAsInline';
22
- import { renderTextSegments } from './utils/render-text-segments';
23
- import { segmentText } from './utils/segment-text';
23
+ import { renderText } from './utils/render-text';
24
24
  function mergeMarks(marksAndNodes) {
25
25
  return marksAndNodes.reduce((acc, markOrNode) => {
26
26
  const prev = acc.length && acc[acc.length - 1] || null;
@@ -50,6 +50,7 @@ export default class ReactSerializer {
50
50
  _defineProperty(this, "allowPlaceholderText", true);
51
51
  _defineProperty(this, "allowCustomPanels", false);
52
52
  _defineProperty(this, "surroundTextNodesWithTextWrapper", false);
53
+ _defineProperty(this, "textFastPath", false);
53
54
  _defineProperty(this, "allowAnnotations", false);
54
55
  _defineProperty(this, "standaloneBackgroundColorMarks", []);
55
56
  _defineProperty(this, "inlinePositions", new Set());
@@ -196,6 +197,7 @@ export default class ReactSerializer {
196
197
  this.allowMediaLinking = init.allowMediaLinking;
197
198
  this.allowAnnotations = Boolean(init.allowAnnotations);
198
199
  this.surroundTextNodesWithTextWrapper = Boolean(init.surroundTextNodesWithTextWrapper);
200
+ this.textFastPath = expVal('platform_renderer_text_paragraph_fast_path', 'isEnabled', false);
199
201
  this.media = init.media;
200
202
  this.mentionNodeDataProvider = init.mentionNodeDataProvider;
201
203
  this.emojiProviderLookupOrder = init.emojiProviderLookupOrder;
@@ -360,11 +362,11 @@ export default class ReactSerializer {
360
362
  startPos: startPos + parentDepth,
361
363
  endPos: endPos + parentDepth,
362
364
  textHighlighter: this.textHighlighter,
363
- marks: mark.marks
365
+ marks: mark.marks,
366
+ plainTextFastPath: this.textFastPath
364
367
  }, mark.text);
365
368
  }
366
- const segments = segmentText(mark.text, this.textHighlighter);
367
- return renderTextSegments(segments, this.textHighlighter, mark.marks, startPos);
369
+ return renderText(mark.text, this.textHighlighter, mark.marks, startPos, this.textFastPath);
368
370
  }
369
371
  renderNode(
370
372
  // Ignored via go/ees005
@@ -597,6 +599,7 @@ export default class ReactSerializer {
597
599
  const startPos = this.startPos + path.length;
598
600
  return {
599
601
  asInline: this.inlinePositions.has(startPos) ? 'on' : undefined,
602
+ plainTextFastPath: this.textFastPath,
600
603
  text: node.text,
601
604
  providers: this.providers,
602
605
  eventHandlers: this.eventHandlers,
@@ -1,13 +1,43 @@
1
1
  import React from 'react';
2
2
 
3
+ /**
4
+ * Allocation-free equivalent of `React.Children.toArray(children).length > 0`.
5
+ *
6
+ * Mirrors `toArray` semantics: `null`, `undefined` and booleans are dropped; arrays are flattened;
7
+ * everything else (strings including `''`, numbers, elements, fragments, portals) counts as a child.
8
+ */
9
+ export function hasRenderableChild(children) {
10
+ if (children == null || typeof children === 'boolean') {
11
+ return false;
12
+ }
13
+ if (Array.isArray(children)) {
14
+ for (const child of children) {
15
+ if (hasRenderableChild(child)) {
16
+ return true;
17
+ }
18
+ }
19
+ return false;
20
+ }
21
+ return true;
22
+ }
23
+
24
+ /**
25
+ * Renders inline content, substituting a non-breaking space when there is nothing to render so
26
+ * an empty paragraph keeps its height.
27
+ *
28
+ * `plainTextFastPath` is threaded down from `ReactSerializer`, which evaluates the experiment
29
+ * once per instance. This component renders once per paragraph, so it must not evaluate the
30
+ * experiment itself. When the prop is absent the original behaviour is used.
31
+ */
3
32
  // Ignored via go/ees005
4
33
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
34
  export default function Inline(props) {
6
35
  const {
7
- children
36
+ children,
37
+ plainTextFastPath
8
38
  } = props;
9
- const childCount = React.Children.toArray(children).length;
10
- if (!childCount) {
39
+ const isEmpty = plainTextFastPath ? !hasRenderableChild(children) : React.Children.toArray(children).length === 0;
40
+ if (isEmpty) {
11
41
  return /*#__PURE__*/React.createElement(React.Fragment, null, "\xA0");
12
42
  }
13
43
  return children;
@@ -5,7 +5,8 @@ export default function Paragraph({
5
5
  children,
6
6
  dataAttributes,
7
7
  localId,
8
- asInline
8
+ asInline,
9
+ plainTextFastPath
9
10
  }) {
10
11
  return (
11
12
  /*#__PURE__*/
@@ -16,6 +17,8 @@ export default function Paragraph({
16
17
  React.createElement("p", _extends({}, dataAttributes, {
17
18
  "data-local-id": localId,
18
19
  "data-as-inline": asInline
19
- }), /*#__PURE__*/React.createElement(Inline, null, children))
20
+ }), /*#__PURE__*/React.createElement(Inline, {
21
+ plainTextFastPath: plainTextFastPath
22
+ }, children))
20
23
  );
21
24
  }
@@ -15,7 +15,8 @@ const TextWrapper = props => {
15
15
  startPos: startPos,
16
16
  endPos: endPos,
17
17
  textHighlighter: props.textHighlighter,
18
- marks: props.marks
18
+ marks: props.marks,
19
+ plainTextFastPath: props.plainTextFastPath
19
20
  }, children);
20
21
  };
21
22
  export default TextWrapper;
@@ -1,11 +1,18 @@
1
1
  import React from 'react';
2
- export function renderTextSegments(segments, textHighlighter, marksList, startPos) {
2
+ /** Collects the mark type names a highlighter component receives for a text node. */
3
+ const toMarkNames = marksList => new Set(marksList.map(m => m.type.name));
4
+ export function renderTextSegments(segments, textHighlighter, marksList, startPos, lazyMarks = false) {
3
5
  const Component = textHighlighter === null || textHighlighter === void 0 ? void 0 : textHighlighter.component;
4
- const marks = new Set(marksList.map(m => m.type.name));
6
+ // With `lazyMarks`, the Set is only built once a highlighted segment is actually rendered, so
7
+ // a highlighter with no matches on this text allocates nothing extra.
8
+ let marks = lazyMarks ? undefined : toMarkNames(marksList);
5
9
  function renderSegment(segment, idx = 0) {
6
10
  if (segment.type === 'plain' || !Component) {
7
11
  return segment.text;
8
12
  }
13
+ if (!marks) {
14
+ marks = toMarkNames(marksList);
15
+ }
9
16
  return /*#__PURE__*/React.createElement("span", {
10
17
  "data-highlighted": true,
11
18
  "data-vc": "highlighted-text",
@@ -0,0 +1,18 @@
1
+ import { renderTextSegments } from './render-text-segments';
2
+ import { segmentText } from './segment-text';
3
+
4
+ /**
5
+ * Renders a text node's string, applying the text highlighter when one is configured.
6
+ *
7
+ * When `fastPath` is enabled and there is no highlighter (or no text), the input string is
8
+ * returned directly. This is output-identical to the segmented path, which for a single plain
9
+ * segment also returns the bare string, but skips the per-text-node segment array/object and
10
+ * marks Set allocations.
11
+ */
12
+ export function renderText(text, textHighlighter, marks, startPos, fastPath) {
13
+ if (fastPath && (!textHighlighter || !text)) {
14
+ return text !== null && text !== void 0 ? text : '';
15
+ }
16
+ const segments = segmentText(text, textHighlighter);
17
+ return renderTextSegments(segments, textHighlighter, marks, startPos, fastPath);
18
+ }
@@ -56,7 +56,7 @@ export const DEGRADED_SEVERITY_THRESHOLD = 3000;
56
56
  const TABLE_INFO_TIMEOUT = 10000;
57
57
  const RENDER_EVENT_SAMPLE_RATE = 0.1;
58
58
  const packageName = "@atlaskit/renderer";
59
- const packageVersion = "144.0.2";
59
+ const packageVersion = "144.0.3";
60
60
  const setAsQueryContainerStyles = css({
61
61
  containerName: 'ak-renderer-wrapper',
62
62
  containerType: 'inline-size'
@@ -8,8 +8,7 @@ import React, { Fragment, useCallback } from 'react';
8
8
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports, @atlaskit/ui-styling-standard/use-compiled -- emotion jsx pragma; go/DSP-18766
9
9
  import { css, jsx } from '@emotion/react'; // oxlint-ignore @typescript-eslint/consistent-type-imports -- classic @jsx jsx factory + jsx.JSX.Element types
10
10
 
11
- import { renderTextSegments } from '../../../react/utils/render-text-segments';
12
- import { segmentText } from '../../../react/utils/segment-text';
11
+ import { renderText } from '../../../react/utils/render-text';
13
12
  import { useAnnotationManagerDispatch } from '../contexts/AnnotationManagerContext';
14
13
  import { useAnnotationRangeState } from '../contexts/AnnotationRangeContext';
15
14
  import { InsertDraftPosition } from '../types';
@@ -78,11 +77,12 @@ export const applyAnnotationOnText = ({
78
77
  shouldApplyAnnotationAt,
79
78
  draftPosition,
80
79
  textHighlighter,
81
- marks
80
+ marks,
81
+ plainTextFastPath = false
82
82
  }) => {
83
83
  const annotateIndex = getAnnotationIndex(shouldApplyAnnotationAt, texts.length);
84
84
  return texts.map((value, index) => {
85
- const segments = segmentText(value, textHighlighter);
85
+ const rendered = renderText(value, textHighlighter, marks || [], draftPosition.from, plainTextFastPath);
86
86
  if (annotateIndex === index) {
87
87
  return (
88
88
  // Ignored via go/ees005
@@ -90,7 +90,7 @@ export const applyAnnotationOnText = ({
90
90
  jsx(AnnotationDraft, {
91
91
  key: index,
92
92
  draftPosition: draftPosition
93
- }, renderTextSegments(segments, textHighlighter, marks || [], draftPosition.from))
93
+ }, rendered)
94
94
  );
95
95
  }
96
96
  return (
@@ -98,7 +98,7 @@ export const applyAnnotationOnText = ({
98
98
  // eslint-disable-next-line react/no-array-index-key
99
99
  jsx(React.Fragment, {
100
100
  key: index
101
- }, renderTextSegments(segments, textHighlighter, marks || [], draftPosition.from))
101
+ }, rendered)
102
102
  );
103
103
  });
104
104
  };
@@ -107,7 +107,8 @@ export const TextWithAnnotationDraft = ({
107
107
  endPos,
108
108
  children,
109
109
  textHighlighter,
110
- marks
110
+ marks,
111
+ plainTextFastPath = false
111
112
  }) => {
112
113
  const textPosition = React.useMemo(() => ({
113
114
  start: startPos,
@@ -127,28 +128,26 @@ export const TextWithAnnotationDraft = ({
127
128
  return jsx(Fragment, null, children);
128
129
  }
129
130
  if (shouldApplyAnnotationAt === false || !nextDraftPosition) {
130
- const segments = segmentText(textString, textHighlighter);
131
- return jsx(Fragment, null, renderTextSegments(segments, textHighlighter, marks || [], startPos));
131
+ return jsx(Fragment, null, renderText(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
132
132
  }
133
133
  if (shouldApplyAnnotationAt === InsertDraftPosition.AROUND_TEXT) {
134
- const segments = segmentText(textString, textHighlighter);
135
134
  return jsx(AnnotationDraft, {
136
135
  key: 0,
137
136
  draftPosition: nextDraftPosition
138
- }, renderTextSegments(segments, textHighlighter, marks || [], startPos));
137
+ }, renderText(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
139
138
  }
140
139
  const offsets = calcTextSplitOffset(nextDraftPosition, textPosition, textString);
141
140
  const texts = splitText(textString, offsets);
142
141
  if (!texts) {
143
- const segments = segmentText(textString, textHighlighter);
144
- return jsx(Fragment, null, renderTextSegments(segments, textHighlighter, marks || [], startPos));
142
+ return jsx(Fragment, null, renderText(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
145
143
  }
146
144
  const components = applyAnnotationOnText({
147
145
  texts,
148
146
  shouldApplyAnnotationAt,
149
147
  draftPosition: nextDraftPosition,
150
148
  textHighlighter,
151
- marks
149
+ marks,
150
+ plainTextFastPath
152
151
  });
153
152
  return jsx(Fragment, null, components);
154
153
  };
@@ -12,6 +12,7 @@ import { getColumnWidths } from '@atlaskit/editor-common/utils';
12
12
  import { getMarksByOrder, isSameMark } from '@atlaskit/editor-common/validator';
13
13
  import { MarkType } from '@atlaskit/editor-prosemirror/model';
14
14
  import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
15
+ import { expVal } from '@atlaskit/platform-feature-experiments/exp-val';
15
16
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
16
17
  import { fg } from '@atlaskit/platform-feature-flags/fg';
17
18
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
@@ -26,8 +27,7 @@ import { createContentGetter } from './utils/content-getter';
26
27
  import { getStandaloneBackgroundColorMarks } from './utils/getStandaloneBackgroundColorMarks';
27
28
  import { isNestedHeaderLinksEnabled } from './utils/links';
28
29
  import { markBlockAsInline } from './utils/markBlockAsInline';
29
- import { renderTextSegments } from './utils/render-text-segments';
30
- import { segmentText } from './utils/segment-text';
30
+ import { renderText } from './utils/render-text';
31
31
  function mergeMarks(marksAndNodes) {
32
32
  return marksAndNodes.reduce(function (acc, markOrNode) {
33
33
  var prev = acc.length && acc[acc.length - 1] || null;
@@ -59,6 +59,7 @@ var ReactSerializer = /*#__PURE__*/function () {
59
59
  _defineProperty(this, "allowPlaceholderText", true);
60
60
  _defineProperty(this, "allowCustomPanels", false);
61
61
  _defineProperty(this, "surroundTextNodesWithTextWrapper", false);
62
+ _defineProperty(this, "textFastPath", false);
62
63
  _defineProperty(this, "allowAnnotations", false);
63
64
  _defineProperty(this, "standaloneBackgroundColorMarks", []);
64
65
  _defineProperty(this, "inlinePositions", new Set());
@@ -202,6 +203,7 @@ var ReactSerializer = /*#__PURE__*/function () {
202
203
  this.allowMediaLinking = init.allowMediaLinking;
203
204
  this.allowAnnotations = Boolean(init.allowAnnotations);
204
205
  this.surroundTextNodesWithTextWrapper = Boolean(init.surroundTextNodesWithTextWrapper);
206
+ this.textFastPath = expVal('platform_renderer_text_paragraph_fast_path', 'isEnabled', false);
205
207
  this.media = init.media;
206
208
  this.mentionNodeDataProvider = init.mentionNodeDataProvider;
207
209
  this.emojiProviderLookupOrder = init.emojiProviderLookupOrder;
@@ -379,11 +381,11 @@ var ReactSerializer = /*#__PURE__*/function () {
379
381
  startPos: startPos + parentDepth,
380
382
  endPos: endPos + parentDepth,
381
383
  textHighlighter: this.textHighlighter,
382
- marks: mark.marks
384
+ marks: mark.marks,
385
+ plainTextFastPath: this.textFastPath
383
386
  }, mark.text);
384
387
  }
385
- var segments = segmentText(mark.text, this.textHighlighter);
386
- return renderTextSegments(segments, this.textHighlighter, mark.marks, startPos);
388
+ return renderText(mark.text, this.textHighlighter, mark.marks, startPos, this.textFastPath);
387
389
  }
388
390
  }, {
389
391
  key: "renderNode",
@@ -664,6 +666,7 @@ var ReactSerializer = /*#__PURE__*/function () {
664
666
  var startPos = this.startPos + path.length;
665
667
  return _objectSpread({
666
668
  asInline: this.inlinePositions.has(startPos) ? 'on' : undefined,
669
+ plainTextFastPath: this.textFastPath,
667
670
  text: node.text,
668
671
  providers: this.providers,
669
672
  eventHandlers: this.eventHandlers,
@@ -1,11 +1,53 @@
1
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
2
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
3
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
1
4
  import React from 'react';
2
5
 
6
+ /**
7
+ * Allocation-free equivalent of `React.Children.toArray(children).length > 0`.
8
+ *
9
+ * Mirrors `toArray` semantics: `null`, `undefined` and booleans are dropped; arrays are flattened;
10
+ * everything else (strings including `''`, numbers, elements, fragments, portals) counts as a child.
11
+ */
12
+ export function hasRenderableChild(children) {
13
+ if (children == null || typeof children === 'boolean') {
14
+ return false;
15
+ }
16
+ if (Array.isArray(children)) {
17
+ var _iterator = _createForOfIteratorHelper(children),
18
+ _step;
19
+ try {
20
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
21
+ var child = _step.value;
22
+ if (hasRenderableChild(child)) {
23
+ return true;
24
+ }
25
+ }
26
+ } catch (err) {
27
+ _iterator.e(err);
28
+ } finally {
29
+ _iterator.f();
30
+ }
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Renders inline content, substituting a non-breaking space when there is nothing to render so
38
+ * an empty paragraph keeps its height.
39
+ *
40
+ * `plainTextFastPath` is threaded down from `ReactSerializer`, which evaluates the experiment
41
+ * once per instance. This component renders once per paragraph, so it must not evaluate the
42
+ * experiment itself. When the prop is absent the original behaviour is used.
43
+ */
3
44
  // Ignored via go/ees005
4
45
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
46
  export default function Inline(props) {
6
- var children = props.children;
7
- var childCount = React.Children.toArray(children).length;
8
- if (!childCount) {
47
+ var children = props.children,
48
+ plainTextFastPath = props.plainTextFastPath;
49
+ var isEmpty = plainTextFastPath ? !hasRenderableChild(children) : React.Children.toArray(children).length === 0;
50
+ if (isEmpty) {
9
51
  return /*#__PURE__*/React.createElement(React.Fragment, null, "\xA0");
10
52
  }
11
53
  return children;
@@ -5,7 +5,8 @@ export default function Paragraph(_ref) {
5
5
  var children = _ref.children,
6
6
  dataAttributes = _ref.dataAttributes,
7
7
  localId = _ref.localId,
8
- asInline = _ref.asInline;
8
+ asInline = _ref.asInline,
9
+ plainTextFastPath = _ref.plainTextFastPath;
9
10
  return (
10
11
  /*#__PURE__*/
11
12
  // ignore the eslint warning Text primitive does not support dataAttributes
@@ -15,6 +16,8 @@ export default function Paragraph(_ref) {
15
16
  React.createElement("p", _extends({}, dataAttributes, {
16
17
  "data-local-id": localId,
17
18
  "data-as-inline": asInline
18
- }), /*#__PURE__*/React.createElement(Inline, null, children))
19
+ }), /*#__PURE__*/React.createElement(Inline, {
20
+ plainTextFastPath: plainTextFastPath
21
+ }, children))
19
22
  );
20
23
  }
@@ -11,7 +11,8 @@ var TextWrapper = function TextWrapper(props) {
11
11
  startPos: startPos,
12
12
  endPos: endPos,
13
13
  textHighlighter: props.textHighlighter,
14
- marks: props.marks
14
+ marks: props.marks,
15
+ plainTextFastPath: props.plainTextFastPath
15
16
  }, children);
16
17
  };
17
18
  export default TextWrapper;
@@ -1,14 +1,24 @@
1
1
  import React from 'react';
2
- export function renderTextSegments(segments, textHighlighter, marksList, startPos) {
3
- var Component = textHighlighter === null || textHighlighter === void 0 ? void 0 : textHighlighter.component;
4
- var marks = new Set(marksList.map(function (m) {
2
+ /** Collects the mark type names a highlighter component receives for a text node. */
3
+ var toMarkNames = function toMarkNames(marksList) {
4
+ return new Set(marksList.map(function (m) {
5
5
  return m.type.name;
6
6
  }));
7
+ };
8
+ export function renderTextSegments(segments, textHighlighter, marksList, startPos) {
9
+ var lazyMarks = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
10
+ var Component = textHighlighter === null || textHighlighter === void 0 ? void 0 : textHighlighter.component;
11
+ // With `lazyMarks`, the Set is only built once a highlighted segment is actually rendered, so
12
+ // a highlighter with no matches on this text allocates nothing extra.
13
+ var marks = lazyMarks ? undefined : toMarkNames(marksList);
7
14
  function renderSegment(segment) {
8
15
  var idx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
9
16
  if (segment.type === 'plain' || !Component) {
10
17
  return segment.text;
11
18
  }
19
+ if (!marks) {
20
+ marks = toMarkNames(marksList);
21
+ }
12
22
  return /*#__PURE__*/React.createElement("span", {
13
23
  "data-highlighted": true,
14
24
  "data-vc": "highlighted-text",
@@ -0,0 +1,18 @@
1
+ import { renderTextSegments } from './render-text-segments';
2
+ import { segmentText } from './segment-text';
3
+
4
+ /**
5
+ * Renders a text node's string, applying the text highlighter when one is configured.
6
+ *
7
+ * When `fastPath` is enabled and there is no highlighter (or no text), the input string is
8
+ * returned directly. This is output-identical to the segmented path, which for a single plain
9
+ * segment also returns the bare string, but skips the per-text-node segment array/object and
10
+ * marks Set allocations.
11
+ */
12
+ export function renderText(text, textHighlighter, marks, startPos, fastPath) {
13
+ if (fastPath && (!textHighlighter || !text)) {
14
+ return text !== null && text !== void 0 ? text : '';
15
+ }
16
+ var segments = segmentText(text, textHighlighter);
17
+ return renderTextSegments(segments, textHighlighter, marks, startPos, fastPath);
18
+ }
@@ -61,7 +61,7 @@ export var DEGRADED_SEVERITY_THRESHOLD = 3000;
61
61
  var TABLE_INFO_TIMEOUT = 10000;
62
62
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
63
63
  var packageName = "@atlaskit/renderer";
64
- var packageVersion = "144.0.2";
64
+ var packageVersion = "144.0.3";
65
65
  var setAsQueryContainerStyles = css({
66
66
  containerName: 'ak-renderer-wrapper',
67
67
  containerType: 'inline-size'
@@ -8,8 +8,7 @@ import React, { Fragment, useCallback } from 'react';
8
8
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports, @atlaskit/ui-styling-standard/use-compiled -- emotion jsx pragma; go/DSP-18766
9
9
  import { css, jsx } from '@emotion/react'; // oxlint-ignore @typescript-eslint/consistent-type-imports -- classic @jsx jsx factory + jsx.JSX.Element types
10
10
 
11
- import { renderTextSegments } from '../../../react/utils/render-text-segments';
12
- import { segmentText } from '../../../react/utils/segment-text';
11
+ import { renderText } from '../../../react/utils/render-text';
13
12
  import { useAnnotationManagerDispatch } from '../contexts/AnnotationManagerContext';
14
13
  import { useAnnotationRangeState } from '../contexts/AnnotationRangeContext';
15
14
  import { InsertDraftPosition } from '../types';
@@ -76,10 +75,12 @@ export var applyAnnotationOnText = function applyAnnotationOnText(_ref2) {
76
75
  shouldApplyAnnotationAt = _ref2.shouldApplyAnnotationAt,
77
76
  draftPosition = _ref2.draftPosition,
78
77
  textHighlighter = _ref2.textHighlighter,
79
- marks = _ref2.marks;
78
+ marks = _ref2.marks,
79
+ _ref2$plainTextFastPa = _ref2.plainTextFastPath,
80
+ plainTextFastPath = _ref2$plainTextFastPa === void 0 ? false : _ref2$plainTextFastPa;
80
81
  var annotateIndex = getAnnotationIndex(shouldApplyAnnotationAt, texts.length);
81
82
  return texts.map(function (value, index) {
82
- var segments = segmentText(value, textHighlighter);
83
+ var rendered = renderText(value, textHighlighter, marks || [], draftPosition.from, plainTextFastPath);
83
84
  if (annotateIndex === index) {
84
85
  return (
85
86
  // Ignored via go/ees005
@@ -87,7 +88,7 @@ export var applyAnnotationOnText = function applyAnnotationOnText(_ref2) {
87
88
  jsx(AnnotationDraft, {
88
89
  key: index,
89
90
  draftPosition: draftPosition
90
- }, renderTextSegments(segments, textHighlighter, marks || [], draftPosition.from))
91
+ }, rendered)
91
92
  );
92
93
  }
93
94
  return (
@@ -95,7 +96,7 @@ export var applyAnnotationOnText = function applyAnnotationOnText(_ref2) {
95
96
  // eslint-disable-next-line react/no-array-index-key
96
97
  jsx(React.Fragment, {
97
98
  key: index
98
- }, renderTextSegments(segments, textHighlighter, marks || [], draftPosition.from))
99
+ }, rendered)
99
100
  );
100
101
  });
101
102
  };
@@ -104,7 +105,9 @@ export var TextWithAnnotationDraft = function TextWithAnnotationDraft(_ref3) {
104
105
  endPos = _ref3.endPos,
105
106
  children = _ref3.children,
106
107
  textHighlighter = _ref3.textHighlighter,
107
- marks = _ref3.marks;
108
+ marks = _ref3.marks,
109
+ _ref3$plainTextFastPa = _ref3.plainTextFastPath,
110
+ plainTextFastPath = _ref3$plainTextFastPa === void 0 ? false : _ref3$plainTextFastPa;
108
111
  var textPosition = React.useMemo(function () {
109
112
  return {
110
113
  start: startPos,
@@ -124,28 +127,26 @@ export var TextWithAnnotationDraft = function TextWithAnnotationDraft(_ref3) {
124
127
  return jsx(Fragment, null, children);
125
128
  }
126
129
  if (shouldApplyAnnotationAt === false || !nextDraftPosition) {
127
- var segments = segmentText(textString, textHighlighter);
128
- return jsx(Fragment, null, renderTextSegments(segments, textHighlighter, marks || [], startPos));
130
+ return jsx(Fragment, null, renderText(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
129
131
  }
130
132
  if (shouldApplyAnnotationAt === InsertDraftPosition.AROUND_TEXT) {
131
- var _segments = segmentText(textString, textHighlighter);
132
133
  return jsx(AnnotationDraft, {
133
134
  key: 0,
134
135
  draftPosition: nextDraftPosition
135
- }, renderTextSegments(_segments, textHighlighter, marks || [], startPos));
136
+ }, renderText(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
136
137
  }
137
138
  var offsets = calcTextSplitOffset(nextDraftPosition, textPosition, textString);
138
139
  var texts = splitText(textString, offsets);
139
140
  if (!texts) {
140
- var _segments2 = segmentText(textString, textHighlighter);
141
- return jsx(Fragment, null, renderTextSegments(_segments2, textHighlighter, marks || [], startPos));
141
+ return jsx(Fragment, null, renderText(textString, textHighlighter, marks || [], startPos, plainTextFastPath));
142
142
  }
143
143
  var components = applyAnnotationOnText({
144
144
  texts: texts,
145
145
  shouldApplyAnnotationAt: shouldApplyAnnotationAt,
146
146
  draftPosition: nextDraftPosition,
147
147
  textHighlighter: textHighlighter,
148
- marks: marks
148
+ marks: marks,
149
+ plainTextFastPath: plainTextFastPath
149
150
  });
150
151
  return jsx(Fragment, null, components);
151
152
  };
@@ -104,6 +104,7 @@ export default class ReactSerializer implements Serializer<JSX.Element> {
104
104
  private initStartPos;
105
105
  private startPos;
106
106
  private surroundTextNodesWithTextWrapper;
107
+ private textFastPath;
107
108
  private media?;
108
109
  private mentionNodeDataProvider?;
109
110
  private emojiProviderLookupOrder?;
@@ -1 +1,17 @@
1
+ import React from 'react';
2
+ /**
3
+ * Allocation-free equivalent of `React.Children.toArray(children).length > 0`.
4
+ *
5
+ * Mirrors `toArray` semantics: `null`, `undefined` and booleans are dropped; arrays are flattened;
6
+ * everything else (strings including `''`, numbers, elements, fragments, portals) counts as a child.
7
+ */
8
+ export declare function hasRenderableChild(children: React.ReactNode): boolean;
9
+ /**
10
+ * Renders inline content, substituting a non-breaking space when there is nothing to render so
11
+ * an empty paragraph keeps its height.
12
+ *
13
+ * `plainTextFastPath` is threaded down from `ReactSerializer`, which evaluates the experiment
14
+ * once per instance. This component renders once per paragraph, so it must not evaluate the
15
+ * experiment itself. When the prop is absent the original behaviour is used.
16
+ */
1
17
  export default function Inline(props: any): any;
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
2
  import type { NodeProps } from '../types';
3
- export default function Paragraph({ children, dataAttributes, localId, asInline, }: NodeProps): React.JSX.Element;
3
+ export default function Paragraph({ children, dataAttributes, localId, asInline, plainTextFastPath, }: NodeProps): React.JSX.Element;
@@ -6,6 +6,7 @@ type Props = {
6
6
  children?: ReactNode | null;
7
7
  endPos: number;
8
8
  marks?: readonly Mark[];
9
+ plainTextFastPath?: boolean;
9
10
  startPos: number;
10
11
  textHighlighter?: TextHighlighter;
11
12
  };
@@ -45,6 +45,11 @@ export interface NodeMeta {
45
45
  hideExtensionKeysWhilePending?: string[];
46
46
  marks: PMNode['marks'];
47
47
  nodeType: NodeType['name'];
48
+ /**
49
+ * Whether the text and paragraph fast path experiment is enabled for this render.
50
+ * Evaluated once per `ReactSerializer` instance rather than per node.
51
+ */
52
+ plainTextFastPath?: boolean;
48
53
  portal?: HTMLElement | undefined;
49
54
  providers?: ProviderFactory | undefined;
50
55
  rendererAppearance?: RendererAppearance;
@@ -2,4 +2,4 @@ import React from 'react';
2
2
  import type { Mark } from '@atlaskit/editor-prosemirror/model';
3
3
  import type { TextHighlighter } from '../types';
4
4
  import type { TextSegment } from './segment-text';
5
- export declare function renderTextSegments(segments: Array<TextSegment>, textHighlighter: TextHighlighter | undefined, marksList: readonly Mark[], startPos: number): string | React.JSX.Element;
5
+ export declare function renderTextSegments(segments: Array<TextSegment>, textHighlighter: TextHighlighter | undefined, marksList: readonly Mark[], startPos: number, lazyMarks?: boolean): string | React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ import type React from 'react';
2
+ import type { Mark } from '@atlaskit/editor-prosemirror/model';
3
+ import type { TextHighlighter } from '../types';
4
+ /**
5
+ * Renders a text node's string, applying the text highlighter when one is configured.
6
+ *
7
+ * When `fastPath` is enabled and there is no highlighter (or no text), the input string is
8
+ * returned directly. This is output-identical to the segmented path, which for a single plain
9
+ * segment also returns the bare string, but skips the per-text-node segment array/object and
10
+ * marks Set allocations.
11
+ */
12
+ export declare function renderText(text: string | undefined, textHighlighter: TextHighlighter | undefined, marks: readonly Mark[], startPos: number, fastPath: boolean): string | React.JSX.Element;
@@ -14,17 +14,19 @@ export declare const AnnotationDraft: ({ draftPosition, children, }: React.Props
14
14
  type ApplyAnnotationsProps = {
15
15
  draftPosition: Position;
16
16
  marks?: readonly Mark[];
17
+ plainTextFastPath?: boolean;
17
18
  shouldApplyAnnotationAt: InsertDraftPosition.INSIDE | InsertDraftPosition.START | InsertDraftPosition.END;
18
19
  textHighlighter?: TextHighlighter;
19
20
  texts: string[];
20
21
  };
21
22
  export declare const getAnnotationIndex: (annotationPosition: InsertDraftPosition, fragmentCount: number) => number;
22
- export declare const applyAnnotationOnText: ({ texts, shouldApplyAnnotationAt, draftPosition, textHighlighter, marks, }: ApplyAnnotationsProps) => JSX.Element[];
23
+ export declare const applyAnnotationOnText: ({ texts, shouldApplyAnnotationAt, draftPosition, textHighlighter, marks, plainTextFastPath, }: ApplyAnnotationsProps) => JSX.Element[];
23
24
  type Props = React.PropsWithChildren<{
24
25
  endPos: number;
25
26
  marks?: readonly Mark[];
27
+ plainTextFastPath?: boolean;
26
28
  startPos: number;
27
29
  textHighlighter?: TextHighlighter;
28
30
  }>;
29
- export declare const TextWithAnnotationDraft: ({ startPos, endPos, children, textHighlighter, marks, }: Props) => jsx.JSX.Element;
31
+ export declare const TextWithAnnotationDraft: ({ startPos, endPos, children, textHighlighter, marks, plainTextFastPath, }: Props) => jsx.JSX.Element;
30
32
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/renderer",
3
- "version": "144.0.3",
3
+ "version": "144.1.0",
4
4
  "description": "Renderer component",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"