import { useState, useEffect, useRef } from '@wordpress/element'; import { Flex, FlexItem, SelectControl, FlexBlock, TextControl, } from '@wordpress/components'; import FontSelect from './FontSelect'; import ApuraRangeControl from './ApuraRangeControl'; import Accordion from './Accordion'; import TextTransform from './TextTransform'; import { __ } from '@wordpress/i18n'; import { ALL_DEVICE_DATA, TYPO_UNIT_SETTINGS, TYPO_UNIT_OPTIONS_DATA, FONT_SIZE_UNIT_OPTIONS_DATA, LETTER_SPACING_UNIT_OPTIONS_DATA, TYPO_LETTER_SPACING_UNIT_SETTINGS, } from './Constants'; import { extractTypographyData, parseCssValue, createDeviceValue } from './utils'; export default function ApuraTypography({ values, defaults, title, description, exclude = [], onChange, }: { values: { [key: string]: string }; defaults: { [key: string]: string }; title: string; description?: string; exclude?: string[]; onChange: (newValues: { [key: string]: string }) => void; }) { const [typography, setTypography] = useState(values); // 初回マウント時に onChange が発火しないようにするフラグ. const isMounted = useRef(false); const [googleFonts, setGoogleFonts] = useState(apuraCustomizerControl?.googleFonts ?? []); useEffect(() => { wp.customize(apuraCustomizerControl?.googleFontSettingsId, (setting: any) => { setting.bind((newGoogleFonts: GoogleFontItem[]) => { setGoogleFonts(newGoogleFonts ?? []); }); }); }, []); useEffect(() => { // 新しいGoogleFontsを参照する。typographyの値は参照しない. // 新しいGoogleFontsのvariantsに..が含まれているかどうかでvariable fontかどうかを判定する。 // もし新しいGoogleFontsのvariantsに..が含まれている場合、typographyのfont_weightが範囲外になっている可能性があるので、その場合はfont_weightを空にする。 // ..がないということは一つの値のみが確定なので、その値を現在のfont_weightに設定する。 if (typography.font_family) { const newFont = googleFonts?.find((font) => font.name === typography.font_family); if (newFont) { const isVariable = newFont.variants.some((variant) => variant.includes('..')); if (!isVariable) { // variable fontではない場合、font_weightを新しいフォントの最初のバリアントに設定する。 setTypography((prev) => ({ ...prev, font_weight: '400', })); } else { // variable fontの場合、現在のfont_weightが新しいフォントの範囲外になっている可能性があるので、その場合はfont_weightを空にする。 const [min, max] = newFont.variants.find((variant) => variant.includes('..'))?.split('..').map(Number) || [100, 900]; const currentWeight = parseInt(typography.font_weight || '400', 10); if (currentWeight < min || currentWeight > max) { setTypography((prev) => ({ ...prev, font_weight: '', })); } } } } }, [googleFonts]); useEffect(() => { setTypography(values); }, [values]); useEffect(() => { // 初回マウント時はスキップし、typography が実際に変更された場合のみ onChange を呼ぶ. if ( ! isMounted.current ) { isMounted.current = true; return; } onChange(typography); }, [typography]); const prepareDeviceValues = (property: string): { deviceValues: RangeControlDeviceValue[]; deviceDefaults: RangeControlDeviceValue[] } => { const typographyData = extractTypographyData(typography, [property]); const defaultsData = extractTypographyData(defaults, [property]); const deviceValues: RangeControlDeviceValue[] = []; const deviceDefaults: RangeControlDeviceValue[] = []; ALL_DEVICE_DATA.forEach(({ device }) => { const parsedValue = parseCssValue(typographyData[property][device]) || 0; const parsedDefaults = parseCssValue(defaultsData[property][device]); const { value, default: defaultValue } = createDeviceValue(device, parsedValue, parsedDefaults, TYPO_UNIT_SETTINGS); deviceValues.push(value); deviceDefaults.push(defaultValue); }); return { deviceValues, deviceDefaults }; }; const { deviceValues: fontSizeValues, deviceDefaults: fontSizeDefaults } = prepareDeviceValues('font_size'); const { deviceValues: lineHeightValues, deviceDefaults: lineHeightDefaults } = prepareDeviceValues('line_height'); const { deviceValues: letterSpacingValues, deviceDefaults: letterSpacingDefaults } = prepareDeviceValues('letter_spacing'); const handleDevicesPropertyChange = (property: string, newValues: any) => { setTypography((prev) => ({ ...prev, [`${property}`]: newValues.desktop, [`tablet_${property}`]: newValues.tablet, [`mobile_${property}`]: newValues.mobile, })); }; const handlePropertyChange = (property: string, newValue: string) => { setTypography((prev) => ({ ...prev, [`${property}`]: newValue })); }; const handleFontFamilyChange = (selectedFont: string | null | undefined) => { // フォントを変更したらfont_weightを400にリセットする setTypography((prev) => ( { ...prev, font_family: selectedFont || '', font_weight: '400' } )); }; // 指定されたフォント名に対応するすべてのバリアントを取得 const getAllVariantsForFont = (fontName: string | null | undefined): string[] => { if (!fontName) return []; return googleFonts?.find((data) => data.name === fontName)?.variants || []; }; // フォントのバリアントからフォントウェイトのオプションを生成 const getFontWeightOptions = (): { value: string; label: string }[] => { const defaultOptions = [ { value: '', label: __('Default', 'apura') }, { value: 'normal', label: __('Normal', 'apura') }, { value: 'bold', label: __('Bold', 'apura') }, ...Array.from({ length: 9 }, (_, i) => ({ value: `${i + 1}00`, label: `${i + 1}00` })), ]; if (!typography.font_family) { return defaultOptions; } const fontVariants = getAllVariantsForFont(typography.font_family); // System fonts don't have variants. if (!fontVariants.length) { return defaultOptions; } // italicなどの文字列を取り除いて数値部分のみを抽出し、重複を排除してソートする. const numericVariants = [...new Set(fontVariants.map((variant) => variant.replace('italic', '')))].sort((a, b) => a.localeCompare(b)); // Sort variants. return numericVariants.map((variant) => ({ value: variant, label: variant })); }; // Googleフォントにおいてvariable fontかどうかを判定. 100..900のようなバリアントが含まれているかどうかで判定 // ただし、googleFontsがundefinedやnullの場合もあるので、その場合はfalseにする const currentFontVariants = getAllVariantsForFont(typography.font_family); const variableVariant = currentFontVariants.find((variant) => variant.includes('..')); const isVariableFont = !! variableVariant; const [variableFontWeightMin, variableFontWeightMax] = variableVariant?.split('..') ?? [100, 900]; return (
{description && (

{description}

)} {!exclude.includes('font_family') && ( )} {(!exclude.includes('font_weight') || !exclude.includes('font_style')) && ( {!exclude.includes('font_weight') && ( {isVariableFont ? ( handlePropertyChange('font_weight', value)} min={variableFontWeightMin} max={variableFontWeightMax} step={100} /> ) : ( handlePropertyChange('font_weight', value)} /> )} )} {!exclude.includes('font_style') && ( handlePropertyChange('font_style', value)} /> )} )} {!exclude.includes('font_size') && ( handleDevicesPropertyChange('font_size', newValues)} /> )} {!exclude.includes('line_height') && ( handleDevicesPropertyChange('line_height', newValues)} /> )} {!exclude.includes('letter_spacing') && ( handleDevicesPropertyChange('letter_spacing', newValues)} /> )} {!exclude.includes('text_transform') && ( handlePropertyChange('text_transform', value)} /> )}
); }