/*
DataTable

shadcn's data table look on a role=grid: a toolbar, a bordered surface
with a muted header row, soft row rules, row hover and selected tints, and
a footer with the selection count and pagination.

Layout is one CSS grid. Its columns (--dt-columns, from the column sizes)
are shared by every row through subgrid, so header, body and summary
cells line up while auto columns still size to their content. Rows are
real boxes (role=row), so hover, selection and focus are row-wide.

Every state is paint, never size: hover, selected, editing, invalid,
sorted, pinned and dragging change backgrounds, shadows and transforms.
Counts that come and go (filters, unsaved changes, the Reset button) keep
their space with visibility.

  .data-table[data-density="compact" | "default" | "comfortable"]
  .data-table-toolbar / .data-table-scroll / .data-table-footer
  .data-table-grid [role=grid]
    .data-table-head > .data-table-header-row > [role=columnheader]
    .data-table-body > .data-table-row-wrap > .data-table-row > .data-table-cell
    .data-table-foot > .data-table-summary-row
  [data-pin="start" | "end"]   sticky column
  .data-table-row[data-state="selected"] [data-pinned] [data-tone] .is-group
  .data-table-cell[data-editable] [data-edited] [data-invalid] [data-editing]
*/

@layer components {

.data-table {
  /* 1 left to right, -1 right to left: the x of every side shadow. */
  --dt-x:         1;
  --dt-row-h:     var(--control-height);
  --dt-pad-y:     var(--table-row-padding-y);
  --dt-row-bg:    var(--surface-bg);
  --dt-head-bg:   var(--surface-bg);
  --dt-rule:      var(--rule-soft, var(--rule));
  /* The widest a line of text in an auto column grows before it ends in an
     ellipsis (the full text is the title): ten control heights. */
  --dt-text-max:  calc(var(--control-height) * 10);

  display:        flex;
  flex-direction: column;
  gap:            var(--space-3);
  min-width:      0;
  width:          100%;
  color:          var(--ink);
  font-family:    var(--font-ui);
  font-size:      var(--text-ui);
}

/* Extra compact (Retool's X-Small rows): a caption line with no padding,
   five eighths of a small control. */
.data-table[data-density="dense"] {
  --dt-row-h: calc(var(--control-height-sm) * 0.625);
  --dt-pad-y: 0px;
}

.data-table[data-density="dense"] .data-table-body {
  font-size: var(--text-caption);
}

.data-table[data-density="compact"] {
  --dt-row-h: var(--control-height-sm);
  --dt-pad-y: var(--space-1);
}

.data-table[data-density="comfortable"] {
  --dt-row-h: var(--control-height-lg);
  --dt-pad-y: var(--space-3);
}

/* Toolbar */

.data-table-toolbar {
  display:         flex;
  flex-wrap:       wrap;
  align-items:     flex-start;
  justify-content: space-between;
  gap:             var(--space-2);
}

.data-table-toolbar-start,
.data-table-toolbar-end {
  display:     flex;
  flex-wrap:   wrap;
  align-items: center;
  gap:         var(--space-2);
  min-width:   0;
}

/* The search shrinks before anything wraps, so both groups share one line
   whenever they fit; otherwise the end group moves whole to a line of its
   own, and when that line is narrower than the group (a phone) the group
   shrinks to the table's width and its buttons wrap inside it. Both groups
   top-align, so they share one top edge. */
.data-table-toolbar-start {
  flex: 1 1 calc(var(--container-xs) * 1.4);
}

.data-table-toolbar-end {
  flex:      0 1 auto;
  max-width: 100%;
}

.data-table-search {
  position:  relative;
  display:   flex;
  flex:      1 1 calc(var(--container-xs) * 0.4);
  min-width: calc(var(--container-xs) * 0.4);
  max-width: calc(var(--container-xs) * 0.8);
}

.data-table-search > svg {
  position:       absolute;
  inset-inline-start: var(--control-padding-x-sm);
  top:            50%;
  width:          var(--icon-size-sm);
  height:         var(--icon-size-sm);
  color:          var(--ink-dim);
  transform:      translateY(-50%);
  pointer-events: none;
}

.data-table-search > .data-table-search-input.input {
  width:                100%;
  padding-inline-start: calc(var(--control-padding-x-sm) + var(--icon-size-sm) + var(--space-2));
}

.button.data-table-facet-trigger {
  border-style: dashed;
}

.data-table-facet-count {
  display:     inline-flex;
  align-items: center;
  gap:         var(--space-1);
}

/* The chosen-count slot is as wide as the largest count (a hidden "00"
   stacked under it), so choosing values only changes the digits. Nothing
   chosen: the slot is kept, empty. */
.data-table-facet-slot {
  display:              inline-grid;
  align-items:          center;
  font-variant-numeric: tabular-nums;
}

.data-table-facet-slot > * {
  grid-area: 1 / 1;
}

.data-table-facet-slot > .badge {
  justify-content: center;
}

.data-table-facet-sizer {
  display:    inline-flex;
  visibility: hidden;
}

.data-table-facet-count[data-empty] > .data-table-facet-sep {
  visibility: hidden;
}

.data-table-facet-sep {
  align-self:   stretch;
  width:        var(--control-border-width);
  margin-inline: var(--space-1);
  background:   var(--rule);
}

.data-table-facet-label {
  flex: 1;
}

.data-table-facet-num {
  margin-inline-start:  auto;
  padding-inline-start: var(--space-4);
  color:                var(--ink-dim);
  font-family:          var(--font-mono);
  font-size:            var(--text-caption);
  font-variant-numeric: tabular-nums;
}

/* A count on a toolbar button: a bubble on its corner, so it comes and goes
   without changing the button's width. */
.button:has(> .data-table-count) {
  overflow: visible;
}

.data-table-count {
  position:        absolute;
  top:             calc(-1 * var(--space-2));
  inset-inline-end: calc(-1 * var(--space-2));
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
  min-width:       calc(var(--tag-height) * 0.8);
  height:          calc(var(--tag-height) * 0.8);
  padding:         0 var(--space-1);
  border-radius:   var(--radius-pill);
  background:      var(--accent);
  color:           var(--accent-ink);
  font-size:       var(--text-ui-sm);
  font-weight:     var(--weight-ui);
  line-height:     1;
  font-variant-numeric: tabular-nums;
  pointer-events:  none;
}

.data-table-count[data-empty],
.data-table-reset[data-hidden],
.data-table-changes[data-hidden] {
  visibility: hidden;
}

.data-table-modified {
  width:         var(--space-2);
  height:        var(--space-2);
  border-radius: var(--radius-pill);
  background:    transparent;
}

.data-table-modified[data-on] {
  background: var(--accent);
}

.data-table-views-current {
  max-width:     calc(var(--container-xs) * 0.5);
  overflow:      hidden;
  text-overflow: ellipsis;
}

/* Filter Builder And Views (popover content) */

.data-table-rules {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
  min-width:      min(var(--container-xl), calc(100vw - 2 * var(--space-4)));
}

.data-table-rule {
  display:     grid;
  grid-template-columns: calc(var(--control-height) * 2) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.4fr) auto;
  align-items: center;
  gap:         var(--space-2);
}

