dockview-react 8.4.0 → 8.5.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-react
3
- * @version 8.4.0
3
+ * @version 8.5.0
4
4
  * @link https://github.com/dockview/dockview
5
5
  * @license MIT
6
6
  */
@@ -189,6 +189,87 @@ function isReactComponent(component) {
189
189
  */
190
190
  return typeof component === "function" || !!(component === null || component === void 0 ? void 0 : component.$$typeof);
191
191
  }
192
+ /**
193
+ * Calls `onRemoved` once `element` leaves the document. Only its ancestors are
194
+ * observed, since any removal above it is a child-list change on one of them.
195
+ */
196
+ function watchForRemoval(element, onRemoved) {
197
+ const parentOf = (node) => node instanceof ShadowRoot ? node.host : node.parentNode;
198
+ const ancestors = /* @__PURE__ */ new Set();
199
+ const observer = new MutationObserver((records) => {
200
+ if (!element.isConnected) {
201
+ observer.disconnect();
202
+ onRemoved();
203
+ return;
204
+ }
205
+ for (const record of records) for (const node of record.removedNodes) if (ancestors.has(node)) {
206
+ observeAncestors();
207
+ return;
208
+ }
209
+ });
210
+ const observeAncestors = () => {
211
+ observer.disconnect();
212
+ ancestors.clear();
213
+ for (let node = parentOf(element); node; node = parentOf(node)) {
214
+ ancestors.add(node);
215
+ observer.observe(node, { childList: true });
216
+ }
217
+ };
218
+ observeAncestors();
219
+ return DockviewDisposable.from(() => observer.disconnect());
220
+ }
221
+ const canHideWithoutUnmount = Number.parseInt(React.version, 10) >= 18;
222
+ /**
223
+ * Creates the instance once and keeps it across effect cleanups that are not
224
+ * unmounts (`<Activity mode="hidden">`, StrictMode). A real unmount removes the
225
+ * host before cleanup, so a still-connected host means hidden; the instance is
226
+ * then disposed only if the host later leaves the document.
227
+ */
228
+ function useKeptInstance(domRef, instanceRef, create) {
229
+ const removalWatcher = React.useRef(void 0);
230
+ React.useEffect(() => {
231
+ var _removalWatcher$curre, _instanceRef$current;
232
+ const element = domRef.current;
233
+ if (!element) return;
234
+ (_removalWatcher$curre = removalWatcher.current) === null || _removalWatcher$curre === void 0 || _removalWatcher$curre.dispose();
235
+ removalWatcher.current = void 0;
236
+ const instance = (_instanceRef$current = instanceRef.current) !== null && _instanceRef$current !== void 0 ? _instanceRef$current : create(element);
237
+ instanceRef.current = instance;
238
+ return () => {
239
+ const dispose = () => {
240
+ removalWatcher.current = void 0;
241
+ instanceRef.current = void 0;
242
+ instance.dispose();
243
+ };
244
+ if (canHideWithoutUnmount && element.isConnected && typeof MutationObserver !== "undefined") removalWatcher.current = watchForRemoval(element, dispose);
245
+ else dispose();
246
+ };
247
+ }, []);
248
+ }
249
+ /**
250
+ * Returns a check for option effects: true when the instance already holds the
251
+ * given values. The first values seen count as applied, since the instance was
252
+ * created from them, so effect replays skip redundant `updateOptions` calls.
253
+ */
254
+ function useAppliedOptions(instanceRef) {
255
+ const applied = React.useRef({
256
+ instance: void 0,
257
+ values: /* @__PURE__ */ new Map()
258
+ });
259
+ return React.useCallback((values) => {
260
+ if (applied.current.instance !== instanceRef.current) applied.current = {
261
+ instance: instanceRef.current,
262
+ values: /* @__PURE__ */ new Map()
263
+ };
264
+ const known = applied.current.values;
265
+ let unchanged = true;
266
+ for (const [key, value] of Object.entries(values)) {
267
+ if (known.has(key) && known.get(key) !== value) unchanged = false;
268
+ known.set(key, value);
269
+ }
270
+ return unchanged;
271
+ }, [instanceRef]);
272
+ }
192
273
 
