@secretstache/wordpress-gutenberg 0.2.5 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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} +44 -10
  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/MediaPicker.js +179 -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 +30 -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,46 @@
1
+ import { Notice, Placeholder, Spinner } from '@wordpress/components';
2
+
3
+ const EmptyNotice = ({
4
+ message = 'No resources were found matching your criteria. Please try to adjust the query.'
5
+ }) => (
6
+ <Notice status="info" isDismissible={false}>
7
+ <p>{message}</p>
8
+ </Notice>
9
+ );
10
+
11
+ const LoadingSpinner = () => <Spinner className="bc-spinner" />;
12
+
13
+ const PlaceholderContent = ({
14
+ icon = 'info-outline',
15
+ instructions = 'Please configure the block in the sidebar.',
16
+ ...restProps
17
+ }) => (
18
+ <Placeholder
19
+ icon={icon}
20
+ instructions={instructions}
21
+ {...restProps}
22
+ />
23
+ );
24
+
25
+ export const ResourcesWrapper = ({
26
+ isLoading,
27
+ isEmpty,
28
+ isPlaceholder,
29
+ emptyMessage,
30
+ placeholderProps = {},
31
+ children,
32
+ }) => {
33
+ if (isLoading) {
34
+ return <LoadingSpinner />;
35
+ }
36
+
37
+ if (isEmpty) {
38
+ return <EmptyNotice message={emptyMessage} />;
39
+ }
40
+
41
+ if (isPlaceholder) {
42
+ return <PlaceholderContent {...placeholderProps} />;
43
+ }
44
+
45
+ return children;
46
+ };
@@ -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 { BCImageRenderer, BCVideoRenderer, BCAnimationRenderer, BCMediaPicker } from './MediaPicker';
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
+ };