I have a list with several elements, and some controlling buttons that can hide/show these elements. Some buttons have control over just one element, while others have multiple elements. Do you maybe know how can I gray out a button automatically when all of its associated elements are already hidden by other buttons? For example:

Button01 hides/shows ElementX and ElementY,
Button02 hides/shows only ElementY.
What I want to do is, graying out Button02 automatically once ElementY is hidden by the other button.

for (let button of document.querySelectorAll(".filterbutton")) {
    button.addEventListener("click", filter);
}

let filters = new Set;

function toggleDisplay(selector, display) {
    let elems = document.querySelectorAll(selector);
    for (let elem of elems) {
        elem.style.display = display;
    }
}

function filter() {
    let filterSelector = this.dataset.filter;
    let show = filters.delete(filterSelector);
    this.style.color = show ? "" : "rgb(200,200,200)";
    if (!show) {
        filters.add(filterSelector); // toggle this filter
    } else {
        toggleDisplay(filterSelector, "");
    }
    if (filters.size) {
        toggleDisplay([...filters].join(","), "none");
    }
}
<div class="filterbutton" data-filter=".filter01">Filter01</div>
<div class="filterbutton" data-filter=".filter02">Filter02</div>

<div class="filter01">ElementX</div>
<div class="filter01 filter02">ElementY</div>

1

Since you have not given a specific problem that can be expressed by code, the answer can only be given algorithmically:

Аdd class active to the visible elements. after clicking the filter button, remove this class from the filtered elements. at the end of filtering, go through all the buttons and if the result is document.querySelectorAll(‘.active.filter’) empty set – disabled the filter button.

You would need to do the following:

  • create function that will return the list of elements whose display would change by the click of a given button — without actually changing anything.

  • Use the above function to actually perform the action on the click of a button.

  • At the same time update the status of each button by calling the first function above. If that function returns an empty list, then disable the corresponding button, otherwise enabled it.

For all styling use CSS classes. This makes it easier to use such styling in CSS selectors.

In the below example snippet, if you apply the filtering through the second and third button, the first button should be disabled.

for (const button of document.querySelectorAll(".filterbutton")) {
    button.addEventListener("click", filter);
}

const filters = new Set;

function getElementsToToggle(button) {
    const filterSelector = button.dataset.filter;
    const selector = filterSelector + (
        !filters.has(filterSelector) ? ":not(.hidden)"
        : filters.size <= 1 ? ".hidden"
        : ".hidden:not(" + [...filters].filter(selector => selector != filterSelector).join(",") + ")"
    );
    return document.querySelectorAll(selector);
}

function filter() {
    if (this.classList.contains("disabled")) return; // No click handling allowed

    const elems = getElementsToToggle(this);
    const show = filters.delete(this.dataset.filter);
    if (!show) {
        filters.add(this.dataset.filter); // toggle this filter
    }
    this.classList.toggle("activefilter"); // Change button style
    for (const elem of elems) {
        elem.classList.toggle("hidden"); // Toggle display of selected elements
    }
    updateButtonStatus();
}

function updateButtonStatus() {
    for (let button of document.querySelectorAll(".filterbutton")) {
        button.classList.toggle("disabled", !getElementsToToggle(button).length);
    }
}

// On page load, initialise status of buttons:
updateButtonStatus();
.filterbutton { border: 2px solid; display: inline-block; background: lightblue; padding: 5px; cursor: pointer }
.activefilter { background: blue }
.disabled { cursor: not-allowed !important; background: grey; border-color: silver }
.hidden { display: none }
<div class="filterbutton" data-filter=".filter01">Filter 01</div>
<div class="filterbutton" data-filter=".filter02">Filter 02</div>
<div class="filterbutton" data-filter=".filter03">Filter 03</div>

<div class="filter01 filter02 filter03">This element has filter01, filter02 and filter03</div>
<div class="filter01 filter02">This element has filter01 and filter02</div>
<div class="filter01 filter03">This element has filter01 and filter03</div>
<div class="filter02 filter03">This element has filter02 and filter03</div>
<div class="filter02">This element has filter02 only</div>
<div class="filter03">This element has filter03 only</div>

You would need to compare the shown elements with the filters on each button when a button gets clicked. See the solution below

const btns = Array.from(document.getElementById('filters').children);
const els = Array.from(document.getElementById('elements').children);

document.addEventListener('click', function(e) {
  if (e.target.hasAttribute('data-filter')) {

    const elArray = e.target.getAttribute('data-filter').split(" ");

    /* If button is active remove matching elements else show */
    e.target.classList.contains('active') ? elArray.forEach(el => els[el - 1].classList.remove('show')) : elArray.forEach(el => els[el - 1].classList.add('show'));

    btns.forEach(btn => {
      const matchArray = btn.getAttribute('data-filter').split(" ");
      
      /* Empty array to push true/false if buttons matching elements are visible */
      const matchesArray = [];
      matchArray.forEach(match => {
        matchesArray.push(els[match - 1].classList.contains('show'));
      });
      
      /* If any matches are visible button is active */
      matchesArray.includes(true) ? btn.classList.add('active') : btn.classList.remove('active');
    });
  }
}, false);
* { box-sizing: border-box } body { margin: 0 }

#filters { display: flex; gap: 1em }
#filters button {
  background-color: none;
  border: 1px solid currentColor;
  border-radius: .375em;
  color: black;
  cursor: pointer;
  opacity: 0.5;
  padding: .5em 1em;
}
#filters button.active { opacity: 1 }

#elements { display: flex; }
#elements article {
  background: lightgrey;
  width: 0;
  overflow: hidden;
  padding: 1em 0;
  text-align: center;
  white-space: nowrap;
  
  --trans: width .5s ease-in-out;

    transition: var(--trans); -o-transition: width var(--trans); -moz-transition: var(--trans); -webkit-transition: var(--trans);
}
#elements article.show { width: 25% }
<div id="filters">
  <button class="active" type="button" data-filter="1 2 3 4">Filter All</button>
  <button class="active" type="button" data-filter="2 3">Filter 2 & 3</button>
  <button class="active" type="button" data-filter="4">Filter 4</button>
</div>

<hr>

<div id="elements">
  <article class="show">Element 1</article>
  <article class="show">Element 2</article>
  <article class="show">Element 3</article>
  <article class="show">Element 4</article>
</div>