193
274
  //#endregion
194
275
  //#region src/dockview/reactContentPart.ts
@@ -460,6 +541,7 @@ const DockviewReact = React.forwardRef((props, ref) => {
460
541
  const domRef = React.useRef(null);
461
542
  const dockviewRef = React.useRef(void 0);
462
543
  const [portals, addPortal] = usePortalsLifecycle();
544
+ const isApplied = useAppliedOptions(dockviewRef);
463
545
  React.useImperativeHandle(ref, () => domRef.current, []);
464
546
  const prevProps = React.useRef({});
465
547
  React.useEffect(() => {
@@ -469,11 +551,10 @@ const DockviewReact = React.forwardRef((props, ref) => {
469
551
  const propValue = props[key];
470
552
  if (key in props && propValue !== prevProps.current[key]) changes[key] = propValue;
471
553
  });
472
- if (dockviewRef.current) dockviewRef.current.updateOptions(changes);
554
+ if (dockviewRef.current && Object.keys(changes).length > 0) dockviewRef.current.updateOptions(changes);
473
555
  prevProps.current = props;
474
556
  }, PROPERTY_KEYS_DOCKVIEW.map((key) => props[key]));
475
- React.useEffect(() => {
476
- if (!domRef.current) return;
557
+ useKeptInstance(domRef, dockviewRef, (element) => {
477
558
  const frameworkTabComponents = _objectSpread2({}, props.tabComponents);
478
559
  if (props.defaultTabComponent) frameworkTabComponents[DEFAULT_REACT_TAB] = props.defaultTabComponent;
479
560
  const frameworkOptions = {
@@ -508,16 +589,12 @@ const DockviewReact = React.forwardRef((props, ref) => {
508
589
  return new ReactGroupDragGhostPart(ghostComponent, { addPortal });
509
590
  };
510
591
  }
511
- const api = createDockview(domRef.current, _objectSpread2(_objectSpread2({}, coreOptions), frameworkOptions));
512
- const { clientWidth, clientHeight } = domRef.current;
592
+ const api = createDockview(element, _objectSpread2(_objectSpread2({}, coreOptions), frameworkOptions));
593
+ const { clientWidth, clientHeight } = element;
513
594
  api.layout(clientWidth, clientHeight);
514
595
  if (props.onReady) props.onReady({ api });
515
- dockviewRef.current = api;
516
- return () => {
517
- dockviewRef.current = void 0;
518
- api.dispose();
519
- };
520
- }, []);
596
+ return api;
597
+ });
521
598
  React.useEffect(() => {
522
599
  if (!dockviewRef.current) return () => {};
523
600
  const disposable = dockviewRef.current.onDidDrop((event) => {
@@ -537,25 +614,28 @@ const DockviewReact = React.forwardRef((props, ref) => {
537
614
  };
538
615
  }, [props.onWillDrop]);
539
616
  React.useEffect(() => {
540
- if (!dockviewRef.current) return;
617
+ if (!dockviewRef.current || isApplied({ tabGroupChipComponent: props.tabGroupChipComponent })) return;
541
618
  dockviewRef.current.updateOptions({ createTabGroupChipComponent: props.tabGroupChipComponent ? () => {
542
619
  return new ReactTabGroupChipPart(props.tabGroupChipComponent, { addPortal });
543
620
  } : void 0 });
544
621
  }, [props.tabGroupChipComponent]);
545
622
  React.useEffect(() => {
546
- if (!dockviewRef.current) return;
623
+ if (!dockviewRef.current || isApplied({ groupDragGhostComponent: props.groupDragGhostComponent })) return;
547
624
  dockviewRef.current.updateOptions({ createGroupDragGhostComponent: props.groupDragGhostComponent ? () => {
548
625
  return new ReactGroupDragGhostPart(props.groupDragGhostComponent, { addPortal });
549
626
  } : void 0 });
550
627
  }, [props.groupDragGhostComponent]);
551
628
  React.useEffect(() => {
552
- if (!dockviewRef.current) return;
629
+ if (!dockviewRef.current || isApplied({ components: props.components })) return;
553
630
  dockviewRef.current.updateOptions({ createComponent: (options) => {
554
631
  return new ReactPanelContentPart(options.id, props.components[options.name], { addPortal });
555
632
  } });
556
633
  }, [props.components]);
557
634
  React.useEffect(() => {
558
- if (!dockviewRef.current) return;
635
+ if (!dockviewRef.current || isApplied({
636
+ tabComponents: props.tabComponents,
637
+ defaultTabComponent: props.defaultTabComponent
638
+ })) return;
559
639
  const frameworkTabComponents = _objectSpread2({}, props.tabComponents);
560
640
  if (props.defaultTabComponent) frameworkTabComponents[DEFAULT_REACT_TAB] = props.defaultTabComponent;
561
641
  dockviewRef.current.updateOptions({
@@ -566,21 +646,21 @@ const DockviewReact = React.forwardRef((props, ref) => {
566
646
  });
567
647
  }, [props.tabComponents, props.defaultTabComponent]);
568
648
  React.useEffect(() => {
569
- if (!dockviewRef.current) return;
649
+ if (!dockviewRef.current || isApplied({ watermarkComponent: props.watermarkComponent })) return;
570
650
  dockviewRef.current.updateOptions({ createWatermarkComponent: props.watermarkComponent ? () => {
571
651
  return new ReactWatermarkPart("watermark", props.watermarkComponent, { addPortal });
572
652
  } : void 0 });
573
653
  }, [props.watermarkComponent]);
574
654
  React.useEffect(() => {
575
- if (!dockviewRef.current) return;
655
+ if (!dockviewRef.current || isApplied({ rightHeaderActionsComponent: props.rightHeaderActionsComponent })) return;
576
656
  dockviewRef.current.updateOptions({ createRightHeaderActionComponent: createGroupControlElement(props.rightHeaderActionsComponent, { addPortal }) });
577
657
  }, [props.rightHeaderActionsComponent]);
578
658
  React.useEffect(() => {
579
- if (!dockviewRef.current) return;
659
+ if (!dockviewRef.current || isApplied({ leftHeaderActionsComponent: props.leftHeaderActionsComponent })) return;
580
660
  dockviewRef.current.updateOptions({ createLeftHeaderActionComponent: createGroupControlElement(props.leftHeaderActionsComponent, { addPortal }) });
581
661
  }, [props.leftHeaderActionsComponent]);
582
662
  React.useEffect(() => {
583
- if (!dockviewRef.current) return;
663
+ if (!dockviewRef.current || isApplied({ prefixHeaderActionsComponent: props.prefixHeaderActionsComponent })) return;
584
664
  dockviewRef.current.updateOptions({ createPrefixHeaderActionComponent: createGroupControlElement(props.prefixHeaderActionsComponent, { addPortal }) });
585
665
  }, [props.prefixHeaderActionsComponent]);
586
666
  return /* @__PURE__ */ React.createElement("div", {
@@ -751,6 +831,7 @@ const SplitviewReact = React.forwardRef((props, ref) => {
751
831
  const domRef = React.useRef(null);
752
832
  const splitviewRef = React.useRef(void 0);
753
833
  const [portals, addPortal] = usePortalsLifecycle();
834
+ const isApplied = useAppliedOptions(splitviewRef);
754
835
  React.useImperativeHandle(ref, () => domRef.current, []);
755
836
  const prevProps = React.useRef({});
756
837
  React.useEffect(() => {
@@ -760,25 +841,20 @@ const SplitviewReact = React.forwardRef((props, ref) => {
760
841
  const propValue = props[key];
761
842
  if (key in props && propValue !== prevProps.current[key]) changes[key] = propValue;
762
843
  });
763
- if (splitviewRef.current) splitviewRef.current.updateOptions(changes);
844
+ if (splitviewRef.current && Object.keys(changes).length > 0) splitviewRef.current.updateOptions(changes);
764
845
  prevProps.current = props;
765
846
  }, PROPERTY_KEYS_SPLITVIEW.map((key) => props[key]));
766
- React.useEffect(() => {
767
- if (!domRef.current) return () => {};
768
- const api = createSplitview(domRef.current, _objectSpread2(_objectSpread2({}, extractCoreOptions$2(props)), { createComponent: (options) => {
847
+ useKeptInstance(domRef, splitviewRef, (element) => {
848
+ const api = createSplitview(element, _objectSpread2(_objectSpread2({}, extractCoreOptions$2(props)), { createComponent: (options) => {
769
849
  return new ReactPanelView(options.id, options.name, props.components[options.name], { addPortal });
770
850
  } }));
771
- const { clientWidth, clientHeight } = domRef.current;
851
+ const { clientWidth, clientHeight } = element;
772
852
  api.layout(clientWidth, clientHeight);
773
853
  if (props.onReady) props.onReady({ api });
774
- splitviewRef.current = api;
775
- return () => {
776
- splitviewRef.current = void 0;
777
- api.dispose();
778
- };
779
- }, []);
854
+ return api;
855
+ });
780
856
  React.useEffect(() => {
781
- if (!splitviewRef.current) return;
857
+ if (!splitviewRef.current || isApplied({ components: props.components })) return;
782
858
  splitviewRef.current.updateOptions({ createComponent: (options) => {
783
859
  return new ReactPanelView(options.id, options.name, props.components[options.name], { addPortal });
784
860
  } });
@@ -823,6 +899,7 @@ const GridviewReact = React.forwardRef((props, ref) => {
823
899
  const domRef = React.useRef(null);
824
900
  const gridviewRef = React.useRef(void 0);
825
901
  const [portals, addPortal] = usePortalsLifecycle();
902
+ const isApplied = useAppliedOptions(gridviewRef);
826
903
  React.useImperativeHandle(ref, () => domRef.current, []);
827
904
  const prevProps = React.useRef({});
828
905
  React.useEffect(() => {
@@ -832,25 +909,20 @@ const GridviewReact = React.forwardRef((props, ref) => {
832
909
  const propValue = props[key];
833
910
  if (key in props && propValue !== prevProps.current[key]) changes[key] = propValue;
834
911
  });
835
- if (gridviewRef.current) gridviewRef.current.updateOptions(changes);
912
+ if (gridviewRef.current && Object.keys(changes).length > 0) gridviewRef.current.updateOptions(changes);
836
913
  prevProps.current = props;
837
914
  }, PROPERTY_KEYS_GRIDVIEW.map((key) => props[key]));
838
- React.useEffect(() => {
839
- if (!domRef.current) return () => {};
840
- const api = createGridview(domRef.current, _objectSpread2(_objectSpread2({}, extractCoreOptions$1(props)), { createComponent: (options) => {
915
+ useKeptInstance(domRef, gridviewRef, (element) => {
916
+ const api = createGridview(element, _objectSpread2(_objectSpread2({}, extractCoreOptions$1(props)), { createComponent: (options) => {
841
917
  return new ReactGridPanelView(options.id, options.name, props.components[options.name], { addPortal });
842
918
  } }));
843
- const { clientWidth, clientHeight } = domRef.current;
919
+ const { clientWidth, clientHeight } = element;
844
920
  api.layout(clientWidth, clientHeight);
845
921
  if (props.onReady) props.onReady({ api });
846
- gridviewRef.current = api;
847
- return () => {
848
- gridviewRef.current = void 0;
849
- api.dispose();
850
- };
851
- }, []);
922
+ return api;
923
+ });
852
924
  React.useEffect(() => {
853
- if (!gridviewRef.current) return;
925
+ if (!gridviewRef.current || isApplied({ components: props.components })) return;
854
926
  gridviewRef.current.updateOptions({ createComponent: (options) => {
855
927
  return new ReactGridPanelView(options.id, options.name, props.components[options.name], { addPortal });
856
928
  } });
@@ -912,6 +984,7 @@ const PaneviewReact = React.forwardRef((props, ref) => {
912
984
  const domRef = React.useRef(null);
913
985
  const paneviewRef = React.useRef(void 0);
914
986
  const [portals, addPortal] = usePortalsLifecycle();
987
+ const isApplied = useAppliedOptions(paneviewRef);
915
988
  React.useImperativeHandle(ref, () => domRef.current, []);
916
989
  const prevProps = React.useRef({});
917
990
  React.useEffect(() => {
@@ -921,14 +994,13 @@ const PaneviewReact = React.forwardRef((props, ref) => {
921
994
  const propValue = props[key];
922
995
  if (key in props && propValue !== prevProps.current[key]) changes[key] = propValue;
923
996
  });
924
- if (paneviewRef.current) paneviewRef.current.updateOptions(changes);
997
+ if (paneviewRef.current && Object.keys(changes).length > 0) paneviewRef.current.updateOptions(changes);
925
998
  prevProps.current = props;
926
999
  }, PROPERTY_KEYS_PANEVIEW.map((key) => props[key]));
927
- React.useEffect(() => {
1000
+ useKeptInstance(domRef, paneviewRef, (element) => {
928
1001
  var _props$headerComponen;
929
- if (!domRef.current) return () => {};
930
1002
  const headerComponents = (_props$headerComponen = props.headerComponents) !== null && _props$headerComponen !== void 0 ? _props$headerComponen : {};
931
- const api = createPaneview(domRef.current, _objectSpread2(_objectSpread2({}, extractCoreOptions(props)), {
1003
+ const api = createPaneview(element, _objectSpread2(_objectSpread2({}, extractCoreOptions(props)), {
932
1004
  createComponent: (options) => {
933
1005
  return new PanePanelSection(options.id, props.components[options.name], { addPortal });
934
1006
  },
@@ -936,24 +1008,20 @@ const PaneviewReact = React.forwardRef((props, ref) => {
936
1008
  return new PanePanelSection(options.id, headerComponents[options.name], { addPortal });
937
1009
  }
938
1010
  }));
939
- const { clientWidth, clientHeight } = domRef.current;
1011
+ const { clientWidth, clientHeight } = element;
940
1012
  api.layout(clientWidth, clientHeight);
941
1013
  if (props.onReady) props.onReady({ api });
942
- paneviewRef.current = api;
943
- return () => {
944
- paneviewRef.current = void 0;
945
- api.dispose();
946
- };
947
- }, []);
1014
+ return api;
1015
+ });
948
1016
  React.useEffect(() => {
949
- if (!paneviewRef.current) return;
1017
+ if (!paneviewRef.current || isApplied({ components: props.components })) return;
950
1018
  paneviewRef.current.updateOptions({ createComponent: (options) => {
951
1019
  return new PanePanelSection(options.id, props.components[options.name], { addPortal });
952
1020
  } });
953
1021
  }, [props.components]);
954
1022
  React.useEffect(() => {
955
1023
  var _props$headerComponen2;
956
- if (!paneviewRef.current) return;
1024
+ if (!paneviewRef.current || isApplied({ headerComponents: props.headerComponents })) return;
957
1025
  const headerComponents = (_props$headerComponen2 = props.headerComponents) !== null && _props$headerComponen2 !== void 0 ? _props$headerComponen2 : {};
958
1026
  paneviewRef.current.updateOptions({ createHeaderComponent: (options) => {
959
1027
  return new PanePanelSection(options.id, headerComponents[options.name], { addPortal });
@@ -979,4 +1047,4 @@ const PaneviewReact = React.forwardRef((props, ref) => {
979
1047
  PaneviewReact.displayName = "PaneviewComponent";
980
1048
 
981
1049
  //#endregion
982
- export { DockviewDefaultTab, DockviewReact, GridviewReact, PaneviewReact, ReactPart, ReactPartContext, SplitviewReact, isReactComponent, usePortalsLifecycle };
1050
+ export { DockviewDefaultTab, DockviewReact, GridviewReact, PaneviewReact, ReactPart, ReactPartContext, SplitviewReact, isReactComponent, useAppliedOptions, useKeptInstance, usePortalsLifecycle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dockview-react",
3
- "version": "8.4.0",
3
+ "version": "8.5.0",
4
4
  "description": "React docking layout manager — tabs, groups, grids, splitviews, drag and drop, floating panels",
5
5
  "keywords": [
6
6
  "splitview",
@@ -85,7 +85,7 @@
85
85
  "format:check": "biome format src"
86
86
  },
87
87
  "dependencies": {
88
- "dockview": "^8.4.0"
88
+ "dockview": "^8.5.0"
89
89
  },
90
90
  "peerDependencies": {
91
91
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",