@opensumi/ide-core-browser 3.2.6-next-1725007925.0 → 3.3.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.
Files changed (44) hide show
  1. package/lib/bootstrap/app.view.d.ts.map +1 -1
  2. package/lib/bootstrap/app.view.js +1 -0
  3. package/lib/bootstrap/app.view.js.map +1 -1
  4. package/lib/components/layout/split-panel.d.ts.map +1 -1
  5. package/lib/components/layout/split-panel.js +7 -8
  6. package/lib/components/layout/split-panel.js.map +1 -1
  7. package/lib/components/resize/resize.d.ts.map +1 -1
  8. package/lib/components/resize/resize.js +111 -95
  9. package/lib/components/resize/resize.js.map +1 -1
  10. package/lib/dom/fastdom.d.ts +11 -0
  11. package/lib/dom/fastdom.d.ts.map +1 -0
  12. package/lib/dom/fastdom.js +96 -0
  13. package/lib/dom/fastdom.js.map +1 -0
  14. package/lib/dom/index.d.ts +2 -0
  15. package/lib/dom/index.d.ts.map +1 -1
  16. package/lib/dom/index.js +3 -1
  17. package/lib/dom/index.js.map +1 -1
  18. package/lib/dom/resize-observer.d.ts +18 -0
  19. package/lib/dom/resize-observer.d.ts.map +1 -0
  20. package/lib/dom/resize-observer.js +43 -0
  21. package/lib/dom/resize-observer.js.map +1 -0
  22. package/lib/layout/layout-hooks.d.ts.map +1 -1
  23. package/lib/layout/layout-hooks.js +27 -22
  24. package/lib/layout/layout-hooks.js.map +1 -1
  25. package/lib/layout/layout.interface.d.ts +1 -0
  26. package/lib/layout/layout.interface.d.ts.map +1 -1
  27. package/lib/layout/layout.interface.js +3 -0
  28. package/lib/layout/layout.interface.js.map +1 -1
  29. package/lib/layout/render.d.ts.map +1 -1
  30. package/lib/layout/render.js.map +1 -1
  31. package/lib/react-providers/slot.d.ts.map +1 -1
  32. package/lib/react-providers/slot.js +3 -0
  33. package/lib/react-providers/slot.js.map +1 -1
  34. package/package.json +5 -5
  35. package/src/bootstrap/app.view.tsx +1 -0
  36. package/src/components/layout/split-panel.tsx +8 -7
  37. package/src/components/resize/resize.tsx +127 -89
  38. package/src/dom/fastdom.ts +110 -0
  39. package/src/dom/index.ts +3 -0
  40. package/src/dom/resize-observer.ts +52 -0
  41. package/src/layout/layout-hooks.ts +35 -24
  42. package/src/layout/layout.interface.ts +5 -1
  43. package/src/layout/render.tsx +6 -6
  44. package/src/react-providers/slot.tsx +3 -0
@@ -1,6 +1,10 @@
1
1
  import cls from 'classnames';
2
2
  import React from 'react';
3
3
 
4
+ import { IDisposable } from '@opensumi/ide-core-common';
5
+
6
+ import { fastdom } from '../../dom';
7
+
4
8
  import styles from './resize.module.less';
5
9
 
6
10
  export const RESIZE_LOCK = 'resize-lock';
