@secretstache/wordpress-gutenberg 0.3.0 → 0.3.2
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/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
|
+
|