@atlaskit/renderer 137.1.7 → 137.1.9

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.
@@ -0,0 +1,198 @@
1
+ import React, { createContext, isValidElement, useContext } from 'react';
2
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
+ import { BLOCK_SEPARATOR, getBlockSearchText, getTableStandInParts, holdsRevealableContent, isExpandNode } from './expand-search-text';
4
+ const ExpandBodyContext = /*#__PURE__*/createContext(null);
5
+ export const ExpandBodyProvider = ExpandBodyContext.Provider;
6
+ export const useExpandBody = () => useContext(ExpandBodyContext);
7
+ /**
8
+ * One or more neighbouring blocks of an expand's body. Shows their text until the expand is
9
+ * opened, then renders them. Falls back to rendering if there is no text, or if there is no
10
+ * expand above it.
11
+ *
12
+ * The text is rendered as-is, with no element around it. Nothing reads it but browser find,
13
+ * which only needs the characters to be in the DOM.
14
+ */
15
+ export const ExpandBodyBlock = ({
16
+ children,
17
+ searchText
18
+ }) => {
19
+ const body = useExpandBody();
20
+ if (searchText === undefined || body === null || body.revealed) {
21
+ return /*#__PURE__*/React.createElement(React.Fragment, null, children);
22
+ }
23
+ return searchText;
24
+ };
25
+
26
+ /**
27
+ * Called for every node the serializer renders. If the node is a block of an expand's body,
28
+ * wraps it so it can show its text instead of rendering while that expand is collapsed.
29
+ * Everything else is returned untouched.
30
+ *
31
+ * A block holding an expand of its own is returned untouched, because standing in for the whole
32
+ * block would take that expand with it, and the expand needs an element of its own: the element
33
+ * browser find reveals is the only way we learn the match was inside it rather than higher up. A
34
+ * table is handed to `ExpandBodyTable`, which stands in for its own rows and keeps only the rows
35
+ * that hold an expand. Everything else — a panel, a list, an extension with stashed ADF — renders
36
+ * in full, so the reader searches once instead of once per level.
37
+ *
38
+ * `ancestors` is the node's ancestor chain, nearest last.
39
+ */
40
+ export const withExpandBodyBlock = (node, ancestors, index, serialized) => {
41
+ const parent = ancestors[ancestors.length - 1];
42
+ if (!parent || !isExpandNode(parent)) {
43
+ return serialized;
44
+ }
45
+ if (!isExperimentEnabled('platform_editor_defer_collapsed_expand_body')) {
46
+ return serialized;
47
+ }
48
+
49
+ // `serialized` is only null for a node the renderer has no component for, which a table is not.
50
+ // Such a table falls through to the paths below, which do not reach into it.
51
+ if (node.type.name === 'table' && serialized !== null) {
52
+ const parts = getTableStandInParts(node);
53
+
54
+ // With no row to keep, one string can stand in for the whole table — the table then renders
55
+ // nothing at all, and its text joins with the blocks either side of it.
56
+ return parts.every(part => typeof part === 'string') ? /*#__PURE__*/React.createElement(ExpandBodyBlock, {
57
+ key: `expand-body-block-${index}`,
58
+ searchText: parts.join('')
59
+ }, serialized) : /*#__PURE__*/React.createElement(ExpandBodyTable, {
60
+ key: `expand-body-table-${index}`,
61
+ node: node
62
+ }, serialized);
63
+ }
64
+ if (holdsRevealableContent(node)) {
65
+ return serialized;
66
+ }
67
+ const searchText = getBlockSearchText(node);
68
+ if (searchText === undefined) {
69
+ return serialized;
70
+ }
71
+ return /*#__PURE__*/React.createElement(ExpandBodyBlock, {
72
+ key: `expand-body-block-${index}`,
73
+ searchText: searchText
74
+ }, serialized);
75
+ };
76
+ const isRow = child => /*#__PURE__*/isValidElement(child) && child.props.nodeType === 'tableRow';
77
+
78
+ /**
79
+ * The rows inside the serialized table, wherever they sit.
80
+ *
81
+ * What the serializer hands over is not reliably the element whose children are the rows. Marks are
82
+ * folded around the node afterwards, and a product's own serializer may wrap the rows themselves —
83
+ * Confluence's progressive renderer wraps every row but the first. Assuming either a depth or a set
84
+ * of direct children means any wrapper added later silently costs the saving, so the rows are found
85
+ * instead by the `nodeType` the serializer puts on every node.
86
+ *
87
+ * Descent stops at each row: a nested table's rows sit inside a row of this one, and would
88
+ * otherwise be counted among them.
89
+ *
90
+ * The count is only a consistency check. `getTableStandInParts` indexes the rows by position, so a
91
+ * partial match cannot be lined up and the table has to render instead.
92
+ */
93
+ const rowsOf = (serialized, rowCount) => {
94
+ const rows = [];
95
+ const collect = node => {
96
+ React.Children.toArray(node).forEach(child => {
97
+ if (! /*#__PURE__*/isValidElement(child)) {
98
+ return;
99
+ }
100
+ if (isRow(child)) {
101
+ rows.push(child);
102
+ return;
103
+ }
104
+ collect(child.props.children);
105
+ });
106
+ };
107
+ collect(serialized);
108
+ return rows.length === rowCount ? rows : undefined;
109
+ };
110
+
111
+ /**
112
+ * A table in the body of a collapsed expand. Shows the text of its rows until the expand is opened,
113
+ * keeping the rows that hold an expand of their own — those need an element for find to reveal.
114
+ *
115
+ * Standing in for the whole table, the way an ordinary block does, would take that expand with it.
116
+ */
117
+ export const ExpandBodyTable = ({
118
+ children,
119
+ node
120
+ }) => {
121
+ const body = useExpandBody();
122
+ if (body === null || body.revealed) {
123
+ return children;
124
+ }
125
+ const rows = rowsOf(children, node.childCount);
126
+ if (!rows) {
127
+ // Nothing is wrong: the table renders as it always did, only without the saving. Still worth
128
+ // saying, because a silent fallback looks exactly like the feature being switched off.
129
+ // Every environment but production says it: a wrapper added by a product is only ever seen
130
+ // once that product has deployed, which a `NODE_ENV` check never reaches.
131
+ if (process.env.CLOUD_ENV !== 'production') {
132
+ // eslint-disable-next-line no-console
133
+ console.info('@atlaskit/renderer: the rows of a table in a collapsed expand were not found, so the table renders in full rather than showing their text');
134
+ }
135
+ return children;
136
+ }
137
+ return getTableStandInParts(node).map(part => typeof part === 'string' ? part :
138
+ /*#__PURE__*/
139
+ // A row cannot stand in the page on its own — `<tr>` is only valid inside a table — so it
140
+ // keeps the least table around it that makes the markup valid. None of this is laid out: the
141
+ // body is hidden until the reader or find opens the expand, and opening it renders the real
142
+ // table in place of all this.
143
+ React.createElement("table", {
144
+ key: `expand-body-row-${part}`
145
+ }, /*#__PURE__*/React.createElement("tbody", null, rows[part])));
146
+ };
147
+
148
+ /** Whatever the serializer produced for one child: an element, a string, an array of either. */
149
+
150
+ const searchTextOf = child => /*#__PURE__*/isValidElement(child) && child.type === ExpandBodyBlock ? child.props.searchText : undefined;
151
+
152
+ /**
153
+ * Joins neighbouring blocks that are showing text into one, so a run of them is a single string in
154
+ * the DOM rather than one per block. A body of twenty paragraphs becomes one text node instead of
155
+ * twenty.
156
+ *
157
+ * A block that is being rendered — a nested expand, say — ends the run, because the text either
158
+ * side of it has to stay either side of it.
159
+ */
160
+ export const mergeExpandBodyText = children => {
161
+ // Called for every fragment in the document, and only an expand's body has anything to merge, so
162
+ // leave the array alone unless two neighbours are actually showing text.
163
+ const hasRun = children.some((child, index) => index > 0 && searchTextOf(child) !== undefined && searchTextOf(children[index - 1]) !== undefined);
164
+ if (!hasRun) {
165
+ return children;
166
+ }
167
+ const merged = [];
168
+ let run = [];
169
+ const endRun = () => {
170
+ if (run.length === 0) {
171
+ return;
172
+ }
173
+ if (run.length === 1) {
174
+ merged.push(run[0]);
175
+ } else {
176
+ var _key;
177
+ const text = run.map(searchTextOf).filter(Boolean).join(BLOCK_SEPARATOR);
178
+ // The blocks themselves, not the wrappers around them: nesting the wrappers would show
179
+ // each block's text again inside the joined run.
180
+ const blocks = run.map(child => child.props.children);
181
+ merged.push( /*#__PURE__*/React.createElement(ExpandBodyBlock, {
182
+ key: (_key = run[0].key) !== null && _key !== void 0 ? _key : undefined,
183
+ searchText: text
184
+ }, blocks));
185
+ }
186
+ run = [];
187
+ };
188
+ children.forEach(child => {
189
+ if (searchTextOf(child) === undefined) {
190
+ endRun();
191
+ merged.push(child);
192
+ return;
193
+ }
194
+ run.push(child);
195
+ });
196
+ endRun();
197
+ return merged;
198
+ };
@@ -117,4 +117,42 @@ export const getBlockSearchText = node => {
117
117
  searchTextCache.set(node, cached);
118
118
  }