@@ -45,6 +49,8 @@ export interface IResizeHandleDelegate {
45
49
  export function preventWebviewCatchMouseEvents() {
46
50
  const iframes = document.getElementsByTagName('iframe');
47
51
  const webviews = document.getElementsByTagName('webview');
52
+ const shadowRootHost = document.getElementsByClassName('shadow-root-host');
53
+
48
54
  for (const webview of webviews as unknown as HTMLElement[]) {
49
55
  webview.classList.add('none-pointer-event');
50
56
  }
@@ -52,7 +58,6 @@ export function preventWebviewCatchMouseEvents() {
52
58
  iframe.classList.add('none-pointer-event');
53
59
  }
54
60
 
55
- const shadowRootHost = document.getElementsByClassName('shadow-root-host');
56
61
  for (const host of shadowRootHost as unknown as HTMLElement[]) {
57
62
  host?.classList.add('none-pointer-event');
58
63
  }
@@ -61,6 +66,8 @@ export function preventWebviewCatchMouseEvents() {
61
66
  export function allowWebviewCatchMouseEvents() {
62
67
  const iframes = document.getElementsByTagName('iframe');
63
68
  const webviews = document.getElementsByTagName('webview');
69
+ const shadowRootHost = document.getElementsByClassName('shadow-root-host');
70
+
64
71
  for (const webview of webviews as unknown as HTMLElement[]) {
65
72
  webview.classList.remove('none-pointer-event');
66
73
  }
@@ -68,7 +75,6 @@ export function allowWebviewCatchMouseEvents() {
68
75
  iframe.classList.remove('none-pointer-event');
69
76
  }
70
77
 
71
- const shadowRootHost = document.getElementsByClassName('shadow-root-host');
72
78
  for (const host of shadowRootHost as unknown as HTMLElement[]) {
73
79
  host?.classList.remove('none-pointer-event');
74
80
  }
@@ -82,16 +88,18 @@ export const ResizeHandleHorizontal = (props: ResizeHandleProps) => {
82
88
  const startNextWidth = React.useRef<number>(0);
83
89
  const prevElement = React.useRef<HTMLElement | null>();
84
90
  const nextElement = React.useRef<HTMLElement | null>();
85
- const requestFrame = React.useRef<number>();
91
+ const requestFrameToDispose = React.useRef<IDisposable>();
86
92
 
87
93
  const setSize = (prev: number, next: number) => {
88
- const parentWidth = ref.current!.parentElement!.offsetWidth;
89
94
  const prevEle = props.findPrevElement ? props.findPrevElement() : prevElement.current!;
90
95
  const nextEle = props.findNextElement ? props.findNextElement() : nextElement.current!;
91
96
 
92
97
  if ((prevEle && prevEle.classList.contains(RESIZE_LOCK)) || (nextEle && nextEle.classList.contains(RESIZE_LOCK))) {
93
98
  return;
94
99
  }
100
+
101
+ const parentWidth = ref.current!.parentElement!.offsetWidth;
102
+
95
103
  const prevMinResize = Number(prevEle?.dataset.minResize || 0);
96
104
  const nextMinResize = Number(nextEle?.dataset.minResize || 0);
97
105
 
@@ -107,6 +115,7 @@ export const ResizeHandleHorizontal = (props: ResizeHandleProps) => {
107
115
  return;
108
116
  }
109
117
  }
118
+
110
119
  if (nextEle) {
111
120
  nextEle.style.width = next * 100 + '%';
112
121
  }
@@ -245,32 +254,39 @@ export const ResizeHandleHorizontal = (props: ResizeHandleProps) => {
245
254
  };
246
255
 
247
256
  const setAbsoluteSize = (size: number, isLatter?: boolean) => {
248
- const currentPrev = prevElement.current!.clientWidth;
249
- const currentNext = nextElement.current!.clientWidth;
250
- const totalSize = currentPrev + currentNext;
251
- if (props.flexMode) {
252
- const prevWidth = props.flexMode === ResizeFlexMode.Prev ? size : totalSize - size;
253
- const nextWidth = props.flexMode === ResizeFlexMode.Next ? size : totalSize - size;
254
- flexModeSetSize(prevWidth, nextWidth, true);
255
- } else {
256
- const currentTotalWidth =
257
- +nextElement.current!.style.width!.replace('%', '') + +prevElement.current!.style.width!.replace('%', '');
258
- if (isLatter) {
259
- nextElement.current!.style.width = currentTotalWidth * (size / totalSize) + '%';
260
- prevElement.current!.style.width = currentTotalWidth * (1 - size / totalSize) + '%';
261
- } else {
262
- prevElement.current!.style.width = currentTotalWidth * (size / totalSize) + '%';
263
- nextElement.current!.style.width = currentTotalWidth * (1 - size / totalSize) + '%';
264
- }
265
- }
266
- if (isLatter) {
267
- handleZeroSize(totalSize - size, size);
268
- } else {
269
- handleZeroSize(size, totalSize - size);
270
- }
271
- if (props.onResize) {
272
- props.onResize(prevElement.current!, nextElement.current!);
273
- }
257
+ fastdom.measure(() => {
258
+ const currentPrev = prevElement.current!.clientWidth;
259
+ const currentNext = nextElement.current!.clientWidth;
260
+
261
+ const nextTotolWidth = +nextElement.current!.style.width!.replace('%', '');
262
+ const prevTotalWidth = +prevElement.current!.style.width!.replace('%', '');
263
+ fastdom.mutate(() => {
264
+ const totalSize = currentPrev + currentNext;
265
+ if (props.flexMode) {
266
+ const prevWidth = props.flexMode === ResizeFlexMode.Prev ? size : totalSize - size;
267
+ const nextWidth = props.flexMode === ResizeFlexMode.Next ? size : totalSize - size;
268
+ flexModeSetSize(prevWidth, nextWidth, true);
269
+ } else {
270
+ const currentTotalWidth = nextTotolWidth + prevTotalWidth;
271
+
272
+ if (isLatter) {
273
+ nextElement.current!.style.width = currentTotalWidth * (size / totalSize) + '%';
274
+ prevElement.current!.style.width = currentTotalWidth * (1 - size / totalSize) + '%';
275
+ } else {
276
+ prevElement.current!.style.width = currentTotalWidth * (size / totalSize) + '%';
277
+ nextElement.current!.style.width = currentTotalWidth * (1 - size / totalSize) + '%';
278
+ }
279
+ }
280
+ if (isLatter) {
281
+ handleZeroSize(totalSize - size, size);
282
+ } else {
283
+ handleZeroSize(size, totalSize - size);
284
+ }
285
+ if (props.onResize) {
286
+ props.onResize(prevElement.current!, nextElement.current!);
287
+ }
288
+ });
289
+ });
274
290
  };
275
291
 
276
292
  const getAbsoluteSize = (isLatter?: boolean) => {
@@ -303,43 +319,57 @@ export const ResizeHandleHorizontal = (props: ResizeHandleProps) => {
303
319
  }
304
320
  const prevWidth = startPrevWidth.current + e.pageX - startX.current;
305
321
  const nextWidth = startNextWidth.current - (e.pageX - startX.current);
306
- if (requestFrame.current) {
307
- window.cancelAnimationFrame(requestFrame.current);
322
+ if (requestFrameToDispose.current) {
323
+ requestFrameToDispose.current.dispose();
308
324
  }
309
- const parentWidth = ref.current!.parentElement!.offsetWidth;
310
- requestFrame.current = window.requestAnimationFrame(() => {
325
+
326
+ requestFrameToDispose.current = fastdom.mutate(() => {
311
327
  if (props.flexMode) {
312
328
  flexModeSetSize(prevWidth, nextWidth);
313
329
  } else {
330
+ const parentWidth = ref.current!.parentElement!.offsetWidth;
314
331
  setSize(prevWidth / parentWidth, nextWidth / parentWidth);
315
332
  }
316
333
  });
317
334
  };
318
- const onMouseUp = (e) => {
335
+ const onMouseUp = () => {
319
336
  resizing.current = false;
320
- ref.current?.classList.remove(styles.active);
321
337
  document.removeEventListener('mousemove', onMouseMove);
322
338
  document.removeEventListener('mouseup', onMouseUp);
323
- // 结束拖拽时恢复拖拽区域滚动条
324
- restoreScrollBar(prevElement.current!);
325
- restoreScrollBar(nextElement.current!);
339
+
326
340
  if (props.onFinished) {
327
341
  props.onFinished();
328
342
  }
329
- allowWebviewCatchMouseEvents();
343
+
344
+ fastdom.mutate(() => {
345
+ ref.current?.classList.remove(styles.active);
346
+
347
+ // 结束拖拽时恢复拖拽区域滚动条
348
+ restoreScrollBar(prevElement.current!);
349
+ restoreScrollBar(nextElement.current!);
350
+
351
+ allowWebviewCatchMouseEvents();
352
+ });
330
353
  };
331
354
  const onMouseDown = (e) => {
332
355
  resizing.current = true;
333
- ref.current?.classList.add(styles.active);
334
356
  document.addEventListener('mousemove', onMouseMove);
335
357
  document.addEventListener('mouseup', onMouseUp);
336
358
  startX.current = e.pageX;
337
- startPrevWidth.current = prevElement.current!.offsetWidth;
338
- startNextWidth.current = nextElement.current!.offsetWidth;
339
- // 开始拖拽时隐藏拖拽区域滚动条
340
- hideScrollBar(prevElement.current!);
341
- hideScrollBar(nextElement.current!);
342
- preventWebviewCatchMouseEvents();
359
+
360
+ fastdom.measure(() => {
361
+ startPrevWidth.current = prevElement.current!.offsetWidth;
362
+ startNextWidth.current = nextElement.current!.offsetWidth;
363
+
364
+ fastdom.mutate(() => {
365
+ ref.current?.classList.add(styles.active);
366
+
367
+ // 开始拖拽时隐藏拖拽区域滚动条
368
+ hideScrollBar(prevElement.current!);
369
+ hideScrollBar(nextElement.current!);
370
+ preventWebviewCatchMouseEvents();
371
+ });
372
+ });
343
373
  };
344
374
 
345
375
  React.useEffect(() => {
@@ -395,7 +425,6 @@ export const ResizeHandleVertical = (props: ResizeHandleProps) => {
395
425
  const cachedPrevElement = React.useRef<HTMLElement>();
396
426
  const cachedNextElement = React.useRef<HTMLElement>();
397
427
 
398
- const requestFrame = React.useRef<number>();
399
428
  // direction: true 为向下,false 为向上
400
429
  const setSize = (prev: number, next: number, direction?: boolean) => {
401
430
  const prevEle = props.findPrevElement ? props.findPrevElement(direction) : prevElement.current!;
@@ -465,7 +494,6 @@ export const ResizeHandleVertical = (props: ResizeHandleProps) => {
465
494
  let currentTotalHeight;
466
495
  if (props.flexMode) {
467
496
  currentTotalHeight = ((prevEle.offsetHeight + nextEle.offsetHeight) / prevEle.parentElement!.offsetHeight) * 100;
468
- // flexModeSetSize(prev / (prev + next) * totalHeight, next / (prev + next) * totalHeight, true);
469
497
  } else {
470
498
  currentTotalHeight = +nextEle.style.height!.replace('%', '') + +prevEle.style.height!.replace('%', '');
471
499
  }
@@ -584,15 +612,21 @@ export const ResizeHandleVertical = (props: ResizeHandleProps) => {
584
612
 
585
613
  const onMouseDown = (e) => {
586
614
  resizing.current = true;
587
- ref.current?.classList.add(styles.active);
588
615
  document.addEventListener('mousemove', onMouseMove);
589
616
  document.addEventListener('mouseup', onMouseUp);
590
617
  startY.current = e.pageY;
591
618
  cachedNextElement.current = nextElement.current;
592
619
  cachedPrevElement.current = prevElement.current;
593
- startPrevHeight.current = prevElement.current!.offsetHeight;
594
- startNextHeight.current = nextElement.current!.offsetHeight;
595
- preventWebviewCatchMouseEvents();
620
+
621
+ fastdom.measure(() => {
622
+ startPrevHeight.current = prevElement.current!.offsetHeight;
623
+ startNextHeight.current = nextElement.current!.offsetHeight;
624
+
625
+ fastdom.mutate(() => {
626
+ ref.current?.classList.add(styles.active);
627
+ preventWebviewCatchMouseEvents();
628
+ });
629
+ });
596
630
  };
597
631
 
598
632
  const onMouseMove = (e: MouseEvent) => {
@@ -600,32 +634,30 @@ export const ResizeHandleVertical = (props: ResizeHandleProps) => {
600
634
  if (ref.current && ref.current.classList.contains('no-resize')) {
601
635
  return;
602
636
  }
603
- const direction = e.pageY > startY.current;
604
- // 若上层未传入findNextElement,dynamicNext为null,否则找不到符合要求的panel时返回undefined
605
- const dynamicNext = props.findNextElement ? props.findNextElement(direction) : null;
606
- const dynamicPrev = props.findPrevElement ? props.findPrevElement(direction) : null;
607
- // 作用元素变化重新初始化当前位置,传入findNextElement时默认已传入findPrevElement
608
- if (
609
- (dynamicNext !== null && cachedNextElement.current !== dynamicNext) ||
610
- (dynamicPrev !== null && cachedPrevElement.current !== dynamicPrev)
611
- ) {
612
- if (!dynamicNext || !dynamicPrev) {
613
- return;
637
+
638
+ fastdom.measure(() => {
639
+ const direction = e.pageY > startY.current;
640
+ // 若上层未传入findNextElement,dynamicNext为null,否则找不到符合要求的panel时返回undefined
641
+ const dynamicNext = props.findNextElement ? props.findNextElement(direction) : null;
642
+ const dynamicPrev = props.findPrevElement ? props.findPrevElement(direction) : null;
643
+ // 作用元素变化重新初始化当前位置,传入findNextElement时默认已传入findPrevElement
644
+ if (
645
+ (dynamicNext !== null && cachedNextElement.current !== dynamicNext) ||
646
+ (dynamicPrev !== null && cachedPrevElement.current !== dynamicPrev)
647
+ ) {
648
+ if (!dynamicNext || !dynamicPrev) {
649
+ return;
650
+ }
651
+ cachedNextElement.current = dynamicNext!;
652
+ cachedPrevElement.current = dynamicPrev!;
653
+ startY.current = e.pageY;
654
+ startPrevHeight.current = cachedPrevElement.current!.offsetHeight;
655
+ startNextHeight.current = cachedNextElement.current!.offsetHeight;
614
656
  }
615
- cachedNextElement.current = dynamicNext!;
616
- cachedPrevElement.current = dynamicPrev!;
617
- startY.current = e.pageY;
618
- startPrevHeight.current = cachedPrevElement.current!.offsetHeight;
619
- startNextHeight.current = cachedNextElement.current!.offsetHeight;
620
- }
621
657
 
622
- const prevHeight = startPrevHeight.current + e.pageY - startY.current;
623
- const nextHeight = startNextHeight.current - (e.pageY - startY.current);
624
- if (requestFrame.current) {
625
- window.cancelAnimationFrame(requestFrame.current);
626
- }
658
+ const prevHeight = startPrevHeight.current + e.pageY - startY.current;
659
+ const nextHeight = startNextHeight.current - (e.pageY - startY.current);
627
660
 
628
- requestFrame.current = window.requestAnimationFrame(() => {
629
661
  const prevMinResize = Number(cachedPrevElement.current!.dataset.minResize || 0);
630
662
  const nextMinResize = Number(cachedNextElement.current!.dataset.minResize || 0);
631
663
  if (prevMinResize || nextMinResize) {
@@ -633,26 +665,32 @@ export const ResizeHandleVertical = (props: ResizeHandleProps) => {
633
665
  return;
634
666
  }
635
667
  }
636
- if (props.flexMode === ResizeFlexMode.Prev || props.flexMode === ResizeFlexMode.Next) {
637
- flexModeSetSize(prevHeight, nextHeight);
638
- } else if (props.flexMode === ResizeFlexMode.Percentage) {
639
- const parentHeight = ref.current!.parentElement!.offsetHeight;
640
- setSize(prevHeight / parentHeight, nextHeight / parentHeight);
641
- } else {
642
- setDomSize(prevHeight, nextHeight, cachedPrevElement.current!, cachedNextElement.current!);
643
- }
668
+
669
+ fastdom.mutate(() => {
670
+ if (props.flexMode === ResizeFlexMode.Prev || props.flexMode === ResizeFlexMode.Next) {
671
+ flexModeSetSize(prevHeight, nextHeight);
672
+ } else if (props.flexMode === ResizeFlexMode.Percentage) {
673
+ const parentHeight = ref.current!.parentElement!.offsetHeight;
674
+ setSize(prevHeight / parentHeight, nextHeight / parentHeight);
675
+ } else {
676
+ setDomSize(prevHeight, nextHeight, cachedPrevElement.current!, cachedNextElement.current!);
677
+ }
678
+ });
644
679
  });
645
680
  };
646
681
 
647
682
  const onMouseUp = (e) => {
648
683
  resizing.current = false;
649
- ref.current?.classList.remove(styles.active);
650
684
  document.removeEventListener('mousemove', onMouseMove);
651
685
  document.removeEventListener('mouseup', onMouseUp);
652
- if (props.onFinished) {
653
- props.onFinished();
654
- }
655
- allowWebviewCatchMouseEvents();
686
+
687
+ fastdom.mutate(() => {
688
+ ref.current?.classList.remove(styles.active);
689
+ if (props.onFinished) {
690
+ props.onFinished();
691
+ }
692
+ allowWebviewCatchMouseEvents();
693
+ });
656
694
  };
657
695
 
658
696
  React.useEffect(() => {
@@ -0,0 +1,110 @@
1
+ import { Heap, IDisposable, onUnexpectedError } from '@opensumi/ide-utils';
2
+
3
+ class DomOperation {
4
+ private _disposed = false;
5
+
6
+ constructor(protected _runner: () => void, public priority: number) {}
7
+
8
+ run() {
9
+ if (this._disposed) {
10
+ return;
11
+ }
12
+
13
+ try {
14
+ this._runner();
15
+ } catch (error) {
16
+ onUnexpectedError(error);
17
+ }
18
+ }
19
+
20
+ dispose() {
21
+ this._disposed = true;
22
+ }
23
+ }
24
+
25
+ const aQueue = new Heap<DomOperation>({
26
+ comparator: (a, b) => b.priority - a.priority,
27
+ });
28
+
29
+ const bQueue = new Heap<DomOperation>({
30
+ comparator: (a, b) => b.priority - a.priority,
31
+ });
32
+
33
+ let runningQueue = aQueue;
34
+ let nextQueue = bQueue;
35
+
36
+ function swapQueue() {
37
+ if (runningQueue === aQueue) {
38
+ runningQueue = bQueue;
39
+ nextQueue = aQueue;
40
+ } else {
41
+ runningQueue = aQueue;
42
+ nextQueue = bQueue;
43
+ }
44
+ }
45
+
46
+ let currentFlushHandle: number | undefined;
47
+ let inAnimationFrame = false;
48
+
49
+ function flush() {
50
+ currentFlushHandle = undefined;
51
+
52
+ inAnimationFrame = true;
53
+
54
+ swapQueue();
55
+
56
+ while (runningQueue.size > 0) {
57
+ const op = runningQueue.pop()!;
58
+ op.run();
59
+ }
60
+
61
+ inAnimationFrame = false;
62
+ }
63
+
64
+ function schedule() {
65
+ if (currentFlushHandle) {
66
+ return;
67
+ }
68
+
69
+ currentFlushHandle = requestAnimationFrame(flush);
70
+ }
71
+
72
+ /**
73
+ * 如果当前在动画帧中,将操作加入当前队列,否则加入下一帧队列
74
+ */
75
+ function runAtThisOrScheduleAtNext(op: DomOperation) {
76
+ if (inAnimationFrame) {
77
+ runningQueue.add(op);
78
+ } else {
79
+ nextQueue.add(op);
80
+ }
81
+ }
82
+
83
+ function measure(fn: () => void): IDisposable {
84
+ const op = new DomOperation(fn, 10000);
85
+ runAtThisOrScheduleAtNext(op);
86
+ schedule();
87
+ return op;
88
+ }
89
+
90
+ function mutate(fn: () => void): IDisposable {
91
+ const op = new DomOperation(fn, -10000);
92
+ runAtThisOrScheduleAtNext(op);
93
+ schedule();
94
+ return op;
95
+ }
96
+
97
+ function measureAtNextFrame(fn: () => void): IDisposable {
98
+ const op = new DomOperation(fn, 10000);
99
+ nextQueue.add(op);
100
+ schedule();
101
+ return op;
102
+ }
103
+
104
+ const fastdom = {
105
+ measure,
106
+ measureAtNextFrame,
107
+ mutate,
108
+ };
109
+
110
+ export default fastdom;
package/src/dom/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Event as BaseEvent, Disposable, Emitter, IDisposable, isWebKit } from '@opensumi/ide-core-common';
2
2
  import { space } from '@opensumi/ide-utils/lib/strings';
3
3
 
4
+ import fastdom from './fastdom';
5
+
4
6
  export * from './event';
5
7
 
6
8
  export const EventType = {
@@ -177,6 +179,7 @@ export function trackFocus(element: HTMLElement | Window): IFocusTracker {
177
179
  return new FocusTracker(element);
178
180
  }
179
181
 
182
+ export { fastdom };
180
183
  /**
181
184
  * https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/button
182
185
  */
@@ -0,0 +1,52 @@
1
+ import { DisposableStore, Emitter, IDisposable } from '@opensumi/ide-core-common';
2
+
3
+ import fastdom from './fastdom';
4
+
5
+ export interface IDimension {
6
+ width: number;
7
+ height: number;
8
+ }
9
+
10
+ export class ResizeObserverWrapper implements IDisposable {
11
+ private _disposables = new DisposableStore();
12
+
13
+ private _onDidChange = this._disposables.add(new Emitter<IDimension>());
14
+ public onDidChange = this._onDidChange.event;
15
+
16
+ private _resizeObserver: ResizeObserver;
17
+
18
+ constructor(private _container: HTMLElement) {
19
+ this._resizeObserver = new ResizeObserver(this._callback);
20
+ }
21
+
22
+ private _callback = (entries: ResizeObserverEntry[]) => {
23
+ if (entries[0] && entries[0].contentRect) {
24
+ const width = entries[0].contentRect.width;
25
+ const height = entries[0].contentRect.height;
26
+
27
+ this._onDidChange.fire({ width, height });
28
+ }
29
+ };
30
+
31
+ private _observed = false;
32
+ observe() {
33
+ if (this._observed) {
34
+ return;
35
+ }
36
+ this._observed = true;
37
+
38
+ this._resizeObserver.observe(this._container);
39
+ fastdom.measure(() => {
40
+ this._onDidChange.fire({
41
+ width: this._container.clientWidth,
42
+ height: this._container.clientHeight,
43
+ });
44
+ });
45
+ }
46
+
47
+ dispose() {
48
+ this._observed = false;
49
+ this._disposables.dispose();
50
+ this._resizeObserver.disconnect();
51
+ }
52
+ }
@@ -1,7 +1,9 @@
1
+ import throttle from 'lodash/throttle';
1
2
  import React from 'react';
2
3
 
3
4
  import { IEventBus } from '@opensumi/ide-core-common';
4
5
 
6
+ import { fastdom } from '../dom';
5
7
  import { useInjectable } from '../react-hooks';
6
8
 
7
9
  import { ResizeEvent } from './layout.interface';
@@ -20,16 +22,35 @@ export const useViewState = (
20
22
  const [viewState, setViewState] = React.useState({ width: 0, height: 0 });
21
23
  const viewStateRef = React.useRef<ViewState>(viewState);
22
24
 
25
+ const updateViewState = throttle(
26
+ (height: number, width: number) => {
27
+ // 当视图被隐藏 (display: none) 时不更新 viewState
28
+ // 避免视图切换时触发无效的渲染
29
+ // 真正的 resize 操作不会出现 width/height 为 0 的情况
30
+ if (
31
+ (width !== viewStateRef.current.width || height !== viewStateRef.current.height) &&
32
+ (width !== 0 || height !== 0)
33
+ ) {
34
+ setViewState({ width, height });
35
+ viewStateRef.current = { width, height };
36
+ }
37
+ },
38
+ 16 * 3,
39
+ {
40
+ leading: true,
41
+ trailing: true,
42
+ },
43
+ );
44
+
23
45
  React.useEffect(() => {
24
- let lastFrame: number | null;
25
- const disposer = eventBus.on(ResizeEvent, (e) => {
26
- if (!manualObserve && e.payload.slotLocation === location) {
27
- if (lastFrame) {
28
- window.cancelAnimationFrame(lastFrame);
29
- }
30
- lastFrame = window.requestAnimationFrame(() => {
31
- if (containerRef.current && containerRef.current.clientHeight && containerRef.current.clientWidth) {
32
- setViewState({ height: containerRef.current.clientHeight, width: containerRef.current.clientWidth });
46
+ const disposer = eventBus.onDirective(ResizeEvent.createDirective(location), () => {
47
+ if (!manualObserve) {
48
+ fastdom.measureAtNextFrame(() => {
49
+ if (containerRef.current) {
50
+ const height = containerRef.current.clientHeight;
51
+ const width = containerRef.current.clientWidth;
52
+
53
+ updateViewState(height, width);
33
54
  }
34
55
  });
35
56
  }
@@ -37,27 +58,17 @@ export const useViewState = (
37
58
  return () => {
38
59
  disposer.dispose();
39
60
  };
40
- }, [containerRef.current]);
61
+ }, []);
41
62
 
42
63
  React.useEffect(() => {
64
+ const ResizeObserver = window.ResizeObserver;
43
65
  // TODO: 统一收敛到 resizeEvent 内
44
66
  if (manualObserve && containerRef.current) {
45
- const ResizeObserver = (window as any).ResizeObserver;
46
- const doUpdate = (entries) => {
67
+ const resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
47
68
  const width = entries[0].contentRect.width;
48
69
  const height = entries[0].contentRect.height;
49
- // 当视图被隐藏 (display: none) 时不更新 viewState
50
- // 避免视图切换时触发无效的渲染
51
- // 真正的 resize 操作不会出现 width/height 为 0 的情况
52
- if (
53
- (width !== viewStateRef.current.width || height !== viewStateRef.current.height) &&
54
- (width !== 0 || height !== 0)
55
- ) {
56
- setViewState({ width, height });
57
- viewStateRef.current = { width, height };
58
- }
59
- };
60
- const resizeObserver = new ResizeObserver(doUpdate);
70
+ updateViewState(height, width);
71
+ });
61
72
  resizeObserver.observe(containerRef.current);
62
73
  return () => {
63
74
  if (containerRef.current) {
@@ -104,6 +104,10 @@ export class ResizePayload {
104
104
  */
105
105
  constructor(public slotLocation: SlotLocation) {}
106
106
  }
107
- export class ResizeEvent extends BasicEvent<ResizePayload> {}
107
+ export class ResizeEvent extends BasicEvent<ResizePayload> {
108
+ static createDirective(location: SlotLocation) {
109
+ return `resize:${location}`;
110
+ }
111
+ }
108
112
 
109
113
  export class RenderedEvent extends BasicEvent<void> {}
@@ -6,9 +6,9 @@ import { ErrorBoundary } from '../react-providers';
6
6
  import { View } from './layout.interface';
7
7
 
8
8
  export const renderView = (view?: View) => (
9
- <>
10
- {view && ReactIs.isValidElementType(view.component) ? (
11
- <ErrorBoundary>{view.component && React.createElement(view.component, view.initialProps)}</ErrorBoundary>
12
- ) : null}
13
- </>
14
- );
9
+ <>
10
+ {view && ReactIs.isValidElementType(view.component) ? (
11
+ <ErrorBoundary>{view.component && React.createElement(view.component, view.initialProps)}</ErrorBoundary>
12
+ ) : null}
13
+ </>
14
+ );
@@ -35,6 +35,9 @@ export const SlotLocation = {
35
35
  };
36
36
 
37
37
  export function getSlotLocation(moduleName: string, layoutConfig: LayoutConfig) {
38
+ if (!layoutConfig) {
39
+ return '';
40
+ }
38
41
  for (const location of Object.keys(layoutConfig)) {
39
42
  if (layoutConfig[location].modules && layoutConfig[location].modules.indexOf(moduleName) > -1) {
40
43
  return location;