@wordpress/block-library
Version:
Block library for the WordPress editor.
354 lines (345 loc) • 12.5 kB
JavaScript
/**
* External dependencies
*/
import { View, AccessibilityInfo } from 'react-native';
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { RichText, PlainText, useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl, ToggleControl, Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { search } from '@wordpress/icons';
import { useRef, useEffect, useState } from '@wordpress/element';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
/**
* Internal dependencies
*/
import styles from './style.scss';
/**
* Constants
*/
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const MIN_BUTTON_WIDTH = 75;
const MARGINS = styles.widthMargin?.marginLeft + styles.widthMargin?.paddingLeft;
const BUTTON_OPTIONS = [{
value: 'button-inside',
label: __('Button inside')
}, {
value: 'button-outside',
label: __('Button outside')
}, {
value: 'no-button',
label: __('No button')
}];
function useIsScreenReaderEnabled() {
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false);
useEffect(() => {
let mounted = true;
const changeListener = AccessibilityInfo.addEventListener('screenReaderChanged', enabled => setIsScreenReaderEnabled(enabled));
AccessibilityInfo.isScreenReaderEnabled().then(screenReaderEnabled => {
if (mounted && screenReaderEnabled) {
setIsScreenReaderEnabled(screenReaderEnabled);
}
});
return () => {
mounted = false;
changeListener.remove();
};
}, []);
return isScreenReaderEnabled;
}
export default function SearchEdit({
onFocus,
isSelected,
attributes,
setAttributes,
className,
blockWidth,
style
}) {
const [isButtonSelected, setIsButtonSelected] = useState(false);
const [isLabelSelected, setIsLabelSelected] = useState(false);
const [isPlaceholderSelected, setIsPlaceholderSelected] = useState(false);
const [isLongButton, setIsLongButton] = useState(false);
const [buttonWidth, setButtonWidth] = useState(MIN_BUTTON_WIDTH);
const isScreenReaderEnabled = useIsScreenReaderEnabled();
const textInputRef = useRef(null);
const {
label,
showLabel,
buttonPosition,
buttonUseIcon,
placeholder,
buttonText
} = attributes;
/*
* Called when the value of isSelected changes. Blurs the PlainText component
* used by the placeholder when this block loses focus.
*/
useEffect(() => {
if (hasTextInput() && isPlaceholderSelected && !isSelected) {
textInputRef.current.blur();
}
}, [isSelected]);
useEffect(() => {
const maxButtonWidth = Math.floor(blockWidth / 2 - MARGINS);
const tempIsLongButton = buttonWidth > maxButtonWidth;
// Update this value only if it has changed to avoid flickering.
if (isLongButton !== tempIsLongButton) {
setIsLongButton(tempIsLongButton);
}
}, [blockWidth, buttonWidth]);
const hasTextInput = () => {
return textInputRef && textInputRef.current;
};
const onLayoutButton = ({
nativeEvent
}) => {
const {
width
} = nativeEvent?.layout;
if (width) {
setButtonWidth(width);
}
};
const getBlockClassNames = () => {
return clsx(className, 'button-inside' === buttonPosition ? 'wp-block-search__button-inside' : undefined, 'button-outside' === buttonPosition ? 'wp-block-search__button-outside' : undefined, 'no-button' === buttonPosition ? 'wp-block-search__no-button' : undefined, 'button-only' === buttonPosition ? 'wp-block-search__button-only' : undefined, !buttonUseIcon && 'no-button' !== buttonPosition ? 'wp-block-search__text-button' : undefined, buttonUseIcon && 'no-button' !== buttonPosition ? 'wp-block-search__icon-button' : undefined);
};
const getSelectedButtonPositionLabel = option => {
switch (option) {
case 'button-inside':
return __('Inside');
case 'button-outside':
return __('Outside');
case 'no-button':
return __('No button');
}
};
const blockProps = useBlockProps({
className: getBlockClassNames()
});
const controls = /*#__PURE__*/_jsx(InspectorControls, {
children: /*#__PURE__*/_jsxs(PanelBody, {
title: __('Search settings'),
children: [/*#__PURE__*/_jsx(ToggleControl, {
label: __('Hide search heading'),
checked: !showLabel,
onChange: () => {
setAttributes({
showLabel: !showLabel
});
}
}), /*#__PURE__*/_jsx(SelectControl, {
label: __('Button position'),
value: getSelectedButtonPositionLabel(buttonPosition),
onChange: position => {
setAttributes({
buttonPosition: position
});
},
options: BUTTON_OPTIONS,
hideCancelButton: true
}), buttonPosition !== 'no-button' && /*#__PURE__*/_jsx(ToggleControl, {
label: __('Use icon button'),
checked: buttonUseIcon,
onChange: () => {
setAttributes({
buttonUseIcon: !buttonUseIcon
});
}
})]
})
});
const isButtonInside = buttonPosition === 'button-inside';
const borderStyle = usePreferredColorSchemeStyle(styles.border, styles.borderDark);
const inputStyle = [!isButtonInside && borderStyle, usePreferredColorSchemeStyle(styles.plainTextInput, styles.plainTextInputDark), style?.baseColors?.color && {
color: style?.baseColors?.color?.text
}];
const placeholderStyle = {
...usePreferredColorSchemeStyle(styles.plainTextPlaceholder, styles.plainTextPlaceholderDark),
...(style?.baseColors?.color && {
color: style?.baseColors?.color?.text
})
};
const searchBarStyle = [styles.searchBarContainer, isButtonInside && borderStyle, isLongButton && {
flexDirection: 'column'
}];
/**
* If a screenreader is enabled, create a descriptive label for this field. If
* not, return a label that is used during automated UI tests.
*
* @return {string} The accessibilityLabel for the Search Button
*/
const getAccessibilityLabelForButton = () => {
if (!isScreenReaderEnabled) {
return 'search-block-button';
}
return `${__('Search button. Current button text is')} ${buttonText}`;
};
/**
* If a screenreader is enabled, create a descriptive label for this field. If
* not, return a label that is used during automated UI tests.
*
* @return {string} The accessibilityLabel for the Search Input
* placeholder field.
*/
const getAccessibilityLabelForPlaceholder = () => {
if (!isScreenReaderEnabled) {
return 'search-block-input';
}
const title = __('Search input field.');
const description = placeholder ? `${__('Current placeholder text is')} ${placeholder}` : __('No custom placeholder set');
return `${title} ${description}`;
};
/**
* If a screenreader is enabled, create a descriptive label for this field. If
* not, return a label that is used during automated UI tests.
*
* @return {string} The accessibilityLabel for the Search Label field
*/
const getAccessibilityLabelForLabel = () => {
if (!isScreenReaderEnabled) {
return 'search-block-label';
}
return `${__('Search block label. Current text is')} ${label}`;
};
const renderTextField = () => {
return /*#__PURE__*/_jsx(View, {
style: styles.searchInputContainer,
accessible: true,
accessibilityRole: "none",
accessibilityHint: isScreenReaderEnabled ? __('Double tap to edit placeholder text') : undefined,
accessibilityLabel: getAccessibilityLabelForPlaceholder(),
children: /*#__PURE__*/_jsx(PlainText, {
ref: textInputRef,
isSelected: isPlaceholderSelected,
className: "wp-block-search__input",
style: inputStyle,
numberOfLines: 1,
ellipsizeMode: "tail" // Currently only works on ios.
,
label: null,
value: placeholder,
placeholder: placeholder ? undefined : __('Optional placeholder…'),
onChange: newVal => setAttributes({
placeholder: newVal
}),
onFocus: () => {
setIsPlaceholderSelected(true);
onFocus();
},
onBlur: () => setIsPlaceholderSelected(false),
placeholderTextColor: placeholderStyle?.color
})
});
};
// To achieve proper expanding and shrinking `RichText` on Android, there is a need to set
// a `placeholder` as an empty string when `RichText` is focused,
// because `AztecView` is calculating a `minWidth` based on placeholder text.
const buttonPlaceholderText = isButtonSelected || !isButtonSelected && buttonText && buttonText !== '' ? '' : __('Add button text');
const baseButtonStyles = {
...style?.baseColors?.blocks?.['core/button']?.color,
...attributes?.style?.color,
...(style?.color && {
text: style.color
})
};
const richTextButtonContainerStyle = [styles.buttonContainer, isLongButton && styles.buttonContainerWide, baseButtonStyles?.background && {
backgroundColor: baseButtonStyles.background,
borderWidth: 0
}, style?.backgroundColor && {
backgroundColor: style.backgroundColor,
borderWidth: 0
}];
const richTextButtonStyle = {
...styles.richTextButton,
...(baseButtonStyles?.text && {
color: baseButtonStyles.text,
placeholderColor: baseButtonStyles.text
})
};
const iconStyles = {
...styles.icon,
...(baseButtonStyles?.text && {
fill: baseButtonStyles.text
})
};
const renderButton = () => {
return /*#__PURE__*/_jsxs(View, {
style: richTextButtonContainerStyle,
children: [buttonUseIcon && /*#__PURE__*/_jsx(Icon, {
icon: search,
...iconStyles,
onLayout: onLayoutButton
}), !buttonUseIcon && /*#__PURE__*/_jsx(View, {
accessible: true,
accessibilityRole: "none",
accessibilityHint: isScreenReaderEnabled ? __('Double tap to edit button text') : undefined,
accessibilityLabel: getAccessibilityLabelForButton(),
onLayout: onLayoutButton,
children: /*#__PURE__*/_jsx(RichText, {
className: "wp-block-search__button",
identifier: "buttonText",
tagName: "p",
style: richTextButtonStyle,
placeholder: buttonPlaceholderText,
value: buttonText,
withoutInteractiveFormatting: true,
onChange: html => setAttributes({
buttonText: html
}),
minWidth: MIN_BUTTON_WIDTH,
maxWidth: blockWidth - MARGINS,
textAlign: "center",
isSelected: isButtonSelected,
__unstableMobileNoFocusOnMount: !isSelected,
unstableOnFocus: () => {
setIsButtonSelected(true);
},
onBlur: () => {
setIsButtonSelected(false);
},
selectionColor: styles.richTextButtonCursor?.color
})
})]
});
};
return /*#__PURE__*/_jsxs(View, {
...blockProps,
style: styles.searchBlockContainer,
importantForAccessibility: isSelected ? 'yes' : 'no-hide-descendants',
accessibilityElementsHidden: isSelected ? false : true,
children: [isSelected && controls, showLabel && /*#__PURE__*/_jsx(View, {
accessible: true,
accessibilityRole: "none",
accessibilityHint: isScreenReaderEnabled ? __('Double tap to edit label text') : undefined,
accessibilityLabel: getAccessibilityLabelForLabel(),
children: /*#__PURE__*/_jsx(RichText, {
className: "wp-block-search__label",
identifier: "label",
tagName: "p",
style: styles.richTextLabel,
placeholder: __('Add label…'),
withoutInteractiveFormatting: true,
value: label,
onChange: html => setAttributes({
label: html
}),
isSelected: isLabelSelected,
__unstableMobileNoFocusOnMount: !isSelected,
unstableOnFocus: () => {
setIsLabelSelected(true);
},
onBlur: () => {
setIsLabelSelected(false);
},
selectionColor: styles.richTextButtonCursor?.color
})
}), ('button-inside' === buttonPosition || 'button-outside' === buttonPosition) && /*#__PURE__*/_jsxs(View, {
style: searchBarStyle,
children: [renderTextField(), renderButton()]
}), 'button-only' === buttonPosition && renderButton(), 'no-button' === buttonPosition && renderTextField()]
});
}
//# sourceMappingURL=edit.native.js.map