119
119
  return (_cached = cached) !== null && _cached !== void 0 ? _cached : undefined;
120
+ };
121
+
122
+ /**
123
+ * One piece of what a collapsed table shows: either the text of the rows being stood in for, or the
124
+ * index of a row that has to keep rendering.
125
+ */
126
+
127
+ const tableStandInCache = new WeakMap();
128
+
129
+ /**
130
+ * What a table inside a collapsed expand shows in place of rendering, in table order so that find
131
+ * walks it the way the reader reads it.
132
+ *
133
+ * A row keeps rendering when it holds an expand of its own, which needs a real element for browser
134
+ * find to reveal, or when its text carries an inline comment. Its text is left out of the runs
135
+ * either side of it, since the row itself puts that text in the DOM.
136
+ */
137
+ export const getTableStandInParts = table => {
138
+ const cached = tableStandInCache.get(table);
139
+ if (cached !== undefined) {
140
+ return cached;
141
+ }
142
+ const parts = [];
143
+ table.forEach((row, _offset, index) => {
144
+ const text = holdsRevealableContent(row) ? undefined : getBlockSearchText(row);
145
+ if (text === undefined) {
146
+ parts.push(index);
147
+ return;
148
+ }
149
+ const last = parts[parts.length - 1];
150
+ if (typeof last === 'string') {
151
+ parts[parts.length - 1] = `${last}${BLOCK_SEPARATOR}${text}`;
152
+ return;
153
+ }
154
+ parts.push(text);
155
+ });
156
+ tableStandInCache.set(table, parts);
157
+ return parts;
120
158
  };
