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