@secretstache/wordpress-gutenberg 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +4 -0
- package/build/index.js +8 -2
- package/build/index.js.map +1 -1
- package/build/{index.css → styles.css} +2 -0
- package/package.json +5 -3
- package/src/components/ColorPaletteControl.js +24 -0
- package/src/components/DataQueryControls.js +51 -0
- package/src/components/DividersControl.js +74 -0
- package/src/components/IconPicker.js +73 -0
- package/src/components/ImageActions.js +59 -0
- package/src/components/LinkControl.js +30 -0
- package/src/components/MediaControl.js +192 -0
- package/src/components/MediaTypeControl.js +54 -0
- package/src/components/ResourcesWrapper.js +46 -0
- package/src/components/ResponsiveSpacingControl.js +74 -0
- package/src/components/SortableSelect.js +60 -0
- package/src/components/SpacingControl.js +119 -0
- package/src/components/index.js +12 -0
- package/src/hooks/index.js +11 -0
- package/src/hooks/useAccordionItem.js +51 -0
- package/src/hooks/useAllowedBlocks.js +25 -0
- package/src/hooks/useBlockTabsData.js +90 -0
- package/src/hooks/useChildBlockPosition.js +31 -0
- package/src/hooks/useColorChange.js +12 -0
- package/src/hooks/useDataQuery.js +45 -0
- package/src/hooks/useParentBlock.js +57 -0
- package/src/hooks/usePreviewToggle.js +32 -0
- package/src/hooks/useSlider.js +24 -0
- package/src/hooks/useThemeColors.js +19 -0
- package/src/hooks/useUpdateAttribute.js +4 -0
- package/src/index.js +6 -0
- package/src/styles/_animation-file-renderer.scss +11 -0
- package/src/styles/_editor-base.scss +56 -0
- package/src/styles/_icon-picker.scss +4 -0
- package/src/styles/_image-wrapper.scss +59 -0
- package/src/styles/_link-control.scss +6 -0
- package/src/styles/_media-picker.scss +20 -0
- package/src/styles/_new-child-btn.scss +15 -0
- package/src/styles/_responsive-spacing.scss +34 -0
- package/src/styles/_root-block-appender.scss +40 -0
- package/src/styles/_sortable-select.scss +5 -0
- package/src/styles/styles.scss +12 -0
- package/src/utils/attributes.js +224 -0
- package/src/utils/constants.js +17 -0
- package/src/utils/helpers.js +175 -0
- package/src/utils/index.js +6 -0
- package/src/utils/rootBlock/README.md +71 -0
- package/src/utils/rootBlock/hideRootBlockForInlineInserter.js +13 -0
- package/src/utils/rootBlock/hideRootBlockForOtherBlocks.js +32 -0
- package/src/utils/rootBlock/index.js +4 -0
- package/src/utils/rootBlock/initRootBlockAppender.js +45 -0
- package/src/utils/rootBlock/setRootBlock.js +32 -0
- package/src/utils/waitForContainer/README.md +40 -0
- package/src/utils/waitForContainer/index.js +25 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
import { TabPanel } from '@wordpress/components';
|
2
|
+
import { useCallback } from '@wordpress/element';
|
3
|
+
|
4
|
+
import { SpacingControl } from './SpacingControl.js';
|
5
|
+
|
6
|
+
export const ResponsiveSpacingControl = ({
|
7
|
+
max = 6,
|
8
|
+
min = -1,
|
9
|
+
hasMargin = true,
|
10
|
+
hasPadding = true,
|
11
|
+
onChange,
|
12
|
+
value = { desktop: { margin: {}, padding: {} }, mobile: { margin: {}, padding: {} } },
|
13
|
+
}) => {
|
14
|
+
const handleDesktopChange = useCallback(
|
15
|
+
(desktop) => {
|
16
|
+
onChange({
|
17
|
+
desktop: desktop,
|
18
|
+
mobile: value.mobile,
|
19
|
+
});
|
20
|
+
},
|
21
|
+
[ onChange, value.mobile ],
|
22
|
+
);
|
23
|
+
|
24
|
+
const handleMobileChange = useCallback(
|
25
|
+
(mobile) => {
|
26
|
+
onChange({
|
27
|
+
mobile: mobile,
|
28
|
+
desktop: value.desktop,
|
29
|
+
});
|
30
|
+
},
|
31
|
+
[ onChange, value.desktop ],
|
32
|
+
);
|
33
|
+
|
34
|
+
return (
|
35
|
+
<TabPanel
|
36
|
+
className="bc-responsive-spacing-control"
|
37
|
+
tabs={[
|
38
|
+
{ name: 'desktop', title: 'Desktop', className: 'bc-responsive-spacing-tab bc-responsive-spacing-tab--desktop' },
|
39
|
+
{ name: 'mobile', title: 'Mobile', className: 'bc-responsive-spacing-tab bc-responsive-spacing-tab--mobile' },
|
40
|
+
]}
|
41
|
+
>
|
42
|
+
{(tab) => {
|
43
|
+
switch (tab.name) {
|
44
|
+
case 'desktop':
|
45
|
+
return (
|
46
|
+
<SpacingControl
|
47
|
+
key="desktop"
|
48
|
+
hasMargin={hasMargin}
|
49
|
+
hasPadding={hasPadding}
|
50
|
+
max={max}
|
51
|
+
min={min}
|
52
|
+
value={value.desktop}
|
53
|
+
onChange={handleDesktopChange}
|
54
|
+
/>
|
55
|
+
);
|
56
|
+
case 'mobile':
|
57
|
+
return (
|
58
|
+
<SpacingControl
|
59
|
+
key="mobile"
|
60
|
+
hasMargin={hasMargin}
|
61
|
+
hasPadding={hasPadding}
|
62
|
+
max={max}
|
63
|
+
min={min}
|
64
|
+
value={value.mobile}
|
65
|
+
onChange={handleMobileChange}
|
66
|
+
/>
|
67
|
+
);
|
68
|
+
default:
|
69
|
+
return null;
|
70
|
+
}
|
71
|
+
}}
|
72
|
+
</TabPanel>
|
73
|
+
);
|
74
|
+
};
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import ReactSelectAsync from 'react-select/async';
|
2
|
+
import ReactSelect from 'react-select';
|
3
|
+
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
|
4
|
+
import { components } from 'react-select';
|
5
|
+
|
6
|
+
const SortableMultiValue = SortableElement(props => {
|
7
|
+
const onMouseDown = e => {
|
8
|
+
e.preventDefault();
|
9
|
+
e.stopPropagation();
|
10
|
+
};
|
11
|
+
const innerProps = { ...props.innerProps, onMouseDown };
|
12
|
+
|
13
|
+
return <components.MultiValue {...props} innerProps={innerProps} />;
|
14
|
+
});
|
15
|
+
|
16
|
+
const SortableMultiValueLabel = SortableHandle(props => <components.MultiValueLabel {...props} />);
|
17
|
+
|
18
|
+
export const SortableSelectAsync = (props) => {
|
19
|
+
const Select = SortableContainer(ReactSelectAsync);
|
20
|
+
|
21
|
+
return (
|
22
|
+
<Select
|
23
|
+
isMulti
|
24
|
+
useDragHandle
|
25
|
+
components={{
|
26
|
+
MultiValue: SortableMultiValue,
|
27
|
+
MultiValueLabel: SortableMultiValueLabel,
|
28
|
+
}}
|
29
|
+
getHelperDimensions={({ node }) => node.getBoundingClientRect()}
|
30
|
+
axis="xy"
|
31
|
+
cacheOptions
|
32
|
+
defaultOptions
|
33
|
+
className="react-select-container"
|
34
|
+
classNamePrefix="react-select"
|
35
|
+
placeholder="Select Items"
|
36
|
+
{...props}
|
37
|
+
/>
|
38
|
+
);
|
39
|
+
};
|
40
|
+
|
41
|
+
export const SortableSelect = (props) => {
|
42
|
+
const Select = SortableContainer(ReactSelect);
|
43
|
+
|
44
|
+
return (
|
45
|
+
<Select
|
46
|
+
isMulti
|
47
|
+
useDragHandle
|
48
|
+
components={{
|
49
|
+
MultiValue: SortableMultiValue,
|
50
|
+
MultiValueLabel: SortableMultiValueLabel,
|
51
|
+
}}
|
52
|
+
getHelperDimensions={({ node }) => node.getBoundingClientRect()}
|
53
|
+
axis="xy"
|
54
|
+
className="react-select-container"
|
55
|
+
classNamePrefix="react-select"
|
56
|
+
placeholder="Select Items"
|
57
|
+
{...props}
|
58
|
+
/>
|
59
|
+
);
|
60
|
+
};
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { RangeControl, Tooltip } from '@wordpress/components';
|
2
|
+
import { useCallback } from '@wordpress/element';
|
3
|
+
|
4
|
+
const generateMarks = (min, max) => [
|
5
|
+
{ value: min, label: min === -1 ? 'Default' : min.toString() },
|
6
|
+
...Array.from({ length: max - min }, (_, i) => ({
|
7
|
+
value: min + i + 1,
|
8
|
+
label: '',
|
9
|
+
})),
|
10
|
+
];
|
11
|
+
|
12
|
+
const Control = ({ label, max, min, value, onChange, disabled, tooltip, ...other }) => (
|
13
|
+
<div className="bc-spacing-control-wrapper">
|
14
|
+
<Tooltip
|
15
|
+
text={tooltip}
|
16
|
+
placement="bottom"
|
17
|
+
delay={0}
|
18
|
+
>
|
19
|
+
<div className="bc-spacing-control-content">
|
20
|
+
<RangeControl
|
21
|
+
className="bc-spacing-range-control"
|
22
|
+
label={label}
|
23
|
+
value={value}
|
24
|
+
disabled={disabled}
|
25
|
+
onChange={onChange}
|
26
|
+
min={min}
|
27
|
+
max={max}
|
28
|
+
marks={generateMarks(min, max)}
|
29
|
+
resetFallbackValue={-1}
|
30
|
+
help="Use -1 for default settings."
|
31
|
+
renderTooltipContent={(value) => {
|
32
|
+
if (value === -1) return 'Default';
|
33
|
+
|
34
|
+
return value;
|
35
|
+
}}
|
36
|
+
{...other}
|
37
|
+
/>
|
38
|
+
</div>
|
39
|
+
</Tooltip>
|
40
|
+
</div>
|
41
|
+
);
|
42
|
+
|
43
|
+
export const SpacingControl = ({
|
44
|
+
hasMargin = true,
|
45
|
+
hasPadding = true,
|
46
|
+
disabledMargin = { top: false, bottom: false },
|
47
|
+
disabledPadding = { top: false, bottom: false },
|
48
|
+
marginTooltips = { top: '', bottom: '' },
|
49
|
+
paddingTooltips = { top: '', bottom: '' },
|
50
|
+
max = 6,
|
51
|
+
min = -1,
|
52
|
+
onChange,
|
53
|
+
value = { margin: {}, padding: {} },
|
54
|
+
}) => {
|
55
|
+
const handleChange = useCallback(
|
56
|
+
(type, direction) => (newValue) => {
|
57
|
+
onChange({
|
58
|
+
...value,
|
59
|
+
[type]: {
|
60
|
+
...value[type],
|
61
|
+
[direction]: newValue,
|
62
|
+
},
|
63
|
+
});
|
64
|
+
},
|
65
|
+
[ onChange, value ],
|
66
|
+
);
|
67
|
+
|
68
|
+
return (
|
69
|
+
<>
|
70
|
+
{hasMargin && (
|
71
|
+
<>
|
72
|
+
<Control
|
73
|
+
label="Top Margin"
|
74
|
+
max={max}
|
75
|
+
min={min}
|
76
|
+
value={value.margin.top ?? min}
|
77
|
+
onChange={handleChange('margin', 'top')}
|
78
|
+
disabled={disabledMargin.top}
|
79
|
+
tooltip={marginTooltips.top}
|
80
|
+
/>
|
81
|
+
|
82
|
+
<Control
|
83
|
+
label="Bottom Margin"
|
84
|
+
max={max}
|
85
|
+
min={min}
|
86
|
+
value={value.margin.bottom ?? min}
|
87
|
+
onChange={handleChange('margin', 'bottom')}
|
88
|
+
disabled={disabledMargin.bottom}
|
89
|
+
tooltip={marginTooltips.bottom}
|
90
|
+
/>
|
91
|
+
</>
|
92
|
+
)}
|
93
|
+
|
94
|
+
{hasPadding && (
|
95
|
+
<>
|
96
|
+
<Control
|
97
|
+
label="Top Padding"
|
98
|
+
max={max}
|
99
|
+
min={min}
|
100
|
+
value={value.padding.top ?? min}
|
101
|
+
onChange={handleChange('padding', 'top')}
|
102
|
+
disabled={disabledPadding.top}
|
103
|
+
tooltip={paddingTooltips.top}
|
104
|
+
/>
|
105
|
+
|
106
|
+
<Control
|
107
|
+
label="Bottom Padding"
|
108
|
+
max={max}
|
109
|
+
min={min}
|
110
|
+
value={value.padding.bottom ?? min}
|
111
|
+
onChange={handleChange('padding', 'bottom')}
|
112
|
+
disabled={disabledPadding.bottom}
|
113
|
+
tooltip={paddingTooltips.bottom}
|
114
|
+
/>
|
115
|
+
</>
|
116
|
+
)}
|
117
|
+
</>
|
118
|
+
);
|
119
|
+
};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export { ColorPaletteControl } from './ColorPaletteControl';
|
2
|
+
export { IconPicker } from './IconPicker';
|
3
|
+
export { ImageActions } from './ImageActions';
|
4
|
+
export { LinkControl } from './LinkControl';
|
5
|
+
export { ImageRenderer, VideoRenderer, AnimationRenderer, MediaControl } from './MediaControl.js';
|
6
|
+
export { SortableSelect, SortableSelectAsync } from './SortableSelect';
|
7
|
+
export { DataQueryControls } from './DataQueryControls.js';
|
8
|
+
export { SpacingControl } from './SpacingControl.js';
|
9
|
+
export { ResponsiveSpacingControl } from './ResponsiveSpacingControl.js';
|
10
|
+
export { ResourcesWrapper } from './ResourcesWrapper.js'
|
11
|
+
export { DividersControl } from './DividersControl.js'
|
12
|
+
export { MediaTypeControl } from './MediaTypeControl.js'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export { usePreviewToggle } from './usePreviewToggle.js';
|
2
|
+
export { useSlider } from './useSlider.js';
|
3
|
+
export { useParentBlock } from './useParentBlock.js';
|
4
|
+
export { useColorChange } from './useColorChange';
|
5
|
+
export { useThemeColors } from './useThemeColors';
|
6
|
+
export { useUpdateAttribute } from './useUpdateAttribute';
|
7
|
+
export { useDataQuery } from './useDataQuery';
|
8
|
+
export { useAccordionItem } from './useAccordionItem.js';
|
9
|
+
export { useBlockTabsData } from './useBlockTabsData.js';
|
10
|
+
export { useAllowedBlocks } from './useAllowedBlocks.js';
|
11
|
+
export { useChildBlockPosition } from './useChildBlockPosition.js';
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { useEffect, useRef } from '@wordpress/element';
|
2
|
+
|
3
|
+
export const useAccordionItem = (itemId, activeItemId, setActiveItemId, contentSelector) => {
|
4
|
+
const isActive = itemId === activeItemId;
|
5
|
+
const blockRef = useRef(null);
|
6
|
+
|
7
|
+
const openContent = () => {
|
8
|
+
const content = blockRef.current?.querySelector(contentSelector);
|
9
|
+
if (content) {
|
10
|
+
content.style.maxHeight = content.scrollHeight + 'px';
|
11
|
+
}
|
12
|
+
};
|
13
|
+
|
14
|
+
const closeContent = () => {
|
15
|
+
const content = blockRef.current?.querySelector(contentSelector);
|
16
|
+
if (content) {
|
17
|
+
content.style.maxHeight = 0;
|
18
|
+
}
|
19
|
+
};
|
20
|
+
|
21
|
+
// TODO: rename to toggleItem
|
22
|
+
const toggleContent = () => {
|
23
|
+
setActiveItemId(isActive ? null : itemId);
|
24
|
+
};
|
25
|
+
|
26
|
+
useEffect(() => {
|
27
|
+
if (isActive) {
|
28
|
+
openContent();
|
29
|
+
} else {
|
30
|
+
closeContent();
|
31
|
+
}
|
32
|
+
}, [isActive]);
|
33
|
+
|
34
|
+
useEffect(() => {
|
35
|
+
if (!isActive || !blockRef.current) return;
|
36
|
+
|
37
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
38
|
+
for (const entry of entries) {
|
39
|
+
if (entry.contentBoxSize) {
|
40
|
+
openContent();
|
41
|
+
}
|
42
|
+
}
|
43
|
+
});
|
44
|
+
|
45
|
+
resizeObserver.observe(blockRef.current);
|
46
|
+
|
47
|
+
return () => resizeObserver.disconnect();
|
48
|
+
}, [isActive]);
|
49
|
+
|
50
|
+
return { blockRef, toggleContent, isActive };
|
51
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { useSelect } from '@wordpress/data';
|
2
|
+
import { useMemo } from '@wordpress/element';
|
3
|
+
|
4
|
+
export const useAllowedBlocks = (blockName, excludedBlocks) => {
|
5
|
+
const allBlocks = useSelect(
|
6
|
+
(select) => select('core/blocks').getBlockTypes(),
|
7
|
+
[],
|
8
|
+
);
|
9
|
+
|
10
|
+
return useMemo(() => allBlocks
|
11
|
+
?.filter((block) => {
|
12
|
+
const blockHasParent = !!block?.parent;
|
13
|
+
const blockHasAncestor = !!block?.ancestor;
|
14
|
+
|
15
|
+
const isParent = block?.parent && block.parent.includes(blockName);
|
16
|
+
const isAncestor = block?.ancestor && block.ancestor.includes(blockName);
|
17
|
+
|
18
|
+
return !excludedBlocks.includes(block.name)
|
19
|
+
&& (!blockHasParent || isParent)
|
20
|
+
&& (!blockHasAncestor || isAncestor);
|
21
|
+
})
|
22
|
+
?.map((block) => block.name),
|
23
|
+
[allBlocks, excludedBlocks, blockName],
|
24
|
+
);
|
25
|
+
};
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import { useSelect, useDispatch } from '@wordpress/data';
|
2
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
3
|
+
import { useLayoutEffect, useState } from '@wordpress/element';
|
4
|
+
import { Button } from '@wordpress/components';
|
5
|
+
import { plus as plusIcon } from '@wordpress/icons';
|
6
|
+
import { createBlock } from '@wordpress/blocks';
|
7
|
+
|
8
|
+
import { useParentBlock } from './useParentBlock';
|
9
|
+
|
10
|
+
export const useBlockTabsData = (clientId, itemBlockName) => {
|
11
|
+
const { insertBlock } = useDispatch(blockEditorStore);
|
12
|
+
|
13
|
+
const {
|
14
|
+
childBlocks,
|
15
|
+
innerBlocksCount,
|
16
|
+
selectedBlock,
|
17
|
+
selectedBlockClientId,
|
18
|
+
parentBlockId,
|
19
|
+
getBlockRootClientId,
|
20
|
+
} = useSelect(
|
21
|
+
(select) => {
|
22
|
+
const {
|
23
|
+
getBlock,
|
24
|
+
getBlockCount,
|
25
|
+
getSelectedBlock,
|
26
|
+
getSelectedBlockClientId,
|
27
|
+
getBlockRootClientId,
|
28
|
+
} = select(blockEditorStore);
|
29
|
+
|
30
|
+
return {
|
31
|
+
childBlocks: getBlock(clientId)?.innerBlocks || [],
|
32
|
+
innerBlocksCount: getBlockCount(clientId),
|
33
|
+
selectedBlock: getSelectedBlock(),
|
34
|
+
selectedBlockClientId: getSelectedBlockClientId(),
|
35
|
+
parentBlockId: getBlockRootClientId(getSelectedBlockClientId()),
|
36
|
+
getBlockRootClientId,
|
37
|
+
};
|
38
|
+
},
|
39
|
+
[clientId]
|
40
|
+
);
|
41
|
+
|
42
|
+
const [activeItemId, setActiveItemId] = useState(null);
|
43
|
+
|
44
|
+
useLayoutEffect(() => {
|
45
|
+
if (childBlocks.length > 0 && !activeItemId) {
|
46
|
+
setActiveItemId(childBlocks[0].clientId);
|
47
|
+
}
|
48
|
+
}, [childBlocks, activeItemId]);
|
49
|
+
|
50
|
+
const parentItem = useParentBlock(selectedBlock?.clientId, clientId);
|
51
|
+
|
52
|
+
useLayoutEffect(() => {
|
53
|
+
if (parentItem) {
|
54
|
+
setActiveItemId(parentItem.clientId);
|
55
|
+
} else if (clientId === parentBlockId && selectedBlock?.clientId) {
|
56
|
+
setActiveItemId(selectedBlock.clientId);
|
57
|
+
}
|
58
|
+
}, [selectedBlock, parentItem, clientId, parentBlockId]);
|
59
|
+
|
60
|
+
// TODO: export
|
61
|
+
const addNewChildBlock = (blockName, attributes = {}, position = innerBlocksCount) => {
|
62
|
+
const newBlock = createBlock(blockName, attributes);
|
63
|
+
insertBlock(newBlock, position, clientId);
|
64
|
+
|
65
|
+
return newBlock;
|
66
|
+
};
|
67
|
+
|
68
|
+
// TODO: rename/export
|
69
|
+
const handleAddNewItem = () => {
|
70
|
+
const newItem = addNewChildBlock(itemBlockName);
|
71
|
+
setActiveItemId(newItem.clientId);
|
72
|
+
};
|
73
|
+
|
74
|
+
// TODO: make more flexible
|
75
|
+
const AddNewTabButton = ({ label = 'Add new tab' }) => (
|
76
|
+
<Button className="add-new-child-btn" icon={plusIcon} label={label} onClick={handleAddNewItem} />
|
77
|
+
);
|
78
|
+
|
79
|
+
return {
|
80
|
+
childBlocks,
|
81
|
+
innerBlocksCount,
|
82
|
+
selectedBlock,
|
83
|
+
selectedBlockClientId,
|
84
|
+
parentBlockId,
|
85
|
+
activeItemId,
|
86
|
+
setActiveItemId,
|
87
|
+
getBlockRootClientId,
|
88
|
+
AddNewTabButton,
|
89
|
+
};
|
90
|
+
};
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { useSelect } from '@wordpress/data';
|
2
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Hook to get the position of a child block within its parent block.
|
6
|
+
*
|
7
|
+
* This hook uses the `useSelect` hook from `@wordpress/data` to retrieve information about a specific block
|
8
|
+
* and its position within its parent block. It returns an object containing the block, the parent block, and
|
9
|
+
* the position of the child block within the parent block's inner blocks array.
|
10
|
+
*
|
11
|
+
* @param {string} childClientId - The client ID of the child block.
|
12
|
+
* @returns {Object} An object containing the block, the parent block, and the position of the child block:
|
13
|
+
* - {Object|null} block: The block object for the child block.
|
14
|
+
* - {Object|null} parentBlock: The block object for the parent block.
|
15
|
+
* - {number} position: The index position of the child block within the parent block's inner blocks array,
|
16
|
+
* or -1 if the parent block or child block is not found.
|
17
|
+
*
|
18
|
+
*/
|
19
|
+
export const useChildBlockPosition = (childClientId) => {
|
20
|
+
return useSelect(
|
21
|
+
(select) => {
|
22
|
+
const block = select(blockEditorStore).getBlock(childClientId);
|
23
|
+
const parentClientId = select(blockEditorStore).getBlockRootClientId(childClientId);
|
24
|
+
const parentBlock = parentClientId ? select(blockEditorStore).getBlock(parentClientId) : null;
|
25
|
+
const position = parentBlock ? parentBlock.innerBlocks.findIndex((block) => block.clientId === childClientId) : -1;
|
26
|
+
|
27
|
+
return { block, parentBlock, position };
|
28
|
+
},
|
29
|
+
[ childClientId ],
|
30
|
+
);
|
31
|
+
};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export const useColorChange = (colors, setAttributes) => (colorValue, property) => {
|
2
|
+
const selectedColor = colors.find(color => color.color === colorValue);
|
3
|
+
|
4
|
+
setAttributes({
|
5
|
+
[property]: selectedColor
|
6
|
+
? {
|
7
|
+
value: colorValue,
|
8
|
+
slug: selectedColor.slug,
|
9
|
+
}
|
10
|
+
: null,
|
11
|
+
});
|
12
|
+
};
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { useSelect } from '@wordpress/data';
|
2
|
+
import { QUERY_TYPES } from '../utils';
|
3
|
+
|
4
|
+
export const useDataQuery = (props) => {
|
5
|
+
const {
|
6
|
+
getPostType,
|
7
|
+
queryType,
|
8
|
+
curatedPostsIds = [],
|
9
|
+
|
10
|
+
categoriesTaxonomy,
|
11
|
+
curatedCategoriesIds = [],
|
12
|
+
|
13
|
+
numberOfPosts = -1, // all posts
|
14
|
+
extraQueryArgs,
|
15
|
+
dependencies,
|
16
|
+
} = props;
|
17
|
+
|
18
|
+
return useSelect((select) => {
|
19
|
+
const queryArgs = {
|
20
|
+
per_page: numberOfPosts,
|
21
|
+
order: 'desc',
|
22
|
+
orderby: 'date',
|
23
|
+
_embed: true,
|
24
|
+
...extraQueryArgs,
|
25
|
+
};
|
26
|
+
|
27
|
+
if (queryType === QUERY_TYPES.BY_CATEGORY && categoriesTaxonomy && curatedCategoriesIds?.length > 0) {
|
28
|
+
queryArgs[categoriesTaxonomy] = curatedCategoriesIds.join(',');
|
29
|
+
} else if (queryType === QUERY_TYPES.CURATED && curatedPostsIds?.length > 0) {
|
30
|
+
queryArgs['include'] = curatedPostsIds;
|
31
|
+
queryArgs['orderby'] = 'include';
|
32
|
+
}
|
33
|
+
|
34
|
+
const postType = getPostType();
|
35
|
+
|
36
|
+
const postsToShow = select('core').getEntityRecords('postType', postType, queryArgs);
|
37
|
+
const isResolving = select('core/data').isResolving('core', 'getEntityRecords', ['postType', postType, queryArgs]);
|
38
|
+
const isLoading = isResolving || postsToShow === undefined;
|
39
|
+
|
40
|
+
return {
|
41
|
+
postsToShow,
|
42
|
+
isLoading,
|
43
|
+
};
|
44
|
+
}, dependencies);
|
45
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { useSelect } from '@wordpress/data';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Hook to find a parent block of a specific type, scoped within a certain root block.
|
5
|
+
*
|
6
|
+
* @param {string} parentBlockName - The block type name to search for as a parent.
|
7
|
+
* @param {string} blockClientIdToLimitSearch - The clientId of the block to limit the search within
|
8
|
+
* @returns {Object|null} The matching parent block, or null if none is found.
|
9
|
+
*/
|
10
|
+
export const useParentBlock = (
|
11
|
+
parentBlockName,
|
12
|
+
blockClientIdToLimitSearch,
|
13
|
+
) => {
|
14
|
+
return useSelect((select) => {
|
15
|
+
const { getBlock, getBlockParents, getSelectedBlock } = select('core/block-editor');
|
16
|
+
|
17
|
+
const currentBlock = getSelectedBlock();
|
18
|
+
|
19
|
+
if (!currentBlock) {
|
20
|
+
return null;
|
21
|
+
}
|
22
|
+
|
23
|
+
// Get the list of parent blocks for the current block, from down to top
|
24
|
+
const parentBlocks = getBlockParents(currentBlock.clientId, true);
|
25
|
+
|
26
|
+
// Check if the blockClientIdToLimitSearch is located in the hierarchy of parents blocks
|
27
|
+
// of the current(selected) block, i.e. check if it's in the scope of searching
|
28
|
+
if (!parentBlocks.includes(blockClientIdToLimitSearch)) {
|
29
|
+
return null;
|
30
|
+
}
|
31
|
+
|
32
|
+
// Return the selected block if it already matches the target type
|
33
|
+
if (currentBlock?.name === parentBlockName) {
|
34
|
+
return currentBlock;
|
35
|
+
}
|
36
|
+
|
37
|
+
if (parentBlocks?.length) {
|
38
|
+
// Traverse the list of parent blocks to find the target parent block type
|
39
|
+
for (let i = 0; i < parentBlocks.length; i++) {
|
40
|
+
const parentBlockId = parentBlocks[i];
|
41
|
+
const parentBlock = getBlock(parentBlockId);
|
42
|
+
|
43
|
+
if (parentBlock?.name === parentBlockName) {
|
44
|
+
return parentBlock;
|
45
|
+
}
|
46
|
+
|
47
|
+
// Stop searching if we reach the top of the scope
|
48
|
+
if (blockClientIdToLimitSearch && parentBlockId === blockClientIdToLimitSearch) {
|
49
|
+
break;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
// No matching parent found within the constraints.
|
55
|
+
return null;
|
56
|
+
}, [ parentBlockName, blockClientIdToLimitSearch ]);
|
57
|
+
};
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { useState } from '@wordpress/element';
|
2
|
+
import { ToggleControl } from '@wordpress/components';
|
3
|
+
|
4
|
+
const defaultLabel = 'Enable Preview';
|
5
|
+
const defaultHelpText = 'Please check this option to see how the block will actually look and behave on the frontend.';
|
6
|
+
|
7
|
+
export const usePreviewToggle = (props = {}) => {
|
8
|
+
const {
|
9
|
+
disabled = false,
|
10
|
+
label = defaultLabel,
|
11
|
+
helpText = defaultHelpText,
|
12
|
+
} = props;
|
13
|
+
|
14
|
+
const [ isPreview, setIsPreview ] = useState(false);
|
15
|
+
|
16
|
+
const onChange = () => setIsPreview(prev => !prev);
|
17
|
+
|
18
|
+
const renderPreviewToggle = () => (
|
19
|
+
<ToggleControl
|
20
|
+
label={label}
|
21
|
+
help={helpText}
|
22
|
+
checked={isPreview}
|
23
|
+
onChange={onChange}
|
24
|
+
disabled={disabled}
|
25
|
+
/>
|
26
|
+
);
|
27
|
+
|
28
|
+
return {
|
29
|
+
isPreview,
|
30
|
+
renderPreviewToggle
|
31
|
+
};
|
32
|
+
};
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { useEffect, useRef } from '@wordpress/element';
|
2
|
+
|
3
|
+
export const useSlider = ({ isEnabled, setupSlider, dependencies = [] }) => {
|
4
|
+
const sliderElRef = useRef(null);
|
5
|
+
const sliderInstance = useRef(null);
|
6
|
+
|
7
|
+
useEffect(() => {
|
8
|
+
if (isEnabled && sliderElRef?.current) {
|
9
|
+
sliderInstance.current = setupSlider(sliderElRef.current);
|
10
|
+
}
|
11
|
+
|
12
|
+
return () => {
|
13
|
+
if (sliderInstance.current) {
|
14
|
+
sliderInstance.current?.destroy();
|
15
|
+
sliderInstance.current = null;
|
16
|
+
}
|
17
|
+
};
|
18
|
+
}, [isEnabled, ...dependencies]);
|
19
|
+
|
20
|
+
return {
|
21
|
+
sliderElRef,
|
22
|
+
sliderInstance: sliderInstance.current,
|
23
|
+
};
|
24
|
+
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { useSelect } from '@wordpress/data';
|
2
|
+
|
3
|
+
export const useThemeColors = (allowedColors = []) => {
|
4
|
+
return useSelect((select) => {
|
5
|
+
const { getSettings } = select('core/block-editor');
|
6
|
+
const allColors = getSettings()?.colors || [];
|
7
|
+
|
8
|
+
if (allowedColors.length > 0) {
|
9
|
+
const colorMap = new Map(allColors.map(color => [color.slug, color]));
|
10
|
+
|
11
|
+
return allowedColors
|
12
|
+
.map(slug => colorMap.get(slug))
|
13
|
+
.filter(Boolean);
|
14
|
+
}
|
15
|
+
|
16
|
+
return allColors;
|
17
|
+
}, [allowedColors]);
|
18
|
+
};
|
19
|
+
|