import { createElement, Fragment, Component, useState, useRef, useEffect, RawHTML, } from '@wordpress/element' import { BaseControl } from '@wordpress/components' import classnames from 'classnames' import ResponsiveControls, { maybePromoteScalarValueIntoResponsive, isOptionEnabledFor, getValueForDevice, isOptionResponsiveFor, } from '../customizer/components/responsive-controls' import deepEqual from 'deep-equal' import { normalizeCondition, matchValuesWithCondition } from 'match-conditions' import { __ } from 'ct-i18n' import { getOptionLabelFor } from './helpers/get-label' import ctEvents from 'ct-events' import { getCurrentDevice } from '../customizer/components/useDeviceManager' import { mutateResponsiveValueWithScalar } from './helpers/mutate-responsive-value' const CORE_OPTIONS_CONTEXT = require.context('./options/', false, /\.js$/) CORE_OPTIONS_CONTEXT.keys().forEach(CORE_OPTIONS_CONTEXT) const hasCoreOptionModifier = (type) => { let index = CORE_OPTIONS_CONTEXT.keys() .map((module) => module.replace(/^\.\//, '').replace(/\.js$/, '')) .indexOf(type) return index > -1 && CORE_OPTIONS_CONTEXT.keys()[index] } export const capitalizeFirstLetter = (str) => { str = str == null ? '' : String(str) return str.charAt(0).toUpperCase() + str.slice(1) } const DefaultOptionComponent = ({ option }) => { return
Unimplemented option: {option.type}
} export const getOptionFor = (option) => { const dynamicOptionTypes = {} ctEvents.trigger('blocksy:options:register', dynamicOptionTypes) if (hasCoreOptionModifier(option.type)) { return CORE_OPTIONS_CONTEXT(hasCoreOptionModifier(option.type)).default } if (dynamicOptionTypes[option.type]) { return dynamicOptionTypes[option.type] } return DefaultOptionComponent } export const optionWithDefault = ({ option, value }) => value === undefined ? option.value : value const GenericOptionType = ({ option, value, values, onChange, onChangeFor, hasRevertButton, id, purpose, }) => { const [liftedOptionState, setLiftedOptionState] = useState(null) let maybeGutenbergDevice = null const childComponentRef = useRef(null) if (wp.data && wp.data.useSelect) { maybeGutenbergDevice = wp.data.useSelect((select) => { if (!select('core/edit-post')) { return null } return getCurrentDevice(select) }) } const getInitialDevice = () => { return getCurrentDevice() } const [device, setInnerDevice] = useState(getInitialDevice()) const listener = () => { setInnerDevice(getInitialDevice()) } const ctEventsListener = ({ device }) => { setInnerDevice(device) } const setDevice = (device) => { ctEvents.trigger('ct:options:device:update', { device }) setInnerDevice(device) if (wp.customize && wp.customize.previewedDevice) { wp.customize.previewedDevice.set(device) } if ( wp.data && wp.data.dispatch && wp.data.dispatch('core/edit-post') && wp.data.dispatch('core/edit-post') .__experimentalSetPreviewDeviceType ) { wp.data .dispatch('core/edit-post') .__experimentalSetPreviewDeviceType( device.replace(/\w/, (c) => c.toUpperCase()) ) } } useEffect(() => { if (maybeGutenbergDevice) { setInnerDevice(maybeGutenbergDevice.toLowerCase()) } }, [maybeGutenbergDevice]) useEffect(() => { if (option.type !== 'ct-typography') { if (!isOptionResponsiveFor(option) && !option.markAsAutoFor) { return } } if (wp.customize && wp.customize.previewedDevice) { setTimeout(() => wp.customize.previewedDevice.bind(listener), 1000) } ctEvents.on('ct:options:device:update', ctEventsListener) setInnerDevice(getInitialDevice()) return () => { if (option.type !== 'ct-typography') { if (!isOptionResponsiveFor(option)) { return } } if (wp.customize && wp.customize.previewedDevice) { wp.customize.previewedDevice.unbind(listener) } ctEvents.off('ct:options:device:update', ctEventsListener) } }, []) let OptionComponent = getOptionFor(option) let BeforeOptionContent = { content: null, option } ctEvents.trigger('blocksy:options:before-option', BeforeOptionContent) const globalResponsiveValue = maybePromoteScalarValueIntoResponsive( optionWithDefault({ value, option }), isOptionResponsiveFor(option) ) const valueWithResponsive = isOptionResponsiveFor(option, { ignoreHidden: true, }) ? getValueForDevice({ option, value: globalResponsiveValue, device }) : globalResponsiveValue const onChangeWithMobileBridge = (value) => { if (option.triggerRefreshOnChange) { wp.customize && wp.customize.previewer && wp.customize.previewer.refresh() } if ( option.switchDeviceOnChange && wp.customize && wp.customize.previewedDevice() !== option.switchDeviceOnChange ) { wp.customize.previewedDevice.set(option.switchDeviceOnChange) } if ( option.sync && (Object.keys(option.sync).length > 0 || Array.isArray(option.sync)) && wp.customize && wp.customize.previewer ) { let ids = ( Array.isArray(option.sync) ? option.sync : [option.sync] ).map((sync) => sync.id || option.id) if (ids.length > 1) { const maybeMainSync = ( Array.isArray(option.sync) ? option.sync : [option.sync] ).find(({ id }) => (id || option.id) === option.id) if (maybeMainSync) { ids = [option.id] } } wp.customize.previewer.send('ct:sync:refresh_partial', { id: ids, option, shouldSkip: !!option.sync.shouldSkip, }) } onChange(value) } const onChangeWithResponsiveBridge = (scalarValue) => { const responsiveValue = maybePromoteScalarValueIntoResponsive( optionWithDefault({ value, option }), isOptionResponsiveFor(option) ) let newValue = scalarValue if (isOptionResponsiveFor(option, { ignoreHidden: true })) { const isTabletSkipping = device === 'tablet' && isOptionEnabledFor('tablet', option.responsive) === 'skip' newValue = mutateResponsiveValueWithScalar({ scalarValue, responsiveValue, device, ...(isTabletSkipping ? { devices: ['desktop', 'mobile'], } : {}), }) } onChangeWithMobileBridge(newValue) } /** * Handle transparent components */ if (!OptionComponent) { return
Unimplemented option: {option.type}
} let renderingConfig = { design: true, label: true, wrapperAttr: {} } let LabelToolbar = () => null let OptionMetaWrapper = null let ControlEnd = () => null let sectionClassName = () => ({}) renderingConfig = { ...renderingConfig, ...(OptionComponent.renderingConfig || {}), } if (option.design) { renderingConfig.design = option.design } if (typeof renderingConfig.design === 'function') { renderingConfig.design = renderingConfig.design({ option, value: valueWithResponsive, }) } if (OptionComponent.LabelToolbar) { LabelToolbar = OptionComponent.LabelToolbar } if (OptionComponent.ControlEnd) { ControlEnd = OptionComponent.ControlEnd } if (OptionComponent.MetaWrapper) { OptionMetaWrapper = OptionComponent.MetaWrapper } if (OptionComponent.sectionClassName) { sectionClassName = OptionComponent.sectionClassName } let supportedPurposes = ['default'] if (OptionComponent.supportedPurposes) { supportedPurposes = OptionComponent.supportedPurposes } let actualPurpose = purpose if (option.purpose && supportedPurposes.indexOf(option.purpose) !== -1) { actualPurpose = option.purpose } let maybeLabel = getOptionLabelFor({ id, option, values, renderingConfig, }) let OptionComponentWithoutDesign = ( {BeforeOptionContent && BeforeOptionContent.content} { if (c) { childComponentRef.current = c } }, } : {}), liftedOptionStateDescriptor: { liftedOptionState, setLiftedOptionState, }, option: { ...option, value: isOptionResponsiveFor(option, { ignoreHidden: true, }) ? getValueForDevice({ device, option, value: maybePromoteScalarValueIntoResponsive( option.value || '' ), }) : maybePromoteScalarValueIntoResponsive( option.value || '', isOptionResponsiveFor(option) ), }, value: valueWithResponsive, id, values, onChangeFor, device, onChange: onChangeWithResponsiveBridge, purpose: actualPurpose, maybeLabel, }} /> ) if (!renderingConfig.design || renderingConfig.design === 'none') { return OptionComponentWithoutDesign } const RevertButton = () => { let computeOptionValue = renderingConfig.computeOptionValue if (!computeOptionValue) { computeOptionValue = (o, { option, values }) => o } if (option.type === 'ct-panel' && !option.switch) { return null } return ( ((option.type !== 'ct-image-picker' && option.type !== 'ct-layers' && hasRevertButton && !option.disableRevertButton) || option.forcedRevertButton) && ( ) ) } let maybeDesc = Object.keys(option).indexOf('desc') === -1 ? false : option.desc let maybeLink = Object.keys(option).indexOf('link') === -1 ? false : option.link || ' ' const actualDesignType = typeof renderingConfig.design === 'boolean' ? 'block' : renderingConfig.design if (purpose === 'taxonomy') { return ( {maybeLabel && ( )} {OptionComponentWithoutDesign} {maybeDesc &&

{maybeDesc}

} ) } if (purpose === 'gutenberg') { if ( !supportedPurposes.includes('gutenberg') || actualPurpose !== 'gutenberg' ) { return ( {maybeDesc} : ''}> {OptionComponentWithoutDesign} ) } return ( {OptionComponentWithoutDesign} ) } if (renderingConfig.design === 'compact') { return (
{maybeLabel && } {((isOptionResponsiveFor(option) && isOptionEnabledFor(device, option.responsive)) || !isOptionResponsiveFor(option)) && OptionComponentWithoutDesign} {maybeLink && ( )}
) } const getActualOption = ({ wrapperAttr: { className, ...additionalWrapperAttr } = {}, ...props } = {}) => { const { className: optionClassName, ...optionAdditionalWrapperAttr } = option.wrapperAttr || {} const { class: sectionClassNameStr, ...sectionAttr } = option.sectionAttr || {} return (
{maybeLabel && } {isOptionResponsiveFor(option, { ignoreHidden: true, }) && actualDesignType.indexOf('block') > -1 && !option.skipResponsiveControls && ( )}
{isOptionResponsiveFor(option) && !isOptionEnabledFor(device, option.responsive) && (
{option.disabledDeviceMessage || __( "Option can't be edited for current device", 'blocksy' )}
)} {((isOptionResponsiveFor(option) && isOptionEnabledFor(device, option.responsive)) || !isOptionResponsiveFor(option)) && (
{isOptionResponsiveFor(option, { ignoreHidden: true, }) && actualDesignType === 'inline' && ( )} {OptionComponentWithoutDesign} {maybeLink && ( )}
{maybeDesc && (
)} )}
) } return OptionMetaWrapper ? ( ) : ( getActualOption() ) } export default GenericOptionType