@atlaskit/editor-plugin-layout 11.0.2 → 11.0.4

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 (30) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/layoutPlugin.js +18 -16
  3. package/dist/cjs/nodeviews/index.js +49 -1
  4. package/dist/cjs/pm-plugins/column-resize-divider.js +25 -7
  5. package/dist/cjs/pm-plugins/main.js +2 -2
  6. package/dist/cjs/pm-plugins/resizing.js +29 -4
  7. package/dist/cjs/ui/LayoutSSRReactContextsProvider.js +34 -0
  8. package/dist/es2019/layoutPlugin.js +20 -16
  9. package/dist/es2019/nodeviews/index.js +49 -1
  10. package/dist/es2019/pm-plugins/column-resize-divider.js +25 -7
  11. package/dist/es2019/pm-plugins/main.js +2 -2
  12. package/dist/es2019/pm-plugins/resizing.js +37 -7
  13. package/dist/es2019/ui/LayoutSSRReactContextsProvider.js +28 -0
  14. package/dist/esm/layoutPlugin.js +18 -16
  15. package/dist/esm/nodeviews/index.js +49 -1
  16. package/dist/esm/pm-plugins/column-resize-divider.js +25 -7
  17. package/dist/esm/pm-plugins/main.js +2 -2
  18. package/dist/esm/pm-plugins/resizing.js +29 -4
  19. package/dist/esm/ui/LayoutSSRReactContextsProvider.js +27 -0
  20. package/dist/types/nodeviews/index.d.ts +5 -0
  21. package/dist/types/pm-plugins/column-resize-divider.d.ts +2 -1
  22. package/dist/types/pm-plugins/main.d.ts +2 -1
  23. package/dist/types/pm-plugins/resizing.d.ts +2 -1
  24. package/dist/types/ui/LayoutSSRReactContextsProvider.d.ts +19 -0
  25. package/dist/types-ts4.5/nodeviews/index.d.ts +5 -0
  26. package/dist/types-ts4.5/pm-plugins/column-resize-divider.d.ts +2 -1
  27. package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -1
  28. package/dist/types-ts4.5/pm-plugins/resizing.d.ts +2 -1
  29. package/dist/types-ts4.5/ui/LayoutSSRReactContextsProvider.d.ts +19 -0
  30. package/package.json +7 -4
@@ -1,6 +1,8 @@
1
1
  import { bind } from 'bind-event-listener';
2
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
3
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
4
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
+ import { fg } from '@atlaskit/platform-feature-flags';
4
6
  import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
5
7
 
6
8
  // Class names for the column resize divider widget — must stay in sync with layout.ts in editor-core
@@ -15,7 +17,7 @@ let dragState = null;
15
17
  * Dispatches a single undoable ProseMirror transaction to commit the final
16
18
  * column widths after a drag completes.
17
19
  */
18
- const dispatchColumnWidths = (view, sectionPos, leftColIndex, leftWidth, rightWidth) => {
20
+ const dispatchColumnWidths = (view, sectionPos, leftColIndex, leftWidth, rightWidth, editorAnalyticsAPI) => {
19
21
  const {
20
22
  state
21
23
  } = view;
@@ -47,6 +49,21 @@ const dispatchColumnWidths = (view, sectionPos, leftColIndex, leftWidth, rightWi
47
49
  tr.replaceWith(sectionPos + 1, sectionPos + sectionNode.nodeSize - 1, Fragment.from(newColumns));
48
50
  tr.setMeta('layoutColumnResize', true);
49
51
  tr.setMeta('scrollIntoView', false);
52
+ if (fg('platform_editor_layout_resize_analytics')) {
53
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
54
+ action: ACTION.DRAGGED,
55
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
56
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
57
+ attributes: {
58
+ columnCount: sectionNode.childCount,
59
+ leftColumnIndex: leftColIndex,
60
+ leftColumnWidth: Number(leftWidth.toFixed(2)),
61
+ rightColumnWidth: Number(rightWidth.toFixed(2)),
62
+ inputMethod: INPUT_METHOD.DRAG
63
+ },
64
+ eventType: EVENT_TYPE.TRACK
65
+ })(tr);
66
+ }
50
67
  view.dispatch(tr);
51
68
  };
52
69
 
@@ -149,7 +166,8 @@ const onDragEnd = clientX => {
149
166
  rafId,
150
167
  startLeftWidth,
151
168
  startRightWidth,
152
- unbindListeners
169
+ unbindListeners,
170
+ editorAnalyticsAPI
153
171
  } = dragState;
154
172
  unbindListeners();
155
173
 
