@atlaskit/renderer 137.0.0 → 137.1.1

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 +25 -0
  2. package/dist/cjs/react/index.js +14 -2
  3. package/dist/cjs/react/nodes/layoutSection.js +3 -2
  4. package/dist/cjs/react/nodes/multiBodiedExtension.js +3 -2
  5. package/dist/cjs/react/nodes/orderedList.js +5 -2
  6. package/dist/cjs/react/utils/content-getter.js +26 -0
  7. package/dist/cjs/ui/ExtensionRenderer.js +7 -2
  8. package/dist/cjs/ui/Renderer/index.js +1 -1
  9. package/dist/es2019/react/index.js +14 -2
  10. package/dist/es2019/react/nodes/layoutSection.js +3 -2
  11. package/dist/es2019/react/nodes/multiBodiedExtension.js +3 -2
  12. package/dist/es2019/react/nodes/orderedList.js +5 -2
  13. package/dist/es2019/react/nodes/syncBlock.js +1 -0
  14. package/dist/es2019/react/utils/content-getter.js +20 -0
  15. package/dist/es2019/ui/ExtensionRenderer.js +7 -2
  16. package/dist/es2019/ui/Renderer/index.js +1 -1
  17. package/dist/esm/react/index.js +14 -2
  18. package/dist/esm/react/nodes/layoutSection.js +3 -2
  19. package/dist/esm/react/nodes/multiBodiedExtension.js +3 -2
  20. package/dist/esm/react/nodes/orderedList.js +5 -2
  21. package/dist/esm/react/nodes/syncBlock.js +1 -0
  22. package/dist/esm/react/utils/content-getter.js +20 -0
  23. package/dist/esm/ui/ExtensionRenderer.js +7 -2
  24. package/dist/esm/ui/Renderer/index.js +1 -1
  25. package/dist/types/entry-points/react-types.d.ts +1 -1
  26. package/dist/types/react/nodes/bodiedExtension.d.ts +1 -0
  27. package/dist/types/react/nodes/layoutSection.d.ts +3 -2
  28. package/dist/types/react/nodes/multiBodiedExtension.d.ts +1 -0
  29. package/dist/types/react/nodes/orderedList.d.ts +3 -1
  30. package/dist/types/react/nodes/syncBlock.d.ts +0 -2
  31. package/dist/types/react/types.d.ts +26 -3
  32. package/dist/types/react/utils/content-getter.d.ts +10 -0
  33. package/dist/types/ui/ExtensionRenderer.d.ts +5 -0
  34. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atlaskit/renderer
2
2
 
3
+ ## 137.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 137.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`6b67bdd5e6a75`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6b67bdd5e6a75) -
14
+ Node components now receive a `getContent()` prop, which supersedes `content` and will replace it.
15
+ `content` is a recursive `Fragment.toJSON()` of the node's subtree, attached to every node and
16
+ serialized on every render whether or not anything reads it — so the document was re-serialized
17
+ once per ancestor node, and a table paid it again for every row and every cell. `getContent()`
18
+ returns the same data, computed on first call and memoized, and is present whether or not the
19
+ experiment below is enabled.
20
+
21
+ Behind experiment `platform_editor_renderer_defer_content`, `content` is no longer provided on any
22
+ node that has children (a childless node keeps the `null` both arms produce). Custom
23
+ `nodeComponents` that read `content` should read `content ?? getContent?.()`, which covers both
24
+ arms without serializing twice while the experiment is off; `content` is removed outright when the
25
+ experiment ships. Extension handlers are unaffected — `ExtensionParams.content` is still
26
+ populated, now from a single `getContent()` call rather than one serialization per ancestor.
27
+
3
28
  ## 137.0.0
4
29
 
5
30
  ### Patch Changes
@@ -21,6 +21,7 @@ var _links = require("./utils/links");
21
21
  var _utils = require("@atlaskit/editor-common/utils");
22
22
  var _validator = require("@atlaskit/editor-common/validator");
23
23
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
24
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
24
25
  var _fg = require("@atlaskit/platform-feature-flags/fg");
25
26
  var _utils3 = require("../utils");
26
27
  var _marks = require("./marks");
@@ -29,6 +30,7 @@ var _rendererNode = require("./renderer-node");
29
30
  var _renderTextSegments = require("./utils/render-text-segments");
30
31
  var _segmentText = require("./utils/segment-text");
31
32
  var _getStandaloneBackgroundColorMarks = require("./utils/getStandaloneBackgroundColorMarks");
33
+ var _contentGetter = require("./utils/content-getter");
32
34
  var _markBlockAsInline = require("./utils/markBlockAsInline");
33
35
  var _excluded = ["key"];
