@atlaskit/editor-plugin-media 16.0.6 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/editor-plugin-media
2
2
 
3
+ ## 16.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`2d23b40eed4f0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2d23b40eed4f0) -
8
+ Add gated content-visibility:auto to tables, expands and media singles to improve editor rendering
9
+ performance on large documents. Applied only to nodes whose rendered size can be estimated closely
10
+ — structurally (row/child counts) for tables and expands, and from the media's own dimensions and
11
+ the renderer's width calculation for media singles. Behind the
12
+ cc_editor_limited_mode_perf_improvements experiment and only active when limited mode is enabled
13
+ for the document (shared large-document detection with the limited-mode plugin).
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+
3
19
  ## 16.0.6
4
20
 
5
21
  ### Patch Changes
@@ -19,6 +19,7 @@ var _providerFactory = require("@atlaskit/editor-common/provider-factory");
19
19
  var _reactNodeView = _interopRequireDefault(require("@atlaskit/editor-common/react-node-view"));
20
20
  var _useSharedPluginStateSelector = require("@atlaskit/editor-common/use-shared-plugin-state-selector");
21
21
  var _utils = require("@atlaskit/editor-common/utils");
22
+ var _contentVisibility = require("@atlaskit/editor-common/utils/content-visibility");
22
23
  var _main = require("../pm-plugins/main");
23
24
  var _MediaSSRReactContextsProvider = require("../ui/MediaSSRReactContextsProvider");
24
25
  var _mediaSingleNext = require("./mediaSingleNext");
@@ -147,6 +148,7 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
147
148
  domRef.classList.add('media-extended-resize-experience');
148
149
  }
149
150
  domRef.setAttribute('data-media-vc-wrapper', 'true');
151
+ this.updateContentVisibility(domRef);
150
152
  return domRef;
151
153
  }
152
154
  }, {
@@ -158,6 +160,28 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
158
160
  dom: dom
159
161
  };
160
162
  }
