@ethlete/components 1.0.0-next.68 → 1.0.0-next.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/design/button.ts +57 -0
  3. package/design/form-field.ts +210 -0
  4. package/design/select.ts +110 -0
  5. package/fesm2022/ethlete-components-testing.mjs +3 -1
  6. package/fesm2022/ethlete-components-testing.mjs.map +1 -1
  7. package/fesm2022/ethlete-components.mjs +1543 -352
  8. package/fesm2022/ethlete-components.mjs.map +1 -1
  9. package/package.json +20 -14
  10. package/src/lib/accordion/accordion-group.component.css +12 -0
  11. package/src/lib/accordion/accordion.component.css +185 -0
  12. package/src/lib/avatar/avatar-group.component.css +31 -0
  13. package/src/lib/avatar/avatar.component.css +75 -0
  14. package/src/lib/badge/badge.component.css +111 -0
  15. package/src/lib/banner/banner.component.css +94 -0
  16. package/src/lib/bracket/bracket-default-continue.component.css +29 -0
  17. package/src/lib/bracket/bracket-default-final-match.component.css +76 -0
  18. package/src/lib/bracket/bracket-default-match.component.css +21 -0
  19. package/src/lib/bracket/bracket-default-round-header.component.css +36 -0
  20. package/src/lib/bracket/bracket-participants.component.css +13 -0
  21. package/src/lib/bracket/bracket-pick-card.component.css +232 -0
  22. package/src/lib/bracket/bracket-rounds-list.component.css +75 -0
  23. package/src/lib/bracket/bracket-skeleton.component.css +55 -0
  24. package/src/lib/bracket/bracket.component.css +102 -0
  25. package/src/lib/bracket/layouts/swiss/bracket-swiss-styles.component.css +10 -0
  26. package/src/lib/breadcrumb/breadcrumb-overflow.component.css +38 -0
  27. package/src/lib/breadcrumb/breadcrumb.component.css +120 -0
  28. package/src/lib/button/button-properties-styles.component.css +143 -0
  29. package/src/lib/button/button.component.css +196 -0
  30. package/src/lib/button/fab.component.css +252 -0
  31. package/src/lib/button/icon-button.component.css +143 -0
  32. package/src/lib/button/split-button.component.css +77 -0
  33. package/src/lib/button/text-button.component.css +140 -0
  34. package/src/lib/button/window-control-button.component.css +158 -0
  35. package/src/lib/calendar/calendar-coarse-grid-styles.component.css +29 -0
  36. package/src/lib/calendar/calendar-comparison-band-styles.component.css +36 -0
  37. package/src/lib/calendar/calendar-week-numbers-styles.component.css +32 -0
  38. package/src/lib/calendar/calendar.component.css +366 -0
  39. package/src/lib/card/card.component.css +39 -0
  40. package/src/lib/carousel/carousel-autoplay-styles.component.css +166 -0
  41. package/src/lib/carousel/carousel-transition-styles.component.css +179 -0
  42. package/src/lib/carousel/carousel.component.css +142 -0
  43. package/src/lib/chart/bar-chart.component.css +176 -0
  44. package/src/lib/chart/chart-axis.component.css +40 -0
  45. package/src/lib/chart/chart-grid.component.css +6 -0
  46. package/src/lib/chart/chart-legend.component.css +46 -0
  47. package/src/lib/chart/chart-tooltip.component.css +34 -0
  48. package/src/lib/chart/line-chart-tooltip.component.css +51 -0
  49. package/src/lib/chart/line-chart.component.css +234 -0
  50. package/src/lib/chart/pie-chart.component.css +176 -0
  51. package/src/lib/chart/sankey-chart.component.css +221 -0
  52. package/src/lib/chip/chip.component.css +155 -0
  53. package/src/lib/command-palette/command-palette-item.component.css +94 -0
  54. package/src/lib/command-palette/command-palette.component.css +100 -0
  55. package/src/lib/description-list/description-list.component.css +77 -0
  56. package/src/lib/divider/divider.component.css +36 -0
  57. package/src/lib/empty-state/empty-state.component.css +73 -0
  58. package/src/lib/floating-action/floating-action-styles.component.css +78 -0
  59. package/src/lib/focus-ring/focus-ring-styles.component.css +13 -0
  60. package/src/lib/forms/cascader/cascader-breadcrumb-styles.component.css +98 -0
  61. package/src/lib/forms/cascader/cascader-panel.component.css +386 -0
  62. package/src/lib/forms/cascader/cascader-sheet-styles.component.css +241 -0
  63. package/src/lib/forms/cascader/cascader.component.css +52 -0
  64. package/src/lib/forms/checkbox/checkbox.component.css +247 -0
  65. package/src/lib/forms/choice-field/choice-field-card-styles.component.css +59 -0
  66. package/src/lib/forms/choice-field/choice-field.component.css +186 -0
  67. package/src/lib/forms/color-input/color-input.component.css +108 -0
  68. package/src/lib/forms/color-input/color-picker-panel.component.css +349 -0
  69. package/src/lib/forms/date-time/date-input/date-input.component.css +35 -0
  70. package/src/lib/forms/date-time/date-picker-panel.component.css +115 -0
  71. package/src/lib/forms/date-time/date-range-input/date-range-input.component.css +16 -0
  72. package/src/lib/forms/date-time/date-range-presets.component.css +60 -0
  73. package/src/lib/forms/date-time/date-time-input/date-time-input.component.css +151 -0
  74. package/src/lib/forms/date-time/date-time-range-input/date-time-range-input.component.css +121 -0
  75. package/src/lib/forms/date-time/duration-input/duration-input.component.css +88 -0
  76. package/src/lib/forms/date-time/range-input-shell-styles.component.css +55 -0
  77. package/src/lib/forms/date-time/time-input/time-input.component.css +35 -0
  78. package/src/lib/forms/date-time/time-range-input/time-range-input.component.css +16 -0
  79. package/src/lib/forms/dropzone/dropzone-item.component.css +51 -0
  80. package/src/lib/forms/dropzone/dropzone-preview-styles.component.css +67 -0
  81. package/src/lib/forms/dropzone/dropzone-readonly-styles.component.css +34 -0
  82. package/src/lib/forms/dropzone/dropzone.component.css +267 -0
  83. package/src/lib/forms/form-field/counter.component.css +25 -0
  84. package/src/lib/forms/form-field/form-field-control-suffix-styles.component.css +116 -0
  85. package/src/lib/forms/form-field/form-field-rich-text-styles.component.css +31 -0
  86. package/src/lib/forms/form-field/form-field-suffix-overlap-styles.component.css +66 -0
  87. package/src/lib/forms/form-field/form-field-text-shell-styles.component.css +312 -0
  88. package/src/lib/forms/form-field/form-field-textarea-styles.component.css +32 -0
  89. package/src/lib/forms/form-field/form-field.component.css +328 -0
  90. package/src/lib/forms/form-field/form-support-styles.component.css +102 -0
  91. package/src/lib/forms/form-field/headless/label.directive.css +25 -0
  92. package/src/lib/forms/form-field/hint.component.css +20 -0
  93. package/src/lib/forms/input/input.component.css +49 -0
  94. package/src/lib/forms/input/number-input.component.css +128 -0
  95. package/src/lib/forms/input/password-input.component.css +103 -0
  96. package/src/lib/forms/multi-language-rich-text-editor/multi-language-rich-text-editor.component.css +8 -0
  97. package/src/lib/forms/multi-language-rich-text-editor/tools/multi-language-rich-text-editor-language-tool.component.css +104 -0
  98. package/src/lib/forms/otp-input/otp-input.component.css +194 -0
  99. package/src/lib/forms/phone-input/phone-input.component.css +126 -0
  100. package/src/lib/forms/rating/rating.component.css +197 -0
  101. package/src/lib/forms/rich-text-editor/rich-text-content-styles.component.css +234 -0
  102. package/src/lib/forms/rich-text-editor/rich-text-editor-floating-toolbar.component.css +90 -0
  103. package/src/lib/forms/rich-text-editor/rich-text-editor-image-editor.component.css +172 -0
  104. package/src/lib/forms/rich-text-editor/rich-text-editor-link-editor.component.css +149 -0
  105. package/src/lib/forms/rich-text-editor/rich-text-editor-token-palette.component.css +18 -0
  106. package/src/lib/forms/rich-text-editor/rich-text-editor-token-popup.component.css +199 -0
  107. package/src/lib/forms/rich-text-editor/rich-text-editor.component.css +175 -0
  108. package/src/lib/forms/rich-text-editor/rich-text-viewer.component.css +6 -0
  109. package/src/lib/forms/rich-text-editor/tools/rich-text-editor-align-tool.component.css +5 -0
  110. package/src/lib/forms/rich-text-editor/tools/rich-text-editor-heading-tool.component.css +5 -0
  111. package/src/lib/forms/rich-text-editor/tools/rich-text-editor-image-styles.component.css +109 -0
  112. package/src/lib/forms/rich-text-editor/tools/rich-text-editor-table-styles.component.css +21 -0
  113. package/src/lib/forms/rich-text-editor/tools/rich-text-editor-table-tool.component.css +44 -0
  114. package/src/lib/forms/select/select-all-option.component.css +14 -0
  115. package/src/lib/forms/select/select-extras-styles.component.css +143 -0
  116. package/src/lib/forms/select/select-option-group.component.css +35 -0
  117. package/src/lib/forms/select/select-option-styles.component.css +137 -0
  118. package/src/lib/forms/select/select-panel.component.css +112 -0
  119. package/src/lib/forms/select/select.component.css +180 -0
  120. package/src/lib/forms/selection-card-styles.component.css +136 -0
  121. package/src/lib/forms/selection-list/checkbox-group/checkbox-group-select-all.component.css +89 -0
  122. package/src/lib/forms/selection-list/checkbox-group/checkbox-group.component.css +142 -0
  123. package/src/lib/forms/selection-list/checkbox-group/checkbox-option.component.css +153 -0
  124. package/src/lib/forms/selection-list/radio-group/radio-group.component.css +134 -0
  125. package/src/lib/forms/selection-list/radio-group/radio.component.css +137 -0
  126. package/src/lib/forms/selection-list/segmented-button-group/segmented-button-group.component.css +149 -0
  127. package/src/lib/forms/selection-list/segmented-button-group/segmented-button.component.css +180 -0
  128. package/src/lib/forms/slider/range-slider.component.css +348 -0
  129. package/src/lib/forms/slider/slider.component.css +342 -0
  130. package/src/lib/forms/switch/switch.component.css +187 -0
  131. package/src/lib/forms/tag-input/tag-input.component.css +54 -0
  132. package/src/lib/forms/textarea/textarea-autosize-styles.component.css +17 -0
  133. package/src/lib/forms/textarea/textarea.component.css +40 -0
  134. package/src/lib/grid/grid-debug.component.css +130 -0
  135. package/src/lib/grid/grid-item-default-actions.component.css +11 -0
  136. package/src/lib/grid/grid-item.component.css +172 -0
  137. package/src/lib/grid/grid.component.css +56 -0
  138. package/src/lib/icon/headless/icon-styles.component.css +7 -0
  139. package/src/lib/kbd/kbd.component.css +62 -0
  140. package/src/lib/loader/brand-loader/brand-loader.component.css +130 -0
  141. package/src/lib/loader/progress-bar/progress-bar.component.css +176 -0
  142. package/src/lib/loader/spinner/spinner.component.css +216 -0
  143. package/src/lib/masonry/masonry-styles.component.css +84 -0
  144. package/src/lib/match/match-card.component.css +441 -0
  145. package/src/lib/match/match-participant.component.css +137 -0
  146. package/src/lib/match/match-score.component.css +74 -0
  147. package/src/lib/menu/menu-group-label.component.css +12 -0
  148. package/src/lib/menu/menu-item-shortcut.component.css +10 -0
  149. package/src/lib/menu/menu-item-submenu-icon.component.css +18 -0
  150. package/src/lib/menu/menu-item.component.css +32 -0
  151. package/src/lib/menu/menu-search-styles.component.css +56 -0
  152. package/src/lib/menu/menu-selection-item.component.css +88 -0
  153. package/src/lib/menu/menu-separator.component.css +9 -0
  154. package/src/lib/menu/menu.component.css +296 -0
  155. package/src/lib/notification/notification-stack.component.css +114 -0
  156. package/src/lib/notification/notification.component.css +271 -0
  157. package/src/lib/overlay/alert-dialog/alert-dialog.component.css +45 -0
  158. package/src/lib/overlay/floating-panel-styles.component.css +71 -0
  159. package/src/lib/overlay/floating-tip-styles.component.css +78 -0
  160. package/src/lib/overlay/overlay-container.component.css +167 -0
  161. package/src/lib/overlay/overlay-main-styles.component.css +105 -0
  162. package/src/lib/overlay/overlay-template-host.component.css +7 -0
  163. package/src/lib/overlay/routing/overlay-route-header-template-outlet.component.css +36 -0
  164. package/src/lib/overlay/routing/overlay-router-outlet.component.css +207 -0
  165. package/src/lib/overlay/routing/overlay-shared-route-template-outlet.component.css +5 -0
  166. package/src/lib/overlay/sidebar/overlay-sidebar.component.css +16 -0
  167. package/src/lib/overlay/strategies/anchored-dialog-styles.component.css +51 -0
  168. package/src/lib/overlay/strategies/dialog-styles.component.css +50 -0
  169. package/src/lib/overlay/strategies/full-screen-dialog-styles.component.css +83 -0
  170. package/src/lib/overlay/strategies/overlay-arrow-styles.component.css +112 -0
  171. package/src/lib/overlay/strategies/overlay-origin-clone.component.css +63 -0
  172. package/src/lib/overlay/strategies/sheet-styles.component.css +143 -0
  173. package/src/lib/pagination/page-size-select.component.css +102 -0
  174. package/src/lib/pagination/pagination.component.css +192 -0
  175. package/src/lib/picture/picture.component.css +66 -0
  176. package/src/lib/progress-steps/progress-step.component.css +187 -0
  177. package/src/lib/progress-steps/progress-steps.component.css +26 -0
  178. package/src/lib/query-error/query-error.component.css +21 -0
  179. package/src/lib/scheduler/scheduler-agenda-view.component.css +97 -0
  180. package/src/lib/scheduler/scheduler-appointment-drag-styles.component.css +56 -0
  181. package/src/lib/scheduler/scheduler-appointment-styles.component.css +111 -0
  182. package/src/lib/scheduler/scheduler-edit-color.component.css +16 -0
  183. package/src/lib/scheduler/scheduler-edit-surface-actions.component.css +14 -0
  184. package/src/lib/scheduler/scheduler-edit-surface-breadcrumb.component.css +26 -0
  185. package/src/lib/scheduler/scheduler-edit-surface-children.component.css +57 -0
  186. package/src/lib/scheduler/scheduler-edit-surface-fields.component.css +11 -0
  187. package/src/lib/scheduler/scheduler-edit-surface-footer.component.css +7 -0
  188. package/src/lib/scheduler/scheduler-edit-surface-header.component.css +15 -0
  189. package/src/lib/scheduler/scheduler-edit-time-range.component.css +11 -0
  190. package/src/lib/scheduler/scheduler-month-view.component.css +143 -0
  191. package/src/lib/scheduler/scheduler-time-grid-view.component.css +260 -0
  192. package/src/lib/scheduler/scheduler.component.css +87 -0
  193. package/src/lib/scrollable/headless/scrollable-buttons.component.css +122 -0
  194. package/src/lib/scrollable/headless/scrollable-footer.css +25 -0
  195. package/src/lib/scrollable/headless/scrollable-masks.component.css +112 -0
  196. package/src/lib/scrollable/headless/scrollable-navigation.component.css +94 -0
  197. package/src/lib/scrollable/scrollable.component.css +192 -0
  198. package/src/lib/scrollbar/scrollbar.component.css +110 -0
  199. package/src/lib/skeleton/skeleton.component.css +108 -0
  200. package/src/lib/standings/standings-pick.component.css +172 -0
  201. package/src/lib/standings/standings.component.css +201 -0
  202. package/src/lib/stat-tile/stat-tile-sparkline.component.css +49 -0
  203. package/src/lib/stat-tile/stat-tile.component.css +111 -0
  204. package/src/lib/stream/pip/pip-chrome.component.css +96 -0
  205. package/src/lib/stream/pip/pip-player.component.css +40 -0
  206. package/src/lib/stream/pip/pip-window.component.css +222 -0
  207. package/src/lib/stream/stream-manager-styles.component.css +10 -0
  208. package/src/lib/stream/stream-overlay-card-styles.component.css +43 -0
  209. package/src/lib/stream/stream-player-slot-styles.component.css +14 -0
  210. package/src/lib/table/table-column-chooser.component.css +17 -0
  211. package/src/lib/table/table-column-menu-trigger.component.css +31 -0
  212. package/src/lib/table/table-detail-styles.component.css +103 -0
  213. package/src/lib/table/table-drag-scroll-styles.component.css +18 -0
  214. package/src/lib/table/table-expander-cell.component.css +58 -0
  215. package/src/lib/table/table-filter-trigger.component.css +44 -0
  216. package/src/lib/table/table-group-header-row.component.css +38 -0
  217. package/src/lib/table/table-inline-edit-styles.component.css +20 -0
  218. package/src/lib/table/table-page-sticky-header-styles.component.css +77 -0
  219. package/src/lib/table/table-reorder-overlay.component.css +42 -0
  220. package/src/lib/table/table-resize-grip.component.css +65 -0
  221. package/src/lib/table/table-row-box-styles.component.css +260 -0
  222. package/src/lib/table/table-select-cell.component.css +89 -0
  223. package/src/lib/table/table-skeleton-rows.component.css +19 -0
  224. package/src/lib/table/table-sort-priority-styles.component.css +15 -0
  225. package/src/lib/table/table-sticky-columns-styles.component.css +80 -0
  226. package/src/lib/table/table-virtual-scroll-styles.component.css +9 -0
  227. package/src/lib/table/table.component.css +829 -0
  228. package/src/lib/tabs/nav-tabs/nav-tab-link-styles.component.css +33 -0
  229. package/src/lib/tabs/tab-bar-styles.component.css +173 -0
  230. package/src/lib/tabs/tab-scale-styles.component.css +61 -0
  231. package/src/lib/time-picker/time-picker.component.css +220 -0
  232. package/src/lib/timeline/timeline-item.component.css +116 -0
  233. package/src/lib/timeline/timeline.component.css +7 -0
  234. package/src/lib/toggletip/toggletip.component.css +83 -0
  235. package/src/lib/toolbar/toolbar.component.css +40 -0
  236. package/src/lib/tooltip/tooltip.component.css +64 -0
  237. package/src/lib/tree/tree.component.css +221 -0
  238. package/types/ethlete-components-testing.d.ts +1 -1
  239. package/types/ethlete-components.d.ts +275 -36
