@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.
Files changed (54) hide show
  1. package/README.md +4 -0
  2. package/build/index.js +8 -2
  3. package/build/index.js.map +1 -1
  4. package/build/{index.css → styles.css} +2 -0
  5. package/package.json +5 -3
  6. package/src/components/ColorPaletteControl.js +24 -0
  7. package/src/components/DataQueryControls.js +51 -0
  8. package/src/components/DividersControl.js +74 -0
  9. package/src/components/IconPicker.js +73 -0
  10. package/src/components/ImageActions.js +59 -0
  11. package/src/components/LinkControl.js +30 -0
  12. package/src/components/MediaControl.js +192 -0
  13. package/src/components/MediaTypeControl.js +54 -0
  14. package/src/components/ResourcesWrapper.js +46 -0
  15. package/src/components/ResponsiveSpacingControl.js +74 -0
  16. package/src/components/SortableSelect.js +60 -0
  17. package/src/components/SpacingControl.js +119 -0
  18. package/src/components/index.js +12 -0
  19. package/src/hooks/index.js +11 -0
  20. package/src/hooks/useAccordionItem.js +51 -0
  21. package/src/hooks/useAllowedBlocks.js +25 -0
  22. package/src/hooks/useBlockTabsData.js +90 -0
  23. package/src/hooks/useChildBlockPosition.js +31 -0
  24. package/src/hooks/useColorChange.js +12 -0
  25. package/src/hooks/useDataQuery.js +45 -0
  26. package/src/hooks/useParentBlock.js +57 -0
  27. package/src/hooks/usePreviewToggle.js +32 -0
  28. package/src/hooks/useSlider.js +24 -0
  29. package/src/hooks/useThemeColors.js +19 -0
  30. package/src/hooks/useUpdateAttribute.js +4 -0
  31. package/src/index.js +6 -0
  32. package/src/styles/_animation-file-renderer.scss +11 -0
  33. package/src/styles/_editor-base.scss +56 -0
  34. package/src/styles/_icon-picker.scss +4 -0
  35. package/src/styles/_image-wrapper.scss +59 -0
  36. package/src/styles/_link-control.scss +6 -0
  37. package/src/styles/_media-picker.scss +20 -0
  38. package/src/styles/_new-child-btn.scss +15 -0
  39. package/src/styles/_responsive-spacing.scss +34 -0
  40. package/src/styles/_root-block-appender.scss +40 -0
  41. package/src/styles/_sortable-select.scss +5 -0
  42. package/src/styles/styles.scss +12 -0
  43. package/src/utils/attributes.js +224 -0
  44. package/src/utils/constants.js +17 -0
  45. package/src/utils/helpers.js +175 -0
  46. package/src/utils/index.js +6 -0
  47. package/src/utils/rootBlock/README.md +71 -0
  48. package/src/utils/rootBlock/hideRootBlockForInlineInserter.js +13 -0
  49. package/src/utils/rootBlock/hideRootBlockForOtherBlocks.js +32 -0
  50. package/src/utils/rootBlock/index.js +4 -0
  51. package/src/utils/rootBlock/initRootBlockAppender.js +45 -0
  52. package/src/utils/rootBlock/setRootBlock.js +32 -0
  53. package/src/utils/waitForContainer/README.md +40 -0
  54. 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
+
@@ -0,0 +1,4 @@
1
+ export const useUpdateAttribute = (setAttributes) => (attributeName, value) => {
2
+ setAttributes({ [attributeName]: value });
3
+ };
4
+