@atlaskit/editor-plugin-table 10.6.9 → 10.6.11

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/editor-plugin-table
2
2
 
3
+ ## 10.6.11
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 10.6.10
10
+
11
+ ### Patch Changes
12
+
13
+ - [#134965](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/134965)
14
+ [`6c2982045451c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6c2982045451c) -
15
+ ED-26568: Remove custom toDOM implementation as it was causing flickering of an unstyled table
16
+ node when loading the table with React18 concurrent mode. Add logic to temporarily ignore all
17
+ non-selection DOM mutations during table initialisation in order to prevent unstyled table
18
+ flashing. Restore mutation handler after react has finished applying the style and structure.
19
+
3
20
  ## 10.6.9
4
21
 
5
22
  ### Patch Changes
@@ -32,6 +32,7 @@ var _tableWidth = require("../pm-plugins/table-width");
32
32
  var _nodes = require("../pm-plugins/utils/nodes");
33
33
  var _TableComponent = _interopRequireDefault(require("./TableComponent"));
34
34
  var _TableComponentWithSharedState = require("./TableComponentWithSharedState");
35
+ var _toDOM = require("./toDOM");
35
36
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
36
37
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
37
38
  function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
@@ -66,6 +67,8 @@ var handleInlineTableWidth = function handleInlineTableWidth(table, width) {
66
67
  }
67
68
  table.style.setProperty('width', "".concat(width, "px"));
68
69
  };
70
+
71
+ // Leave as a fallback incase the table's NodeSpec.toDOM is not defined.
69
72
  var toDOM = function toDOM(node, props) {
70
73
  var colgroup = '';
71
74
  if (props.allowColumnResizing) {
@@ -86,16 +89,37 @@ var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) {
86
89
  _this.eventDispatcher = props.eventDispatcher;
87
90
  _this.options = props.options;
88
91
  _this.getEditorFeatureFlags = props.getEditorFeatureFlags;
92
+ if ((0, _platformFeatureFlags.fg)('platform_editor_table_initial_load_fix')) {
93
+ _this.handleRef = function (node) {
94
+ return _this._handleTableRef(node);
95
+ };
96
+ }
89
97
  return _this;
90
98
  }
91
99
  (0, _inherits2.default)(TableView, _ReactNodeView);
92
100
  return (0, _createClass2.default)(TableView, [{
93
101
  key: "getContentDOM",
94
102
  value: function getContentDOM() {
95
- var rendered = _model.DOMSerializer.renderSpec(document, toDOM(this.node, this.reactComponentProps));
103
+ var tableDOMStructure;
104
+ if ((0, _platformFeatureFlags.fg)('platform_editor_table_initial_load_fix')) {
105
+ tableDOMStructure = (0, _toDOM.tableNodeSpecWithFixedToDOM)({
106
+ allowColumnResizing: !!this.reactComponentProps.allowColumnResizing,
107
+ tableResizingEnabled: !!this.reactComponentProps.allowTableResizing,
108
+ getEditorContainerWidth: this.reactComponentProps.getEditorContainerWidth
109
+ }).toDOM(this.node);
110
+ } else {
111
+ tableDOMStructure = toDOM(this.node, this.reactComponentProps);
112
+ }
113
+ var rendered = _model.DOMSerializer.renderSpec(document, tableDOMStructure);
96
114
  if (rendered.dom) {
97
115
  var _this$options, _this$options2, _this$getEditorFeatur;
98
- this.table = rendered.dom;
116
+ if ((0, _platformFeatureFlags.fg)('platform_editor_table_initial_load_fix')) {
117
+ var tableElement = rendered.dom.querySelector('table');
118
+ this.table = tableElement ? tableElement : rendered.dom;
119
+ this.renderedDOM = rendered.dom;
120
+ } else {
121
+ this.table = rendered.dom;
122
+ }
99
123
  if (!((_this$options = this.options) !== null && _this$options !== void 0 && _this$options.isTableScalingEnabled) || (_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.isTableScalingEnabled && (_this$getEditorFeatur = this.getEditorFeatureFlags) !== null && _this$getEditorFeatur !== void 0 && _this$getEditorFeatur.call(this).tableWithFixedColumnWidthsOption && this.node.attrs.displayMode === 'fixed') {
100
124
  var tableInlineWidth = getInlineWidth(this.node, this.reactComponentProps.options, this.reactComponentProps.view.state, this.reactComponentProps.getPos(), this.reactComponentProps.allowTableResizing);
101
125
  if (tableInlineWidth) {
@@ -105,10 +129,79 @@ var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) {
105
129
  }
106
130
  return rendered;
107
131
  }
132
+
133
+ /**
134
+ * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node.
135
+ * Temporarily disables mutation observers (except for selection changes) during the move,
136
+ * preserves selection state, and restores it afterwards if mutations occurred and cursor
137
+ * wasn't at start of node. This prevents duplicate tables and maintains editor state during
138
+ * the DOM manipulation.
139
+ */
140
+ }, {
141
+ key: "_handleTableRef",
142
+ value: function _handleTableRef(node) {
143
+ var _this2 = this;
144
+ var oldIgnoreMutation;
145
+ var selectionBookmark;
146
+ var parentOffset = 0;
147
+ var mutationsIgnored = false;
148
+
149
+ // Only proceed if we have both a node and table, and the table isn't already inside the node
150
+ if (node && this.table && !node.contains(this.table)) {
151
+ var _this$view$state$sele;
152
+ // Store the current ignoreMutation handler so we can restore it later
153
+ oldIgnoreMutation = this.ignoreMutation;
154
+
155
+ // Set up a temporary mutation handler that:
156
+ // - Ignores all DOM mutations except selection changes
157
+ // - Tracks when mutations have been ignored via mutationsIgnored flag
158
+ this.ignoreMutation = function (m) {
159
+ var isSelectionMutation = m.target instanceof Selection;
160
+ if (!isSelectionMutation) {
161
+ mutationsIgnored = true;
162
+ }
163
+ return !isSelectionMutation;
164
+ };
165
+
166
+ // Store the current selection state if there is a visible selection
167
+ // This lets us restore it after DOM changes
168
+ if (this.view.state.selection.visible) {
169
+ selectionBookmark = this.view.state.selection.getBookmark();
170
+ }
171
+
172
+ // Store the current cursor position within the parent node
173
+ // Used to determine if we need to restore selection later
174
+ if (((_this$view$state$sele = this.view.state.selection) === null || _this$view$state$sele === void 0 ? void 0 : _this$view$state$sele.ranges.length) > 0) {
175
+ var _this$view$state$sele2, _this$view$state$sele3;
176
+ parentOffset = (_this$view$state$sele2 = (_this$view$state$sele3 = this.view.state.selection) === null || _this$view$state$sele3 === void 0 || (_this$view$state$sele3 = _this$view$state$sele3.ranges[0].$from) === null || _this$view$state$sele3 === void 0 ? void 0 : _this$view$state$sele3.parentOffset) !== null && _this$view$state$sele2 !== void 0 ? _this$view$state$sele2 : 0;
177
+ }
178
+
179
+ // Remove the ProseMirror table DOM structure to avoid duplication, as it's replaced with the React table node.
180
+ if (this.renderedDOM) {
181
+ this.dom.removeChild(this.renderedDOM);
182
+ }
183
+ // Move the table from the ProseMirror table structure into the React rendered table node.
184
+ node.appendChild(this.table);
185
+
186
+ // After the next frame:
187
+ requestAnimationFrame(function () {
188
+ // Restore the original mutation handler
189
+ _this2.ignoreMutation = oldIgnoreMutation;
190
+
191
+ // Restore the selection only if:
192
+ // - We have a selection bookmark
193
+ // - Mutations were ignored during the table move
194
+ // - The cursor wasn't at the start of the node
195
+ if (selectionBookmark && mutationsIgnored && parentOffset > 0) {
196
+ _this2.view.dispatch(_this2.view.state.tr.setSelection(selectionBookmark.resolve(_this2.view.state.tr.doc)));
197
+ }
198
+ });
199
+ }
200
+ }
108
201
  }, {
109
202
  key: "setDomAttrs",
110
203
  value: function setDomAttrs(node) {
111
- var _this2 = this,
204
+ var _this3 = this,
112
205
  _this$options3,
113
206
  _this$options4,
114
207
  _this$getEditorFeatur2;
@@ -119,7 +212,7 @@ var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) {
119
212
  Object.keys(attrs).forEach(function (attr) {
120
213
  // Ignored via go/ees005
121
214
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
122
- _this2.table.setAttribute(attr, attrs[attr]);
215
+ _this3.table.setAttribute(attr, attrs[attr]);
123
216
  });
124
217
 
125
218
  // Preserve Table Width cannot have inline width set on the table
@@ -136,7 +229,7 @@ var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) {
136
229
  }, {
137
230
  key: "render",
138
231
  value: function render(props, forwardRef) {
139
- var _this3 = this;
232
+ var _this4 = this;
140
233
  if ((0, _platformFeatureFlags.fg)('platform_editor_table_use_shared_state_hook_fg')) {
141
234
  return /*#__PURE__*/_react.default.createElement(_TableComponentWithSharedState.TableComponentWithSharedState, {
142
235
  forwardRef: forwardRef,
@@ -154,7 +247,6 @@ var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) {
154
247
  dispatchAnalyticsEvent: props.dispatchAnalyticsEvent
155
248
  });
156
249
  }
157
-
158
250
  // Please, do not copy or use this kind of code below
159
251
  // @ts-ignore
160
252
  var fakePluginKey = {
@@ -246,7 +338,7 @@ var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) {
246
338
  ,
247
339
  ordering: pluginState.ordering,
248
340
  isResizing: isResizing,
249
- getNode: _this3.getNode
341
+ getNode: _this4.getNode
250
342
  // Ignored via go/ees005
251
343
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
252
344
  ,
@@ -389,8 +389,12 @@ var getToolbarConfig = exports.getToolbarConfig = function getToolbarConfig(getE
389
389
  }
390
390
  var isTableOrColumnResizing = !!(resizeState !== null && resizeState !== void 0 && resizeState.dragging || tableWidthState !== null && tableWidthState !== void 0 && tableWidthState.resizing);
391
391
  var isTableMenuOpened = pluginState.isContextualMenuOpen || isDragHandleMenuOpened;
392
+ var isTableState = isTableRowOrColumnDragged || isTableOrColumnResizing || isTableMenuOpened;
392
393
  var isViewMode = (api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode) === 'view';
393
- var shouldSuppressAllToolbars = !pluginState.editorHasFocus && !isViewMode || isTableMenuOpened || isTableOrColumnResizing || isTableRowOrColumnDragged;
394
+
395
+ // Note: when focus is in codeblocks, pluginState.editorHasFocus is false, so the codeblocks toolbar
396
+ // won't be suppressed.
397
+ var shouldSuppressAllToolbars = isTableState && pluginState.editorHasFocus && !isViewMode;
394
398
  if (shouldSuppressAllToolbars) {
395
399
  return {
396
400
  title: toolbarTitle,
@@ -17,6 +17,7 @@ import { pluginKey as tableWidthPluginKey } from '../pm-plugins/table-width';
17
17
  import { isTableNested } from '../pm-plugins/utils/nodes';
18
18
  import TableComponent from './TableComponent';
19
19
  import { TableComponentWithSharedState } from './TableComponentWithSharedState';
20
+ import { tableNodeSpecWithFixedToDOM } from './toDOM';
20
21
  const tableAttributes = node => {
21
22
  return {
22
23
  'data-number-column': node.attrs.isNumberColumnEnabled,
@@ -48,6 +49,8 @@ const handleInlineTableWidth = (table, width) => {
48
49
  }
49
50
  table.style.setProperty('width', `${width}px`);
50
51
  };
52
+
53
+ // Leave as a fallback incase the table's NodeSpec.toDOM is not defined.
51
54
  const toDOM = (node, props) => {
52
55
  let colgroup = '';
53
56
  if (props.allowColumnResizing) {
@@ -66,12 +69,31 @@ export default class TableView extends ReactNodeView {
66
69
  this.eventDispatcher = props.eventDispatcher;
67
70
  this.options = props.options;
68
71
  this.getEditorFeatureFlags = props.getEditorFeatureFlags;
72
+ if (fg('platform_editor_table_initial_load_fix')) {
73
+ this.handleRef = node => this._handleTableRef(node);
74
+ }
69
75
  }
70
76
  getContentDOM() {
71
- const rendered = DOMSerializer.renderSpec(document, toDOM(this.node, this.reactComponentProps));
77
+ let tableDOMStructure;
78
+ if (fg('platform_editor_table_initial_load_fix')) {
79
+ tableDOMStructure = tableNodeSpecWithFixedToDOM({
80
+ allowColumnResizing: !!this.reactComponentProps.allowColumnResizing,
81
+ tableResizingEnabled: !!this.reactComponentProps.allowTableResizing,
82
+ getEditorContainerWidth: this.reactComponentProps.getEditorContainerWidth
83
+ }).toDOM(this.node);
84
+ } else {
85
+ tableDOMStructure = toDOM(this.node, this.reactComponentProps);
86
+ }
87
+ const rendered = DOMSerializer.renderSpec(document, tableDOMStructure);
72
88
  if (rendered.dom) {
73
89
  var _this$options, _this$options2, _this$getEditorFeatur;
74
- this.table = rendered.dom;
90
+ if (fg('platform_editor_table_initial_load_fix')) {
91
+ const tableElement = rendered.dom.querySelector('table');
92
+ this.table = tableElement ? tableElement : rendered.dom;
93
+ this.renderedDOM = rendered.dom;
94
+ } else {
95
+ this.table = rendered.dom;
96
+ }
75
97
  if (!((_this$options = this.options) !== null && _this$options !== void 0 && _this$options.isTableScalingEnabled) || (_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.isTableScalingEnabled && (_this$getEditorFeatur = this.getEditorFeatureFlags) !== null && _this$getEditorFeatur !== void 0 && _this$getEditorFeatur.call(this).tableWithFixedColumnWidthsOption && this.node.attrs.displayMode === 'fixed') {
76
98
  const tableInlineWidth = getInlineWidth(this.node, this.reactComponentProps.options, this.reactComponentProps.view.state, this.reactComponentProps.getPos(), this.reactComponentProps.allowTableResizing);
77
99
  if (tableInlineWidth) {
@@ -81,6 +103,72 @@ export default class TableView extends ReactNodeView {
81
103
  }
82
104
  return rendered;
83
105
  }
106
+
107
+ /**
108
+ * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node.
109
+ * Temporarily disables mutation observers (except for selection changes) during the move,
110
+ * preserves selection state, and restores it afterwards if mutations occurred and cursor
111
+ * wasn't at start of node. This prevents duplicate tables and maintains editor state during
112
+ * the DOM manipulation.
113
+ */
114
+ _handleTableRef(node) {
115
+ let oldIgnoreMutation;
116
+ let selectionBookmark;
117
+ let parentOffset = 0;
118
+ let mutationsIgnored = false;
119
+
120
+ // Only proceed if we have both a node and table, and the table isn't already inside the node
121
+ if (node && this.table && !node.contains(this.table)) {
122
+ var _this$view$state$sele;
123
+ // Store the current ignoreMutation handler so we can restore it later
124
+ oldIgnoreMutation = this.ignoreMutation;
125
+
126
+ // Set up a temporary mutation handler that:
127
+ // - Ignores all DOM mutations except selection changes
128
+ // - Tracks when mutations have been ignored via mutationsIgnored flag
129
+ this.ignoreMutation = m => {
130
+ const isSelectionMutation = m.target instanceof Selection;
131
+ if (!isSelectionMutation) {
132
+ mutationsIgnored = true;
133
+ }
134
+ return !isSelectionMutation;
135
+ };
136
+
137
+ // Store the current selection state if there is a visible selection
138
+ // This lets us restore it after DOM changes
139
+ if (this.view.state.selection.visible) {
140
+ selectionBookmark = this.view.state.selection.getBookmark();
141
+ }
142
+
143
+ // Store the current cursor position within the parent node
144
+ // Used to determine if we need to restore selection later
145
+ if (((_this$view$state$sele = this.view.state.selection) === null || _this$view$state$sele === void 0 ? void 0 : _this$view$state$sele.ranges.length) > 0) {
146
+ var _this$view$state$sele2, _this$view$state$sele3, _this$view$state$sele4;
147
+ parentOffset = (_this$view$state$sele2 = (_this$view$state$sele3 = this.view.state.selection) === null || _this$view$state$sele3 === void 0 ? void 0 : (_this$view$state$sele4 = _this$view$state$sele3.ranges[0].$from) === null || _this$view$state$sele4 === void 0 ? void 0 : _this$view$state$sele4.parentOffset) !== null && _this$view$state$sele2 !== void 0 ? _this$view$state$sele2 : 0;
148
+ }
149
+
150
+ // Remove the ProseMirror table DOM structure to avoid duplication, as it's replaced with the React table node.
151
+ if (this.renderedDOM) {
152
+ this.dom.removeChild(this.renderedDOM);
153
+ }
154
+ // Move the table from the ProseMirror table structure into the React rendered table node.
155
+ node.appendChild(this.table);
156
+
157
+ // After the next frame:
158
+ requestAnimationFrame(() => {
159
+ // Restore the original mutation handler
160
+ this.ignoreMutation = oldIgnoreMutation;
161
+
162
+ // Restore the selection only if:
163
+ // - We have a selection bookmark
164
+ // - Mutations were ignored during the table move
165
+ // - The cursor wasn't at the start of the node
166
+ if (selectionBookmark && mutationsIgnored && parentOffset > 0) {
167
+ this.view.dispatch(this.view.state.tr.setSelection(selectionBookmark.resolve(this.view.state.tr.doc)));
168
+ }
169
+ });
170
+ }
171
+ }
84
172
  setDomAttrs(node) {
85
173
  var _this$options3, _this$options4, _this$getEditorFeatur2;
86
174
  if (!this.table) {
@@ -122,7 +210,6 @@ export default class TableView extends ReactNodeView {
122
210
  dispatchAnalyticsEvent: props.dispatchAnalyticsEvent
123
211
  });
124
212
  }
125
-
126
213
  // Please, do not copy or use this kind of code below
127
214
  // @ts-ignore
128
215
  const fakePluginKey = {
@@ -367,8 +367,12 @@ export const getToolbarConfig = (getEditorContainerWidth, api, editorAnalyticsAP
367
367
  }
368
368
  const isTableOrColumnResizing = !!(resizeState !== null && resizeState !== void 0 && resizeState.dragging || tableWidthState !== null && tableWidthState !== void 0 && tableWidthState.resizing);
369
369
  const isTableMenuOpened = pluginState.isContextualMenuOpen || isDragHandleMenuOpened;
370
+ const isTableState = isTableRowOrColumnDragged || isTableOrColumnResizing || isTableMenuOpened;
370
371
  const isViewMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode) === 'view';
371
- const shouldSuppressAllToolbars = !pluginState.editorHasFocus && !isViewMode || isTableMenuOpened || isTableOrColumnResizing || isTableRowOrColumnDragged;
372
+
373
+ // Note: when focus is in codeblocks, pluginState.editorHasFocus is false, so the codeblocks toolbar
374
+ // won't be suppressed.
375
+ const shouldSuppressAllToolbars = isTableState && pluginState.editorHasFocus && !isViewMode;
372
376
  if (shouldSuppressAllToolbars) {
373
377
  return {
374
378
  title: toolbarTitle,
@@ -28,6 +28,7 @@ import { pluginKey as tableWidthPluginKey } from '../pm-plugins/table-width';
28
28
  import { isTableNested } from '../pm-plugins/utils/nodes';
29
29
  import TableComponent from './TableComponent';
30
30
  import { TableComponentWithSharedState } from './TableComponentWithSharedState';
31
+ import { tableNodeSpecWithFixedToDOM } from './toDOM';
31
32
  var tableAttributes = function tableAttributes(node) {
32
33
  return {
33
34
  'data-number-column': node.attrs.isNumberColumnEnabled,
@@ -59,6 +60,8 @@ var handleInlineTableWidth = function handleInlineTableWidth(table, width) {
59
60
  }
60
61
  table.style.setProperty('width', "".concat(width, "px"));
61
62
  };
63
+
64
+ // Leave as a fallback incase the table's NodeSpec.toDOM is not defined.
62
65
  var toDOM = function toDOM(node, props) {
63
66
  var colgroup = '';
64
67
  if (props.allowColumnResizing) {
@@ -79,16 +82,37 @@ var TableView = /*#__PURE__*/function (_ReactNodeView) {
79
82
  _this.eventDispatcher = props.eventDispatcher;
80
83
  _this.options = props.options;
81
84
  _this.getEditorFeatureFlags = props.getEditorFeatureFlags;
85
+ if (fg('platform_editor_table_initial_load_fix')) {
86
+ _this.handleRef = function (node) {
87
+ return _this._handleTableRef(node);
88
+ };
89
+ }
82
90
  return _this;
83
91
  }
84
92
  _inherits(TableView, _ReactNodeView);
85
93
  return _createClass(TableView, [{
86
94
  key: "getContentDOM",
87
95
  value: function getContentDOM() {
88
- var rendered = DOMSerializer.renderSpec(document, toDOM(this.node, this.reactComponentProps));
96
+ var tableDOMStructure;
97
+ if (fg('platform_editor_table_initial_load_fix')) {
98
+ tableDOMStructure = tableNodeSpecWithFixedToDOM({
99
+ allowColumnResizing: !!this.reactComponentProps.allowColumnResizing,
100
+ tableResizingEnabled: !!this.reactComponentProps.allowTableResizing,
101
+ getEditorContainerWidth: this.reactComponentProps.getEditorContainerWidth
102
+ }).toDOM(this.node);
103
+ } else {
104
+ tableDOMStructure = toDOM(this.node, this.reactComponentProps);
105
+ }
106
+ var rendered = DOMSerializer.renderSpec(document, tableDOMStructure);
89
107
  if (rendered.dom) {
90
108
  var _this$options, _this$options2, _this$getEditorFeatur;
91
- this.table = rendered.dom;
109
+ if (fg('platform_editor_table_initial_load_fix')) {
110
+ var tableElement = rendered.dom.querySelector('table');
111
+ this.table = tableElement ? tableElement : rendered.dom;
112
+ this.renderedDOM = rendered.dom;
113
+ } else {
114
+ this.table = rendered.dom;
115
+ }
92
116
  if (!((_this$options = this.options) !== null && _this$options !== void 0 && _this$options.isTableScalingEnabled) || (_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.isTableScalingEnabled && (_this$getEditorFeatur = this.getEditorFeatureFlags) !== null && _this$getEditorFeatur !== void 0 && _this$getEditorFeatur.call(this).tableWithFixedColumnWidthsOption && this.node.attrs.displayMode === 'fixed') {
93
117
  var tableInlineWidth = getInlineWidth(this.node, this.reactComponentProps.options, this.reactComponentProps.view.state, this.reactComponentProps.getPos(), this.reactComponentProps.allowTableResizing);
94
118
  if (tableInlineWidth) {
@@ -98,10 +122,79 @@ var TableView = /*#__PURE__*/function (_ReactNodeView) {
98
122
  }
99
123
  return rendered;
100
124
  }
125
+
126
+ /**
127
+ * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node.
128
+ * Temporarily disables mutation observers (except for selection changes) during the move,
129
+ * preserves selection state, and restores it afterwards if mutations occurred and cursor
130
+ * wasn't at start of node. This prevents duplicate tables and maintains editor state during
131
+ * the DOM manipulation.
132
+ */
133
+ }, {
134
+ key: "_handleTableRef",
135
+ value: function _handleTableRef(node) {
136
+ var _this2 = this;
137
+ var oldIgnoreMutation;
138
+ var selectionBookmark;
139
+ var parentOffset = 0;
140
+ var mutationsIgnored = false;
141
+
142
+ // Only proceed if we have both a node and table, and the table isn't already inside the node
143
+ if (node && this.table && !node.contains(this.table)) {
144
+ var _this$view$state$sele;
145
+ // Store the current ignoreMutation handler so we can restore it later
146
+ oldIgnoreMutation = this.ignoreMutation;
147
+
148
+ // Set up a temporary mutation handler that:
149
+ // - Ignores all DOM mutations except selection changes
150
+ // - Tracks when mutations have been ignored via mutationsIgnored flag
151
+ this.ignoreMutation = function (m) {
152
+ var isSelectionMutation = m.target instanceof Selection;
153
+ if (!isSelectionMutation) {
154
+ mutationsIgnored = true;
155
+ }
156
+ return !isSelectionMutation;
157
+ };
158
+
159
+ // Store the current selection state if there is a visible selection
160
+ // This lets us restore it after DOM changes
161
+ if (this.view.state.selection.visible) {
162
+ selectionBookmark = this.view.state.selection.getBookmark();
163
+ }
164
+
165
+ // Store the current cursor position within the parent node
166
+ // Used to determine if we need to restore selection later
167
+ if (((_this$view$state$sele = this.view.state.selection) === null || _this$view$state$sele === void 0 ? void 0 : _this$view$state$sele.ranges.length) > 0) {
168
+ var _this$view$state$sele2, _this$view$state$sele3;
169
+ parentOffset = (_this$view$state$sele2 = (_this$view$state$sele3 = this.view.state.selection) === null || _this$view$state$sele3 === void 0 || (_this$view$state$sele3 = _this$view$state$sele3.ranges[0].$from) === null || _this$view$state$sele3 === void 0 ? void 0 : _this$view$state$sele3.parentOffset) !== null && _this$view$state$sele2 !== void 0 ? _this$view$state$sele2 : 0;
170
+ }
171
+
172
+ // Remove the ProseMirror table DOM structure to avoid duplication, as it's replaced with the React table node.
173
+ if (this.renderedDOM) {
174
+ this.dom.removeChild(this.renderedDOM);
175
+ }
176
+ // Move the table from the ProseMirror table structure into the React rendered table node.
177
+ node.appendChild(this.table);
178
+
179
+ // After the next frame:
180
+ requestAnimationFrame(function () {
181
+ // Restore the original mutation handler
182
+ _this2.ignoreMutation = oldIgnoreMutation;
183
+
184
+ // Restore the selection only if:
185
+ // - We have a selection bookmark
186
+ // - Mutations were ignored during the table move
187
+ // - The cursor wasn't at the start of the node
188
+ if (selectionBookmark && mutationsIgnored && parentOffset > 0) {
189
+ _this2.view.dispatch(_this2.view.state.tr.setSelection(selectionBookmark.resolve(_this2.view.state.tr.doc)));
190
+ }
191
+ });
192
+ }
193
+ }
101
194
  }, {
102
195
  key: "setDomAttrs",
103
196
  value: function setDomAttrs(node) {
104
- var _this2 = this,
197
+ var _this3 = this,
105
198
  _this$options3,
106
199
  _this$options4,
107
200
  _this$getEditorFeatur2;
@@ -112,7 +205,7 @@ var TableView = /*#__PURE__*/function (_ReactNodeView) {
112
205
  Object.keys(attrs).forEach(function (attr) {
113
206
  // Ignored via go/ees005
114
207
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
115
- _this2.table.setAttribute(attr, attrs[attr]);
208
+ _this3.table.setAttribute(attr, attrs[attr]);
116
209
  });
117
210
 
118
211
  // Preserve Table Width cannot have inline width set on the table
@@ -129,7 +222,7 @@ var TableView = /*#__PURE__*/function (_ReactNodeView) {
129
222
  }, {
130
223
  key: "render",
131
224
  value: function render(props, forwardRef) {
132
- var _this3 = this;
225
+ var _this4 = this;
133
226
  if (fg('platform_editor_table_use_shared_state_hook_fg')) {
134
227
  return /*#__PURE__*/React.createElement(TableComponentWithSharedState, {
135
228
  forwardRef: forwardRef,
@@ -147,7 +240,6 @@ var TableView = /*#__PURE__*/function (_ReactNodeView) {
147
240
  dispatchAnalyticsEvent: props.dispatchAnalyticsEvent
148
241
  });
149
242
  }
150
-
151
243
  // Please, do not copy or use this kind of code below
152
244
  // @ts-ignore
153
245
  var fakePluginKey = {
@@ -239,7 +331,7 @@ var TableView = /*#__PURE__*/function (_ReactNodeView) {
239
331
  ,
240
332
  ordering: pluginState.ordering,
241
333
  isResizing: isResizing,
242
- getNode: _this3.getNode
334
+ getNode: _this4.getNode
243
335
  // Ignored via go/ees005
244
336
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
245
337
  ,
@@ -381,8 +381,12 @@ export var getToolbarConfig = function getToolbarConfig(getEditorContainerWidth,
381
381
  }
382
382
  var isTableOrColumnResizing = !!(resizeState !== null && resizeState !== void 0 && resizeState.dragging || tableWidthState !== null && tableWidthState !== void 0 && tableWidthState.resizing);
383
383
  var isTableMenuOpened = pluginState.isContextualMenuOpen || isDragHandleMenuOpened;
384
+ var isTableState = isTableRowOrColumnDragged || isTableOrColumnResizing || isTableMenuOpened;
384
385
  var isViewMode = (api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode) === 'view';
385
- var shouldSuppressAllToolbars = !pluginState.editorHasFocus && !isViewMode || isTableMenuOpened || isTableOrColumnResizing || isTableRowOrColumnDragged;
386
+
387
+ // Note: when focus is in codeblocks, pluginState.editorHasFocus is false, so the codeblocks toolbar
388
+ // won't be suppressed.
389
+ var shouldSuppressAllToolbars = isTableState && pluginState.editorHasFocus && !isViewMode;
386
390
  if (shouldSuppressAllToolbars) {
387
391
  return {
388
392
  title: toolbarTitle,
@@ -11,6 +11,7 @@ import type { Props } from './types';
11
11
  type ForwardRef = (node: HTMLElement | null) => void;
12
12
  export default class TableView extends ReactNodeView<Props> {
13
13
  private table;
14
+ private renderedDOM?;
14
15
  private resizeObserver?;
15
16
  eventDispatcher?: EventDispatcher;
16
17
  getPos: getPosHandlerNode;
@@ -21,6 +22,14 @@ export default class TableView extends ReactNodeView<Props> {
21
22
  dom: HTMLElement;
22
23
  contentDOM?: HTMLElement | undefined;
23
24
  };
25
+ /**
26
+ * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node.
27
+ * Temporarily disables mutation observers (except for selection changes) during the move,
28
+ * preserves selection state, and restores it afterwards if mutations occurred and cursor
29
+ * wasn't at start of node. This prevents duplicate tables and maintains editor state during
30
+ * the DOM manipulation.
31
+ */
32
+ private _handleTableRef;
24
33
  setDomAttrs(node: PmNode): void;
25
34
  getNode: () => PmNode;
26
35
  render(props: Props, forwardRef: ForwardRef): React.JSX.Element;
@@ -1,10 +1,12 @@
1
1
  import type { GetEditorContainerWidth } from '@atlaskit/editor-common/types';
2
- import type { NodeSpec } from '@atlaskit/editor-prosemirror/model';
2
+ import type { DOMOutputSpec, NodeSpec, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
3
  type Config = {
4
4
  allowColumnResizing: boolean;
5
5
  tableResizingEnabled: boolean;
6
6
  getEditorContainerWidth: GetEditorContainerWidth;
7
7
  isNestingSupported?: boolean;
8
8
  };
9
- export declare const tableNodeSpecWithFixedToDOM: (config: Config) => NodeSpec;
9
+ export declare const tableNodeSpecWithFixedToDOM: (config: Config) => NodeSpec & {
10
+ toDOM: (node: PMNode) => DOMOutputSpec;
11
+ };
10
12
  export {};
@@ -11,6 +11,7 @@ import type { Props } from './types';
11
11
  type ForwardRef = (node: HTMLElement | null) => void;
12
12
  export default class TableView extends ReactNodeView<Props> {
13
13
  private table;
14
+ private renderedDOM?;
14
15
  private resizeObserver?;
15
16
  eventDispatcher?: EventDispatcher;
16
17
  getPos: getPosHandlerNode;
@@ -21,6 +22,14 @@ export default class TableView extends ReactNodeView<Props> {
21
22
  dom: HTMLElement;
22
23
  contentDOM?: HTMLElement | undefined;
23
24
  };
25
+ /**
26
+ * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node.
27
+ * Temporarily disables mutation observers (except for selection changes) during the move,
28
+ * preserves selection state, and restores it afterwards if mutations occurred and cursor
29
+ * wasn't at start of node. This prevents duplicate tables and maintains editor state during
30
+ * the DOM manipulation.
31
+ */
32
+ private _handleTableRef;
24
33
  setDomAttrs(node: PmNode): void;
25
34
  getNode: () => PmNode;
26
35
  render(props: Props, forwardRef: ForwardRef): React.JSX.Element;
@@ -1,10 +1,12 @@
1
1
  import type { GetEditorContainerWidth } from '@atlaskit/editor-common/types';
2
- import type { NodeSpec } from '@atlaskit/editor-prosemirror/model';
2
+ import type { DOMOutputSpec, NodeSpec, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
3
  type Config = {
4
4
  allowColumnResizing: boolean;
5
5
  tableResizingEnabled: boolean;
6
6
  getEditorContainerWidth: GetEditorContainerWidth;
7
7
  isNestingSupported?: boolean;
8
8
  };
9
- export declare const tableNodeSpecWithFixedToDOM: (config: Config) => NodeSpec;
9
+ export declare const tableNodeSpecWithFixedToDOM: (config: Config) => NodeSpec & {
10
+ toDOM: (node: PMNode) => DOMOutputSpec;
11
+ };
10
12
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-table",
3
- "version": "10.6.9",
3
+ "version": "10.6.11",
4
4
  "description": "Table plugin for the @atlaskit/editor",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -32,14 +32,14 @@
32
32
  "@atlaskit/adf-schema": "^47.6.0",
33
33
  "@atlaskit/button": "^23.0.0",
34
34
  "@atlaskit/custom-steps": "^0.11.0",
35
- "@atlaskit/editor-common": "^103.2.0",
35
+ "@atlaskit/editor-common": "^103.3.0",
36
36
  "@atlaskit/editor-palette": "^2.1.0",
37
37
  "@atlaskit/editor-plugin-accessibility-utils": "^2.0.0",
38
38
  "@atlaskit/editor-plugin-analytics": "^2.2.0",
39
39
  "@atlaskit/editor-plugin-batch-attribute-updates": "^2.1.0",
40
40
  "@atlaskit/editor-plugin-content-insertion": "^2.1.0",
41
41
  "@atlaskit/editor-plugin-editor-viewmode": "^3.0.0",
42
- "@atlaskit/editor-plugin-extension": "5.2.2",
42
+ "@atlaskit/editor-plugin-extension": "5.2.3",
43
43
  "@atlaskit/editor-plugin-guideline": "^2.0.0",
44
44
  "@atlaskit/editor-plugin-selection": "^2.1.0",
45
45
  "@atlaskit/editor-plugin-width": "^3.0.0",
@@ -123,6 +123,9 @@
123
123
  "type": "boolean",
124
124
  "referenceOnly": true
125
125
  },
126
+ "platform_editor_table_initial_load_fix": {
127
+ "type": "boolean"
128
+ },
126
129
  "platform_editor_table_overflow_in_full_width_fix": {
127
130
  "type": "boolean"
128
131
  },
@@ -1033,6 +1033,7 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
1033
1033
  if (elem) {
1034
1034
  this.props.contentDOM(elem);
1035
1035
  const tableElement = elem.querySelector('table');
1036
+
1036
1037
  if (tableElement !== this.table) {
1037
1038
  this.table = tableElement;
1038
1039
  this.createShadowSentinels(this.table);
@@ -15,7 +15,7 @@ import type {
15
15
  import { WithPluginState } from '@atlaskit/editor-common/with-plugin-state';
16
16
  import type { DOMOutputSpec, Node as PmNode } from '@atlaskit/editor-prosemirror/model';
17
17
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
18
- import type { EditorState, PluginKey } from '@atlaskit/editor-prosemirror/state';
18
+ import type { EditorState, PluginKey, SelectionBookmark } from '@atlaskit/editor-prosemirror/state';
19
19
  import type { EditorView, NodeView } from '@atlaskit/editor-prosemirror/view';
20
20
  import { akEditorTableNumberColumnWidth } from '@atlaskit/editor-shared-styles';
21
21
  import { TableMap } from '@atlaskit/editor-tables/table-map';
@@ -33,6 +33,7 @@ import type { PluginInjectionAPI } from '../types';
33
33
 
34
34
  import TableComponent from './TableComponent';
35
35
  import { TableComponentWithSharedState } from './TableComponentWithSharedState';
36
+ import { tableNodeSpecWithFixedToDOM } from './toDOM';
36
37
  import type { Props } from './types';
37
38
 
38
39
  type ForwardRef = (node: HTMLElement | null) => void;
@@ -80,18 +81,18 @@ const handleInlineTableWidth = (table: HTMLElement, width: number | undefined) =
80
81
  table.style.setProperty('width', `${width}px`);
81
82
  };
82
83
 
84
+ // Leave as a fallback incase the table's NodeSpec.toDOM is not defined.
83
85
  const toDOM = (node: PmNode, props: Props) => {
84
86
  let colgroup: DOMOutputSpec = '';
85
-
86
87
  if (props.allowColumnResizing) {
87
88
  colgroup = ['colgroup', {}, ...generateColgroup(node)];
88
89
  }
89
-
90
90
  return ['table', tableAttributes(node), colgroup, ['tbody', 0]] as DOMOutputSpec;
91
91
  };
92
92
 
93
93
  export default class TableView extends ReactNodeView<Props> {
94
94
  private table: HTMLElement | undefined;
95
+ private renderedDOM?: HTMLElement;
95
96
  private resizeObserver?: ResizeObserver;
96
97
  eventDispatcher?: EventDispatcher;
97
98
  getPos: getPosHandlerNode;
@@ -111,19 +112,37 @@ export default class TableView extends ReactNodeView<Props> {
111
112
  this.eventDispatcher = props.eventDispatcher;
112
113
  this.options = props.options;
113
114
  this.getEditorFeatureFlags = props.getEditorFeatureFlags;
115
+
116
+ if (fg('platform_editor_table_initial_load_fix')) {
117
+ this.handleRef = (node: HTMLElement | null) => this._handleTableRef(node);
118
+ }
114
119
  }
115
120
 
116
121
  getContentDOM() {
117
- const rendered = DOMSerializer.renderSpec(
118
- document,
119
- toDOM(this.node, this.reactComponentProps as Props),
120
- ) as {
122
+ let tableDOMStructure;
123
+ if (fg('platform_editor_table_initial_load_fix')) {
124
+ tableDOMStructure = tableNodeSpecWithFixedToDOM({
125
+ allowColumnResizing: !!this.reactComponentProps.allowColumnResizing,
126
+ tableResizingEnabled: !!this.reactComponentProps.allowTableResizing,
127
+ getEditorContainerWidth: this.reactComponentProps.getEditorContainerWidth,
128
+ }).toDOM(this.node);
129
+ } else {
130
+ tableDOMStructure = toDOM(this.node, this.reactComponentProps as Props);
131
+ }
132
+
133
+ const rendered = DOMSerializer.renderSpec(document, tableDOMStructure) as {
121
134
  dom: HTMLElement;
122
135
  contentDOM?: HTMLElement;
123
136
  };
124
137
 
125
138
  if (rendered.dom) {
126
- this.table = rendered.dom;
139
+ if (fg('platform_editor_table_initial_load_fix')) {
140
+ const tableElement = rendered.dom.querySelector('table');
141
+ this.table = tableElement ? tableElement : rendered.dom;
142
+ this.renderedDOM = rendered.dom;
143
+ } else {
144
+ this.table = rendered.dom;
145
+ }
127
146
 
128
147
  if (
129
148
  !this.options?.isTableScalingEnabled ||
@@ -147,6 +166,73 @@ export default class TableView extends ReactNodeView<Props> {
147
166
  return rendered;
148
167
  }
149
168
 
169
+ /**
170
+ * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node.
171
+ * Temporarily disables mutation observers (except for selection changes) during the move,
172
+ * preserves selection state, and restores it afterwards if mutations occurred and cursor
173
+ * wasn't at start of node. This prevents duplicate tables and maintains editor state during
174
+ * the DOM manipulation.
175
+ */
176
+ private _handleTableRef(node: HTMLElement | null) {
177
+ let oldIgnoreMutation: (mutation: MutationRecord) => boolean;
178
+
179
+ let selectionBookmark: SelectionBookmark;
180
+ let parentOffset = 0;
181
+ let mutationsIgnored = false;
182
+
183
+ // Only proceed if we have both a node and table, and the table isn't already inside the node
184
+ if (node && this.table && !node.contains(this.table)) {
185
+ // Store the current ignoreMutation handler so we can restore it later
186
+ oldIgnoreMutation = this.ignoreMutation;
187
+
188
+ // Set up a temporary mutation handler that:
189
+ // - Ignores all DOM mutations except selection changes
190
+ // - Tracks when mutations have been ignored via mutationsIgnored flag
191
+ this.ignoreMutation = (m: MutationRecord) => {
192
+ const isSelectionMutation = m.target instanceof Selection;
193
+ if (!isSelectionMutation) {
194
+ mutationsIgnored = true;
195
+ }
196
+ return !isSelectionMutation;
197
+ };
198
+
199
+ // Store the current selection state if there is a visible selection
200
+ // This lets us restore it after DOM changes
201
+ if (this.view.state.selection.visible) {
202
+ selectionBookmark = this.view.state.selection.getBookmark();
203
+ }
204
+
205
+ // Store the current cursor position within the parent node
206
+ // Used to determine if we need to restore selection later
207
+ if (this.view.state.selection?.ranges.length > 0) {
208
+ parentOffset = this.view.state.selection?.ranges[0].$from?.parentOffset ?? 0;
209
+ }
210
+
211
+ // Remove the ProseMirror table DOM structure to avoid duplication, as it's replaced with the React table node.
212
+ if (this.renderedDOM) {
213
+ this.dom.removeChild(this.renderedDOM);
214
+ }
215
+ // Move the table from the ProseMirror table structure into the React rendered table node.
216
+ node.appendChild(this.table);
217
+
218
+ // After the next frame:
219
+ requestAnimationFrame(() => {
220
+ // Restore the original mutation handler
221
+ this.ignoreMutation = oldIgnoreMutation;
222
+
223
+ // Restore the selection only if:
224
+ // - We have a selection bookmark
225
+ // - Mutations were ignored during the table move
226
+ // - The cursor wasn't at the start of the node
227
+ if (selectionBookmark && mutationsIgnored && parentOffset > 0) {
228
+ this.view.dispatch(
229
+ this.view.state.tr.setSelection(selectionBookmark.resolve(this.view.state.tr.doc)),
230
+ );
231
+ }
232
+ });
233
+ }
234
+ }
235
+
150
236
  setDomAttrs(node: PmNode) {
151
237
  if (!this.table) {
152
238
  return;
@@ -206,7 +292,6 @@ export default class TableView extends ReactNodeView<Props> {
206
292
  />
207
293
  );
208
294
  }
209
-
210
295
  // Please, do not copy or use this kind of code below
211
296
  // @ts-ignore
212
297
  const fakePluginKey = {
@@ -17,7 +17,9 @@ type Config = {
17
17
  getEditorContainerWidth: GetEditorContainerWidth;
18
18
  isNestingSupported?: boolean;
19
19
  };
20
- export const tableNodeSpecWithFixedToDOM = (config: Config): NodeSpec => {
20
+ export const tableNodeSpecWithFixedToDOM = (
21
+ config: Config,
22
+ ): NodeSpec & { toDOM: (node: PMNode) => DOMOutputSpec } => {
21
23
  const tableNode = config.isNestingSupported ? tableWithNestedTable : table;
22
24
 
23
25
  return {
@@ -508,14 +508,13 @@ export const getToolbarConfig =
508
508
 
509
509
  const isTableOrColumnResizing = !!(resizeState?.dragging || tableWidthState?.resizing);
510
510
  const isTableMenuOpened = pluginState.isContextualMenuOpen || isDragHandleMenuOpened;
511
+ const isTableState =
512
+ isTableRowOrColumnDragged || isTableOrColumnResizing || isTableMenuOpened;
511
513
  const isViewMode = api?.editorViewMode?.sharedState.currentState()?.mode === 'view';
512
514
 
513
- const shouldSuppressAllToolbars =
514
- (!pluginState.editorHasFocus && !isViewMode) ||
515
- isTableMenuOpened ||
516
- isTableOrColumnResizing ||
517
- isTableRowOrColumnDragged;
518
-
515
+ // Note: when focus is in codeblocks, pluginState.editorHasFocus is false, so the codeblocks toolbar
516
+ // won't be suppressed.
517
+ const shouldSuppressAllToolbars = isTableState && pluginState.editorHasFocus && !isViewMode;
519
518
  if (shouldSuppressAllToolbars) {
520
519
  return {
521
520
  title: toolbarTitle,