// Simple & Reliable Collapsible Filter Hierarchy
(function() {
// Modern CSS styles
const styles = `
<style>
.filter-parent-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 12px;
}
.filter-content-left {
flex: 1;
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.filter-content-left input[type="checkbox"] {
flex-shrink: 0;
}
.filter-text-wrapper {
flex: 1;
min-width: 0;
}
.collapse-toggle {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: rgba(0, 0, 0, 0.04);
margin-left: 8px;
user-select: none;
}
.collapse-toggle:hover {
background: rgba(0, 0, 0, 0.08);
transform: scale(1.05);
}
.collapse-toggle:active {
transform: scale(0.95);
}
.collapse-toggle svg {
width: 14px;
height: 14px;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
color: #666;
}
.collapse-toggle:hover svg {
color: #000;
}
.collapse-toggle.expanded svg {
transform: rotate(90deg);
}
.has-children > label {
width: 100%;
}
/* Simple hide/show with smooth transition */
.child-item {
overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.child-item:not(.collapsed) {
max-height: 500px;
opacity: 1;
}
/* Use display none for collapsed - cleanest solution */
.child-item.collapsed {
display: none !important;
}
/* Smooth transition for expanding */
@keyframes expandChild {
from {
max-height: 0;
opacity: 0;
}
to {
max-height: 500px;
opacity: 1;
}
}
.child-item:not(.collapsed) {
animation: expandChild 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
`;
// Inject styles once
if (!document.getElementById('filter-collapse-styles')) {
const styleElement = document.createElement('div');
styleElement.id = 'filter-collapse-styles';
styleElement.innerHTML = styles;
document.head.appendChild(styleElement);
}
// Modern chevron SVG icon
const chevronIcon = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
`;
function initializeFilters() {
const filterLists = document.querySelectorAll('.brxe-filter-checkbox');
filterLists.forEach(list => {
const items = Array.from(list.querySelectorAll('li'));
items.forEach((item, index) => {
const depth = parseInt(item.getAttribute('data-depth'));
// Process parent items (depth-0)
if (depth === 0) {
const nextItem = items[index + 1];
const hasChildren = nextItem && parseInt(nextItem.getAttribute('data-depth')) === 1;
if (hasChildren && !item.querySelector('.collapse-toggle')) {
const label = item.querySelector('label');
const checkbox = label.querySelector('input[type="checkbox"]');
const optionText = label.querySelector('.brx-option-text');
// Check if wrapper already exists
if (!label.querySelector('.filter-parent-wrapper')) {
// Store original checkbox reference
const originalCheckbox = checkbox;
// Create new structure
const wrapper = document.createElement('div');
wrapper.className = 'filter-parent-wrapper';
const leftContent = document.createElement('div');
leftContent.className = 'filter-content-left';
const textWrapper = document.createElement('div');
textWrapper.className = 'filter-text-wrapper';
// Build structure - keep original checkbox
leftContent.appendChild(originalCheckbox);
textWrapper.appendChild(optionText.cloneNode(true));
leftContent.appendChild(textWrapper);
wrapper.appendChild(leftContent);
// Create toggle button
const toggle = document.createElement('div');
toggle.className = 'collapse-toggle';
toggle.innerHTML = chevronIcon;
toggle.setAttribute('role', 'button');
toggle.setAttribute('aria-label', 'Toggle child items');
wrapper.appendChild(toggle);
// Clear label (except checkbox which we already moved)
label.innerHTML = '';
label.appendChild(wrapper);
// Add click handler only to toggle
toggle.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
toggleChildren(item, items, index, toggle);
});
// Mark parent
item.classList.add('has-children');
// Check if should be expanded by default (active state)
const hasActiveChild = items.slice(index + 1).some(childItem => {
const childDepth = parseInt(childItem.getAttribute('data-depth'));
if (childDepth === 0) return false;
return childItem.classList.contains('brx-option-active');
});
if (hasActiveChild || item.classList.contains('brx-option-active')) {
item.setAttribute('data-collapsed', 'false');
toggle.classList.add('expanded');
} else {
item.setAttribute('data-collapsed', 'true');
}
}
}
}
// Process child items (depth-1)
if (depth === 1) {
if (!item.classList.contains('child-item')) {
item.classList.add('child-item');
// Find parent to check its state
let parentItem = null;
for (let j = index - 1; j >= 0; j--) {
if (parseInt(items[j].getAttribute('data-depth')) === 0) {
parentItem = items[j];
break;
}
}
// Check if any sibling is active
let hasActiveSibling = false;
if (parentItem) {
const parentIndex = items.indexOf(parentItem);
for (let j = parentIndex + 1; j < items.length; j++) {
const siblingDepth = parseInt(items[j].getAttribute('data-depth'));
if (siblingDepth === 0) break;
if (items[j].classList.contains('brx-option-active')) {
hasActiveSibling = true;
break;
}
}
}
const isActive = item.classList.contains('brx-option-active');
const isParentActive = parentItem && parentItem.classList.contains('brx-option-active');
const parentCollapsed = parentItem && parentItem.getAttribute('data-collapsed') === 'true';
// Show if active, parent active, has active sibling, or parent not collapsed
if (isActive || isParentActive || hasActiveSibling || !parentCollapsed) {
item.classList.remove('collapsed');
} else {
item.classList.add('collapsed');
}
}
}
});
});
}
function toggleChildren(parentItem, allItems, parentIndex, toggle) {
const isCollapsed = parentItem.getAttribute('data-collapsed') === 'true';
// Find all children of this parent
const children = [];
for (let i = parentIndex + 1; i < allItems.length; i++) {
const item = allItems[i];
const depth = parseInt(item.getAttribute('data-depth'));
if (depth === 0) break;
if (depth === 1) children.push(item);
}
// Check if parent or any child has active class
const hasActiveChild = children.some(child =>
child.classList.contains('brx-option-active')
);
const isParentActive = parentItem.classList.contains('brx-option-active');
// Don't allow collapsing if parent or children are active
if ((hasActiveChild || isParentActive) && !isCollapsed) {
return;
}
// Toggle state
if (isCollapsed) {
toggle.classList.add('expanded');
parentItem.setAttribute('data-collapsed', 'false');
// Show all children with stagger
children.forEach((item, idx) => {
setTimeout(() => {
item.classList.remove('collapsed');
}, idx * 30);
});
} else {
toggle.classList.remove('expanded');
parentItem.setAttribute('data-collapsed', 'true');
// Hide all children (except active ones)
children.forEach((item, idx) => {
if (!item.classList.contains('brx-option-active')) {
setTimeout(() => {
item.classList.add('collapsed');
}, idx * 30);
}
});
}
}
// Initialize on page load
initializeFilters();
// Watch for DOM changes (when filters update)
const observer = new MutationObserver((mutations) => {
let shouldReinit = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList' || mutation.type === 'attributes') {
shouldReinit = true;
}
});
if (shouldReinit) {
setTimeout(initializeFilters, 100);
}
});
// Observe all filter lists
document.querySelectorAll('.brxe-filter-checkbox').forEach(list => {
observer.observe(list, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
});
// Also observe for new filter lists being added
const bodyObserver = new MutationObserver(() => {
initializeFilters();
});
bodyObserver.observe(document.body, {
childList: true,
subtree: true
});
})();