163
+
164
+ /**
165
+ * Skip rendering off-screen media in large (limited-mode) documents; the estimate is derived from
166
+ * the media's own dimensions and the renderer's width calculation. Called from both `createDomRef`
167
+ * and `update()` because limited mode can flip from disabled→enabled after the document loads.
168
+ * Takes the target element explicitly because `createDomRef` runs before `this.dom` is assigned.
169
+ */
170
+ }, {
171
+ key: "updateContentVisibility",
172
+ value: function updateContentVisibility(dom) {
173
+ var _api$limitedMode,
174
+ _this2 = this;
175
+ var api = this.reactComponentProps.pluginInjectionApi;
176
+ // Read limited mode from `this.view.state` via the exposed plugin key, NOT `currentState().enabled`
177
+ // (which reads a stale false during initial EditorView construction — see the table nodeView).
178
+ var enabled = Boolean(api === null || api === void 0 || (_api$limitedMode = api.limitedMode) === null || _api$limitedMode === void 0 || (_api$limitedMode = _api$limitedMode.sharedState.currentState()) === null || _api$limitedMode === void 0 || (_api$limitedMode = _api$limitedMode.limitedModePluginKey) === null || _api$limitedMode === void 0 || (_api$limitedMode = _api$limitedMode.getState(this.view.state)) === null || _api$limitedMode === void 0 ? void 0 : _api$limitedMode.documentSizeBreachesThreshold);
179
+ (0, _contentVisibility.applyContentVisibility)(dom, enabled, function () {
180
+ var _api$width, _widthState$lineLengt, _widthState$width;
181
+ var widthState = api === null || api === void 0 || (_api$width = api.width) === null || _api$width === void 0 ? void 0 : _api$width.sharedState.currentState();
182
+ return (0, _contentVisibility.estimateMediaSingleIntrinsicSize)(_this2.node, (_widthState$lineLengt = widthState === null || widthState === void 0 ? void 0 : widthState.lineLength) !== null && _widthState$lineLengt !== void 0 ? _widthState$lineLengt : 0, (_widthState$width = widthState === null || widthState === void 0 ? void 0 : widthState.width) !== null && _widthState$width !== void 0 ? _widthState$width : 0);
183
+ });
184
+ }
161
185
  }, {
162
186
  key: "viewShouldUpdate",
163
187
  value: function viewShouldUpdate(nextNode) {
@@ -180,10 +204,10 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
180
204
  key: "subscribeToViewModeChange",
181
205
  value: function subscribeToViewModeChange(domRef) {
182
206
  var _this$reactComponentP3,
183
- _this2 = this;
207
+ _this3 = this;
184
208
  return (_this$reactComponentP3 = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP3 === void 0 || (_this$reactComponentP3 = _this$reactComponentP3.editorViewMode) === null || _this$reactComponentP3 === void 0 ? void 0 : _this$reactComponentP3.sharedState.onChange(function (viewModeState) {
185
209
  var _viewModeState$nextSh;
186
- _this2.updateDomRefContentEditable(domRef, (_viewModeState$nextSh = viewModeState.nextSharedState) === null || _viewModeState$nextSh === void 0 ? void 0 : _viewModeState$nextSh.mode);
210
+ _this3.updateDomRefContentEditable(domRef, (_viewModeState$nextSh = viewModeState.nextSharedState) === null || _viewModeState$nextSh === void 0 ? void 0 : _viewModeState$nextSh.mode);
187
211
  });
188
212
  }
189
213
  }, {
@@ -225,10 +249,10 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
225
249
  }, {
226
250
  key: "update",
227
251
  value: function update(node, decorations, _innerDecorations, isValidUpdate) {
228
- var _this3 = this;
252
+ var _this4 = this;
229
253
  if (!isValidUpdate) {
230
254
  isValidUpdate = function isValidUpdate(currentNode, newNode) {
231
- return _this3.getNodeMediaId(currentNode) === _this3.getNodeMediaId(newNode);
255
+ return _this4.getNodeMediaId(currentNode) === _this4.getNodeMediaId(newNode);
232
256
  };
233
257
  }
234
258
  // Detect mediaSingle width attribute changes and signal child media node to update
@@ -236,12 +260,16 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
236
260
  var target = this.dom.querySelector('div[data-prosemirror-node-name="media"]');
237
261
  target === null || target === void 0 || target.dispatchEvent(new CustomEvent('resized'));
238
262
  }
239
- return _superPropGet(MediaSingleNodeView, "update", this, 3)([node, decorations, _innerDecorations, isValidUpdate]);
263
+ var didUpdate = _superPropGet(MediaSingleNodeView, "update", this, 3)([node, decorations, _innerDecorations, isValidUpdate]);
264
+ if (this.dom) {
265
+ this.updateContentVisibility(this.dom);
266
+ }
267
+ return didUpdate;
240
268
  }
241
269
  }, {
242
270
  key: "render",
243
271
  value: function render(props, forwardRef) {
244
- var _this4 = this;
272
+ var _this5 = this;
245
273
  var _this$reactComponentP5 = this.reactComponentProps,
246
274
  eventDispatcher = _this$reactComponentP5.eventDispatcher,
247
275
  fullWidthMode = _this$reactComponentP5.fullWidthMode,
@@ -267,12 +295,12 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
267
295
  return (0, _react2.jsx)(MediaSingleNodeWrapper, {
268
296
  pluginInjectionApi: pluginInjectionApi,
269
297
  contextIdentifierProvider: contextIdentifierProvider,
270
- node: _this4.node,
298
+ node: _this5.node,
271
299
  getPos: getPos,
272
300
  mediaOptions: mediaOptions,
273
- view: _this4.view,
301
+ view: _this5.view,
274
302
  fullWidthMode: fullWidthMode,
275
- selected: _this4.isNodeSelected,
303
+ selected: _this5.isNodeSelected,
276
304
  eventDispatcher: eventDispatcher
277
305
  // Ignored via go/ees005
278
306
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -14,6 +14,7 @@ import ReactNodeView from '@atlaskit/editor-common/react-node-view';
14
14
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
15
15
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
16
16
  import { isNodeSelectedOrInRange } from '@atlaskit/editor-common/utils';
17
+ import { applyContentVisibility, estimateMediaSingleIntrinsicSize } from '@atlaskit/editor-common/utils/content-visibility';
17
18
  import { MEDIA_CONTENT_WRAP_CLASS_NAME } from '../pm-plugins/main';
18
19
  import { MediaSSRReactContextsProvider } from '../ui/MediaSSRReactContextsProvider';
19
20
  import { MediaSingleNodeNext } from './mediaSingleNext';
@@ -125,6 +126,7 @@ class MediaSingleNodeView extends ReactNodeView {
125
126
  domRef.classList.add('media-extended-resize-experience');
126
127
  }
127
128
  domRef.setAttribute('data-media-vc-wrapper', 'true');
129
+ this.updateContentVisibility(domRef);
128
130
  return domRef;
129
131
  }
130
132
  getContentDOM() {
@@ -134,6 +136,25 @@ class MediaSingleNodeView extends ReactNodeView {
134
136
  dom
135
137
  };
136
138
  }
139
+
140
+ /**
141
+ * Skip rendering off-screen media in large (limited-mode) documents; the estimate is derived from
142
+ * the media's own dimensions and the renderer's width calculation. Called from both `createDomRef`
143
+ * and `update()` because limited mode can flip from disabled→enabled after the document loads.
144
+ * Takes the target element explicitly because `createDomRef` runs before `this.dom` is assigned.
145
+ */
146
+ updateContentVisibility(dom) {
147
+ var _api$limitedMode, _api$limitedMode$shar, _api$limitedMode$shar2, _api$limitedMode$shar3;
148
+ const api = this.reactComponentProps.pluginInjectionApi;
149
+ // Read limited mode from `this.view.state` via the exposed plugin key, NOT `currentState().enabled`
150
+ // (which reads a stale false during initial EditorView construction — see the table nodeView).
151
+ const enabled = Boolean(api === null || api === void 0 ? void 0 : (_api$limitedMode = api.limitedMode) === null || _api$limitedMode === void 0 ? void 0 : (_api$limitedMode$shar = _api$limitedMode.sharedState.currentState()) === null || _api$limitedMode$shar === void 0 ? void 0 : (_api$limitedMode$shar2 = _api$limitedMode$shar.limitedModePluginKey) === null || _api$limitedMode$shar2 === void 0 ? void 0 : (_api$limitedMode$shar3 = _api$limitedMode$shar2.getState(this.view.state)) === null || _api$limitedMode$shar3 === void 0 ? void 0 : _api$limitedMode$shar3.documentSizeBreachesThreshold);
152
+ applyContentVisibility(dom, enabled, () => {
153
+ var _api$width, _widthState$lineLengt, _widthState$width;
154
+ const widthState = api === null || api === void 0 ? void 0 : (_api$width = api.width) === null || _api$width === void 0 ? void 0 : _api$width.sharedState.currentState();
155
+ return estimateMediaSingleIntrinsicSize(this.node, (_widthState$lineLengt = widthState === null || widthState === void 0 ? void 0 : widthState.lineLength) !== null && _widthState$lineLengt !== void 0 ? _widthState$lineLengt : 0, (_widthState$width = widthState === null || widthState === void 0 ? void 0 : widthState.width) !== null && _widthState$width !== void 0 ? _widthState$width : 0);
156
+ });
157
+ }
137
158
  viewShouldUpdate(nextNode) {
138
159
  if (this.forceViewUpdate) {
139
160
  this.forceViewUpdate = false;
@@ -196,7 +217,11 @@ class MediaSingleNodeView extends ReactNodeView {
196
217
  const target = this.dom.querySelector('div[data-prosemirror-node-name="media"]');
197
218
  target === null || target === void 0 ? void 0 : target.dispatchEvent(new CustomEvent('resized'));
198
219
  }
199
- return super.update(node, decorations, _innerDecorations, isValidUpdate);
220
+ const didUpdate = super.update(node, decorations, _innerDecorations, isValidUpdate);
221
+ if (this.dom) {
222
+ this.updateContentVisibility(this.dom);
223
+ }
224
+ return didUpdate;
200
225
  }
201
226
  render(props, forwardRef) {
202
227
  const {
@@ -23,6 +23,7 @@ import ReactNodeView from '@atlaskit/editor-common/react-node-view';
23
23
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
24
24
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
25
25
  import { isNodeSelectedOrInRange } from '@atlaskit/editor-common/utils';
26
+ import { applyContentVisibility, estimateMediaSingleIntrinsicSize } from '@atlaskit/editor-common/utils/content-visibility';
26
27
  import { MEDIA_CONTENT_WRAP_CLASS_NAME } from '../pm-plugins/main';
27
28
  import { MediaSSRReactContextsProvider } from '../ui/MediaSSRReactContextsProvider';
28
29
  import { MediaSingleNodeNext } from './mediaSingleNext';
@@ -143,6 +144,7 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
143
144
  domRef.classList.add('media-extended-resize-experience');
144
145
  }
145
146
  domRef.setAttribute('data-media-vc-wrapper', 'true');
147
+ this.updateContentVisibility(domRef);
146
148
  return domRef;
147
149
  }
148
150
  }, {
@@ -154,6 +156,28 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
154
156
  dom: dom
155
157
  };
156
158
  }
159
+
160
+ /**
161
+ * Skip rendering off-screen media in large (limited-mode) documents; the estimate is derived from
162
+ * the media's own dimensions and the renderer's width calculation. Called from both `createDomRef`
163
+ * and `update()` because limited mode can flip from disabled→enabled after the document loads.
164
+ * Takes the target element explicitly because `createDomRef` runs before `this.dom` is assigned.
165
+ */
166
+ }, {
167
+ key: "updateContentVisibility",
168
+ value: function updateContentVisibility(dom) {
169
+ var _api$limitedMode,
170
+ _this2 = this;
171
+ var api = this.reactComponentProps.pluginInjectionApi;
172
+ // Read limited mode from `this.view.state` via the exposed plugin key, NOT `currentState().enabled`
173
+ // (which reads a stale false during initial EditorView construction — see the table nodeView).
174
+ var enabled = Boolean(api === null || api === void 0 || (_api$limitedMode = api.limitedMode) === null || _api$limitedMode === void 0 || (_api$limitedMode = _api$limitedMode.sharedState.currentState()) === null || _api$limitedMode === void 0 || (_api$limitedMode = _api$limitedMode.limitedModePluginKey) === null || _api$limitedMode === void 0 || (_api$limitedMode = _api$limitedMode.getState(this.view.state)) === null || _api$limitedMode === void 0 ? void 0 : _api$limitedMode.documentSizeBreachesThreshold);
175
+ applyContentVisibility(dom, enabled, function () {
176
+ var _api$width, _widthState$lineLengt, _widthState$width;
177
+ var widthState = api === null || api === void 0 || (_api$width = api.width) === null || _api$width === void 0 ? void 0 : _api$width.sharedState.currentState();
178
+ return estimateMediaSingleIntrinsicSize(_this2.node, (_widthState$lineLengt = widthState === null || widthState === void 0 ? void 0 : widthState.lineLength) !== null && _widthState$lineLengt !== void 0 ? _widthState$lineLengt : 0, (_widthState$width = widthState === null || widthState === void 0 ? void 0 : widthState.width) !== null && _widthState$width !== void 0 ? _widthState$width : 0);
179
+ });
180
+ }
157
181
  }, {
158
182
  key: "viewShouldUpdate",
159
183
  value: function viewShouldUpdate(nextNode) {
@@ -176,10 +200,10 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
176
200
  key: "subscribeToViewModeChange",
177
201
  value: function subscribeToViewModeChange(domRef) {
178
202
  var _this$reactComponentP3,
179
- _this2 = this;
203
+ _this3 = this;
180
204
  return (_this$reactComponentP3 = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP3 === void 0 || (_this$reactComponentP3 = _this$reactComponentP3.editorViewMode) === null || _this$reactComponentP3 === void 0 ? void 0 : _this$reactComponentP3.sharedState.onChange(function (viewModeState) {
181
205
  var _viewModeState$nextSh;
182
- _this2.updateDomRefContentEditable(domRef, (_viewModeState$nextSh = viewModeState.nextSharedState) === null || _viewModeState$nextSh === void 0 ? void 0 : _viewModeState$nextSh.mode);
206
+ _this3.updateDomRefContentEditable(domRef, (_viewModeState$nextSh = viewModeState.nextSharedState) === null || _viewModeState$nextSh === void 0 ? void 0 : _viewModeState$nextSh.mode);
183
207
  });
184
208
  }
185
209
  }, {
@@ -221,10 +245,10 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
221
245
  }, {
222
246
  key: "update",
223
247
  value: function update(node, decorations, _innerDecorations, isValidUpdate) {
224
- var _this3 = this;
248
+ var _this4 = this;
225
249
  if (!isValidUpdate) {
226
250
  isValidUpdate = function isValidUpdate(currentNode, newNode) {
227
- return _this3.getNodeMediaId(currentNode) === _this3.getNodeMediaId(newNode);
251
+ return _this4.getNodeMediaId(currentNode) === _this4.getNodeMediaId(newNode);
228
252
  };
229
253
  }
230
254
  // Detect mediaSingle width attribute changes and signal child media node to update
@@ -232,12 +256,16 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
232
256
  var target = this.dom.querySelector('div[data-prosemirror-node-name="media"]');
233
257
  target === null || target === void 0 || target.dispatchEvent(new CustomEvent('resized'));
234
258
  }
235
- return _superPropGet(MediaSingleNodeView, "update", this, 3)([node, decorations, _innerDecorations, isValidUpdate]);
259
+ var didUpdate = _superPropGet(MediaSingleNodeView, "update", this, 3)([node, decorations, _innerDecorations, isValidUpdate]);
260
+ if (this.dom) {
261
+ this.updateContentVisibility(this.dom);
262
+ }
263
+ return didUpdate;
236
264
  }
237
265
  }, {
238
266
  key: "render",
239
267
  value: function render(props, forwardRef) {
240
- var _this4 = this;
268
+ var _this5 = this;
241
269
  var _this$reactComponentP5 = this.reactComponentProps,
242
270
  eventDispatcher = _this$reactComponentP5.eventDispatcher,
243
271
  fullWidthMode = _this$reactComponentP5.fullWidthMode,
@@ -263,12 +291,12 @@ var MediaSingleNodeView = /*#__PURE__*/function (_ReactNodeView) {
263
291
  return jsx(MediaSingleNodeWrapper, {
264
292
  pluginInjectionApi: pluginInjectionApi,
265
293
  contextIdentifierProvider: contextIdentifierProvider,
266
- node: _this4.node,
294
+ node: _this5.node,
267
295
  getPos: getPos,
268
296
  mediaOptions: mediaOptions,
269
- view: _this4.view,
297
+ view: _this5.view,
270
298
  fullWidthMode: fullWidthMode,
271
- selected: _this4.isNodeSelected,
299
+ selected: _this5.isNodeSelected,
272
300
  eventDispatcher: eventDispatcher
273
301
  // Ignored via go/ees005
274
302
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -15,6 +15,7 @@ import type { FocusPlugin } from '@atlaskit/editor-plugin-focus';
15
15
  import type { GridPlugin } from '@atlaskit/editor-plugin-grid';
16
16
  import type { GuidelinePlugin } from '@atlaskit/editor-plugin-guideline';
17
17
  import type { InteractionPlugin } from '@atlaskit/editor-plugin-interaction';
18
+ import type { LimitedModePlugin } from '@atlaskit/editor-plugin-limited-mode/limited-mode-plugin-type';
18
19
  import type { MediaEditingPlugin } from '@atlaskit/editor-plugin-media-editing';
19
20
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
20
21
  import type { ToolbarPlugin } from '@atlaskit/editor-plugin-toolbar';
@@ -44,7 +45,8 @@ export type MediaPluginDependencies = [
44
45
  OptionalPlugin<ConnectivityPlugin>,
45
46
  OptionalPlugin<InteractionPlugin>,
46
47
  OptionalPlugin<ToolbarPlugin>,
47
- OptionalPlugin<MediaEditingPlugin>
48
+ OptionalPlugin<MediaEditingPlugin>,
49
+ OptionalPlugin<LimitedModePlugin>
48
50
  ];
49
51
  export type MediaNextEditorPluginType = NextEditorPlugin<'media', {
50
52
  actions: {
@@ -22,6 +22,13 @@ declare class MediaSingleNodeView extends ReactNodeView<MediaSingleNodeViewProps
22
22
  getContentDOM(): {
23
23
  dom: HTMLDivElement;
24
24
  };
25
+ /**
26
+ * Skip rendering off-screen media in large (limited-mode) documents; the estimate is derived from
27
+ * the media's own dimensions and the renderer's width calculation. Called from both `createDomRef`
28
+ * and `update()` because limited mode can flip from disabled→enabled after the document loads.
29
+ * Takes the target element explicitly because `createDomRef` runs before `this.dom` is assigned.
30
+ */
31
+ private updateContentVisibility;
25
32
  viewShouldUpdate(nextNode: PMNode): boolean;
26
33
  subscribeToViewModeChange(domRef: HTMLElement): (() => void) | undefined;
27
34
  updateDomRefContentEditable(domRef: HTMLElement, editorViewMode?: 'edit' | 'view'): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-media",
3
- "version": "16.0.6",
3
+ "version": "16.1.0",
4
4
  "description": "Media plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -37,6 +37,7 @@
37
37
  "@atlaskit/editor-plugin-grid": "^14.0.0",
38
38
  "@atlaskit/editor-plugin-guideline": "^14.0.0",
39
39
  "@atlaskit/editor-plugin-interaction": "^25.0.0",
40
+ "@atlaskit/editor-plugin-limited-mode": "^11.0.0",
40
41
  "@atlaskit/editor-plugin-media-editing": "^8.0.0",
41
42
  "@atlaskit/editor-plugin-selection": "^14.0.0",
42
43
  "@atlaskit/editor-plugin-toolbar": "^11.0.0",
@@ -70,7 +71,7 @@
70
71
  "uuid": "^3.1.0"
71
72
  },
72
73
  "peerDependencies": {
73
- "@atlaskit/editor-common": "^118.10.0",
74
+ "@atlaskit/editor-common": "^118.11.0",
74
75
  "@atlaskit/media-core": "^38.0.0",
75
76
  "react": "^18.2.0 || ^19.2.0",
76
77
  "react-dom": "^18.2.0 || ^19.2.0",
@@ -160,11 +161,6 @@
160
161
  "type": "boolean"
161
162
  }
162
163
  },
163
- "stricter": {
164
- "no-unused-dependencies": {
165
- "checkDevDependencies": true
166
- }
167
- },
168
164
  "devDependencies": {
169
165
  "react": "^19.2.0",
170
166
  "react-dom": "^19.2.0",