From 58dccc86754deda247eb49310c266f6cba86d36a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 01 Jan 2026 23:40:35 +0000
Subject: [PATCH] Merge branch 'main' of https://github.com/jakevdwerf/jvb

---
 inc/meta/MetaForm.php |  194 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 184 insertions(+), 10 deletions(-)

diff --git a/inc/meta/MetaForm.php b/inc/meta/MetaForm.php
index 21895b9..aa537c0 100644
--- a/inc/meta/MetaForm.php
+++ b/inc/meta/MetaForm.php
@@ -202,6 +202,12 @@
 		$validationAttrs = $this->buildValidationAttributes($field);
 		$conditional = array_key_exists('condition', $field) ? $this->handleConditionalField($field) : '';
 
+		$customData = '';
+		if (array_key_exists('data', $field) && !empty($field['data'])) {
+			foreach ($field['data'] as $key => $v) {
+				$customData .= ($v === '') ? ' data-' . $key : ' data-' . $key . '="' . $v . '"';
+			}
+		}
 		?>
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
@@ -217,6 +223,7 @@
 					name="<?= esc_attr($data['name']) ?>"
 					value="<?= esc_attr($data['value']) ?>"
 					<?= $inputAttrs ?>
+					<?= $customData?>
 				>
 				<span class="validation-icon success" hidden aria-hidden="true">
                     <?= jvbIcon('check-circle') ?>
@@ -475,8 +482,7 @@
 				<select
 					id="<?= esc_attr($data['id']) ?>"
 					name="<?= esc_attr($data['name']) ?>"
-					<?= $inputAttrs ?>
-				>
+					<?= $inputAttrs ?>>
 					<?php foreach ($field['options'] as $key => $label) : ?>
 						<option value="<?= esc_attr($key) ?>" <?php selected($value, $key); ?>>
 							<?= esc_html($label) ?>
@@ -624,7 +630,6 @@
 
 	private function renderRepeaterField(string $name, mixed $value, array $field):void
 	{
-		error_log('Rendering Repeater Field!');
 		$values = is_array($value) ? $value : array();
 
 		$conditional = $this->handleConditionalField($field);
@@ -722,9 +727,11 @@
 	protected function renderGroupField(string $name, mixed $value, array $field): void
 	{
 		if (!array_key_exists('fields', $field) || empty($field['fields'])) {
+			error_log('No fields to render');
 			return;
 		}
 
+
 		$values = is_array($value) ? $value : [];
 		$original = $name;
 
@@ -744,14 +751,15 @@
 		$conditional = $this->handleConditionalField($field);
 		$validationAttrs = $this->buildValidationAttributes($field);
 		$describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : '';
-
+		$fieldset = (array_key_exists('wrap', $field) && $field['wrap'] === 'details') ? 'details' : 'fieldset';
+		$legend = (array_key_exists('wrap', $field) && $field['wrap'] === 'details') ? 'summary' : 'legend';
 		?>
-		<fieldset class="field group <?= esc_attr($name) ?>"
+		<<?= $fieldset?> class="field group <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 				  data-field="<?= esc_attr($name) ?>"
 			<?= $validationAttrs ?>
 			<?= $describedBy ?>>
-			<legend><?= esc_html($field['label']) ?></legend>
+			<<?=$legend?>><?= esc_html($field['label']) ?></<?=$legend?>>
 
 			<?php $this->renderHintAndDescription($field, $name); ?>
 
@@ -760,7 +768,7 @@
 			</div>
 
 			<span class="validation-message" hidden role="alert"></span>
-		</fieldset>
+		</<?= $fieldset?>>
 		<?php
 	}
 
@@ -810,7 +818,7 @@
 			//Processing Options
 			'max_size' => null, // Override default size limits
 			'convert' => 'webp', // Image conversion format
-			'quality' => 80, // Conversion quality
+			'quality' => 90, // Conversion quality
 			'create_thumbnails' => true,
 		];
 		$config = array_merge($defaultConfig, $field);
@@ -909,6 +917,7 @@
 					<?php endif; ?>
 					<div class="file-error"></div>
 				</div>
+				<?php jvbRenderProgressBar(); ?>
 			</div>
 
 
@@ -919,7 +928,7 @@
 						<div class="selection-controls">
 							<div class="selected">
 								<div class="field">
-									<input type="checkbox" id="select-all-uploads" name="select-all-uploads">
+									<input type="checkbox" id="select-all-uploads" data-select-all data-selects="item-grid" name="select-all-uploads">
 									<label for="select-all-uploads">
 										Select All
 									</label>
@@ -986,6 +995,29 @@
 		<?php
 	}
 
+	private function renderExistingAttachment(int $attachmentId, string $subtype): string
+	{
+		ob_start();
+
+		switch ($subtype) {
+			case 'image':
+				$this->renderImagePreview($attachmentId);
+				break;
+			case 'video':
+				$this->renderVideoPreview($attachmentId);
+				break;
+			case 'document':
+			case 'file':
+				$this->renderFilePreview($attachmentId);
+				break;
+			default:
+				$this->renderImagePreview($attachmentId);
+				break;
+		}
+
+		return ob_get_clean();
+	}
+
 	/**
 	 * Get max file size for subtype
 	 */
@@ -1072,7 +1104,7 @@
 					<?php jvbRenderProgressBar('',true) ?>
 					<input type="checkbox" class="upload-select" name="select-item" id="select-item<?=$addID?>">
 					<label for="select-item<?=$addID?>" aria-label="Select image">
