PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.6
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.6
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 / blocks / styled-list / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.6, at blocks/styled-list/index.js

1,110 lines 62.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 wp.domReady(function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var useEffect = wp.element.useEffect;
7 var useMemo = wp.element.useMemo;
8 var registerBlockType = wp.blocks.registerBlockType;
9 var InspectorControls = wp.blockEditor.InspectorControls;
10 var BlockControls = wp.blockEditor.BlockControls;
11 var useBlockProps = wp.blockEditor.useBlockProps;
12 var RichText = wp.blockEditor.RichText;
13 var PanelBody = wp.components.PanelBody;
14 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
15 var Button = wp.components.Button;
16 var ToggleControl = wp.components.ToggleControl;
17 var RangeControl = wp.components.RangeControl;
18 var SelectControl = wp.components.SelectControl;
19 var TextControl = wp.components.TextControl;
20 var ColorPicker = wp.components.ColorPicker;
21 var Popover = wp.components.Popover;
22 var ToolbarGroup = wp.components.ToolbarGroup;
23 var ToolbarButton = wp.components.ToolbarButton;
24 var TabPanel = wp.components.TabPanel;
25
26 // ── Built-in SVG icons ──────────────────────────────────────────────────────
27 var builtInIcons = {
28 check: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
29 el('path', { d: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z' })
30 ),
31 check2: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
32 el('path', { d: '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' })
33 ),
34 arrow: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
35 el('path', { d: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z' })
36 ),
37 'arrow-right': el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
38 el('path', { d: 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z' })
39 ),
40 star: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
41 el('path', { d: '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' })
42 ),
43 disc: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
44 el('circle', { cx: '12', cy: '12', r: '6' })
45 ),
46 square: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
47 el('rect', { x: '6', y: '6', width: '12', height: '12', rx: '2' })
48 ),
49 dash: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
50 el('path', { d: 'M4 11h16v2H4z' })
51 ),
52 cross: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
53 el('path', { d: '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' })
54 ),
55 sparkle: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
56 el('path', { d: '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' })
57 ),
58 bolt: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
59 el('path', { d: 'M7 2v11h3v9l7-12h-4l4-8z' })
60 )
61 };
62
63 // ── Dashicons list (subset) ──────────────────────────────────────────────────
64 var dashicons = [
65 'yes', 'yes-alt', 'no', 'no-alt', 'arrow-right', 'arrow-right-alt', 'arrow-right-alt2',
66 'arrow-left-alt2', 'arrow-up-alt2', 'arrow-down-alt2',
67 'star-filled', 'star-half', 'star-empty', 'heart', 'thumbs-up', 'thumbs-down',
68 'awards', 'flag', 'warning', 'info', 'lightbulb', 'marker', 'tag', 'category',
69 'location', 'location-alt', 'clock', 'calendar', 'calendar-alt',
70 'groups', 'businessman', 'businesswoman', 'admin-users', 'id', 'id-alt',
71 'products', 'cart', 'money', 'money-alt', 'bank', 'feedback', 'forms',
72 'edit', 'trash', 'upload', 'download', 'cloud', 'cloud-upload', 'cloud-saved',
73 'search', 'lock', 'unlock', 'performance', 'analytics', 'chart-bar', 'chart-pie',
74 'chart-line', 'chart-area', 'list-view', 'grid-view', 'slides',
75 'desktop', 'laptop', 'tablet', 'smartphone', 'phone',
76 'media-audio', 'media-video', 'media-document', 'format-image', 'camera',
77 'portfolio', 'book', 'book-alt', 'text-page', 'welcome-write-blog',
78 'hammer', 'art', 'schedule', 'rest-api', 'database', 'code-standards',
79 'rss', 'email', 'email-alt', 'share', 'share-alt', 'networking',
80 'facebook', 'twitter', 'instagram', 'linkedin', 'youtube',
81 'wordpress', 'superhero', 'superhero-alt', 'plugins-checked',
82 'food', 'coffee', 'beer', 'car', 'airplane', 'palmtree', 'building', 'store',
83 'smiley', 'bell', 'megaphone', 'microphone', 'games', 'tickets', 'tickets-alt',
84 'privacy', 'shield', 'shield-alt', 'vault', 'sos', 'nametag', 'clipboard',
85 'sticky', 'open-folder', 'paperclip', 'printer', 'shortcode', 'layout', 'saved',
86 'plus', 'plus-alt', 'plus-alt2', 'minus', 'dismiss', 'remove', 'insert',
87 'ellipsis', 'external', 'exit', 'fullscreen-alt', 'fullscreen-exit-alt'
88 ];
89
90 function getBuiltInIcon(type) {
91 return builtInIcons[type] || builtInIcons.check;
92 }
93
94 // ── Helper: resolve effective icon settings for an item ─────────────────────
95 function resolveItemIcon(a, item) {
96 // Per-item icon overrides global if set (no flag required)
97 var hasPerItem = item && item.itemIconType;
98 var type = hasPerItem ? item.itemIconType : a.iconType;
99 var customChar = hasPerItem ? (item.itemIconChar || a.customChar) : a.customChar;
100 var dashicon = hasPerItem ? (item.itemIconDashicon || a.customDashicon) : a.customDashicon;
101 var imageUrl = hasPerItem ? (item.itemIconUrl || a.customImageUrl) : a.customImageUrl;
102 return { type: type, customChar: customChar, dashicon: dashicon, imageUrl: imageUrl };
103 }
104
105 // ── Helper: build icon element for editor/save ───────────────────────────────
106 function buildIconEl(a, item, index) {
107 var resolved = resolveItemIcon(a, item);
108 var type = resolved.type;
109 var color = (a.perItemColor && item && item.customColor) ? item.customColor : a.iconColor;
110
111 if (type === 'none') return null;
112
113 var inner;
114
115 if (type === 'custom-image') {
116 var url = resolved.imageUrl;
117 if (!url) return null;
118 inner = el('img', {
119 src: url,
120 alt: '',
121 className: 'bkbg-sl-img-icon',
122 style: { width: '100%', height: '100%', objectFit: 'contain', display: 'block' }
123 });
124 } else if (type === 'number') {
125 inner = el('span', { className: 'bkbg-sl-num' }, (index + 1) + '');
126 } else if (type === 'custom-char') {
127 inner = el('span', { className: 'bkbg-sl-char' }, resolved.customChar || '');
128 } else if (type === 'dashicon') {
129 inner = el('span', { className: 'dashicons dashicons-' + resolved.dashicon });
130 } else if (builtInIcons[type]) {
131 inner = getBuiltInIcon(type);
132 } else {
133 inner = getBuiltInIcon('check');
134 }
135
136 return el('span', {
137 className: 'bkbg-sl-icon',
138 'aria-hidden': 'true',
139 style: (color && type !== 'custom-image') ? { color: color } : {}
140 }, inner);
141 }
142
143 registerBlockType('blockenberg/styled-list', {
144 title: __('Styled List', 'blockenberg'),
145 icon: 'editor-ul',
146 category: 'blockenberg',
147 description: __('Fully customizable list with icons, rich text, and flexible layout options.', 'blockenberg'),
148
149 edit: function (props) {
150 var attributes = props.attributes;
151 var setAttributes = props.setAttributes;
152 var isSelected = props.isSelected;
153 var a = attributes;
154
155 // State: which item is focused
156 var activeState = useState(null);
157 var activeItem = activeState[0];
158 var setActiveItem = activeState[1];
159
160 // FIX: clear active item when block is deselected
161 useEffect(function () {
162 if (!isSelected) { setActiveItem(null); }
163 }, [isSelected]);
164
165 // State: dashicon picker open for which item ('global' | index | null)
166 var iconPickerState = useState(null);
167 var openIconPicker = iconPickerState[0];
168 var setOpenIconPicker = iconPickerState[1];
169
170 // State: per-item icon picker open (stores index or null)
171 var itemIconPickerState = useState(null);
172 var openItemIconPicker = itemIconPickerState[0];
173 var setOpenItemIconPicker = itemIconPickerState[1];
174
175 // State: per-item color picker
176 var colorPickerState = useState(null);
177 var colorPicker = colorPickerState[0];
178 var setColorPicker = colorPickerState[1];
179
180 // State: dashicon search
181 var searchState = useState('');
182 var iconSearch = searchState[0];
183 var setIconSearch = searchState[1];
184
185 // Filtered dashicons
186 var filteredDashicons = useMemo(function () {
187 if (!iconSearch) return dashicons;
188 var s = iconSearch.toLowerCase();
189 return dashicons.filter(function (d) { return d.includes(s); });
190 }, [iconSearch]);
191
192 // ── Helpers ────────────────────────────────────────────────────────────
193 function updateItem(index, key, value) {
194 var newItems = a.items.slice();
195 newItems[index] = Object.assign({}, newItems[index]);
196 newItems[index][key] = value;
197 setAttributes({ items: newItems });
198 }
199
200 function addItem() {
201 setAttributes({
202 items: a.items.concat([{
203 text: __('New list item — click to edit', 'blockenberg'),
204 description: '',
205 customColor: ''
206 }])
207 });
208 }
209
210 function removeItem(index) {
211 if (a.items.length <= 1) return;
212 setAttributes({ items: a.items.filter(function (_, i) { return i !== index; }) });
213 }
214
215 function moveItem(index, dir) {
216 var next = index + dir;
217 if (next < 0 || next >= a.items.length) return;
218 var newItems = a.items.slice();
219 var tmp = newItems[index];
220 newItems[index] = newItems[next];
221 newItems[next] = tmp;
222 setAttributes({ items: newItems });
223 }
224
225 function duplicateItem(index) {
226 var newItems = a.items.slice();
227 newItems.splice(index + 1, 0, Object.assign({}, a.items[index]));
228 setAttributes({ items: newItems });
229 }
230
231 // ── Option lists ───────────────────────────────────────────────────────
232 var listStyleOptions = [
233 { label: __('Vertical (Stack)', 'blockenberg'), value: 'vertical' },
234 { label: __('Two Columns', 'blockenberg'), value: 'grid' },
235 { label: __('Custom Columns', 'blockenberg'), value: 'grid-custom' }
236 ];
237 var iconTypeOptions = [
238 { label: __('Checkmark ✓', 'blockenberg'), value: 'check' },
239 { label: __('Check Circle ●✓', 'blockenberg'), value: 'check2' },
240 { label: __('Arrow ›', 'blockenberg'), value: 'arrow' },
241 { label: __('Arrow Right →', 'blockenberg'), value: 'arrow-right' },
242 { label: __('Disc •', 'blockenberg'), value: 'disc' },
243 { label: __('Square ▪', 'blockenberg'), value: 'square' },
244 { label: __('Star �
245 ', 'blockenberg'), value: 'star' },
246 { label: __('Sparkle ✦', 'blockenberg'), value: 'sparkle' },
247 { label: __('Bolt ⚡', 'blockenberg'), value: 'bolt' },
248 { label: __('Dash —', 'blockenberg'), value: 'dash' },
249 { label: __('Cross ✕', 'blockenberg'), value: 'cross' },
250 { label: __('Number 1 2 3', 'blockenberg'), value: 'number' },
251 { label: __('Custom Character', 'blockenberg'), value: 'custom-char' },
252 { label: __('Custom Image', 'blockenberg'), value: 'custom-image' },
253 { label: __('Dashicon', 'blockenberg'), value: 'dashicon' },
254 { label: __('None', 'blockenberg'), value: 'none' }
255 ];
256
257 // Also used for per-item picker — same options + "Use Global"
258 var itemIconTypeOptions = [{ label: __('— Use Global Icon —', 'blockenberg'), value: '' }].concat(iconTypeOptions);
259 var iconShapeOptions = [
260 { label: __('None (icon only)', 'blockenberg'), value: 'none' },
261 { label: __('Circle — Filled', 'blockenberg'), value: 'circle-filled' },
262 { label: __('Circle — Outline', 'blockenberg'), value: 'circle-outline' },
263 { label: __('Square — Filled', 'blockenberg'), value: 'square-filled' },
264 { label: __('Square — Outline', 'blockenberg'), value: 'square-outline' },
265 { label: __('Rounded Square — Filled', 'blockenberg'), value: 'rounded-filled' },
266 { label: __('Rounded Square — Outline', 'blockenberg'), value: 'rounded-outline' }
267 ];
268 var iconPositionOptions = [
269 { label: __('Left', 'blockenberg'), value: 'left' },
270 { label: __('Right', 'blockenberg'), value: 'right' }
271 ];
272 var borderStyleOptions = [
273 { label: __('Solid', 'blockenberg'), value: 'solid' },
274 { label: __('Dashed', 'blockenberg'), value: 'dashed' },
275 { label: __('Dotted', 'blockenberg'), value: 'dotted' }
276 ];
277 var fontWeightOptions = [
278 { label: '300', value: 300 },
279 { label: '400 — Regular', value: 400 },
280 { label: '500 — Medium', value: 500 },
281 { label: '600 — Semibold', value: 600 },
282 { label: '700 — Bold', value: 700 },
283 { label: '800 — Extrabold', value: 800 }
284 ];
285 var alignmentOptions = [
286 { label: __('Left', 'blockenberg'), value: 'left' },
287 { label: __('Center', 'blockenberg'), value: 'center' },
288 { label: __('Right', 'blockenberg'), value: 'right' }
289 ];
290
291 // ── Dynamic computed styles for preview ────────────────────────────────
292 var wrapDataAttrs = {
293 'data-list-style': a.listStyle,
294 'data-icon-type': a.iconType,
295 'data-icon-shape': a.iconShape,
296 'data-icon-pos': a.iconPosition,
297 'data-icon-align': a.iconAlignTop ? 'top' : 'center',
298 'data-divider': a.dividerEnabled ? '1' : '0',
299 'data-align': a.alignment
300 };
301
302 // Compute inline layout so it always wins over editor iframe resets
303 var isHorizontal = a.listStyle === 'horizontal';
304 var isGrid = a.listStyle === 'grid' || a.listStyle === 'grid-custom';
305 var computedDisplay = isGrid ? 'grid' : 'flex';
306 var computedFlexDir = isHorizontal ? 'row' : 'column';
307
308 var wrapStyle = {
309 display: computedDisplay,
310 flexDirection: isHorizontal ? 'row' : (isGrid ? undefined : 'column'),
311 flexWrap: isHorizontal ? 'wrap' : undefined,
312 gridTemplateColumns: isGrid ? ('repeat(' + ((a.listStyle === 'grid-custom') ? a.columns : 2) + ', 1fr)') : undefined,
313 gap: isHorizontal ? (a.gap + 'px ' + a.columnGap + 'px') : (a.gap + 'px'),
314 columnGap: isGrid ? (a.columnGap + 'px') : undefined,
315 '--bkbg-sl-gap': a.gap + 'px',
316 '--bkbg-sl-col-gap': a.columnGap + 'px',
317 '--bkbg-sl-columns': (a.listStyle === 'grid-custom') ? a.columns : (a.listStyle === 'grid' ? 2 : 1),
318 '--bkbg-sl-icon-size': a.iconSize + 'px',
319 '--bkbg-sl-icon-color': a.iconColor,
320 '--bkbg-sl-icon-bg': a.iconBgColor,
321 '--bkbg-sl-icon-bg-size': a.iconBgSize + 'px',
322 '--bkbg-sl-icon-radius': a.iconBgRadius + '%',
323 '--bkbg-sl-item-padding': a.itemPaddingTop + 'px ' + a.itemPaddingRight + 'px ' + a.itemPaddingBottom + 'px ' + a.itemPaddingLeft + 'px',
324 '--bkbg-sl-item-bg': a.itemBg,
325 '--bkbg-sl-item-bg-hover': a.itemBgHover || a.itemBg,
326 '--bkbg-sl-item-brd-w': a.itemBorderWidth + 'px',
327 '--bkbg-sl-item-brd-color': a.itemBorderColor,
328 '--bkbg-sl-item-brd-style': a.itemBorderStyle,
329 '--bkbg-sl-item-radius': a.itemBorderRadius + 'px',
330 '--bkbg-sl-divider-color': a.dividerColor,
331 '--bkbg-sl-divider-w': a.dividerWidth + 'px',
332 '--bkbg-sl-font-size': a.fontSize + 'px',
333 '--bkbg-sl-font-weight': a.fontWeight,
334 '--bkbg-sl-line-height': a.lineHeight,
335 '--bkbg-sl-text-color': a.textColor,
336 '--bkbg-sl-desc-size': a.descFontSize + 'px',
337 '--bkbg-sl-desc-weight': a.descFontWeight,
338 '--bkbg-sl-desc-color': a.descColor
339 };
340
341 // ── Open media library for image icon ─────────────────────────────────
342 function openMediaLibrary(onSelect) {
343 var frame = wp.media({
344 title: __('Select Icon Image', 'blockenberg'),
345 button: { text: __('Use as Icon', 'blockenberg') },
346 multiple: false,
347 library: { type: 'image' }
348 });
349 frame.on('select', function () {
350 var attachment = frame.state().get('selection').first().toJSON();
351 onSelect(attachment.url);
352 });
353 frame.open();
354 }
355
356 // ── Dashicon picker popover ────────────────────────────────────────────
357 function renderDashiconPicker(onPick) {
358 return el(Popover, {
359 position: 'bottom left',
360 className: 'bkbg-sl-icon-popover',
361 onClose: function () {
362 setOpenIconPicker(null);
363 setIconSearch('');
364 }
365 },
366 el('div', {
367 className: 'bkbg-sc-icon-picker-modal',
368 onMouseDown: function (e) { e.stopPropagation(); },
369 onClick: function (e) { e.stopPropagation(); }
370 },
371 el('div', { className: 'bkbg-sc-icon-picker-header' },
372 el('h3', {}, __('Choose Dashicon', 'blockenberg')),
373 el(Button, {
374 icon: 'no-alt',
375 onClick: function () { setOpenIconPicker(null); setIconSearch(''); },
376 className: 'bkbg-sc-icon-picker-close'
377 })
378 ),
379 el(TextControl, {
380 placeholder: __('Search icons…', 'blockenberg'),
381 value: iconSearch,
382 onChange: setIconSearch,
383 className: 'bkbg-sc-icon-search'
384 }),
385 el('div', { className: 'bkbg-sc-dashicons-grid' },
386 filteredDashicons.length === 0
387 ? el('p', { className: 'bkbg-sc-no-icons' }, __('No icons found', 'blockenberg'))
388 : filteredDashicons.map(function (icon) {
389 var isActive = (typeof onPick === 'function' ? false : a.customDashicon === icon);
390 return el('button', {
391 key: icon,
392 type: 'button',
393 className: 'bkbg-sc-dashicon-btn' + (isActive ? ' is-active' : ''),
394 onClick: function () {
395 if (typeof onPick === 'function') { onPick(icon); }
396 else { setAttributes({ customDashicon: icon }); }
397 setOpenIconPicker(null);
398 setIconSearch('');
399 },
400 title: icon
401 },
402 el('span', { className: 'dashicons dashicons-' + icon })
403 );
404 })
405 )
406 )
407 );
408 }
409
410 // ── Inspector Controls ─────────────────────────────────────────────────
411 var inspector = el(InspectorControls, {},
412
413 // ── Layout ────────────────────────────────────────────────────────
414 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
415 el(SelectControl, {
416 label: __('List Layout', 'blockenberg'),
417 value: a.listStyle,
418 options: listStyleOptions,
419 onChange: function (v) { setAttributes({ listStyle: v }); }
420 }),
421 (a.listStyle === 'grid-custom') && el(RangeControl, {
422 label: __('Columns', 'blockenberg'),
423 value: a.columns,
424 min: 2,
425 max: 6,
426 onChange: function (v) { setAttributes({ columns: v }); }
427 }),
428 el(SelectControl, {
429 label: __('Text Alignment', 'blockenberg'),
430 value: a.alignment,
431 options: alignmentOptions,
432 onChange: function (v) { setAttributes({ alignment: v }); }
433 }),
434 el(RangeControl, {
435 label: __('Row Gap', 'blockenberg'),
436 value: a.gap,
437 min: 0,
438 max: 60,
439 onChange: function (v) { setAttributes({ gap: v }); }
440 }),
441 (a.listStyle === 'grid' || a.listStyle === 'grid-custom' || a.listStyle === 'horizontal') && el(RangeControl, {
442 label: __('Column Gap', 'blockenberg'),
443 value: a.columnGap,
444 min: 0,
445 max: 80,
446 onChange: function (v) { setAttributes({ columnGap: v }); }
447 })
448 ),
449
450 // ── Icon ──────────────────────────────────────────────────────────
451 el(PanelBody, { title: __('Icon', 'blockenberg'), initialOpen: false },
452 el(SelectControl, {
453 label: __('Icon Type', 'blockenberg'),
454 value: a.iconType,
455 options: iconTypeOptions,
456 onChange: function (v) { setAttributes({ iconType: v }); }
457 }),
458
459 // Custom char input
460 (a.iconType === 'custom-char') && el(TextControl, {
461 label: __('Custom Character', 'blockenberg'),
462 value: a.customChar,
463 onChange: function (v) { setAttributes({ customChar: v }); },
464 help: __('Enter any character or emoji, e.g. → ✦ ◆ 🔥', 'blockenberg')
465 }),
466
467 // Custom image upload
468 (a.iconType === 'custom-image') && el('div', { style: { marginBottom: '16px' } },
469 el('p', { className: 'components-base-control__label' }, __('Image Icon', 'blockenberg')),
470 a.customImageUrl && el('div', { style: { marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '8px' } },
471 el('img', { src: a.customImageUrl, alt: '', style: { width: '40px', height: '40px', objectFit: 'contain', border: '1px solid #e2e8f0', borderRadius: '4px', padding: '2px' } }),
472 el(Button, {
473 isSmall: true,
474 isDestructive: true,
475 variant: 'secondary',
476 onClick: function () { setAttributes({ customImageUrl: '' }); }
477 }, __('Remove', 'blockenberg'))
478 ),
479 el(Button, {
480 variant: 'secondary',
481 onClick: function () { openMediaLibrary(function (url) { setAttributes({ customImageUrl: url }); }); }
482 },
483 el('span', { className: 'dashicons dashicons-upload', style: { marginRight: '4px' } }),
484 a.customImageUrl ? __('Replace Image', 'blockenberg') : __('Select from Media Library', 'blockenberg')
485 )
486 ),
487
488 // Dashicon picker button
489 (a.iconType === 'dashicon') && el('div', { style: { marginBottom: '16px' } },
490 el('p', { className: 'components-base-control__label' }, __('Dashicon', 'blockenberg')),
491 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
492 el('span', { className: 'dashicons dashicons-' + a.customDashicon, style: { fontSize: '24px', width: '24px', height: '24px' } }),
493 el(Button, {
494 variant: 'secondary',
495 isSmall: true,
496 onClick: function () { setOpenIconPicker(openIconPicker ? null : 'global'); }
497 }, __('Change Icon', 'blockenberg')),
498 openIconPicker === 'global' && renderDashiconPicker()
499 )
500 ),
501
502 el(SelectControl, {
503 label: __('Icon Shape', 'blockenberg'),
504 value: a.iconShape,
505 options: iconShapeOptions,
506 onChange: function (v) { setAttributes({ iconShape: v }); }
507 }),
508 el(SelectControl, {
509 label: __('Icon Position', 'blockenberg'),
510 value: a.iconPosition,
511 options: iconPositionOptions,
512 onChange: function (v) { setAttributes({ iconPosition: v }); }
513 }),
514 el(ToggleControl, {
515 label: __('Align icon to top', 'blockenberg'),
516 help: __('By default icon aligns to the first line of text.', 'blockenberg'),
517 checked: a.iconAlignTop,
518 __nextHasNoMarginBottom: true,
519 onChange: function (v) { setAttributes({ iconAlignTop: v }); }
520 }),
521 el(RangeControl, {
522 label: __('Icon Size', 'blockenberg'),
523 value: a.iconSize,
524 min: 10,
525 max: 60,
526 onChange: function (v) { setAttributes({ iconSize: v }); }
527 }),
528 (a.iconShape !== 'none') && el(RangeControl, {
529 label: __('Icon Container Size', 'blockenberg'),
530 value: a.iconBgSize,
531 min: 20,
532 max: 80,
533 onChange: function (v) { setAttributes({ iconBgSize: v }); }
534 }),
535 (a.iconShape !== 'none' && (a.iconShape === 'rounded-filled' || a.iconShape === 'rounded-outline')) && el(RangeControl, {
536 label: __('Container Radius', 'blockenberg'),
537 value: a.iconBgRadius,
538 min: 0,
539 max: 50,
540 onChange: function (v) { setAttributes({ iconBgRadius: v }); }
541 })
542 ),
543
544 // ── Item Style ────────────────────────────────────────────────────
545 el(PanelBody, { title: __('Item Style', 'blockenberg'), initialOpen: false },
546 el('p', { className: 'components-base-control__label', style: { marginBottom: '8px' } }, __('Item Padding', 'blockenberg')),
547 el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '16px' } },
548 el(RangeControl, { label: __('Top', 'blockenberg'), value: a.itemPaddingTop, min: 0, max: 40, onChange: function (v) { setAttributes({ itemPaddingTop: v }); } }),
549 el(RangeControl, { label: __('Right', 'blockenberg'), value: a.itemPaddingRight, min: 0, max: 40, onChange: function (v) { setAttributes({ itemPaddingRight: v }); } }),
550 el(RangeControl, { label: __('Bottom', 'blockenberg'), value: a.itemPaddingBottom, min: 0, max: 40, onChange: function (v) { setAttributes({ itemPaddingBottom: v }); } }),
551 el(RangeControl, { label: __('Left', 'blockenberg'), value: a.itemPaddingLeft, min: 0, max: 40, onChange: function (v) { setAttributes({ itemPaddingLeft: v }); } })
552 ),
553 el(RangeControl, {
554 label: __('Border Width', 'blockenberg'),
555 value: a.itemBorderWidth,
556 min: 0,
557 max: 6,
558 onChange: function (v) { setAttributes({ itemBorderWidth: v }); }
559 }),
560 (a.itemBorderWidth > 0) && el(SelectControl, {
561 label: __('Border Style', 'blockenberg'),
562 value: a.itemBorderStyle,
563 options: borderStyleOptions,
564 onChange: function (v) { setAttributes({ itemBorderStyle: v }); }
565 }),
566 el(RangeControl, {
567 label: __('Border Radius', 'blockenberg'),
568 value: a.itemBorderRadius,
569 min: 0,
570 max: 32,
571 onChange: function (v) { setAttributes({ itemBorderRadius: v }); }
572 }),
573 el(ToggleControl, {
574 label: __('Item Shadow', 'blockenberg'),
575 checked: a.itemShadow,
576 __nextHasNoMarginBottom: true,
577 onChange: function (v) { setAttributes({ itemShadow: v }); }
578 }),
579 el(ToggleControl, {
580 label: __('Divider between items', 'blockenberg'),
581 checked: a.dividerEnabled,
582 __nextHasNoMarginBottom: true,
583 onChange: function (v) { setAttributes({ dividerEnabled: v }); }
584 }),
585 a.dividerEnabled && el(RangeControl, {
586 label: __('Divider Height', 'blockenberg'),
587 value: a.dividerWidth,
588 min: 1,
589 max: 4,
590 onChange: function (v) { setAttributes({ dividerWidth: v }); }
591 })
592 ),
593
594 // ── Typography ────────────────────────────────────────────────────
595 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
596 el('p', { className: 'components-base-control__label' }, __('Main Text', 'blockenberg')),
597 el(RangeControl, {
598 label: __('Font Size', 'blockenberg'),
599 value: a.fontSize,
600 min: 12,
601 max: 36,
602 onChange: function (v) { setAttributes({ fontSize: v }); }
603 }),
604 el(SelectControl, {
605 label: __('Font Weight', 'blockenberg'),
606 value: a.fontWeight,
607 options: fontWeightOptions,
608 onChange: function (v) { setAttributes({ fontWeight: parseInt(v, 10) }); }
609 }),
610 el(RangeControl, {
611 label: __('Line Height', 'blockenberg'),
612 value: a.lineHeight,
613 min: 1,
614 max: 3,
615 step: 0.1,
616 onChange: function (v) { setAttributes({ lineHeight: v }); }
617 }),
618 el(ToggleControl, {
619 label: __('Show description line', 'blockenberg'),
620 help: __('Add a secondary text beneath each item.', 'blockenberg'),
621 checked: a.showDescription,
622 __nextHasNoMarginBottom: true,
623 onChange: function (v) { setAttributes({ showDescription: v }); }
624 }),
625 a.showDescription && el(Fragment, {},
626 el('p', { className: 'components-base-control__label', style: { marginTop: '12px' } }, __('Description Text', 'blockenberg')),
627 el(RangeControl, {
628 label: __('Font Size', 'blockenberg'),
629 value: a.descFontSize,
630 min: 10,
631 max: 24,
632 onChange: function (v) { setAttributes({ descFontSize: v }); }
633 }),
634 el(SelectControl, {
635 label: __('Font Weight', 'blockenberg'),
636 value: a.descFontWeight,
637 options: fontWeightOptions,
638 onChange: function (v) { setAttributes({ descFontWeight: parseInt(v, 10) }); }
639 })
640 )
641 ),
642
643 // ── Colors ────────────────────────────────────────────────────────
644 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
645 el(PanelColorSettings, {
646 title: __('Icon', 'blockenberg'),
647 initialOpen: true,
648 colorSettings: [
649 {
650 value: a.iconColor,
651 onChange: function (v) { setAttributes({ iconColor: v || '#3b82f6' }); },
652 label: __('Icon Color', 'blockenberg')
653 },
654 (a.iconShape !== 'none') ? {
655 value: a.iconBgColor,
656 onChange: function (v) { setAttributes({ iconBgColor: v || '#dbeafe' }); },
657 label: __('Icon Background', 'blockenberg')
658 } : null
659 ].filter(Boolean)
660 }),
661 el(PanelColorSettings, {
662 title: __('Text', 'blockenberg'),
663 initialOpen: false,
664 colorSettings: [
665 {
666 value: a.textColor,
667 onChange: function (v) { setAttributes({ textColor: v || '#374151' }); },
668 label: __('Text Color', 'blockenberg')
669 },
670 a.showDescription ? {
671 value: a.descColor,
672 onChange: function (v) { setAttributes({ descColor: v || '#6b7280' }); },
673 label: __('Description Color', 'blockenberg')
674 } : null
675 ].filter(Boolean)
676 }),
677 el(PanelColorSettings, {
678 title: __('Item', 'blockenberg'),
679 initialOpen: false,
680 colorSettings: [
681 {
682 value: a.itemBg,
683 onChange: function (v) { setAttributes({ itemBg: v || 'transparent' }); },
684 label: __('Item Background', 'blockenberg')
685 },
686 (a.itemBorderWidth > 0) ? {
687 value: a.itemBorderColor,
688 onChange: function (v) { setAttributes({ itemBorderColor: v || '#e2e8f0' }); },
689 label: __('Item Border Color', 'blockenberg')
690 } : null,
691 a.dividerEnabled ? {
692 value: a.dividerColor,
693 onChange: function (v) { setAttributes({ dividerColor: v || '#e2e8f0' }); },
694 label: __('Divider Color', 'blockenberg')
695 } : null
696 ].filter(Boolean)
697 })
698 ),
699
700 // ── Per-item settings ─────────────────────────────────────────────
701 el(PanelBody, { title: __('Per-item Options', 'blockenberg'), initialOpen: false },
702 el(ToggleControl, {
703 label: __('Enable per-item icon color', 'blockenberg'),
704 help: __('Set a custom icon color per item in the canvas.', 'blockenberg'),
705 checked: a.perItemColor,
706 __nextHasNoMarginBottom: true,
707 onChange: function (v) { setAttributes({ perItemColor: v }); }
708 }),
709 el(ToggleControl, {
710 label: __('Enable per-item icon override', 'blockenberg'),
711 help: __('Set a custom icon type / image for individual items.', 'blockenberg'),
712 checked: a.perItemIcon,
713 __nextHasNoMarginBottom: true,
714 onChange: function (v) { setAttributes({ perItemIcon: v }); }
715 })
716 )
717 );
718
719 // ── Block Controls (Toolbar) ───────────────────────────────────────────
720 var blockControls = el(BlockControls, {},
721 el(ToolbarGroup, {},
722 el(ToolbarButton, {
723 icon: 'editor-alignleft',
724 title: __('Align Left', 'blockenberg'),
725 isActive: a.alignment === 'left',
726 onClick: function () { setAttributes({ alignment: 'left' }); }
727 }),
728 el(ToolbarButton, {
729 icon: 'editor-aligncenter',
730 title: __('Align Center', 'blockenberg'),
731 isActive: a.alignment === 'center',
732 onClick: function () { setAttributes({ alignment: 'center' }); }
733 }),
734 el(ToolbarButton, {
735 icon: 'editor-alignright',
736 title: __('Align Right', 'blockenberg'),
737 isActive: a.alignment === 'right',
738 onClick: function () { setAttributes({ alignment: 'right' }); }
739 })
740 )
741 );
742
743 // ── Render items ──────────────────────────────────────────────────────
744 var itemsEl = a.items.map(function (item, index) {
745 // Toolbar only when block is selected AND this item is focused
746 var isActive2 = isSelected && activeItem === index;
747 var itemColor = (a.perItemColor && item.customColor) ? item.customColor : null;
748 var iconEl = buildIconEl(a, item, index);
749
750 return el('div', {
751 key: 'item-' + index,
752 className: 'bkbg-sl-item' + (isActive2 ? ' is-focused' : '') + (a.itemShadow ? ' has-shadow' : ''),
753 onClick: function () { setActiveItem(index); }
754 },
755 // ── Item toolbar ──────────────────────────────────────────
756 isActive2 && el('div', { className: 'bkbg-sl-item-toolbar' },
757 el(Button, {
758 icon: 'arrow-up-alt2',
759 label: __('Move Up', 'blockenberg'),
760 isSmall: true,
761 disabled: index === 0,
762 onClick: function (e) { e.stopPropagation(); moveItem(index, -1); }
763 }),
764 el(Button, {
765 icon: 'arrow-down-alt2',
766 label: __('Move Down', 'blockenberg'),
767 isSmall: true,
768 disabled: index === a.items.length - 1,
769 onClick: function (e) { e.stopPropagation(); moveItem(index, 1); }
770 }),
771 el(Button, {
772 icon: 'admin-page',
773 label: __('Duplicate', 'blockenberg'),
774 isSmall: true,
775 onClick: function (e) { e.stopPropagation(); duplicateItem(index); }
776 }),
777 // Per-item color picker
778 a.perItemColor && el('div', { style: { position: 'relative', display: 'inline-block' } },
779 el(Button, {
780 isSmall: true,
781 onClick: function (e) {
782 e.stopPropagation();
783 setColorPicker(colorPicker === index ? null : index);
784 },
785 style: { display: 'flex', alignItems: 'center', gap: '4px' }
786 },
787 el('span', {
788 style: {
789 display: 'inline-block',
790 width: '14px',
791 height: '14px',
792 borderRadius: '50%',
793 background: item.customColor || a.iconColor,
794 border: '1px solid rgba(0,0,0,.2)'
795 }
796 }),
797 __('Color', 'blockenberg')
798 ),
799 colorPicker === index && el(Popover, {
800 position: 'bottom center',
801 onClose: function () { setColorPicker(null); }
802 },
803 el('div', {
804 style: { padding: '8px' },
805 onMouseDown: function (e) { e.stopPropagation(); }
806 },
807 el(ColorPicker, {
808 color: item.customColor || a.iconColor,
809 onChange: function (v) { updateItem(index, 'customColor', v); }
810 }),
811 item.customColor && el(Button, {
812 isSmall: true,
813 isDestructive: true,
814 onClick: function () { updateItem(index, 'customColor', ''); }
815 }, __('Reset', 'blockenberg'))
816 )
817 )
818 ),
819 el(Button, {
820 icon: 'trash',
821 label: __('Remove Item', 'blockenberg'),
822 isSmall: true,
823 isDestructive: true,
824 disabled: a.items.length <= 1,
825 onClick: function (e) { e.stopPropagation(); removeItem(index); }
826 }),
827
828 // Per-item icon override picker – always available
829 el('div', { style: { position: 'relative', display: 'inline-block' } },
830 el(Button, {
831 isSmall: true,
832 onClick: function (e) {
833 e.stopPropagation();
834 setOpenItemIconPicker(openItemIconPicker === index ? null : index);
835 },
836 style: { display: 'flex', alignItems: 'center', gap: '4px' }
837 },
838 el('span', { className: 'dashicons dashicons-insert', style: { fontSize: '16px', width: '16px', height: '16px' } }),
839 __('Icon', 'blockenberg')
840 ),
841 openItemIconPicker === index && el(Popover, {
842 position: 'bottom center',
843 onClose: function () { setOpenItemIconPicker(null); }
844 },
845 el('div', {
846 style: { padding: '12px', minWidth: '200px' },
847 onMouseDown: function (e) { e.stopPropagation(); }
848 },
849 el('p', { style: { margin: '0 0 8px', fontWeight: 600, fontSize: '12px', textTransform: 'uppercase', color: '#888' } }, __('Per-item Icon', 'blockenberg')),
850 el(SelectControl, {
851 label: __('Icon Type', 'blockenberg'),
852 value: item.itemIconType || '',
853 options: itemIconTypeOptions,
854 onChange: function (v) { updateItem(index, 'itemIconType', v); }
855 }),
856 (item.itemIconType === 'custom-char') && el(TextControl, {
857 label: __('Character', 'blockenberg'),
858 value: item.itemIconChar || '',
859 onChange: function (v) { updateItem(index, 'itemIconChar', v); }
860 }),
861 (item.itemIconType === 'dashicon') && el('div', {},
862 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } },
863 el('span', { className: 'dashicons dashicons-' + (item.itemIconDashicon || a.customDashicon), style: { fontSize: '20px', width: '20px', height: '20px' } }),
864 el(Button, {
865 variant: 'secondary',
866 isSmall: true,
867 onClick: function () { setOpenIconPicker('item-' + index); }
868 }, __('Pick Dashicon', 'blockenberg'))
869 ),
870 openIconPicker === ('item-' + index) && renderDashiconPicker(function (icon) {
871 updateItem(index, 'itemIconDashicon', icon);
872 })
873 ),
874 (item.itemIconType === 'custom-image') && el('div', {},
875 item.itemIconUrl && el('div', { style: { marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' } },
876 el('img', { src: item.itemIconUrl, alt: '', style: { width: '32px', height: '32px', objectFit: 'contain', border: '1px solid #e2e8f0', borderRadius: '4px' } }),
877 el(Button, { isSmall: true, isDestructive: true, onClick: function () { updateItem(index, 'itemIconUrl', ''); } }, __('Remove', 'blockenberg'))
878 ),
879 el(Button, {
880 variant: 'secondary',
881 isSmall: true,
882 onClick: function () {
883 openMediaLibrary(function (url) { updateItem(index, 'itemIconUrl', url); });
884 }
885 }, item.itemIconUrl ? __('Replace Image', 'blockenberg') : __('Select Image', 'blockenberg'))
886 )
887 )
888 )
889 )
890 ),
891
892 // ── Icon (left) ───────────────────────────────────────────
893 (a.iconPosition === 'left') && iconEl && el('div', {
894 className: 'bkbg-sl-icon-wrap',
895 style: itemColor ? { color: itemColor } : {}
896 }, iconEl),
897
898 // ── Text area ─────────────────────────────────────────────
899 el('div', { className: 'bkbg-sl-content' },
900 el(RichText, {
901 tagName: 'div',
902 className: 'bkbg-sl-text',
903 value: item.text,
904 onChange: function (v) { updateItem(index, 'text', v); },
905 placeholder: __('List item text…', 'blockenberg'),
906 allowedFormats: ['core/bold', 'core/italic', 'core/strikethrough', 'core/link', 'core/underline', 'core/text-color']
907 }),
908 a.showDescription && el(RichText, {
909 tagName: 'div',
910 className: 'bkbg-sl-desc',
911 value: item.description,
912 onChange: function (v) { updateItem(index, 'description', v); },
913 placeholder: __('Optional description…', 'blockenberg'),
914 allowedFormats: ['core/bold', 'core/italic', 'core/link']
915 })
916 ),
917
918 // ── Icon (right) ──────────────────────────────────────────
919 (a.iconPosition === 'right') && iconEl && el('div', {
920 className: 'bkbg-sl-icon-wrap',
921 style: itemColor ? { color: itemColor } : {}
922 }, iconEl)
923 );
924 });
925
926 var blockProps = useBlockProps({
927 className: 'bkbg-editor-wrap',
928 'data-block-label': 'Styled List'
929 });
930
931 return el('div', blockProps,
932 inspector,
933 blockControls,
934 el('ul', Object.assign({
935 className: 'bkbg-sl-wrap',
936 style: wrapStyle
937 }, wrapDataAttrs),
938 itemsEl.map(function (item, i) {
939 return el('li', { key: 'li-' + i, className: 'bkbg-sl-li' }, item);
940 })
941 ),
942 el('div', { className: 'bkbg-editor-actions' },
943 el(Button, {
944 variant: 'secondary',
945 icon: 'plus-alt2',
946 onClick: addItem
947 }, __('Add Item', 'blockenberg'))
948 )
949 );
950 },
951
952 // ── Save ─────────────────────────────────────────────────────────────────
953 save: function (props) {
954 var a = props.attributes;
955 var RichTextContent = wp.blockEditor.RichText.Content;
956
957 var saveIsHorizontal = a.listStyle === 'horizontal';
958 var saveIsGrid = a.listStyle === 'grid' || a.listStyle === 'grid-custom';
959
960 var wrapStyle = {
961 display: saveIsGrid ? 'grid' : 'flex',
962 flexDirection: saveIsHorizontal ? 'row' : (saveIsGrid ? undefined : 'column'),
963 flexWrap: saveIsHorizontal ? 'wrap' : undefined,
964 gridTemplateColumns: saveIsGrid ? ('repeat(' + ((a.listStyle === 'grid-custom') ? a.columns : 2) + ', 1fr)') : undefined,
965 gap: saveIsHorizontal ? (a.gap + 'px ' + a.columnGap + 'px') : (a.gap + 'px'),
966 '--bkbg-sl-gap': a.gap + 'px',
967 '--bkbg-sl-col-gap': a.columnGap + 'px',
968 '--bkbg-sl-columns': (a.listStyle === 'grid-custom') ? a.columns : (a.listStyle === 'grid' ? 2 : 1),
969 '--bkbg-sl-icon-size': a.iconSize + 'px',
970 '--bkbg-sl-icon-color': a.iconColor,
971 '--bkbg-sl-icon-bg': a.iconBgColor,
972 '--bkbg-sl-icon-bg-size': a.iconBgSize + 'px',
973 '--bkbg-sl-icon-radius': a.iconBgRadius + '%',
974 '--bkbg-sl-item-padding': a.itemPaddingTop + 'px ' + a.itemPaddingRight + 'px ' + a.itemPaddingBottom + 'px ' + a.itemPaddingLeft + 'px',
975 '--bkbg-sl-item-bg': a.itemBg,
976 '--bkbg-sl-item-bg-hover': a.itemBgHover || a.itemBg,
977 '--bkbg-sl-item-brd-w': a.itemBorderWidth + 'px',
978 '--bkbg-sl-item-brd-color': a.itemBorderColor,
979 '--bkbg-sl-item-brd-style': a.itemBorderStyle,
980 '--bkbg-sl-item-radius': a.itemBorderRadius + 'px',
981 '--bkbg-sl-divider-color': a.dividerColor,
982 '--bkbg-sl-divider-w': a.dividerWidth + 'px',
983 '--bkbg-sl-font-size': a.fontSize + 'px',
984 '--bkbg-sl-font-weight': a.fontWeight,
985 '--bkbg-sl-line-height': a.lineHeight,
986 '--bkbg-sl-text-color': a.textColor,
987 '--bkbg-sl-desc-size': a.descFontSize + 'px',
988 '--bkbg-sl-desc-weight': a.descFontWeight,
989 '--bkbg-sl-desc-color': a.descColor
990 };
991
992 // Build SVG icons inline for save (no React)
993 var svgPaths = {
994 check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
995 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',
996 arrow: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z',
997 'arrow-right': 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z',
998 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',
999 dash: 'M4 11h16v2H4z',
1000 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',
1001 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',
1002 bolt: 'M7 2v11h3v9l7-12h-4l4-8z'
1003 };
1004
1005 function buildSaveIconEl(item, index) {
1006 // Resolve per-item override (no flag required)
1007 var hasPerItem = item && item.itemIconType;
1008 var type = hasPerItem ? item.itemIconType : a.iconType;
1009 var customChar = hasPerItem ? (item.itemIconChar || a.customChar) : a.customChar;
1010 var dashiconName = hasPerItem ? (item.itemIconDashicon || a.customDashicon) : a.customDashicon;
1011 var imageUrl = hasPerItem ? (item.itemIconUrl || a.customImageUrl) : a.customImageUrl;
1012
1013 if (type === 'none') return null;
1014 var color = (a.perItemColor && item.customColor) ? item.customColor : null;
1015 var iconStyle = (color && type !== 'custom-image') ? { color: color } : {};
1016
1017 var inner;
1018 if (type === 'custom-image') {
1019 if (!imageUrl) return null;
1020 inner = el('img', {
1021 src: imageUrl,
1022 alt: '',
1023 className: 'bkbg-sl-img-icon',
1024 style: { width: '100%', height: '100%', objectFit: 'contain', display: 'block' }
1025 });
1026 } else if (type === 'number') {
1027 inner = el('span', { className: 'bkbg-sl-num' }, (index + 1) + '');
1028 } else if (type === 'custom-char') {
1029 inner = el('span', { className: 'bkbg-sl-char' }, customChar || '');
1030 } else if (type === 'dashicon') {
1031 inner = el('span', { className: 'dashicons dashicons-' + dashiconName });
1032 } else if (type === 'disc') {
1033 inner = el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg', 'aria-hidden': 'true' },
1034 el('circle', { cx: '12', cy: '12', r: '6' })
1035 );
1036 } else if (type === 'square') {
1037 inner = el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg', 'aria-hidden': 'true' },
1038 el('rect', { x: '6', y: '6', width: '12', height: '12', rx: '2' })
1039 );
1040 } else if (svgPaths[type]) {
1041 inner = el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg', 'aria-hidden': 'true' },
1042 el('path', { d: svgPaths[type] })
1043 );
1044 } else {
1045 inner = el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg', 'aria-hidden': 'true' },
1046 el('path', { d: svgPaths.check })
1047 );
1048 }
1049
1050 return el('span', {
1051 className: 'bkbg-sl-icon',
1052 'aria-hidden': 'true',
1053 style: iconStyle
1054 }, inner);
1055 }
1056
1057 var itemEls = a.items.map(function (item, index) {
1058 var iconEl = buildSaveIconEl(item, index);
1059 // Effective icon type (per-item or global)
1060 var effectiveType = item.itemIconType ? item.itemIconType : a.iconType;
1061 var showIcon = effectiveType !== 'none' && iconEl !== null;
1062 var itemColor = (a.perItemColor && item.customColor) ? item.customColor : null;
1063
1064 return el('li', {
1065 key: 'li-' + index,
1066 className: 'bkbg-sl-li'
1067 },
1068 el('div', {
1069 className: 'bkbg-sl-item' + (a.itemShadow ? ' has-shadow' : '')
1070 },
1071 (a.iconPosition === 'left') && showIcon && el('div', {
1072 className: 'bkbg-sl-icon-wrap',
1073 style: itemColor ? { color: itemColor } : {}
1074 }, iconEl),
1075 el('div', { className: 'bkbg-sl-content' },
1076 el(RichTextContent, {
1077 tagName: 'div',
1078 className: 'bkbg-sl-text',
1079 value: item.text
1080 }),
1081 (a.showDescription && item.description) && el(RichTextContent, {
1082 tagName: 'div',
1083 className: 'bkbg-sl-desc',
1084 value: item.description
1085 })
1086 ),
1087 (a.iconPosition === 'right') && showIcon && el('div', {
1088 className: 'bkbg-sl-icon-wrap',
1089 style: itemColor ? { color: itemColor } : {}
1090 }, iconEl)
1091 )
1092 );
1093 });
1094
1095 return el('ul', {
1096 className: 'bkbg-sl-wrap',
1097 style: wrapStyle,
1098 'data-list-style': a.listStyle,
1099 'data-icon-type': a.iconType,
1100 'data-icon-shape': a.iconShape,
1101 'data-icon-pos': a.iconPosition,
1102 'data-divider': a.dividerEnabled ? '1' : '0',
1103 'data-align': a.alignment,
1104 'data-shadow': a.itemShadow ? '1' : '0',
1105 'data-icon-align': a.iconAlignTop ? 'top' : 'center'
1106 }, itemEls);
1107 }
1108 });
1109 });
1110