UNPKG

@wordpress/block-library

Version:
449 lines (442 loc) 16.8 kB
/** * External dependencies */ import clsx from 'clsx'; /** * WordPress dependencies */ import { useBlockProps, InspectorControls, RichText, __experimentalUseBorderProps as useBorderProps, __experimentalUseColorProps as useColorProps, getTypographyClassesAndStyles as useTypographyProps, store as blockEditorStore, __experimentalGetElementClassName, useSettings } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; import { SelectControl, ToggleControl, ResizableBox, __experimentalUseCustomUnits as useCustomUnits, __experimentalUnitControl as UnitControl, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalVStack as VStack } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { Icon, search } from '@wordpress/icons'; import { __, sprintf } from '@wordpress/i18n'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies */ import { PC_WIDTH_DEFAULT, PX_WIDTH_DEFAULT, MIN_WIDTH, isPercentageUnit } from './utils.js'; import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; // Used to calculate border radius adjustment to avoid "fat" corners when // button is placed inside wrapper. import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const DEFAULT_INNER_PADDING = '4px'; const PERCENTAGE_WIDTHS = [25, 50, 75, 100]; export default function SearchEdit({ className, attributes, setAttributes, toggleSelection, isSelected, clientId }) { const { label, showLabel, placeholder, width, widthUnit, align, buttonText, buttonPosition, buttonUseIcon, isSearchFieldHidden, style } = attributes; const wasJustInsertedIntoNavigationBlock = useSelect(select => { const { getBlockParentsByBlockName, wasBlockJustInserted } = select(blockEditorStore); return !!getBlockParentsByBlockName(clientId, 'core/navigation')?.length && wasBlockJustInserted(clientId); }, [clientId]); const { __unstableMarkNextChangeAsNotPersistent } = useDispatch(blockEditorStore); useEffect(() => { if (wasJustInsertedIntoNavigationBlock) { // This side-effect should not create an undo level. __unstableMarkNextChangeAsNotPersistent(); setAttributes({ showLabel: false, buttonUseIcon: true, buttonPosition: 'button-inside' }); } }, [__unstableMarkNextChangeAsNotPersistent, wasJustInsertedIntoNavigationBlock, setAttributes]); const borderRadius = style?.border?.radius; let borderProps = useBorderProps(attributes); // Check for old deprecated numerical border radius. Done as a separate // check so that a borderRadius style won't overwrite the longhand // per-corner styles. if (typeof borderRadius === 'number') { borderProps = { ...borderProps, style: { ...borderProps.style, borderRadius: `${borderRadius}px` } }; } const colorProps = useColorProps(attributes); const [fluidTypographySettings, layout] = useSettings('typography.fluid', 'layout'); const typographyProps = useTypographyProps(attributes, { typography: { fluid: fluidTypographySettings }, layout: { wideSize: layout?.wideSize } }); const unitControlInstanceId = useInstanceId(UnitControl); const unitControlInputId = `wp-block-search__width-${unitControlInstanceId}`; const isButtonPositionInside = 'button-inside' === buttonPosition; const isButtonPositionOutside = 'button-outside' === buttonPosition; const hasNoButton = 'no-button' === buttonPosition; const hasOnlyButton = 'button-only' === buttonPosition; const searchFieldRef = useRef(); const buttonRef = useRef(); const units = useCustomUnits({ availableUnits: ['%', 'px'], defaultValues: { '%': PC_WIDTH_DEFAULT, px: PX_WIDTH_DEFAULT } }); useEffect(() => { if (hasOnlyButton && !isSelected) { setAttributes({ isSearchFieldHidden: true }); } }, [hasOnlyButton, isSelected, setAttributes]); // Show the search field when width changes. useEffect(() => { if (!hasOnlyButton || !isSelected) { return; } setAttributes({ isSearchFieldHidden: false }); }, [hasOnlyButton, isSelected, setAttributes, width]); const getBlockClassNames = () => { return clsx(className, isButtonPositionInside ? 'wp-block-search__button-inside' : undefined, isButtonPositionOutside ? 'wp-block-search__button-outside' : undefined, hasNoButton ? 'wp-block-search__no-button' : undefined, hasOnlyButton ? 'wp-block-search__button-only' : undefined, !buttonUseIcon && !hasNoButton ? 'wp-block-search__text-button' : undefined, buttonUseIcon && !hasNoButton ? 'wp-block-search__icon-button' : undefined, hasOnlyButton && isSearchFieldHidden ? 'wp-block-search__searchfield-hidden' : undefined); }; const buttonPositionControls = [{ label: __('Button outside'), value: 'button-outside' }, { label: __('Button inside'), value: 'button-inside' }, { label: __('No button'), value: 'no-button' }, { label: __('Button only'), value: 'button-only' }]; const getResizableSides = () => { if (hasOnlyButton) { return {}; } return { right: align !== 'right', left: align === 'right' }; }; const renderTextField = () => { // If the input is inside the wrapper, the wrapper gets the border color styles/classes, not the input control. const textFieldClasses = clsx('wp-block-search__input', isButtonPositionInside ? undefined : borderProps.className, typographyProps.className); const textFieldStyles = { ...(isButtonPositionInside ? { borderRadius } : borderProps.style), ...typographyProps.style, textDecoration: undefined }; return /*#__PURE__*/_jsx("input", { type: "search", className: textFieldClasses, style: textFieldStyles, "aria-label": __('Optional placeholder text') // We hide the placeholder field's placeholder when there is a value. This // stops screen readers from reading the placeholder field's placeholder // which is confusing. , placeholder: placeholder ? undefined : __('Optional placeholder…'), value: placeholder, onChange: event => setAttributes({ placeholder: event.target.value }), ref: searchFieldRef }); }; const renderButton = () => { // If the button is inside the wrapper, the wrapper gets the border color styles/classes, not the button. const buttonClasses = clsx('wp-block-search__button', colorProps.className, typographyProps.className, isButtonPositionInside ? undefined : borderProps.className, buttonUseIcon ? 'has-icon' : undefined, __experimentalGetElementClassName('button')); const buttonStyles = { ...colorProps.style, ...typographyProps.style, ...(isButtonPositionInside ? { borderRadius } : borderProps.style) }; const handleButtonClick = () => { if (hasOnlyButton) { setAttributes({ isSearchFieldHidden: !isSearchFieldHidden }); } }; return /*#__PURE__*/_jsxs(_Fragment, { children: [buttonUseIcon && /*#__PURE__*/_jsx("button", { type: "button", className: buttonClasses, style: buttonStyles, "aria-label": buttonText ? stripHTML(buttonText) : __('Search'), onClick: handleButtonClick, ref: buttonRef, children: /*#__PURE__*/_jsx(Icon, { icon: search }) }), !buttonUseIcon && /*#__PURE__*/_jsx(RichText, { identifier: "buttonText", className: buttonClasses, style: buttonStyles, "aria-label": __('Button text'), placeholder: __('Add button text…'), withoutInteractiveFormatting: true, value: buttonText, onChange: html => setAttributes({ buttonText: html }), onClick: handleButtonClick })] }); }; const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const controls = /*#__PURE__*/_jsx(_Fragment, { children: /*#__PURE__*/_jsx(InspectorControls, { children: /*#__PURE__*/_jsxs(ToolsPanel, { label: __('Settings'), resetAll: () => { setAttributes({ width: undefined, widthUnit: undefined, showLabel: true, buttonUseIcon: false, buttonPosition: 'button-outside', isSearchFieldHidden: false }); }, dropdownMenuProps: dropdownMenuProps, children: [/*#__PURE__*/_jsx(ToolsPanelItem, { hasValue: () => !showLabel, label: __('Show label'), onDeselect: () => { setAttributes({ showLabel: true }); }, isShownByDefault: true, children: /*#__PURE__*/_jsx(ToggleControl, { __nextHasNoMarginBottom: true, checked: showLabel, label: __('Show label'), onChange: value => setAttributes({ showLabel: value }) }) }), /*#__PURE__*/_jsx(ToolsPanelItem, { hasValue: () => buttonPosition !== 'button-outside', label: __('Button position'), onDeselect: () => { setAttributes({ buttonPosition: 'button-outside', isSearchFieldHidden: false }); }, isShownByDefault: true, children: /*#__PURE__*/_jsx(SelectControl, { value: buttonPosition, __next40pxDefaultSize: true, __nextHasNoMarginBottom: true, label: __('Button position'), onChange: value => { setAttributes({ buttonPosition: value, isSearchFieldHidden: value === 'button-only' }); }, options: buttonPositionControls }) }), buttonPosition !== 'no-button' && /*#__PURE__*/_jsx(ToolsPanelItem, { hasValue: () => !!buttonUseIcon, label: __('Use button with icon'), onDeselect: () => { setAttributes({ buttonUseIcon: false }); }, isShownByDefault: true, children: /*#__PURE__*/_jsx(ToggleControl, { __nextHasNoMarginBottom: true, checked: buttonUseIcon, label: __('Use button with icon'), onChange: value => setAttributes({ buttonUseIcon: value }) }) }), /*#__PURE__*/_jsx(ToolsPanelItem, { hasValue: () => !!width, label: __('Width'), onDeselect: () => { setAttributes({ width: undefined, widthUnit: undefined }); }, isShownByDefault: true, children: /*#__PURE__*/_jsxs(VStack, { children: [/*#__PURE__*/_jsx(UnitControl, { __next40pxDefaultSize: true, label: __('Width'), id: unitControlInputId // Unused, kept for backwards compatibility , min: isPercentageUnit(widthUnit) ? 0 : MIN_WIDTH, max: isPercentageUnit(widthUnit) ? 100 : undefined, step: 1, onChange: newWidth => { const parsedNewWidth = newWidth === '' ? undefined : parseInt(newWidth, 10); setAttributes({ width: parsedNewWidth }); }, onUnitChange: newUnit => { setAttributes({ width: '%' === newUnit ? PC_WIDTH_DEFAULT : PX_WIDTH_DEFAULT, widthUnit: newUnit }); }, __unstableInputWidth: "80px", value: `${width}${widthUnit}`, units: units }), /*#__PURE__*/_jsx(ToggleGroupControl, { label: __('Percentage Width'), value: PERCENTAGE_WIDTHS.includes(width) && widthUnit === '%' ? width : undefined, hideLabelFromVision: true, onChange: newWidth => { setAttributes({ width: newWidth, widthUnit: '%' }); }, isBlock: true, __next40pxDefaultSize: true, __nextHasNoMarginBottom: true, children: PERCENTAGE_WIDTHS.map(widthValue => { return /*#__PURE__*/_jsx(ToggleGroupControlOption, { value: widthValue, label: sprintf(/* translators: Percentage value. */ __('%d%%'), widthValue) }, widthValue); }) })] }) })] }) }) }); const padBorderRadius = radius => radius ? `calc(${radius} + ${DEFAULT_INNER_PADDING})` : undefined; const getWrapperStyles = () => { const styles = isButtonPositionInside ? borderProps.style : { borderRadius: borderProps.style?.borderRadius, borderTopLeftRadius: borderProps.style?.borderTopLeftRadius, borderTopRightRadius: borderProps.style?.borderTopRightRadius, borderBottomLeftRadius: borderProps.style?.borderBottomLeftRadius, borderBottomRightRadius: borderProps.style?.borderBottomRightRadius }; const isNonZeroBorderRadius = borderRadius !== undefined && parseInt(borderRadius, 10) !== 0; if (isButtonPositionInside && isNonZeroBorderRadius) { // We have button inside wrapper and a border radius value to apply. // Add default padding so we don't get "fat" corners. // // CSS calc() is used here to support non-pixel units. The inline // style using calc() will only apply if both values have units. if (typeof borderRadius === 'object') { // Individual corner border radii present. const { topLeft, topRight, bottomLeft, bottomRight } = borderRadius; return { ...styles, borderTopLeftRadius: padBorderRadius(topLeft), borderTopRightRadius: padBorderRadius(topRight), borderBottomLeftRadius: padBorderRadius(bottomLeft), borderBottomRightRadius: padBorderRadius(bottomRight) }; } // The inline style using calc() will only apply if both values // supplied to calc() have units. Deprecated block's may have // unitless integer. const radius = Number.isInteger(borderRadius) ? `${borderRadius}px` : borderRadius; styles.borderRadius = `calc(${radius} + ${DEFAULT_INNER_PADDING})`; } return styles; }; const blockProps = useBlockProps({ className: getBlockClassNames(), style: { ...typographyProps.style, // Input opts out of text decoration. textDecoration: undefined } }); const labelClassnames = clsx('wp-block-search__label', typographyProps.className); return /*#__PURE__*/_jsxs("div", { ...blockProps, children: [controls, showLabel && /*#__PURE__*/_jsx(RichText, { identifier: "label", className: labelClassnames, "aria-label": __('Label text'), placeholder: __('Add label…'), withoutInteractiveFormatting: true, value: label, onChange: html => setAttributes({ label: html }), style: typographyProps.style }), /*#__PURE__*/_jsxs(ResizableBox, { size: { width: width === undefined ? 'auto' : `${width}${widthUnit}`, height: 'auto' }, className: clsx('wp-block-search__inside-wrapper', isButtonPositionInside ? borderProps.className : undefined), style: getWrapperStyles(), minWidth: MIN_WIDTH, enable: getResizableSides(), onResizeStart: (event, direction, elt) => { setAttributes({ width: parseInt(elt.offsetWidth, 10), widthUnit: 'px' }); toggleSelection(false); }, onResizeStop: (event, direction, elt, delta) => { setAttributes({ width: parseInt(width + delta.width, 10) }); toggleSelection(true); }, showHandle: isSelected, children: [(isButtonPositionInside || isButtonPositionOutside || hasOnlyButton) && /*#__PURE__*/_jsxs(_Fragment, { children: [renderTextField(), renderButton()] }), hasNoButton && renderTextField()] })] }); } //# sourceMappingURL=edit.js.map