HEX
Server: Apache
System: Linux webd003.cluster128.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
User: slyfwmm (169339)
PHP: 8.1.34
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/slyfwmm/adspray/wp-content/plugins/lazy-blocks/controls/repeater/repeater-control.js
/**
 * External dependencies.
 */
import classnames from 'classnames/dedupe';
import {
	DndContext,
	closestCenter,
	useSensor,
	useSensors,
} from '@dnd-kit/core';
import {
	SortableContext,
	verticalListSortingStrategy,
	useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

/**
 * WordPress dependencies.
 */
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import { Button, ToggleControl } from '@wordpress/components';
import { withInstanceId } from '@wordpress/compose';

/**
 * Internal dependencies
 */
import CustomPointerSensor from '../../assets/utils/dnd-kit-custom-pointer-sensor';

const RepeaterItem = function (props) {
	const {
		attributes,
		listeners,
		setNodeRef,
		transform,
		transition,
		isDragging,
		isSorting,
	} = useSortable({
		id: props.id,
	});

	const style = {
		transform: CSS.Translate.toString(transform),
		transition: isSorting ? transition : '',
	};

	return (
		<div
			className={classnames(
				'lzb-gutenberg-repeater-item',
				isDragging ? 'lzb-gutenberg-repeater-item-dragging' : ''
			)}
			ref={setNodeRef}
			style={style}
		>
			<div
				className={`lzb-gutenberg-repeater-btn${
					props.active ? ' lzb-gutenberg-repeater-btn-active' : ''
				}`}
				onClick={props.onToggle}
				onKeyDown={() => {}}
				role="button"
				tabIndex={0}
			>
				<Button
					className="lzb-gutenberg-repeater-btn-drag"
					role="button"
					{...attributes}
					{...listeners}
				>
					<svg
						width="18"
						height="18"
						xmlns="http://www.w3.org/2000/svg"
						viewBox="0 0 18 18"
						role="img"
						aria-hidden="true"
						focusable="false"
					>
						<path d="M5 4h2V2H5v2zm6-2v2h2V2h-2zm-6 8h2V8H5v2zm6 0h2V8h-2v2zm-6 6h2v-2H5v2zm6 0h2v-2h-2v2z" />
					</svg>
				</Button>
				<div
					className="lzb-gutenberg-repeater-btn-title"
					// eslint-disable-next-line react/no-danger
					dangerouslySetInnerHTML={{ __html: props.title }}
				/>
				<div className="lzb-gutenberg-repeater-btn-arrow">
					<svg
						width="24"
						height="24"
						viewBox="0 0 24 24"
						xmlns="http://www.w3.org/2000/svg"
						role="img"
						aria-hidden="true"
						focusable="false"
					>
						<path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z" />
					</svg>
				</div>
			</div>
			{!props.controlData.rows_min ||
			props.count > props.controlData.rows_min ? (
				// eslint-disable-next-line react/button-has-type
				<button
					className="lzb-gutenberg-repeater-btn-remove"
					onClick={props.onRemove}
				>
					<svg
						xmlns="http://www.w3.org/2000/svg"
						viewBox="-2 -2 24 24"
						width="24"
						height="24"
						role="img"
						aria-hidden="true"
						focusable="false"
					>
						<path d="M12 4h3c.6 0 1 .4 1 1v1H3V5c0-.6.5-1 1-1h3c.2-1.1 1.3-2 2.5-2s2.3.9 2.5 2zM8 4h3c-.2-.6-.9-1-1.5-1S8.2 3.4 8 4zM4 7h11l-.9 10.1c0 .5-.5.9-1 .9H5.9c-.5 0-.9-.4-1-.9L4 7z" />
					</svg>
				</button>
			) : null}
			{props.active ? props.renderContent() : ''}
		</div>
	);
};

function RepeaterControl(props) {
	const {
		count = 0,
		controlData,
		renderRow = () => {},
		addRow = () => {},
		removeRow = () => {},
		resortRow = () => {},
		getInnerControls = () => {},
	} = props;

	const sensors = useSensors(useSensor(CustomPointerSensor));

	let activeItemDefault = -1;

	if (controlData.rows_collapsible === 'false') {
		activeItemDefault = -2;
	} else if (controlData.rows_collapsed === 'false') {
		activeItemDefault = -2;
	}

	const [activeItem, setActiveItem] = useState(activeItemDefault);

	function getRowTitle(i) {
		const valObjectVariants = [
			'alt',
			'url',
			'title',
			'caption',
			'description',
			'id',
			'link',
		];
		let title = controlData.rows_label || __('Row {{#}}', 'lazy-blocks');

		// add row number.
		title = title.replace(/{{#}}/g, i + 1);

		const innerControls = getInnerControls(i);

		// add inner controls values.
		if (innerControls) {
			Object.keys(innerControls).forEach((k) => {
				const { data } = innerControls[k];

				let val = innerControls[k].val || '';

				// Prepare object value variants.
				if (typeof val === 'object') {
					valObjectVariants.forEach((tag) => {
						title = title.replace(
							new RegExp(`{{${data.name}.${tag}}}`, 'g'),
							val[tag] || ''
						);
					});
				}

				// Add support for image control tag displaying.
				if (data.type === 'image' && val.url) {
					val = `<img src="${val.url}" loading="lazy" />`;
				}

				// In case if value is object - display nothing.
				if (typeof val === 'object') {
					val = '';
				}

				title = title.replace(new RegExp(`{{${data.name}}}`, 'g'), val);
			});
		}

		return title;
	}

	// Mount.
	useEffect(() => {
		// add rows to meet Minimum requirements
		if (
			controlData.rows_min &&
			controlData.rows_min > 0 &&
			controlData.rows_min > count
		) {
			const needToAdd = controlData.rows_min - count;

			for (let i = 0; i < needToAdd; i += 1) {
				addRow();
			}
		}
	});

	const items = [];
	for (let i = 0; i < count; i += 1) {
		const active = activeItem === -2 || activeItem === i;

		items.push({
			id: i + 1,
			title: getRowTitle(i),
			active,
			count,
			controlData,
			onToggle: (e) => {
				e.preventDefault();
				e.stopPropagation();

				if (controlData.rows_collapsible === 'true') {
					setActiveItem(active ? -1 : i);
				}
			},
			onRemove: (e) => {
				e.preventDefault();
				e.stopPropagation();
				removeRow(i);
			},
			renderContent: () => (
				<div className="lzb-gutenberg-repeater-item-content">
					{renderRow(i)}
				</div>
			),
		});
	}

	return (
		<div className="lzb-gutenberg-repeater">
			<DndContext
				sensors={sensors}
				collisionDetection={closestCenter}
				onDragEnd={(event) => {
					const { active, over } = event;

					if (active.id !== over.id) {
						resortRow(active.id - 1, over.id - 1);

						if (activeItem > -1) {
							setActiveItem(over.id - 1);
						}
					}
				}}
			>
				{items.length ? (
					<div className="lzb-gutenberg-repeater-items">
						<SortableContext
							items={items}
							strategy={verticalListSortingStrategy}
						>
							{items.map((value) => (
								<RepeaterItem
									key={`lzb-constructor-controls-items-sortable-${value.id}`}
									{...value}
								/>
							))}
						</SortableContext>
					</div>
				) : null}
			</DndContext>
			<div className="lzb-gutenberg-repeater-options">
				<Button
					variant="secondary"
					size="small"
					disabled={
						controlData.rows_max && count >= controlData.rows_max
					}
					onClick={() => {
						addRow();

						// Open newly added row.
						if (activeItem !== -2) {
							setActiveItem(items.length);
						}
					}}
				>
					{controlData.rows_add_button_label ||
						__('+ Add Row', 'lazy-blocks')}
				</Button>
				{controlData.rows_collapsible === 'true' &&
				items.length &&
				items.length > 1 ? (
					<ToggleControl
						label={__('Toggle all', 'lazy-blocks')}
						checked={activeItem === -2}
						onChange={() => {
							setActiveItem(activeItem === -2 ? -1 : -2);
						}}
					/>
				) : (
					''
				)}
			</div>
		</div>
	);
}

export default withInstanceId(RepeaterControl);