-						<?= ($attachment) ? $attachment : '<img>
+						<?= ($attachment) ?: '<img>
 						<video></video>
 						<span></span>' ?>
 					</label>
@@ -1586,5 +1618,147 @@
 
 		return is_numeric($value) ? [$value] : [];
 	}
+	/**
+	 * Render tag list field - inline tag input interface
+	 */
+	protected function renderTagListField(string $name, mixed $value, array $field): void
+	{
+		$values = is_array($value) ? $value : [];
+		$conditional = $this->handleConditionalField($field);
+		$validationAttrs = $this->buildValidationAttributes($field);
 
+		if (array_key_exists('group', $field)) {
+			$name = $field['group'] . '::' . $name;
+		}
+
+		$describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : '';
+
+		// Tag display format - defaults to first field value
+		$tagFormat = $field['tag_format'] ?? 'first_field';
+		?>
+		<div class="field tag-list <?= esc_attr($name) ?>"
+			 data-field="<?= esc_attr($name) ?>"
+			 data-tag-format="<?= esc_attr($tagFormat) ?>"
+			<?= $describedBy ?>
+			<?= $conditional ?>
+			<?= $validationAttrs ?>>
+
+			<?php if (!empty($field['label'])): ?>
+				<h3><?= esc_html($field['label']) ?></h3>
+			<?php endif; ?>
+
+			<!-- Inline input row -->
+			<div class="tag-input-row">
+				<?php foreach ($field['fields'] as $subfield_name => $subfield_config): ?>
+					<?php
+					$subfield_config['label'] = $subfield_config['label'] ?? ucfirst($subfield_name);
+					$input_name = 'new_' . $subfield_name;
+
+					// Store required state but don't render it on the input
+					// This prevents form submission validation but allows JS validation
+
+					if (array_key_exists('required', $subfield_config)) {
+						$subfield_config['data']['required'] = true;
+						unset($subfield_config['required']); // Remove required for HTML rendering
+					}
+					$subfield_config['data']['ignore'] = true;
+
+					$this->render($input_name, '', $subfield_config, false, false);
+					?>
+				<?php endforeach; ?>
+
+				<button type="button" class="button add-tag-item">
+					<?= jvbIcon('plus') ?> <?= $field['add_label'] ?? 'Add' ?>
+				</button>
+			</div>
+
+			<!-- Tags display -->
+			<div class="tag-items">
+				<?php foreach ($values as $index => $item_data): ?>
+					<?php $this->renderTagItem($field['fields'], $item_data, $index, $name, $tagFormat); ?>
+				<?php endforeach; ?>
+			</div>
+
+			<!-- Template for new tags -->
+			<template class="tag-template">
+				<?php $this->renderTagItem($field['fields'], [], '', $name, $tagFormat); ?>
+			</template>
+
+			<?php if (!empty($field['hint'])): ?>
+				<?php $this->renderHint($field['hint']); ?>
+			<?php endif; ?>
+
+			<?php if (!empty($field['description'])): ?>
+				<?php $this->renderDescription($field['description'], $name); ?>
+			<?php endif; ?>
+		</div>
+		<?php
+	}
+
+	/**
+	 * Render individual tag item
+	 */
+	protected function renderTagItem(array $fields, array $data, int|string $index, string $base_name, string $format): void
+	{
+		$tag_text = $this->getTagDisplayText($fields, $data, $format);
+		?>
+		<div class="tag-item" data-index="<?= esc_attr($index) ?>">
+			<span class="tag-label"><?= esc_html($tag_text) ?></span>
+
+			<!-- Hidden inputs for data -->
+			<?php foreach ($fields as $field_name => $field_config): ?>
+				<?php
+				$value = $data[$field_name] ?? '';
+				$full_name = is_string($index) ? $field_name : "{$base_name}:{$index}:{$field_name}";
+				?>
+				<input type="hidden"
+					   name="<?= esc_attr($full_name) ?>"
+					   value="<?= esc_attr($value) ?>"
+					   data-field="<?= esc_attr($field_name) ?>" />
+			<?php endforeach; ?>
+
+			<button type="button" class="remove-tag" aria-label="Remove">
+				<?= jvbIcon('x') ?>
+			</button>
+		</div>
+		<?php
+	}
+
+	/**
+	 * Get tag display text based on format
+	 */
+	protected function getTagDisplayText(array $fields, array $data, string $format): string
+	{
+		if (empty($data)) {
+			return 'New Item';
+		}
+
+		switch ($format) {
+			case 'first_field':
+				// Use the first field's value
+				$first_key = array_key_first($fields);
+				return $data[$first_key] ?? 'New Item';
+
+			case 'all_fields':
+				// Show all field values separated by commas
+				$values = array_filter(array_values($data));
+				return implode(', ', $values) ?: 'New Item';
+
+			case 'custom':
+				// Custom format - would need callback
+				return 'New Item';
+
+			default:
+				// Format is a template string like "{name} ({email})"
+				if (strpos($format, '{') !== false) {
+					$text = $format;
+					foreach ($data as $key => $value) {
+						$text = str_replace('{' . $key . '}', $value, $text);
+					}
+					return $text;
+				}
+				// Use specific field name
+				return $data[$format] ?? 'New Item';
+		}
+	}
 }

--
Gitblit v1.10.0