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