This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
/
Copy pathhacks.ts
153 lines (134 loc) · 3.86 KB
/
hacks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
* HACKS
*
* This file contains functionality to "lock" blocks i.e. to prevent blocks being moved or deleted. This needs to be
* kept in place until native support for locking is available in WordPress (estimated WordPress 5.9).
*/
/**
* @todo Remove custom block locking (requires native WordPress support)
*/
/**
* External dependencies
*/
import {
useBlockProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { isTextField } from '@wordpress/dom';
import { store as blocksStore } from '@wordpress/blocks';
import { useSelect, subscribe, select as _select } from '@wordpress/data';
import { useEffect, useRef } from '@wordpress/element';
import { MutableRefObject } from 'react';
import { BACKSPACE, DELETE } from '@wordpress/keycodes';
/**
* Toggle class on body.
*
* @param {string} className CSS Class name.
* @param {boolean} add True to add, false to remove.
*/
const toggleBodyClass = ( className: string, add = true ) => {
if ( add ) {
window.document.body.classList.add( className );
} else {
window.document.body.classList.remove( className );
}
};
/**
* addClassToBody
*
* This components watches the current selected block and adds a class name to the body if that block is locked. If the
* current block is not locked, it removes the class name. The appended body class is used to hide UI elements to prevent
* the block from being deleted.
*
* We use a component so we can react to changes in the store.
*/
export const addClassToBody = (): void => {
subscribe( () => {
const blockEditorSelect = _select( blockEditorStore );
if ( ! blockEditorSelect ) {
return;
}
const selectedBlock = blockEditorSelect.getSelectedBlock();
if ( ! selectedBlock ) {
return;
}
const blockSelect = _select( blocksStore );
const selectedBlockType = blockSelect.getBlockType(
selectedBlock.name
);
toggleBodyClass(
'wc-lock-selected-block--remove',
!! selectedBlockType?.supports?.lock?.remove
);
toggleBodyClass(
'wc-lock-selected-block--move',
!! selectedBlockType?.supports?.lock?.move
);
} );
};
/**
* This is a hook we use in conjunction with useBlockProps. Its goal is to check if a block is locked (move or remove)
* and will stop the keydown event from propagating to stop it from being deleted via the keyboard.
*
* @todo Disable custom locking support if native support is detected.
*/
const useLockBlock = ( {
clientId,
ref,
type,
}: {
clientId: string;
ref: MutableRefObject< Element >;
type: string;
} ): void => {
const { isSelected, blockType } = useSelect(
( select ) => {
return {
isSelected: select( blockEditorStore ).isBlockSelected(
clientId
),
blockType: select( blocksStore ).getBlockType( type ),
};
},
[ clientId ]
);
const node = ref.current;
return useEffect( () => {
if ( ! isSelected || ! node ) {
return;
}
function onKeyDown( event: KeyboardEvent ) {
const { keyCode, target } = event;
if ( keyCode !== BACKSPACE && keyCode !== DELETE ) {
return;
}
if ( target !== node || isTextField( target ) ) {
return;
}
// Prevent the keyboard event from propogating if it supports locking.
if ( blockType?.supports?.lock?.remove ) {
event.preventDefault();
event.stopPropagation();
}
}
node.addEventListener( 'keydown', onKeyDown, true );
return () => {
node.removeEventListener( 'keydown', onKeyDown, true );
};
}, [ node, isSelected, blockType ] );
};
/**
* This hook is a light wrapper to useBlockProps, it wraps that hook plus useLockBlock to pass data between them.
*/
export const useBlockPropsWithLocking = (
props?: Record< string, unknown > = {}
): Record< string, unknown > => {
const ref = useRef< Element >();
const blockProps = useBlockProps( { ref, ...props } );
useLockBlock( {
ref,
type: blockProps[ 'data-type' ],
clientId: blockProps[ 'data-block' ],
} );
return blockProps;
};