Well, I went the following route. If someone knows a better/cleaner way, let me know. Otherwise this works pretty well.
My field referencing the taxonomy: field_categories
.
Preprocess Form
First I'm adding an extra class via hook_preprocess_form_element. I don't love that I have to load each term, but it's only for admin pages.
/**
* Implements hook_preprocess_form_element().
*/
function admin_helper_preprocess_form_element(&$variables) {
if ($variables["element"]["#type"] == 'checkbox') {
$tid = $variables["element"]["#attributes"]["value"];
// Check if term has parents.
$parents = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadParents($tid);
// If parents exist, add classes.
if (!empty($parents)) {
$key = array_key_first($parents);
$variables['attributes']['class'][] = 'child-term--' . $parents[$key]->id();
}
}
}
Javascript
Then my not-too-hacky javascript. I could probably combine some of this and clean up a bit. But again, this is just for one content type on the admin side.
(() => {
const parents = document.querySelectorAll('[id^="edit-field-categories"] input');
[].forEach.call(parents, el => {
const id = el.value;
const children = document.querySelectorAll(`.child-term--${id}`);
if (el.checked) {
[].forEach.call(children, el => {
el.style.display = 'block';
})
} else {
[].forEach.call(children, el => {
el.style.display = 'none';
})
}
});
[].forEach.call(parents, el => {
el.addEventListener('click', (e) => {
const id = e.currentTarget.value;
const children = document.querySelectorAll(`.child-term--${id}`);
if (el.checked) {
[].forEach.call(children, el => {
el.style.display = 'block';
})
} else {
[].forEach.call(children, el => {
el.style.display = 'none';
})
}
});
});
})();