/*
Tree

<ul class="tree" role="tree">
  <li class="tree-item" role="treeitem" data-state data-selected>
    <div class="tree-row" style="--tree-level: N">
      <span class="tree-toggle">   chevron (turns open), spinner while loading, or empty
      <svg>                        node.icon
      <span class="tree-label">
    <ul class="tree-group" role="group">

Rows are menu-tier items: --item-height, --item-padding-x, highlight on
hover, --item-selected when selected. Indentation is one toggle width
plus a gap per level. Selection and opening change colors and the
chevron's rotation only, never a row's box.
*/

@layer components {

.tree {
  --tree-indent: calc(var(--icon-size) + var(--item-gap));
  --tree-guide-width: calc(var(--icon-stroke) * var(--icon-size) / 24);

  margin:      0;
  padding:     0;
  list-style:  none;
  font-family: var(--font-ui);
  font-size:   var(--text-ui);
  color:       var(--ink);
  min-width:   0;
}

.tree-group {
  margin:     0;
  padding:    0;
  list-style: none;
}

/* indentGuides: a rule down each open group, under its parent's chevron,
   drawn with the chevron's stroke. Absolutely placed, so it moves nothing. */
.tree.is-guides .tree-group {
  position: relative;
}

.tree.is-guides .tree-group::before {
  content:             "";
  position:            absolute;
  inset-block:         0;
  inset-inline-start:  calc(var(--item-padding-x) + (var(--tree-level, 1) - 1) * var(--tree-indent) + (var(--icon-size) - var(--tree-guide-width)) / 2);
  width:               var(--tree-guide-width);
  background:          var(--rule);
  pointer-events:      none;
}

.tree-item {
  margin:  0;
  padding: 0;
  outline: none;
}

.tree-row {
  display:       flex;
  align-items:   center;
  gap:           var(--item-gap);
  min-height:    var(--item-height);
  padding-block: 0;
  padding-inline: calc(var(--item-padding-x) + (var(--tree-level, 1) - 1) * var(--tree-indent)) var(--item-padding-x);
  border-radius: var(--radius-item);
  cursor:        default;
  user-select:   none;
  -webkit-tap-highlight-color: transparent;
}

.tree-row:hover {
  background: var(--item-highlight);
  color:      var(--item-highlight-ink);
}

.tree-item[data-selected] > .tree-row {
  background: var(--item-selected);
  color:      var(--item-selected-ink);
}

.tree-item:focus-visible > .tree-row {
  box-shadow: var(--focus-ring);
}

/* activeDescendant: the tree holds focus and its pointed-at node shows the
   ring, as a focused node would. */
.tree[tabindex]:focus {
  outline: none;
}

.tree[tabindex]:focus-visible .tree-item[data-focus] > .tree-row {
  box-shadow: var(--focus-ring);
}

.tree-item[data-disabled] > .tree-row {
  opacity: var(--opacity-disabled);
  cursor:  not-allowed;
}

.tree-row > svg {
  flex:   none;
  width:  var(--icon-size);
  height: var(--icon-size);
  color:  var(--ink-dim);
}

.tree-toggle {
  display:         inline-flex;
  flex:            none;
  align-items:     center;
  justify-content: center;
  width:           var(--icon-size);
  height:          var(--icon-size);
  color:           var(--ink-dim);
  cursor:          pointer;
}

.tree-toggle:empty {
  cursor: default;
}

.tree-toggle svg {
  width:      var(--icon-size);
  height:     var(--icon-size);
  transition: transform var(--duration-enter) var(--ease-enter);
}

.tree-item[data-state="open"] > .tree-row > .tree-toggle svg {
  transform: rotate(90deg);
}

:dir(rtl) .tree-toggle svg {
  scale: -1 1;
}

/* The Spinner template's ring (spinnerClass), so it turns at the theme's
   speed and slows under prefers-reduced-motion. Sized to sit inside the
   toggle box, with the stroke the chevron it replaces is drawn with: an
   icon's stroke is --icon-stroke units of its 24-unit viewBox. */
.tree-spinner.spinner {
  --spinner-size: calc(var(--icon-size) * 0.75);

  border-width: calc(var(--icon-stroke) * var(--icon-size) / 24);
}

/* The label and, while renaming (F2), an input stacked over it in the same
   cell: the label keeps the row's size, so renaming moves nothing. */
.tree-label-cell {
  display:   inline-grid;
  min-width: 0;
  flex:      1;
}

.tree-label-cell > * {
  grid-area: 1 / 1;
}

.tree-label {
  min-width:     0;
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
  color:         inherit;
  text-decoration: none;
}

.tree-item[data-renaming] > .tree-row .tree-label {
  visibility: hidden;
}

.tree-rename {
  min-width:     0;
  width:         100%;
  height:        100%;
  margin:        0;
  padding:       0;
  border:        none;
  border-radius: var(--radius-item);
  background:    var(--control-bg);
  color:         var(--ink);
  font:          inherit;
  box-shadow:    var(--focus-ring);
  outline:       none;
}

/* Checkbox tree: the box the Checkbox template draws, as a picture; the
   treeitem carries aria-checked. */
.tree-check {
  display:         inline-grid;
  flex:            none;
  place-items:     center;
  width:           var(--icon-size);
  height:          var(--icon-size);
  border:          var(--control-border-width) solid var(--rule-strong);
  border-radius:   var(--radius-check);
  background:      var(--control-bg);
  color:           var(--accent-ink);
  cursor:          pointer;
}

.tree-check > svg {
  grid-area:    1 / 1;
  width:        calc(var(--icon-size) * 0.75);
  height:       calc(var(--icon-size) * 0.75);
  stroke-width: calc(var(--icon-stroke) * 1.5);
  visibility:   hidden;
}

.tree-check[data-state="true"],
.tree-check[data-state="mixed"] {
  border-color: var(--accent);
  background:   var(--accent);
}

.tree-check[data-state="true"] > .icon[data-icon="check"],
.tree-check[data-state="mixed"] > .icon[data-icon="minus"] {
  visibility: visible;
}

/* orientation="horizontal": the top-level nodes stand side by side, each
   with its branch below it; Right and Left move between nodes. */
.tree[aria-orientation="horizontal"] {
  display:     flex;
  flex-wrap:   wrap;
  align-items: flex-start;
  gap:         var(--space-4);
}

.tree[aria-orientation="horizontal"] > .tree-item {
  min-width: 0;
}


/* virtual: the tree scrolls itself (the caller gives it a height) and only
   the rows in and near the view render, each placed at its offset over a
   spacer as tall as every shown row together. */
.tree.is-virtual {
  position:   relative;
  overflow-y: auto;
  overflow-x: hidden;
  contain:    content;
}

/* Rows sit flush with the scroller's clipped edges, so an outer ring would
   be cut off: the ring is drawn inside the row. */
.tree.is-virtual .tree-item:focus-visible > .tree-row,
.tree.is-virtual[tabindex]:focus-visible .tree-item[data-focus] > .tree-row {
  box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus);
}

.tree.is-virtual::after {
  content: "";
  display: block;
  height:  var(--tree-total, 0px);
}

.tree.is-virtual > .tree-item {
  position:     absolute;
  inset-inline: 0;
  top:          0;
  transform:    translateY(var(--tree-start, 0px));
}

}