34
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; }
@@ -667,8 +669,18 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
667
669
  }, {
668
670
  key: "getProps",
669
671
  value: function getProps(node) {
672
+ var _node$content;
670
673
  var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
671
674
  var startPos = this.startPos + path.length;
675
+
676
+ // `content` is a recursive Fragment.toJSON() attached to every node, so the document is
677
+ // re-serialized once per ancestor — O(depth x size) overall, and a table paid it again for
678
+ // every row and cell. Consumers read `getContent()` instead, which serializes at most once.
679
+ // Experiment call last, so exposure only fires for nodes that have content to serialize.
680
+ var skipEagerContent = !!((_node$content = node.content) !== null && _node$content !== void 0 && _node$content.childCount) && (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_renderer_defer_content');
681
+
682
+ // Shared so the `content` prop and `getContent()` never serialize the subtree twice.
683
+ var getContent = (0, _contentGetter.createContentGetter)(node.content);
672
684
  return _objectSpread({
673
685
  asInline: this.inlinePositions.has(startPos) ? 'on' : undefined,
674
686
  text: node.text,
@@ -678,7 +690,8 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
678
690
  portal: this.portal,
679
691
  rendererContext: this.rendererContext,
680
692
  serializer: this,
681
- content: node.content ? node.content.toJSON() : undefined,
693
+ content: skipEagerContent ? undefined : getContent(),
694
+ getContent: getContent,
682
695
  allowHeadingAnchorLinks: this.allowHeadingAnchorLinks,
683
696
  allowCopyToClipboard: this.allowCopyToClipboard,
684
697
  allowDownloadCodeBlock: this.allowDownloadCodeBlock,
@@ -714,7 +727,6 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
714
727
  value: function getHeadingProps(node) {
715
728
  var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
716
729
  return _objectSpread(_objectSpread({}, this.getProps(node, path)), {}, {
717
- content: node.content ? node.content.toJSON() : undefined,
718
730
  headingId: this.getHeadingId(node, this.headingIds),
719
731
  showAnchorLink: this.appearance !== 'comment' && this.allowHeadingAnchorLinks && !this.disableHeadingIDs && this.headingAnchorSupported(path)
720
732
  });
@@ -8,7 +8,8 @@ exports.default = LayoutSection;
8
8
  var _react = _interopRequireDefault(require("react"));
9
9
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
10
10
  function LayoutSection(props) {
11
- var _props$content;
11
+ var _ref, _props$content, _props$getContent;
12
+ var columnCount = (_ref = (_props$content = props.content) !== null && _props$content !== void 0 ? _props$content : (_props$getContent = props.getContent) === null || _props$getContent === void 0 ? void 0 : _props$getContent.call(props)) === null || _ref === void 0 ? void 0 : _ref.length;
12
13
  return (0, _experiments.editorExperiment)('advanced_layouts', true) ?
13
14
  /*#__PURE__*/
14
15
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
@@ -17,7 +18,7 @@ function LayoutSection(props) {
17
18
  "data-local-id": props.localId
18
19
  }, /*#__PURE__*/_react.default.createElement("div", {
19
20
  "data-layout-section": true,
20
- "data-layout-columns": props === null || props === void 0 || (_props$content = props.content) === null || _props$content === void 0 ? void 0 : _props$content.length
21
+ "data-layout-columns": columnCount
21
22
  }, props.children)) : /*#__PURE__*/_react.default.createElement("div", {
22
23
  "data-layout-section": true,
23
24
  "data-local-id": props.localId
@@ -119,6 +119,7 @@ var MultiBodiedExtension = function MultiBodiedExtension(props) {
119
119
  extensionKey = props.extensionKey,
120
120
  extensionHandlers = props.extensionHandlers,
121
121
  content = props.content,
122
+ getContent = props.getContent,
122
123
  marks = props.marks,
123
124
  localId = props.localId,
124
125
  rendererAppearance = props.rendererAppearance;
@@ -159,7 +160,7 @@ var MultiBodiedExtension = function MultiBodiedExtension(props) {
159
160
  extensionKey: extensionKey,
160
161
  extensionType: extensionType,
161
162
  parameters: parameters,
162
- content: content,
163
+ content: content !== null && content !== void 0 ? content : getContent === null || getContent === void 0 ? void 0 : getContent(),
163
164
  localId: localId,
164
165
  fragmentLocalId: fragmentLocalId
165
166
  };
@@ -174,7 +175,7 @@ var MultiBodiedExtension = function MultiBodiedExtension(props) {
174
175
  actions: actions
175
176
  })), (0, _react.jsx)(MultiBodiedExtensionChildrenContainer, null, children));
176
177
  }
177
- }, [loading, extensionContext, marks, extensionKey, extensionType, parameters, content, localId, actions, children]);
178
+ }, [loading, extensionContext, marks, extensionKey, extensionType, parameters, content, getContent, localId, actions, children]);
178
179
 
179
180
  // make the frame visible
180
181
  // eslint-disable-next-line @atlaskit/design-system/no-css-tagged-template-expression
@@ -12,11 +12,14 @@ var _styles = require("@atlaskit/editor-common/styles");
12
12
  var _utils = require("@atlaskit/editor-common/utils");
13
13
  var _lists = require("../utils/lists");
14
14
  function OrderedList(props) {
15
- var _props$content;
15
+ var _ref, _props$content, _props$getContent;
16
16
  var extraProps = {};
17
+
18
+ // Item count drives the marker column width, so losing it narrows the gutter by a digit.
19
+ var itemsCount = (_ref = (_props$content = props.content) !== null && _props$content !== void 0 ? _props$content : (_props$getContent = props.getContent) === null || _props$getContent === void 0 ? void 0 : _props$getContent.call(props)) === null || _ref === void 0 ? void 0 : _ref.length;
17
20
  var itemCounterDigitsSize = (0, _utils.getItemCounterDigitsSize)({
18
21
  order: props.order,
19
- itemsCount: props === null || props === void 0 || (_props$content = props.content) === null || _props$content === void 0 ? void 0 : _props$content.length
22
+ itemsCount: itemsCount
20
23
  });
21
24
  if (itemCounterDigitsSize && itemCounterDigitsSize > 1) {
22
25
  extraProps.style = (0, _styles.getOrderedListInlineStyles)(itemCounterDigitsSize, 'object');
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createContentGetter = void 0;
7
+ /**
8
+ * Builds the `getContent` node prop: `fragment.toJSON()` computed on first call and memoized for
9
+ * the lifetime of the props object it is attached to.
10
+ *
11
+ * A prop getter cannot achieve the same deferral: `getProps`' callers spread its result and React
12
+ * copies props again on element creation, so a getter would run before any component reads it.
13
+ */
14
+ var createContentGetter = exports.createContentGetter = function createContentGetter(fragment) {
15
+ var serialized;
16
+ var materialized = false;
17
+ return function () {
18
+ if (!materialized) {
19
+ serialized = fragment ? fragment.toJSON() : undefined;
20
+ // Set last: if `toJSON()` throws, the error propagates and a retry re-attempts instead of
21
+ // caching a partial result.
22
+ materialized = true;
23
+ }
24
+ return serialized;
25
+ };
26
+ };
@@ -54,6 +54,7 @@ function ExtensionRenderer(props) {
54
54
  extensionKey = props.extensionKey,
55
55
  parameters = props.parameters,
56
56
  content = props.content,
57
+ getContent = props.getContent,
57
58
  text = props.text,
58
59
  type = props.type,
59
60
  localId = props.localId,
@@ -90,12 +91,16 @@ function ExtensionRenderer(props) {
90
91
  var fragmentLocalId = marks === null || marks === void 0 || (_marks$find = marks.find(function (m) {
91
92
  return m.type.name === 'fragment';
92
93
  })) === null || _marks$find === void 0 || (_marks$find = _marks$find.attrs) === null || _marks$find === void 0 ? void 0 : _marks$find.localId;
94
+
95
+ // Extension handlers read the macro body off `node.content`, so this is the one place that
96
+ // always has to serialize. `getContent` is what makes it a single pass instead of one per
97
+ // ancestor node.
93
98
  var node = {
94
99
  type: type,
95
100
  extensionKey: extensionKey,
96
101
  extensionType: extensionType,
97
102
  parameters: parameters,
98
- content: content || text,
103
+ content: (content !== null && content !== void 0 ? content : getContent === null || getContent === void 0 ? void 0 : getContent()) || text,
99
104
  localId: localId,
100
105
  fragmentLocalId: fragmentLocalId
101
106
  };
@@ -134,7 +139,7 @@ function ExtensionRenderer(props) {
134
139
  node: node,
135
140
  result: result
136
141
  });
137
- }, [actions, children, content, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
142
+ }, [actions, children, content, getContent, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
138
143
  var setupAndRenderExtensionNode = _react2.default.useCallback(function (providers) {
139
144
  if (extensionProvider === null && providers.extensionProvider) {
140
145
  handleProvider('extensionProvider', providers.extensionProvider);
@@ -74,7 +74,7 @@ var DEGRADED_SEVERITY_THRESHOLD = exports.DEGRADED_SEVERITY_THRESHOLD = 3000;
74
74
  var TABLE_INFO_TIMEOUT = 10000;
75
75
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
76
76
  var packageName = "@atlaskit/renderer";
77
- var packageVersion = "136.2.7";
77
+ var packageVersion = "137.1.0";
78
78
  var setAsQueryContainerStyles = (0, _react2.css)({
79
79
  containerName: 'ak-renderer-wrapper',
80
80
  containerType: 'inline-size'
@@ -10,6 +10,7 @@ import { isNestedHeaderLinksEnabled } from './utils/links';
10
10
  import { getColumnWidths } from '@atlaskit/editor-common/utils';
11
11
  import { getMarksByOrder, isSameMark } from '@atlaskit/editor-common/validator';
12
12
  import { findChildrenByMark, findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
13
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
13
14
  import { fg } from '@atlaskit/platform-feature-flags/fg';
14
15
  import { getText } from '../utils';
15
16
  import { isAnnotationMark, toReact as markToReact } from './marks';
@@ -18,6 +19,7 @@ import { getNestedUnderNodes, insideBlockNode, insideMultiBodiedExtension, insid
18
19
  import { renderTextSegments } from './utils/render-text-segments';
19
20
  import { segmentText } from './utils/segment-text';
20
21
  import { getStandaloneBackgroundColorMarks } from './utils/getStandaloneBackgroundColorMarks';
22
+ import { createContentGetter } from './utils/content-getter';
21
23
  import { markBlockAsInline } from './utils/markBlockAsInline';
22
24
  function mergeMarks(marksAndNodes) {
23
25
  return marksAndNodes.reduce((acc, markOrNode) => {
@@ -595,7 +597,17 @@ export default class ReactSerializer {
595
597
  };
596
598
  }
597
599
  getProps(node, path = []) {
600
+ var _node$content;
598
601
  const startPos = this.startPos + path.length;
602
+
603
+ // `content` is a recursive Fragment.toJSON() attached to every node, so the document is
604
+ // re-serialized once per ancestor — O(depth x size) overall, and a table paid it again for
605
+ // every row and cell. Consumers read `getContent()` instead, which serializes at most once.
606
+ // Experiment call last, so exposure only fires for nodes that have content to serialize.
607
+ const skipEagerContent = !!((_node$content = node.content) !== null && _node$content !== void 0 && _node$content.childCount) && isExperimentEnabled('platform_editor_renderer_defer_content');
608
+
609
+ // Shared so the `content` prop and `getContent()` never serialize the subtree twice.
610
+ const getContent = createContentGetter(node.content);
599
611
  return {
600
612
  asInline: this.inlinePositions.has(startPos) ? 'on' : undefined,
601
613
  text: node.text,
@@ -605,7 +617,8 @@ export default class ReactSerializer {
605
617
  portal: this.portal,
606
618
  rendererContext: this.rendererContext,
607
619
  serializer: this,
608
- content: node.content ? node.content.toJSON() : undefined,
620
+ content: skipEagerContent ? undefined : getContent(),
621
+ getContent,
609
622
  allowHeadingAnchorLinks: this.allowHeadingAnchorLinks,
610
623
  allowCopyToClipboard: this.allowCopyToClipboard,
611
624
  allowDownloadCodeBlock: this.allowDownloadCodeBlock,
@@ -637,7 +650,6 @@ export default class ReactSerializer {
637
650
  getHeadingProps(node, path = []) {
638
651
  return {
639
652
  ...this.getProps(node, path),
640
- content: node.content ? node.content.toJSON() : undefined,
641
653
  headingId: this.getHeadingId(node, this.headingIds),
642
654
  showAnchorLink: this.appearance !== 'comment' && this.allowHeadingAnchorLinks && !this.disableHeadingIDs && this.headingAnchorSupported(path)
643
655
  };
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
3
  export default function LayoutSection(props) {
4
- var _props$content;
4
+ var _ref, _props$content, _props$getContent;
5
+ const columnCount = (_ref = (_props$content = props.content) !== null && _props$content !== void 0 ? _props$content : (_props$getContent = props.getContent) === null || _props$getContent === void 0 ? void 0 : _props$getContent.call(props)) === null || _ref === void 0 ? void 0 : _ref.length;
5
6
  return editorExperiment('advanced_layouts', true) ?
6
7
  /*#__PURE__*/
7
8
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
@@ -10,7 +11,7 @@ export default function LayoutSection(props) {
10
11
  "data-local-id": props.localId
11
12
  }, /*#__PURE__*/React.createElement("div", {
12
13
  "data-layout-section": true,
13
- "data-layout-columns": props === null || props === void 0 ? void 0 : (_props$content = props.content) === null || _props$content === void 0 ? void 0 : _props$content.length
14
+ "data-layout-columns": columnCount
14
15
  }, props.children)) : /*#__PURE__*/React.createElement("div", {
15
16
  "data-layout-section": true,
16
17
  "data-local-id": props.localId
@@ -111,6 +111,7 @@ const MultiBodiedExtension = props => {
111
111
  extensionKey,
112
112
  extensionHandlers,
113
113
  content,
114
+ getContent,
114
115
  marks,
115
116
  localId,
116
117
  rendererAppearance
@@ -151,7 +152,7 @@ const MultiBodiedExtension = props => {
151
152
  extensionKey,
152
153
  extensionType,
153
154
  parameters,
154
- content,
155
+ content: content !== null && content !== void 0 ? content : getContent === null || getContent === void 0 ? void 0 : getContent(),
155
156
  localId,
156
157
  fragmentLocalId
157
158
  };
@@ -166,7 +167,7 @@ const MultiBodiedExtension = props => {
166
167
  actions: actions
167
168
  })), jsx(MultiBodiedExtensionChildrenContainer, null, children));
168
169
  }
169
- }, [loading, extensionContext, marks, extensionKey, extensionType, parameters, content, localId, actions, children]);
170
+ }, [loading, extensionContext, marks, extensionKey, extensionType, parameters, content, getContent, localId, actions, children]);
170
171
 
171
172
  // make the frame visible
172
173
  // eslint-disable-next-line @atlaskit/design-system/no-css-tagged-template-expression
@@ -5,11 +5,14 @@ import { getOrderedListInlineStyles } from '@atlaskit/editor-common/styles';
5
5
  import { getItemCounterDigitsSize, resolveOrder } from '@atlaskit/editor-common/utils';
6
6
  import { getListIndentLevel } from '../utils/lists';
7
7
  export default function OrderedList(props) {
8
- var _props$content;
8
+ var _ref, _props$content, _props$getContent;
9
9
  const extraProps = {};
10
+
11
+ // Item count drives the marker column width, so losing it narrows the gutter by a digit.
12
+ const itemsCount = (_ref = (_props$content = props.content) !== null && _props$content !== void 0 ? _props$content : (_props$getContent = props.getContent) === null || _props$getContent === void 0 ? void 0 : _props$getContent.call(props)) === null || _ref === void 0 ? void 0 : _ref.length;
10
13
  const itemCounterDigitsSize = getItemCounterDigitsSize({
11
14
  order: props.order,
12
- itemsCount: props === null || props === void 0 ? void 0 : (_props$content = props.content) === null || _props$content === void 0 ? void 0 : _props$content.length
15
+ itemsCount
13
16
  });
14
17
  if (itemCounterDigitsSize && itemCounterDigitsSize > 1) {
15
18
  extraProps.style = getOrderedListInlineStyles(itemCounterDigitsSize, 'object');
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+
2
3
  // For sync block to work, we are passing in a custom syncBlock renderer through nodeComponents
3
4
  // However, we need define a basic component to render the sync block
4
5
  export default function SyncBlock(props) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Builds the `getContent` node prop: `fragment.toJSON()` computed on first call and memoized for
3
+ * the lifetime of the props object it is attached to.
4
+ *
5
+ * A prop getter cannot achieve the same deferral: `getProps`' callers spread its result and React
6
+ * copies props again on element creation, so a getter would run before any component reads it.
7
+ */
8
+ export const createContentGetter = fragment => {
9
+ let serialized;
10
+ let materialized = false;
11
+ return () => {
12
+ if (!materialized) {
13
+ serialized = fragment ? fragment.toJSON() : undefined;
14
+ // Set last: if `toJSON()` throws, the error propagates and a retry re-attempts instead of
15
+ // caching a partial result.
16
+ materialized = true;
17
+ }
18
+ return serialized;
19
+ };
20
+ };
@@ -44,6 +44,7 @@ export default function ExtensionRenderer(props) {
44
44
  extensionKey,
45
45
  parameters,
46
46
  content,
47
+ getContent,
47
48
  text,
48
49
  type,
49
50
  localId,
@@ -74,12 +75,16 @@ export default function ExtensionRenderer(props) {
74
75
  const renderExtensionNode = React.useCallback((extensionProvider, isExtensionProviderPending) => {
75
76
  var _marks$find, _marks$find$attrs, _node$parameters, _node$parameters$macr;
76
77
  const fragmentLocalId = marks === null || marks === void 0 ? void 0 : (_marks$find = marks.find(m => m.type.name === 'fragment')) === null || _marks$find === void 0 ? void 0 : (_marks$find$attrs = _marks$find.attrs) === null || _marks$find$attrs === void 0 ? void 0 : _marks$find$attrs.localId;
78
+
79
+ // Extension handlers read the macro body off `node.content`, so this is the one place that
80
+ // always has to serialize. `getContent` is what makes it a single pass instead of one per
81
+ // ancestor node.
77
82
  const node = {
78
83
  type,
79
84
  extensionKey,
80
85
  extensionType,
81
86
  parameters,
82
- content: content || text,
87
+ content: (content !== null && content !== void 0 ? content : getContent === null || getContent === void 0 ? void 0 : getContent()) || text,
83
88
  localId,
84
89
  fragmentLocalId
85
90
  };
@@ -118,7 +123,7 @@ export default function ExtensionRenderer(props) {
118
123
  node,
119
124
  result
120
125
  });
121
- }, [actions, children, content, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
126
+ }, [actions, children, content, getContent, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
122
127
  const setupAndRenderExtensionNode = React.useCallback(providers => {
123
128
  if (extensionProvider === null && providers.extensionProvider) {
124
129
  handleProvider('extensionProvider', providers.extensionProvider);
@@ -60,7 +60,7 @@ export const DEGRADED_SEVERITY_THRESHOLD = 3000;
60
60
  const TABLE_INFO_TIMEOUT = 10000;
61
61
  const RENDER_EVENT_SAMPLE_RATE = 0.1;
62
62
  const packageName = "@atlaskit/renderer";
63
- const packageVersion = "136.2.7";
63
+ const packageVersion = "137.1.0";
64
64
  const setAsQueryContainerStyles = css({
65
65
  containerName: 'ak-renderer-wrapper',
66
66
  containerType: 'inline-size'
@@ -17,6 +17,7 @@ import { isNestedHeaderLinksEnabled } from './utils/links';
17
17
  import { getColumnWidths } from '@atlaskit/editor-common/utils';
18
18
  import { getMarksByOrder, isSameMark } from '@atlaskit/editor-common/validator';
19
19
  import { findChildrenByMark, findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
20
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
20
21
  import { fg } from '@atlaskit/platform-feature-flags/fg';
21
22
  import { getText } from '../utils';
22
23
  import { isAnnotationMark, toReact as markToReact } from './marks';
@@ -25,6 +26,7 @@ import { getNestedUnderNodes, insideBlockNode, insideMultiBodiedExtension, insid
25
26
  import { renderTextSegments } from './utils/render-text-segments';
26
27
  import { segmentText } from './utils/segment-text';
27
28
  import { getStandaloneBackgroundColorMarks } from './utils/getStandaloneBackgroundColorMarks';
29
+ import { createContentGetter } from './utils/content-getter';
28
30
  import { markBlockAsInline } from './utils/markBlockAsInline';
29
31
  function mergeMarks(marksAndNodes) {
30
32
  return marksAndNodes.reduce(function (acc, markOrNode) {
@@ -660,8 +662,18 @@ var ReactSerializer = /*#__PURE__*/function () {
660
662
  }, {
661
663
  key: "getProps",
662
664
  value: function getProps(node) {
665
+ var _node$content;
663
666
  var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
664
667
  var startPos = this.startPos + path.length;
668
+
669
+ // `content` is a recursive Fragment.toJSON() attached to every node, so the document is
670
+ // re-serialized once per ancestor — O(depth x size) overall, and a table paid it again for
671
+ // every row and cell. Consumers read `getContent()` instead, which serializes at most once.
672
+ // Experiment call last, so exposure only fires for nodes that have content to serialize.
673
+ var skipEagerContent = !!((_node$content = node.content) !== null && _node$content !== void 0 && _node$content.childCount) && isExperimentEnabled('platform_editor_renderer_defer_content');
674
+
675
+ // Shared so the `content` prop and `getContent()` never serialize the subtree twice.
676
+ var getContent = createContentGetter(node.content);
665
677
  return _objectSpread({
666
678
  asInline: this.inlinePositions.has(startPos) ? 'on' : undefined,
667
679
  text: node.text,
@@ -671,7 +683,8 @@ var ReactSerializer = /*#__PURE__*/function () {
671
683
  portal: this.portal,
672
684
  rendererContext: this.rendererContext,
673
685
  serializer: this,
674
- content: node.content ? node.content.toJSON() : undefined,
686
+ content: skipEagerContent ? undefined : getContent(),
687
+ getContent: getContent,
675
688
  allowHeadingAnchorLinks: this.allowHeadingAnchorLinks,
676
689
  allowCopyToClipboard: this.allowCopyToClipboard,
677
690
  allowDownloadCodeBlock: this.allowDownloadCodeBlock,
@@ -707,7 +720,6 @@ var ReactSerializer = /*#__PURE__*/function () {
707
720
  value: function getHeadingProps(node) {
708
721
  var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
709
722
  return _objectSpread(_objectSpread({}, this.getProps(node, path)), {}, {
710
- content: node.content ? node.content.toJSON() : undefined,
711
723
  headingId: this.getHeadingId(node, this.headingIds),
712
724
  showAnchorLink: this.appearance !== 'comment' && this.allowHeadingAnchorLinks && !this.disableHeadingIDs && this.headingAnchorSupported(path)
713
725
  });
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
3
  export default function LayoutSection(props) {
4
- var _props$content;
4
+ var _ref, _props$content, _props$getContent;
5
+ var columnCount = (_ref = (_props$content = props.content) !== null && _props$content !== void 0 ? _props$content : (_props$getContent = props.getContent) === null || _props$getContent === void 0 ? void 0 : _props$getContent.call(props)) === null || _ref === void 0 ? void 0 : _ref.length;
5
6
  return editorExperiment('advanced_layouts', true) ?
6
7
  /*#__PURE__*/
7
8
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
@@ -10,7 +11,7 @@ export default function LayoutSection(props) {
10
11
  "data-local-id": props.localId
11
12
  }, /*#__PURE__*/React.createElement("div", {
12
13
  "data-layout-section": true,
13
- "data-layout-columns": props === null || props === void 0 || (_props$content = props.content) === null || _props$content === void 0 ? void 0 : _props$content.length
14
+ "data-layout-columns": columnCount
14
15
  }, props.children)) : /*#__PURE__*/React.createElement("div", {
15
16
  "data-layout-section": true,
16
17
  "data-local-id": props.localId
@@ -111,6 +111,7 @@ var MultiBodiedExtension = function MultiBodiedExtension(props) {
111
111
  extensionKey = props.extensionKey,
112
112
  extensionHandlers = props.extensionHandlers,
113
113
  content = props.content,
114
+ getContent = props.getContent,
114
115
  marks = props.marks,
115
116
  localId = props.localId,
116
117
  rendererAppearance = props.rendererAppearance;
@@ -151,7 +152,7 @@ var MultiBodiedExtension = function MultiBodiedExtension(props) {
151
152
  extensionKey: extensionKey,
152
153
  extensionType: extensionType,
153
154
  parameters: parameters,
154
- content: content,
155
+ content: content !== null && content !== void 0 ? content : getContent === null || getContent === void 0 ? void 0 : getContent(),
155
156
  localId: localId,
156
157
  fragmentLocalId: fragmentLocalId
157
158
  };
@@ -166,7 +167,7 @@ var MultiBodiedExtension = function MultiBodiedExtension(props) {
166
167
  actions: actions
167
168
  })), jsx(MultiBodiedExtensionChildrenContainer, null, children));
168
169
  }
169
- }, [loading, extensionContext, marks, extensionKey, extensionType, parameters, content, localId, actions, children]);
170
+ }, [loading, extensionContext, marks, extensionKey, extensionType, parameters, content, getContent, localId, actions, children]);
170
171
 
171
172
  // make the frame visible
172
173
  // eslint-disable-next-line @atlaskit/design-system/no-css-tagged-template-expression
@@ -5,11 +5,14 @@ import { getOrderedListInlineStyles } from '@atlaskit/editor-common/styles';
5
5
  import { getItemCounterDigitsSize, resolveOrder } from '@atlaskit/editor-common/utils';
6
6
  import { getListIndentLevel } from '../utils/lists';
7
7
  export default function OrderedList(props) {
8
- var _props$content;
8
+ var _ref, _props$content, _props$getContent;
9
9
  var extraProps = {};
10
+
11
+ // Item count drives the marker column width, so losing it narrows the gutter by a digit.
12
+ var itemsCount = (_ref = (_props$content = props.content) !== null && _props$content !== void 0 ? _props$content : (_props$getContent = props.getContent) === null || _props$getContent === void 0 ? void 0 : _props$getContent.call(props)) === null || _ref === void 0 ? void 0 : _ref.length;
10
13
  var itemCounterDigitsSize = getItemCounterDigitsSize({
11
14
  order: props.order,
12
- itemsCount: props === null || props === void 0 || (_props$content = props.content) === null || _props$content === void 0 ? void 0 : _props$content.length
15
+ itemsCount: itemsCount
13
16
  });
14
17
  if (itemCounterDigitsSize && itemCounterDigitsSize > 1) {
15
18
  extraProps.style = getOrderedListInlineStyles(itemCounterDigitsSize, 'object');
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+
2
3
  // For sync block to work, we are passing in a custom syncBlock renderer through nodeComponents
3
4
  // However, we need define a basic component to render the sync block
4
5
  export default function SyncBlock(props) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Builds the `getContent` node prop: `fragment.toJSON()` computed on first call and memoized for
3
+ * the lifetime of the props object it is attached to.
4
+ *
5
+ * A prop getter cannot achieve the same deferral: `getProps`' callers spread its result and React
6
+ * copies props again on element creation, so a getter would run before any component reads it.
7
+ */
8
+ export var createContentGetter = function createContentGetter(fragment) {
9
+ var serialized;
10
+ var materialized = false;
11
+ return function () {
12
+ if (!materialized) {
13
+ serialized = fragment ? fragment.toJSON() : undefined;
14
+ // Set last: if `toJSON()` throws, the error propagates and a retry re-attempts instead of
15
+ // caching a partial result.
16
+ materialized = true;
17
+ }
18
+ return serialized;
19
+ };
20
+ };
@@ -44,6 +44,7 @@ export default function ExtensionRenderer(props) {
44
44
  extensionKey = props.extensionKey,
45
45
  parameters = props.parameters,
46
46
  content = props.content,
47
+ getContent = props.getContent,
47
48
  text = props.text,
48
49
  type = props.type,
49
50
  localId = props.localId,
@@ -80,12 +81,16 @@ export default function ExtensionRenderer(props) {
80
81
  var fragmentLocalId = marks === null || marks === void 0 || (_marks$find = marks.find(function (m) {
81
82
  return m.type.name === 'fragment';
82
83
  })) === null || _marks$find === void 0 || (_marks$find = _marks$find.attrs) === null || _marks$find === void 0 ? void 0 : _marks$find.localId;
84
+
85
+ // Extension handlers read the macro body off `node.content`, so this is the one place that
86
+ // always has to serialize. `getContent` is what makes it a single pass instead of one per
87
+ // ancestor node.
83
88
  var node = {
84
89
  type: type,
85
90
  extensionKey: extensionKey,
86
91
  extensionType: extensionType,
87
92
  parameters: parameters,
88
- content: content || text,
93
+ content: (content !== null && content !== void 0 ? content : getContent === null || getContent === void 0 ? void 0 : getContent()) || text,
89
94
  localId: localId,
90
95
  fragmentLocalId: fragmentLocalId
91
96
  };
@@ -124,7 +129,7 @@ export default function ExtensionRenderer(props) {
124
129
  node: node,
125
130
  result: result
126
131
  });
127
- }, [actions, children, content, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
132
+ }, [actions, children, content, getContent, extensionHandlers, extensionKey, extensionType, localGetNodeRenderer, localId, marks, parameters, rendererContext === null || rendererContext === void 0 ? void 0 : rendererContext.adDoc, text, type]);
128
133
  var setupAndRenderExtensionNode = React.useCallback(function (providers) {
129
134
  if (extensionProvider === null && providers.extensionProvider) {
130
135
  handleProvider('extensionProvider', providers.extensionProvider);
@@ -65,7 +65,7 @@ export var DEGRADED_SEVERITY_THRESHOLD = 3000;
65
65
  var TABLE_INFO_TIMEOUT = 10000;
66
66
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
67
67
  var packageName = "@atlaskit/renderer";
68
- var packageVersion = "136.2.7";
68
+ var packageVersion = "137.1.0";
69
69
  var setAsQueryContainerStyles = css({
70
70
  containerName: 'ak-renderer-wrapper',
71
71
  containerType: 'inline-size'
@@ -1 +1 @@
1
- export type { AnnotationMarkMeta, ExtensionViewportSize, MarkMeta, MarkProps, NodeMeta, NodeProps, RendererContext, TextHighlighter, } from '../react/types';
1
+ export type { AnnotationMarkMeta, ExtensionViewportSize, MarkMeta, MarkProps, NodeContent, NodeMeta, NodeProps, RendererContext, TextHighlighter, } from '../react/types';
@@ -14,6 +14,7 @@ interface Props {
14
14
  extensionType: string;
15
15
  extensionViewportSizes?: ExtensionViewportSize[];
16
16
  fireAnalyticsEvent?: (event: AnalyticsEventPayload) => void;
17
+ getContent?: () => any;
17
18
  layout?: ExtensionLayout;
18
19
  localId?: string;
19
20
  marks?: PMMark[];
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
- import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ import type { NodeContent } from '../types';
3
3
  export default function LayoutSection(props: React.PropsWithChildren<{
4
- content?: PMNode[];
4
+ content?: NodeContent;
5
+ getContent?: () => NodeContent | undefined;
5
6
  localId?: string;
6
7
  }>): React.JSX.Element;
@@ -16,6 +16,7 @@ type Props = React.PropsWithChildren<{
16
16
  extensionHandlers?: ExtensionHandlers;
17
17
  extensionKey: string;
18
18
  extensionType: string;
19
+ getContent?: () => any;
19
20
  layout?: ExtensionLayout;
20
21
  localId?: string;
21
22
  marks?: PMMark[];
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
2
  import type { Node } from '@atlaskit/editor-prosemirror/model';
3
+ import type { NodeContent } from '../types';
3
4
  export default function OrderedList(props: {
4
5
  children: React.ReactNode;
5
- content?: Node[];
6
+ content?: NodeContent;
7
+ getContent?: () => NodeContent | undefined;
6
8
  localId?: string;
7
9
  order?: number;
8
10
  path?: Node[];
@@ -1,7 +1,5 @@
1
1
  import React from 'react';
2
- import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
2
  export default function SyncBlock(props: React.PropsWithChildren<{
4
- content?: PMNode[];
5
3
  localId?: string;
6
4
  resourceId?: string;
7
5
  }>): React.JSX.Element;
@@ -14,6 +14,14 @@ export interface RendererContext {
14
14
  objectAri?: string;
15
15
  schema?: Schema;
16
16
  }
17
+ /**
18
+ * `Fragment.toJSON()`: the node's children as plain ADF JSON objects, or `null` for an empty
19
+ * fragment. Not ProseMirror `Node` instances, so model APIs such as `nodeSize` or `childCount`
20
+ * are unavailable.
21
+ */
22
+ export type NodeContent = Array<{
23
+ [key: string]: any;
24
+ }> | null;
17
25
  export interface NodeMeta {
18
26
  [key: string]: any;
19
27
  allowCopyToClipboard?: boolean;
@@ -23,15 +31,30 @@ export interface NodeMeta {
23
31
  allowPlaceholderText?: boolean;
24
32
  allowWrapCodeBlock?: boolean;
25
33
  asInline?: 'on' | undefined;
26
- content?: {
27
- [key: string]: any;
28
- } | null;
34
+ /**
35
+ * Superseded by `getContent()`, and removed once `platform_editor_renderer_defer_content` ships.
36
+ * A recursive `Fragment.toJSON()` of the node's subtree, serialized on every render whether or
37
+ * not anything reads it — so the document was re-serialized once per ancestor node. While that
38
+ * experiment is enabled it is not provided on any node that has children; a childless node keeps
39
+ * the `null` both arms produce. Read `content ?? getContent?.()` so both arms are covered.
40
+ */
41
+ content?: NodeContent;
29
42
  dataAttributes: {
30
43
  'data-renderer-start-pos': number;
31
44
  };
32
45
  eventHandlers?: EventHandlers | undefined;
33
46
  extensionHandlers?: ExtensionHandlers | undefined;
34
47
  fireAnalyticsEvent?: (event: AnalyticsEventPayload) => void;
48
+ /**
49
+ * Serialized JSON of the node's content — the replacement for the `content` prop. Computed on
50
+ * first call and memoized, so a node whose content nothing reads costs nothing.
51
+ *
52
+ * `ReactSerializer` attaches this to every node it builds props for, in both arms of
53
+ * `platform_editor_renderer_defer_content`. Read `content ?? getContent?.()`: with the experiment
54
+ * off `content` is already serialized and calling this adds nothing, and with it on `content` is
55
+ * the operand that is absent — so both sides of that expression stay live until `content` goes.
56
+ */
57
+ getContent?: () => NodeContent | undefined;
35
58
  hideExtensionKeysWhilePending?: string[];
36
59
  marks: PMNode['marks'];
37
60
  nodeType: NodeType['name'];
@@ -0,0 +1,10 @@
1
+ import type { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import type { NodeContent } from '../types';
3
+ /**
4
+ * Builds the `getContent` node prop: `fragment.toJSON()` computed on first call and memoized for
5
+ * the lifetime of the props object it is attached to.
6
+ *
7
+ * A prop getter cannot achieve the same deferral: `getProps`' callers spread its result and React
8
+ * copies props again on element creation, so a getter would run before any component reads it.
9
+ */
10
+ export declare const createContentGetter: (fragment: Fragment | undefined) => (() => NodeContent | undefined);
@@ -16,10 +16,15 @@ interface Props {
16
16
  node: ExtensionParams<Parameters>;
17
17
  result?: JSX.Element | null;
18
18
  }) => JSX.Element;
19
+ /**
20
+ * Read as `content ?? getContent?.()` — absent on nodes with children once the serializer defers
21
+ * serialization, and the already-serialized value otherwise.
22
+ */
19
23
  content?: any;
20
24
  extensionHandlers?: ExtensionHandlers;
21
25
  extensionKey: string;
22
26
  extensionType: string;
27
+ getContent?: () => any;
23
28
  layout?: ExtensionLayout;
24
29
  localId?: string;
25
30
  marks?: PMMark[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/renderer",
3
- "version": "137.0.0",
3
+ "version": "137.1.1",
4
4
  "description": "Renderer component",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -70,7 +70,7 @@
70
70
  "@atlaskit/status": "^5.8.0",
71
71
  "@atlaskit/task-decision": "^21.8.0",
72
72
  "@atlaskit/theme": "^28.1.0",
73
- "@atlaskit/tmp-editor-statsig": "^160.0.0",
73
+ "@atlaskit/tmp-editor-statsig": "^161.0.0",
74
74
  "@atlaskit/tokens": "^16.8.0",
75
75
  "@atlaskit/tooltip": "^24.2.0",
76
76
  "@atlaskit/visually-hidden": "^4.3.0",