@@ -11,7 +11,7 @@ import React from 'react';
11
11
  import { MarkType } from '@atlaskit/editor-prosemirror/model';
12
12
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
13
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
14
- import { mergeExpandBodyText, withExpandBodyBlock } from '../ui/expand-body';
14
+ import { mergeExpandBodyText, withExpandBodyBlock } from '../ui/utils/expand-body';
15
15
  import { Doc, DocWithSelectAllTrap, isTextNode, isTextWrapper, mergeTextNodes, toReact } from './nodes';
16
16
  import TextWrapperComponent from './nodes/text-wrapper';
17
17
  import { isNestedHeaderLinksEnabled } from './utils/links';
@@ -891,36 +891,6 @@ var addTableCellEdgePropsThroughWrappers = function addTableCellEdgePropsThrough
891
891
  return rows;
892
892
  }
893
893
  };
894
- var addTableCellEdgeProps = function addTableCellEdgeProps(rows, tableNode, isNumberColumnEnabled) {
895
- try {
896
- if (!tableNode) {
897
- return rows;
898
- }
899
- var cellEdgePropsByCellOffset = getCellEdgePropsByCellOffset(tableNode, isNumberColumnEnabled);
900
- var cellOffset = 0;
901
- return React.Children.map(rows, function (row, rowIndex) {
902
- var rowNode = tableNode.child(rowIndex);
903
- cellOffset += 1;
904
- var cellIndex = 0;
905
- var rowChildren = React.Children.map(row.props.children, function (child) {
906
- if (! /*#__PURE__*/React.isValidElement(child)) {
907
- return child;
908
- }
909
- var cellNode = rowNode.child(cellIndex);
910
- var edgeProps = cellEdgePropsByCellOffset.get(cellOffset);
911
- cellIndex += 1;
912
- cellOffset += cellNode.nodeSize;
913
- return edgeProps ? /*#__PURE__*/React.cloneElement(child, edgeProps) : child;
914
- });
915
- cellOffset += 1;
916
- return /*#__PURE__*/React.cloneElement(row, undefined, rowChildren);
917
- });
918
- } catch (_unused2) {
919
- // Renderer can receive malformed historical ADF. If the table shape cannot
920
- // be described safely, keep rendering without rounded edge metadata.
921
- return rows;
922
- }
923
- };
924
894
 
