@elementor/editor-floating-panels 4.3.0-970

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 (41) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +117 -0
  3. package/dist/index.d.mts +159 -0
  4. package/dist/index.d.ts +159 -0
  5. package/dist/index.js +1108 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +1062 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +58 -0
  10. package/src/api.ts +36 -0
  11. package/src/components/external/floating-panel-body.tsx +15 -0
  12. package/src/components/external/floating-panel-footer.tsx +22 -0
  13. package/src/components/external/floating-panel-header.tsx +100 -0
  14. package/src/components/external/floating-panel.tsx +10 -0
  15. package/src/components/external/index.ts +4 -0
  16. package/src/components/internal/corner-resize-handle.tsx +69 -0
  17. package/src/components/internal/drag-handle.tsx +29 -0
  18. package/src/components/internal/host.tsx +86 -0
  19. package/src/components/internal/panel-window.tsx +100 -0
  20. package/src/components/internal/resize-handle.tsx +60 -0
  21. package/src/constants.ts +1 -0
  22. package/src/hooks/use-floating-panel-actions.ts +26 -0
  23. package/src/hooks/use-floating-panel-drag.ts +85 -0
  24. package/src/hooks/use-floating-panel-resize.ts +166 -0
  25. package/src/hooks/use-floating-panel-status.ts +17 -0
  26. package/src/hooks/use-floating-panel-z-index.ts +7 -0
  27. package/src/index.ts +15 -0
  28. package/src/init.ts +14 -0
  29. package/src/location.ts +3 -0
  30. package/src/persistence.ts +76 -0
  31. package/src/store/index.ts +2 -0
  32. package/src/store/selectors.ts +61 -0
  33. package/src/store/slice.ts +113 -0
  34. package/src/sync.ts +104 -0
  35. package/src/types.ts +64 -0
  36. package/src/utils/clamp.ts +3 -0
  37. package/src/utils/corner-position.ts +149 -0
  38. package/src/utils/direction.ts +3 -0
  39. package/src/utils/drag-math.ts +49 -0
  40. package/src/utils/resize-math.ts +107 -0
  41. package/src/utils/viewport-bounds.ts +7 -0
