PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.8
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.8
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / assets / js / icon-picker.js

icon-picker.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.8, at assets/js/icon-picker.js

557 lines 30.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Blockenberg — Shared Icon Picker
3 *
4 * Provides icon type selection, dashicon picker, image upload, and icon rendering
5 * for use across all Blockenberg blocks.
6 *
7 * Works in both editor (React/createElement) and frontend (DOM) contexts.
8 *
9 * Editor API:
10 * window.bkbgIconPicker.IconPickerControl — React control for inspector
11 * window.bkbgIconPicker.buildEditorIcon — React element for editor preview
12 * window.bkbgIconPicker.buildSaveIcon — React element for static save
13 * window.bkbgIconPicker.iconPickerProps(attr, set [, config]) — convenience mapper
14 *
15 * Frontend API:
16 * window.bkbgIconPicker.buildFrontendIcon — DOM element builder
17 *
18 * Data:
19 * window.bkbgIconPicker.svgPaths — { type: 'd-attribute' }
20 * window.bkbgIconPicker.dashicons — [ 'icon-name', ... ]
21 * window.bkbgIconPicker.iconTypeOptions — [ { label, value }, ... ]
22 */
23 (function () {
24 'use strict';
25
26 // ── SVG Path Data ────────────────────────────────────────────────────────────
27 var svgPaths = {
28 check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
29 check2: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5l-4.5-4.5 1.41-1.41L10 13.67l7.59-7.59L19 7.5l-9 9z',
30 arrow: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z',
31 'arrow-right': 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z',
32 star: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z',
33 dash: 'M4 11h16v2H4z',
34 cross: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z',
35 sparkle: 'M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 17l-6.2 4.3 2.4-7.4L2 9.4h7.6z',
36 bolt: 'M7 2v11h3v9l7-12h-4l4-8z',
37 heart: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z',
38 info: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z',
39 warning: 'M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z',
40 shield: 'M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z',
41 'thumb-up': 'M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z',
42 'thumb-down': 'M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z',
43 lightbulb: 'M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7z',
44 lock: 'M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z'
45 };
46
47 // SVG elements that need special markup (not just <path>)
48 var svgSpecial = {
49 disc: '<circle cx="12" cy="12" r="6"/>',
50 square: '<rect x="6" y="6" width="12" height="12" rx="2"/>'
51 };
52
53 // ── Dashicons List (subset) ──────────────────────────────────────────────────
54 var dashicons = [
55 'yes', 'yes-alt', 'no', 'no-alt', 'arrow-right', 'arrow-right-alt', 'arrow-right-alt2',
56 'arrow-left-alt2', 'arrow-up-alt2', 'arrow-down-alt2',
57 'star-filled', 'star-half', 'star-empty', 'heart', 'thumbs-up', 'thumbs-down',
58 'awards', 'flag', 'warning', 'info', 'lightbulb', 'marker', 'tag', 'category',
59 'location', 'location-alt', 'clock', 'calendar', 'calendar-alt',
60 'groups', 'businessman', 'businesswoman', 'admin-users', 'id', 'id-alt',
61 'products', 'cart', 'money', 'money-alt', 'bank', 'feedback', 'forms',
62 'edit', 'trash', 'upload', 'download', 'cloud', 'cloud-upload', 'cloud-saved',
63 'search', 'lock', 'unlock', 'performance', 'analytics', 'chart-bar', 'chart-pie',
64 'chart-line', 'chart-area', 'list-view', 'grid-view', 'slides',
65 'desktop', 'laptop', 'tablet', 'smartphone', 'phone',
66 'media-audio', 'media-video', 'media-document', 'format-image', 'camera',
67 'portfolio', 'book', 'book-alt', 'text-page', 'welcome-write-blog',
68 'hammer', 'art', 'schedule', 'rest-api', 'database', 'code-standards',
69 'rss', 'email', 'email-alt', 'share', 'share-alt', 'networking',
70 'facebook', 'twitter', 'instagram', 'linkedin', 'youtube',
71 'wordpress', 'superhero', 'superhero-alt', 'plugins-checked',
72 'food', 'coffee', 'beer', 'car', 'airplane', 'palmtree', 'building', 'store',
73 'smiley', 'bell', 'megaphone', 'microphone', 'games', 'tickets', 'tickets-alt',
74 'privacy', 'shield', 'shield-alt', 'vault', 'sos', 'nametag', 'clipboard',
75 'sticky', 'open-folder', 'paperclip', 'printer', 'shortcode', 'layout', 'saved',
76 'plus', 'plus-alt', 'plus-alt2', 'minus', 'dismiss', 'remove', 'insert',
77 'ellipsis', 'external', 'exit', 'fullscreen-alt', 'fullscreen-exit-alt'
78 ];
79
80 // ── Icon Type Options (for SelectControl) ────────────────────────────────────
81 var iconTypeOptions = [
82 { label: 'Checkmark ✓', value: 'check' },
83 { label: 'Check Circle ●✓', value: 'check2' },
84 { label: 'Arrow ›', value: 'arrow' },
85 { label: 'Arrow Right →', value: 'arrow-right' },
86 { label: 'Disc •', value: 'disc' },
87 { label: 'Square ▪', value: 'square' },
88 { label: 'Star �
89 ', value: 'star' },
90 { label: 'Sparkle ✦', value: 'sparkle' },
91 { label: 'Bolt ⚡', value: 'bolt' },
92 { label: 'Dash —', value: 'dash' },
93 { label: 'Cross ✕', value: 'cross' },
94 { label: 'Heart ♥', value: 'heart' },
95 { label: 'Info ℹ', value: 'info' },
96 { label: 'Warning ⚠', value: 'warning' },
97 { label: 'Shield 🛡', value: 'shield' },
98 { label: 'Thumbs Up 👍', value: 'thumb-up' },
99 { label: 'Thumbs Down 👎', value: 'thumb-down' },
100 { label: 'Lightbulb 💡', value: 'lightbulb' },
101 { label: 'Lock 🔒', value: 'lock' },
102 { label: 'Custom Character', value: 'custom-char' },
103 { label: 'Custom Image', value: 'custom-image' },
104 { label: 'Dashicon', value: 'dashicon' },
105 { label: 'None', value: 'none' }
106 ];
107
108 // ── Frontend DOM icon builder (always available) ─────────────────────────────
109 function buildFrontendIcon(type, customChar, dashicon, imageUrl, dashiconColor) {
110 if (!type || type === 'none') return null;
111
112 var span = document.createElement('span');
113 span.className = 'bkbg-icon-el';
114 span.setAttribute('aria-hidden', 'true');
115 span.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;line-height:1;';
116
117 if (type === 'custom-image') {
118 if (!imageUrl) return null;
119 var img = document.createElement('img');
120 img.src = imageUrl;
121 img.alt = '';
122 img.className = 'bkbg-icon-img';
123 img.style.cssText = 'width:1em;height:1em;object-fit:contain;display:block;';
124 span.appendChild(img);
125 } else if (type === 'custom-char') {
126 span.textContent = customChar || '';
127 } else if (type === 'dashicon') {
128 var di = document.createElement('span');
129 di.className = 'dashicons dashicons-' + (dashicon || 'star-filled');
130 di.style.cssText = 'font-size:inherit;width:1em;height:1em;line-height:1;';
131 if (dashiconColor) di.style.color = dashiconColor;
132 span.appendChild(di);
133 } else {
134 var svgContent;
135 if (svgSpecial[type]) {
136 svgContent = svgSpecial[type];
137 } else if (svgPaths[type]) {
138 svgContent = '<path d="' + svgPaths[type] + '"/>';
139 } else {
140 svgContent = '<path d="' + svgPaths.check + '"/>';
141 }
142 span.innerHTML = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" ' +
143 'aria-hidden="true" style="width:1em;height:1em;fill:currentColor;display:block;">' +
144 svgContent + '</svg>';
145 }
146
147 return span;
148 }
149
150 // ── Editor Components (only when WP editor globals exist) ────────────────────
151 var IconPickerControl = null;
152 var buildEditorIcon = null;
153 var buildSaveIcon = null;
154 var builtInIcons = null;
155
156 if (typeof wp !== 'undefined' && wp.element && wp.components) {
157 var el = wp.element.createElement;
158 var __ = wp.i18n.__;
159 var useState = wp.element.useState;
160 var Fragment = wp.element.Fragment;
161 var SelectControl = wp.components.SelectControl;
162 var TextControl = wp.components.TextControl;
163 var Button = wp.components.Button;
164 var Popover = wp.components.Popover;
165
166 var svgStyle = { width: '1em', height: '1em', fill: 'currentColor', display: 'block' };
167
168 // ── Built-in icons as React elements ──────────────────────────────────
169 builtInIcons = {};
170 Object.keys(svgPaths).forEach(function (key) {
171 builtInIcons[key] = el('svg', {
172 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
173 'aria-hidden': 'true', style: svgStyle
174 }, el('path', { d: svgPaths[key] }));
175 });
176 builtInIcons.disc = el('svg', {
177 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
178 'aria-hidden': 'true', style: svgStyle
179 }, el('circle', { cx: '12', cy: '12', r: '6' }));
180 builtInIcons.square = el('svg', {
181 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
182 'aria-hidden': 'true', style: svgStyle
183 }, el('rect', { x: '6', y: '6', width: '12', height: '12', rx: '2' }));
184
185 // ── Media library opener ──────────────────────────────────────────────
186 function openMediaLibrary(onSelect) {
187 var frame = wp.media({
188 title: __('Select Icon Image', 'blockenberg'),
189 button: { text: __('Use as Icon', 'blockenberg') },
190 multiple: false,
191 library: { type: 'image' }
192 });
193 frame.on('select', function () {
194 var attachment = frame.state().get('selection').first().toJSON();
195 onSelect(attachment.url);
196 });
197 frame.open();
198 }
199
200 // ── IconPickerControl Component ───────────────────────────────────────
201 // Props:
202 // iconType - current icon type string
203 // customChar - current custom character
204 // dashicon - current dashicon name
205 // imageUrl - current image URL
206 // onChangeType - fn(newType)
207 // onChangeChar - fn(newChar)
208 // onChangeDashicon - fn(newDashicon)
209 // onChangeImageUrl - fn(newUrl)
210 // showPreview - (optional, default true) show inline preview for built-in icons
211 // dashiconColor - (optional) color for dashicon
212 // onChangeColor - fn(newColor)
213 IconPickerControl = function (props) {
214 var iconType = props.iconType || 'custom-char';
215 var customChar = props.customChar || '';
216 var dashiconV = props.dashicon || 'star-filled';
217 var imageUrl = props.imageUrl || '';
218 var dashiconColor = props.dashiconColor || '';
219 var showPreview = props.showPreview !== false;
220
221 var pickerState = useState(false);
222 var pickerOpen = pickerState[0];
223 var setPickerOpen = pickerState[1];
224
225 var searchState = useState('');
226 var iconSearch = searchState[0];
227 var setIconSearch = searchState[1];
228
229 var filteredDashicons = dashicons.filter(function (i) {
230 return !iconSearch || i.indexOf(iconSearch.toLowerCase()) !== -1;
231 });
232
233 return el(Fragment, {},
234
235 // Icon Type selector
236 el(SelectControl, {
237 label: __('Icon Type', 'blockenberg'),
238 value: iconType,
239 options: iconTypeOptions,
240 __nextHasNoMarginBottom: true,
241 onChange: props.onChangeType
242 }),
243
244 // ── Custom character input ────────────────────────────────────
245 iconType === 'custom-char' && el('div', { style: { marginTop: '8px' } },
246 el(TextControl, {
247 label: __('Custom Character', 'blockenberg'),
248 value: customChar,
249 onChange: props.onChangeChar,
250 help: __('Any character or emoji, e.g. → ✦ ◆ 🔥 ⚠️', 'blockenberg'),
251 __nextHasNoMarginBottom: true
252 })
253 ),
254
255 // ── Custom image upload ───────────────────────────────────────
256 iconType === 'custom-image' && el('div', { style: { marginTop: '8px', marginBottom: '8px' } },
257 el('p', { className: 'components-base-control__label' },
258 __('Image Icon', 'blockenberg')
259 ),
260 imageUrl && el('div', {
261 style: { marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '8px' }
262 },
263 el('img', {
264 src: imageUrl, alt: '',
265 style: {
266 width: '40px', height: '40px', objectFit: 'contain',
267 border: '1px solid #e2e8f0', borderRadius: '4px', padding: '2px'
268 }
269 }),
270 el(Button, {
271 isSmall: true, isDestructive: true, variant: 'secondary',
272 onClick: function () { props.onChangeImageUrl(''); }
273 }, __('Remove', 'blockenberg'))
274 ),
275 el(Button, {
276 variant: 'secondary',
277 onClick: function () {
278 openMediaLibrary(function (url) { props.onChangeImageUrl(url); });
279 }
280 },
281 el('span', { className: 'dashicons dashicons-upload', style: { marginRight: '4px' } }),
282 imageUrl
283 ? __('Replace Image', 'blockenberg')
284 : __('Select from Media Library', 'blockenberg')
285 )
286 ),
287
288 // ── Dashicon picker ───────────────────────────────────────────
289 iconType === 'dashicon' && el('div', { style: { marginTop: '8px', marginBottom: '8px' } },
290 el('p', { className: 'components-base-control__label' },
291 __('Dashicon', 'blockenberg')
292 ),
293 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
294 el('span', {
295 className: 'dashicons dashicons-' + dashiconV,
296 style: { fontSize: '24px', width: '24px', height: '24px', color: dashiconColor || 'inherit' }
297 }),
298 el(Button, {
299 variant: 'secondary', isSmall: true,
300 onClick: function () { setPickerOpen(!pickerOpen); }
301 }, __('Change Icon', 'blockenberg')),
302
303 pickerOpen && el(Popover, {
304 position: 'bottom left',
305 onClose: function () { setPickerOpen(false); setIconSearch(''); }
306 },
307 el('div', {
308 style: {
309 width: '260px', maxHeight: '450px', background: '#fff',
310 borderRadius: '8px',
311 boxShadow: '0 4px 24px rgba(0,0,0,0.15)',
312 overflow: 'hidden'
313 },
314 onMouseDown: function (e) { e.stopPropagation(); },
315 onClick: function (e) { e.stopPropagation(); }
316 },
317 // Header
318 el('div', {
319 style: {
320 display: 'flex', justifyContent: 'space-between',
321 alignItems: 'center', padding: '12px 16px',
322 borderBottom: '1px solid #e0e0e0'
323 }
324 },
325 el('h3', { style: { margin: 0, fontSize: '14px', fontWeight: 600 } },
326 __('Choose Dashicon', 'blockenberg')
327 ),
328 el(Button, {
329 icon: 'no-alt',
330 onClick: function () { setPickerOpen(false); setIconSearch(''); },
331 style: { padding: 0, minWidth: '24px', height: '24px' }
332 })
333 ),
334 // Search
335 el('div', { style: { padding: '8px 12px 0' } },
336 el(TextControl, {
337 placeholder: __('Search icons…', 'blockenberg'),
338 value: iconSearch,
339 onChange: setIconSearch
340 })
341 ),
342 // Grid
343 el('div', {
344 style: {
345 display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)',
346 gap: '4px', maxHeight: '280px', overflowY: 'auto',
347 padding: '8px 12px 12px'
348 }
349 },
350 filteredDashicons.length === 0
351 ? el('p', {
352 style: {
353 gridColumn: '1 / -1', textAlign: 'center',
354 color: '#666', padding: '20px 0'
355 }
356 }, __('No icons found', 'blockenberg'))
357 : filteredDashicons.map(function (icon) {
358 var isActive = (dashiconV === icon);
359 return el('button', {
360 key: icon,
361 type: 'button',
362 style: {
363 width: '36px', height: '36px',
364 display: 'flex', alignItems: 'center',
365 justifyContent: 'center',
366 border: isActive ? '2px solid #007cba' : '1px solid transparent',
367 borderRadius: '4px',
368 background: isActive ? '#e6f3ff' : 'transparent',
369 cursor: 'pointer', padding: 0
370 },
371 onClick: function () {
372 props.onChangeDashicon(icon);
373 setPickerOpen(false);
374 setIconSearch('');
375 },
376 title: icon
377 },
378 el('span', {
379 className: 'dashicons dashicons-' + icon,
380 style: { fontSize: '20px', width: '20px', height: '20px' }
381 })
382 );
383 })
384 )
385 )
386 )
387 ),
388
389 // ── Dashicon color ────────────────────────────────────────
390 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' } },
391 el('label', { style: { fontSize: '11px', fontWeight: 500 } }, __('Icon Color', 'blockenberg')),
392 el('input', {
393 type: 'color',
394 value: dashiconColor || '#000000',
395 onChange: function (e) { props.onChangeColor(e.target.value); },
396 style: { width: '32px', height: '24px', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: 0 }
397 }),
398 dashiconColor && el(Button, {
399 isSmall: true, variant: 'tertiary',
400 onClick: function () { props.onChangeColor(''); },
401 style: { fontSize: '11px' }
402 }, __('Reset', 'blockenberg'))
403 )
404 ),
405
406 // ── Inline preview for built-in SVG icons ────────────────────
407 showPreview && iconType !== 'none' && iconType !== 'custom-image' &&
408 iconType !== 'dashicon' && iconType !== 'custom-char' &&
409 el('div', {
410 style: { marginTop: '8px', display: 'flex', alignItems: 'center', gap: '8px' }
411 },
412 el('span', { style: { display: 'inline-flex', width: '24px', height: '24px', fontSize: '24px' } },
413 builtInIcons[iconType] || builtInIcons.check
414 ),
415 el('span', { style: { fontSize: '12px', color: '#666' } },
416 __('Preview', 'blockenberg')
417 )
418 )
419 );
420 };
421
422 // ── Build editor icon (React element for live preview) ────────────────
423 // 5th param: string → dashiconColor, object → { dashiconColor, style, className }
424 buildEditorIcon = function (type, customChar, dashicon, imageUrl, extra) {
425 if (!type || type === 'none') return null;
426
427 var dashiconColor = '';
428 var wrapStyle = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', lineHeight: 1 };
429 var wrapClass = 'bkbg-icon-el';
430
431 if (typeof extra === 'string') {
432 dashiconColor = extra;
433 } else if (extra && typeof extra === 'object') {
434 dashiconColor = extra.dashiconColor || '';
435 if (extra.className) wrapClass += ' ' + extra.className;
436 if (extra.style) { var es = extra.style; for (var k in es) { if (es.hasOwnProperty(k)) wrapStyle[k] = es[k]; } }
437 }
438
439 var inner;
440
441 if (type === 'custom-image') {
442 if (!imageUrl) return null;
443 inner = el('img', {
444 src: imageUrl, alt: '', className: 'bkbg-icon-img',
445 style: { width: '1em', height: '1em', objectFit: 'contain', display: 'block' }
446 });
447 } else if (type === 'custom-char') {
448 inner = customChar || '';
449 } else if (type === 'dashicon') {
450 inner = el('span', {
451 className: 'dashicons dashicons-' + (dashicon || 'star-filled'),
452 style: { fontSize: 'inherit', width: '1em', height: '1em', lineHeight: 1, color: dashiconColor || undefined }
453 });
454 } else if (builtInIcons[type]) {
455 inner = builtInIcons[type];
456 } else {
457 inner = builtInIcons.check;
458 }
459
460 return el('span', {
461 className: wrapClass, 'aria-hidden': 'true', style: wrapStyle
462 }, inner);
463 };
464
465 // ── Build save icon (React element for static save) ───────────────────
466 // 5th param: string → dashiconColor, object → { dashiconColor }
467 buildSaveIcon = function (type, customChar, dashicon, imageUrl, extra) {
468 if (!type || type === 'none') return null;
469
470 var dashiconColor = typeof extra === 'string' ? extra : (extra && extra.dashiconColor) || '';
471 var inner;
472 if (type === 'custom-image') {
473 if (!imageUrl) return null;
474 inner = el('img', {
475 src: imageUrl, alt: '', className: 'bkbg-icon-img',
476 style: { width: '1em', height: '1em', objectFit: 'contain', display: 'block' }
477 });
478 } else if (type === 'custom-char') {
479 inner = el('span', { className: 'bkbg-icon-char' }, customChar || '');
480 } else if (type === 'dashicon') {
481 inner = el('span', {
482 className: 'dashicons dashicons-' + (dashicon || 'star-filled'),
483 style: { fontSize: 'inherit', width: '1em', height: '1em', lineHeight: 1, color: dashiconColor || undefined }
484 });
485 } else if (type === 'disc') {
486 inner = el('svg', {
487 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
488 'aria-hidden': 'true', style: svgStyle
489 }, el('circle', { cx: '12', cy: '12', r: '6' }));
490 } else if (type === 'square') {
491 inner = el('svg', {
492 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
493 'aria-hidden': 'true', style: svgStyle
494 }, el('rect', { x: '6', y: '6', width: '12', height: '12', rx: '2' }));
495 } else if (svgPaths[type]) {
496 inner = el('svg', {
497 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
498 'aria-hidden': 'true', style: svgStyle
499 }, el('path', { d: svgPaths[type] }));
500 } else {
501 inner = el('svg', {
502 viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg',
503 'aria-hidden': 'true', style: svgStyle
504 }, el('path', { d: svgPaths.check }));
505 }
506
507 return el('span', {
508 className: 'bkbg-icon-el', 'aria-hidden': 'true',
509 style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', lineHeight: 1 }
510 }, inner);
511 };
512 }
513
514 // ── Convenience: standard attribute ↔ prop mapping ──────────────────────────
515 // Default mapping:
516 // attr.iconType → iconType (override with config.typeAttr)
517 // attr.icon → customChar (override with config.charAttr)
518 // attr.iconDashicon → dashicon (override with config.dashiconAttr)
519 // attr.iconImageUrl → imageUrl (override with config.imageUrlAttr)
520 function iconPickerProps(attr, setAttributes, config) {
521 var c = config || {};
522 var charAttr = c.charAttr || 'icon';
523 var typeAttr = c.typeAttr || 'iconType';
524 var dashiconAttr = c.dashiconAttr || 'iconDashicon';
525 var imageUrlAttr = c.imageUrlAttr || 'iconImageUrl';
526 var colorAttr = c.colorAttr || 'iconDashiconColor';
527
528 return {
529 iconType: attr[typeAttr],
530 customChar: attr[charAttr],
531 dashicon: attr[dashiconAttr],
532 imageUrl: attr[imageUrlAttr],
533 dashiconColor: attr[colorAttr] || '',
534 onChangeType: function (v) { var u = {}; u[typeAttr] = v; setAttributes(u); },
535 onChangeChar: function (v) { var u = {}; u[charAttr] = v; setAttributes(u); },
536 onChangeDashicon: function (v) { var u = {}; u[dashiconAttr] = v; setAttributes(u); },
537 onChangeImageUrl: function (v) { var u = {}; u[imageUrlAttr] = v; setAttributes(u); },
538 onChangeColor: function (v) { var u = {}; u[colorAttr] = v; setAttributes(u); }
539 };
540 }
541
542 // ── Export ────────────────────────────────────────────────────────────────────
543 window.bkbgIconPicker = {
544 svgPaths: svgPaths,
545 svgSpecial: svgSpecial,
546 dashicons: dashicons,
547 iconTypeOptions: iconTypeOptions,
548 builtInIcons: builtInIcons,
549 IconPickerControl: IconPickerControl,
550 buildEditorIcon: buildEditorIcon,
551 buildSaveIcon: buildSaveIcon,
552 buildFrontendIcon: buildFrontendIcon,
553 iconPickerProps: iconPickerProps
554 };
555
556 })();
557