feat: unified preview eye icon component, enhanced search & preview
- Unify filter group and filter value preview icons into shared .filter-chip-wrapper + .chip-preview-btn component pattern - Remove old .toggle-count.clickable inline eye icon approach - Add dropdown-level event handler for preview buttons (dropdown appended to body, needs separate delegation) - Enhanced EntitySearchEngine with improved product condition resolution and preview data - Add EntityPreviewHandler for richer preview popovers - Various SCSS improvements for chips, groups, and list-preview Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -799,6 +799,10 @@
|
||||
.entity-selector-trait.single-mode .target-block-container {
|
||||
display: block;
|
||||
}
|
||||
.target-conditions-trait.single-mode .entity-selector-tabs-row .target-block-tabs,
|
||||
.entity-selector-trait.single-mode .entity-selector-tabs-row .target-block-tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.target-conditions-trait .header-actions,
|
||||
.entity-selector-trait .header-actions {
|
||||
@@ -1749,6 +1753,19 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.target-conditions-trait .dropdown-item.is-combination,
|
||||
.entity-selector-trait .dropdown-item.is-combination {
|
||||
padding-left: 28px;
|
||||
}
|
||||
.target-conditions-trait .dropdown-item.is-combination .result-name,
|
||||
.entity-selector-trait .dropdown-item.is-combination .result-name {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.target-conditions-trait .dropdown-item.is-parent-product,
|
||||
.entity-selector-trait .dropdown-item.is-parent-product {
|
||||
background: #f8fafc;
|
||||
font-weight: 500;
|
||||
}
|
||||
.target-conditions-trait .no-results,
|
||||
.entity-selector-trait .no-results {
|
||||
display: flex;
|
||||
@@ -3041,46 +3058,9 @@ body > .target-search-dropdown .filter-group-toggle .toggle-name,
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.125rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count i,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count i {
|
||||
font-size: 10px;
|
||||
color: #25b9d7;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count.clickable,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count.clickable {
|
||||
cursor: pointer;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.2rem;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count.clickable:hover,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count.clickable:hover {
|
||||
background: rgba(37, 185, 215, 0.1);
|
||||
color: #25b9d7;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count.clickable:hover i,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count.clickable:hover i {
|
||||
color: #25b9d7;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count.clickable.popover-open,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count.clickable.popover-open {
|
||||
background: #25b9d7;
|
||||
color: #ffffff;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count.clickable.popover-open i,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count.clickable.popover-open i {
|
||||
color: #ffffff;
|
||||
}
|
||||
body > .target-search-dropdown .filter-group-toggle .toggle-count.clickable.loading i,
|
||||
.target-search-dropdown .filter-group-toggle .toggle-count.clickable.loading i {
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip, body > .target-search-dropdown .filter-attr-chip,
|
||||
body > .target-search-dropdown .filter-feat-chip,
|
||||
.target-search-dropdown .filter-chip,
|
||||
@@ -3201,6 +3181,81 @@ body > .target-search-dropdown .filter-chip.active .chip-count,
|
||||
.target-search-dropdown .active.filter-feat-chip .chip-count {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper,
|
||||
.target-search-dropdown .filter-chip-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
border-radius: 0.2rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .filter-chip,
|
||||
body > .target-search-dropdown .filter-chip-wrapper .filter-group-toggle,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-chip,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-attr-chip,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-feat-chip,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-group-toggle {
|
||||
border-radius: 0.2rem 0 0 0.2rem;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .chip-preview-btn,
|
||||
.target-search-dropdown .filter-chip-wrapper .chip-preview-btn {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .chip-preview-btn:focus,
|
||||
.target-search-dropdown .filter-chip-wrapper .chip-preview-btn:focus {
|
||||
outline: none;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .chip-preview-btn,
|
||||
.target-search-dropdown .filter-chip-wrapper .chip-preview-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 0.375rem;
|
||||
font-size: 10px;
|
||||
color: #6c757d;
|
||||
background: #f1f5f9;
|
||||
border-left: 1px solid #dee2e6;
|
||||
border-radius: 0 0.2rem 0.2rem 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .chip-preview-btn:hover,
|
||||
.target-search-dropdown .filter-chip-wrapper .chip-preview-btn:hover {
|
||||
background: rgba(37, 185, 215, 0.1);
|
||||
color: #25b9d7;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .chip-preview-btn.popover-open,
|
||||
.target-search-dropdown .filter-chip-wrapper .chip-preview-btn.popover-open {
|
||||
background: #25b9d7;
|
||||
color: #ffffff;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .chip-preview-btn.loading i,
|
||||
.target-search-dropdown .filter-chip-wrapper .chip-preview-btn.loading i {
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .filter-chip:last-child,
|
||||
body > .target-search-dropdown .filter-chip-wrapper .filter-group-toggle:last-child,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-chip:last-child,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-attr-chip:last-child,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-feat-chip:last-child,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-group-toggle:last-child {
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .filter-group-toggle.active + .chip-preview-btn,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-group-toggle.active + .chip-preview-btn {
|
||||
border-left-color: #25b9d7;
|
||||
background: rgba(37, 185, 215, 0.05);
|
||||
}
|
||||
body > .target-search-dropdown .filter-chip-wrapper .filter-group-toggle.has-selection + .chip-preview-btn,
|
||||
.target-search-dropdown .filter-chip-wrapper .filter-group-toggle.has-selection + .chip-preview-btn {
|
||||
border-left-color: #28a745;
|
||||
background: rgba(40, 167, 69, 0.03);
|
||||
}
|
||||
body > .target-search-dropdown .dropdown-content,
|
||||
.target-search-dropdown .dropdown-content {
|
||||
max-height: 400px;
|
||||
@@ -4982,6 +5037,16 @@ body > .target-search-dropdown .dropdown-item:not(:last-child) {
|
||||
.entity-selector-trait .chip-name {
|
||||
word-break: break-word;
|
||||
}
|
||||
.target-conditions-trait .chip-attrs,
|
||||
.entity-selector-trait .chip-attrs {
|
||||
font-size: 0.85em;
|
||||
opacity: 0.7;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.target-conditions-trait .chip-attrs::before,
|
||||
.entity-selector-trait .chip-attrs::before {
|
||||
content: "— ";
|
||||
}
|
||||
.target-conditions-trait .chip-remove,
|
||||
.entity-selector-trait .chip-remove {
|
||||
padding: 0;
|
||||
@@ -7070,6 +7135,42 @@ body > .target-search-dropdown .dropdown-item:not(:last-child) {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.25em 1.25em;
|
||||
}
|
||||
.target-conditions-trait[data-mode=single] .groups-container,
|
||||
.target-conditions-trait .mode-single .groups-container,
|
||||
.entity-selector-trait[data-mode=single] .groups-container,
|
||||
.entity-selector-trait .mode-single .groups-container {
|
||||
padding: 0;
|
||||
}
|
||||
.target-conditions-trait[data-mode=single] .group-body,
|
||||
.target-conditions-trait .mode-single .group-body,
|
||||
.entity-selector-trait[data-mode=single] .group-body,
|
||||
.entity-selector-trait .mode-single .group-body {
|
||||
padding: 0;
|
||||
}
|
||||
.target-conditions-trait[data-mode=single] .group-include,
|
||||
.target-conditions-trait .mode-single .group-include,
|
||||
.entity-selector-trait[data-mode=single] .group-include,
|
||||
.entity-selector-trait .mode-single .group-include {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.target-conditions-trait[data-mode=single] .selection-group,
|
||||
.target-conditions-trait .mode-single .selection-group,
|
||||
.entity-selector-trait[data-mode=single] .selection-group,
|
||||
.entity-selector-trait .mode-single .selection-group {
|
||||
background: transparent;
|
||||
border: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.target-conditions-trait[data-mode=single] .group-header,
|
||||
.target-conditions-trait .mode-single .group-header,
|
||||
.entity-selector-trait[data-mode=single] .group-header,
|
||||
.entity-selector-trait .mode-single .group-header {
|
||||
display: none;
|
||||
}
|
||||
.target-conditions-trait .condition-match-count,
|
||||
.entity-selector-trait .condition-match-count {
|
||||
display: inline-flex;
|
||||
@@ -8182,6 +8283,20 @@ body > .target-search-dropdown .dropdown-item:not(:last-child) {
|
||||
right: 20px;
|
||||
transform: none;
|
||||
}
|
||||
.target-preview-popover.position-above::before,
|
||||
.target-list-preview-popover.position-above::before {
|
||||
top: auto;
|
||||
bottom: -8px;
|
||||
border-top: 8px solid #dee2e6;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.target-preview-popover.position-above::after,
|
||||
.target-list-preview-popover.position-above::after {
|
||||
top: auto;
|
||||
bottom: -6px;
|
||||
border-top: 6px solid #ffffff;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -272,9 +272,9 @@
|
||||
!$(e.target).closest('.group-count-badge').length &&
|
||||
!$(e.target).closest('.group-modifiers').length &&
|
||||
!$(e.target).closest('.group-preview-badge').length &&
|
||||
!$(e.target).closest('.toggle-count.clickable').length &&
|
||||
!$(e.target).closest('.trait-total-count').length &&
|
||||
!$(e.target).closest('.chip-preview-holidays').length) {
|
||||
!$(e.target).closest('.chip-preview-holidays').length &&
|
||||
!$(e.target).closest('.chip-preview-btn').length) {
|
||||
self.hidePreviewPopover();
|
||||
// Also close holiday popover
|
||||
$('.holiday-preview-popover').remove();
|
||||
@@ -317,6 +317,31 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Filter chip/group preview eye button (unified handler)
|
||||
this.$wrapper.on('click', '.chip-preview-btn', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var $btn = $(this);
|
||||
|
||||
if ($btn.hasClass('popover-open')) {
|
||||
self.hidePreviewPopover();
|
||||
} else {
|
||||
var valueId = $btn.data('id');
|
||||
var valueType = $btn.data('type');
|
||||
var valueName = $btn.data('name');
|
||||
var groupId = $btn.data('groupId');
|
||||
|
||||
if (valueId) {
|
||||
// Value-level preview (specific attribute/feature value)
|
||||
self.showFilterValuePreviewPopover($btn, valueId, valueType, valueName, groupId);
|
||||
} else if (groupId) {
|
||||
// Group-level preview (entire attribute/feature group)
|
||||
self.showFilterGroupPreviewPopover($btn, groupId, valueType, valueName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Group-level collapse toggle (click on group header or toggle icon)
|
||||
this.$wrapper.on('click', '.group-header', function(e) {
|
||||
if ($(e.target).closest('.btn-remove-group, .group-name-input').length) {
|
||||
@@ -1196,7 +1221,7 @@
|
||||
if (isSelected) {
|
||||
// Remove from pending selections
|
||||
self.pendingSelections = self.pendingSelections.filter(function(s) {
|
||||
return parseInt(s.id, 10) !== parseInt(id, 10);
|
||||
return String(s.id) !== String(id);
|
||||
});
|
||||
self.removeSelection($picker, id);
|
||||
$item.toggleClass('selected');
|
||||
@@ -1223,7 +1248,7 @@
|
||||
} else {
|
||||
// Add to pending selections
|
||||
var exists = self.pendingSelections.some(function(s) {
|
||||
return parseInt(s.id, 10) === parseInt(id, 10);
|
||||
return String(s.id) === String(id);
|
||||
});
|
||||
if (!exists) {
|
||||
self.pendingSelections.push({ id: id, name: name, data: $item.data() });
|
||||
@@ -1248,7 +1273,7 @@
|
||||
// Also remove from pending selections if dropdown is open
|
||||
if (self.pendingSelections) {
|
||||
self.pendingSelections = self.pendingSelections.filter(function(s) {
|
||||
return parseInt(s.id, 10) !== parseInt(id, 10);
|
||||
return String(s.id) !== String(id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1375,7 +1400,7 @@
|
||||
|
||||
// Add to pending selections for Save button
|
||||
var exists = self.pendingSelections.some(function(s) {
|
||||
return parseInt(s.id, 10) === parseInt(id, 10);
|
||||
return String(s.id) === String(id);
|
||||
});
|
||||
if (!exists) {
|
||||
self.pendingSelections.push({
|
||||
@@ -1990,10 +2015,6 @@
|
||||
|
||||
// Toggle filter group - show values
|
||||
this.$dropdown.on('click', '.filter-group-toggle', function(e) {
|
||||
// Ignore clicks on the preview badge
|
||||
if ($(e.target).closest('.toggle-count.clickable').length) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var $btn = $(this);
|
||||
var groupId = $btn.data('group-id');
|
||||
@@ -2010,20 +2031,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Filter group toggle count badge click for preview popover
|
||||
this.$dropdown.on('click', '.filter-group-toggle .toggle-count.clickable', function(e) {
|
||||
e.stopPropagation();
|
||||
// Filter preview eye button (dropdown-level, since dropdown is appended to body)
|
||||
this.$dropdown.on('click', '.chip-preview-btn', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var $badge = $(this);
|
||||
var groupId = $badge.data('groupId');
|
||||
var groupType = $badge.data('type');
|
||||
var groupName = $badge.data('groupName');
|
||||
var $btn = $(this);
|
||||
|
||||
if ($badge.hasClass('popover-open')) {
|
||||
if ($btn.hasClass('popover-open')) {
|
||||
self.hidePreviewPopover();
|
||||
} else {
|
||||
self.showFilterGroupPreviewPopover($badge, groupId, groupType, groupName);
|
||||
var valueId = $btn.data('id');
|
||||
var valueType = $btn.data('type');
|
||||
var valueName = $btn.data('name');
|
||||
var groupId = $btn.data('groupId');
|
||||
|
||||
if (valueId) {
|
||||
self.showFilterValuePreviewPopover($btn, valueId, valueType, valueName, groupId);
|
||||
} else if (groupId) {
|
||||
self.showFilterGroupPreviewPopover($btn, groupId, valueType, valueName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2182,13 +2209,13 @@
|
||||
// If already pinned, unpin and close
|
||||
if ($wrapper.hasClass('pinned')) {
|
||||
$wrapper.removeClass('pinned');
|
||||
$wrapper.find('.material-icons').text('info_outline');
|
||||
$wrapper.find('.material-icons').text('info');
|
||||
$('.mpr-tooltip-fixed.pinned').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any other pinned tooltips
|
||||
$('.mpr-info-wrapper.pinned').removeClass('pinned').find('.material-icons').text('info_outline');
|
||||
$('.mpr-info-wrapper.pinned').removeClass('pinned').find('.material-icons').text('info');
|
||||
$('.mpr-tooltip-fixed').remove();
|
||||
|
||||
var content = $wrapper.attr('data-tooltip');
|
||||
@@ -2208,7 +2235,7 @@
|
||||
// Close button click
|
||||
$closeBtn.on('click', function() {
|
||||
$wrapper.removeClass('pinned');
|
||||
$wrapper.find('.material-icons').text('info_outline');
|
||||
$wrapper.find('.material-icons').text('info');
|
||||
$tooltip.remove();
|
||||
});
|
||||
|
||||
@@ -2716,6 +2743,11 @@
|
||||
sort_dir: this.currentSort ? this.currentSort.dir : 'ASC'
|
||||
};
|
||||
|
||||
// Add product_selection_level if set
|
||||
if (this.config.productSelectionLevel && this.config.productSelectionLevel !== 'product') {
|
||||
requestData.product_selection_level = this.config.productSelectionLevel;
|
||||
}
|
||||
|
||||
// Add refine query if present
|
||||
if (this.refineQuery) {
|
||||
requestData.refine = this.refineQuery;
|
||||
@@ -3043,6 +3075,8 @@
|
||||
var isSelected = selectedIds.indexOf(String(item.id)) !== -1;
|
||||
var itemClass = 'dropdown-item' + (isSelected ? ' selected' : '');
|
||||
if (item.type === 'product') itemClass += ' result-item-product';
|
||||
if (item.is_combination) itemClass += ' is-combination';
|
||||
if (item.is_parent) itemClass += ' is-parent-product';
|
||||
|
||||
html += '<div class="' + itemClass + '" ';
|
||||
html += 'data-id="' + self.escapeAttr(item.id) + '" ';
|
||||
@@ -3050,6 +3084,7 @@
|
||||
if (item.image) html += ' data-image="' + self.escapeAttr(item.image) + '"';
|
||||
if (item.subtitle) html += ' data-subtitle="' + self.escapeAttr(item.subtitle) + '"';
|
||||
if (item.iso_code) html += ' data-iso="' + self.escapeAttr(item.iso_code) + '"';
|
||||
if (item.attributes) html += ' data-attributes="' + self.escapeAttr(item.attributes) + '"';
|
||||
html += '>';
|
||||
|
||||
html += '<span class="result-checkbox"><i class="icon-check"></i></span>';
|
||||
@@ -3786,6 +3821,7 @@
|
||||
if (!this.$dropdown || !this.filterableData) return;
|
||||
|
||||
var self = this;
|
||||
var previewLabel = self.config.trans && self.config.trans.preview || 'Preview';
|
||||
|
||||
// Render attribute group toggle buttons
|
||||
var $attrContainer = this.$dropdown.find('.filter-attributes-container');
|
||||
@@ -3793,12 +3829,17 @@
|
||||
|
||||
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
||||
this.filterableData.attributes.forEach(function(group) {
|
||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
var html = '<span class="filter-chip-wrapper">';
|
||||
html += '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||
if (group.count !== undefined) {
|
||||
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '"><i class="icon-eye"></i> ' + group.count + '</span>';
|
||||
html += '<span class="toggle-count">(' + group.count + ')</span>';
|
||||
}
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="chip-preview-btn" data-group-id="' + group.id + '" data-type="attribute" data-name="' + self.escapeAttr(group.name) + '" title="' + previewLabel + '">';
|
||||
html += '<i class="icon-eye"></i>';
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
$attrContainer.append(html);
|
||||
});
|
||||
this.$dropdown.find('.filter-row-attributes').show();
|
||||
@@ -3810,12 +3851,17 @@
|
||||
|
||||
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
||||
this.filterableData.features.forEach(function(group) {
|
||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
var html = '<span class="filter-chip-wrapper">';
|
||||
html += '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||
if (group.count !== undefined) {
|
||||
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '"><i class="icon-eye"></i> ' + group.count + '</span>';
|
||||
html += '<span class="toggle-count">(' + group.count + ')</span>';
|
||||
}
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="chip-preview-btn" data-group-id="' + group.id + '" data-type="feature" data-name="' + self.escapeAttr(group.name) + '" title="' + previewLabel + '">';
|
||||
html += '<i class="icon-eye"></i>';
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
$featContainer.append(html);
|
||||
});
|
||||
this.$dropdown.find('.filter-row-features').show();
|
||||
@@ -3853,6 +3899,7 @@
|
||||
var colorStyle = val.color ? ' style="--chip-color: ' + val.color + '"' : '';
|
||||
var colorClass = val.color ? ' has-color' : '';
|
||||
|
||||
html += '<span class="filter-chip-wrapper">';
|
||||
html += '<button type="button" class="filter-chip ' + chipClass + activeClass + colorClass + '" data-id="' + val.id + '" data-group-id="' + groupId + '"' + colorStyle + '>';
|
||||
if (val.color) {
|
||||
html += '<span class="chip-color-dot"></span>';
|
||||
@@ -3862,6 +3909,10 @@
|
||||
html += '<span class="chip-count">(' + val.count + ')</span>';
|
||||
}
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="chip-preview-btn" data-id="' + val.id + '" data-group-id="' + groupId + '" data-type="' + type + '" data-name="' + self.escapeAttr(val.name) + '" title="' + (self.config.trans && self.config.trans.preview || 'Preview') + '">';
|
||||
html += '<i class="icon-eye"></i>';
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
});
|
||||
|
||||
$valuesContainer.html(html);
|
||||
@@ -4378,16 +4429,20 @@
|
||||
console.log('[EntitySelector] Making bulk AJAX request for entities:', JSON.stringify(bulkRequest));
|
||||
|
||||
// Single bulk AJAX call for all entity types
|
||||
var bulkAjaxData = {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIdsBulk',
|
||||
trait: 'EntitySelector',
|
||||
entities: JSON.stringify(bulkRequest)
|
||||
};
|
||||
if (self.config.productSelectionLevel && self.config.productSelectionLevel !== 'product') {
|
||||
bulkAjaxData.product_selection_level = self.config.productSelectionLevel;
|
||||
}
|
||||
$.ajax({
|
||||
url: self.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIdsBulk',
|
||||
trait: 'EntitySelector',
|
||||
entities: JSON.stringify(bulkRequest)
|
||||
},
|
||||
data: bulkAjaxData,
|
||||
success: function(response) {
|
||||
console.log('[EntitySelector] AJAX response:', response);
|
||||
if (!response.success || !response.entities) {
|
||||
@@ -4706,18 +4761,22 @@
|
||||
|
||||
// Handle entity_search type - load via AJAX
|
||||
var searchEntity = $picker.attr('data-search-entity') || blockType;
|
||||
var pickerAjaxData = {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIds',
|
||||
trait: 'EntitySelector',
|
||||
entity_type: searchEntity,
|
||||
ids: JSON.stringify(values)
|
||||
};
|
||||
if (this.config.productSelectionLevel && this.config.productSelectionLevel !== 'product') {
|
||||
pickerAjaxData.product_selection_level = this.config.productSelectionLevel;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIds',
|
||||
trait: 'EntitySelector',
|
||||
entity_type: searchEntity,
|
||||
ids: JSON.stringify(values)
|
||||
},
|
||||
data: pickerAjaxData,
|
||||
success: function(response) {
|
||||
if (response.success && response.entities) {
|
||||
// Track which IDs were actually found (entities may have been deleted)
|
||||
@@ -7167,7 +7226,8 @@
|
||||
clearValidationError: function() {
|
||||
this.$wrapper.removeClass('has-validation-error');
|
||||
this.$wrapper.find('.trait-validation-error').remove();
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -7760,7 +7820,7 @@
|
||||
class: 'mpr-info-wrapper',
|
||||
'data-tooltip': helpContent
|
||||
});
|
||||
$infoWrapper.append($('<i>', { class: 'material-icons', text: 'info_outline' }));
|
||||
$infoWrapper.append($('<i>', { class: 'material-icons', text: 'info' }));
|
||||
$placeholder.append($infoWrapper);
|
||||
}
|
||||
},
|
||||
@@ -8240,23 +8300,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Position popover below badge
|
||||
var badgeOffset = $badge.offset();
|
||||
var badgeHeight = $badge.outerHeight();
|
||||
var badgeWidth = $badge.outerWidth();
|
||||
var popoverWidth = $popover.outerWidth();
|
||||
|
||||
var leftPos = badgeOffset.left + (badgeWidth / 2) - (popoverWidth / 2);
|
||||
var minLeft = 10;
|
||||
var maxLeft = $(window).width() - popoverWidth - 10;
|
||||
leftPos = Math.max(minLeft, Math.min(leftPos, maxLeft));
|
||||
|
||||
$popover.css({
|
||||
position: 'absolute',
|
||||
top: badgeOffset.top + badgeHeight + 8,
|
||||
left: leftPos,
|
||||
zIndex: 10000
|
||||
});
|
||||
// Position popover relative to badge (handles viewport overflow)
|
||||
this.positionPopover($popover, $badge);
|
||||
|
||||
// Show with transition
|
||||
$popover.addClass('show');
|
||||
@@ -8264,6 +8309,61 @@
|
||||
return $popover;
|
||||
},
|
||||
|
||||
/**
|
||||
* Position a popover relative to a trigger element.
|
||||
* Handles horizontal and vertical viewport overflow.
|
||||
* @param {jQuery} $popover - The popover element
|
||||
* @param {jQuery} $trigger - The trigger element to position against
|
||||
* @param {number} [zIndex=10000] - Optional z-index
|
||||
*/
|
||||
positionPopover: function($popover, $trigger, zIndex) {
|
||||
var triggerRect = $trigger[0].getBoundingClientRect();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var scrollLeft = $(window).scrollLeft();
|
||||
var popoverWidth = $popover.outerWidth();
|
||||
var popoverHeight = $popover.outerHeight();
|
||||
var windowWidth = $(window).width();
|
||||
var windowHeight = $(window).height();
|
||||
var gap = 8;
|
||||
|
||||
// Horizontal: center on trigger, then clamp to viewport
|
||||
var left = triggerRect.left + scrollLeft + (triggerRect.width / 2) - (popoverWidth / 2);
|
||||
left = Math.max(10, Math.min(left, windowWidth - popoverWidth - 10));
|
||||
|
||||
// Vertical: prefer below, flip above if it would overflow
|
||||
var top;
|
||||
var positionAbove = false;
|
||||
if (triggerRect.bottom + popoverHeight + gap > windowHeight - 10) {
|
||||
// Position above the trigger
|
||||
top = triggerRect.top + scrollTop - popoverHeight - gap;
|
||||
positionAbove = true;
|
||||
} else {
|
||||
// Position below the trigger
|
||||
top = triggerRect.bottom + scrollTop + gap;
|
||||
}
|
||||
|
||||
$popover.css({
|
||||
position: 'absolute',
|
||||
top: top,
|
||||
left: left,
|
||||
zIndex: zIndex || 10000
|
||||
});
|
||||
|
||||
// Toggle arrow direction class
|
||||
$popover.toggleClass('position-above', positionAbove);
|
||||
|
||||
// Determine horizontal arrow position class
|
||||
var triggerCenter = triggerRect.left + (triggerRect.width / 2);
|
||||
var popoverLeft = left;
|
||||
var popoverCenter = popoverLeft + (popoverWidth / 2);
|
||||
$popover.removeClass('position-left position-right');
|
||||
if (triggerCenter - popoverLeft < popoverWidth * 0.3) {
|
||||
$popover.addClass('position-right');
|
||||
} else if (triggerCenter - popoverLeft > popoverWidth * 0.7) {
|
||||
$popover.addClass('position-left');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update popover after loading more items
|
||||
*/
|
||||
@@ -9228,6 +9328,144 @@
|
||||
});
|
||||
},
|
||||
|
||||
// =========================================================================
|
||||
// FILTER VALUE PREVIEW (individual attribute/feature value chip)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Show preview popover for a specific filter value (attribute or feature value)
|
||||
* @param {jQuery} $badge - The preview button on the chip
|
||||
* @param {number} valueId - The attribute/feature value ID
|
||||
* @param {string} valueType - 'attribute' or 'feature'
|
||||
* @param {string} valueName - Display name of the value
|
||||
* @param {number} groupId - The parent group ID
|
||||
*/
|
||||
showFilterValuePreviewPopover: function($badge, valueId, valueType, valueName, groupId) {
|
||||
var self = this;
|
||||
|
||||
this.hidePreviewPopover();
|
||||
|
||||
$badge.addClass('popover-open loading');
|
||||
this.$activeBadge = $badge;
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'previewFilterValueProducts',
|
||||
trait: 'EntitySelector',
|
||||
value_id: valueId,
|
||||
value_type: valueType,
|
||||
group_id: groupId,
|
||||
limit: 10
|
||||
},
|
||||
success: function(response) {
|
||||
$badge.removeClass('loading');
|
||||
|
||||
if (response.success) {
|
||||
self.createPreviewPopover({
|
||||
$badge: $badge,
|
||||
items: response.items || [],
|
||||
totalCount: response.count || 0,
|
||||
hasMore: response.hasMore || false,
|
||||
entityLabel: 'products',
|
||||
previewType: 'filter-value',
|
||||
context: { valueId: valueId, valueType: valueType, groupId: groupId, valueName: valueName },
|
||||
onLoadMore: function($btn) {
|
||||
self.loadMoreFilterValueItems($btn);
|
||||
},
|
||||
onFilter: function(query) {
|
||||
self.filterFilterValueItems(query);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$badge.removeClass('popover-open');
|
||||
self.$activeBadge = null;
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$badge.removeClass('loading popover-open');
|
||||
self.$activeBadge = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* AJAX filter handler for filter value preview
|
||||
*/
|
||||
filterFilterValueItems: function(query) {
|
||||
var self = this;
|
||||
var ctx = this.previewContext;
|
||||
|
||||
if (!ctx || !ctx.valueId) {
|
||||
self.showFilterLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'previewFilterValueProducts',
|
||||
trait: 'EntitySelector',
|
||||
value_id: ctx.valueId,
|
||||
value_type: ctx.valueType,
|
||||
group_id: ctx.groupId,
|
||||
filter: query,
|
||||
limit: 20
|
||||
},
|
||||
success: function(response) {
|
||||
self.updatePreviewPopoverFiltered(response);
|
||||
},
|
||||
error: function() {
|
||||
self.showFilterLoading(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadMoreFilterValueItems: function($btn) {
|
||||
var self = this;
|
||||
var ctx = this.previewContext;
|
||||
|
||||
if (!ctx || !ctx.valueId) return;
|
||||
|
||||
var loadCount = this.previewLoadCount || 20;
|
||||
|
||||
var ajaxData = {
|
||||
ajax: 1,
|
||||
action: 'previewFilterValueProducts',
|
||||
trait: 'EntitySelector',
|
||||
value_id: ctx.valueId,
|
||||
value_type: ctx.valueType,
|
||||
group_id: ctx.groupId,
|
||||
limit: self.previewLoadedCount + loadCount
|
||||
};
|
||||
if (self.previewCurrentFilter) {
|
||||
ajaxData.filter = self.previewCurrentFilter;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: ajaxData,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
self.previewTotalCount = response.count;
|
||||
self.updatePreviewPopover(response.items || [], response.hasMore);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$btn.removeClass('loading');
|
||||
$btn.find('i').removeClass('icon-spinner icon-spin').addClass('icon-plus');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// =========================================================================
|
||||
// CATEGORY ITEMS PREVIEW (products/pages in a category)
|
||||
// =========================================================================
|
||||
@@ -9565,28 +9803,9 @@
|
||||
self.switchToBlock(blockType);
|
||||
});
|
||||
|
||||
// Position popover
|
||||
// Position popover relative to badge (handles viewport overflow)
|
||||
$('body').append($popover);
|
||||
var badgeOffset = $badge.offset();
|
||||
var badgeHeight = $badge.outerHeight();
|
||||
var popoverWidth = $popover.outerWidth();
|
||||
|
||||
$popover.css({
|
||||
position: 'absolute',
|
||||
top: badgeOffset.top + badgeHeight + 5,
|
||||
left: badgeOffset.left - (popoverWidth / 2) + ($badge.outerWidth() / 2),
|
||||
zIndex: 10000
|
||||
});
|
||||
|
||||
// Adjust if off screen
|
||||
var windowWidth = $(window).width();
|
||||
var popoverRight = $popover.offset().left + popoverWidth;
|
||||
if (popoverRight > windowWidth - 10) {
|
||||
$popover.css('left', windowWidth - popoverWidth - 10);
|
||||
}
|
||||
if ($popover.offset().left < 10) {
|
||||
$popover.css('left', 10);
|
||||
}
|
||||
this.positionPopover($popover, $badge);
|
||||
|
||||
$popover.hide().fadeIn(150);
|
||||
},
|
||||
|
||||
2
assets/js/admin/entity-selector.min.js
vendored
2
assets/js/admin/entity-selector.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user