@@ -0,0 +1,113 @@
1
+ import { __createSlice, type PayloadAction } from '@elementor/store';
2
+
3
+ import { type FloatingPanelDefaults, type FloatingPanelState, type LogicalPosition, type LogicalSize } from '../types';
4
+ import { buildInitialPosition, type PanelCorner } from '../utils/corner-position';
5
+
6
+ type SliceState = {
7
+ byId: Record< string, FloatingPanelState >;
8
+ minSizeById: Record< string, LogicalSize >;
9
+ titlesById: Record< string, string >;
10
+ isDraggableById: Record< string, boolean >;
11
+ isResizableById: Record< string, boolean >;
12
+ topZIndex: number;
13
+ };
14
+
15
+ const DEFAULT_CORNER: PanelCorner = 'block-start-inline-start';
16
+
17
+ const initialState: SliceState = {
18
+ byId: {},
19
+ minSizeById: {},
20
+ titlesById: {},
21
+ isDraggableById: {},
22
+ isResizableById: {},
23
+ topZIndex: 0,
24
+ };
25
+
26
+ export const slice = __createSlice( {
27
+ name: 'floatingPanels',
28
+ initialState,
29
+ reducers: {
30
+ register(
31
+ state,
32
+ action: PayloadAction< {
33
+ id: string;
34
+ defaults: FloatingPanelDefaults;
35
+ title?: string;
36
+ isDraggable?: boolean;
37
+ isResizable?: boolean;
38
+ persisted?: FloatingPanelState;
39
+ } >
40
+ ) {
41
+ const { id, defaults, title, isDraggable, isResizable, persisted } = action.payload;
42
+
43
+ state.minSizeById[ id ] = { inlineSize: defaults.minWidth, blockSize: defaults.minHeight };
44
+ state.isDraggableById[ id ] = isDraggable ?? false;
45
+ state.isResizableById[ id ] = isResizable ?? false;
46
+
47
+ if ( title !== undefined ) {
48
+ state.titlesById[ id ] = title;
49
+ }
50
+
51
+ if ( state.byId[ id ] ) {
52
+ return;
53
+ }
54
+
55
+ const corner = defaults.corner ?? DEFAULT_CORNER;
56
+ const canReusePersisted = persisted && persisted.corner === corner;
57
+
58
+ state.byId[ id ] = canReusePersisted
59
+ ? persisted
60
+ : {
61
+ isOpen: false,
62
+ corner,
63
+ position: buildInitialPosition( corner, defaults.initialPosition ),
64
+ size: { inlineSize: defaults.width, blockSize: defaults.height },
65
+ zIndex: 0,
66
+ };
67
+
68
+ if ( persisted && persisted.zIndex > state.topZIndex ) {
69
+ state.topZIndex = persisted.zIndex;
70
+ }
71
+ },
72
+ open( state, action: PayloadAction< string > ) {
73
+ const panel = state.byId[ action.payload ];
74
+
75
+ if ( panel ) {
76
+ panel.isOpen = true;
77
+ }
78
+ },
79
+ close( state, action: PayloadAction< string > ) {
80
+ const panel = state.byId[ action.payload ];
81
+
82
+ if ( panel ) {
83
+ panel.isOpen = false;
84
+ }
85
+ },
86
+ setPosition( state, action: PayloadAction< { id: string; position: LogicalPosition } > ) {
87
+ const panel = state.byId[ action.payload.id ];
88
+
89
+ if ( panel ) {
90
+ panel.position = action.payload.position;
91
+ }
92
+ },
93
+ setSize( state, action: PayloadAction< { id: string; size: LogicalSize } > ) {
94
+ const panel = state.byId[ action.payload.id ];
95
+
96
+ if ( panel ) {
97
+ panel.size = action.payload.size;
98
+ }
99
+ },
100
+ bringToFront( state, action: PayloadAction< string > ) {
101
+ const panel = state.byId[ action.payload ];
102
+
103
+ if ( ! panel ) {
104
+ return;
105
+ }
106
+
107
+ state.topZIndex += 1;
108
+ panel.zIndex = state.topZIndex;
109
+ },
110
+ },
111
+ } );
112
+
113
+ export type FloatingPanelsSliceState = SliceState;
package/src/sync.ts ADDED
@@ -0,0 +1,104 @@
1
+ import { __getStore, __subscribeWithSelector } from '@elementor/store';
2
+
3
+ import { decodePersistedState, encodePersistedState, PERSISTENCE_STORAGE_KEY } from './persistence';
4
+ import { type GlobalState } from './store/selectors';
5
+ import { type FloatingPanelState } from './types';
6
+
7
+ const PERSIST_DEBOUNCE_MS = 250;
8
+
9
+ export interface PanelStateStorage {
10
+ read: () => string | null;
11
+ write: ( value: string ) => void;
12
+ }
13
+
14
+ export const localStorageAdapter: PanelStateStorage = {
15
+ read: () => {
16
+ try {
17
+ return globalThis.localStorage?.getItem( PERSISTENCE_STORAGE_KEY ) ?? null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ },
22
+ write: ( value ) => {
23
+ try {
24
+ globalThis.localStorage?.setItem( PERSISTENCE_STORAGE_KEY, value );
25
+ } catch {
26
+ // Best-effort: storage may be full, blocked, or unavailable.
27
+ }
28
+ },
29
+ };
30
+
31
+ let cachedPersistedState: Record< string, FloatingPanelState > = {};
32
+ let isSyncInitialized = false;
33
+ let persistenceTimer: ReturnType< typeof setTimeout > | null = null;
34
+ let persistenceUnsubscribe: ( () => void ) | null = null;
35
+ let persistenceSession = 0;
36
+
37
+ export function isFloatingPanelsSyncInitialized(): boolean {
38
+ return isSyncInitialized;
39
+ }
40
+
41
+ export function sync( storage: PanelStateStorage = localStorageAdapter ): void {
42
+ isSyncInitialized = true;
43
+ cachedPersistedState = decodePersistedState( storage.read() );
44
+
45
+ schedulePersistence( storage );
46
+ }
47
+
48
+ export function getPersistedState( id: string ): FloatingPanelState | undefined {
49
+ return cachedPersistedState[ id ];
50
+ }
51
+
52
+ const STORE_READY_POLL_MS = 16;
53
+
54
+ function clearPersistenceTimer(): void {
55
+ if ( persistenceTimer ) {
56
+ clearTimeout( persistenceTimer );
57
+ persistenceTimer = null;
58
+ }
59
+ }
60
+
61
+ function schedulePersistence( storage: PanelStateStorage ): void {
62
+ clearPersistenceTimer();
63
+ persistenceUnsubscribe?.();
64
+ persistenceUnsubscribe = null;
65
+
66
+ const session = ++persistenceSession;
67
+
68
+ const subscribe = () => {
69
+ if ( session !== persistenceSession ) {
70
+ return;
71
+ }
72
+
73
+ persistenceUnsubscribe = __subscribeWithSelector(
74
+ ( state: GlobalState ) => state.floatingPanels.byId,
75
+ ( byId ) => {
76
+ clearPersistenceTimer();
77
+
78
+ persistenceTimer = setTimeout( () => {
79
+ if ( session !== persistenceSession ) {
80
+ return;
81
+ }
82
+
83
+ cachedPersistedState = { ...cachedPersistedState, ...byId };
84
+ storage.write( encodePersistedState( cachedPersistedState ) );
85
+ }, PERSIST_DEBOUNCE_MS );
86
+ }
87
+ );
88
+ };
89
+
90
+ const waitForStore = () => {
91
+ if ( session !== persistenceSession ) {
92
+ return;
93
+ }
94
+
95
+ if ( __getStore() ) {
96
+ subscribe();
97
+ return;
98
+ }
99
+
100
+ setTimeout( waitForStore, STORE_READY_POLL_MS );
101
+ };
102
+
103
+ waitForStore();
104
+ }
package/src/types.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { type ComponentType } from 'react';
2
+
3
+ import type { PanelCorner } from './utils/corner-position';
4
+
5
+ export type { PanelCorner };
6
+
7
+ export type LogicalSize = {
8
+ inlineSize: number;
9
+ blockSize: number;
10
+ };
11
+
12
+ export type LogicalPosition = {
13
+ insetInlineStart: number;
14
+ insetInlineEnd: number;
15
+ insetBlockStart: number;
16
+ insetBlockEnd: number;
17
+ };
18
+
19
+ /**
20
+ * Public API for declaring a floating panel's initial dimensions.
21
+ *
22
+ * Sizes use physical names (width/height) intentionally — Elementor renders
23
+ * in horizontal writing mode only, so these are equivalent to logical
24
+ * inline-size/block-size. Positioning continues to use logical names
25
+ * because position is genuinely direction-sensitive.
26
+ *
27
+ * If multi-writing-mode support is ever needed, replace these fields with
28
+ * `size: LogicalSize` and `minSize: LogicalSize` and update the store's
29
+ * register reducer.
30
+ */
31
+ export type FloatingPanelDefaults = {
32
+ width: number;
33
+ height: number;
34
+ minWidth: number;
35
+ minHeight: number;
36
+ corner?: PanelCorner;
37
+ initialPosition?: Partial< LogicalPosition >;
38
+ };
39
+
40
+ export type FloatingPanelHeaderAction = {
41
+ id: string;
42
+ icon: ComponentType;
43
+ label: string;
44
+ onClick?: () => void;
45
+ disabled?: boolean;
46
+ };
47
+
48
+ export type FloatingPanelDeclaration = {
49
+ id: string;
50
+ title: string;
51
+ icon: ComponentType;
52
+ component: ComponentType;
53
+ isDraggable?: boolean;
54
+ isResizable?: boolean;
55
+ defaults: FloatingPanelDefaults;
56
+ };
57
+
58
+ export type FloatingPanelState = {
59
+ isOpen: boolean;
60
+ zIndex: number;
61
+ size: LogicalSize;
62
+ corner: PanelCorner;
63
+ position: LogicalPosition;
64
+ };
@@ -0,0 +1,3 @@
1
+ export function clamp( value: number, min: number, max: number ): number {
2
+ return Math.min( Math.max( min, value ), Math.max( min, max ) );
3
+ }
@@ -0,0 +1,149 @@
1
+ import type { LogicalPosition, LogicalSize } from '../types';
2
+
3
+ export type PanelCorner =
4
+ | 'block-start-inline-start'
5
+ | 'block-start-inline-end'
6
+ | 'block-end-inline-start'
7
+ | 'block-end-inline-end';
8
+
9
+ export const DEFAULT_INSET_BLOCK_PX = 80;
10
+ export const DEFAULT_INSET_INLINE_PX = 24;
11
+
12
+ type InsetKey = keyof LogicalPosition;
13
+
14
+ const ACTIVE_INSETS: Record< PanelCorner, [ InsetKey, InsetKey ] > = {
15
+ 'block-start-inline-start': [ 'insetInlineStart', 'insetBlockStart' ],
16
+ 'block-start-inline-end': [ 'insetInlineEnd', 'insetBlockStart' ],
17
+ 'block-end-inline-start': [ 'insetInlineStart', 'insetBlockEnd' ],
18
+ 'block-end-inline-end': [ 'insetInlineEnd', 'insetBlockEnd' ],
19
+ };
20
+
21
+ const CORNER_DEFAULTS: Record< PanelCorner, Partial< LogicalPosition > > = {
22
+ 'block-start-inline-start': { insetInlineStart: DEFAULT_INSET_INLINE_PX, insetBlockStart: DEFAULT_INSET_BLOCK_PX },
23
+ 'block-start-inline-end': { insetInlineEnd: DEFAULT_INSET_INLINE_PX, insetBlockStart: DEFAULT_INSET_BLOCK_PX },
24
+ 'block-end-inline-start': { insetInlineStart: DEFAULT_INSET_INLINE_PX, insetBlockEnd: DEFAULT_INSET_BLOCK_PX },
25
+ 'block-end-inline-end': { insetInlineEnd: DEFAULT_INSET_INLINE_PX, insetBlockEnd: DEFAULT_INSET_BLOCK_PX },
26
+ };
27
+
28
+ const EMPTY_POSITION: LogicalPosition = {
29
+ insetBlockStart: 0,
30
+ insetBlockEnd: 0,
31
+ insetInlineStart: 0,
32
+ insetInlineEnd: 0,
33
+ };
34
+
35
+ export function getActiveInsetKeys( corner: PanelCorner ): [ InsetKey, InsetKey ] {
36
+ return ACTIVE_INSETS[ corner ];
37
+ }
38
+
39
+ export function usesInlineStart( corner: PanelCorner ): boolean {
40
+ return corner.endsWith( 'inline-start' );
41
+ }
42
+
43
+ export function usesBlockStart( corner: PanelCorner ): boolean {
44
+ return corner.startsWith( 'block-start' );
45
+ }
46
+
47
+ export function buildInitialPosition( corner: PanelCorner, overrides?: Partial< LogicalPosition > ): LogicalPosition {
48
+ const [ inlineKey, blockKey ] = getActiveInsetKeys( corner );
49
+ const defaults = CORNER_DEFAULTS[ corner ];
50
+
51
+ return {
52
+ ...EMPTY_POSITION,
53
+ [ inlineKey ]: overrides?.[ inlineKey ] ?? defaults[ inlineKey ] ?? 0,
54
+ [ blockKey ]: overrides?.[ blockKey ] ?? defaults[ blockKey ] ?? 0,
55
+ };
56
+ }
57
+
58
+ export function positionToCssInsets(
59
+ corner: PanelCorner,
60
+ position: LogicalPosition
61
+ ): Partial< Record< InsetKey, string > > {
62
+ const [ inlineKey, blockKey ] = getActiveInsetKeys( corner );
63
+
64
+ return {
65
+ [ inlineKey ]: `${ position[ inlineKey ] }px`,
66
+ [ blockKey ]: `${ position[ blockKey ] }px`,
67
+ };
68
+ }
69
+
70
+ export function toStartAnchoredPosition(
71
+ corner: PanelCorner,
72
+ position: LogicalPosition,
73
+ size: LogicalSize,
74
+ viewport: { width: number; height: number }
75
+ ): Pick< LogicalPosition, 'insetInlineStart' | 'insetBlockStart' > {
76
+ return {
77
+ insetInlineStart: usesInlineStart( corner )
78
+ ? position.insetInlineStart
79
+ : viewport.width - position.insetInlineEnd - size.inlineSize,
80
+ insetBlockStart: usesBlockStart( corner )
81
+ ? position.insetBlockStart
82
+ : viewport.height - position.insetBlockEnd - size.blockSize,
83
+ };
84
+ }
85
+
86
+ export function fromStartAnchoredPosition(
87
+ corner: PanelCorner,
88
+ startAnchored: Pick< LogicalPosition, 'insetInlineStart' | 'insetBlockStart' >,
89
+ size: LogicalSize,
90
+ viewport: { width: number; height: number }
91
+ ): LogicalPosition {
92
+ if ( usesInlineStart( corner ) && usesBlockStart( corner ) ) {
93
+ return {
94
+ ...EMPTY_POSITION,
95
+ insetInlineStart: startAnchored.insetInlineStart,
96
+ insetBlockStart: startAnchored.insetBlockStart,
97
+ };
98
+ }
99
+
100
+ const position = { ...EMPTY_POSITION };
101
+
102
+ if ( usesInlineStart( corner ) ) {
103
+ position.insetInlineStart = startAnchored.insetInlineStart;
104
+ position.insetBlockEnd = viewport.height - startAnchored.insetBlockStart - size.blockSize;
105
+ } else if ( usesBlockStart( corner ) ) {
106
+ position.insetInlineEnd = viewport.width - startAnchored.insetInlineStart - size.inlineSize;
107
+ position.insetBlockStart = startAnchored.insetBlockStart;
108
+ } else {
109
+ position.insetInlineEnd = viewport.width - startAnchored.insetInlineStart - size.inlineSize;
110
+ position.insetBlockEnd = viewport.height - startAnchored.insetBlockStart - size.blockSize;
111
+ }
112
+
113
+ return position;
114
+ }
115
+
116
+ export function activePositionChanged( corner: PanelCorner, before: LogicalPosition, after: LogicalPosition ): boolean {
117
+ const [ inlineKey, blockKey ] = getActiveInsetKeys( corner );
118
+
119
+ return before[ inlineKey ] !== after[ inlineKey ] || before[ blockKey ] !== after[ blockKey ];
120
+ }
121
+
122
+ export type DragBounds = {
123
+ minInlineStart: number;
124
+ maxInlineStart: number;
125
+ minInlineEnd: number;
126
+ maxInlineEnd: number;
127
+ minBlockStart: number;
128
+ maxBlockStart: number;
129
+ minBlockEnd: number;
130
+ maxBlockEnd: number;
131
+ };
132
+
133
+ export function getDragBounds(
134
+ size: LogicalSize,
135
+ viewport: { width: number; height: number },
136
+ sidePanelInlineSize: number,
137
+ appBarHeight: number
138
+ ): DragBounds {
139
+ return {
140
+ minInlineStart: sidePanelInlineSize,
141
+ maxInlineStart: viewport.width - size.inlineSize,
142
+ minInlineEnd: 0,
143
+ maxInlineEnd: viewport.width - sidePanelInlineSize - size.inlineSize,
144
+ minBlockStart: appBarHeight,
145
+ maxBlockStart: viewport.height - size.blockSize,
146
+ minBlockEnd: 0,
147
+ maxBlockEnd: viewport.height - appBarHeight - size.blockSize,
148
+ };
149
+ }
@@ -0,0 +1,3 @@
1
+ export function isRtl(): boolean {
2
+ return ( document?.documentElement?.dir ?? '' ).toLowerCase() === 'rtl';
3
+ }
@@ -0,0 +1,49 @@
1
+ import { type LogicalPosition } from '../types';
2
+ import { clamp } from './clamp';
3
+ import {
4
+ type DragBounds,
5
+ getActiveInsetKeys,
6
+ type PanelCorner,
7
+ usesBlockStart,
8
+ usesInlineStart,
9
+ } from './corner-position';
10
+
11
+ export type { DragBounds } from './corner-position';
12
+
13
+ export type PhysicalDelta = { dx: number; dy: number };
14
+
15
+ export type LogicalDelta = { inlineDelta: number; blockDelta: number };
16
+
17
+ export function physicalToLogicalDelta( delta: PhysicalDelta, isRtl: boolean ): LogicalDelta {
18
+ return {
19
+ inlineDelta: isRtl ? -delta.dx : delta.dx,
20
+ blockDelta: delta.dy,
21
+ };
22
+ }
23
+
24
+ export function applyDragDelta(
25
+ corner: PanelCorner,
26
+ position: LogicalPosition,
27
+ delta: LogicalDelta,
28
+ bounds: DragBounds
29
+ ): LogicalPosition {
30
+ const [ inlineKey, blockKey ] = getActiveInsetKeys( corner );
31
+ const inlineDelta = usesInlineStart( corner ) ? delta.inlineDelta : -delta.inlineDelta;
32
+ const blockDelta = usesBlockStart( corner ) ? delta.blockDelta : -delta.blockDelta;
33
+
34
+ const inlineBounds =
35
+ inlineKey === 'insetInlineStart'
36
+ ? { min: bounds.minInlineStart, max: bounds.maxInlineStart }
37
+ : { min: bounds.minInlineEnd, max: bounds.maxInlineEnd };
38
+
39
+ const blockBounds =
40
+ blockKey === 'insetBlockStart'
41
+ ? { min: bounds.minBlockStart, max: bounds.maxBlockStart }
42
+ : { min: bounds.minBlockEnd, max: bounds.maxBlockEnd };
43
+
44
+ return {
45
+ ...position,
46
+ [ inlineKey ]: clamp( position[ inlineKey ] + inlineDelta, inlineBounds.min, inlineBounds.max ),
47
+ [ blockKey ]: clamp( position[ blockKey ] + blockDelta, blockBounds.min, blockBounds.max ),
48
+ };
49
+ }
@@ -0,0 +1,107 @@
1
+ import { type LogicalPosition, type LogicalSize } from '../types';
2
+ import { clamp } from './clamp';
3
+ import { type PanelCorner } from './corner-position';
4
+
5
+ export type ResizeEdge = 'inline-start' | 'inline-end' | 'block-start' | 'block-end';
6
+
7
+ export type ResizeCorner = PanelCorner;
8
+
9
+ export type ResizeDirection = ResizeEdge | PanelCorner;
10
+
11
+ export type ResizeBounds = {
12
+ minInlineSize: number;
13
+ maxInlineSize: number;
14
+ minBlockSize: number;
15
+ maxBlockSize: number;
16
+ minInlineStart: number;
17
+ minBlockStart: number;
18
+ };
19
+
20
+ export function applyInlineEndResize( size: LogicalSize, inlineDelta: number, bounds: ResizeBounds ): LogicalSize {
21
+ return {
22
+ inlineSize: clamp( size.inlineSize + inlineDelta, bounds.minInlineSize, bounds.maxInlineSize ),
23
+ blockSize: size.blockSize,
24
+ };
25
+ }
26
+
27
+ export function applyBlockEndResize( size: LogicalSize, blockDelta: number, bounds: ResizeBounds ): LogicalSize {
28
+ return {
29
+ inlineSize: size.inlineSize,
30
+ blockSize: clamp( size.blockSize + blockDelta, bounds.minBlockSize, bounds.maxBlockSize ),
31
+ };
32
+ }
33
+
34
+ export function applyInlineStartResize(
35
+ position: LogicalPosition,
36
+ size: LogicalSize,
37
+ inlineDelta: number,
38
+ bounds: ResizeBounds
39
+ ): { position: LogicalPosition; size: LogicalSize } {
40
+ const anchorInlineEnd = position.insetInlineStart + size.inlineSize;
41
+ const lowBound = Math.max( bounds.minInlineStart, anchorInlineEnd - bounds.maxInlineSize );
42
+ const highBound = anchorInlineEnd - bounds.minInlineSize;
43
+ const nextInlineStart = clamp( position.insetInlineStart + inlineDelta, lowBound, highBound );
44
+
45
+ return {
46
+ position: { ...position, insetInlineStart: nextInlineStart },
47
+ size: { ...size, inlineSize: anchorInlineEnd - nextInlineStart },
48
+ };
49
+ }
50
+
51
+ export function applyBlockStartResize(
52
+ position: LogicalPosition,
53
+ size: LogicalSize,
54
+ blockDelta: number,
55
+ bounds: ResizeBounds
56
+ ): { position: LogicalPosition; size: LogicalSize } {
57
+ const anchorBlockEnd = position.insetBlockStart + size.blockSize;
58
+ const lowBound = Math.max( bounds.minBlockStart, anchorBlockEnd - bounds.maxBlockSize );
59
+ const highBound = anchorBlockEnd - bounds.minBlockSize;
60
+ const nextBlockStart = clamp( position.insetBlockStart + blockDelta, lowBound, highBound );
61
+
62
+ return {
63
+ position: { ...position, insetBlockStart: nextBlockStart },
64
+ size: { ...size, blockSize: anchorBlockEnd - nextBlockStart },
65
+ };
66
+ }
67
+
68
+ export function applyResize(
69
+ direction: ResizeDirection,
70
+ position: LogicalPosition,
71
+ size: LogicalSize,
72
+ inlineDelta: number,
73
+ blockDelta: number,
74
+ bounds: ResizeBounds
75
+ ): { position: LogicalPosition; size: LogicalSize } {
76
+ if ( direction === 'inline-end' ) {
77
+ return { position, size: applyInlineEndResize( size, inlineDelta, bounds ) };
78
+ }
79
+
80
+ if ( direction === 'block-end' ) {
81
+ return { position, size: applyBlockEndResize( size, blockDelta, bounds ) };
82
+ }
83
+
84
+ if ( direction === 'inline-start' ) {
85
+ return applyInlineStartResize( position, size, inlineDelta, bounds );
86
+ }
87
+
88
+ if ( direction === 'block-start' ) {
89
+ return applyBlockStartResize( position, size, blockDelta, bounds );
90
+ }
91
+
92
+ let next = { position, size };
93
+
94
+ if ( direction.includes( 'inline-start' ) ) {
95
+ next = applyInlineStartResize( next.position, next.size, inlineDelta, bounds );
96
+ } else {
97
+ next = { position: next.position, size: applyInlineEndResize( next.size, inlineDelta, bounds ) };
98
+ }
99
+
100
+ if ( direction.includes( 'block-start' ) ) {
101
+ next = applyBlockStartResize( next.position, next.size, blockDelta, bounds );
102
+ } else {
103
+ next = { position: next.position, size: applyBlockEndResize( next.size, blockDelta, bounds ) };
104
+ }
105
+
106
+ return next;
107
+ }
@@ -0,0 +1,7 @@
1
+ export const APP_BAR_HEIGHT_PX = 48;
2
+
3
+ const SIDE_PANEL_SELECTOR = '#elementor-panel';
4
+
5
+ export function getSidePanelInlineSize(): number {
6
+ return document.querySelector< HTMLElement >( SIDE_PANEL_SELECTOR )?.getBoundingClientRect().width ?? 0;
7
+ }