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