.data-table-rule-join {
  color:     var(--ink-soft);
  font-size: var(--text-caption);
}

.data-table-rule .native-select,
.data-table-rule .input {
  width: 100%;
}

.data-table-rule-values {
  display: flex;
  gap:     var(--space-2);
  min-width: 0;
}

.data-table-rule-values > * {
  flex:      1 1 0;
  min-width: 0;
}

.data-table-rules-empty {
  margin:    0;
  color:     var(--ink-soft);
  font-size: var(--text-caption);
}

.data-table-rule-item {
  display: contents;
}

/* A filter group: its rules sit in a soft box, under a head that holds its
   join to the stack, its name and Remove. */
.data-table-rule-group {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
}

.data-table-rule-group-head {
  display:     grid;
  grid-template-columns: calc(var(--control-height) * 2) minmax(0, 1fr) auto;
  align-items: center;
  gap:         var(--space-2);
}

.data-table-rule-group-title {
  color:       var(--ink);
  font-size:   var(--text-caption);
  font-weight: var(--weight-ui);
}

.data-table-rule-group-body {
  display:        flex;
  flex-direction: column;
  align-items:    stretch;
  gap:            var(--space-2);
  padding:        var(--space-2);
  border:         var(--surface-border-width) solid var(--rule);
  border-radius:  var(--radius-control);
  background:     var(--bg-soft);
}

.data-table-rule-group-body > .button {
  align-self: flex-start;
}

.data-table-rules-foot {
  display:         flex;
  flex-wrap:       wrap;
  justify-content: space-between;
  gap:             var(--space-2);
  padding-top:     var(--space-2);
  border-top:      var(--surface-border-width) solid var(--rule);
}

.data-table-views {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-1);
  min-width:      calc(var(--container-xs) * 0.8);
  margin:         0 0 var(--space-3);
  padding:        0;
  list-style:     none;
}

.data-table-view-item {
  display:     flex;
  align-items: center;
  gap:         var(--space-1);
}

.button.data-table-view-apply {
  flex:            1;
  justify-content: flex-start;
}

.button.data-table-view-apply > .button-label > svg,
.button.data-table-view-apply > svg {
  visibility: hidden;
}

.button.data-table-view-apply[aria-pressed="true"] > svg {
  visibility: visible;
}

.data-table-view-form {
  display: flex;
  gap:     var(--space-2);
}

.data-table-view-form .input {
  flex: 1;
  min-width: 0;
}

/* The Grid */

/* Its own stacking context: the grid's order below stays inside the table
   and never lifts a sticky head over the page around it. */
.data-table-scroll {
  position:      relative;
  isolation:     isolate;
  max-height:    var(--dt-height, none);
  overflow:      auto;
  overscroll-behavior-x: contain;
  border:        var(--surface-border-width) solid var(--surface-border);
  border-radius: var(--radius-surface);
  background:    var(--surface-bg);
  box-shadow:    var(--surface-shadow);
  scroll-padding-top: var(--dt-head-h, 0px);
  scroll-padding-bottom: var(--dt-foot-h, 0px);
}

/* alwaysShowScrollbars (Retool): both scrollbars stay drawn, even where the
   platform hides them until a scroll, so the table never changes width
   between scrolling and resting. */
.data-table[data-scrollbars="always"] .data-table-scroll {
  overflow: scroll;
}

.data-table[data-scrollbars="always"] .data-table-scroll::-webkit-scrollbar {
  width:      var(--space-2);
  height:     var(--space-2);
  background: var(--surface-bg);
}

.data-table[data-scrollbars="always"] .data-table-scroll::-webkit-scrollbar-thumb {
  border-radius: var(--radius-surface);
  background:    var(--rule);
}

.data-table[data-scrollbars="always"] .data-table-scroll::-webkit-scrollbar-corner {
  background: var(--surface-bg);
}

