@elementor/editor-canvas 4.2.0-913 → 4.2.0-915

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.
@@ -36,97 +36,120 @@ describe( '<GridOutline />', () => {
36
36
  expect( svg ).toHaveAttribute( 'height', '200' );
37
37
  } );
38
38
 
39
- it( 'draws one rect per cell for an N×M grid', () => {
40
- const { container } = renderWithTheme(
41
- <GridOutline
42
- tracks={ makeTracks( { columns: [ 100, 100, 100 ], rows: [ 80, 80 ] } ) }
43
- width={ 300 }
44
- height={ 160 }
45
- />
46
- );
47
-
48
- expect( container.querySelectorAll( 'rect' ) ).toHaveLength( 3 * 2 );
49
- } );
50
-
51
- it( 'keeps the cell count stable with a gap and offsets cells past the gap', () => {
52
- const { container } = renderWithTheme(
53
- <GridOutline
54
- tracks={ makeTracks( {
55
- columns: [ 100, 100, 100 ],
56
- rows: [ 80, 80 ],
57
- columnGap: 10,
58
- rowGap: 8,
59
- } ) }
60
- width={ 320 }
61
- height={ 176 }
62
- />
63
- );
64
-
65
- const rects = Array.from( container.querySelectorAll( 'rect' ) );
66
- expect( rects ).toHaveLength( 3 * 2 );
67
-
68
- const xs = rects.map( ( rect ) => rect.getAttribute( 'x' ) );
69
- expect( xs ).toContain( '0.5' );
70
- expect( xs ).toContain( '110.5' );
71
- expect( xs ).toContain( '220.5' );
72
- } );
39
+ describe( 'no gap', () => {
40
+ it( 'draws one line per unique boundary for an N×M grid', () => {
41
+ const { container } = renderWithTheme(
42
+ <GridOutline
43
+ tracks={ makeTracks( { columns: [ 100, 100, 100 ], rows: [ 80, 80 ] } ) }
44
+ width={ 300 }
45
+ height={ 160 }
46
+ />
47
+ );
48
+
49
+ expect( container.querySelectorAll( 'line' ) ).toHaveLength( 4 + 3 );
50
+ expect( container.querySelectorAll( 'rect' ) ).toHaveLength( 0 );
51
+ } );
73
52
 
74
- it( 'snaps cell coordinates to half pixels for crisp 1px strokes', () => {
75
- const { container } = renderWithTheme(
76
- <GridOutline
77
- tracks={ makeTracks( {
78
- columns: [ 100, 100 ],
79
- rows: [ 80 ],
80
- padding: { top: 10, right: 10, bottom: 10, left: 10 },
81
- } ) }
82
- width={ 220 }
83
- height={ 100 }
84
- />
85
- );
53
+ it( 'snaps line coordinates to half pixels for crisp 1px strokes', () => {
54
+ const { container } = renderWithTheme(
55
+ <GridOutline
56
+ tracks={ makeTracks( {
57
+ columns: [ 100, 100 ],
58
+ rows: [ 80 ],
59
+ padding: { top: 10, right: 10, bottom: 10, left: 10 },
60
+ } ) }
61
+ width={ 220 }
62
+ height={ 100 }
63
+ />
64
+ );
65
+
66
+ const verticalXs = Array.from( container.querySelectorAll( 'line' ) )
67
+ .filter( ( line ) => line.getAttribute( 'x1' ) === line.getAttribute( 'x2' ) )
68
+ .map( ( line ) => line.getAttribute( 'x1' ) );
69
+ expect( verticalXs ).toEqual( [ '10.5', '110.5', '210.5' ] );
70
+ } );
86
71
 
87
- const xs = Array.from( container.querySelectorAll( 'rect' ) ).map( ( rect ) => rect.getAttribute( 'x' ) );
88
- expect( xs ).toEqual( [ '10.5', '110.5' ] );
89
- } );
72
+ it( 'passes the resolved iframe border color through to each line', () => {
73
+ const { container } = renderWithTheme(
74
+ <GridOutline
75
+ tracks={ makeTracks( { columns: [ 100 ], rows: [ 100 ], borderColor: '#abcdef' } ) }
76
+ width={ 100 }
77
+ height={ 100 }
78
+ />
79
+ );
80
+
81
+ const lines = container.querySelectorAll( 'line' );
82
+ expect( lines.length ).toBeGreaterThan( 0 );
83
+ lines.forEach( ( line ) => {
84
+ expect( line ).toHaveAttribute( 'stroke', '#abcdef' );
85
+ } );
86
+ } );
90
87
 
91
- it( 'spans a single full-width cell per row when only rows are defined', () => {
92
- const { container } = renderWithTheme(
93
- <GridOutline
94
- tracks={ makeTracks( {
95
- rows: [ 50, 50 ],
96
- padding: { top: 8, right: 12, bottom: 6, left: 4 },
97
- } ) }
98
- width={ 300 }
99
- height={ 120 }
100
- />
101
- );
88
+ it( 'renders nothing when there are no tracks on either axis', () => {
89
+ const { container } = renderWithTheme(
90
+ <GridOutline tracks={ makeTracks() } width={ 100 } height={ 100 } />
91
+ );
102
92
 
103
- const rects = Array.from( container.querySelectorAll( 'rect' ) );
104
- expect( rects ).toHaveLength( 2 );
105
- for ( const rect of rects ) {
106
- expect( rect ).toHaveAttribute( 'x', '4.5' );
107
- expect( rect ).toHaveAttribute( 'width', '284' );
108
- }
93
+ expect( container.querySelectorAll( 'line' ) ).toHaveLength( 0 );
94
+ expect( container.querySelectorAll( 'rect' ) ).toHaveLength( 0 );
95
+ } );
109
96
  } );
110
97
 
111
- it( 'passes the resolved iframe border color through to each cell', () => {
112
- const { container } = renderWithTheme(
113
- <GridOutline
114
- tracks={ makeTracks( { columns: [ 100 ], rows: [ 100 ], borderColor: '#abcdef' } ) }
115
- width={ 100 }
116
- height={ 100 }
117
- />
118
- );
119
-
120
- const rects = container.querySelectorAll( 'rect' );
121
- expect( rects.length ).toBeGreaterThan( 0 );
122
- rects.forEach( ( rect ) => {
123
- expect( rect ).toHaveAttribute( 'stroke', '#abcdef' );
98
+ describe( 'with a gap', () => {
99
+ it( 'draws one rect per cell so each cell has its own framed perimeter', () => {
100
+ const { container } = renderWithTheme(
101
+ <GridOutline
102
+ tracks={ makeTracks( {
103
+ columns: [ 100, 100, 100 ],
104
+ rows: [ 80, 80 ],
105
+ columnGap: 10,
106
+ rowGap: 8,
107
+ } ) }
108
+ width={ 320 }
109
+ height={ 176 }
110
+ />
111
+ );
112
+
113
+ expect( container.querySelectorAll( 'rect' ) ).toHaveLength( 3 * 2 );
114
+ expect( container.querySelectorAll( 'line' ) ).toHaveLength( 0 );
124
115
  } );
125
- } );
126
116
 
127
- it( 'renders no cells when there are no tracks on either axis', () => {
128
- const { container } = renderWithTheme( <GridOutline tracks={ makeTracks() } width={ 100 } height={ 100 } /> );
117
+ it( 'offsets cells past the gap', () => {
118
+ const { container } = renderWithTheme(
119
+ <GridOutline
120
+ tracks={ makeTracks( {
121
+ columns: [ 100, 100, 100 ],
122
+ rows: [ 80 ],
123
+ columnGap: 10,
124
+ } ) }
125
+ width={ 320 }
126
+ height={ 80 }
127
+ />
128
+ );
129
+
130
+ const xs = Array.from( container.querySelectorAll( 'rect' ) ).map( ( rect ) => rect.getAttribute( 'x' ) );
131
+ expect( xs ).toEqual( [ '0.5', '110.5', '220.5' ] );
132
+ } );
129
133
 
130
- expect( container.querySelectorAll( 'rect' ) ).toHaveLength( 0 );
134
+ it( 'passes the resolved iframe border color through to each cell', () => {
135
+ const { container } = renderWithTheme(
136
+ <GridOutline
137
+ tracks={ makeTracks( {
138
+ columns: [ 100, 100 ],
139
+ rows: [ 100 ],
140
+ columnGap: 10,
141
+ borderColor: '#abcdef',
142
+ } ) }
143
+ width={ 210 }
144
+ height={ 100 }
145
+ />
146
+ );
147
+
148
+ const rects = container.querySelectorAll( 'rect' );
149
+ expect( rects.length ).toBeGreaterThan( 0 );
150
+ rects.forEach( ( rect ) => {
151
+ expect( rect ).toHaveAttribute( 'stroke', '#abcdef' );
152
+ } );
153
+ } );
131
154
  } );
132
155
  } );
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  const FALLBACK_COLOR = 'rgba(0, 0, 0, 0.12)';
4
- export const DASH = '2 2';
5
4
 
6
5
  type Props = {
7
6
  x: number;
@@ -11,7 +10,7 @@ type Props = {
11
10
  color?: string;
12
11
  };
13
12
 
14
- export function GridOutlineCell( { x, y, width, height, color }: Props ) {
13
+ export function Cell( { x, y, width, height, color }: Props ) {
15
14
  return (
16
15
  <rect
17
16
  x={ x }
@@ -21,7 +20,7 @@ export function GridOutlineCell( { x, y, width, height, color }: Props ) {
21
20
  fill="none"
22
21
  stroke={ color || FALLBACK_COLOR }
23
22
  strokeWidth={ 1 }
24
- strokeDasharray={ DASH }
23
+ strokeDasharray="2 2"
25
24
  vectorEffect="non-scaling-stroke"
26
25
  />
27
26
  );
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { type GridTracks } from '../../hooks/use-grid-tracks';
4
- import { computeCellRects, snapToHalfPixel } from '../../utils/grid-outline-utils';
5
- import { GridOutlineCell } from './grid-outline-cell';
4
+ import { computeCellRects, computeGridLines, snapToHalfPixel } from '../../utils/grid-outline-utils';
5
+ import { Cell } from './cell';
6
+ import { Line } from './line';
6
7
 
7
8
  type Props = {
8
9
  tracks: GridTracks;
@@ -10,8 +11,47 @@ type Props = {
10
11
  height: number;
11
12
  };
12
13
 
14
+ const renderCells = ( tracks: GridTracks, width: number, height: number ) =>
15
+ computeCellRects( tracks, width, height ).map( ( cell, i ) => (
16
+ <Cell
17
+ key={ i }
18
+ x={ snapToHalfPixel( cell.x ) }
19
+ y={ snapToHalfPixel( cell.y ) }
20
+ width={ Math.round( cell.width ) }
21
+ height={ Math.round( cell.height ) }
22
+ color={ tracks.borderColor }
23
+ />
24
+ ) );
25
+
26
+ const renderLines = ( tracks: GridTracks, width: number, height: number ) => {
27
+ const { vertical, horizontal } = computeGridLines( tracks, width, height );
28
+
29
+ return [
30
+ ...vertical.map( ( line, i ) => (
31
+ <Line
32
+ key={ `v${ i }` }
33
+ x1={ snapToHalfPixel( line.x1 ) }
34
+ y1={ Math.round( line.y1 ) }
35
+ x2={ snapToHalfPixel( line.x2 ) }
36
+ y2={ Math.round( line.y2 ) }
37
+ color={ tracks.borderColor }
38
+ />
39
+ ) ),
40
+ ...horizontal.map( ( line, i ) => (
41
+ <Line
42
+ key={ `h${ i }` }
43
+ x1={ Math.round( line.x1 ) }
44
+ y1={ snapToHalfPixel( line.y1 ) }
45
+ x2={ Math.round( line.x2 ) }
46
+ y2={ snapToHalfPixel( line.y2 ) }
47
+ color={ tracks.borderColor }
48
+ />
49
+ ) ),
50
+ ];
51
+ };
52
+
13
53
  export function GridOutline( { tracks, width, height }: Props ) {
14
- const cells = computeCellRects( tracks, width, height );
54
+ const hasGap = tracks.columnGap > 0 || tracks.rowGap > 0;
15
55
 
16
56
  return (
17
57
  <svg
@@ -20,16 +60,7 @@ export function GridOutline( { tracks, width, height }: Props ) {
20
60
  style={ { position: 'absolute', inset: 0, overflow: 'visible' } }
21
61
  xmlns="http://www.w3.org/2000/svg"
22
62
  >
23
- { cells.map( ( cell, i ) => (
24
- <GridOutlineCell
25
- key={ i }
26
- x={ snapToHalfPixel( cell.x ) }
27
- y={ snapToHalfPixel( cell.y ) }
28
- width={ Math.round( cell.width ) }
29
- height={ Math.round( cell.height ) }
30
- color={ tracks.borderColor }
31
- />
32
- ) ) }
63
+ { hasGap ? renderCells( tracks, width, height ) : renderLines( tracks, width, height ) }
33
64
  </svg>
34
65
  );
35
66
  }
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+
3
+ const FALLBACK_COLOR = 'rgba(0, 0, 0, 0.12)';
4
+
5
+ type Props = {
6
+ x1: number;
7
+ y1: number;
8
+ x2: number;
9
+ y2: number;
10
+ color?: string;
11
+ };
12
+
13
+ export function Line( { x1, y1, x2, y2, color }: Props ) {
14
+ return (
15
+ <line
16
+ x1={ x1 }
17
+ y1={ y1 }
18
+ x2={ x2 }
19
+ y2={ y2 }
20
+ stroke={ color || FALLBACK_COLOR }
21
+ strokeWidth={ 1 }
22
+ strokeDasharray="2 2"
23
+ vectorEffect="non-scaling-stroke"
24
+ />
25
+ );
26
+ }
@@ -0,0 +1,158 @@
1
+ /* eslint-disable testing-library/no-node-access */
2
+ import { act, renderHook } from '@testing-library/react';
3
+
4
+ import { useGridChildren } from '../use-grid-children';
5
+
6
+ type ResizeCallback = ( entries: unknown[] ) => void;
7
+
8
+ class MockResizeObserver {
9
+ static instances: MockResizeObserver[] = [];
10
+ callback: ResizeCallback;
11
+ observed: Element[] = [];
12
+ disconnected = false;
13
+
14
+ constructor( callback: ResizeCallback ) {
15
+ this.callback = callback;
16
+ MockResizeObserver.instances.push( this );
17
+ }
18
+
19
+ observe( element: Element ) {
20
+ this.observed.push( element );
21
+ }
22
+
23
+ unobserve( element: Element ) {
24
+ this.observed = this.observed.filter( ( item ) => item !== element );
25
+ }
26
+
27
+ disconnect() {
28
+ this.disconnected = true;
29
+ this.observed = [];
30
+ }
31
+
32
+ trigger() {
33
+ this.callback( [] );
34
+ }
35
+ }
36
+
37
+ function createGridWithChildren( count: number ): HTMLElement {
38
+ const grid = document.createElement( 'div' );
39
+ for ( let i = 0; i < count; i++ ) {
40
+ grid.appendChild( document.createElement( 'div' ) );
41
+ }
42
+ document.body.appendChild( grid );
43
+ return grid;
44
+ }
45
+
46
+ describe( 'useGridChildren', () => {
47
+ const originalResizeObserver = globalThis.ResizeObserver;
48
+
49
+ beforeEach( () => {
50
+ MockResizeObserver.instances = [];
51
+ globalThis.ResizeObserver = MockResizeObserver as unknown as typeof ResizeObserver;
52
+ } );
53
+
54
+ afterEach( () => {
55
+ globalThis.ResizeObserver = originalResizeObserver;
56
+ document.body.innerHTML = '';
57
+ } );
58
+
59
+ it( 'returns 0 for a null element', () => {
60
+ const { result } = renderHook( () => useGridChildren( null ) );
61
+
62
+ expect( result.current ).toBe( 0 );
63
+ expect( MockResizeObserver.instances ).toHaveLength( 0 );
64
+ } );
65
+
66
+ it( 'observes existing children on mount', () => {
67
+ const grid = createGridWithChildren( 2 );
68
+
69
+ renderHook( () => useGridChildren( grid ) );
70
+
71
+ expect( MockResizeObserver.instances ).toHaveLength( 1 );
72
+ expect( MockResizeObserver.instances[ 0 ].observed ).toHaveLength( 2 );
73
+ } );
74
+
75
+ it( 'increments when a child is appended', async () => {
76
+ const grid = createGridWithChildren( 1 );
77
+
78
+ const { result } = renderHook( () => useGridChildren( grid ) );
79
+ const before = result.current;
80
+
81
+ await act( async () => {
82
+ grid.appendChild( document.createElement( 'div' ) );
83
+ await Promise.resolve();
84
+ } );
85
+
86
+ expect( result.current ).toBeGreaterThan( before );
87
+ expect( MockResizeObserver.instances[ 0 ].observed ).toHaveLength( 2 );
88
+ } );
89
+
90
+ it( 'increments when a child is removed', async () => {
91
+ const grid = createGridWithChildren( 2 );
92
+
93
+ const { result } = renderHook( () => useGridChildren( grid ) );
94
+ const before = result.current;
95
+
96
+ await act( async () => {
97
+ grid.removeChild( grid.children[ 0 ] );
98
+ await Promise.resolve();
99
+ } );
100
+
101
+ expect( result.current ).toBeGreaterThan( before );
102
+ expect( MockResizeObserver.instances[ 0 ].observed ).toHaveLength( 1 );
103
+ } );
104
+
105
+ it( 'increments when children are reordered', async () => {
106
+ const grid = createGridWithChildren( 3 );
107
+
108
+ const { result } = renderHook( () => useGridChildren( grid ) );
109
+ const before = result.current;
110
+
111
+ await act( async () => {
112
+ grid.insertBefore( grid.children[ 2 ], grid.children[ 0 ] );
113
+ await Promise.resolve();
114
+ } );
115
+
116
+ expect( result.current ).toBeGreaterThan( before );
117
+ } );
118
+
119
+ it( 'increments when a child resizes', () => {
120
+ const grid = createGridWithChildren( 1 );
121
+
122
+ const { result } = renderHook( () => useGridChildren( grid ) );
123
+ const before = result.current;
124
+
125
+ act( () => {
126
+ MockResizeObserver.instances[ 0 ].trigger();
127
+ } );
128
+
129
+ expect( result.current ).toBeGreaterThan( before );
130
+ } );
131
+
132
+ it( 'disconnects observers on unmount', () => {
133
+ const grid = createGridWithChildren( 2 );
134
+
135
+ const { unmount } = renderHook( () => useGridChildren( grid ) );
136
+
137
+ unmount();
138
+
139
+ expect( MockResizeObserver.instances[ 0 ].disconnected ).toBe( true );
140
+ } );
141
+
142
+ it( 'switches observation when the element changes', () => {
143
+ const first = createGridWithChildren( 1 );
144
+ const second = createGridWithChildren( 2 );
145
+
146
+ const { rerender } = renderHook( ( { element } ) => useGridChildren( element ), {
147
+ initialProps: { element: first as HTMLElement | null },
148
+ } );
149
+
150
+ const initialObserver = MockResizeObserver.instances[ 0 ];
151
+
152
+ rerender( { element: second } );
153
+
154
+ expect( initialObserver.disconnected ).toBe( true );
155
+ expect( MockResizeObserver.instances ).toHaveLength( 2 );
156
+ expect( MockResizeObserver.instances[ 1 ].observed ).toHaveLength( 2 );
157
+ } );
158
+ } );
@@ -45,8 +45,9 @@ function mockElement( style: Partial< Style > = {} ) {
45
45
  getPropertyValue: ( name: string ) => ( name === '--e-a-border-color-bold' ? resolved[ name ] : '' ),
46
46
  } ) );
47
47
 
48
- const element = {
49
- ownerDocument: {
48
+ const element = document.createElement( 'div' );
49
+ Object.defineProperty( element, 'ownerDocument', {
50
+ value: {
50
51
  defaultView: {
51
52
  getComputedStyle,
52
53
  requestAnimationFrame: ( cb: FrameRequestCallback ) => {
@@ -56,7 +57,8 @@ function mockElement( style: Partial< Style > = {} ) {
56
57
  cancelAnimationFrame: jest.fn(),
57
58
  } as unknown as Window,
58
59
  },
59
- } as unknown as HTMLElement;
60
+ configurable: true,
61
+ } );
60
62
 
61
63
  return { element, resolved, getComputedStyle };
62
64
  }
@@ -78,7 +80,11 @@ describe( 'useGridTracks', () => {
78
80
  } );
79
81
 
80
82
  it( 'returns the empty snapshot when the element has no owner window', () => {
81
- const element = { ownerDocument: { defaultView: null } } as unknown as HTMLElement;
83
+ const element = document.createElement( 'div' );
84
+ Object.defineProperty( element, 'ownerDocument', {
85
+ value: { defaultView: null },
86
+ configurable: true,
87
+ } );
82
88
 
83
89
  const { result } = renderHook( () => useGridTracks( element, RECT ) );
84
90
 
@@ -154,6 +160,54 @@ describe( 'useGridTracks', () => {
154
160
  expect( result.current.columns ).toEqual( [ 100, 100, 100, 100 ] );
155
161
  } );
156
162
 
163
+ it( 'recomputes when children are added to the grid', async () => {
164
+ const grid = document.createElement( 'div' );
165
+ grid.appendChild( document.createElement( 'div' ) );
166
+ document.body.appendChild( grid );
167
+
168
+ const resolved: Style = { ...DEFAULT_STYLE, gridTemplateRows: '80px' };
169
+ const getComputedStyle = jest.fn().mockImplementation( () => ( {
170
+ gridTemplateColumns: resolved.gridTemplateColumns,
171
+ gridTemplateRows: resolved.gridTemplateRows,
172
+ columnGap: resolved.columnGap,
173
+ rowGap: resolved.rowGap,
174
+ paddingTop: resolved.paddingTop,
175
+ paddingRight: resolved.paddingRight,
176
+ paddingBottom: resolved.paddingBottom,
177
+ paddingLeft: resolved.paddingLeft,
178
+ getPropertyValue: ( name: string ) => ( name === '--e-a-border-color-bold' ? resolved[ name ] : '' ),
179
+ } ) );
180
+
181
+ Object.defineProperty( grid, 'ownerDocument', {
182
+ value: {
183
+ defaultView: {
184
+ getComputedStyle,
185
+ requestAnimationFrame: ( cb: FrameRequestCallback ) => {
186
+ cb( 0 );
187
+ return 1;
188
+ },
189
+ cancelAnimationFrame: jest.fn(),
190
+ },
191
+ },
192
+ configurable: true,
193
+ } );
194
+
195
+ const { result } = renderHook( () => useGridTracks( grid, RECT ) );
196
+
197
+ expect( result.current.rows ).toEqual( [ 80 ] );
198
+
199
+ resolved.gridTemplateRows = '80px 80px';
200
+
201
+ await act( async () => {
202
+ grid.appendChild( document.createElement( 'div' ) );
203
+ await Promise.resolve();
204
+ } );
205
+
206
+ expect( result.current.rows ).toEqual( [ 80, 80 ] );
207
+
208
+ document.body.removeChild( grid );
209
+ } );
210
+
157
211
  it( 'recomputes when the device mode changes', () => {
158
212
  const mock = mockElement( { gridTemplateRows: '80px', rowGap: '8px' } );
159
213
 
@@ -0,0 +1,48 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export function useGridChildren( element: HTMLElement | null ): number {
4
+ const [ signal, setSignal ] = useState( 0 );
5
+
6
+ useEffect( () => {
7
+ if ( ! element ) {
8
+ return;
9
+ }
10
+
11
+ const bump = () => setSignal( ( previous ) => previous + 1 );
12
+
13
+ const resizeObserver = new ResizeObserver( bump );
14
+ const observed = new Set< Element >();
15
+
16
+ const syncChildren = () => {
17
+ for ( const child of Array.from( element.children ) ) {
18
+ if ( ! observed.has( child ) ) {
19
+ resizeObserver.observe( child );
20
+ observed.add( child );
21
+ }
22
+ }
23
+
24
+ for ( const child of observed ) {
25
+ if ( child.parentElement !== element ) {
26
+ resizeObserver.unobserve( child );
27
+ observed.delete( child );
28
+ }
29
+ }
30
+ };
31
+
32
+ syncChildren();
33
+
34
+ const mutationObserver = new MutationObserver( () => {
35
+ syncChildren();
36
+ bump();
37
+ } );
38
+ mutationObserver.observe( element, { childList: true } );
39
+
40
+ return () => {
41
+ mutationObserver.disconnect();
42
+ resizeObserver.disconnect();
43
+ observed.clear();
44
+ };
45
+ }, [ element ] );
46
+
47
+ return signal;
48
+ }
@@ -3,6 +3,7 @@ import { ELEMENT_STYLE_CHANGE_EVENT } from '@elementor/editor-elements';
3
3
  import { __privateUseListenTo as useListenTo, windowEvent } from '@elementor/editor-v1-adapters';
4
4
 
5
5
  import { toGridTracks } from '../utils/grid-outline-utils';
6
+ import { useGridChildren } from './use-grid-children';
6
7
 
7
8
  export type GridTracks = {
8
9
  columns: number[];
@@ -31,6 +32,7 @@ export function useGridTracks( element: HTMLElement | null, rect: DOMRect ): Gri
31
32
  [ windowEvent( ELEMENT_STYLE_CHANGE_EVENT ), windowEvent( DEVICE_MODE_CHANGE_EVENT ) ],
32
33
  () => ( {} )
33
34
  );
35
+ const childrenTrigger = useGridChildren( element );
34
36
 
35
37
  useEffect( () => {
36
38
  const previewWindow = element?.ownerDocument?.defaultView;
@@ -47,7 +49,7 @@ export function useGridTracks( element: HTMLElement | null, rect: DOMRect ): Gri
47
49
  return () => {
48
50
  previewWindow.cancelAnimationFrame( frame );
49
51
  };
50
- }, [ element, rect.width, rect.height, trigger ] );
52
+ }, [ element, rect.width, rect.height, trigger, childrenTrigger ] );
51
53
 
52
54
  return tracks;
53
55
  }