@slotted/styles 0.0.0-stage → 0.1.0-beta.0

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,1000 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * A hierarchy the reader opens, walks and chooses from.
4
+ *
5
+ * ┌─ root ─ the query container ─ the frame ───────────────────────┐
6
+ * │ ┌─ canvas ─ role="tree" ─ scrolls ─ one tab stop ─────────┐ │
7
+ * │ │ ┌─ node ─ role="treeitem" ────────────────────────┐ │ │
8
+ * │ │ │ ┌─ row ─ the band, the target, the indent ─┐ │ │ │
9
+ * │ │ │ │ guide │ expander │ indicator │ icon │ │ │ │
10
+ * │ │ │ │ label │ meta │ loader │ actions │ │ │ │
11
+ * │ │ │ └───────────────────────────────────────────┘ │ │ │
12
+ * │ │ │ ┌─ group ─ role="group" ─ only while open ──┐ │ │ │
13
+ * │ │ │ │ node ▸ … │ │ │ │
14
+ * │ │ │ └───────────────────────────────────────────┘ │ │ │
15
+ * │ │ └──────────────────────────────────────────────────┘ │ │
16
+ * │ │ empty │ │
17
+ * │ └──────────────────────────────────────────────────────────┘ │
18
+ * └─────────────────────────────────────────────────────────────────┘
19
+ *
20
+ * ── Why the indent is on the row and not on the group ───────────────
21
+ *
22
+ * The band a hovered or selected row paints spans the whole width of the
23
+ * tree. Indenting the group — which is what a nested list does on its own —
24
+ * starts that band at the indent, and a selected file three levels down then
25
+ * paints a rectangle that begins in the middle of the page. So the group
26
+ * carries no inset at all, and the row states its own with
27
+ * `padding-inline-start`, from a depth the family writes per node.
28
+ *
29
+ * ── Why the guide is one element ────────────────────────────────────
30
+ *
31
+ * A line per ancestor level is a line per ancestor element, and depth twenty
32
+ * is then twenty elements on every row. One element, sized `depth × step`,
33
+ * with a repeating gradient at the step, draws the same picture at any depth
34
+ * for one box. The cost is that no elbow can be drawn at a last child, which
35
+ * none of the surveyed implementations draws either.
36
+ *
37
+ * ── Why the selected band has no radius ─────────────────────────────
38
+ *
39
+ * With a cascade, a chosen folder and its children are selected together and
40
+ * their bands are adjacent. A radius on each one meets its neighbour at a
41
+ * waist and the column reads as a stack of pills rather than as one block —
42
+ * the defect `selection-block.test.mjs` holds `listbox` to. A tree cannot fix
43
+ * it the way a listbox does, because two adjacent selected rows are often a
44
+ * parent and a child and therefore in two different lists, where `:has(+ …)`
45
+ * cannot reach. A square band has no waist to fix.
46
+ *
47
+ * ── Density ─────────────────────────────────────────────────────────
48
+ *
49
+ * A row is a control, so its height, inset, type, glyph and gap all read
50
+ * control geometry. A dense tree holds more rows and is the same component;
51
+ * only the frame's own inset, radius and shadow come off the space, radius
52
+ * and shadow scales.
53
+ */
54
+ [data-ui='tree'] {
55
+ --_surface: var(--slotted-tree-surface, var(--slotted-surface, #ffffff));
56
+ --_text: var(--slotted-tree-text, var(--slotted-text, #0f172a));
57
+ --_muted: var(--slotted-tree-text-muted, var(--slotted-text-muted, #5b6b7f));
58
+ --_border-color: var(--slotted-tree-border-color, var(--slotted-control-border-color));
59
+ --_border-width: var(--slotted-border-width-thin, 1px);
60
+ --_radius: var(--slotted-tree-radius, var(--slotted-radius-md, 6px));
61
+ --_shadow: var(--slotted-tree-shadow, var(--slotted-control-shadow));
62
+
63
+ /**
64
+ * One quiet ground for hover and for the row the keyboard is on, told apart
65
+ * by the ring rather than by two greys. It is the rule `listbox` settled:
66
+ * a second, darker grey made the pointer's feedback shout louder than the
67
+ * keyboard's position.
68
+ */
69
+ --_highlight: var(--slotted-tree-highlight, var(--slotted-tone-secondary-subtle-hover));
70
+ --_highlight-active: var(
71
+ --slotted-tree-highlight-active,
72
+ var(--slotted-tone-secondary-subtle-active)
73
+ );
74
+ --_selected-surface: var(--slotted-tree-selected-surface, var(--slotted-tone-accent-surface));
75
+ --_selected-text: var(--slotted-tree-selected-text, var(--slotted-tone-accent-text));
76
+ --_selected-edge: var(--slotted-tree-selected-edge, var(--slotted-tone-accent-border));
77
+
78
+ /**
79
+ * The line that says which parent a row belongs to.
80
+ *
81
+ * Measured against the surface it is drawn on: the subtle step alone is
82
+ * 1.23:1 in `light` and 1.25:1 in `dark`, which is a line a reader has to
83
+ * look for; the strong step is 3.59:1 and 3.84:1, which is a separator's
84
+ * weight repeated once per level and reads as a table of columns. The
85
+ * guide is neither — it is a redundant aid, because the indent and
86
+ * `aria-level` already carry the hierarchy — so it wants to be seen at a
87
+ * glance and to stay under the text.
88
+ *
89
+ * Six parts of the strong step to four of the subtle lands between them:
90
+ * `#a1aebd` at 2.26:1 in `light` and `#525258` at 2.39:1 in `dark`,
91
+ * measured on the built page. The dark scheme takes the higher of the two
92
+ * on purpose — a thin light line on a dark ground reads weaker than the
93
+ * same ratio does on white.
94
+ *
95
+ * Mixing the two ends of the scale rather than mixing one end into
96
+ * transparency is what keeps `high-contrast` right: there both ends are
97
+ * black, so the mix is black, the line stays at 21:1, and no rule has to
98
+ * read the scheme.
99
+ */
100
+ --_guide-color: var(
101
+ --slotted-tree-guide-color,
102
+ color-mix(in oklab, var(--slotted-border-strong) 60%, var(--slotted-border-subtle))
103
+ );
104
+
105
+ /**
106
+ * One step of depth, stated from the glyph rather than taken from the space
107
+ * scale.
108
+ *
109
+ * What a reader sees on a row is the distance from its deepest guide line
110
+ * to its own chevron, and that distance is `step - glyph / 2`, because the
111
+ * line sits on the centre of the chevron one level up. With the step read
112
+ * from the space scale and the glyph from the control scale, the two moved
113
+ * apart: measured on the built page, five pixels at `sm`, eight at `md`,
114
+ * eleven at `lg`, and nought at 240 pixels, where the line met the chevron.
115
+ * The tree looked tighter at one size than at another for a reason nobody
116
+ * could name.
117
+ *
118
+ * Half a glyph plus a fixed step of the space scale holds it at eight
119
+ * pixels everywhere, and leaves `md` at exactly the sixteen it already was.
120
+ */
121
+ --_indent: var(
122
+ --slotted-tree-indent,
123
+ calc(var(--_icon-size) / 2 + var(--slotted-space-4, 0.5rem))
124
+ );
125
+
126
+ --_inset-block: var(--slotted-tree-padding-block, var(--slotted-space-2, 0.25rem));
127
+ --_inset-inline: var(--slotted-tree-padding-inline, var(--slotted-space-2, 0.25rem));
128
+ --_row-inset: var(--slotted-control-padding-inline-md, 0.8125rem);
129
+ --_row-height: var(--slotted-control-height-md, 34px);
130
+ --_font-size: var(--slotted-control-font-size-md, 13px);
131
+ --_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
132
+ --_icon-size: var(--slotted-control-icon-size-md, 16px);
133
+ --_gap: var(--slotted-control-gap-md, 0.5rem);
134
+ --_check-size: var(--slotted-selection-size-md, 16px);
135
+
136
+ /**
137
+ * How many rows tall the box is. A tree with no bound is a tree that pushes
138
+ * the page down as its branches open, and a windowed one has no viewport at
139
+ * all.
140
+ */
141
+ --_rows: var(--slotted-tree-rows, 12);
142
+
143
+ /**
144
+ * The cap. Measure belongs to `container`, so the family offers no axis for
145
+ * it and still has to stop a `max-content` row from setting one label the
146
+ * width of the page.
147
+ */
148
+ --_measure: var(--slotted-tree-measure, 28rem);
149
+
150
+ /**
151
+ * `--slotted-tree-depth` is deliberately not aliased here.
152
+ *
153
+ * A custom property composed on an ancestor resolves its `var()` calls
154
+ * there: `--_depth: var(--slotted-tree-depth, 0)` written on this element
155
+ * would substitute the root's own value — nothing, so nought — and every
156
+ * row would inherit that one number however deep its node is. The family
157
+ * writes the depth per node and the two rules that need it read it
158
+ * directly. It is the same trap `--slotted-control-shadow-focus-geometry`
159
+ * is shaped around, and `docs/token-foundation.md` records that one.
160
+ */
161
+
162
+ box-sizing: border-box;
163
+ color: var(--_text);
164
+ /**
165
+ * The query container is the root and the tracks are one element in:
166
+ * `container-type: inline-size` makes an element a query container for its
167
+ * descendants and not for itself, and what answers the width here is the
168
+ * indent the rows inside carry.
169
+ */
170
+ container-name: slotted-tree;
171
+ container-type: inline-size;
172
+ display: block;
173
+ font-family: var(--slotted-font-family-sans);
174
+ font-size: var(--_font-size);
175
+ inline-size: 100%;
176
+ max-inline-size: var(--_measure);
177
+ text-align: start;
178
+ }
179
+
180
+ [data-ui='tree'][data-size='sm'] {
181
+ --_row-inset: var(--slotted-control-padding-inline-sm, 0.625rem);
182
+ --_row-height: var(--slotted-control-height-sm, 30px);
183
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
184
+ --_secondary-font-size: var(--slotted-font-size-xs, 0.75rem);
185
+ --_icon-size: var(--slotted-control-icon-size-sm, 14px);
186
+ --_gap: var(--slotted-control-gap-sm, 0.375rem);
187
+ --_check-size: var(--slotted-selection-size-sm, 14px);
188
+ }
189
+
190
+ [data-ui='tree'][data-size='lg'] {
191
+ --_row-inset: var(--slotted-control-padding-inline-lg, 1rem);
192
+ --_row-height: var(--slotted-control-height-lg, 38px);
193
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
194
+ --_secondary-font-size: var(--slotted-control-font-size-md, 13px);
195
+ --_icon-size: var(--slotted-control-icon-size-lg, 18px);
196
+ --_gap: var(--slotted-control-gap-lg, 0.5rem);
197
+ --_check-size: var(--slotted-selection-size-lg, 18px);
198
+ }
199
+
200
+ /**
201
+ * `framed` is the standalone control; `plain` is what a tree renders as
202
+ * inside a panel, a drawer or a page region that already has an edge.
203
+ */
204
+ [data-ui='tree'][data-variant='framed'] {
205
+ background-color: var(--_surface);
206
+ border: var(--_border-width) solid var(--_border-color);
207
+ border-radius: var(--_radius);
208
+ box-shadow: var(--_shadow);
209
+ }
210
+
211
+ [data-ui='tree'] [data-part='canvas'] {
212
+ /**
213
+ * The block cap is stated rather than the height: a tree of four branches
214
+ * is four rows tall, and one of four hundred stops at `rows` and scrolls.
215
+ * The inset is added so the last row is not cut in half by the padding the
216
+ * frame reserved for itself.
217
+ */
218
+ max-block-size: calc(var(--_rows) * var(--_row-height) + 2 * var(--_inset-block));
219
+ outline: none;
220
+ overflow: auto;
221
+ padding-block: var(--_inset-block);
222
+ padding-inline: 0;
223
+ }
224
+
225
+ [data-ui='tree'] :is([data-part='canvas'], [data-part='group']) {
226
+ list-style: none;
227
+ margin-block: 0;
228
+ margin-inline: 0;
229
+ padding-inline: 0;
230
+ }
231
+
232
+ /**
233
+ * The group carries no inset: the row states the whole indent, so the band
234
+ * can span the tree's full width at every level.
235
+ */
236
+ [data-ui='tree'] [data-part='group'] {
237
+ padding-block: 0;
238
+ padding-inline: 0;
239
+ }
240
+
241
+ /**
242
+ * A node states its own box rather than inheriting one.
243
+ *
244
+ * The family is built on `ul` and `li`, so it is exposed to whatever reset
245
+ * the page around it has: the reference page gave every `li` after the first
246
+ * seven pixels of margin, and the guide line — which spans one row — broke
247
+ * into segments at each of them. The page was fixed where the page is, and a
248
+ * family that only works under one reset is a family waiting for the next
249
+ * one.
250
+ */
251
+ [data-ui='tree'] [data-part='node'] {
252
+ display: block;
253
+ margin-block: 0;
254
+ margin-inline: 0;
255
+ padding-block: 0;
256
+ padding-inline: 0;
257
+ /**
258
+ * The node holds focus and the row draws the ring, so the node draws none
259
+ * of its own. Left to the user agent it drew one anyway — Chrome's is a
260
+ * white line inside a dark one — and a focused branch showed two rings at
261
+ * once: the browser's around the whole subtree, and the family's inside it
262
+ * around the row. Suppressing it is only safe because the row replaces it,
263
+ * which is the condition the rule below satisfies.
264
+ */
265
+ outline: none;
266
+ }
267
+
268
+ [data-ui='tree'] [data-part='row'] {
269
+ align-items: center;
270
+ background-color: transparent;
271
+ color: inherit;
272
+ /**
273
+ * ── The hand, and where the library's two answers meet ───────────────
274
+ *
275
+ * `menu`, `menubar` and `listbox` keep the arrow: an option is one of a set
276
+ * the reader picks from, and the set is the control. `button`, `select` and
277
+ * `combobox` draw the hand, and SL11 records why the arrow was wrong for a
278
+ * trigger — the one control in a form that said nothing happens when it is
279
+ * pressed.
280
+ *
281
+ * A tree row is on both sides of that line. It is an option in a set, and
282
+ * under the default `expandOn="row"` it is also the disclosure that opens
283
+ * its own branch. Drawing the arrow on the element that opens the branch
284
+ * would be the defect SL11 fixed, and drawing two cursors inside one tree —
285
+ * the hand on a branch, the arrow on a leaf that selects — is worse than
286
+ * either answer on its own, which is what this stylesheet did until now.
287
+ *
288
+ * So: the hand on every row that does something, and the arrow on a row
289
+ * that does nothing, which is a leaf in a tree that selects nothing.
290
+ */
291
+ cursor: pointer;
292
+ display: flex;
293
+ /**
294
+ * No `gap`. A gap is drawn between two cells even when one of them is
295
+ * empty, so a tree whose nodes carry no icon opened with a gutter down its
296
+ * start edge and a tree with no mark opened with two. Each optional cell
297
+ * carries its own margin instead, and a cell nothing occupies costs
298
+ * nothing — which is `menu`'s rule and `listbox`'s, and the defect the
299
+ * visual gate found here.
300
+ */
301
+ min-block-size: var(--_row-height);
302
+ outline: none;
303
+ padding-block: 0;
304
+ /**
305
+ * Every level of depth, plus the frame's own inset, before the first cell.
306
+ * The end inset is the frame's alone: a row is as deep as its node and as
307
+ * wide as the tree.
308
+ */
309
+ padding-inline: calc(var(--_inset-inline) + var(--slotted-tree-depth, 0) * var(--_indent))
310
+ var(--_row-inset);
311
+ position: relative;
312
+ transition:
313
+ background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
314
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
315
+ color var(--slotted-control-transition-duration, var(--slotted-duration-short))
316
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
317
+ user-select: none;
318
+ }
319
+
320
+ /**
321
+ * One element, `depth × step` wide, one line per ancestor level, drawn as a
322
+ * gradient that repeats at the step.
323
+ *
324
+ * ── Where the line sits ──────────────────────────────────────────────
325
+ *
326
+ * Under the centre of the chevron of the level above, which is half a glyph
327
+ * from the start of that level's cell — not half a step. The two agree only
328
+ * at `md`, where the step and the glyph are both sixteen pixels; at `sm` and
329
+ * `lg` they differ by a pixel, and under the narrow container queries, where
330
+ * the step drops to twelve and to eight while the glyph does not, by two and
331
+ * by four. Every line was off its chevron at five of the nine combinations.
332
+ *
333
+ * ── Why the line does not stop at the last child ─────────────────────
334
+ *
335
+ * It did, at half the row's height, which is where an elbow would turn. With
336
+ * no elbow to turn into, a chain of only children — every one of them a last
337
+ * child — drew a staircase of short segments with a gap under each, and the
338
+ * picture read as broken rather than as ended. A straight line through every
339
+ * row is what `VS Code`, Primer and Carbon all draw, and what the reviewer
340
+ * asked for at the visual gate.
341
+ */
342
+ [data-ui='tree'] [data-part='guide'] {
343
+ background-image: repeating-linear-gradient(
344
+ to right,
345
+ var(--_guide-color) 0 var(--_border-width),
346
+ transparent var(--_border-width) var(--_indent)
347
+ );
348
+ inline-size: calc(var(--slotted-tree-depth, 0) * var(--_indent));
349
+ inset-block: 0;
350
+ inset-inline-start: calc(var(--_inset-inline) + var(--_icon-size) / 2);
351
+ pointer-events: none;
352
+ position: absolute;
353
+ }
354
+
355
+ [data-ui='tree'] [data-part='guide']:dir(rtl) {
356
+ background-image: repeating-linear-gradient(
357
+ to left,
358
+ var(--_guide-color) 0 var(--_border-width),
359
+ transparent var(--_border-width) var(--_indent)
360
+ );
361
+ }
362
+
363
+ [data-ui='tree'][data-guides='false'] [data-part='guide'] {
364
+ background-image: none;
365
+ }
366
+
367
+ /**
368
+ * The chevron: the `Glyph` the binding draws inside this box for a branch.
369
+ * One glyph turned, rather than two glyphs swapped, so the two states read
370
+ * as one control moving. A leaf keeps the box and the binding draws nothing
371
+ * in it, which is what holds every label in one column whatever its node is.
372
+ */
373
+ [data-ui='tree'] [data-part='expander'] {
374
+ /**
375
+ * The glyph is the icon size and never the box: under `expandOn="expander"`
376
+ * the box grows by padding to the target minimum, and the `icon` box stays
377
+ * in the content box at the size of the icons beside it.
378
+ */
379
+ --slotted-icon-size: var(--_icon-size);
380
+ block-size: var(--_icon-size);
381
+ color: var(--_muted);
382
+ display: flex;
383
+ flex: none;
384
+ inline-size: var(--_icon-size);
385
+ /**
386
+ * The hit area the artwork does not have is drawn with padding taken back
387
+ * as a negative margin, rather than with a pseudo-element hanging out of
388
+ * the box: `overflow-clip-margin` does not contain what hangs out, which is
389
+ * the measurement `docs/token-foundation.md` records.
390
+ */
391
+ margin-inline-end: var(--_gap);
392
+ }
393
+
394
+ [data-ui='tree'] [data-part='expander'] > [data-ui='icon'] {
395
+ transition: rotate var(--slotted-control-transition-duration, var(--slotted-duration-short))
396
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
397
+ }
398
+
399
+ [data-ui='tree'] [data-part='expander']:dir(rtl) > [data-ui='icon'] {
400
+ rotate: 180deg;
401
+ }
402
+
403
+ [data-ui='tree']
404
+ [data-part='node'][data-expanded]
405
+ > [data-part='row']
406
+ [data-part='expander']
407
+ > [data-ui='icon'] {
408
+ rotate: 90deg;
409
+ }
410
+
411
+ /**
412
+ * The chevron is the target under `expandOn="expander"`, and the whole row is
413
+ * the target otherwise. Under the first the box is grown to the WCAG minimum
414
+ * without the artwork growing, and the growth is taken back so no label moves.
415
+ */
416
+ [data-ui='tree'][data-expand-on='expander']
417
+ [data-part='node'][data-branch]
418
+ > [data-part='row']
419
+ [data-part='expander'] {
420
+ box-sizing: content-box;
421
+ cursor: pointer;
422
+ margin-block: calc((var(--slotted-target-min, 24px) - var(--_icon-size)) / -2);
423
+ /**
424
+ * The start is pulled back by exactly what the padding adds, so the artwork
425
+ * does not move off the guide line under it. The end keeps a real gap
426
+ * instead of the whole one: taking the growth off both sides left the
427
+ * chevron and the box touching, and the pair read as one glyph.
428
+ */
429
+ margin-inline-end: calc(
430
+ var(--_gap) - (var(--slotted-target-min, 24px) - var(--_icon-size)) / 2
431
+ );
432
+ margin-inline-start: calc((var(--slotted-target-min, 24px) - var(--_icon-size)) / -2);
433
+ padding-block: calc((var(--slotted-target-min, 24px) - var(--_icon-size)) / 2);
434
+ padding-inline: calc((var(--slotted-target-min, 24px) - var(--_icon-size)) / 2);
435
+ }
436
+
437
+ /**
438
+ * A row that does nothing takes the arrow back. With nothing to select, a
439
+ * leaf is text; and with the chevron as the only target, no row is pressed at
440
+ * all.
441
+ */
442
+ [data-ui='tree'][data-selection='none'][data-expand-on='expander'] [data-part='row'],
443
+ [data-ui='tree'][data-selection='none']
444
+ [data-part='node']:not([data-branch])
445
+ > [data-part='row'] {
446
+ cursor: default;
447
+ }
448
+
449
+ /**
450
+ * A tree the reader may read and not move keeps the arrow throughout, as
451
+ * `listbox` does with the same word.
452
+ */
453
+ [data-ui='tree'][data-readonly] [data-part='row'] {
454
+ cursor: default;
455
+ }
456
+
457
+ /**
458
+ * ── The box, and why `highlight` has none ──────────────────────────────
459
+ *
460
+ * `highlight` says the selection with the row's ground and its ink, and
461
+ * draws no mark at all. A mark needs a cell, a cell that is empty on every
462
+ * unchosen row is a gutter down the start edge of the whole tree, and a tree
463
+ * already spends one cell on the chevron and one per level on the indent.
464
+ * Every surveyed implementation that highlights — Primer, Carbon, the APG's
465
+ * own examples — draws no mark either, and under forced colours the row takes
466
+ * `Highlight` and `HighlightText`, which is the system's own selection rather
467
+ * than a colour of ours. `listbox` keeps its check because a flat list has
468
+ * one cell to spare and no indent; this is where the two families part, and
469
+ * the reason is the indent.
470
+ *
471
+ * A tree that wants a mark asks for `checkbox`, and then the mark is a box
472
+ * the reader can press.
473
+ *
474
+ * ── Why the box is the one `checkbox` draws ────────────────────────────
475
+ *
476
+ * Same square from the selection scale, same medium edge in the strong
477
+ * border colour, same smallest radius, same solid ground when chosen, and the
478
+ * same mark: a whole two pixels of clearance on every side, at every step,
479
+ * revealed by a clip that opens along the stroke rather than by a scale that
480
+ * grows out of the middle. The clearance is a whole number and not a
481
+ * fraction for the reason `checkbox` states at length — a fraction of a
482
+ * sixteen-pixel box rasterises the mark and the square onto different
483
+ * subpixels — and the two families are held together by
484
+ * `tree.styles.test.mjs`, which reads `checkbox.css` and compares them.
485
+ */
486
+ [data-ui='tree'] [data-part='indicator'] {
487
+ --_mark-inset: var(--slotted-tree-mark-inset, 2px);
488
+ --_check-border: var(--slotted-border-width-medium, 1.5px);
489
+ --_mark-size: calc(var(--_check-size) - 2 * var(--_mark-inset));
490
+ --slotted-icon-size: var(--_mark-size);
491
+
492
+ background-color: var(--_surface);
493
+ block-size: var(--_check-size);
494
+ border: var(--_check-border) solid var(--slotted-border-strong);
495
+ border-radius: var(--slotted-radius-xs, 2px);
496
+ box-sizing: border-box;
497
+ cursor: pointer;
498
+ flex: none;
499
+ inline-size: var(--_check-size);
500
+ margin-inline-end: var(--_gap);
501
+ position: relative;
502
+ transition:
503
+ background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
504
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
505
+ border-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
506
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
507
+ }
508
+
509
+ /**
510
+ * The mark is the `Glyph` the binding draws inside the box — the check, or
511
+ * the dash while the node is indeterminate — rather than a second part, so a
512
+ * node stays one part in the DOM. Its `icon` box is placed against the
513
+ * border box and not the content box: `2px - 1.5px` from the padding edge is
514
+ * two whole pixels from the outside of the square, which is where `checkbox`
515
+ * puts its own.
516
+ */
517
+ [data-ui='tree'] [data-part='indicator'] > [data-ui='icon'] {
518
+ /* Physical on purpose: a check is not mirrored in a right-to-left row, so
519
+ neither is the hand that draws it. `checkbox` states the same. */
520
+ clip-path: inset(0% 100% 0% 0%);
521
+ color: var(--slotted-tone-accent-on-solid);
522
+ inset-block-start: calc(var(--_mark-inset) - var(--_check-border));
523
+ inset-inline-start: calc(var(--_mark-inset) - var(--_check-border));
524
+ opacity: 0;
525
+ pointer-events: none;
526
+ position: absolute;
527
+ transition:
528
+ clip-path var(--slotted-duration-medium)
529
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
530
+ opacity var(--slotted-duration-medium)
531
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
532
+ }
533
+
534
+ [data-ui='tree']
535
+ [data-part='node']:is([data-selected], [data-indeterminate])
536
+ > [data-part='row']
537
+ [data-part='indicator'] {
538
+ background-color: var(--slotted-tone-accent-solid);
539
+ border-color: var(--slotted-tone-accent-solid);
540
+ }
541
+
542
+ [data-ui='tree']
543
+ [data-part='node']:is([data-selected], [data-indeterminate])
544
+ > [data-part='row']
545
+ [data-part='indicator']
546
+ > [data-ui='icon'] {
547
+ clip-path: inset(0% 0% 0% 0%);
548
+ opacity: 1;
549
+ }
550
+
551
+ [data-ui='tree'] [data-part='node'][data-disabled] > [data-part='row'] [data-part='indicator'] {
552
+ background-color: var(--slotted-disabled-background, Canvas);
553
+ border-color: var(--slotted-disabled-border, GrayText);
554
+ cursor: not-allowed;
555
+ }
556
+
557
+ /**
558
+ * Only the box mode draws a cell. `highlight` and `none` render no indicator
559
+ * at all, and the rule is written so a consumer who leaves the element in the
560
+ * markup still gets the tree the axis promises.
561
+ */
562
+ [data-ui='tree']:is([data-indicator='highlight'], [data-indicator='none'])
563
+ [data-part='indicator'] {
564
+ display: none;
565
+ }
566
+
567
+ /**
568
+ * The node's own picture, which the consumer supplies. The family owns no
569
+ * folder glyph: a folder is content, and the glyph scale holds none.
570
+ */
571
+ [data-ui='tree'] [data-part='icon'] {
572
+ block-size: var(--_icon-size);
573
+ color: var(--_muted);
574
+ flex: none;
575
+ inline-size: var(--_icon-size);
576
+ margin-inline-end: var(--_gap);
577
+ }
578
+
579
+ /**
580
+ * A tree where some nodes carry a picture and others do not keeps every name
581
+ * in one column: a row with no icon opens the same cell the icon would have
582
+ * taken. Primer asks the consumer to pass a visual on every item or to accept
583
+ * two columns of names; the fact is one the tree can read for itself.
584
+ */
585
+ [data-ui='tree'][data-part='root']:has([data-part='icon'])
586
+ [data-part='row']:not(:has([data-part='icon']))
587
+ [data-part='label'] {
588
+ margin-inline-start: calc(var(--_icon-size) + var(--_gap));
589
+ }
590
+
591
+ /**
592
+ * The three parts that hold the consumer's own words take their direction
593
+ * from those words rather than from the document.
594
+ *
595
+ * `unicode-bidi: plaintext` reads the first strong character and sets the
596
+ * base direction from it, which is what a file tree needs and what the
597
+ * harness showed: in a right-to-left document an English `4 kB` was laid out
598
+ * as `kB 4`, and `“invoices”` had its closing quote moved to the front.
599
+ * `isolate` does not fix either — it gives the span its own context and still
600
+ * takes the base direction from the document. A node's name, its second line
601
+ * and its count are data of unknown direction, and each one is now its own
602
+ * paragraph.
603
+ */
604
+ [data-ui='tree']
605
+ :is([data-part='label'], [data-part='description'], [data-part='meta'], [data-part='empty']) {
606
+ unicode-bidi: plaintext;
607
+ }
608
+
609
+ /**
610
+ * The label shrinks to its own words rather than growing to the row.
611
+ *
612
+ * A label that took the free space put its text at the far edge of that box
613
+ * once `unicode-bidi: plaintext` had given it its own direction: in a
614
+ * right-to-left tree an English name sat against the opposite edge from the
615
+ * indent that placed it. Shrink-wrapping the label and pushing the trailing
616
+ * cells to the end with an automatic margin puts every name against the
617
+ * reader's own start edge in both directions, and leaves the truncation
618
+ * where it was.
619
+ */
620
+ [data-ui='tree'] [data-part='label'] {
621
+ flex: 0 1 auto;
622
+ line-height: var(--slotted-line-height-sm, 1.45);
623
+ /**
624
+ * The one part that gives way. A label that is one unbroken token truncates
625
+ * and stays whole in the DOM, which is the library's standing decision on
626
+ * truncation.
627
+ */
628
+ min-inline-size: 0;
629
+ overflow: hidden;
630
+ text-overflow: ellipsis;
631
+ white-space: nowrap;
632
+ }
633
+
634
+ /**
635
+ * A placeholder row has no words to shrink to, so its label takes the space a
636
+ * name would have taken. Without this the skeleton resolved to nothing and a
637
+ * branch fetching its children drew a one-pixel mark where a row should be —
638
+ * which is what the harness showed.
639
+ */
640
+ /**
641
+ * A placeholder row has no words to shrink to, so its label takes the space a
642
+ * name would have taken. Without this the skeleton resolved to nothing and a
643
+ * branch fetching its children drew a one-pixel mark where a row should be.
644
+ *
645
+ * The three widths are what stop a fetching branch from reading as a table of
646
+ * identical bars: names are not all one length, and a placeholder that claims
647
+ * they are is a placeholder the eye refuses. They are literals because they
648
+ * are a proportion of a row rather than a step on any scale.
649
+ */
650
+ [data-ui='tree'] [data-part='label']:has([data-ui='skeleton']) {
651
+ flex: 1 1 auto;
652
+ max-inline-size: 62%;
653
+ }
654
+
655
+ [data-ui='tree']
656
+ [data-part='node']:nth-child(2n)
657
+ > [data-part='row']
658
+ [data-part='label']:has([data-ui='skeleton']) {
659
+ max-inline-size: 44%;
660
+ }
661
+
662
+ [data-ui='tree']
663
+ [data-part='node']:nth-child(3n)
664
+ > [data-part='row']
665
+ [data-part='label']:has([data-ui='skeleton']) {
666
+ max-inline-size: 73%;
667
+ }
668
+
669
+ /**
670
+ * The band behind a matched span, mixed from the tone's ink rather than taken
671
+ * from its ground, which is the rule `--slotted-highlight-tint-opacity`
672
+ * states and what `select` and `combobox` already draw.
673
+ */
674
+ [data-ui='tree'] [data-part='label'] mark {
675
+ background-color: color-mix(
676
+ in oklab,
677
+ var(--slotted-tone-accent-text) calc(var(--slotted-highlight-tint-opacity, 0.075) * 100%),
678
+ transparent
679
+ );
680
+ color: inherit;
681
+ font-weight: var(--slotted-font-weight-semibold, 600);
682
+ }
683
+
684
+ /**
685
+ * A matched span on a chosen row keeps its weight and drops its band. The
686
+ * band is seven and a half per cent of the tone's ink mixed into the surface,
687
+ * and over the tone's own ground it is neither visible nor clean — two washes
688
+ * of one colour, one of them too faint to read as deliberate.
689
+ */
690
+ [data-ui='tree'] [data-part='node'][data-selected] > [data-part='row'] [data-part='label'] mark {
691
+ background-color: transparent;
692
+ }
693
+
694
+ [data-ui='tree'] [data-part='description'] {
695
+ color: var(--_muted);
696
+ margin-inline-start: var(--_gap);
697
+ flex: 0 1 auto;
698
+ font-size: var(--_secondary-font-size);
699
+ min-inline-size: 0;
700
+ overflow: hidden;
701
+ text-overflow: ellipsis;
702
+ white-space: nowrap;
703
+ }
704
+
705
+ /**
706
+ * The trailing count. It keeps its intrinsic width so a label twice as long
707
+ * in translation pushes on itself rather than on the number beside it.
708
+ */
709
+ [data-ui='tree'] [data-part='meta'] {
710
+ color: var(--_muted);
711
+ flex: none;
712
+ font-size: var(--_secondary-font-size);
713
+ font-variant-numeric: tabular-nums;
714
+ /**
715
+ * The automatic margin pushes the count to the end of the row, and collapses
716
+ * to nothing the moment the name fills the row — which left a truncated
717
+ * label touching its own count. The padding is the separation that does not
718
+ * depend on there being room to spare.
719
+ */
720
+ margin-inline-start: auto;
721
+ padding-inline-start: var(--_gap);
722
+ }
723
+
724
+ /**
725
+ * The wait is `progress-indicator`'s ring, standing where the chevron stands.
726
+ * A family that drew an arc of its own would be a second answer to the same
727
+ * wait, drifting from the first the moment one of them is retuned.
728
+ */
729
+ /**
730
+ * The wait is `progress-indicator`'s ring, in the chevron's own cell and at
731
+ * the chevron's own size, so a branch that starts fetching moves nothing: the
732
+ * name does not step sideways and the rows below do not step down. A family
733
+ * that drew an arc of its own would be a second answer to the same wait,
734
+ * drifting from the first the moment one of them is retuned.
735
+ */
736
+ [data-ui='tree'] [data-part='loader'] {
737
+ --slotted-spinner-size: var(--_icon-size);
738
+
739
+ block-size: var(--_icon-size);
740
+ color: var(--_muted);
741
+ flex: none;
742
+ inline-size: var(--_icon-size);
743
+ margin-inline-end: var(--_gap);
744
+ }
745
+
746
+ /**
747
+ * The consumer's trailing controls. They are always in the DOM and always
748
+ * reachable: revealing them on hover alone puts a control behind a gesture a
749
+ * touch reader does not have.
750
+ */
751
+ [data-ui='tree'] [data-part='actions'] {
752
+ align-items: center;
753
+ display: flex;
754
+ flex: none;
755
+ gap: var(--slotted-space-1, 0.125rem);
756
+ margin-inline-start: auto;
757
+ padding-inline-start: var(--_gap);
758
+ }
759
+
760
+ /* The free space is taken once. With a count before them, the controls sit a
761
+ gap after it rather than splitting the row between the two. */
762
+ [data-ui='tree'] [data-part='meta'] ~ [data-part='actions'] {
763
+ margin-inline-start: 0;
764
+ }
765
+
766
+ [data-ui='tree'] [data-part='empty'] {
767
+ color: var(--_muted);
768
+ padding-block: var(--slotted-space-5, 0.75rem);
769
+ padding-inline: var(--_row-inset);
770
+ text-align: center;
771
+ }
772
+
773
+ /**
774
+ * ── States ─────────────────────────────────────────────────────────
775
+ *
776
+ * A weaker state holds itself back for every stronger one, in the order
777
+ * `STATE_PRECEDENCE` names.
778
+ */
779
+ [data-ui='tree']
780
+ [data-part='node']:not([data-disabled]):not([data-loading])
781
+ > [data-part='row']:hover {
782
+ background-color: var(--_highlight);
783
+ }
784
+
785
+ [data-ui='tree']
786
+ [data-part='node']:not([data-disabled]):not([data-loading])
787
+ > [data-part='row']:active {
788
+ background-color: var(--_highlight-active);
789
+ }
790
+
791
+ /**
792
+ * The row the keyboard is on paints the same quiet ground as hover, and the
793
+ * ring is what tells the two apart.
794
+ */
795
+ [data-ui='tree'] [data-part='node'][data-current]:not([data-disabled]) > [data-part='row'] {
796
+ background-color: var(--_highlight);
797
+ }
798
+
799
+ [data-ui='tree'] [data-part='node'][data-selected]:not([data-disabled]) > [data-part='row'] {
800
+ background-color: var(--_selected-surface);
801
+ color: var(--_selected-text);
802
+ }
803
+
804
+ [data-ui='tree']
805
+ [data-part='node'][data-selected]:not([data-disabled])
806
+ > [data-part='row']
807
+ :is([data-part='icon'], [data-part='meta'], [data-part='expander']) {
808
+ color: inherit;
809
+ }
810
+
811
+ /**
812
+ * A collapsed branch holding a selected descendant. Carbon states the rule
813
+ * and ships no API for it: a selection that disappears when its branch closes
814
+ * is a selection the reader has lost.
815
+ *
816
+ * It is said in ink rather than with a bar on the start edge. R1 —
817
+ * `state-rail.test.mjs` — refuses the bar for `current`, `selected`,
818
+ * `active` and `checked`, and a fact that stands in for a selection is the
819
+ * same claim in a quieter voice; a family allowed to draw the rail here would
820
+ * have found the way around the rule rather than an exception to it. Ink is
821
+ * one of the three devices R1 names, and it is quieter than a ground, which
822
+ * is right: the reader chose the descendant and not this.
823
+ */
824
+ [data-ui='tree']
825
+ [data-part='node'][data-selected-within]:not([data-selected]):not([data-disabled])
826
+ > [data-part='row'] {
827
+ color: var(--_selected-text);
828
+ }
829
+
830
+ [data-ui='tree']
831
+ [data-part='node'][data-selected-within]:not([data-selected]):not([data-disabled])
832
+ > [data-part='row']
833
+ [data-part='expander'] {
834
+ color: inherit;
835
+ }
836
+
837
+ [data-ui='tree'] [data-part='node'][data-disabled] > [data-part='row'] {
838
+ color: var(--slotted-disabled-foreground, GrayText);
839
+ cursor: not-allowed;
840
+ }
841
+
842
+ [data-ui='tree']
843
+ [data-part='node'][data-disabled]
844
+ > [data-part='row']
845
+ :is([data-part='icon'], [data-part='meta'], [data-part='expander'], [data-part='description']) {
846
+ color: inherit;
847
+ }
848
+
849
+ [data-ui='tree'] [data-part='node'][data-loading] > [data-part='row'] {
850
+ cursor: progress;
851
+ }
852
+
853
+ /**
854
+ * The ring is drawn inside the row rather than around it, so a focused row at
855
+ * the top or the bottom of a scrolling canvas is not clipped by its own
856
+ * viewport.
857
+ */
858
+ [data-ui='tree'] [data-part='node']:focus-visible > [data-part='row'],
859
+ [data-ui='tree']
860
+ [data-part='canvas']:focus-visible
861
+ [data-part='node'][data-current]
862
+ > [data-part='row'] {
863
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color);
864
+ outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
865
+ }
866
+
867
+ [data-ui='tree'][data-invalid] {
868
+ --slotted-focus-ring-color: var(--slotted-focus-ring-color-invalid);
869
+ --_border-color: var(--slotted-tone-danger-border);
870
+ /* Width as well as colour (WCAG 1.4.1): in `high-contrast` the danger
871
+ border is the resting edge's black. An edge inside the border, so
872
+ nothing moves, in place of the resting lift — the one `text-field`
873
+ draws. */
874
+ --_invalid-edge-width: var(--slotted-control-invalid-edge-width, 1px);
875
+ --_shadow: inset 0 0 0 var(--_invalid-edge-width) var(--slotted-tone-danger-border);
876
+ }
877
+
878
+ [data-ui='tree'][data-disabled] {
879
+ color: var(--slotted-disabled-foreground, GrayText);
880
+ }
881
+
882
+ [data-ui='tree'][data-disabled][data-variant='framed'] {
883
+ background-color: var(--slotted-disabled-background, Canvas);
884
+ border-color: var(--slotted-disabled-border, GrayText);
885
+ box-shadow: none;
886
+ }
887
+
888
+ /**
889
+ * ── The windowed shape ─────────────────────────────────────────────
890
+ *
891
+ * The scroller moves to the root and the canvas becomes the thing that is
892
+ * sized, which is the split `listbox` already draws: a `ul` cannot be both
893
+ * the box that scrolls and the box that states the height of a list it only
894
+ * holds a slice of. The canvas falls back to `auto`, so a flat tree with no
895
+ * window is laid out exactly as a nested one.
896
+ */
897
+ [data-ui='tree'][data-structure='flat'] {
898
+ max-block-size: calc(var(--_rows) * var(--_row-height) + 2 * var(--_inset-block));
899
+ overflow: auto;
900
+ }
901
+
902
+ [data-ui='tree'][data-structure='flat'] [data-part='canvas'] {
903
+ block-size: var(--_canvas-size, auto);
904
+ max-block-size: none;
905
+ overflow: visible;
906
+ padding-block-start: calc(var(--_inset-block) + var(--_canvas-offset, 0px));
907
+ position: relative;
908
+ }
909
+
910
+ /**
911
+ * A row the window does not cover and the family renders anyway: the active
912
+ * node, and the anchor a range is measured from. Without it the element
913
+ * `aria-activedescendant` names leaves the document under a momentum scroll,
914
+ * and the reader is operating a widget with no focused node at all.
915
+ *
916
+ * It sits at its own place on the canvas rather than being pulled into view,
917
+ * because the reader scrolled it out and moving it back would take the
918
+ * scrollbar away from them.
919
+ */
920
+ [data-ui='tree'] [data-part='node'][data-pinned] {
921
+ inset-block-start: var(--slotted-tree-row-offset, 0px);
922
+ inset-inline: 0;
923
+ position: absolute;
924
+ }
925
+
926
+ /**
927
+ * ── The narrow container ───────────────────────────────────────────
928
+ *
929
+ * The indent is the first thing to give way, because a tree that has lost a
930
+ * step of indent is still readable and one that has lost its labels is not.
931
+ * The breakpoints are literal: `@container (min-width: var(--x))` is invalid
932
+ * CSS, which `docs/token-foundation.md` records.
933
+ *
934
+ * The retuned value lands on the canvas rather than on the root. A container
935
+ * query styles the container's descendants and never the container itself, so
936
+ * a rule for `[data-ui='tree']` inside this block matches nothing — the tree
937
+ * is not its own descendant. Written on the root it looked correct and did
938
+ * nothing, and the harness at 240 pixels is what showed the indent had not
939
+ * moved. It is the second half of the two-element decision the briefing
940
+ * records, and the half that is easy to write and hard to see.
941
+ */
942
+ @container slotted-tree (max-width: 320px) {
943
+ [data-ui='tree'] [data-part='canvas'] {
944
+ --_indent: calc(var(--_icon-size) / 2 + var(--slotted-space-3, 0.375rem));
945
+ }
946
+ }
947
+
948
+ @container slotted-tree (max-width: 240px) {
949
+ [data-ui='tree'] [data-part='canvas'] {
950
+ --_indent: calc(var(--_icon-size) / 2 + var(--slotted-space-2, 0.25rem));
951
+ --_row-inset: var(--slotted-control-padding-inline-sm, 0.625rem);
952
+ }
953
+
954
+ /* The second line has no room to sit beside the first at this width. */
955
+ [data-ui='tree'] [data-part='description'] {
956
+ display: none;
957
+ }
958
+ }
959
+
960
+ /**
961
+ * The one thing the theme cannot reach. Every tone surface becomes `Canvas`
962
+ * under forced colours, so a selection said with a tinted ground alone would
963
+ * say nothing in the mode that needs it most — and under the `highlight`
964
+ * indicator there is no mark to fall back on. `accent` is the selected tone
965
+ * and the theme maps it to `Highlight`, which is what is restated here.
966
+ */
967
+ @media (forced-colors: active) {
968
+ [data-ui='tree'] [data-part='node'][data-selected] > [data-part='row'] {
969
+ background-color: Highlight;
970
+ color: HighlightText;
971
+ forced-color-adjust: none;
972
+ }
973
+
974
+ [data-ui='tree'] [data-part='node'][data-disabled] > [data-part='row'] {
975
+ color: GrayText;
976
+ }
977
+
978
+ /*
979
+ * The box and its mark are painted from a tone. The theme maps `accent` to
980
+ * `Highlight` and `on-solid` to `HighlightText`, so the chosen box keeps
981
+ * both; the mark opts out as well, or the system forces its ink to the
982
+ * text colour and the tick loses its contrast with the box it stands on.
983
+ */
984
+ [data-ui='tree'] [data-part='indicator'] > [data-ui='icon'] {
985
+ forced-color-adjust: none;
986
+ }
987
+ }
988
+
989
+ /*
990
+ * Forced colours remove every box shadow, and the edge with them. The width
991
+ * comes back as an outline inside the border; the
992
+ * ring belongs to a row and leaves it alone.
993
+ */
994
+ @media (forced-colors: active) {
995
+ [data-ui='tree'][data-variant='framed'][data-invalid] {
996
+ outline: var(--_invalid-edge-width) solid CanvasText;
997
+ outline-offset: calc(-1 * (var(--_border-width) + var(--_invalid-edge-width)));
998
+ }
999
+ }
1000
+ }