.data-table-grid {
  display:               grid;
  grid-template-columns: var(--dt-columns);
  min-width:             100%;
  width:                 max-content;
  line-height:           var(--leading-snug);
}

.data-table-head,
.data-table-body,
.data-table-foot,
.data-table-row,
.data-table-header-row {
  display:               grid;
  grid-column:           1 / -1;
  grid-template-columns: subgrid;
}

.data-table-body {
  align-content: start;
}

.data-table-row-wrap {
  display: contents;
}

/* A manual table shows one page at a time and cannot look ahead: its
   cells give their tracks no width of their own, so the columns are as wide
   as their headers and their share, and the next page never moves an edge.
   A long value ends in an ellipsis and shows in full as the title. */
.data-table[data-manual] .data-table-body .data-table-cell:not(.is-wrap, .is-select, .is-drag, .is-expand, .is-actions) {
  contain: inline-size;
}

/* Sizer rows (sizerRowsOf): the widest values in all the data, laid out so
   auto columns take their width from them, but never seen. */
.data-table-sizer {
  display:               grid;
  grid-column:           1 / -1;
  grid-template-columns: subgrid;
  block-size:            0;
  overflow:              hidden;
  visibility:            hidden;
  pointer-events:        none;
}

/* One stacking order for the grid. Cells and unpinned headers make no
   stacking context of their own, so whatever they lift above their
   neighbors (a selection's outline, a fill handle, a focused header's
   ring, a resize handle) stays below every pinned column, and a pinned
   column stays below a pinned row, the sticky head and foot.

   The rule: what scrolls passes under what stays. A merged cell scrolls
   with the rows, a pinned cell stays put sideways, a pinned row stays put
   up and down, the head and foot stay put both ways. A validation message
   belongs to its cell and moves with it, so it paints over everything in
   its cell's layer and under every layer its cell scrolls under. A sticky
   cell or row is a stacking context that caps what it holds, so the one
   holding a message rises to the top of its own layer, never above the
   next: its box never covers what it scrolls under.

   A message hangs below its cell, over the row under it, and that row's
   cell may hold a message too (a paste or a fill down of text into a
   number column fills a whole pinned column with them). So each layer
   that holds a message is a band, not a level: every row carries
   --dt-rank, its distance from the last rendered row (pinned top rows,
   then the body rows, then pinned bottom rows), and a message's level is
   its band's floor plus its row's rank. An earlier row is always above
   every later row in the same band, however many there are. The template
   sets --dt-band, the number of rendered rows, to the size of a band, and
   every level above a band starts past it.

   Body, in the scroll area (B is --dt-band, r a row's --dt-rank, 0 to B-1):
     1          a cell's overlays: selection outline, fill preview
     2          the fill handle
     3          a merged cell, over the rows it covers
     4 + r      a cell's validation message, and a merged cell that holds one
     4 + B      pinned cells
     5 + B      a merged pinned cell, over the pinned cells it covers
     6 + B + r  a pinned cell, merged or not, that holds a validation message
     6 + 2B     pinned rows, top and bottom
     7 + 2B + r a pinned row that holds a validation message
     7 + 3B     the head and the sticky foot
     8 + 3B     a dragged row
     9 + 3B     the loading veil
   A pinned or dragged row repeats the levels up to 6 + 2B inside itself.
   Head (its own stacking context at 7 + 3B):
     1  a resize handle, over the next header
     2  a focused header button, over the resize handle
     3  pinned headers
     4  a dragged header */
/* The band floors live on the table, where --dt-band is set. A rank is
   added where it is read (a custom property resolves its var() where it is
   declared, so a rank written into one here would always be 0). */
.data-table {
  --dt-z-pin:    calc(4 + var(--dt-band, 1));
  --dt-z-row:    calc(6 + 2 * var(--dt-band, 1));
  --dt-z-frame:  calc(7 + 3 * var(--dt-band, 1));
  --dt-z-drag:   calc(8 + 3 * var(--dt-band, 1));
  --dt-z-veil:   calc(9 + 3 * var(--dt-band, 1));
}

.data-table-head {
  z-index: var(--dt-z-frame);
}

.data-table[data-sticky] .data-table-head {
  position: sticky;
  top:      0;
}

.data-table[data-sticky] .data-table-foot {
  position: sticky;
  bottom:   0;
  z-index:  var(--dt-z-frame);
}

/* Header cells */

.data-table-header {
  box-sizing:      border-box;
  position:        relative;
  display:         flex;
  align-items:     center;
  gap:             var(--space-1);
  min-width:       0;
  min-height:      var(--dt-row-h);
  padding:         0 var(--item-padding-x);
  border-bottom:   var(--surface-border-width) solid var(--rule);
  background:      var(--dt-head-bg);
  color:           var(--ink-soft);
  font-weight:     var(--weight-ui);
  letter-spacing:  var(--tracking-ui);
  text-transform:  var(--case-ui);
  white-space:     nowrap;
  user-select:     none;
}

.data-table-header.is-group {
  justify-content: center;
  border-inline-end: var(--surface-border-width) solid var(--dt-rule);
}

.data-table-header[data-align="end"] {
  justify-content: flex-end;
}

.data-table-header[data-align="center"] {
  justify-content: center;
}

.data-table-header-button {
  display:        inline-flex;
  align-items:    center;
  gap:            var(--space-1);
  min-width:      0;
  height:         var(--control-height-sm);
  margin-inline:  calc(-1 * var(--space-2));
  padding:        0 var(--space-2);
  border:         0;
  border-radius:  var(--radius-control);
  background:     transparent;
  color:          inherit;
  font:           inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor:         pointer;
}