@@ -173,7 +191,7 @@ const onDragEnd = clientX => {
173
191
  leftColEl.style.flexBasis = '';
174
192
  rightColEl.style.flexBasis = '';
175
193
  if (widths && (widths.leftWidth !== startLeftWidth || widths.rightWidth !== startRightWidth)) {
176
- dispatchColumnWidths(view, sectionPos, leftColIndex, widths.leftWidth, widths.rightWidth);
194
+ dispatchColumnWidths(view, sectionPos, leftColIndex, widths.leftWidth, widths.rightWidth, editorAnalyticsAPI);
177
195
  }
178
196
  };
179
197
  const onDragMouseUp = e => {
@@ -200,8 +218,7 @@ const onDragCancel = () => {
200
218
  * column DOM elements for zero-overhead visual feedback (no PM transactions).
201
219
  * A single undoable PM transaction is dispatched on mouseup to commit the final widths.
202
220
  */
203
- const createColumnDividerWidget = (view, sectionPos, columnIndex // index of the column to the RIGHT of this divider
204
- ) => {
221
+ const createColumnDividerWidget = (view, sectionPos, columnIndex, editorAnalyticsAPI) => {
205
222
  const ownerDoc = view.dom.ownerDocument;
206
223
 
207
224
  // Outer container: wide transparent hit area for easy grabbing, zero flex footprint
@@ -296,6 +313,7 @@ const createColumnDividerWidget = (view, sectionPos, columnIndex // index of the
296
313
  const totalGap = childCount > 1 ? computedGap * (childCount - 1) : 0;
297
314
  const columnsWidth = sectionRect.width - dividersWidth - totalGap;
298
315
  dragState = {
316
+ editorAnalyticsAPI,
299
317
  hasDragged: false,
300
318
  lastClientX: e.clientX,
301
319
  rafId: null,
@@ -327,7 +345,7 @@ const createColumnDividerWidget = (view, sectionPos, columnIndex // index of the
327
345
  * Returns ProseMirror Decoration widgets for column dividers between layout columns.
328
346
  * Each divider supports drag-to-resize interaction for the adjacent columns.
329
347
  */
330
- export const getColumnDividerDecorations = (state, view) => {
348
+ export const getColumnDividerDecorations = (state, view, editorAnalyticsAPI) => {
331
349
  const decorations = [];
332
350
  if (!view) {
333
351
  return decorations;
@@ -344,7 +362,7 @@ export const getColumnDividerDecorations = (state, view) => {
344
362
  const sectionPos = pos;
345
363
  const colIndex = index;
346
364
  const widgetPos = pos + offset + 1; // position at the start of this column
347
- decorations.push(Decoration.widget(widgetPos, () => createColumnDividerWidget(view, sectionPos, colIndex), {
365
+ decorations.push(Decoration.widget(widgetPos, () => createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI), {
348
366
  side: -1,
349
367
  // place before the position
350
368
  key: `layout-col-divider-${pos}-${index}`,
@@ -96,7 +96,7 @@ const handleDeleteLayoutColumn = (state, dispatch) => {
96
96
  }
97
97
  return false;
98
98
  };
99
- export default (options => {
99
+ export default ((options, editorAnalyticsAPI) => {
100
100
  // Store a reference to the EditorView so widget decorations can dispatch transactions
101
101
  let editorViewRef;
102
102
  return new SafePlugin({
@@ -147,7 +147,7 @@ export default (options => {
147
147
  const layoutState = pluginKey.getState(state);
148
148
  const isLayoutResizingPluginAvailable = layoutResizingPluginKey.get(state) !== undefined;
149
149
  if (editorExperiment('advanced_layouts', true) && editorExperiment('platform_editor_layout_column_resize_handle', true) && isLayoutResizingPluginAvailable) {
150
- const dividerDecorations = getColumnDividerDecorations(state, editorViewRef);
150
+ const dividerDecorations = getColumnDividerDecorations(state, editorViewRef, editorAnalyticsAPI);
151
151
  const selectedDecorations = layoutState.pos !== null ? getNodeDecoration(layoutState.pos, state.doc.nodeAt(layoutState.pos)) : [];
152
152
  const allDecorations = [...selectedDecorations, ...dividerDecorations];
153
153
  if (allDecorations.length > 0) {
@@ -1,3 +1,4 @@
1
+ import { isSSR, isSSRStreaming } from '@atlaskit/editor-common/core-utils';
1
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
3
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
3
4
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
@@ -14,6 +15,28 @@ export const pluginKey = new PluginKey('layoutResizingPlugin');
14
15
  * (e.g. setting flex-basis to give real-time visual feedback without dispatching
15
16
  * PM transactions) are not "corrected" back by ProseMirror's DOM reconciliation.
16
17
  */
18
+ const isLayoutElementLike = element => {
19
+ if (isSSR() && isSSRStreaming()) {
20
+ // In SSR environments, `HTMLElement` is undefined globally so a plain
21
+ // `instanceof HTMLElement` check is always `false`. That makes the
22
+ // `DOMSerializer.renderSpec(...)` result get rejected by the guard below and
23
+ // the NodeView falls back to a bare `<div>`, losing every schema-defined
24
+ // attribute (`data-layout-column`, `style="flex-basis:..."`,
25
+ // `data-column-width`, plus the inner `<div data-layout-content="true">`
26
+ // wrapper) and breaking the layout's flex sizing in SSR output.
27
+ //
28
+ // To unblock SSR streaming without changing CSR semantics, we gate the check:
29
+ // - In SSR (and only when `platform_editor_editor_ssr_streaming` is enabled),
30
+ // use a duck-typed check that mirrors `safe-plugin`'s `isHTMLElement`.
31
+ // - Everywhere else, keep the original `instanceof HTMLElement` check exactly
32
+ // as it was so we don't accidentally widen acceptance in CSR.
33
+ if (element === null || element === undefined) {
34
+ return false;
35
+ }
36
+ return typeof element === 'object' && 'innerHTML' in element && 'style' in element && 'classList' in element;
37
+ }
38
+ return element instanceof HTMLElement;
39
+ };
17
40
  class LayoutColumnView {
18
41
  constructor(node, view, getPos) {
19
42
  // Use the NodeSpec's own toDOM to produce the correct DOM structure and attributes.
@@ -31,7 +54,7 @@ class LayoutColumnView {
31
54
  dom,
32
55
  contentDOM
33
56
  } = DOMSerializer.renderSpec(document, nodeType.spec.toDOM(node));
34
- if (!(dom instanceof HTMLElement) || !(contentDOM instanceof HTMLElement)) {
57
+ if (!isLayoutElementLike(dom) || !isLayoutElementLike(contentDOM)) {
35
58
  const fallbackDiv = document.createElement('div');
36
59
  this.dom = fallbackDiv;
37
60
  this.contentDOM = fallbackDiv;
@@ -56,7 +79,7 @@ class LayoutColumnView {
56
79
  return mutation.type === 'attributes' && mutation.attributeName === 'style';
57
80
  }
58
81
  }
59
- export default ((options, pluginInjectionApi, portalProviderAPI, eventDispatcher) => new SafePlugin({
82
+ export default ((options, pluginInjectionApi, portalProviderAPI, eventDispatcher, intl) => new SafePlugin({
60
83
  key: pluginKey,
61
84
  props: {
62
85
  nodeViews: {
@@ -68,13 +91,20 @@ export default ((options, pluginInjectionApi, portalProviderAPI, eventDispatcher
68
91
  portalProviderAPI,
69
92
  eventDispatcher,
70
93
  pluginInjectionApi,
71
- options
94
+ options,
95
+ intl
72
96
  }).init();
73
97
  },
74
- // Only register the column node view when the resize handle experiment is on.
75
- // It exists solely to suppress style-attribute MutationObserver callbacks
76
- // during drag, allowing direct flex-basis writes without PM interference.
77
- ...(editorExperiment('platform_editor_layout_column_resize_handle', true) ? {
98
+ // Register the column node view when EITHER:
99
+ // 1. The resize handle experiment is on (its original purpose:
100
+ // suppress style-attribute MutationObserver callbacks during
101
+ // drag, allowing direct flex-basis writes without PM
102
+ // interference).
103
+ // 2. SSR streaming is enabled — the column node view stamps
104
+ // `container-type: inline-size` inline on each column dom so
105
+ // that the SSR-rendered table inside the column constrains
106
+ // its width to the column (see comment in the constructor).
107
+ ...(editorExperiment('platform_editor_layout_column_resize_handle', true) || isSSR() && isSSRStreaming() ? {
78
108
  layoutColumn: (node, view, getPos) => new LayoutColumnView(node, view, getPos)
79
109
  } : {})
80
110
  }
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { RawIntlProvider } from 'react-intl';
3
+ import { isSSR, isSSRStreaming } from '@atlaskit/editor-common/core-utils';
4
+ /**
5
+ * Wraps the layout section nodeview children with the editor's actual
6
+ * IntlProvider during SSR streaming (renderToStaticMarkup). This ensures any
7
+ * descendants that call `useIntl()` (e.g. `BreakoutResizer`'s ARIA labels)
8
+ * have a valid intl context and do not throw during the static render pass.
9
+ *
10
+ * Outside of SSR streaming this is a no-op passthrough.
11
+ *
12
+ * Follows the same pattern as `MediaSSRReactContextsProvider` and
13
+ * `SyncBlockSSRReactContextsProvider`.
14
+ */
15
+ export function LayoutSSRReactContextsProvider({
16
+ children,
17
+ intl
18
+ }) {
19
+ if (!isSSRStreaming() || !isSSR()) {
20
+ return children;
21
+ }
22
+ if (!intl) {
23
+ return children;
24
+ }
25
+ return /*#__PURE__*/React.createElement(RawIntlProvider, {
26
+ value: intl
27
+ }, children);
28
+ }
@@ -50,7 +50,7 @@ export var selectIntoLayoutSection = function selectIntoLayoutSection(tr) {
50
50
  return tr;
51
51
  };
52
52
  export var layoutPlugin = function layoutPlugin(_ref) {
53
- var _api$analytics, _api$analytics4;
53
+ var _api$analytics2, _api$analytics5;
54
54
  var _ref$config = _ref.config,
55
55
  options = _ref$config === void 0 ? {} : _ref$config,
56
56
  api = _ref.api;
@@ -98,7 +98,8 @@ export var layoutPlugin = function layoutPlugin(_ref) {
98
98
  var plugins = [{
99
99
  name: 'layout',
100
100
  plugin: function plugin() {
101
- return createLayoutPlugin(options);
101
+ var _api$analytics;
102
+ return createLayoutPlugin(options, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
102
103
  }
103
104
  }];
104
105
  if ((options.editorAppearance === 'full-page' || options.editorAppearance === 'full-width' || options.editorAppearance === 'max' && editorExperiment('platform_editor_layout_column_resize_handle', true)) && api && editorExperiment('advanced_layouts', true)) {
@@ -106,15 +107,16 @@ export var layoutPlugin = function layoutPlugin(_ref) {
106
107
  name: 'layoutResizing',
107
108
  plugin: function plugin(_ref2) {
108
109
  var portalProviderAPI = _ref2.portalProviderAPI,
109
- eventDispatcher = _ref2.eventDispatcher;
110
- return createLayoutResizingPlugin(options, api, portalProviderAPI, eventDispatcher);
110
+ eventDispatcher = _ref2.eventDispatcher,
111
+ getIntl = _ref2.getIntl;
112
+ return createLayoutResizingPlugin(options, api, portalProviderAPI, eventDispatcher, getIntl());
111
113
  }
112
114
  });
113
115
  }
114
116
  return plugins;
115
117
  },
116
118
  actions: {
117
- insertLayoutColumns: insertLayoutColumnsWithAnalytics(api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)
119
+ insertLayoutColumns: insertLayoutColumnsWithAnalytics(api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions)
118
120
  },
119
121
  pluginsOptions: {
120
122
  floatingToolbar: function floatingToolbar(state, intl) {
@@ -136,8 +138,8 @@ export var layoutPlugin = function layoutPlugin(_ref) {
136
138
  quickInsert: function quickInsert(_ref3) {
137
139
  var formatMessage = _ref3.formatMessage;
138
140
  var withInsertLayoutAnalytics = function withInsertLayoutAnalytics(tr, columnCount) {
139
- var _api$analytics2;
140
- api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.attachAnalyticsEvent({
141
+ var _api$analytics3;
142
+ api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.attachAnalyticsEvent({
141
143
  action: ACTION.INSERTED,
142
144
  actionSubject: ACTION_SUBJECT.DOCUMENT,
143
145
  actionSubjectId: ACTION_SUBJECT_ID.LAYOUT,
@@ -333,9 +335,9 @@ export var layoutPlugin = function layoutPlugin(_ref) {
333
335
  return /*#__PURE__*/React.createElement(IconLayout, null);
334
336
  },
335
337
  action: function action(insert, state) {
336
- var _api$analytics3;
338
+ var _api$analytics4;
337
339
  var tr = insert(createDefaultLayoutSection(state));
338
- api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.attachAnalyticsEvent({
340
+ api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 || (_api$analytics4 = _api$analytics4.actions) === null || _api$analytics4 === void 0 || _api$analytics4.attachAnalyticsEvent({
339
341
  action: ACTION.INSERTED,
340
342
  actionSubject: ACTION_SUBJECT.DOCUMENT,
341
343
  actionSubjectId: ACTION_SUBJECT_ID.LAYOUT,
@@ -370,18 +372,18 @@ export var layoutPlugin = function layoutPlugin(_ref) {
370
372
  return pluginKey.getState(editorState);
371
373
  },
372
374
  commands: {
373
- deleteLayoutColumn: deleteLayoutColumn(api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions, api),
375
+ deleteLayoutColumn: deleteLayoutColumn(api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions, api),
374
376
  distributeLayoutColumns: function distributeLayoutColumns(options) {
375
- var _api$analytics5;
376
- return _distributeLayoutColumns(api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions, api)(options);
377
+ var _api$analytics6;
378
+ return _distributeLayoutColumns(api === null || api === void 0 || (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : _api$analytics6.actions, api)(options);
377
379
  },
378
380
  insertLayoutColumn: function insertLayoutColumn(side) {
379
- var _api$analytics6;
380
- return _insertLayoutColumn(side, api === null || api === void 0 || (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : _api$analytics6.actions, api);
381
+ var _api$analytics7;
382
+ return _insertLayoutColumn(side, api === null || api === void 0 || (_api$analytics7 = api.analytics) === null || _api$analytics7 === void 0 ? void 0 : _api$analytics7.actions, api);
381
383
  },
382
384
  setLayoutColumnValign: function setLayoutColumnValign(valign) {
383
- var _api$analytics7;
384
- return _setLayoutColumnValign(valign, api === null || api === void 0 || (_api$analytics7 = api.analytics) === null || _api$analytics7 === void 0 ? void 0 : _api$analytics7.actions, api);
385
+ var _api$analytics8;
386
+ return _setLayoutColumnValign(valign, api === null || api === void 0 || (_api$analytics8 = api.analytics) === null || _api$analytics8 === void 0 ? void 0 : _api$analytics8.actions, api);
385
387
  },
386
388
  toggleLayoutColumnMenu: toggleLayoutColumnMenu
387
389
  }
@@ -9,8 +9,9 @@ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.
9
9
  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; }
10
10
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
11
11
  import React, { useCallback } from 'react';
12
+ import { isSSR, isSSRStreaming } from '@atlaskit/editor-common/core-utils';
12
13
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
13
- import ReactNodeView from '@atlaskit/editor-common/react-node-view';
14
+ import ReactNodeView, { NodeViewContentHole } from '@atlaskit/editor-common/react-node-view';
14
15
  import { BreakoutResizer, ignoreResizerMutations } from '@atlaskit/editor-common/resizer';
15
16
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
16
17
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
@@ -18,6 +19,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
18
19
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
19
20
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
20
21
  import { selectIntoLayout } from '../pm-plugins/utils';
22
+ import { LayoutSSRReactContextsProvider } from '../ui/LayoutSSRReactContextsProvider';
21
23
  var layoutDynamicFullWidthGuidelineOffset = 16;
22
24
  var isEmptyParagraph = function isEmptyParagraph(node) {
23
25
  return !!node && node.type.name === 'paragraph' && !node.childCount;
@@ -131,6 +133,7 @@ export var LayoutSectionView = /*#__PURE__*/function (_ReactNodeView) {
131
133
  * @param props.eventDispatcher
132
134
  * @param props.pluginInjectionApi
133
135
  * @param props.options
136
+ * @param props.intl
134
137
  * @example
135
138
  */
136
139
  function LayoutSectionView(props) {
@@ -139,6 +142,7 @@ export var LayoutSectionView = /*#__PURE__*/function (_ReactNodeView) {
139
142
  _this = _callSuper(this, LayoutSectionView, [props.node, props.view, props.getPos, props.portalProviderAPI, props.eventDispatcher, props]);
140
143
  _this.isEmpty = isEmptyLayout(_this.node);
141
144
  _this.options = props.options;
145
+ _this.intl = props.intl;
142
146
  return _this;
143
147
  }
144
148
 
@@ -151,6 +155,13 @@ export var LayoutSectionView = /*#__PURE__*/function (_ReactNodeView) {
151
155
  return _createClass(LayoutSectionView, [{
152
156
  key: "getContentDOM",
153
157
  value: function getContentDOM() {
158
+ // Build the layout DOM via the schema's toDOM spec. This is the same
159
+ // path used in both CSR and SSR — the only SSR-specific concern is
160
+ // re-attaching `contentDOM` (= the `[data-layout-section]` element)
161
+ // after the portal's renderToStaticMarkup + innerHTML write detaches
162
+ // it. We handle that by stamping `data-ssr-content-dom-ref` on the
163
+ // outer container so `ReactNodeView.init()` can find a re-attach
164
+ // target inside `domRef` after the portal write.
154
165
  var _ref2 = DOMSerializer.renderSpec(document, toDOM(this.node)),
155
166
  container = _ref2.dom,
156
167
  contentDOM = _ref2.contentDOM;
@@ -163,6 +174,20 @@ export var LayoutSectionView = /*#__PURE__*/function (_ReactNodeView) {
163
174
  if (fg('platform_editor_adf_with_localid')) {
164
175
  this.layoutDOM.setAttribute('data-local-id', this.node.attrs.localId);
165
176
  }
177
+
178
+ // SSR streaming re-attach note:
179
+ // In SSR, `init()` appends `container` into `domRef`; the portal's
180
+ // renderToStaticMarkup + innerHTML write then wipes `domRef`,
181
+ // detaching the entire subtree (with PM-serialized children inside
182
+ // `[data-layout-section]`). React's `render()` emits a
183
+ // `<NodeViewContentHole/>` placeholder inside `domRef`; the SSR
184
+ // re-attach logic in `init()` finds it via `[data-ssr-content-dom-ref]`
185
+ // and calls `_handleRef`, which appends `contentDOMWrapper` (the
186
+ // detached `container`) back inside the placeholder. The end result
187
+ // is `domRef > NodeViewContentHole > layout-section-container >
188
+ // [data-layout-section] > [data-layout-column] children` — the
189
+ // layout DOM contract is preserved.
190
+
166
191
  return {
167
192
  dom: container,
168
193
  contentDOM: contentDOM
@@ -197,6 +222,29 @@ export var LayoutSectionView = /*#__PURE__*/function (_ReactNodeView) {
197
222
  if (this.layoutDOM) {
198
223
  this.layoutDOM.setAttribute('data-empty-layout', Boolean(this.isEmpty).toString());
199
224
  }
225
+
226
+ // SSR streaming path: render only a `<NodeViewContentHole/>` placeholder
227
+ // so ReactNodeView.init()'s SSR re-attach logic can find the marker
228
+ // (`data-ssr-content-dom-ref`) and re-append the detached
229
+ // contentDOMWrapper — which is the FULL layout structure
230
+ // (`layout-section-container > [data-layout-section] > children`) built
231
+ // in `getContentDOM` via DOMSerializer.renderSpec. This avoids
232
+ // duplicating layout structure between getContentDOM and render(), which
233
+ // previously caused an extra wrapping div between `[data-layout-section]`
234
+ // and the `[data-layout-column]` children and broke the flex layout.
235
+ //
236
+ // The BreakoutResizer is intentionally omitted in SSR — it relies on
237
+ // browser-only APIs and contributes no useful static markup. The
238
+ // LayoutSSRReactContextsProvider wraps the placeholder to inject the
239
+ // editor's IntlShape, defending against any descendants that call
240
+ // `useIntl()` during renderToStaticMarkup.
241
+ if (isSSR() && isSSRStreaming()) {
242
+ return /*#__PURE__*/React.createElement(LayoutSSRReactContextsProvider, {
243
+ intl: this.intl
244
+ }, /*#__PURE__*/React.createElement(NodeViewContentHole, {
245
+ ref: forwardRef
246
+ }));
247
+ }
200
248
  if (expValEquals('platform_editor_breakout_resizing', 'isEnabled', true)) {
201
249
  return null;
202
250
  }
@@ -2,8 +2,10 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  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; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  import { bind } from 'bind-event-listener';
5
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
5
6
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
6
7
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
8
+ import { fg } from '@atlaskit/platform-feature-flags';
7
9
  import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
8
10
 
9
11
  // Class names for the column resize divider widget — must stay in sync with layout.ts in editor-core
@@ -18,7 +20,7 @@ var dragState = null;
18
20
  * Dispatches a single undoable ProseMirror transaction to commit the final
19
21
  * column widths after a drag completes.
20
22
  */
21
- var dispatchColumnWidths = function dispatchColumnWidths(view, sectionPos, leftColIndex, leftWidth, rightWidth) {
23
+ var dispatchColumnWidths = function dispatchColumnWidths(view, sectionPos, leftColIndex, leftWidth, rightWidth, editorAnalyticsAPI) {
22
24
  var state = view.state;
23
25
  var sectionNode = state.doc.nodeAt(sectionPos);
24
26
  if (!sectionNode) {
@@ -45,6 +47,21 @@ var dispatchColumnWidths = function dispatchColumnWidths(view, sectionPos, leftC
45
47
  tr.replaceWith(sectionPos + 1, sectionPos + sectionNode.nodeSize - 1, Fragment.from(newColumns));
46
48
  tr.setMeta('layoutColumnResize', true);
47
49
  tr.setMeta('scrollIntoView', false);
50
+ if (fg('platform_editor_layout_resize_analytics')) {
51
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
52
+ action: ACTION.DRAGGED,
53
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
54
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
55
+ attributes: {
56
+ columnCount: sectionNode.childCount,
57
+ leftColumnIndex: leftColIndex,
58
+ leftColumnWidth: Number(leftWidth.toFixed(2)),
59
+ rightColumnWidth: Number(rightWidth.toFixed(2)),
60
+ inputMethod: INPUT_METHOD.DRAG
61
+ },
62
+ eventType: EVENT_TYPE.TRACK
63
+ })(tr);
64
+ }
48
65
  view.dispatch(tr);
49
66
  };
50
67
 
@@ -146,7 +163,8 @@ var onDragEnd = function onDragEnd(clientX) {
146
163
  rafId = _dragState2.rafId,
147
164
  startLeftWidth = _dragState2.startLeftWidth,
148
165
  startRightWidth = _dragState2.startRightWidth,
149
- unbindListeners = _dragState2.unbindListeners;
166
+ unbindListeners = _dragState2.unbindListeners,
167
+ editorAnalyticsAPI = _dragState2.editorAnalyticsAPI;
150
168
  unbindListeners();
151
169
 
152
170
  // Cancel any pending rAF so a stale frame doesn't write styles after teardown.
@@ -169,7 +187,7 @@ var onDragEnd = function onDragEnd(clientX) {
169
187
  leftColEl.style.flexBasis = '';
170
188
  rightColEl.style.flexBasis = '';
171
189
  if (widths && (widths.leftWidth !== startLeftWidth || widths.rightWidth !== startRightWidth)) {
172
- dispatchColumnWidths(view, sectionPos, leftColIndex, widths.leftWidth, widths.rightWidth);
190
+ dispatchColumnWidths(view, sectionPos, leftColIndex, widths.leftWidth, widths.rightWidth, editorAnalyticsAPI);
173
191
  }
174
192
  };
175
193
  var onDragMouseUp = function onDragMouseUp(e) {
@@ -196,8 +214,7 @@ var onDragCancel = function onDragCancel() {
196
214
  * column DOM elements for zero-overhead visual feedback (no PM transactions).
197
215
  * A single undoable PM transaction is dispatched on mouseup to commit the final widths.
198
216
  */
199
- var createColumnDividerWidget = function createColumnDividerWidget(view, sectionPos, columnIndex // index of the column to the RIGHT of this divider
200
- ) {
217
+ var createColumnDividerWidget = function createColumnDividerWidget(view, sectionPos, columnIndex, editorAnalyticsAPI) {
201
218
  var ownerDoc = view.dom.ownerDocument;
202
219
 
203
220
  // Outer container: wide transparent hit area for easy grabbing, zero flex footprint
@@ -292,6 +309,7 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
292
309
  var totalGap = childCount > 1 ? computedGap * (childCount - 1) : 0;
293
310
  var columnsWidth = sectionRect.width - dividersWidth - totalGap;
294
311
  dragState = {
312
+ editorAnalyticsAPI: editorAnalyticsAPI,
295
313
  hasDragged: false,
296
314
  lastClientX: e.clientX,
297
315
  rafId: null,
@@ -323,7 +341,7 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
323
341
  * Returns ProseMirror Decoration widgets for column dividers between layout columns.
324
342
  * Each divider supports drag-to-resize interaction for the adjacent columns.
325
343
  */
326
- export var getColumnDividerDecorations = function getColumnDividerDecorations(state, view) {
344
+ export var getColumnDividerDecorations = function getColumnDividerDecorations(state, view, editorAnalyticsAPI) {
327
345
  var decorations = [];
328
346
  if (!view) {
329
347
  return decorations;
@@ -339,7 +357,7 @@ export var getColumnDividerDecorations = function getColumnDividerDecorations(st
339
357
  var colIndex = index;
340
358
  var widgetPos = pos + offset + 1; // position at the start of this column
341
359
  decorations.push(Decoration.widget(widgetPos, function () {
342
- return createColumnDividerWidget(view, sectionPos, colIndex);
360
+ return createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI);
343
361
  }, {
344
362
  side: -1,
345
363
  // place before the position
@@ -95,7 +95,7 @@ var handleDeleteLayoutColumn = function handleDeleteLayoutColumn(state, dispatch
95
95
  }
96
96
  return false;
97
97
  };
98
- export default (function (options) {
98
+ export default (function (options, editorAnalyticsAPI) {
99
99
  // Store a reference to the EditorView so widget decorations can dispatch transactions
100
100
  var editorViewRef;
101
101
  return new SafePlugin({
@@ -145,7 +145,7 @@ export default (function (options) {
145
145
  var layoutState = pluginKey.getState(state);
146
146
  var isLayoutResizingPluginAvailable = layoutResizingPluginKey.get(state) !== undefined;
147
147
  if (editorExperiment('advanced_layouts', true) && editorExperiment('platform_editor_layout_column_resize_handle', true) && isLayoutResizingPluginAvailable) {
148
- var dividerDecorations = getColumnDividerDecorations(state, editorViewRef);
148
+ var dividerDecorations = getColumnDividerDecorations(state, editorViewRef, editorAnalyticsAPI);
149
149
  var selectedDecorations = layoutState.pos !== null ? getNodeDecoration(layoutState.pos, state.doc.nodeAt(layoutState.pos)) : [];
150
150
  var allDecorations = [].concat(_toConsumableArray(selectedDecorations), _toConsumableArray(dividerDecorations));
151
151
  if (allDecorations.length > 0) {
@@ -1,8 +1,10 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
3
  import _createClass from "@babel/runtime/helpers/createClass";
4
+ import _typeof from "@babel/runtime/helpers/typeof";
4
5
  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; }
5
6
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
7
+ import { isSSR, isSSRStreaming } from '@atlaskit/editor-common/core-utils';
6
8
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
7
9
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
8
10
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
@@ -19,6 +21,28 @@ export var pluginKey = new PluginKey('layoutResizingPlugin');
19
21
  * (e.g. setting flex-basis to give real-time visual feedback without dispatching
20
22
  * PM transactions) are not "corrected" back by ProseMirror's DOM reconciliation.
21
23
  */
24
+ var isLayoutElementLike = function isLayoutElementLike(element) {
25
+ if (isSSR() && isSSRStreaming()) {
26
+ // In SSR environments, `HTMLElement` is undefined globally so a plain
27
+ // `instanceof HTMLElement` check is always `false`. That makes the
28
+ // `DOMSerializer.renderSpec(...)` result get rejected by the guard below and
29
+ // the NodeView falls back to a bare `<div>`, losing every schema-defined
30
+ // attribute (`data-layout-column`, `style="flex-basis:..."`,
31
+ // `data-column-width`, plus the inner `<div data-layout-content="true">`
32
+ // wrapper) and breaking the layout's flex sizing in SSR output.
33
+ //
34
+ // To unblock SSR streaming without changing CSR semantics, we gate the check:
35
+ // - In SSR (and only when `platform_editor_editor_ssr_streaming` is enabled),
36
+ // use a duck-typed check that mirrors `safe-plugin`'s `isHTMLElement`.
37
+ // - Everywhere else, keep the original `instanceof HTMLElement` check exactly
38
+ // as it was so we don't accidentally widen acceptance in CSR.
39
+ if (element === null || element === undefined) {
40
+ return false;
41
+ }
42
+ return _typeof(element) === 'object' && 'innerHTML' in element && 'style' in element && 'classList' in element;
43
+ }
44
+ return element instanceof HTMLElement;
45
+ };
22
46
  var LayoutColumnView = /*#__PURE__*/function () {
23
47
  function LayoutColumnView(node, view, getPos) {
24
48
  _classCallCheck(this, LayoutColumnView);
@@ -36,7 +60,7 @@ var LayoutColumnView = /*#__PURE__*/function () {
36
60
  var _DOMSerializer$render = DOMSerializer.renderSpec(document, nodeType.spec.toDOM(node)),
37
61
  dom = _DOMSerializer$render.dom,
38
62
  contentDOM = _DOMSerializer$render.contentDOM;
39
- if (!(dom instanceof HTMLElement) || !(contentDOM instanceof HTMLElement)) {
63
+ if (!isLayoutElementLike(dom) || !isLayoutElementLike(contentDOM)) {
40
64
  var _fallbackDiv = document.createElement('div');
41
65
  this.dom = _fallbackDiv;
42
66
  this.contentDOM = _fallbackDiv;
@@ -64,7 +88,7 @@ var LayoutColumnView = /*#__PURE__*/function () {
64
88
  }
65
89
  }]);
66
90
  }();
67
- export default (function (options, pluginInjectionApi, portalProviderAPI, eventDispatcher) {
91
+ export default (function (options, pluginInjectionApi, portalProviderAPI, eventDispatcher, intl) {
68
92
  return new SafePlugin({
69
93
  key: pluginKey,
70
94
  props: {
@@ -77,10 +101,11 @@ export default (function (options, pluginInjectionApi, portalProviderAPI, eventD
77
101
  portalProviderAPI: portalProviderAPI,
78
102
  eventDispatcher: eventDispatcher,
79
103
  pluginInjectionApi: pluginInjectionApi,
80
- options: options
104
+ options: options,
105
+ intl: intl
81
106
  }).init();
82
107
  }
83
- }, editorExperiment('platform_editor_layout_column_resize_handle', true) ? {
108
+ }, editorExperiment('platform_editor_layout_column_resize_handle', true) || isSSR() && isSSRStreaming() ? {
84
109
  layoutColumn: function layoutColumn(node, view, getPos) {
85
110
  return new LayoutColumnView(node, view, getPos);
86
111
  }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { RawIntlProvider } from 'react-intl';
3
+ import { isSSR, isSSRStreaming } from '@atlaskit/editor-common/core-utils';
4
+ /**
5
+ * Wraps the layout section nodeview children with the editor's actual
6
+ * IntlProvider during SSR streaming (renderToStaticMarkup). This ensures any
7
+ * descendants that call `useIntl()` (e.g. `BreakoutResizer`'s ARIA labels)
8
+ * have a valid intl context and do not throw during the static render pass.
9
+ *
10
+ * Outside of SSR streaming this is a no-op passthrough.
11
+ *
12
+ * Follows the same pattern as `MediaSSRReactContextsProvider` and
13
+ * `SyncBlockSSRReactContextsProvider`.
14
+ */
15
+ export function LayoutSSRReactContextsProvider(_ref) {
16
+ var children = _ref.children,
17
+ intl = _ref.intl;
18
+ if (!isSSRStreaming() || !isSSR()) {
19
+ return children;
20
+ }
21
+ if (!intl) {
22
+ return children;
23
+ }
24
+ return /*#__PURE__*/React.createElement(RawIntlProvider, {
25
+ value: intl
26
+ }, children);
27
+ }