925
895
  /**
926
896
  * Processes table children before passing them to the styled table container.
@@ -1012,7 +982,7 @@ export var TableProcessorWithContainerStyles = /*#__PURE__*/function (_React$Com
1012
982
  return null;
1013
983
  }
1014
984
  var childrenArray = React.Children.toArray(children);
1015
- var childrenWithTableEdgeProps = expValEquals('platform_editor_table_q4_loveability', 'isEnabled', true) ? expValEquals('platform_editor_table_q4_patch_5', 'isEnabled', true) ? addTableCellEdgePropsThroughWrappers(childrenArray, tableNode, isNumberColumnEnabled) : addTableCellEdgeProps(childrenArray, tableNode, isNumberColumnEnabled) : childrenArray;
985
+ var childrenWithTableEdgeProps = expValEquals('platform_editor_table_q4_loveability', 'isEnabled', true) ? addTableCellEdgePropsThroughWrappers(childrenArray, tableNode, isNumberColumnEnabled) : childrenArray;
1016
986
  var orderedChildren = compose(this.addNumberColumnIndexes, this.addSortableColumn
1017
987
  // @ts-expect-error TS2345: Argument of type '(ReactChild | ReactFragment | ReactPortal)[]' is not assignable to parameter of type 'ReactElement<any, string | JSXElementConstructor<any>>[]'
1018
988
  )(childrenWithTableEdgeProps);
@@ -14,7 +14,7 @@ import { akEditorLineHeight, akEditorSwoopCubicBezier, akLayoutGutterOffset } fr
14
14
  import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
15
15
  import Tooltip from '@atlaskit/tooltip/Tooltip';
16
16
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
17
- import { ExpandBodyProvider, useExpandBody } from './expand-body';
17
+ import { ExpandBodyProvider, useExpandBody } from './utils/expand-body';
18
18
  import _uniqueId from 'lodash/uniqueId';
19
19
  import { injectIntl } from 'react-intl';
20
20
  import { MODE, PLATFORM } from '../analytics/events';
@@ -209,6 +209,7 @@ function fireExpandToggleAnalytics(nodeType, expanded, fireAnalyticsEvent) {
209
209
  });
210
210
  }