.data-table-header-button:hover,
.data-table-header-button[data-state="open"] {
  background: var(--item-highlight);
  color:      var(--item-highlight-ink);
}

/* Inset and above the resize handle: in a dense row the button fills its
   header, flush with the scroll area's top edge and the first row, and a
   narrow column puts its end under the handle's rule. Below pinned headers,
   which it scrolls under. */
.data-table-header-button:focus-visible {
  position:   relative;
  z-index:    2;
  outline:    none;
  box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus);
}

.data-table-header-label {
  min-width:     0;
  overflow:      hidden;
  text-overflow: ellipsis;
}

.data-table-sort-icon {
  position: relative;
  display:  inline-flex;
  color:    var(--ink-dim);
}

.data-table-sort-icon[data-sorted] {
  color: var(--ink);
}

.data-table-sort-icon svg,
.data-table-header svg {
  width:  var(--icon-size-sm);
  height: var(--icon-size-sm);
}

/* A sort's rank sits on the icon's top end corner and takes no room: a
   header that is its column's widest content does not widen when a second
   sort ranks it. */
.data-table-sort-index {
  position:             absolute;
  inset-block-start:    calc(-1 * var(--space-1));
  inset-inline-start:   100%;
  color:                var(--ink-dim);
  font-size:            var(--text-caption);
  line-height:          1;
  font-variant-numeric: tabular-nums;
}

/* The grip takes its own slot at the header's start, always in layout and
   only faded in on hover, so the label sits a fixed gap after it and nothing
   moves when the pointer arrives. The sort button's negative start margin
   (which lines a plain header's label up with its cells) is dropped here so
   its hover background never reaches the grip. */
.data-table-header:has(> .data-table-header-grip) {
  padding-inline-start: calc(var(--item-padding-x) / 2);
}

.data-table-header:has(> .data-table-header-grip) .data-table-header-button {
  margin-inline-start: 0;
}

.data-table-header-grip {
  flex:            none;
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
  align-self:      stretch;
  color:           var(--ink-dim);
  opacity:         0;
  cursor:          grab;
  touch-action:    none;
}

.data-table-header[data-align="end"] > .data-table-header-grip {
  margin-inline-end: auto;
}

.data-table-header:hover > .data-table-header-grip,
.data-table-header[data-dragging] > .data-table-header-grip {
  opacity: 1;
}

.data-table-header[data-dragging] {
  z-index:    4;
  opacity:    0.85;
  box-shadow: var(--overlay-shadow);
  cursor:     grabbing;
}

.data-table:dir(rtl) {
  --dt-x: -1;
}

.data-table-header[data-drop] {
  box-shadow: inset calc(var(--focus-ring-width) * var(--dt-x)) 0 0 var(--accent);
}

/* The header menu's start and end icons (Pin to start, Move left) mirror in
   a right-to-left table, as its moves and pins do. */
.data-table:dir(rtl) .data-table-header :is(svg[data-icon="arrow-left"], svg[data-icon="arrow-right"], svg[data-icon="panel-left"], svg[data-icon="panel-right"]) {
  transform: scaleX(-1);
}

.data-table-resizer {
  position:     absolute;
  top:          0;
  bottom:       0;
  inset-inline-end: calc(-1 * var(--space-1));
  z-index:      1;
  width:        var(--space-2);
  cursor:       col-resize;
  touch-action: none;
}

.data-table-resizer::after {
  content:    "";
  position:   absolute;
  top:        25%;
  bottom:     25%;
  left:       50%;
  width:      var(--control-border-width);
  background: var(--rule);
  transform:  translateX(-50%);
}

.data-table-resizer:hover::after,
.data-table-resizer[data-dragging]::after {
  top:        0;
  bottom:     0;
  width:      var(--focus-ring-width);
  background: var(--accent);
}

/* Body cells */

.data-table-row {
  min-height: var(--dt-row-h);
}

.data-table-cell {
  box-sizing:    border-box;
  position:      relative;
  display:       flex;
  align-items:   center;
  min-width:     0;
  min-height:    var(--dt-row-h);
  padding:       var(--dt-pad-y) var(--item-padding-x);
  border-bottom: var(--surface-border-width) solid var(--dt-rule);
  background:    linear-gradient(var(--dt-tint, transparent), var(--dt-tint, transparent)), var(--dt-row-bg);
  white-space:   nowrap;
}

.data-table-row[data-last] > .data-table-cell {
  border-bottom-color: transparent;
}

.data-table-cell[data-align="end"] {
  justify-content:      flex-end;
  text-align:           end;
  font-variant-numeric: tabular-nums;
}

.data-table-cell[data-align="center"] {
  justify-content: center;
  text-align:      center;
}

.data-table-cell.is-drag,
.data-table-header.is-drag,
.data-table-cell.is-select,
.data-table-header.is-select,
.data-table-cell.is-expand,
.data-table-header.is-expand {
  justify-content: center;
  padding-inline:  var(--space-2);
}

.data-table-cell.is-actions,
.data-table-header.is-actions {
  justify-content: flex-end;
  padding-inline:  var(--space-1);
}

.button.data-table-row-grip {
  color:        var(--ink-dim);
  cursor:       grab;
  touch-action: none;
}

.button.data-table-row-grip[aria-disabled="true"] {
  cursor:  not-allowed;
  opacity: 0.4;
}

