PluginProbe ʕ •ᴥ•ʔ
VK Blocks / trunk
VK Blocks vtrunk
1.122.0 1.121.0 1.120.0 1.119.2 1.119.1 1.119.0 1.118.7 1.82.0.0 1.82.0.1 1.83.0.0 1.83.0.1 1.84.0.0 1.84.0.1 1.85.0.2 1.85.0.3 1.85.1.0 1.85.1.1 1.86.0.0 1.86.0.1 1.86.1.0 1.87.0.0 1.87.0.1 1.88.0.1 1.88.0.2 1.89.0.0 1.9.2 1.90.0.0 1.90.0.1 1.90.1.1 1.91.0.0 1.91.1.1 1.92.0.0 1.92.0.1 1.92.1.0 1.92.1.1 1.93.0.0 trunk 1.93.0.1 0.1.1 1.93.1.0 0.10.3 1.93.1.1 0.11.0 1.94.0.0 0.12.6 1.94.0.1 0.15.1 1.94.1.0 0.16.2 1.94.2.1 0.17.6 1.94.2.2 0.2.2 1.95.0.3 0.22.4 1.95.0.4 0.26.7 1.96.2.0 0.3.2 1.96.2.1 0.31.0 1.97.0.1 0.35.5 1.97.0.2 0.37.5 1.99.0.0 0.38.8 1.99.0.1 0.39.4 0.4.7 0.41.0 0.42.0 0.44.13 0.45.2 0.46.1 0.47.0 0.48.0 0.49.8 0.5.2 0.50.3 0.51.0 0.52.2 0.53.2 0.54.0 0.55.0 0.56.2 0.56.3 0.57.6 0.58.10 0.59.0 0.6.0 0.60.1 0.61.2 0.7.1 0.8.2 1.0.16 1.10.0 1.100.0.0 1.100.0.1 1.102.0.0 1.102.0.1 1.103.0.0 1.104.0.0 1.104.0.1 1.105.1.0 1.105.1.1 1.106.0.0 1.106.0.1 1.107.0.0 1.107.0.1 1.107.0.2 1.108.0.0 1.108.0.1 1.109.0.0 1.109.0.1 1.11.4 1.110.0.0 1.110.0.1 1.111.0.0 1.111.0.1 1.111.0.2 1.112.0.0 1.112.0.1 1.113.0.0 1.113.0.1 1.114.0.0 1.114.0.1 1.114.2.0 1.114.2.1 1.115.0.0 1.115.0.1 1.115.1.0 1.115.1.1 1.115.2.0 1.115.2.1 1.116.0.0 1.116.0.1 1.116.1.0 1.116.1.1 1.116.2.0 1.117.0.0 1.117.0.1 1.117.1.0 1.118.2 1.118.5 1.12.0 1.13.2 1.14.1 1.15.1 1.16.11 1.17.0 1.18.6 1.19.0 1.19.1 1.2.3 1.20.7 1.21.0 1.22.4 1.23.0 1.24.5 1.25.1 1.26.2 1.27.7.2 1.28.0.1 1.29.2.0 1.3.9 1.30.0.1 1.31.0.1 1.32.0.2 1.33.2.1 1.36.1.5 1.37.0.0 1.39.2.1 1.4.6 1.40.0.1 1.40.1.0 1.40.1.1 1.41.0.0 1.41.0.1 1.41.2.2 1.41.2.3 1.43.0.0 1.43.0.1 1.43.0.2 1.44.0.0 1.44.0.1 1.45.0.0 1.45.0.1 1.46.0.0 1.46.0.1 1.47.0.0 1.47.0.1 1.47.1.0 1.48.0.0 1.48.0.1 1.48.0.2 1.48.1.0 1.48.1.1 1.5.0 1.50.0.0 1.50.0.1 1.51.0.0 1.51.0.1 1.52.0.0 1.52.0.1 1.53.0.0 1.53.0.1 1.54.0.0 1.54.0.1 1.55.0.0 1.55.0.1 1.56.0.0 1.56.0.1 1.57.0.0 1.57.0.2 1.57.0.3 1.57.0.4 1.57.0.5 1.57.1.0 1.57.1.1 1.57.1.2 1.58.0.0 1.58.0.1 1.59.0.0 1.59.0.1 1.6.0 1.60.0.0 1.60.0.1 1.63.0.0 1.63.0.1 1.64.0.0 1.64.0.1 1.64.1.0 1.64.1.2 1.67.0.0 1.67.0.1 1.68.0.0 1.68.0.1 1.69.0.0 1.69.0.1 1.69.1.1 1.69.1.2 1.7.1 1.70.0.0 1.70.0.1 1.71.0.0 1.71.0.1 1.72.0.0 1.72.1.0 1.72.1.1 1.73.0.0 1.73.0.1 1.74.0.0 1.74.0.1 1.75.0.0 1.75.1.0 1.75.1.1 1.76.0.0 1.76.0.1 1.76.1.0 1.76.1.1 1.76.2.0 1.76.2.1 1.77.0.0 1.77.0.1 1.78.0.0 1.78.0.1 1.79.0.0 1.79.0.1 1.79.0.2 1.79.0.3 1.79.1.0 1.79.1.1 1.8.2 1.80.1.0 1.80.1.1 1.80.1.2 1.81.0.1 1.81.0.2
vk-blocks / src / blocks / button / edit.js
vk-blocks / src / blocks / button Last commit date
deprecated 1 week ago block.json 1 week ago component.js 1 week ago edit.js 1 week ago icon.svg 2 months ago index.js 1 week ago index.php 2 months ago save.js 1 week ago style.scss 1 week ago transforms.js 2 months ago
edit.js
1335 lines
1 import { __ } from '@wordpress/i18n';
2 import { VKBButton } from './component';
3 import { FontAwesome } from '@vkblocks/utils/font-awesome-new';
4 import {
5 SelectControl,
6 PanelBody,
7 BaseControl,
8 TextControl,
9 Button,
10 RangeControl,
11 __experimentalToggleGroupControl as ToggleGroupControl,
12 __experimentalToggleGroupControlOption as ToggleGroupControlOption,
13 ToolbarGroup,
14 __experimentalUnitControl as UnitControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis
15 } from '@wordpress/components';
16 import {
17 RichText,
18 InspectorControls,
19 useBlockProps,
20 BlockControls,
21 } from '@wordpress/block-editor';
22 import { settings } from '@wordpress/icons';
23 import { useEffect, useState } from '@wordpress/element';
24 import { useInstanceId } from '@wordpress/compose';
25 import { dispatch, select } from '@wordpress/data';
26 import { AdvancedColorPalette } from '@vkblocks/components/advanced-color-palette';
27 import { isHexColor } from '@vkblocks/utils/is-hex-color';
28 import { isParentReusableBlock } from '@vkblocks/utils/is-parent-reusable-block';
29 import LinkToolbar from '@vkblocks/components/link-toolbar';
30 import { iconLabel } from '@vkblocks/utils/icon-label';
31 import { fixBrokenUnicode } from '@vkblocks/utils/fixBrokenUnicode';
32 import { toPresetSpacingVar } from '@vkblocks/utils/to-preset-spacing-var';
33
34 export default function ButtonEdit(props) {
35 const { attributes, setAttributes, clientId, context, isSelected } = props;
36 const buttonSizeLabelId = useInstanceId(ButtonEdit, 'vk-button-size-label');
37 // プリセット⇔数値指定の切り替えをスクリーンリーダーへ通知するための文言。
38 // onChange ハンドラ�
39 でのみ更新する(既存データを開いた瞬間に dirty 化させないため)。
40 // Live-region message announced to screen readers when switching between the preset
41 // and the numeric size. Updated only inside onChange handlers so opening existing
42 // data never marks the block dirty.
43 const [fontSizeAnnouncement, setFontSizeAnnouncement] = useState('');
44 // 数値�
45 �力(UnitControl)を強制再マウントするためのキー。
46 // UnitControl は�
47 部に�
48 �力ドラフトを持つ制御コンポーネントで、ドラフトの
49 // クランプ結果が現在の保存値(value prop)と等しいと、属性が変化せず再レンダリングが
50 // 起きず、�
51 部ドラフト(例: '1')value('10px')へ再同期されず表示だけ取り残される。
52 // blur 確定時に「ドラフト由来の確定値と保存値が乖離していたら」このキーを更新して
53 // UnitControl だけを作り直し、表示を保存値へ揃える。再マウントは blur 後にだけ
54 // 起こすため、�
55 �力中のフォーカスは奪わない。
56 // Key used to force-remount the numeric input (UnitControl).
57 // UnitControl is a controlled component with an internal input draft; when the
58 // clamped draft equals the current stored value (the value prop), the attribute
59 // does not change, no re-render happens, and the internal draft (e.g. '1') is
60 // never re-synced to value ('10px') — so the display is left stale. On blur, if
61 // the committed value diverges from the stored value, we bump this key to rebuild
62 // only the UnitControl and snap its display to the stored value. The remount fires
63 // only after blur, so it never steals focus while the user is typing.
64 const [fontSizeInputKey, setFontSizeInputKey] = useState(0);
65 // 数値指定が優�
66 �されたことを通知する文言(aria-live でのみ使用)。
67 // 視覚的な注記はコア FontSizePicker 同様に廃し、セグメントを隠す構成へ
68 // 変えたため不要になったが、スクリーンリーダー向けのアナウンスは残す。
69 // Text announced (via aria-live only) when the numeric size takes priority.
70 // The visual note is dropped to match the core FontSizePicker (the preset
71 // segment is hidden instead), but the screen-reader announcement is kept.
72 const fontSizePriorityMessage = __(
73 'The numeric font size takes priority.',
74 'vk-blocks'
75 );
76 const {
77 content,
78 subCaption,
79 buttonUrl,
80 buttonTarget,
81 relAttribute,
82 linkToPost,
83 buttonSize,
84 buttonType,
85 buttonEffect,
86 buttonColor,
87 buttonTextColorCustom,
88 buttonColorCustom,
89 buttonAlign,
90 buttonWidthMobile,
91 buttonWidthTablet,
92 buttonWidth,
93 outerGap,
94 fontAwesomeIconBefore,
95 fontAwesomeIconAfter,
96 iconSizeBefore,
97 iconSizeAfter,
98 buttonHoverBgColorCustom,
99 buttonHoverTextColorCustom,
100 blockId,
101 old_1_31_0,
102 fontSizeValue,
103 } = attributes;
104
105 const isDescendentOfQueryLoop =
106 typeof context?.queryId === 'number' &&
107 Number.isFinite(context.queryId);
108
109 // 親ブロックが vk-blocks/button-outer かどうか判定
110 const parents = select('core/block-editor').getBlockParentsByBlockName(
111 clientId,
112 ['vk-blocks/button-outer']
113 );
114 const isInnerButton = parents.length ? true : false;
115
116 // 「カスタムサイズモード」の表示状�
117 �。コアの FontSizePicker と同様、
118 // 数値�
119 �力欄はトグルで出し�
120 �れする。これは属性ではなく UI のローカル状�
121 �なので
122 // ブロックを dirty 化しない。既存データで fontSizeValue があれば custom モードで開く。
123 // Local UI state for the custom-size mode. As with the core FontSizePicker,
124 // the numeric input is toggled in/out. This is UI-only state (not an attribute),
125 // so it never marks the block dirty. Existing data that has a fontSizeValue
126 // opens in custom mode.
127 const [isCustomSize, setIsCustomSize] = useState(!!fontSizeValue);
128
129 // スライダー(RangeControl)に渡す数値。fontSizeValue は単位付き文字列
130 // ('53px' 形式・レガシーで '1.2em' 等もありうる)なので、parseFloat で
131 // 数値部だけ取り出してスライダー位置に反映する。falsy / 解釈不能な値は
132 // undefined を渡し、マウントしただけでは onChange を発火させない
133 // (=触るまで保存値を書き換えない)。max(100) を�
134 えるレガシー値は
135 // 表示だけ 100 で頭打ちにし、state(fontSizeValue) 自体は書き換えない。
136 // Numeric value passed to the slider (RangeControl). fontSizeValue is a
137 // unit-bearing string ('53px'; legacy values such as '1.2em' are possible),
138 // so parseFloat extracts the numeric part for the slider position. For
139 // falsy / unparsable values, undefined is passed and no onChange fires on
140 // mount (the stored value is not rewritten until the user edits it).
141 // Legacy values above max(100) are clamped to 100 for display only; the
142 // stored fontSizeValue itself is left untouched.
143 const parsedFontSize = fontSizeValue
144 ? parseFloat(fontSizeValue)
145 : undefined;
146 const sliderFontSizeValue =
147 parsedFontSize !== undefined && !Number.isNaN(parsedFontSize)
148 ? Math.min(parsedFontSize, 100)
149 : undefined;
150 // 現在の単位を判定する。px(または値が空=既定 px)ではスライダーを px レンジで、
151 // em / rem 等の相対単位では相対レンジ(0〜10・0.1 刻み)で動かす(コアの
152 // FontSizePicker と同じ考え方)。単位は表示用の派生値で、保存値は書き換えない。
153 // Determine the current unit. For px (or empty, defaulting to px) the slider
154 // uses the px range; for relative units (em / rem) it uses a relative range
155 // (0–10, step 0.1), mirroring the core FontSizePicker. The unit is a derived
156 // value for display only and never rewrites the stored value.
157 const currentFontUnit = (() => {
158 if (!fontSizeValue) {
159 return 'px';
160 }
161 const unitMatch = String(fontSizeValue).match(/[a-z%]+$/i);
162 return unitMatch ? unitMatch[0].toLowerCase() : 'px';
163 })();
164 const isPxFontUnit = currentFontUnit === 'px';
165 const sliderFontMin = isPxFontUnit ? 10 : 0;
166 const sliderFontMax = isPxFontUnit ? 100 : 10;
167 const sliderFontStep = isPxFontUnit ? 1 : 0.1;
168
169 // コアの FontSizePicker と同じ単位セット(px / em / rem / vw / vh)。
170 // useCustomUnits フックは古い wp.components 環境では未提供のため、
171 // バージョン非依存の静的�
172 �列で UnitControl にそのまま渡す。
173 // Same unit set as the core FontSizePicker (px / em / rem / vw / vh).
174 // The useCustomUnits hook is absent on older wp.components builds, so we
175 // pass a version-independent static array straight to UnitControl.
176 const fontSizeUnits = [
177 { value: 'px', label: 'px' },
178 { value: 'em', label: 'em' },
179 { value: 'rem', label: 'rem' },
180 { value: 'vw', label: 'vw' },
181 { value: 'vh', label: 'vh' },
182 ];
183
184 // 数値�
185 �力(UnitControl)・スライダー(RangeControl)の双方から呼ぶ�
186 �通更新処理。
187 // 「カスタム値の更新」だけを�
188 い、プリセット復帰や歯車トグルの排他ロジックは
189 // 混ぜない。next は単位付き文字列('24px')または null(=プリセットへ復帰)。
190 // 初回に数値が�
191 �った時は「数値指定優�
192 �」、値が空になった時は「プリセット復帰」を
193 // スクリーンリーダーへアナウンスする(既存挙動を踏襲)。
194 // Shared updater called by both the numeric input (UnitControl) and the
195 // slider (RangeControl). It only handles "updating the custom value" and does
196 // not mix in preset-return or gear-toggle exclusivity logic. `next` is a
197 // unit-bearing string ('24px') or null (return to preset). It announces the
198 // numeric-priority message on the first set, and the preset-return message
199 // when cleared, mirroring the existing behavior.
200 const updateCustomFontSize = (next) => {
201 setAttributes({ fontSizeValue: next });
202 if (next && !fontSizeValue) {
203 setFontSizeAnnouncement(fontSizePriorityMessage);
204 } else if (!next && fontSizeValue) {
205 setFontSizeAnnouncement(
206 __('Returned to the preset size.', 'vk-blocks')
207 );
208 }
209 };
210
211 // 数値�
212 �力(UnitControl)が満たすべき新規�
213 �力レンジ。スライダー(RangeControl)
214 // と同じ min=10 / max=100 / step=1(px 整数)に揃える。
215 // Range the numeric input (UnitControl) must enforce for new input, matching
216 // the slider (RangeControl): min=10 / max=100 / step=1 (integer px).
217 const FONT_SIZE_MIN = 10;
218 const FONT_SIZE_MAX = 100;
219
220 // 数値�
221 �力(UnitControl)専用の onChange ハンドラ。新規�
222 �力をレンジ
223 // (下限10・上限100・px 整数)に揃えつつ、既存ユーザーのレンジ外レガシー値
224 // (例: '200px'・'1.2em')を受動操作で書き換えないことを両立させる。
225 //
226 // 重要: UnitControl(�
227 NumberControl)に min/max を「渡さない」。min/max
228 // 渡すと�
229 部の number inputHTML 制約違反となるレガシー値を、ユーザーが
230 // 何も操作していなくてもフォーカス→ブラーしただけで境界値へクランプし、その値で
231 // onChange を発火させる(@wordpress/components 34.0.0 を確認・実機e2eでも再現:
232 // isPressEnterToChange=false では blur 時 commit 条件が実質 !validity.valid のみ
233 // となり、レンジ外値で commit→クランプ→onChange 伝播し 200px→100px / 1.2em→10px
234 // と保存値が書き換わる=後方互換違反)。
235 // min/max を渡さなければ validity.valid は常に true・isDirty も立たないため、
236 // レガシー値をフォーカス→ブラーしても commit が走らず onChange 自体が発火しない
237 // (=触らなければ保存値は不変)。
238 // そのうえで新規�
239 �力レンジは、ユーザーが実際に編集したときだけ来るこの onChange の
240 // 中で明示的にクランプして�
241 保する。レンジ外レガシー値はユーザーが能動的に編集した
242 // 場合にのみレンジへ丸められる(その時は新仕様適用が妥当)。
243 // IMPORTANT: do NOT pass min/max to UnitControl. With min/max, the native
244 // number input clamps an out-of-range legacy value on blur and fires onChange
245 // even without user edits (verified in @wordpress/components 34.0.0 and
246 // reproduced in e2e), rewriting the stored value and breaking backward
247 // compatibility. Without min/max, validity stays valid and isDirty stays
248 // false, so no commit/onChange fires on passive focus/blur. The new-input
249 // range is instead enforced here, inside onChange, which only fires on actual
250 // user edits.
251 const clampFontSizePx = (raw) => {
252 // step=1(整数)に丸めてから下限/上限へクランプする。
253 const stepped = Math.round(raw);
254 return Math.min(Math.max(stepped, FONT_SIZE_MIN), FONT_SIZE_MAX);
255 };
256 const handleUnitControlFontSize = (value) => {
257 // 空(クリア)はプリセット復帰。
258 if (!value) {
259 updateCustomFontSize(null);
260 return;
261 }
262 const raw = parseFloat(value);
263 // 数値として解釈できない異常値はそのまま既存�
264 �通処理に委ねる(基本到達しない)。
265 if (Number.isNaN(raw)) {
266 updateCustomFontSize(value);
267 return;
268 }
269 // 単位を判定する。px(または単位なし)は新規�
270 �力レンジ(10〜100・整数px)へ
271 // 揃える。em / rem 等の相対単位は、ユーザー指定の数値・単位をそのまま尊重する
272 // (スライダーは単位に応じて範囲が切り替わり、相対単位でも操作できる)。
273 // Determine the unit. px (or no unit) is snapped to the new-input range
274 // (10–100, integer px). Relative units (em / rem) keep the user's value and
275 // unit as-is (the slider switches range per unit and works for them too).
276 const unitMatch = String(value).match(/[a-z%]+$/i);
277 const unit = unitMatch ? unitMatch[0].toLowerCase() : 'px';
278 if (unit === 'px') {
279 updateCustomFontSize(`${clampFontSizePx(raw)}px`);
280 return;
281 }
282 updateCustomFontSize(`${raw}${unit}`);
283 };
284
285 // blur(編集確定)時に、�
286 �力欄の生ドラフトを「保存される確定値」と突き合わせ、
287 // 両�
288 が乖離していたら UnitControl を再マウントして表示を保存値へ揃える。
289 // 例: 既存保存値が '10px' のときにレンジ下限未満の '1' を打つと、確定値は
290 // clamp 結果 '10px' となり属性が変化しない→再レンダリングが起きず�
291 �力欄に '1' が
292 // 残る。この乖離をここで検出してキーを更新し、�
293 �力欄を保存値('10px')に揃える。
294 // 乖離が無い通常�
295 �力では何もしない(不要な再マウントを避ける)。再マウントは
296 // blur 後にしか走らないため、�
297 �力途中のフォーカスは奪わない(後方互換のための
298 // 「触らなければ書き換えない」�
299 保とも独立しており、保存値は変えない)。
300 // On blur (edit committed), compare the raw input draft with the value that
301 // would actually be stored; if they diverge, remount the UnitControl so its
302 // display snaps to the stored value. Example: with a stored value of '10px',
303 // typing '1' (below the lower bound) commits the clamped '10px', so the
304 // attribute is unchanged, no re-render occurs, and the input keeps showing '1'.
305 // We detect that divergence here and bump the key to align the input to the
306 // stored value ('10px'). For ordinary input with no divergence, do nothing
307 // (avoiding needless remounts). Because the remount runs only after blur, it
308 // never steals focus mid-typing; it also leaves the stored value untouched, so
309 // it is independent of the backward-compat "don't rewrite unless touched" rule.
310 const handleUnitControlFontSizeBlur = (event) => {
311 const raw = event?.target?.value;
312 // �
313 �力欄が空(クリア)のときは onChange 側でプリセット復帰済み。何もしない。
314 if (raw === undefined || raw === null || raw === '') {
315 return;
316 }
317 const numeric = parseFloat(raw);
318 if (Number.isNaN(numeric)) {
319 return;
320 }
321 // onChange と同じ規則で「確定後に保存されているはずの値」を組み立てる。
322 const unitMatch = String(raw).match(/[a-z%]+$/i);
323 const unit = unitMatch ? unitMatch[0].toLowerCase() : currentFontUnit;
324 const committed =
325 unit === 'px'
326 ? `${clampFontSizePx(numeric)}px`
327 : `${numeric}${unit}`;
328 // 表示中の生ドラフトを正規化(単位を補ってから)保存確定値と比較する。
329 const normalizedRaw = unitMatch ? String(raw) : `${raw}${unit}`;
330 if (normalizedRaw !== committed) {
331 setFontSizeInputKey((prev) => prev + 1);
332 }
333 };
334
335 // 以前の値を切り替え
336 useEffect(() => {
337 if (attributes.clientId !== undefined) {
338 setAttributes({ clientId: undefined });
339 }
340 if (
341 blockId === undefined ||
342 isParentReusableBlock(clientId) === false
343 ) {
344 setAttributes({ blockId: clientId });
345 }
346 if (
347 buttonUrl === null ||
348 buttonUrl === 'null' ||
349 buttonUrl === 'undefined' ||
350 buttonUrl === ''
351 ) {
352 setAttributes({ buttonUrl: undefined });
353 }
354 if (buttonColorCustom === undefined) {
355 setAttributes({ buttonTextColorCustom: undefined });
356 }
357 if (
358 buttonColorCustom === null ||
359 buttonColorCustom === 'null' ||
360 buttonColorCustom === 'undefined' ||
361 buttonColorCustom === ''
362 ) {
363 setAttributes({ buttonColorCustom: undefined });
364 }
365 if (
366 fontAwesomeIconBefore === null ||
367 fontAwesomeIconBefore === 'null' ||
368 fontAwesomeIconBefore === 'undefined' ||
369 fontAwesomeIconBefore === ''
370 ) {
371 setAttributes({ fontAwesomeIconBefore: undefined });
372 }
373 if (
374 fontAwesomeIconAfter === null ||
375 fontAwesomeIconAfter === 'null' ||
376 fontAwesomeIconAfter === 'undefined' ||
377 fontAwesomeIconAfter === ''
378 ) {
379 setAttributes({ fontAwesomeIconAfter: undefined });
380 }
381 if (
382 subCaption === null ||
383 subCaption === 'null' ||
384 subCaption === 'undefined' ||
385 subCaption === ''
386 ) {
387 setAttributes({ subCaption: undefined });
388 }
389 if (old_1_31_0 === undefined) {
390 if (buttonWidthMobile === undefined) {
391 setAttributes({ buttonWidthMobile: buttonWidth });
392 }
393 if (buttonWidthTablet === undefined) {
394 setAttributes({ buttonWidthTablet: buttonWidth });
395 }
396 setAttributes({ old_1_31_0: true });
397 }
398 if (!isInnerButton) {
399 setAttributes({ buttonWidth: 0 });
400 }
401 }, [clientId]);
402
403 const { updateBlockAttributes } = dispatch('core/block-editor');
404
405 // buttonColor が有効なら buttonColorCustom と buttonTextColorCustom を無効化
406 // プルダウンから直接カスタムを選ぶとその瞬間色が適用されなくなるので primary に戻す
407 // buttonColorCustom が有効でないと buttonTextColorCustom は意味を成さないので無効化
408 useEffect(() => {
409 if (buttonColor !== 'custom') {
410 updateBlockAttributes(clientId, {
411 buttonTextColorCustom: undefined,
412 });
413 updateBlockAttributes(clientId, { buttonColorCustom: undefined });
414 } else if (
415 buttonColorCustom === undefined &&
416 buttonColor === 'custom'
417 ) {
418 updateBlockAttributes(clientId, { buttonColor: 'primary' });
419 updateBlockAttributes(clientId, {
420 buttonTextColorCustom: undefined,
421 });
422 }
423 }, [buttonColor]);
424
425 // buttonColorCustom が有効なら buttonColor を custom に
426 // buttonColorCustom が空白かつ buttonColor が custom なら buttonColor を primary に
427 useEffect(() => {
428 if (buttonColorCustom !== undefined) {
429 updateBlockAttributes(clientId, { buttonColor: 'custom' });
430 } else if (buttonColor === 'custom') {
431 // 背景色クリアされたらデフォルトに戻す
432 updateBlockAttributes(clientId, { buttonColor: 'primary' });
433 }
434 }, [buttonColorCustom]);
435
436 useEffect(() => {
437 const fixes = {};
438 if (fontAwesomeIconBefore) {
439 const fixed = fixBrokenUnicode(fontAwesomeIconBefore);
440 if (fixed !== fontAwesomeIconBefore) {
441 fixes.fontAwesomeIconBefore = fixed;
442 }
443 }
444 if (fontAwesomeIconAfter) {
445 const fixed = fixBrokenUnicode(fontAwesomeIconAfter);
446 if (fixed !== fontAwesomeIconAfter) {
447 fixes.fontAwesomeIconAfter = fixed;
448 }
449 }
450 if (Object.keys(fixes).length > 0) {
451 setAttributes(fixes);
452 }
453 }, [fontAwesomeIconBefore, fontAwesomeIconAfter]);
454
455 let containerClass;
456 // カスタムカラーの場合 またはアウターにギャップが指定されている場合 またはホバー色が指定されている場合
457 if (
458 (buttonColorCustom !== undefined && isHexColor(buttonColorCustom)) ||
459 (buttonTextColorCustom !== undefined &&
460 isHexColor(buttonTextColorCustom)) ||
461 outerGap ||
462 (buttonHoverBgColorCustom !== undefined &&
463 buttonHoverBgColorCustom !== '') ||
464 (buttonHoverTextColorCustom !== undefined &&
465 buttonHoverTextColorCustom !== '')
466 ) {
467 containerClass = `vk_button vk_button-color-custom vk_button-${blockId}`;
468 } else {
469 containerClass = `vk_button vk_button-color-custom`;
470 }
471
472 if (isInnerButton) {
473 if (buttonWidthMobile) {
474 // 横並びボタンで�
475 が指定されている
476 containerClass += ` vk_button-width-mobile-${buttonWidthMobile}`;
477 }
478 if (buttonWidthTablet) {
479 containerClass += ` vk_button-width-tablet-${buttonWidthTablet}`;
480 }
481 if (buttonWidth) {
482 containerClass += ` vk_button-width-${buttonWidth}`;
483 }
484 } else {
485 containerClass += ` vk_button-align-${buttonAlign}`;
486 }
487
488 // エフェクト
489 if (buttonEffect !== '') {
490 containerClass += ` is-style-${buttonEffect}`;
491 }
492
493 // アイコン単位
494 const units = [
495 { value: 'px', label: 'px', default: 16 },
496 { value: 'em', label: 'em', default: 1 },
497 { value: 'rem', label: 'rem', default: 1 },
498 ];
499
500 const blockProps = useBlockProps({
501 className: containerClass,
502 });
503
504 // コア spacing.padding は wrapper (.vk_button) にインライン style として
505 // 自動注�
506 �されるが、編集画面でも save と同様に wrapper 側を取り除き、
507 // 同じ値を <a> 側に転写する。これによりエディタ表示と保存後 HTML を一致させる。
508 // Same wrapper-padding strip / forward to <a> as in save.js, so the editor
509 // preview matches the saved HTML.
510 const spacingPadding = attributes?.style?.spacing?.padding;
511 const hasSpacingPadding =
512 spacingPadding &&
513 (spacingPadding.top ||
514 spacingPadding.right ||
515 spacingPadding.bottom ||
516 spacingPadding.left);
517
518 if (hasSpacingPadding && blockProps.style) {
519 const {
520 paddingTop: _pt,
521 paddingRight: _pr,
522 paddingBottom: _pb,
523 paddingLeft: _pl,
524 ...restWrapperStyle
525 } = blockProps.style;
526 blockProps.style = restWrapperStyle;
527 }
528
529 let inlineStyle = {};
530 if (
531 buttonTextColorCustom !== undefined &&
532 isHexColor(buttonTextColorCustom)
533 ) {
534 inlineStyle = {
535 // 編集画面対策
536 color: `${buttonTextColorCustom}`,
537 };
538 }
539 if (hasSpacingPadding) {
540 if (spacingPadding.top) {
541 inlineStyle.paddingTop = toPresetSpacingVar(spacingPadding.top);
542 }
543 if (spacingPadding.right) {
544 inlineStyle.paddingRight = toPresetSpacingVar(spacingPadding.right);
545 }
546 if (spacingPadding.bottom) {
547 inlineStyle.paddingBottom = toPresetSpacingVar(
548 spacingPadding.bottom
549 );
550 }
551 if (spacingPadding.left) {
552 inlineStyle.paddingLeft = toPresetSpacingVar(spacingPadding.left);
553 }
554 }
555
556 // buttonTargetをlink-toolbarのlinkTargetに変換
557 const linkTarget = buttonTarget ? '_blank' : '';
558
559 return (
560 <>
561 <BlockControls>
562 <ToolbarGroup>
563 <LinkToolbar
564 linkUrl={buttonUrl || ''}
565 setLinkUrl={(value) =>
566 setAttributes({ buttonUrl: value })
567 }
568 linkTarget={linkTarget}
569 setLinkTarget={(value) =>
570 setAttributes({ buttonTarget: value === '_blank' })
571 }
572 relAttribute={relAttribute || ''}
573 setRelAttribute={(value) =>
574 setAttributes({ relAttribute: value })
575 }
576 isDescendentOfQueryLoop={isDescendentOfQueryLoop}
577 linkToPost={linkToPost}
578 setLinkToPost={(checked) =>
579 setAttributes({ linkToPost: !!checked })
580 }
581 />
582 </ToolbarGroup>
583 </BlockControls>
584 <InspectorControls>
585 <PanelBody title={__('Button setting', 'vk-blocks')}>
586 <TextControl
587 label={__('Sub Caption', 'vk-blocks')}
588 value={subCaption}
589 className={`mt-0 mb-3`}
590 onChange={(value) =>
591 setAttributes({ subCaption: value })
592 }
593 placeholder={__('Sub Caption', 'vk-blocks')}
594 />
595
596 {/* 見出しとカスタムサイズ切替トグルを 1 行に並べる。
597 コアの FontSizePicker のヘッダー(ラベル+歯車トグル)に倣う。
598 Lay out the heading and the custom-size toggle on one row,
599 mirroring the core FontSizePicker header (label + gear toggle). */}
600 <div className="vk-button-font-size-header mt-0 mb-2">
601 <h4 id={buttonSizeLabelId}>
602 {__('Button Size:', 'vk-blocks')}
603 </h4>
604 {/* プリセット⇔カスタムサイズを切り替えるトグル。
605 isPressed はコアの Button �
606 部で aria-pressed に変換されるため、
607 これだけでカスタムサイズモードの ON/OFF を支援技術へ伝えられる。
608 Toggle between preset and custom size. The core Button converts
609 isPressed into aria-pressed, so this alone conveys whether the
610 custom-size mode is on to assistive technologies. */}
611 <Button
612 className="vk-button-font-size-toggle"
613 size="small"
614 icon={settings}
615 isPressed={isCustomSize}
616 label={
617 isCustomSize
618 ? __('Use size preset', 'vk-blocks')
619 : __('Set custom size', 'vk-blocks')
620 }
621 showTooltip
622 onClick={() => {
623 if (isCustomSize) {
624 // カスタム → プリセットへ戻す。
625 // 数値指定を解除し、プリセットへ戻した旨を通知する。
626 // Custom -> preset. Clear the numeric size and
627 // announce the return to the preset.
628 setIsCustomSize(false);
629 if (fontSizeValue) {
630 setAttributes({ fontSizeValue: null });
631 setFontSizeAnnouncement(
632 __(
633 'Returned to the preset size.',
634 'vk-blocks'
635 )
636 );
637 }
638 } else {
639 // プリセット → カスタムへ。
640 // ここでは fontSizeValue を自動セットしない。ユーザーが
641 // 数値を�
642 �れるまで出力はプリセットのまま(dirty 化を避ける)。
643 // Preset -> custom. Do not auto-set fontSizeValue here;
644 // the output stays on the preset until the user enters a
645 // value (avoids marking the block dirty).
646 setIsCustomSize(true);
647 }
648 }}
649 />
650 </div>
651 {/* プリセットモード: XS/S/M/L/XL のセグメント。
652 カスタムサイズモード時は隠す(コア FontSizePicker と同じ挙動)。
653 Preset mode: the XS/S/M/L/XL segments. Hidden while in custom-size
654 mode (matching the core FontSizePicker behavior). */}
655 {!isCustomSize && (
656 <ToggleGroupControl
657 value={buttonSize}
658 // プリセットを選び直したら数値指定を解除してプリセットに戻す。
659 // Choosing a preset clears the numeric size and returns to the preset.
660 onChange={(value) => {
661 setAttributes({
662 buttonSize: value,
663 fontSizeValue: null,
664 });
665 if (fontSizeValue) {
666 setFontSizeAnnouncement(
667 __(
668 'Returned to the preset size.',
669 'vk-blocks'
670 )
671 );
672 }
673 }}
674 aria-labelledby={buttonSizeLabelId}
675 isBlock
676 >
677 <ToggleGroupControlOption
678 value="xs"
679 label={__('XS', 'vk-blocks')}
680 aria-label={__('Extra Small', 'vk-blocks')}
681 showTooltip
682 />
683 <ToggleGroupControlOption
684 value="sm"
685 label={__('S', 'vk-blocks')}
686 aria-label={__('Small', 'vk-blocks')}
687 showTooltip
688 />
689 <ToggleGroupControlOption
690 value="md"
691 label={__('M', 'vk-blocks')}
692 aria-label={__('Medium', 'vk-blocks')}
693 showTooltip
694 />
695 <ToggleGroupControlOption
696 value="lg"
697 label={__('L', 'vk-blocks')}
698 aria-label={__('Large', 'vk-blocks')}
699 showTooltip
700 />
701 <ToggleGroupControlOption
702 value="xl"
703 label={__('XL', 'vk-blocks')}
704 aria-label={__('Extra Large', 'vk-blocks')}
705 showTooltip
706 />
707 </ToggleGroupControl>
708 )}
709 {/* カスタムサイズモード: 数値�
710 �力(px固定)+スライダー。
711 コアの FontSizePicker(withSlider)の見た目を、min/max/step を
712 指定できる UnitControl + RangeControl の併置で再現する。コアの
713 FontSizePicker コンポーネント自体は min/max/step を props で
714 受け取れず、植草指定の min=10/max=100/step=1・px 固定を満たせない
715 ため採用しない(A-2 方針)。
716 Custom-size mode: a px-only numeric input plus a slider.
717 This reproduces the look of the core FontSizePicker (withSlider)
718 using a UnitControl + RangeControl placed side by side, because the
719 core FontSizePicker component cannot accept min/max/step props and so
720 cannot satisfy the specified min=10/max=100/step=1 px-only range. */}
721 {isCustomSize && (
722 <div className="vk-button-font-size-control mt-0 mb-2">
723 <div className="vk-button-font-size-control__row">
724 <UnitControl
725 // 保存値とクランプ確定値が一致して再レンダリングが
726 // 起きないケースで、blur 時にこのキーを変えて再マウントし
727 // 表示を保存値へ強制同期する(詳細は宣言箇所のコメント参�
728 �)。
729 // Bumping this key on blur remounts the control so its
730 // display snaps to the stored value when the clamped result
731 // matches the stored value and no re-render would occur.
732 key={fontSizeInputKey}
733 className="vk-button-font-size-control__unit"
734 label={__('Font Size', 'vk-blocks')}
735 value={fontSizeValue}
736 // 単位はコア準拠(px / em / rem / vw / vh)。レガシー値は
737 // ユーザーが能動的に触るまで温存し、勝手に変換しない。
738 // Units follow the core set (px / em / rem / vw / vh). Legacy
739 // values are kept until the user actively edits them; they are
740 // never auto-converted.
741 units={fontSizeUnits}
742 // 新規�
743 �力レンジ(下限10・上限100・px 整数)の�
744 保は
745 // handleUnitControlFontSize 側で明示的に行う。
746 // min/max を UnitControl に渡すと、レンジ外のレガシー値
747 // ('200px'・'1.2em' 等)をフォーカス→ブラーしただけで
748 // 受動的に境界値へクランプ&保存してしまい後方互換を壊すため、
749 // あえて min/max は渡さない(step は整数�
750 �力のUIヒント)。
751 // The new-input range (min 10, max 100, integer px) is enforced
752 // in handleUnitControlFontSize. min/max are intentionally NOT
753 // passed to UnitControl because they would passively clamp and
754 // persist out-of-range legacy values ('200px', '1.2em') on mere
755 // focus/blur, breaking backward compatibility. (step is only a
756 // UI hint for integer input.)
757 step={1}
758 // 数値指定したらプリセットより優�
759 �。空ならプリセットへ戻す。
760 // Setting a value makes it take priority over the preset; an
761 // empty value returns control to the preset.
762 onChange={(value) =>
763 handleUnitControlFontSize(value)
764 }
765 // 編集確定(blur)時に、表示中のドラフトが保存確定値と
766 // 乖離していれば再マウントして表示を揃える。
767 // On blur, remount to align the display when the draft
768 // diverges from the committed stored value.
769 onBlur={handleUnitControlFontSizeBlur}
770 />
771 {/* スライダー。数値�
772 �力は UnitControl 側に集約するため、
773 ここでは�
774 �力欄を出さない(spinbutton の二重化を防ぐ)。
775 見出しラベルを持たないので aria-label で操作対象を明示する。
776 The slider. The numeric input lives in the UnitControl, so
777 this hides its own input field to avoid duplicate spinbuttons.
778 It has no visible label, so aria-label states its purpose. */}
779 <RangeControl
780 className="vk-button-font-size-control__slider"
781 aria-label={__('Font Size', 'vk-blocks')}
782 value={sliderFontSizeValue}
783 min={sliderFontMin}
784 max={sliderFontMax}
785 step={sliderFontStep}
786 withInputField={false}
787 showTooltip
788 // スライダー操作時は常に px の数値として確定させる。
789 // On slider change, always commit a px numeric value.
790 onChange={(value) =>
791 updateCustomFontSize(
792 value === undefined ||
793 value === null
794 ? null
795 : `${value}${currentFontUnit}`
796 )
797 }
798 />
799 </div>
800 </div>
801 )}
802 {/* 状�
803 �変化をスクリーンリーダーへ通知する live region。
804 Live region announcing the state change to screen readers. */}
805 <div
806 className="screen-reader-text"
807 aria-live="polite"
808 aria-atomic="true"
809 >
810 {fontSizeAnnouncement}
811 </div>
812 <style>
813 {`
814 /* 見出しと歯車トグルを両端に�
815 �置する。
816 Place the heading and the gear toggle at opposite ends. */
817 .vk-button-font-size-header {
818 display: flex;
819 align-items: center;
820 justify-content: space-between;
821 gap: 8px;
822 }
823 /* ヘッダー�
824 の見出しは flex で縦中央に揃えるためマージンを消す。
825 Drop the heading margin so flex can center it vertically. */
826 .vk-button-font-size-header h4 {
827 margin: 0;
828 }
829 /* カスタムサイズUI�
830 �体のラッパー。数値�
831 �力とスライダーを __row �
832
833 横1行で収める(コア FontSizePicker と同じ構成)。
834 Wrapper for the whole custom-size UI. The numeric input and slider
835 sit on a single __row (same layout as the core FontSizePicker). */
836 .vk-button-font-size-control {
837 display: flex;
838 flex-direction: column;
839 align-items: stretch;
840 }
841 /* 数値�
842 �力(UnitControl)とスライダー(RangeControl)を横1行に並べ、
843 コアの FontSizePicker(数値+px+スライダー)の見た目に寄せる。
844 狭いサイドバーでも縦積みにならないよう折り返しはさせず、
845 数値�
846 �力はコンパクトな固定�
847 、スライダーが残り�
848 を埋める。
849 Lay out the numeric input (UnitControl) and the slider
850 (RangeControl) on a single row to mirror the core FontSizePicker
851 (number + px + slider). Do not wrap (so they never stack on a
852 narrow sidebar): the numeric input keeps a compact fixed width
853 and the slider fills the remaining space. */
854 .vk-button-font-size-control__row {
855 display: flex;
856 align-items: center;
857 gap: 8px;
858 }
859 /* 数値�
860 �力はコンパクトな固定�
861 。スライダーは残り�
862 いっぱい。
863 min-width:0 で flex 子要素が�
864 容�
865 を�
866 えて縮められるようにする。
867 The numeric input takes a compact fixed width; the slider fills
868 the rest. min-width:0 lets the flex children shrink below their
869 content width. */
870 .vk-button-font-size-control__unit {
871 flex: 0 1 104px;
872 min-width: 0;
873 }
874 .vk-button-font-size-control__slider {
875 flex: 1 1 auto;
876 min-width: 0;
877 margin: 0;
878 }
879 `}
880 </style>
881 {!isInnerButton && (
882 <>
883 <h4 className="mt-0 mb-2">
884 {__('Button Position:', 'vk-blocks')}
885 </h4>
886 <ToggleGroupControl
887 value={buttonAlign}
888 onChange={(value) =>
889 setAttributes({ buttonAlign: value })
890 }
891 className="vk-button-align-control"
892 isBlock
893 >
894 <ToggleGroupControlOption
895 value="left"
896 label={__('Left', 'vk-blocks')}
897 />
898 <ToggleGroupControlOption
899 value="center"
900 label={__('Center', 'vk-blocks')}
901 />
902 <ToggleGroupControlOption
903 value="right"
904 label={__('Right', 'vk-blocks')}
905 />
906 <ToggleGroupControlOption
907 value="wide"
908 label={__('Wide', 'vk-blocks')}
909 />
910 <ToggleGroupControlOption
911 value="block"
912 label={__('Block', 'vk-blocks')}
913 />
914 </ToggleGroupControl>
915 <style>
916 {`
917 .vk-button-align-control .components-toggle-group-control-option-base {
918 padding: 0;
919 }
920 `}
921 </style>
922 </>
923 )}
924
925 {isInnerButton && (
926 <>
927 <h4 className="mt-0 mb-2">
928 {__('Button Width:', 'vk-blocks')}
929 </h4>
930 <p className="mt-0 mb-2">
931 {__('Mobile', 'vk-blocks')}
932 </p>
933 <ToggleGroupControl
934 value={String(buttonWidthMobile)}
935 onChange={(value) => {
936 setAttributes({
937 buttonWidthMobile: Number(value),
938 });
939 }}
940 isBlock
941 >
942 <ToggleGroupControlOption
943 value="0"
944 label="Auto"
945 />
946 <ToggleGroupControlOption
947 value="25"
948 label="25%"
949 />
950 <ToggleGroupControlOption
951 value="50"
952 label="50%"
953 />
954 <ToggleGroupControlOption
955 value="75"
956 label="75%"
957 />
958 <ToggleGroupControlOption
959 value="100"
960 label="100%"
961 />
962 </ToggleGroupControl>
963 <p className="mt-0 mb-2">
964 {__('Tablet', 'vk-blocks')}
965 </p>
966
967 <ToggleGroupControl
968 value={String(buttonWidthTablet)}
969 onChange={(value) => {
970 setAttributes({
971 buttonWidthTablet: Number(value),
972 });
973 }}
974 isBlock
975 >
976 <ToggleGroupControlOption
977 value="0"
978 label="Auto"
979 />
980 <ToggleGroupControlOption
981 value="25"
982 label="25%"
983 />
984 <ToggleGroupControlOption
985 value="50"
986 label="50%"
987 />
988 <ToggleGroupControlOption
989 value="75"
990 label="75%"
991 />
992 <ToggleGroupControlOption
993 value="100"
994 label="100%"
995 />
996 </ToggleGroupControl>
997
998 <p className="mt-0 mb-2">{__('PC', 'vk-blocks')}</p>
999 <ToggleGroupControl
1000 value={String(buttonWidth)}
1001 onChange={(value) => {
1002 setAttributes({
1003 buttonWidth: Number(value),
1004 });
1005 }}
1006 isBlock
1007 >
1008 <ToggleGroupControlOption
1009 value="0"
1010 label="Auto"
1011 />
1012 <ToggleGroupControlOption
1013 value="25"
1014 label="25%"
1015 />
1016 <ToggleGroupControlOption
1017 value="50"
1018 label="50%"
1019 />
1020 <ToggleGroupControlOption
1021 value="75"
1022 label="75%"
1023 />
1024 <ToggleGroupControlOption
1025 value="100"
1026 label="100%"
1027 />
1028 </ToggleGroupControl>
1029 </>
1030 )}
1031
1032 <h4 className="mt-0 mb-2">
1033 {__('Button Style:', 'vk-blocks')}
1034 </h4>
1035 <ToggleGroupControl
1036 value={buttonType}
1037 onChange={(value) => {
1038 setAttributes({ buttonType: value });
1039
1040 if (value === '1' || value === '2') {
1041 setAttributes({
1042 buttonTextColorCustom: undefined,
1043 buttonEffect: '',
1044 });
1045 }
1046 if (value === '2') {
1047 setAttributes({
1048 buttonHoverBgColorCustom: undefined,
1049 });
1050 }
1051 }}
1052 isBlock
1053 >
1054 <ToggleGroupControlOption
1055 value="0"
1056 label={__('Solid color', 'vk-blocks')}
1057 />
1058 <ToggleGroupControlOption
1059 value="1"
1060 label={__('No background', 'vk-blocks')}
1061 />
1062 <ToggleGroupControlOption
1063 value="2"
1064 label={__('Text only', 'vk-blocks')}
1065 />
1066 </ToggleGroupControl>
1067 <p className={`mb-3`}>
1068 {__(
1069 'If you select "No background", you need to select a custom color.',
1070 'vk-blocks'
1071 )}
1072 </p>
1073
1074 {'0' === buttonType && (
1075 <>
1076 <h4 className="mt-0 mb-2">
1077 {__('Button Effect:', 'vk-blocks')}
1078 </h4>
1079 <ToggleGroupControl
1080 value={buttonEffect}
1081 onChange={(value) =>
1082 setAttributes({ buttonEffect: value })
1083 }
1084 isBlock
1085 >
1086 <ToggleGroupControlOption
1087 value="none"
1088 label={__('None', 'vk-blocks')}
1089 />
1090 <ToggleGroupControlOption
1091 value="shine"
1092 label={__('Shine', 'vk-blocks')}
1093 />
1094 </ToggleGroupControl>
1095 </>
1096 )}
1097
1098 <h4 className={`mt-0 mb-2`}>{__('Color', 'vk-blocks')}</h4>
1099 <SelectControl
1100 label={__('Default Color (Bootstrap)', 'vk-blocks')}
1101 value={buttonColor}
1102 options={[
1103 {
1104 label: __('Primary', 'vk-blocks'),
1105 value: 'primary',
1106 },
1107 {
1108 label: __('Secondary', 'vk-blocks'),
1109 value: 'secondary',
1110 },
1111 {
1112 label: __('Success', 'vk-blocks'),
1113 value: 'success',
1114 },
1115 {
1116 label: __('Info', 'vk-blocks'),
1117 value: 'info',
1118 },
1119 {
1120 label: __('Warning', 'vk-blocks'),
1121 value: 'warning',
1122 },
1123 {
1124 label: __('Danger', 'vk-blocks'),
1125 value: 'danger',
1126 },
1127 {
1128 label: __('Light', 'vk-blocks'),
1129 value: 'light',
1130 },
1131 {
1132 label: __('Dark', 'vk-blocks'),
1133 value: 'dark',
1134 },
1135 {
1136 label: __('Custom Color', 'vk-blocks'),
1137 value: 'custom',
1138 },
1139 ]}
1140 onChange={(value) =>
1141 setAttributes({ buttonColor: value })
1142 }
1143 />
1144 <BaseControl
1145 label={__('Custom Color', 'vk-blocks')}
1146 id={`vk_block_button_custom_color`}
1147 >
1148 <BaseControl
1149 id={`vk_block_button_custom_background_color`}
1150 label={
1151 buttonType === '0' || buttonType === null
1152 ? __('Background Color', 'vk-blocks')
1153 : __('Button Color', 'vk-blocks')
1154 }
1155 help={__(
1156 'This color palette overrides the default color. If you want to use the default color, click the clear button.',
1157 'vk-blocks'
1158 )}
1159 >
1160 <AdvancedColorPalette
1161 schema={'buttonColorCustom'}
1162 {...props}
1163 />
1164 </BaseControl>
1165 {(buttonType === '0' || buttonType === null) &&
1166 buttonColorCustom !== undefined && (
1167 <BaseControl
1168 id={`vk_block_button_custom_text_color`}
1169 label={__('Text Color', 'vk-blocks')}
1170 >
1171 <AdvancedColorPalette
1172 schema={'buttonTextColorCustom'}
1173 {...props}
1174 />
1175 </BaseControl>
1176 )}
1177 </BaseControl>
1178 <h4 className={`mt-0 mb-2`}>
1179 {__('Hover Color', 'vk-blocks')}
1180 </h4>
1181 <BaseControl id={`vk_block_button_hover_color`}>
1182 {buttonType !== '2' && (
1183 <BaseControl
1184 id={`vk_block_button_hover_background_color`}
1185 label={__(
1186 'Hover Background Color',
1187 'vk-blocks'
1188 )}
1189 >
1190 <AdvancedColorPalette
1191 schema={'buttonHoverBgColorCustom'}
1192 {...props}
1193 />
1194 </BaseControl>
1195 )}
1196 <BaseControl
1197 id={`vk_block_button_hover_text_color`}
1198 label={__('Hover Text Color', 'vk-blocks')}
1199 >
1200 <AdvancedColorPalette
1201 schema={'buttonHoverTextColorCustom'}
1202 {...props}
1203 />
1204 </BaseControl>
1205 </BaseControl>
1206 <BaseControl>
1207 <h4 className={`mt-0 mb-2`}>
1208 {iconLabel(__('Icon', 'vk-blocks'))}
1209 </h4>
1210 <BaseControl
1211 id={`vk_block_button_fa_before_text`}
1212 label={__('Before text', 'vk-blocks')}
1213 >
1214 <FontAwesome
1215 attributeName={'fontAwesomeIconBefore'}
1216 {...props}
1217 />
1218 <UnitControl
1219 label={__('Size', 'vk-blocks')}
1220 value={iconSizeBefore}
1221 units={units}
1222 onChange={(value) => {
1223 setAttributes({
1224 iconSizeBefore: parseFloat(value)
1225 ? value
1226 : null,
1227 });
1228 }}
1229 />
1230 </BaseControl>
1231 <hr />
1232 <BaseControl
1233 id={`vk_block_button_fa_after_text`}
1234 label={__('After text', 'vk-blocks')}
1235 >
1236 <FontAwesome
1237 attributeName={'fontAwesomeIconAfter'}
1238 {...props}
1239 />
1240 <UnitControl
1241 label={__('Size', 'vk-blocks')}
1242 value={iconSizeAfter}
1243 units={units}
1244 onChange={(value) => {
1245 setAttributes({
1246 iconSizeAfter: parseFloat(value)
1247 ? value
1248 : null,
1249 });
1250 }}
1251 />
1252 </BaseControl>
1253 </BaseControl>
1254 <h4 className={`mt-0 mb-2`}>
1255 {__('Button border radius', 'vk-blocks')}
1256 </h4>
1257 <UnitControl
1258 value={attributes.borderRadius}
1259 onChange={(value) => {
1260 setAttributes({ borderRadius: value || null });
1261 }}
1262 units={[
1263 { value: 'px', label: 'px', default: 5 },
1264 { value: '%', label: '%', default: 5 },
1265 { value: 'em', label: 'em', default: 1 },
1266 { value: 'rem', label: 'rem', default: 1 },
1267 ]}
1268 />
1269 </PanelBody>
1270 </InspectorControls>
1271 <div {...blockProps}>
1272 <VKBButton
1273 lbTextColorCustom={buttonTextColorCustom}
1274 lbColorCustom={buttonColorCustom}
1275 lbColor={buttonColor}
1276 lbType={buttonType}
1277 lbAlign={buttonAlign}
1278 lbSize={buttonSize}
1279 lbFontAwesomeIconBefore={fontAwesomeIconBefore}
1280 lbFontAwesomeIconAfter={fontAwesomeIconAfter}
1281 lbIconSizeBefore={iconSizeBefore}
1282 lbIconSizeAfter={iconSizeAfter}
1283 lbsubCaption={subCaption}
1284 lbFontSizeValue={fontSizeValue}
1285 lbsubCaptionRichText={
1286 isSelected || subCaption ? (
1287 <RichText
1288 tagName="span"
1289 value={subCaption}
1290 onChange={(value) =>
1291 setAttributes({ subCaption: value })
1292 }
1293 placeholder={__('Sub Caption', 'vk-blocks')}
1294 allowedFormats={[]}
1295 aria-label={__('Sub Caption', 'vk-blocks')}
1296 />
1297 ) : null
1298 }
1299 inlineStyle={{
1300 ...inlineStyle,
1301 borderRadius: attributes.borderRadius,
1302 }}
1303 lbRichtext={
1304 <RichText
1305 tagName={'span'}
1306 className={'vk_button_link_txt'}
1307 onChange={(value) =>
1308 setAttributes({ content: value })
1309 }
1310 value={content}
1311 placeholder={__('Input text', 'vk-blocks')}
1312 allowedFormats={[
1313 'core/bold',
1314 // 'core/code',
1315 // 'core/image',
1316 'core/italic',
1317 // 'core/link',
1318 'core/strikethrough',
1319 // 'core/underline',
1320 // 'core/text-color',
1321 'core/superscript',
1322 'core/subscript',
1323 // 'vk-blocks/highlighter',
1324 'vk-blocks/responsive-br',
1325 'vk-blocks/nowrap',
1326 'vk-blocks/inline-font-size',
1327 ]}
1328 />
1329 }
1330 />
1331 </div>
1332 </>
1333 );
1334 }
1335