211
211
  function Expand(_ref) {
212
+ var _ancestorBody$reveale;
212
213
  var title = _ref.title,
213
214
  children = _ref.children,
214
215
  nodeType = _ref.nodeType,
@@ -219,7 +220,25 @@ function Expand(_ref) {
219
220
  rendererContentMode = _ref.rendererContentMode,
220
221
  node = _ref.node;
221
222
  var ancestorBody = useExpandBody();
222
- var _useState = useState(false),
223
+
224
+ // Shared with every expand in the chain: the element that stood in for this expand and caught the
225
+ // find sits under an expand further out, and by the time this one mounts that element is gone.
226
+ //
227
+ // Built on first render rather than passed to `useRef`, which would evaluate and discard a new set
228
+ // on every render for the life of the expand.
229
+ var ownRevealedByFind = useRef(null);
230
+ if (!ownRevealedByFind.current) {
231
+ ownRevealedByFind.current = new WeakSet();
232
+ }
233
+ var revealedByFind = (_ancestorBody$reveale = ancestorBody === null || ancestorBody === void 0 ? void 0 : ancestorBody.revealedByFind) !== null && _ancestorBody$reveale !== void 0 ? _ancestorBody$reveale : ownRevealedByFind.current;
234
+ /**
235
+ * Browser find matched inside this expand while the expand above it was still collapsed, so it
236
+ * opens as soon as it mounts — otherwise the reader would watch the outer expand open onto a
237
+ * closed one, with the match nowhere to be seen. Empty on the server and on the first client
238
+ * render, so hydration cannot disagree; only a find on the client ever puts anything in it.
239
+ */
240
+ var revealedByFindOnMount = node !== undefined && revealedByFind.has(node);
241
+ var _useState = useState(revealedByFindOnMount),
223
242
  _useState2 = _slicedToArray(_useState, 2),
224
243
  expanded = _useState2[0],
225
244
  setExpanded = _useState2[1];
@@ -232,9 +251,9 @@ function Expand(_ref) {
232
251
  * Throwing it away would re-run every macro and Forge fetch inside it on the next open, which is
233
252
  * slower and visibly reloads content the reader has already seen. We only wanted to save work on
234
253
  * the first page load, and by now that has happened. Starts false on both the server and the
235
- * client, so hydration cannot disagree.
254
+ * first client render, so hydration cannot disagree.
236
255
  */
237
- var _useState5 = useState(false),
256
+ var _useState5 = useState(revealedByFindOnMount),
238
257
  _useState6 = _slicedToArray(_useState5, 2),
239
258
  hasBeenExpanded = _useState6[0],
240
259
  setHasBeenExpanded = _useState6[1];
@@ -260,9 +279,15 @@ function Expand(_ref) {
260
279
  // inside other expands, those have to open too or the reader still cannot see it. Each expand
261
280
  // asks the one above it, so a single match opens the whole chain and nothing else.
262
281
  var openWithAncestors = useCallback(function () {
282
+ // Remembered before anything opens: a block above this expand may have been standing in for
283
+ // itself, and rendering it rebuilds this expand. Without this it would come back closed over
284
+ // the match the reader was just taken to.
285
+ if (node) {
286
+ revealedByFind.add(node);
287
+ }
263
288
  openBody();
264
289
  ancestorBody === null || ancestorBody === void 0 || ancestorBody.openWithAncestors();
265
- }, [ancestorBody, openBody]);
290
+ }, [ancestorBody, node, openBody, revealedByFind]);
266
291
 
267
292
  // Feature-detect hidden="until-found" support via the beforematch event. Chrome 102+,
268
293
  // Firefox 139+ and Safari 26.2+ all have it; in a browser without it, hidden="until-found" is
@@ -344,9 +369,10 @@ function Expand(_ref) {
344
369
  var expandBody = useMemo(function () {
345
370
  return {
346
371
  revealed: revealed,
347
- openWithAncestors: openWithAncestors
372
+ openWithAncestors: openWithAncestors,
373
+ revealedByFind: revealedByFind
348
374
  };
349
- }, [revealed, openWithAncestors]);
375
+ }, [revealed, openWithAncestors, revealedByFind]);
350
376
  return jsx(Container, {
351
377
  "data-testid": "expand-container-".concat(nodeType, "-").concat(id),
352
378
  "data-node-type": nodeType,
@@ -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 = "137.1.6";
68
+ var packageVersion = "137.1.8";
69
69
  var setAsQueryContainerStyles = css({
70
70
  containerName: 'ak-renderer-wrapper',
71
71
  containerType: 'inline-size'
@@ -0,0 +1,210 @@
1
+ import React, { createContext, isValidElement, useContext } from 'react';
2
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
+ import { BLOCK_SEPARATOR, getBlockSearchText, getTableStandInParts, holdsRevealableContent, isExpandNode } from './expand-search-text';
4
+ var ExpandBodyContext = /*#__PURE__*/createContext(null);
5
+ export var ExpandBodyProvider = ExpandBodyContext.Provider;
6
+ export var useExpandBody = function useExpandBody() {
7
+ return useContext(ExpandBodyContext);
8
+ };
9
+ /**
10
+ * One or more neighbouring blocks of an expand's body. Shows their text until the expand is
11
+ * opened, then renders them. Falls back to rendering if there is no text, or if there is no
12
+ * expand above it.
13
+ *
14
+ * The text is rendered as-is, with no element around it. Nothing reads it but browser find,
15
+ * which only needs the characters to be in the DOM.
16
+ */
17
+ export var ExpandBodyBlock = function ExpandBodyBlock(_ref) {
18
+ var children = _ref.children,
19
+ searchText = _ref.searchText;
20
+ var body = useExpandBody();
21
+ if (searchText === undefined || body === null || body.revealed) {
22
+ return /*#__PURE__*/React.createElement(React.Fragment, null, children);
23
+ }
24
+ return searchText;
25
+ };
26
+
27
+ /**
28
+ * Called for every node the serializer renders. If the node is a block of an expand's body,
29
+ * wraps it so it can show its text instead of rendering while that expand is collapsed.
30
+ * Everything else is returned untouched.
31
+ *
32
+ * A block holding an expand of its own is returned untouched, because standing in for the whole
33
+ * block would take that expand with it, and the expand needs an element of its own: the element
34
+ * browser find reveals is the only way we learn the match was inside it rather than higher up. A
35
+ * table is handed to `ExpandBodyTable`, which stands in for its own rows and keeps only the rows
36
+ * that hold an expand. Everything else — a panel, a list, an extension with stashed ADF — renders
37
+ * in full, so the reader searches once instead of once per level.
38
+ *
39
+ * `ancestors` is the node's ancestor chain, nearest last.
40
+ */
41
+ export var withExpandBodyBlock = function withExpandBodyBlock(node, ancestors, index, serialized) {
42
+ var parent = ancestors[ancestors.length - 1];
43
+ if (!parent || !isExpandNode(parent)) {
44
+ return serialized;
45
+ }
46
+ if (!isExperimentEnabled('platform_editor_defer_collapsed_expand_body')) {
47
+ return serialized;
48
+ }
49
+
50
+ // `serialized` is only null for a node the renderer has no component for, which a table is not.
51
+ // Such a table falls through to the paths below, which do not reach into it.
52
+ if (node.type.name === 'table' && serialized !== null) {
53
+ var parts = getTableStandInParts(node);
54
+
55
+ // With no row to keep, one string can stand in for the whole table — the table then renders
56
+ // nothing at all, and its text joins with the blocks either side of it.
57
+ return parts.every(function (part) {
58
+ return typeof part === 'string';
59
+ }) ? /*#__PURE__*/React.createElement(ExpandBodyBlock, {
60
+ key: "expand-body-block-".concat(index),
61
+ searchText: parts.join('')
62
+ }, serialized) : /*#__PURE__*/React.createElement(ExpandBodyTable, {
63
+ key: "expand-body-table-".concat(index),
64
+ node: node
65
+ }, serialized);
66
+ }
67
+ if (holdsRevealableContent(node)) {
68
+ return serialized;
69
+ }
70
+ var searchText = getBlockSearchText(node);
71
+ if (searchText === undefined) {
72
+ return serialized;
73
+ }
74
+ return /*#__PURE__*/React.createElement(ExpandBodyBlock, {
75
+ key: "expand-body-block-".concat(index),
76
+ searchText: searchText
77
+ }, serialized);
78
+ };
79
+ var isRow = function isRow(child) {
80
+ return /*#__PURE__*/isValidElement(child) && child.props.nodeType === 'tableRow';
81
+ };
82
+
83
+ /**
84
+ * The rows inside the serialized table, wherever they sit.
85
+ *
86
+ * What the serializer hands over is not reliably the element whose children are the rows. Marks are
87
+ * folded around the node afterwards, and a product's own serializer may wrap the rows themselves —
88
+ * Confluence's progressive renderer wraps every row but the first. Assuming either a depth or a set
89
+ * of direct children means any wrapper added later silently costs the saving, so the rows are found
90
+ * instead by the `nodeType` the serializer puts on every node.
91
+ *
92
+ * Descent stops at each row: a nested table's rows sit inside a row of this one, and would
93
+ * otherwise be counted among them.
94
+ *
95
+ * The count is only a consistency check. `getTableStandInParts` indexes the rows by position, so a
96
+ * partial match cannot be lined up and the table has to render instead.
97
+ */
98
+ var rowsOf = function rowsOf(serialized, rowCount) {
99
+ var rows = [];
100
+ var _collect = function collect(node) {
101
+ React.Children.toArray(node).forEach(function (child) {
102
+ if (! /*#__PURE__*/isValidElement(child)) {
103
+ return;
104
+ }
105
+ if (isRow(child)) {
106
+ rows.push(child);
107
+ return;
108
+ }
109
+ _collect(child.props.children);
110
+ });
111
+ };
112
+ _collect(serialized);
113
+ return rows.length === rowCount ? rows : undefined;
114
+ };
115
+
116
+ /**
117
+ * A table in the body of a collapsed expand. Shows the text of its rows until the expand is opened,
118
+ * keeping the rows that hold an expand of their own — those need an element for find to reveal.
119
+ *
120
+ * Standing in for the whole table, the way an ordinary block does, would take that expand with it.
121
+ */
122
+ export var ExpandBodyTable = function ExpandBodyTable(_ref2) {
123
+ var children = _ref2.children,
124
+ node = _ref2.node;
125
+ var body = useExpandBody();
126
+ if (body === null || body.revealed) {
127
+ return children;
128
+ }
129
+ var rows = rowsOf(children, node.childCount);
130
+ if (!rows) {
131
+ // Nothing is wrong: the table renders as it always did, only without the saving. Still worth
132
+ // saying, because a silent fallback looks exactly like the feature being switched off.
133
+ // Every environment but production says it: a wrapper added by a product is only ever seen
134
+ // once that product has deployed, which a `NODE_ENV` check never reaches.
135
+ if (process.env.CLOUD_ENV !== 'production') {
136
+ // eslint-disable-next-line no-console
137
+ console.info('@atlaskit/renderer: the rows of a table in a collapsed expand were not found, so the table renders in full rather than showing their text');
138
+ }
139
+ return children;
140
+ }
141
+ return getTableStandInParts(node).map(function (part) {
142
+ return typeof part === 'string' ? part :
143
+ /*#__PURE__*/
144
+ // A row cannot stand in the page on its own — `<tr>` is only valid inside a table — so it
145
+ // keeps the least table around it that makes the markup valid. None of this is laid out: the
146
+ // body is hidden until the reader or find opens the expand, and opening it renders the real
147
+ // table in place of all this.
148
+ React.createElement("table", {
149
+ key: "expand-body-row-".concat(part)
150
+ }, /*#__PURE__*/React.createElement("tbody", null, rows[part]));
151
+ });
152
+ };
153
+
154
+ /** Whatever the serializer produced for one child: an element, a string, an array of either. */
155
+
156
+ var searchTextOf = function searchTextOf(child) {
157
+ return /*#__PURE__*/isValidElement(child) && child.type === ExpandBodyBlock ? child.props.searchText : undefined;
158
+ };
159
+
160
+ /**
161
+ * Joins neighbouring blocks that are showing text into one, so a run of them is a single string in
162
+ * the DOM rather than one per block. A body of twenty paragraphs becomes one text node instead of
163
+ * twenty.
164
+ *
165
+ * A block that is being rendered — a nested expand, say — ends the run, because the text either
166
+ * side of it has to stay either side of it.
167
+ */
168
+ export var mergeExpandBodyText = function mergeExpandBodyText(children) {
169
+ // Called for every fragment in the document, and only an expand's body has anything to merge, so
170
+ // leave the array alone unless two neighbours are actually showing text.
171
+ var hasRun = children.some(function (child, index) {
172
+ return index > 0 && searchTextOf(child) !== undefined && searchTextOf(children[index - 1]) !== undefined;
173
+ });
174
+ if (!hasRun) {
175
+ return children;
176
+ }
177
+ var merged = [];
178
+ var run = [];
179
+ var endRun = function endRun() {
180
+ if (run.length === 0) {
181
+ return;
182
+ }
183
+ if (run.length === 1) {
184
+ merged.push(run[0]);
185
+ } else {
186
+ var _key;
187
+ var text = run.map(searchTextOf).filter(Boolean).join(BLOCK_SEPARATOR);
188
+ // The blocks themselves, not the wrappers around them: nesting the wrappers would show
189
+ // each block's text again inside the joined run.
190
+ var blocks = run.map(function (child) {
191
+ return child.props.children;
192
+ });
193
+ merged.push( /*#__PURE__*/React.createElement(ExpandBodyBlock, {
194
+ key: (_key = run[0].key) !== null && _key !== void 0 ? _key : undefined,
195
+ searchText: text
196
+ }, blocks));
197
+ }
198
+ run = [];
199
+ };
200
+ children.forEach(function (child) {
201
+ if (searchTextOf(child) === undefined) {
202
+ endRun();
203
+ merged.push(child);
204
+ return;
205
+ }
206
+ run.push(child);
207
+ });
208
+ endRun();
209
+ return merged;
210
+ };
@@ -130,4 +130,42 @@ export var getBlockSearchText = function getBlockSearchText(node) {
130
130
  searchTextCache.set(node, cached);
131
131
  }
132
132
  return cached !== null && cached !== void 0 ? cached : undefined;
133
+ };
134
+
135
+ /**
136
+ * One piece of what a collapsed table shows: either the text of the rows being stood in for, or the
137
+ * index of a row that has to keep rendering.
138
+ */
139
+
140
+ var tableStandInCache = new WeakMap();
141
+
142
+ /**
143
+ * What a table inside a collapsed expand shows in place of rendering, in table order so that find
144
+ * walks it the way the reader reads it.
145
+ *
146
+ * A row keeps rendering when it holds an expand of its own, which needs a real element for browser
147
+ * find to reveal, or when its text carries an inline comment. Its text is left out of the runs
148
+ * either side of it, since the row itself puts that text in the DOM.
149
+ */
150
+ export var getTableStandInParts = function getTableStandInParts(table) {
151
+ var cached = tableStandInCache.get(table);
152
+ if (cached !== undefined) {
153
+ return cached;
154
+ }
155
+ var parts = [];
156
+ table.forEach(function (row, _offset, index) {
157
+ var text = holdsRevealableContent(row) ? undefined : getBlockSearchText(row);
158
+ if (text === undefined) {
159
+ parts.push(index);
160
+ return;
161
+ }
162
+ var last = parts[parts.length - 1];
163
+ if (typeof last === 'string') {
164
+ parts[parts.length - 1] = "".concat(last).concat(BLOCK_SEPARATOR).concat(text);
165
+ return;
166
+ }
167
+ parts.push(text);
168
+ });
169
+ tableStandInCache.set(table, parts);
170
+ return parts;
133
171
  };