.data-table-row[data-dragging] {
  position:   relative;
  z-index:    var(--dt-z-drag);
  transition: none;
  box-shadow: var(--overlay-shadow);
  cursor:     grabbing;
}

.data-table-cell[data-selected] {
  --dt-tint: var(--item-selected);
  color:     var(--item-selected-ink);
}

.data-table[data-cell-selection] .data-table-body {
  user-select: none;
}

/* The selection's outline (getSelectionEdges): each outer side of
   a selected range is drawn over the cell, inside its box, so selecting
   never moves anything. */
.data-table-cell[data-edges]::after {
  content:        "";
  position:       absolute;
  inset:          0;
  z-index:        1;
  border:         0 solid var(--accent);
  pointer-events: none;
}

.data-table-cell[data-edges~="top"]::after    { border-top-width:    var(--focus-ring-width); }
.data-table-cell[data-edges~="right"]::after  { border-right-width:  var(--focus-ring-width); }
.data-table-cell[data-edges~="bottom"]::after { border-bottom-width: var(--focus-ring-width); }
.data-table-cell[data-edges~="left"]::after   { border-left-width:   var(--focus-ring-width); }

/* Cell spanning: a merged cell reaches down over the rows it
   covers (each row is one --dt-row-h), above them; a covered cell keeps its
   slot in the row and shows nothing. */
.data-table-cell[aria-rowspan] {
  z-index:       3;
  align-self:    stretch;
  margin-bottom: calc((1 - var(--dt-span-rows, 1)) * var(--dt-row-h));
}

.data-table-cell[aria-rowspan][data-invalid] {
  z-index: calc(4 + var(--dt-rank, 0));
}

.data-table-cell[data-covered="col"] {
  display: none;
}

.data-table-cell[data-covered="row"] {
  background: var(--dt-row-bg);
}

/* A merged block (merges): one cell, its value centered. */
.data-table-cell[data-merged] {
  justify-content: center;
  text-align:      center;
}

/* The fill handle: a square on the selection's bottom end corner, drawn
   over the outline inside the cell, so it never moves anything. */
.data-table-fill-handle {
  position:         absolute;
  inset-inline-end: 0;
  bottom:           0;
  z-index:          2;
  width:            calc(var(--focus-ring-width) * 4);
  height:           calc(var(--focus-ring-width) * 4);
  background:       var(--accent);
  box-shadow:       0 0 0 var(--focus-ring-width) var(--surface-bg);
  cursor:           crosshair;
  touch-action:     none;
}

/* Cells a fill drag would write: a dashed outline inside the cell. */
.data-table-cell[data-fill]::before {
  content:        "";
  position:       absolute;
  inset:          0;
  z-index:        1;
  border:         var(--focus-ring-width) dashed var(--accent);
  pointer-events: none;
}

/* The value bar (the spreadsheet's fx bar): the active range's name, then
   its value, in one row whose size never depends on either. */
.data-table-valuebar {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  min-width:   0;
}

.data-table-valuebar-ref {
  flex:                 none;
  display:              block;
  box-sizing:           border-box;
  width:                calc(var(--control-height) * 3);
  height:               var(--control-height-sm);
  overflow:             hidden;
  padding-inline:       var(--control-padding-x-sm);
  border:               var(--control-border-width) solid var(--rule);
  border-radius:        var(--radius-control);
  background:           var(--control-bg);
  line-height:          calc(var(--control-height-sm) - 2 * var(--control-border-width));
  font-family:          var(--font-mono);
  font-size:            var(--text-ui-sm);
  font-variant-numeric: tabular-nums;
  white-space:          nowrap;
  text-overflow:        ellipsis;
  color:                var(--ink);
}

.data-table-valuebar-fx {
  flex:       none;
  color:      var(--ink-soft);
  font-style: italic;
  font-size:  var(--text-ui-sm);
}

.data-table-valuebar > .input {
  flex:      1 1 auto;
  min-width: 0;
}

.data-table-row[data-drop-row] > .data-table-cell {
  box-shadow: inset 0 calc(var(--focus-ring-width) * -1) 0 var(--focus);
}

.data-table-row:hover > .data-table-cell {
  --dt-tint: var(--item-highlight);
}

.data-table-row[data-state="selected"] > .data-table-cell {
  --dt-tint: var(--item-selected);
  color:     var(--item-selected-ink);
}

.data-table-row[data-tone="success"] > .data-table-cell { --dt-row-bg: color-mix(in oklab, var(--success) 10%, var(--surface-bg)); }
.data-table-row[data-tone="warning"] > .data-table-cell { --dt-row-bg: color-mix(in oklab, var(--warning) 12%, var(--surface-bg)); }
.data-table-row[data-tone="danger"] > .data-table-cell  { --dt-row-bg: color-mix(in oklab, var(--danger) 10%, var(--surface-bg)); }
.data-table-row[data-tone="info"] > .data-table-cell    { --dt-row-bg: color-mix(in oklab, var(--info) 10%, var(--surface-bg)); }

/* A column's background (Retool's column background color): the cell's own
   base color, under the hover and selection tints, over a row tone. */
