import React, { useCallback, useEffect } from 'react'; import { createRoot } from 'react-dom/client'; import './scss/style.scss'; import ApuraRangeControl from './components/ApuraRangeControl'; import ApuraColor from './components/ApuraColor'; import ApuraColors from './components/ApuraColors'; import ApuraSortable from './components/ApuraSortable'; import ApuraButtonGroup from './components/ApuraButtonGroup'; import ApuraToggle from './components/ApuraToggle'; import ApuraImageButtonGroup from './components/ApuraImageButtonGroup'; import ApuraPaletteOverrides from './components/ApuraPaletteOverrides'; import ApuraCustomPalette from './components/ApuraCustomPalette'; import ApuraTypography from './components/ApuraTypography'; import GoogleFontSettings from './components/GoogleFontSettings'; import ApuraText from './components/ApuraText'; import ApuraTabs from './components/ApuraTabs'; import { parseCssValue, createDeviceValue } from './components/utils'; import ApuraSectionNavigation from './components/ApuraSectionNavigation'; import { __, sprintf, _x } from '@wordpress/i18n'; // React Rootsを管理するためのWeakMap const rootsMap = new WeakMap(); function renderControl(controls: any, type: string, renderCallback: (control: any) => JSX.Element) { const ids = Object.keys(controls).filter(key => controls[key].type === type); ids.forEach(id => { const control = wp.customize.control(id); const field = document.querySelector(control.selector); if (!field) return; // 既存のRootが存在するかチェック let root = rootsMap.get(field); if (!root) { root = createRoot(field); rootsMap.set(field, root); } root.render(renderCallback(control)); }); } function getDevices(control: any): devices { const devices : devices = []; if (control.params.settings.hasOwnProperty('desktop')) { devices.push({ device: 'desktop', label: __('Desktop', 'apura') }); } if (control.params.settings.hasOwnProperty('tablet')) { devices.push({ device: 'tablet', label: __('Tablet', 'apura') }); } if (control.params.settings.hasOwnProperty('mobile')) { devices.push({ device: 'mobile', label: __('Mobile', 'apura') }); } return devices; } function getUnitOptions(sizeUnits: unitData[]) { return (sizeUnits || []).map((unit: unitData) => ({ label: unit?.label ?? unit?.value, value: unit?.value })); } function renderRangeControls(controls: any) { renderControl(controls, 'apura-range-control', (control) => { const params = control.params; const sizeUnits: unitData[] = params?.choices?.sizeUnits; const devices: devices = getDevices(control); const values: RangeControlDeviceValue[] = []; const defaults: RangeControlDeviceValue[] = []; devices.forEach(({ device }) => { let currentValue = control.settings[device].get(); // console.log(currentValue); const parsedValue = currentValue ? parseCssValue(currentValue) : { value: null, unit: sizeUnits[0].value || '-' }; const parsedDefaults = params[device]?.default ? parseCssValue(params[device]?.default) : { value: null, unit: sizeUnits[0].value || '-' }; const { value, default: defaultValue } = createDeviceValue(device, parsedValue, parsedDefaults, sizeUnits); values.push(value); defaults.push(defaultValue); }); return ( { devices.forEach(device => { // console.log(newValues[device.device]); control.settings[device.device].set(newValues[device.device]); }); }} /> ) } ); } function renderPaletteOverridesControls(controls: Controls) { renderControl(controls, 'apura-palette-overrides', (control) => { // console.log(control); return ( ) }); } function renderCustomPaletteControls(controls: Controls) { renderControl(controls, 'apura-custom-palette', (control) => { // console.log(control); return ( ) }); } function renderColorControls(controls: any) { renderControl(controls, 'apura-color', (control) => ( )); } function renderColorsControls(controls: any) { renderControl(controls, 'apura-colors', (control) => ( )); } type ControlParams = { label: string; sort_label: string; description: string; choices: { [key: string]: string }; }; type Control = { type: string; selector: string; setting: { get: () => string[]; set: () => void; }; params: ControlParams; }; type Controls = { [key: string]: Control; }; interface Choice { value: string; label: string; subControls?: string[]; } function renderSortableControls(controls: Controls) { renderControl(controls, 'apura-sortable', (control) => { const { params, setting, selector } = control; // optionの値 const values: string[] = setting.get(); const valueSet = new Set(values); const disabledSort: string[] = control.params.disabledSort || []; const lockedItems = control.params.lockedItems || []; let items: SortItems; if (values.length === 0) { // 初期値が空の場合、choices の順番で設定 items = params.choices.map((item: Choice) => ({ id: item.value, label: item.label, value: item.value, isActive: false, subControls: item?.subControls || null, })); } else { // values にある順で並び替え(順番維持) const orderedItems: SortItems = values .map((id) => { const item: SortableItem = params.choices.find((choice: Choice) => choice.value === id); if (!item) return null; return { id: item.value, label: item.label, value: item.value, isActive: true, subControls: item?.subControls || null, }; }) .filter((item) => item !== null); // null を除外 // values に含まれていない choices を後ろに追加 const remainingItems: SortItems = params.choices .filter((choice: Choice) => !valueSet.has(choice.value)) .map((item: Choice) => ({ id: item.value, label: item.label, value: item.value, isActive: false, subControls: item?.subControls || null, })); items = [...orderedItems, ...remainingItems]; } return ( { setting.set(newValues); }} /> ); }); } type radioButtonsChoices = { [key: string]: string }; function renderButtonGroupControls(controls: Controls) { renderControl(controls, 'apura-button-group', (control) => { // console.log(control); const choices: radioButtonsChoices = control.params.choices; return ( ) }); } type imageButtonsChoices = { [key: string]: { label: string, title: string, image: string } }; function renderImageButtonGroupControls(controls: Controls) { renderControl(controls, 'apura-image-button-group', (control) => { const choices: imageButtonsChoices = control.params.choices; return ( ) }); } function renderToggleControls(controls: Controls) { renderControl(controls, 'apura-toggle', (control) => { return ( ) }); } // このコントロールはサイトに一つだけ。 function renderGoogleFontSettingsControl(controls: Controls) { const id = Object.keys(controls).find(key => controls[key].type === 'apura-google-font-settings'); if (!id) return; const control = wp.customize.control(id); const field = document.querySelector(control.selector); if (!field) return; // 既存のRootが存在するかチェック let root = rootsMap.get(field); if (!root) { root = createRoot(field); rootsMap.set(field, root); } const choices = control.params?.choices; const fonts = Object.keys(choices)?.map(key => ({ label: key, value: key })); const variants = Object.keys(choices)?.map(key => ({ type: choices[key].type, name: key, category: choices[key].category, variants: choices[key].variants, italic: choices[key]?.italic || '', optical_size: choices[key]?.optical_size || '', width: choices[key]?.width || '', })); root.render( { control.setting.set(newValues); }} /> ); } function renderTypographyControls(controls: Controls) { renderControl(controls, 'apura-typography', (control) => { const exclude: string[] = control.params?.choices?.exclude || []; return ( { control.setting.set(newValues); }} /> ) }); } function renderTextControls(controls: Controls) { renderControl(controls, 'apura-text', (control) => { // console.log(control); return ( { control.setting.set(newValue); }} /> ) }); } function renderTabsControls(controls: Controls) { renderControl(controls, 'apura-tabs', (control) => { // console.log(control.params.tabs); return ( ) }); } /** * Section Navigation コントロールのレンダリング */ function renderSectionNavigationControls(controls: Controls) { renderControl(controls, 'apura-section-navigation', (control) => { const navigationConfig = control.params.navigationConfig; const handleNavigate = (sectionId: string) => { navigateToSection(sectionId); }; return ( ); }); } wp.customize.bind('ready', function() { const controls = wp.customize.settings.controls; renderRangeControls(controls); renderColorControls(controls); renderColorsControls(controls); renderSortableControls(controls); renderButtonGroupControls(controls); renderImageButtonGroupControls(controls); renderToggleControls(controls); renderTypographyControls(controls); renderPaletteOverridesControls(controls); renderCustomPaletteControls(controls); renderTextControls(controls); // 一つだけ表示されるコントロール. renderGoogleFontSettingsControl(controls); renderTabsControls(controls); renderSectionNavigationControls(controls); }); /** * シンプルなセクション移動機能 * * @since 1.0.0 * * @param {string} sectionId 移動先セクションID */ function navigateToSection(sectionId: string) { if (!wp?.customize?.section) { console.error('WordPress Customizer API not available'); return; } const targetSection = wp.customize.section(sectionId); if (!targetSection) { console.error(`Target section not found: ${sectionId}`); return; } // セクションにフォーカスを移動 targetSection.focus(); }