@@ -0,0 +1,829 @@
1
+ @layer components {
2
+ @property --et-table-cell-padding-block {
3
+ syntax: '<length>';
4
+ inherits: true;
5
+ initial-value: 10px;
6
+ }
7
+
8
+ @property --et-table-cell-padding-inline {
9
+ syntax: '<length>';
10
+ inherits: true;
11
+ initial-value: 12px;
12
+ }
13
+
14
+ @property --et-table-opacity-disabled {
15
+ syntax: '<number>';
16
+ inherits: true;
17
+ initial-value: 0.4;
18
+ }
19
+
20
+ /* The table owns its scroll container: give the host a bounded block-size to scroll within, and
21
+ the sticky header pins to it (virtualization relies on this too). Without a height it grows to
22
+ content; a too-wide table scrolls horizontally inside itself instead of breaking the page.
23
+
24
+ Laid out as a single-column grid of [grid area, footer bar]: the first row is `1fr` so it grows
25
+ into leftover space when the content is shorter than a bounded host — without it the footer bar
26
+ would end up mid-box with dead space under it (`position: sticky` can't help, since the footer's
27
+ containing block ends with the content). `1fr` means `minmax(auto, 1fr)`, so the row still never
28
+ shrinks below its content and taller content scrolls exactly as before. The column is
29
+ `minmax(0, 1fr)` (not `auto`) so the footer spans the viewport rather than the full track width
30
+ of a horizontally scrolling table; the grid itself overflows that column on purpose. */
31
+ :where(.et-table-host) {
32
+ display: grid;
33
+ grid-template-columns: minmax(0, 1fr);
34
+ grid-template-rows: 1fr auto;
35
+ overflow: auto;
36
+ /* The table owns this scroll container, so it owns the scrollbar in it: left to the platform, a
37
+ horizontally scrolling table ends on a wide light-grey slab that no theme reaches. */
38
+ scrollbar-width: thin;
39
+ scrollbar-color: color-mix(in srgb, var(--et-surface-interaction-solid, gray) 35%, transparent) transparent;
40
+
41
+ /* Skin knobs — the flat cell rules read these; each appearance overrides what it needs. The band
42
+ is a subtle neutral tint mixed over the surface so it stays opaque under the sticky header. */
43
+ --_et-table-band: color-mix(
44
+ in srgb,
45
+ var(--et-surface-interaction-solid, gray) 7%,
46
+ var(--et-surface-background-solid, Canvas)
47
+ );
48
+ /* The opaque paint behind the two bars rows pass under: the sticky header row and the footer bar.
49
+ Both read it, so an app that recolours one cannot leave the other behind. */
50
+ --_et-table-header-bg: var(--et-table-header-background-color, var(--et-surface-background-solid, Canvas));
51
+ --_et-table-header-border: var(--et-surface-border-solid, rgb(128 128 128 / 0.3));
52
+ --_et-table-row-divider: transparent;
53
+ /* The box of a header adornment (a filter trigger, a column menu trigger), read both by the button
54
+ itself and by the height the header row reserves for it. */
55
+ --_et-table-header-adornment-size: 2.4rem;
56
+ /* The opaque paint under a row. A pinned cell has to be opaque for scrolled cells to pass behind it,
57
+ so it cannot use the translucent tints the other cells layer on - it needs each of them mixed over
58
+ what the row is actually painted on. Every `*-solid` below mixes over this one knob, and an
59
+ appearance that paints its rows differently (`cards`) sets it once. */
60
+ --_et-table-row-surface: var(--et-surface-background-solid, Canvas);
61
+ /* Stripe as a translucent tint (layered over the row's paint for normal cells) and as an opaque
62
+ solid (for pinned cells). */
63
+ --_et-table-stripe-bg: transparent;
64
+ --_et-table-stripe-solid: var(--_et-table-row-surface);
65
+ /* The hover tint, in the same two forms and for the same reason. Read as a knob so an appearance
66
+ can drop it, where a row has nothing to react to. */
67
+ --_et-table-hover-tint: color-mix(in srgb, var(--et-surface-interaction-solid, gray) 8%, transparent);
68
+ --_et-table-hover-tint-solid: color-mix(
69
+ in srgb,
70
+ var(--et-surface-interaction-solid, gray) 8%,
71
+ var(--_et-table-row-surface)
72
+ );
73
+ --_et-table-row-hover: var(--_et-table-hover-tint);
74
+ --_et-table-row-hover-solid: var(--_et-table-hover-tint-solid);
75
+ /* Held rather than hovered. Twice the hover tint, the same step the library's buttons take, in the
76
+ same two forms as the hover tint and for the same reason. */
77
+ --_et-table-pressed-tint: color-mix(in srgb, var(--et-surface-interaction-solid, gray) 16%, transparent);
78
+ --_et-table-pressed-tint-solid: color-mix(
79
+ in srgb,
80
+ var(--et-surface-interaction-solid, gray) 16%,
81
+ var(--_et-table-row-surface)
82
+ );
83
+ --_et-table-row-pressed: var(--_et-table-pressed-tint);
84
+ --_et-table-row-pressed-solid: var(--_et-table-pressed-tint-solid);
85
+ /* The tint for a row with nothing to react to, which an appearance drops where its rows are cards.
86
+ A knob of its own rather than the row rule reading the appearance, so the two stay separable. */
87
+ --_et-table-inert-row-hover: var(--_et-table-hover-tint);
88
+ --_et-table-inert-row-hover-solid: var(--_et-table-hover-tint-solid);
89
+
90
+ /* Skin knobs. Every appearance writes what it needs into these, and every table host re-declares
91
+ the defaults on itself — which is what stops a skin from reaching a table nested inside this one.
92
+ A descendant selector from the host cannot do that: a sub table's rows are as much its
93
+ descendants as its own rows are, so `[data-appearance='cards'] .et-table-row` paints the cards
94
+ of every table below it. The custom property stops at the next host that declares it. */
95
+ --_et-table-row-gap-block: 0px;
96
+ --_et-table-row-paint: transparent;
97
+ --_et-table-row-corner: 0px;
98
+ --_et-table-header-gap-block: 0px;
99
+ --_et-table-cell-rule: 0;
100
+ --_et-table-sticky-clip: none;
101
+ --_et-table-busy-margin-block-start: 0px;
102
+ --_et-table-busy-margin-block-end: -2px;
103
+ --_et-table-detail-rule: 1px solid var(--et-surface-border-solid, rgb(128 128 128 / 0.15));
104
+ /* The paint outside a card's rounded corner - the table's own surface. Declared here and not on the
105
+ corner box, which sits inside a card row: a card row raises its elevation, so a var() resolved
106
+ inside it would pick the card's own surface and the rounding would vanish. */
107
+ --_et-table-card-outside: var(--et-surface-background-solid, Canvas);
108
+ /* The mark at an edge rows pass under - a pinned column's inner edge, the sticky header, the footer
109
+ bar. The surface's own interaction neutral rather than a fixed black, which is invisible on a dark
110
+ table: the boundary has to lighten there instead. Named per direction so a cell sitting at two of
111
+ them at once (a pinned header cell) can carry both.
112
+
113
+ Wide and soft rather than tight: the offset carries the whole shadow past the edge it marks, so a
114
+ tighter one puts its own strongest pixel right against that edge and the mark reads as a drawn line
115
+ - a shine on a dark table - instead of as a shadow rising and falling.
116
+
117
+ Each starts at nothing. An edge with everything already in view has nothing behind it to explain,
118
+ so the mark arrives with the scroll offset that puts something there (see the `--scrolled-*`
119
+ classes) - and every rule below composes these, so none of them needs to ask. */
120
+ --_et-table-pin-shadow: var(
121
+ --et-table-pin-shadow-color,
122
+ color-mix(in srgb, var(--et-surface-interaction-solid, black) 30%, transparent)
123
+ );
124
+ /* Per axis as well, because the two marks answer to different designs: an app that draws its own
125
+ rule at a pinned column's edge takes the inline mark to `transparent` and still wants the block
126
+ one, which is the only thing that says a row is behind the header rather than cut off. */
127
+ --_et-table-pin-shadow-inline: var(--et-table-pin-shadow-color-inline, var(--_et-table-pin-shadow));
128
+ --_et-table-pin-shadow-block: var(--et-table-pin-shadow-color-block, var(--_et-table-pin-shadow));
129
+ --_et-table-edge-none: 0 0 transparent;
130
+ --_et-table-edge-start: var(--_et-table-edge-none);
131
+ --_et-table-edge-end: var(--_et-table-edge-none);
132
+ --_et-table-edge-block-start: var(--_et-table-edge-none);
133
+ --_et-table-edge-block-end: var(--_et-table-edge-none);
134
+ }
135
+
136
+ .et-table-host--scrolled-inline-start {
137
+ --_et-table-edge-start: 8px 0 10px -6px var(--_et-table-pin-shadow-inline);
138
+ }
139
+
140
+ .et-table-host--scrolled-inline-end {
141
+ --_et-table-edge-end: -8px 0 10px -6px var(--_et-table-pin-shadow-inline);
142
+ }
143
+
144
+ .et-table-host--scrolled-block-start {
145
+ --_et-table-edge-block-start: 0 8px 10px -6px var(--_et-table-pin-shadow-block);
146
+ }
147
+
148
+ .et-table-host--scrolled-block-end {
149
+ --_et-table-edge-block-end: 0 -8px 10px -6px var(--_et-table-pin-shadow-block);
150
+ }
151
+
152
+ /* Density drives the shared cell-padding custom props. */
153
+ .et-table-host:where([data-density='sm']) {
154
+ --et-table-cell-padding-block: 6px;
155
+ --et-table-cell-padding-inline: 10px;
156
+ }
157
+
158
+ .et-table-host:where([data-density='md']) {
159
+ --et-table-cell-padding-block: 10px;
160
+ --et-table-cell-padding-inline: 12px;
161
+ }
162
+
163
+ .et-table-host:where([data-density='lg']) {
164
+ --et-table-cell-padding-block: 15px;
165
+ --et-table-cell-padding-inline: 16px;
166
+ }
167
+
168
+ /* "Panel" skins wrap the table in a bordered, rounded surface; overflow:auto (above) clips the
169
+ rounded corners and keeps the sticky header inside the panel. */
170
+ .et-table-host:where([data-appearance='enclosed'], [data-appearance='zebra'], [data-appearance='grid']) {
171
+ border: 1px solid var(--et-surface-border-solid, rgb(128 128 128 / 0.3));
172
+ border-radius: var(--et-table-radius, 10px);
173
+ }
174
+
175
+ .et-table-host:where([data-appearance='enclosed']) {
176
+ --_et-table-header-bg: var(--et-table-header-background-color, var(--_et-table-band));
177
+ --_et-table-row-divider: var(--et-surface-border-solid, rgb(128 128 128 / 0.15));
178
+ box-shadow:
179
+ 0 1px 2px rgb(var(--et-shadow-color-rgb, 0 0 0) / 0.04),
180
+ 0 6px 16px rgb(var(--et-shadow-color-rgb, 0 0 0) / 0.06);
181
+ }
182
+
183
+ .et-table-host:where([data-appearance='divided']) {
184
+ --_et-table-row-divider: var(--et-surface-border-solid, rgb(128 128 128 / 0.15));
185
+ }
186
+
187
+ .et-table-host:where([data-appearance='zebra']) {
188
+ --_et-table-header-bg: var(--et-table-header-background-color, var(--_et-table-band));
189
+ --_et-table-stripe-bg: color-mix(in srgb, var(--et-surface-interaction-solid, gray) 4%, transparent);
190
+ --_et-table-stripe-solid: color-mix(
191
+ in srgb,
192
+ var(--et-surface-interaction-solid, gray) 4%,
193
+ var(--_et-table-row-surface)
194
+ );
195
+ }
196
+
197
+ .et-table-host:where([data-appearance='grid']) {
198
+ --_et-table-cell-rule: 1px solid var(--et-surface-border-solid, rgb(128 128 128 / 0.15));
199
+ --_et-table-header-bg: var(--et-table-header-background-color, var(--_et-table-band));
200
+ --_et-table-row-divider: var(--et-surface-border-solid, rgb(128 128 128 / 0.15));
201
+ }
202
+
203
+ .et-table-host:where([data-appearance='bare']) {
204
+ --_et-table-header-border: transparent;
205
+ }
206
+
207
+ /* Cards: every row is a box of its own (see `.et-table-row--box`), painted one surface elevation above
208
+ the table so it reads as a card lying on it (see `.et-table-row--card`), and spaced by a margin
209
+ rather than by the grid's `row-gap` - a gap would push the header, the detail rows and the footer
210
+ apart too. */
211
+ .et-table-host:where([data-appearance='cards']) {
212
+ --_et-table-row-gap-block: var(--et-table-row-gap, 8px);
213
+ --_et-table-row-corner: var(--et-table-row-radius, 10px);
214
+ --_et-table-header-gap-block: var(--et-table-row-gap, 8px);
215
+ --_et-table-sticky-clip: inset(0 -24px);
216
+ /* The first row is a box with a border and rounded corners, which a bar across its top edge would
217
+ cut into - so the bar draws centred in the gap the header already leaves above the first card,
218
+ touching neither. The two margins still cancel the 2px grid row, so a refetch moves nothing. */
219
+ --_et-table-busy-margin-block-start: calc((var(--et-table-row-gap, 8px) + 2px) / -2);
220
+ --_et-table-busy-margin-block-end: calc((var(--et-table-row-gap, 8px) - 2px) / 2);
221
+ --_et-table-detail-rule: 0;
222
+ --_et-table-inert-row-hover: transparent;
223
+ --_et-table-header-bg: var(--et-table-header-background-color, var(--et-surface-background-solid, Canvas));
224
+ --_et-table-row-divider: transparent;
225
+ }
226
+
227
+ .et-table {
228
+ display: grid;
229
+ /* Containing block for the scroll-fade layer, which has to span the full scroll extent. */
230
+ position: relative;
231
+ /* Row heights come from density alone. The host stretches this area into leftover space (see the
232
+ host's grid rows), and a grid's default `align-content: normal` would hand that space to the
233
+ auto-sized rows — so pin the rows to the start and let the slack collect below the last one. */
234
+ align-content: start;
235
+ /* The box must always cover the tracks. At `inline-size: 100%` it stops at the viewport while
236
+ wider tracks scroll on past it, which strands everything positioned against that box: a pinned
237
+ column's containing block, the scroll-fade layer, and the painted surface itself.
238
+
239
+ `min-content` is the sizing that says exactly "as wide as the tracks' own minimums" — a fixed
240
+ track contributes its length, and a `minmax(<min>, 1fr)` one contributes that floor, which is
241
+ precisely what a flexible track collapses to once the rigid ones have eaten the room.
242
+ (`max-content` would not do: it resolves `fr` against cell content, blowing the table out to
243
+ its longest untruncated value.) `min-inline-size` covers the other direction — tracks that
244
+ don't reach the viewport — where the box stretches and the flexible tracks share the slack. */
245
+ inline-size: min-content;
246
+ min-inline-size: 100%;
247
+ /* The table is a surface panel: paint its background so cells (and the sticky
248
+ header, which reuses this token) sit on an opaque, themed surface. */
249
+ background: var(--et-surface-background-solid, Canvas);
250
+ color: var(--et-surface-color-solid, CanvasText);
251
+ font-size: 14px;
252
+ line-height: 1.4;
253
+ }
254
+
255
+ /* The trailing slack cell: table chrome only (band, dividers, rules), never content. Its track is
256
+ `minmax(0, 1fr)`, so on a table wide enough to scroll the track resolves to 0 and any inline padding
257
+ left on the cell becomes overflow past the last column - a header band that outruns the rows under
258
+ it. Every cell kind sets `padding` as a shorthand, and the group cell's rule is in another file, so
259
+ this has to outweigh them rather than merely follow them. */
260
+ .et-table-header-cell.et-table-filler-cell,
261
+ .et-table-cell.et-table-filler-cell,
262
+ .et-table-footer-cell.et-table-filler-cell,
263
+ .et-table-group-cell.et-table-filler-cell {
264
+ padding-inline: 0;
265
+ }
266
+
267
+ /* Rows are semantic (role=row) but layout-transparent, so their cells are the
268
+ actual grid items and every column track lines up across all rows. */
269
+ .et-table-header-row,
270
+ .et-table-row {
271
+ display: contents;
272
+ }
273
+
274
+ .et-table-header-cell,
275
+ .et-table-cell {
276
+ display: flex;
277
+ align-items: center;
278
+ min-inline-size: 0;
279
+ /* Cells stay stretched to the row height (grid default), so their bottom
280
+ borders line up; the flex box centers content within that height. Clip
281
+ overflow so long values never bleed into the next column. */
282
+ overflow: hidden;
283
+ padding: var(--et-table-cell-padding-block) var(--et-table-cell-padding-inline);
284
+ }
285
+
286
+ /* Truncate default text content with an ellipsis instead of overflowing. */
287
+ .et-table-cell-text {
288
+ min-inline-size: 0;
289
+ overflow: hidden;
290
+ text-overflow: ellipsis;
291
+ white-space: nowrap;
292
+ }
293
+
294
+ .et-table-header-cell {
295
+ position: sticky;
296
+ /* Sticks below a group-header row when etTableGroupHeaders sets `--_et-table-group-h` on the host;
297
+ unset (no feature) the fallback is 0 and the header pins to the top. Overlap it by 1px so a
298
+ rounded group-row height can't leave a sub-pixel seam that scrolling rows show through — the group
299
+ cell (higher z-index, opaque, owns the divider) draws over the overlap. */
300
+ inset-block-start: max(0px, calc(var(--_et-table-group-h, 0px) - 1px));
301
+ /* Keeps a feature's controls (filter, column menu) off the label and off each other. It only shows
302
+ on an end-aligned column, where the label's text ends exactly where the round hit area of the
303
+ first control begins and its hover ring would otherwise touch the text. 4px and no more: the gap
304
+ is taken out of the label's own width, and a wider one truncates a narrow header. */
305
+ gap: 4px;
306
+ z-index: 1;
307
+ font-weight: 600;
308
+ color: var(--et-table-header-color, var(--et-surface-color-muted-solid, CanvasText));
309
+ /* Opaque (band or plain surface) so scrolling rows pass behind the pinned header. */
310
+ background: var(--_et-table-header-bg);
311
+ border-block-end: 1px solid var(--_et-table-header-border);
312
+ }
313
+
314
+ /* The adornments are one group, so the cell's gap holds them off the label and nothing holds them
315
+ off each other: two of them are two narrow glyphs in two padded boxes, and a gap on top of that
316
+ padding reads as a hole in the header rather than as spacing. Each box still keeps its own target
317
+ size, so touching costs nothing. */
318
+ .et-table-header-adornments {
319
+ display: flex;
320
+ align-items: center;
321
+ flex: none;
322
+ }
323
+
324
+ /* The header's adornments are `xs` icon buttons, whose 24px box is exactly the WCAG 2.2 target-size
325
+ minimum. Sized here rather than promoted to `sm`, which would take the button - and the header row
326
+ with it - past the label's own line box by half again. `min-*` as well as the size, so an app that
327
+ scales its `xs` down stays over the 24px minimum.
328
+
329
+ Two classes deep, not `:where()`: the button sets its own size at single-class weight, so a
330
+ `:where()` here would tie with it and leave source order to decide. */
331
+ .et-table-header-cell .et-table-filter-trigger,
332
+ .et-table-header-cell .et-table-column-menu-trigger {
333
+ --et-icon-button-size: var(--_et-table-header-adornment-size);
334
+ --et-icon-button-icon-size: 1.6rem;
335
+
336
+ min-width: 24px;
337
+ min-height: 24px;
338
+ }
339
+
340
+ /* An adornment is dropped from a header cell whose column scrolls behind a pinned one, and it is
341
+ taller than the label's line box - so the row would change height as columns pass behind the pins.
342
+ Reserve its box on every header cell instead. `max()` and the `+ 1px` mirror the rule above: the
343
+ button never goes under 24px, and the block-end border counts towards a border-box height. */
344
+ .et-table-host--header-adornments .et-table-header-cell {
345
+ min-block-size: calc(
346
+ max(var(--_et-table-header-adornment-size), 24px) + 2 * var(--et-table-cell-padding-block) + 1px
347
+ );
348
+ }
349
+
350
+ .et-table-cell {
351
+ min-block-size: var(--et-table-row-min-block-size, auto);
352
+ border-block-end: 1px solid var(--_et-table-row-divider);
353
+ }
354
+
355
+ /* Rows are cut off mid-height where they pass under the sticky header and the footer bar, and on
356
+ `cards` that cut goes straight through a card. The shadow says the row is behind a bar rather than
357
+ broken. Same mark as a pinned column's edge, turned through 90° - and a cell at both edges at once
358
+ carries both, since one `box-shadow` replaces the other. Each edge's variable is nothing until
359
+ something is actually behind it, so none of these asks whether the table is scrolled. */
360
+ .et-table-header-cell {
361
+ box-shadow: var(--_et-table-edge-block-start);
362
+ }
363
+
364
+ .et-table-footer,
365
+ .et-table-footer-cell {
366
+ box-shadow: var(--_et-table-edge-block-end);
367
+ }
368
+
369
+ /* A divider separates two rows, so the one that ends the table has nothing to separate: a `divided`
370
+ table would close on a rule with empty space under it, and an `enclosed` or `grid` one would draw it
371
+ a pixel inside its own border. The row keeps its rule while its detail row follows it - the detail
372
+ row then ends the table, and drops its own (see the detail stylesheet). */
373
+ .et-table-row--last:not(:has(+ .et-table-detail-row)) .et-table-cell {
374
+ border-block-end: 0;
375
+ }
376
+
377
+ /* Zebra striping — keyed off the absolute row index so it survives virtualization and expansion. */
378
+ .et-table-row--stripe .et-table-cell {
379
+ background: var(--_et-table-stripe-bg);
380
+ }
381
+
382
+ /* Vertical rules between cells, drawn by the skins that ask for them (dropped on the last column of
383
+ each row). */
384
+ .et-table-header-cell,
385
+ .et-table-cell {
386
+ border-inline-end: var(--_et-table-cell-rule);
387
+ }
388
+
389
+ .et-table-header-row .et-table-header-cell:last-child,
390
+ .et-table-row .et-table-cell:nth-last-child(1 of .et-table-cell) {
391
+ border-inline-end: 0;
392
+ }
393
+
394
+ /* ── Sticky footer row ──────────────────────────────────────────────────
395
+ A summary/totals row pinned to the bottom of the scroll viewport. */
396
+ .et-table-footer-row {
397
+ display: contents;
398
+ }
399
+
400
+ .et-table-footer-cell {
401
+ display: flex;
402
+ align-items: center;
403
+ min-inline-size: 0;
404
+ overflow: hidden;
405
+ padding: var(--et-table-cell-padding-block) var(--et-table-cell-padding-inline);
406
+ position: sticky;
407
+ inset-block-end: 0;
408
+ z-index: 1;
409
+ font-weight: 600;
410
+ /* Opaque band so scrolling rows pass behind the pinned footer. */
411
+ background: var(--_et-table-header-bg);
412
+ border-block-start: 1px solid var(--et-surface-border-solid, rgb(128 128 128 / 0.3));
413
+ }
414
+
415
+ .et-table-footer-cell:where([data-align='center']) {
416
+ justify-content: center;
417
+ text-align: center;
418
+ }
419
+
420
+ .et-table-footer-cell:where([data-align='end']) {
421
+ justify-content: flex-end;
422
+ text-align: end;
423
+ }
424
+
425
+ /* ── Horizontal scroll fades ────────────────────────────────────────────
426
+ The layer covers the grid's whole box — the entire scroll extent, not just the viewport — because
427
+ that is the containing block the two fades inside it stick within: `position: sticky` is clamped to
428
+ its containing block, so a viewport-sized one would let them drift off after a screenful of
429
+ scrolling. Being out of flow, it adds no grid track. `space-between` parks each fade at its own
430
+ edge, giving sticky a resting position to pull away from. */
431
+ .et-table-scroll-fades {
432
+ position: absolute;
433
+ inset: 0;
434
+ display: flex;
435
+ justify-content: space-between;
436
+ z-index: 3;
437
+ pointer-events: none;
438
+ }
439
+
440
+ .et-table-scroll-fade {
441
+ position: sticky;
442
+ flex: none;
443
+ inline-size: var(--et-table-scroll-fade-size, 28px);
444
+ opacity: 0;
445
+ transition: opacity 150ms ease;
446
+ /* The surface's own interaction neutral, which contrasts with it in both directions - a fixed black
447
+ is invisible on a dark table, where the mark has to lighten rather than darken. Not an `@property`,
448
+ so the public knob can fall back to a theme token (see the theming guide). */
449
+ --_et-table-scroll-fade-color: var(
450
+ --et-table-scroll-fade-color,
451
+ color-mix(in srgb, var(--et-surface-interaction-solid, black) 22%, transparent)
452
+ );
453
+ }
454
+
455
+ .et-table-scroll-fade--visible {
456
+ opacity: 1;
457
+ }
458
+
459
+ .et-table-scroll-fade--start {
460
+ background: linear-gradient(to right, var(--_et-table-scroll-fade-color), transparent);
461
+ }
462
+
463
+ .et-table-scroll-fade--end {
464
+ background: linear-gradient(to left, var(--_et-table-scroll-fade-color), transparent);
465
+ }
466
+
467
+ @media (prefers-reduced-motion: reduce) {
468
+ .et-table-scroll-fade {
469
+ transition: none;
470
+ }
471
+ }
472
+
473
+ /* ── Full-width footer slot ─────────────────────────────────────────────
474
+ A content-projection region below the grid for arbitrary controls (paginator,
475
+ page-size select). It's a sibling of the grid — not a grid row — so it spans the
476
+ viewport and stays put during horizontal scroll (sticky start), pinned to the
477
+ bottom of the scroll viewport (sticky block-end), above the pinned footer cells. */
478
+ .et-table-footer {
479
+ display: flex;
480
+ flex-wrap: wrap;
481
+ align-items: center;
482
+ justify-content: space-between;
483
+ gap: 12px;
484
+ position: sticky;
485
+ inset-block-end: 0;
486
+ inset-inline-start: 0;
487
+ z-index: 4;
488
+ padding: var(--et-table-cell-padding-block) var(--et-table-cell-padding-inline);
489
+ background: var(--_et-table-header-bg);
490
+ border-block-start: 1px solid var(--et-surface-border-solid, rgb(128 128 128 / 0.3));
491
+ }
492
+
493
+ /* Secondary text projected into the footer bar — an "Items per page:" label beside a page-size
494
+ select, a record count, a hint. Matches the paginator's own readout exactly, so the texts sharing
495
+ that row read as one style instead of two near-misses. */
496
+ .et-table-footer-label {
497
+ margin: 0;
498
+ font-size: 14px;
499
+ color: var(--et-surface-color-muted-solid, CanvasText);
500
+ }
501
+
502
+ /* Tighten the footer bar on touch layouts, where it otherwise eats a lot of vertical room. */
503
+ @media (pointer: coarse) {
504
+ .et-table-footer {
505
+ gap: 8px;
506
+ padding: 8px var(--et-table-cell-padding-inline);
507
+ }
508
+ }
509
+
510
+ .et-table-header-cell:where([data-align='center']),
511
+ .et-table-cell:where([data-align='center']) {
512
+ justify-content: center;
513
+ text-align: center;
514
+ }
515
+
516
+ .et-table-header-cell:where([data-align='end']),
517
+ .et-table-cell:where([data-align='end']) {
518
+ justify-content: flex-end;
519
+ text-align: end;
520
+ }
521
+
522
+ /* Interactive rows read as clickable via a pointer cursor (the hover tint below applies to every
523
+ row already). The cursor lives on the cells because the row itself is display:contents. */
524
+ .et-table-row--interactive .et-table-cell {
525
+ cursor: pointer;
526
+ }
527
+
528
+ /* Hover only on real hover-capable pointers, so a touch tap doesn't leave the row stuck grey.
529
+ (A hovered *selected* row keeps its accent instead — see the selection feature's styles.) */
530
+ @media (hover: hover) {
531
+ .et-table-row:hover .et-table-cell {
532
+ background: var(--_et-table-row-hover);
533
+ }
534
+ }
535
+
536
+ /* Held. Only on a row that reacts to the press - hover marks every row the pointer crosses, but a row
537
+ that does nothing when you click it must not look as if it did. */
538
+ .et-table-row:where(.et-table-row--interactive, .et-table-row--link):active .et-table-cell {
539
+ background: var(--_et-table-row-pressed);
540
+ }
541
+
542
+ /* ── Focus ──────────────────────────────────────────────────────────────
543
+ One ring for everything the table can focus, and it is drawn *inside* the element it marks: a cell
544
+ clips its overflow, so the SDK's own ring (3px out, see `et-focus-ring`) is cut off on the utility
545
+ columns and at either edge of the table. Same width and colour as that ring, so the table's focus
546
+ still reads as the rest of the app's.
547
+
548
+ The row-link ring and the row/card box itself live in `TableRowLinkStylesComponent` and
549
+ `TableCardRowStylesComponent`, mounted by the table when `[rowLink]` or `[appearance]="'cards'"`
550
+ is set - a table using neither carries none of it. */
551
+ .et-table-header-label:focus-visible,
552
+ .et-table-expander:focus-visible,
553
+ .et-table-cell:focus-visible {
554
+ outline: 2px solid var(--et-theme-color-primary-solid, currentColor);
555
+ outline-offset: -2px;
556
+ }
557
+
558
+ .et-table-header-label {
559
+ border-radius: 4px;
560
+ }
561
+
562
+ /* ── Header label & sort ────────────────────────────────────────────────
563
+ The label fills the cell and, when the column is sortable, *is* the sort button — a big, obvious
564
+ target that needs no control of its own. It grows so the header's adornments (filter trigger,
565
+ column menu) are pushed to the trailing edge, and shrinks so a long header still truncates. */
566
+ .et-table-header-label {
567
+ display: flex;
568
+ align-items: center;
569
+ flex: 1;
570
+ min-inline-size: 0;
571
+ margin: 0;
572
+ padding: 0;
573
+ font: inherit;
574
+ color: inherit;
575
+ text-align: inherit;
576
+ background: none;
577
+ border: none;
578
+ }
579
+
580
+ /* The label's own box, which is what carries the sort button's tint - the label itself grows to push
581
+ the adornments out, so a tint there is a band of colour across the whole column rather than a
582
+ control. The padding is cancelled by an equal negative margin, so the tint stands off the text
583
+ without moving it: the header label still lines up with the cells under it, and the header row
584
+ keeps its height. Sideways that leaves the box 4px wider than the label at each end, which is the
585
+ header cell's own gap - so even a label wide enough to truncate stops where an adornment starts. */
586
+ .et-table-header-label-box {
587
+ display: flex;
588
+ align-items: center;
589
+ gap: 4px;
590
+ min-inline-size: 0;
591
+ padding: 4px;
592
+ margin: -4px;
593
+ border-radius: 4px;
594
+ }
595
+
596
+ /* The header *is* the sort button, so it escalates like any other control in the library: a hover
597
+ tint, then the focus ring, then a stronger tint while it is held. The tint is painted on the label
598
+ and not on the cell, which is pinned and opaque - a tint there would have to be layered over the
599
+ header's own background. */
600
+ .et-table-header-label:where(.et-table-header-label--sortable) {
601
+ cursor: pointer;
602
+ }
603
+
604
+ .et-table-header-label--sortable .et-table-header-label-box {
605
+ background-color: var(--_et-table-header-label-tint, transparent);
606
+ transition: background-color 120ms var(--ease-out-5, ease);
607
+ }
608
+
609
+ @media (hover: hover) {
610
+ .et-table-header-label--sortable:hover:not(:disabled) {
611
+ --_et-table-header-label-tint: var(--_et-table-hover-tint);
612
+ }
613
+ }
614
+
615
+ .et-table-header-label--sortable:active:not(:disabled) {
616
+ --_et-table-header-label-tint: var(--_et-table-pressed-tint);
617
+ }
618
+
619
+ /* Scoped to the cell rather than to the button, so a disabled column that is not sortable - whose
620
+ label is a plain span - dims with the rest of them. */
621
+ .et-table-header-cell:where([data-disabled]) .et-table-header-label {
622
+ opacity: var(--et-table-opacity-disabled);
623
+ }
624
+
625
+ .et-table-header-label:where(.et-table-header-label--sortable:disabled) {
626
+ cursor: not-allowed;
627
+ }
628
+
629
+ .et-table-header-cell:where([data-align='center']) .et-table-header-label {
630
+ justify-content: center;
631
+ }
632
+
633
+ .et-table-header-cell:where([data-align='end']) .et-table-header-label {
634
+ justify-content: flex-end;
635
+ }
636
+
637
+ /* The only sort affordance: absent while unsorted, then the direction the column is sorted by. It is
638
+ accented so a sorted column reads at a glance, and it never reserves space — an unsorted header is
639
+ just its label. */
640
+ /* Sized so the drawn arrow lands on the label's cap height (the glyph fills ~79% of its own box, so
641
+ the box has to be bigger than the mark you want), and nudged up off the line box's centre: the
642
+ line box is padded by the font's descent, which a header without descenders never uses, so
643
+ centring in it leaves the arrow visibly low against the text's own middle. */
644
+ .et-table-sort-indicator {
645
+ flex: none;
646
+ inline-size: 1.15em;
647
+ block-size: 1.15em;
648
+ transform: translateY(-0.045em);
649
+ color: var(--et-theme-color-primary-solid, currentColor);
650
+ /* Reversing the sort turns the arrow over in place — the element stays mounted across the flip,
651
+ so a plain transition on `transform` is the whole animation. The glyph is drawn centred in its
652
+ own box (see ARROW_UP_ICON), which is what keeps the ink still while it rotates. */
653
+ transition: transform 200ms var(--ease-out-5, ease);
654
+ }
655
+
656
+ .et-table-sort-indicator:where([data-direction='desc']) {
657
+ transform: translateY(-0.045em) rotate(180deg);
658
+ }
659
+
660
+ /* Appearing and disappearing are a plain opacity fade: a rotate or a scale here would read as the
661
+ arrow flying in, which is a lot of motion for a mark this small. */
662
+ .et-table-sort-indicator.et-table-sort-indicator--enter {
663
+ animation: et-table-sort-in 150ms ease;
664
+ }
665
+
666
+ .et-table-sort-indicator.et-table-sort-indicator--leave {
667
+ animation: et-table-sort-in 120ms ease reverse forwards;
668
+ }
669
+
670
+ @keyframes et-table-sort-in {
671
+ from {
672
+ opacity: 0;
673
+ }
674
+
675
+ to {
676
+ opacity: 1;
677
+ }
678
+ }
679
+
680
+ @media (prefers-reduced-motion: reduce) {
681
+ .et-table-sort-indicator {
682
+ transition: none;
683
+ }
684
+
685
+ .et-table-sort-indicator.et-table-sort-indicator--enter,
686
+ .et-table-sort-indicator.et-table-sort-indicator--leave {
687
+ animation: none;
688
+ }
689
+ }
690
+
691
+ /* A standalone body row (the empty state, the error state): layout-transparent like a real row, so
692
+ its single cell is the grid item that spans every column track. */
693
+ .et-table-message-row {
694
+ display: contents;
695
+ }
696
+
697
+ /* Centred on the scroll viewport, not on the tracks: the cell spans every column, so on a table wider
698
+ than its viewport a centred message would sit half a scroll extent off to the side, where it is
699
+ cut off or missed entirely. Sized to the viewport and pinned to its leading edge instead, so the
700
+ message stays where the reader is looking however far the table scrolls - the same shape the footer
701
+ bar has. `--_et-table-viewport-inline-size` is measured by the table (see `viewportInlineSize`). */
702
+ .et-table-empty-cell,
703
+ .et-table-error-cell {
704
+ grid-column: 1 / -1;
705
+ position: sticky;
706
+ inset-inline-start: 0;
707
+ inline-size: var(--_et-table-viewport-inline-size, 100%);
708
+ padding: 32px var(--et-table-cell-padding-inline);
709
+ color: var(--et-surface-color-muted-solid, CanvasText);
710
+ text-align: center;
711
+ }
712
+
713
+ /* So a message given a block size of its own - one that stands in for the rows it replaces - holds its
714
+ content in the middle of it rather than at the top. */
715
+ .et-table-empty-cell {
716
+ display: grid;
717
+ place-content: center;
718
+ justify-items: center;
719
+ }
720
+
721
+ /* ── Error state ────────────────────────────────────────────────────────
722
+ A message rather than a row of data: mark and text centred on one line, wrapping on a narrow
723
+ table. Only the mark takes the error colour (scoped in the template) — a wall of red text reads
724
+ as an alarm, and the label still has to be legible on any surface. */
725
+ .et-table-error-cell {
726
+ display: flex;
727
+ flex-wrap: wrap;
728
+ align-items: center;
729
+ align-content: center;
730
+ justify-content: center;
731
+ gap: 8px;
732
+ }
733
+
734
+ .et-table-error-icon {
735
+ flex: none;
736
+ inline-size: 16px;
737
+ block-size: 16px;
738
+ /* Ink, not `primary`: the mark sits on the table's own surface, not on a filled swatch. */
739
+ color: var(--et-theme-color-ink-solid, currentColor);
740
+ }
741
+
742
+ /* A cell that failed on its own keeps its value and gains the mark in front of it, so the row stays
743
+ readable and the failure is local. */
744
+ .et-table-cell:where([data-state='error']) {
745
+ gap: 6px;
746
+ }
747
+
748
+ .et-table-cell-error-icon {
749
+ flex: none;
750
+ inline-size: 14px;
751
+ block-size: 14px;
752
+ color: var(--et-theme-color-ink-solid, currentColor);
753
+ }
754
+
755
+ /* ── Loading ────────────────────────────────────────────────────────────
756
+ A cell mid-save (see `cellState`) lays its stand-in out as its own flex child, so whatever fills it
757
+ stretches to the cell's inline size. The stand-in itself comes from a feature - see
758
+ registerCellPlaceholder - as do the placeholder rows of a first load. */
759
+ .et-table-cell:where([data-state='loading']) {
760
+ display: flex;
761
+ }
762
+
763
+ /* Refetch over rows that are already on screen: a 2px indeterminate bar in the grid row right below
764
+ the header, accented so it reads as activity. It scrolls with the content in a bounded table —
765
+ a refetch is short, and pinning it would mean measuring the sticky header's height every frame. */
766
+ .et-table-busy-bar {
767
+ grid-column: 1 / -1;
768
+ block-size: 2px;
769
+ /* Contributes no height, so starting a refetch never nudges the rows down by 2px: the negative
770
+ margin cancels the size the grid row would take, and the bar paints over the top edge of the
771
+ first row instead. */
772
+ margin-block-start: var(--_et-table-busy-margin-block-start);
773
+ margin-block-end: var(--_et-table-busy-margin-block-end);
774
+ overflow: hidden;
775
+ background: color-mix(in srgb, var(--et-surface-interaction-solid, gray) 10%, transparent);
776
+ }
777
+
778
+ .et-table-busy-bar.et-table-busy-bar--enter {
779
+ animation: et-table-busy-fade 150ms ease;
780
+ }
781
+
782
+ .et-table-busy-bar.et-table-busy-bar--leave {
783
+ animation: et-table-busy-fade 250ms ease reverse forwards;
784
+ }
785
+
786
+ @keyframes et-table-busy-fade {
787
+ from {
788
+ opacity: 0;
789
+ }
790
+
791
+ to {
792
+ opacity: 1;
793
+ }
794
+ }
795
+
796
+ .et-table-busy-bar::after {
797
+ content: '';
798
+ display: block;
799
+ inline-size: 40%;
800
+ block-size: 100%;
801
+ border-radius: 999px;
802
+ background: var(--et-theme-color-primary-solid, currentColor);
803
+ animation: et-table-busy-sweep 1.1s ease-in-out infinite;
804
+ }
805
+
806
+ @keyframes et-table-busy-sweep {
807
+ from {
808
+ transform: translateX(-100%);
809
+ }
810
+
811
+ to {
812
+ transform: translateX(250%);
813
+ }
814
+ }
815
+
816
+ @media (prefers-reduced-motion: reduce) {
817
+ /* The busy bar still has to be visible without motion, so it becomes a static accent rule across
818
+ the table. */
819
+ .et-table-busy-bar::after {
820
+ inline-size: 100%;
821
+ animation: none;
822
+ }
823
+
824
+ .et-table-busy-bar.et-table-busy-bar--enter,
825
+ .et-table-busy-bar.et-table-busy-bar--leave {
826
+ animation: none;
827
+ }
828
+ }
829
+ }