.data-table-row > .data-table-cell[data-tint="success"] { --dt-row-bg: color-mix(in oklab, var(--success) 14%, var(--surface-bg)); }
.data-table-row > .data-table-cell[data-tint="warning"] { --dt-row-bg: color-mix(in oklab, var(--warning) 16%, var(--surface-bg)); }
.data-table-row > .data-table-cell[data-tint="danger"]  { --dt-row-bg: color-mix(in oklab, var(--danger) 14%, var(--surface-bg)); }
.data-table-row > .data-table-cell[data-tint="info"]    { --dt-row-bg: color-mix(in oklab, var(--info) 14%, var(--surface-bg)); }
.data-table-row > .data-table-cell[data-tint="accent"]  { --dt-row-bg: color-mix(in oklab, var(--accent) 12%, var(--surface-bg)); }
.data-table-row > .data-table-cell[data-tint="muted"]   { --dt-row-bg: var(--bg-muted); }

/* Markdown cells: inline marks only, in the cell's own type, on one line
   that ends in an ellipsis at --dt-text-max like any text. */
.data-table-text.data-table-markdown {
  display:         block;
  overflow:        hidden;
  text-overflow:   ellipsis;
  max-inline-size: var(--dt-text-max);
}

.data-table-cell.is-wrap .data-table-text.data-table-markdown {
  overflow:      visible;
  white-space:   normal;
  overflow-wrap: anywhere;
}

.data-table-markdown code {
  font-family:   var(--font-mono);
  font-size:      var(--text-caption);
  padding-inline: var(--space-1);
  border-radius:  var(--radius-tag);
  background:     var(--bg-muted);
}

.data-table-markdown strong {
  font-weight: var(--weight-strong);
}

.data-table-row.is-group > .data-table-cell,
.data-table-sizer > .data-table-cell[data-sizer-kind="group"] {
  --dt-row-bg: var(--bg-soft);
  font-weight: var(--weight-ui);
}

/* Pinned rows stay in view at the top and bottom of the scroll area. The
   row is the sticky box: a cell could only stick inside its own row. */
.data-table-row[data-pinned="top"] {
  position: sticky;
  top:      var(--dt-head-h, 0px);
  z-index:  var(--dt-z-row);
}

.data-table-row[data-pinned="bottom"] {
  position: sticky;
  bottom:   var(--dt-foot-h, 0px);
  z-index:  var(--dt-z-row);
}

.data-table-row[data-pinned]:has(> .data-table-cell[data-invalid]) {
  z-index: calc(var(--dt-z-row) + 1 + var(--dt-rank, 0));
}

.data-table-row[data-pinned] > .data-table-cell {
  --dt-row-bg: var(--bg-soft);
}

/* Pinned columns. */
.data-table-cell[data-pin] {
  position: sticky;
  z-index:  var(--dt-z-pin);
}

.data-table-cell[data-pin][aria-rowspan] {
  z-index: calc(var(--dt-z-pin) + 1);
}

.data-table-cell[data-pin][data-invalid] {
  z-index: calc(var(--dt-z-pin) + 2 + var(--dt-rank, 0));
}

.data-table-header[data-pin] {
  position: sticky;
  z-index:  3;
}

.data-table[data-scrolled-start] [data-pin="start"]:is(.data-table-cell, .data-table-header) {
  box-shadow: calc(var(--space-1) * var(--dt-x)) 0 var(--space-2) calc(-1 * var(--space-1)) color-mix(in oklab, var(--ink) 18%, transparent);
}

.data-table[data-scrolled-end] [data-pin="end"]:is(.data-table-cell, .data-table-header) {
  box-shadow: calc(var(--space-1) * -1 * var(--dt-x)) 0 var(--space-2) calc(-1 * var(--space-1)) color-mix(in oklab, var(--ink) 18%, transparent);
}

/* Keyboard focus on a cell or header: an inset ring, drawn inside the cell so
   neighbours never cover it. */
.data-table-cell:focus-visible,
.data-table-header:focus-visible {
  outline:    none;
  box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus);
}

.data-table-cell :is(button, a, input, select):focus-visible {
  outline:    none;
  box-shadow: var(--focus-ring);
}

/* Cell content */

.data-table-value {
  display:              flex;
  align-items:          center;
  gap:                  var(--item-gap);
  min-width:            0;
  max-width:            100%;
  padding-inline-start: calc(var(--dt-depth, 0) * var(--space-5, var(--space-4)));
}

.data-table-cell[data-align="end"] > .data-table-value {
  justify-content: flex-end;
}

.data-table-text {
  display:     flex;
  align-items: center;
  gap:         var(--item-gap);
  min-width:   0;
}

.data-table-text > svg {
  flex:   none;
  width:  var(--icon-size);
  height: var(--icon-size);
  color:  var(--ink-soft);
}

.data-table-text-lines {
  display:        flex;
  flex-direction: column;
  min-width:      0;
}

.data-table-text-main,
.data-table-text-sub {
  overflow:      hidden;
  text-overflow: ellipsis;
}

/* One long unbroken value never widens its column past --dt-text-max. */
.data-table-cell:not(.is-wrap) :is(.data-table-text-main, .data-table-text-sub, .data-table-link) {
  max-inline-size: var(--dt-text-max);
}

.data-table-text-sub {
  color:     var(--ink-soft);
  font-size: var(--text-caption);
}

.data-table-text.is-mono {
  font-family: var(--font-mono);
}

.data-table-text.is-muted {
  color: var(--ink-soft);
}

.data-table-cell.is-wrap {
  white-space: normal;
}

.data-table-cell.is-wrap :is(.data-table-text-main, .data-table-text-sub) {
  overflow:      visible;
  overflow-wrap: anywhere;
}

.data-table-avatar {
  flex:          none;
  width:         var(--icon-size-lg);
  height:        var(--icon-size-lg);
  border-radius: var(--radius-pill);
  background:    var(--bg-muted);
  object-fit:    cover;
}

