@elementor/editor-canvas 4.2.0-898 → 4.2.0-899
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/dist/index.js +453 -271
- package/dist/index.mjs +332 -150
- package/package.json +18 -18
- package/src/components/elements-overlays.tsx +14 -1
- package/src/components/grid-outline/__tests__/grid-outline-overlay.test.tsx +151 -0
- package/src/components/grid-outline/__tests__/grid-outline.test.tsx +131 -0
- package/src/components/grid-outline/grid-outline-line.tsx +27 -0
- package/src/components/grid-outline/grid-outline-overlay.tsx +41 -0
- package/src/components/grid-outline/grid-outline.tsx +45 -0
- package/src/components/grid-outline/index.ts +1 -0
- package/src/hooks/__tests__/use-grid-tracks.test.ts +152 -0
- package/src/hooks/use-grid-tracks.ts +52 -0
- package/src/utils/__tests__/grid-outline-utils.test.ts +142 -0
- package/src/utils/grid-outline-utils.ts +70 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { parseTrackList, toPx } from '../utils/grid-outline-utils';
|
|
4
|
+
|
|
5
|
+
export type GridTracks = {
|
|
6
|
+
columns: number[];
|
|
7
|
+
rows: number[];
|
|
8
|
+
columnGap: number;
|
|
9
|
+
rowGap: number;
|
|
10
|
+
padding: { top: number; right: number; bottom: number; left: number };
|
|
11
|
+
borderColor: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const EMPTY: GridTracks = {
|
|
15
|
+
columns: [],
|
|
16
|
+
rows: [],
|
|
17
|
+
columnGap: 0,
|
|
18
|
+
rowGap: 0,
|
|
19
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
20
|
+
borderColor: '',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function useGridTracks( element: HTMLElement | null, rect: DOMRect ): GridTracks {
|
|
24
|
+
return useMemo( () => {
|
|
25
|
+
if ( ! element ) {
|
|
26
|
+
return EMPTY;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const previewWindow = element.ownerDocument?.defaultView;
|
|
30
|
+
|
|
31
|
+
if ( ! previewWindow ) {
|
|
32
|
+
return EMPTY;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const computedStyle = previewWindow.getComputedStyle( element );
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
columns: parseTrackList( computedStyle.gridTemplateColumns ),
|
|
39
|
+
rows: parseTrackList( computedStyle.gridTemplateRows ),
|
|
40
|
+
columnGap: toPx( computedStyle.columnGap ),
|
|
41
|
+
rowGap: toPx( computedStyle.rowGap ),
|
|
42
|
+
padding: {
|
|
43
|
+
top: toPx( computedStyle.paddingTop ),
|
|
44
|
+
right: toPx( computedStyle.paddingRight ),
|
|
45
|
+
bottom: toPx( computedStyle.paddingBottom ),
|
|
46
|
+
left: toPx( computedStyle.paddingLeft ),
|
|
47
|
+
},
|
|
48
|
+
borderColor: computedStyle.getPropertyValue( '--e-a-border-color-bold' ).trim(),
|
|
49
|
+
};
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [ element, rect.width, rect.height ] );
|
|
52
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type GridTracks } from '../../hooks/use-grid-tracks';
|
|
2
|
+
import { computeOutlineGeometry, parseTrackList, snapToHalfPixel, toPx } from '../grid-outline-utils';
|
|
3
|
+
|
|
4
|
+
function makeTracks( partial: Partial< GridTracks > = {} ): GridTracks {
|
|
5
|
+
return {
|
|
6
|
+
columns: [],
|
|
7
|
+
rows: [],
|
|
8
|
+
columnGap: 0,
|
|
9
|
+
rowGap: 0,
|
|
10
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
11
|
+
borderColor: '',
|
|
12
|
+
...partial,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe( 'computeOutlineGeometry', () => {
|
|
17
|
+
it( 'returns empty boundary lists when there are no tracks', () => {
|
|
18
|
+
const geometry = computeOutlineGeometry( makeTracks(), 100, 100 );
|
|
19
|
+
|
|
20
|
+
expect( geometry.vertical ).toEqual( [] );
|
|
21
|
+
expect( geometry.horizontal ).toEqual( [] );
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
it( 'shifts boundaries by the padding offset on each axis', () => {
|
|
25
|
+
const tracks = makeTracks( {
|
|
26
|
+
columns: [ 100, 100 ],
|
|
27
|
+
rows: [ 80, 80 ],
|
|
28
|
+
padding: { top: 5, right: 0, bottom: 0, left: 20 },
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
const geometry = computeOutlineGeometry( tracks, 300, 300 );
|
|
32
|
+
|
|
33
|
+
expect( geometry.vertical ).toEqual( [ 20, 120, 220 ] );
|
|
34
|
+
expect( geometry.horizontal ).toEqual( [ 5, 85, 165 ] );
|
|
35
|
+
} );
|
|
36
|
+
|
|
37
|
+
it( 'emits both edges of every gap between tracks', () => {
|
|
38
|
+
const tracks = makeTracks( {
|
|
39
|
+
columns: [ 100, 100, 100 ],
|
|
40
|
+
columnGap: 10,
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
const geometry = computeOutlineGeometry( tracks, 400, 100 );
|
|
44
|
+
|
|
45
|
+
// 0 — 100 [gap 10] 110 — 210 [gap 10] 220 — 320
|
|
46
|
+
expect( geometry.vertical ).toEqual( [ 0, 100, 110, 210, 220, 320 ] );
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
it( 'collapses gap boundaries when the gap is zero', () => {
|
|
50
|
+
const tracks = makeTracks( {
|
|
51
|
+
rows: [ 50, 50, 50 ],
|
|
52
|
+
rowGap: 0,
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
const geometry = computeOutlineGeometry( tracks, 100, 200 );
|
|
56
|
+
|
|
57
|
+
expect( geometry.horizontal ).toEqual( [ 0, 50, 100, 150 ] );
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
it( 'handles uneven track sizes', () => {
|
|
61
|
+
const tracks = makeTracks( {
|
|
62
|
+
columns: [ 100, 200, 100 ],
|
|
63
|
+
columnGap: 5,
|
|
64
|
+
padding: { top: 0, right: 0, bottom: 0, left: 10 },
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
const geometry = computeOutlineGeometry( tracks, 500, 100 );
|
|
68
|
+
|
|
69
|
+
// 10 — 110 [5] 115 — 315 [5] 320 — 420
|
|
70
|
+
expect( geometry.vertical ).toEqual( [ 10, 110, 115, 315, 320, 420 ] );
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'derives the content rect from element size and padding', () => {
|
|
74
|
+
const tracks = makeTracks( {
|
|
75
|
+
padding: { top: 5, right: 8, bottom: 12, left: 20 },
|
|
76
|
+
} );
|
|
77
|
+
|
|
78
|
+
const geometry = computeOutlineGeometry( tracks, 300, 200 );
|
|
79
|
+
|
|
80
|
+
expect( geometry.top ).toBe( 5 );
|
|
81
|
+
expect( geometry.left ).toBe( 20 );
|
|
82
|
+
expect( geometry.right ).toBe( 292 );
|
|
83
|
+
expect( geometry.bottom ).toBe( 188 );
|
|
84
|
+
} );
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
describe( 'snapToHalfPixel', () => {
|
|
88
|
+
it.each( [
|
|
89
|
+
[ 0, 0.5 ],
|
|
90
|
+
[ 0.4, 0.5 ],
|
|
91
|
+
[ 0.6, 1.5 ],
|
|
92
|
+
[ 99.2, 99.5 ],
|
|
93
|
+
[ 100, 100.5 ],
|
|
94
|
+
[ -0.4, 0.5 ],
|
|
95
|
+
[ -1.6, -1.5 ],
|
|
96
|
+
] )( 'snaps %p to %p for crisp 1px strokes', ( input, expected ) => {
|
|
97
|
+
expect( snapToHalfPixel( input ) ).toBe( expected );
|
|
98
|
+
} );
|
|
99
|
+
} );
|
|
100
|
+
|
|
101
|
+
describe( 'parseTrackList', () => {
|
|
102
|
+
it( 'returns an empty list for empty input', () => {
|
|
103
|
+
expect( parseTrackList( '' ) ).toEqual( [] );
|
|
104
|
+
} );
|
|
105
|
+
|
|
106
|
+
it( 'returns an empty list for the "none" keyword', () => {
|
|
107
|
+
expect( parseTrackList( 'none' ) ).toEqual( [] );
|
|
108
|
+
} );
|
|
109
|
+
|
|
110
|
+
it( 'parses a single resolved px track', () => {
|
|
111
|
+
expect( parseTrackList( '200px' ) ).toEqual( [ 200 ] );
|
|
112
|
+
} );
|
|
113
|
+
|
|
114
|
+
it( 'parses multiple resolved px tracks', () => {
|
|
115
|
+
expect( parseTrackList( '100px 200px 100px' ) ).toEqual( [ 100, 200, 100 ] );
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
it( 'parses fractional pixel sizes', () => {
|
|
119
|
+
expect( parseTrackList( '99.5px 100.25px' ) ).toEqual( [ 99.5, 100.25 ] );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
it( 'collapses runs of whitespace between tracks', () => {
|
|
123
|
+
expect( parseTrackList( ' 100px 200px\t300px ' ) ).toEqual( [ 100, 200, 300 ] );
|
|
124
|
+
} );
|
|
125
|
+
|
|
126
|
+
it( 'drops zero-width entries (e.g. tracks resolved to 0)', () => {
|
|
127
|
+
expect( parseTrackList( '100px 0px 200px' ) ).toEqual( [ 100, 200 ] );
|
|
128
|
+
} );
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
describe( 'toPx', () => {
|
|
132
|
+
it.each( [
|
|
133
|
+
[ '0px', 0 ],
|
|
134
|
+
[ '10px', 10 ],
|
|
135
|
+
[ '99.5px', 99.5 ],
|
|
136
|
+
[ 'normal', 0 ],
|
|
137
|
+
[ '', 0 ],
|
|
138
|
+
[ 'auto', 0 ],
|
|
139
|
+
] )( 'parses %p as %p', ( input, expected ) => {
|
|
140
|
+
expect( toPx( input ) ).toBe( expected );
|
|
141
|
+
} );
|
|
142
|
+
} );
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type GridTracks } from '../hooks/use-grid-tracks';
|
|
2
|
+
|
|
3
|
+
export type OutlineGeometry = {
|
|
4
|
+
vertical: number[];
|
|
5
|
+
horizontal: number[];
|
|
6
|
+
top: number;
|
|
7
|
+
bottom: number;
|
|
8
|
+
left: number;
|
|
9
|
+
right: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function computeOutlineGeometry( tracks: GridTracks, width: number, height: number ): OutlineGeometry {
|
|
13
|
+
const { columns, rows, columnGap, rowGap, padding } = tracks;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
vertical: computeBoundaries( columns, columnGap, padding.left ),
|
|
17
|
+
horizontal: computeBoundaries( rows, rowGap, padding.top ),
|
|
18
|
+
top: padding.top,
|
|
19
|
+
bottom: height - padding.bottom,
|
|
20
|
+
left: padding.left,
|
|
21
|
+
right: width - padding.right,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function computeBoundaries( sizes: number[], gap: number, offset: number ): number[] {
|
|
26
|
+
if ( sizes.length === 0 ) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const boundaries: number[] = [];
|
|
31
|
+
let cursor = offset;
|
|
32
|
+
|
|
33
|
+
for ( let i = 0; i < sizes.length; i++ ) {
|
|
34
|
+
if ( i === 0 ) {
|
|
35
|
+
boundaries.push( cursor );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
cursor += sizes[ i ];
|
|
39
|
+
boundaries.push( cursor );
|
|
40
|
+
|
|
41
|
+
if ( i < sizes.length - 1 && gap > 0 ) {
|
|
42
|
+
cursor += gap;
|
|
43
|
+
boundaries.push( cursor );
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return boundaries;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function snapToHalfPixel( value: number ): number {
|
|
51
|
+
return Math.round( value ) + 0.5;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseTrackList( value: string ): number[] {
|
|
55
|
+
if ( ! value || value === 'none' ) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return value
|
|
60
|
+
.trim()
|
|
61
|
+
.split( /\s+/ )
|
|
62
|
+
.map( toPx )
|
|
63
|
+
.filter( ( n ) => n > 0 );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function toPx( value: string ): number {
|
|
67
|
+
const parsed = parseFloat( value );
|
|
68
|
+
|
|
69
|
+
return Number.isFinite( parsed ) ? parsed : 0;
|
|
70
|
+
}
|