.data-table-link {
  color:                 var(--link);
  text-decoration:       underline;
  text-decoration-color: transparent;
  text-underline-offset: var(--link-underline-offset);
  overflow:              hidden;
  text-overflow:         ellipsis;
}

.data-table-link:hover {
  text-decoration-color: currentColor;
}

.data-table-bool {
  display: inline-flex;
  color:   var(--ink-dim);
}

.data-table-bool[data-on] {
  color: var(--ink);
}

.data-table-bool svg {
  width:  var(--icon-size);
  height: var(--icon-size);
}

.data-table-tags {
  display:   flex;
  flex-wrap: nowrap;
  gap:       var(--space-1);
  overflow:  hidden;
}

.data-table-rating {
  display: inline-flex;
  gap:     calc(var(--space-1) / 2);
  color:   var(--rule);
}

.data-table-star svg {
  width:  var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.data-table-star[data-on] {
  color: var(--warning);
}

.data-table-star[data-on] svg {
  fill: currentColor;
}

.data-table-image {
  display:       block;
  height:        var(--icon-size-lg);
  max-width:     calc(var(--icon-size-lg) * 2);
  border-radius: var(--radius-item);
  background:    var(--bg-muted);
  object-fit:    cover;
}

.data-table-cell[data-type="json"] .data-table-text-main,
.data-table-cell[data-type="bigint"] .data-table-text-main {
  font-variant-numeric: tabular-nums;
}

.data-table-progress {
  display:     flex;
  align-items: center;
  gap:         var(--item-gap);
  width:       100%;
  min-width:   calc(var(--control-height) * 2.5);
}

.data-table-progress-track {
  flex:          1;
  height:        calc(var(--space-1) * 1.5);
  overflow:      hidden;
  border-radius: var(--radius-pill);
  background:    var(--bg-muted);
}

.data-table-progress-bar {
  display:       block;
  height:        100%;
  border-radius: inherit;
  background:    var(--accent);
}

.data-table-progress-text {
  min-width:   4ch;
  color:       var(--ink-soft);
  font-size:   var(--text-caption);
  text-align:  end;
  font-variant-numeric: tabular-nums;
}

.data-table-actions {
  display:     inline-flex;
  align-items: center;
  gap:         var(--space-1);
}

.data-table-menu-group {
  display: contents;
}

/* Inline row actions show on hover and focus; they keep their space. */
.button.data-table-inline-action {
  opacity: 0;
}

.data-table-row:hover .button.data-table-inline-action,
.data-table-row:focus-within .button.data-table-inline-action {
  opacity: 1;
}

/* A touch screen has no hover to reveal them: the row actions and the
   column grip stay shown there. */
@media (hover: none) {
  .button.data-table-inline-action,
  .data-table-header-grip {
    opacity: 1;
  }
}

.button.data-table-expander svg {
  transition: transform var(--duration-enter) var(--ease-enter);
}

.button.data-table-expander[data-state="open"] svg {
  transform: rotate(90deg);
}

[dir="rtl"] .button.data-table-expander[data-state="closed"] svg {
  transform: scaleX(-1);
}

.data-table-group {
  display:     inline-flex;
  align-items: center;
  gap:         var(--space-1);
  min-width:   0;
}

.data-table-group-count,
.data-table-aggregate {
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.data-table-tree {
  display:            inline-flex;
  margin-inline-start: calc(-1 * var(--space-1));
}

.data-table-radio-input {
  margin:       0;
  accent-color: var(--accent);
}

/* Editing: an editor looks like the value until it is entered. */
.data-table-cell[data-editable] {
  cursor: text;
}

/* A cell holding an editor gives it the whole column, so a value is only
   cut where the column ends, and then with an ellipsis (its title has it all). */
.data-table-value:has(> .data-table-editor, > .data-table-editor-select) {
  flex:  1 1 auto;
  width: 100%;
}

.data-table-editor {
  flex:           1 1 auto;
  width:          100%;
  text-overflow:  ellipsis;
  min-width:      calc(var(--control-height) * 2);
  height:         auto;
  margin:         0;
  padding:        0;
  border:         0;
  border-radius:  var(--radius-xs, 0);
  background:     transparent;
  color:          inherit;
  font:           inherit;
  text-align:     inherit;
  outline:        none;
  box-shadow:     none;
}

.data-table-editor[type="checkbox"] {
  width:  auto;
  min-width: 0;
}

.data-table-cell[data-editing] {
  box-shadow: inset 0 0 0 var(--focus-ring-width) var(--accent);
  --dt-row-bg: var(--control-bg);
}

.data-table-cell[data-editing] .data-table-editor {
  box-shadow: none;
}

.data-table-editor-select {
  width: 100%;
}

/* A spreadsheet cell (cellSelection): the value shows as text, formatted,
   and the editor takes its place only while the cell is entered (Enter, F2,
   typing, a double click). Both share one grid cell, so nothing moves. */
.data-table-value:has(> .data-table-sheet) {
  flex:  1 1 auto;
  width: 100%;
}

.data-table-sheet {
  display:  grid;
  flex:     1 1 auto;
  width:    100%;
  min-width: 0;
}

.data-table-sheet > * {
  grid-area: 1 / 1;
  min-width: 0;
}

.data-table-sheet-text {
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
}

.data-table-sheet > .data-table-editor {
  min-width:      0;
  opacity:        0;
  pointer-events: none;
}

.data-table-cell[data-editing] .data-table-sheet > .data-table-editor {
  opacity:        1;
  pointer-events: auto;
}

.data-table-cell[data-editing] .data-table-sheet-text {
  visibility: hidden;
}

.data-table-editor-select .native-select-control {
  height:     auto;
  padding-block: 0;
  border:     0;
  background: transparent;
  box-shadow: none;
}

/* An edited value waiting in the changeset: a corner mark and a tint. */
.data-table-cell[data-edited] {
  --dt-tint: color-mix(in oklab, var(--accent) 12%, transparent);
}

.data-table-cell[data-edited]::before {
  content:    "";
  position:   absolute;
  top:        0;
  inset-inline-end: 0;
  border-top: var(--space-2) solid var(--accent);
  border-inline-start: var(--space-2) solid transparent;
}

.data-table-cell[data-invalid] {
  box-shadow: inset 0 0 0 var(--focus-ring-width) var(--danger);
}

/* The validation message floats under the cell: it pushes nothing. Its
   level is in the grid's stacking order (THE GRID). */
.data-table-error {
  position:       absolute;
  top:            calc(100% - var(--space-1));
  inset-inline-start: var(--item-padding-x);
  z-index:        calc(4 + var(--dt-rank, 0));
  padding:        var(--space-1) var(--space-2);
  border-radius:  var(--radius-item);
  background:     var(--danger-fill, var(--danger));
  color:          var(--white);
  font-size:      var(--text-caption);
  white-space:    nowrap;
  box-shadow:     var(--overlay-shadow);
  pointer-events: none;
}

/* Detail, empty, summary, spacer */

/* A row spanning every column (detail, empty) takes the width the columns
   give it and gives none back: without inline-size containment its content
   would widen the auto tracks it spans the moment it opens. */
.data-table-detail {
  grid-column:   1 / -1;
  contain:       inline-size;
  padding:       var(--surface-padding-sm);
  border-bottom: var(--surface-border-width) solid var(--dt-rule);
  background:    var(--bg-soft);
  white-space:   normal;
}

.data-table-empty {
  grid-column:     1 / -1;
  contain:         inline-size;
  display:         flex;
  align-items:     center;
  justify-content: center;
  min-height:      calc(var(--dt-row-h) * 3);
  color:           var(--ink-soft);
}

.data-table-summary-cell {
  --dt-row-bg:   var(--bg-soft);
  border-top:    var(--surface-border-width) solid var(--rule);
  border-bottom: 0;
  font-weight:   var(--weight-ui);
}

.data-table-spacer {
  grid-column: 1 / -1;
}

/* Loading: a veil and a spinner over the rows; nothing moves. */
.data-table-loading {
  position:        absolute;
  inset:           0;
  z-index:         var(--dt-z-veil);
  display:         flex;
  align-items:     center;
  justify-content: center;
  background:      color-mix(in oklab, var(--surface-bg) 60%, transparent);
}

.data-table-spinner {
  width:            var(--icon-size-lg);
  height:           var(--icon-size-lg);
  box-sizing:       border-box;
  border:           calc(var(--icon-stroke) * 1px) solid var(--ink-soft);
  border-top-color: transparent;
  border-radius:    var(--radius-full);
  animation:        data-table-spin calc(var(--duration-slower) * 1.5) linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .data-table-spinner {
    animation-duration: calc(var(--duration-slower) * 4);
  }
}

@keyframes data-table-spin {
  to { transform: rotate(360deg); }
}

/* Footer */

.data-table-footer {
  display:         flex;
  flex-wrap:       wrap;
  align-items:     center;
  justify-content: space-between;
  gap:             var(--space-2) var(--space-4);
  color:           var(--ink-soft);
  font-size:       var(--text-caption);
}

.data-table-footer-start {
  flex: 1 1 auto;
  font-variant-numeric: tabular-nums;
}

/* The text holds the width of its widest value (data-max), so paging
   and selecting never rewrap the footer. */
.data-table-selection-text[data-max],
.data-table-rows-text[data-max] {
  display: inline-block;
}

.data-table-selection-text[data-max]::before,
.data-table-rows-text[data-max]::before {
  content:    attr(data-max);
  display:    block;
  height:     0;
  overflow:   hidden;
  visibility: hidden;
}

.data-table-changes {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  color:       var(--ink);
}

.data-table-pagination {
  display:     flex;
  flex-wrap:   wrap;
  align-items: center;
  gap:         var(--space-2) var(--space-5, var(--space-4));
}

.data-table-page-size {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  color:       var(--ink);
}

.data-table-page-jump {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  color:       var(--ink);
}

.data-table-page-jump > .data-table-page-jump-input.input {
  width: calc(var(--control-height) * 2);
}

.data-table-page-text {
  min-width:   12ch;
  color:       var(--ink);
  font-weight: var(--weight-ui);
  text-align:  center;
  font-variant-numeric: tabular-nums;
}

.data-table-page-buttons {
  display: flex;
  gap:     var(--space-2);
}

/* First, previous, next and last point toward the start and end of the
   reading direction, as Pagination's do. */
.data-table:dir(rtl) .data-table-page-buttons svg {
  transform: scaleX(-1);
}

@media (max-width: 40rem) {
  .data-table-search {
    flex: 1 1 100%;
  }

  .button.data-table-first,
  .button.data-table-last,
  .data-table-page-size-label {
    display: none;
  }

  .data-table-rule {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  }

  .data-table-rule-join {
    grid-column: 1 / -1;
  }

  .data-table-rule-values {
    grid-column: 1 / 3;
  }
}

}
