@ixfx/components 0.16.2 → 0.16.4

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 (111) hide show
  1. package/bundle/index.d.ts +61 -7
  2. package/bundle/index.js +151 -143
  3. package/dist/ac-text.d.ts +1 -1
  4. package/dist/{ac-token-Cq7lsYf-.d.ts → ac-token-qEs3FsQv.d.ts} +1 -1
  5. package/dist/ac-token.d.ts +1 -1
  6. package/dist/ac-token.js +1 -1
  7. package/dist/{button-D1sPDUCM.d.ts → button-N92DKoWb.d.ts} +1 -1
  8. package/dist/button.d.ts +1 -1
  9. package/dist/colour-picker.d.ts +1 -1
  10. package/dist/{crumb-navigation-nnr881n-.d.ts → crumb-navigation-BbqfmkYs.d.ts} +1 -1
  11. package/dist/crumbs.d.ts +1 -1
  12. package/dist/{data-grid-CaPLWt5E.js → data-grid-8TsiF2ES.js} +7 -6
  13. package/dist/{data-grid-BAEpfTkb.d.ts → data-grid-CslzF16R.d.ts} +4 -4
  14. package/dist/data-grid.d.ts +1 -1
  15. package/dist/data-grid.js +1 -1
  16. package/dist/{detail-list-DK49TsJd.d.ts → detail-list-n1MbNbhF.d.ts} +4 -4
  17. package/dist/{detail-list-C3XKTRmE.js → detail-list-w3GXKO2k.js} +4 -3
  18. package/dist/detail-list.d.ts +2 -2
  19. package/dist/detail-list.js +1 -1
  20. package/dist/{flat-list-Bv4-qfjp.d.ts → flat-list-D_4y3wgn.d.ts} +4 -4
  21. package/dist/flat-list.d.ts +1 -1
  22. package/dist/flat-list.js +2 -2
  23. package/dist/{grid-list-DaeSHv9P.d.ts → grid-list-CqYS28NY.d.ts} +4 -2
  24. package/dist/{grid-list-51XsR2X2.js → grid-list-Cz2tKcNZ.js} +4 -3
  25. package/dist/grid-list.d.ts +2 -2
  26. package/dist/grid-list.js +1 -1
  27. package/dist/{grouped-item-lister-DZIjo9nO.d.ts → grouped-item-lister-CS4yInuw.d.ts} +2 -2
  28. package/dist/grouped-item-lister.d.ts +1 -1
  29. package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +1 -1
  30. package/dist/icons.d.ts +2 -2
  31. package/dist/{incr-search-2YaxUUA9.js → incr-search-Dc6gYpJG.js} +24 -4
  32. package/dist/incr-search.d.ts +1 -1
  33. package/dist/incr-search.js +1 -1
  34. package/dist/{index-BjRoPrwj.d.ts → index-Bm9a9S3_.d.ts} +3 -2
  35. package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +1 -1
  36. package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +1 -1
  37. package/dist/{index-Bz3loIAf.d.ts → index-DN2BWctP.d.ts} +1 -1
  38. package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +1 -1
  39. package/dist/{index-vdbbGE7N.d.ts → index-VAs9akt4.d.ts} +18 -2
  40. package/dist/{index-DXDz60XH.d.ts → index-YCDugjdu.d.ts} +4 -4
  41. package/dist/{index-DM9OLgTH.d.ts → index-ZaA90u09.d.ts} +2 -2
  42. package/dist/index.d.ts +37 -26
  43. package/dist/index.js +88 -13
  44. package/dist/item-appearance-BjcMyauw.js +29 -0
  45. package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +1 -1
  46. package/dist/labelled-radial-input.d.ts +2 -2
  47. package/dist/labelled-range-input.d.ts +1 -1
  48. package/dist/labelled-slider-input.d.ts +1 -1
  49. package/dist/list-group-header.d.ts +1 -1
  50. package/dist/{list-selection-controller-CJDdjH7E.js → list-selection-controller-D4iIrvJH.js} +12 -8
  51. package/dist/{list-selection-types-hj9Njei3.d.ts → list-selection-types-Dnamh2Lu.d.ts} +29 -3
  52. package/dist/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +1 -1
  53. package/dist/menu.d.ts +2 -2
  54. package/dist/{miller-BfrTcujx.js → miller-BrmBywNZ.js} +20 -9
  55. package/dist/miller.d.ts +1 -1
  56. package/dist/miller.js +1 -1
  57. package/dist/narrowed-text.js +1 -1
  58. package/dist/panel.d.ts +1 -1
  59. package/dist/{item-appearance-Bl6rMIID.js → partial-keys-Ck6CiprB.js} +1 -29
  60. package/dist/{split-layout-ChZ0Glrs.d.ts → split-layout-CophkNeU.d.ts} +1 -1
  61. package/dist/split-layout.d.ts +1 -1
  62. package/dist/sunburst-tree.d.ts +1 -1
  63. package/dist/sunburst-tree.js +1 -1
  64. package/dist/{sunbursttree-list-BAS0KrDG.d.ts → sunbursttree-list-Bk20giTF.d.ts} +1 -1
  65. package/dist/{sunbursttree-list-906G33ME.js → sunbursttree-list-DQF6DcsS.js} +1 -1
  66. package/dist/{tab-list-wC0xNz1b.d.ts → tab-list-Df371rqw.d.ts} +1 -1
  67. package/dist/tabs.d.ts +1 -1
  68. package/dist/timeline.d.ts +1 -1
  69. package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +1 -1
  70. package/dist/titlebar.d.ts +1 -1
  71. package/dist/{tree-C_H1COTf.js → tree-BaXYyt5U.js} +185 -69
  72. package/dist/tree.d.ts +1 -1
  73. package/dist/tree.js +1 -1
  74. package/dist/{vertical-list-od4050Bo.d.ts → vertical-list-DtHX26JO.d.ts} +2 -2
  75. package/dist/{vertical-list-qMRIxZIT.js → vertical-list-o5hfqH87.js} +4 -3
  76. package/dist/vertical-list.d.ts +2 -2
  77. package/dist/vertical-list.js +1 -1
  78. package/docs-user/README.md +6 -0
  79. package/docs-user/bottom-drawer.md +2 -2
  80. package/docs-user/crumbs.md +1 -1
  81. package/docs-user/data-grid.md +3 -3
  82. package/docs-user/detail-list.md +3 -3
  83. package/docs-user/dock.md +3 -3
  84. package/docs-user/flat-list.md +7 -7
  85. package/docs-user/grid-list.md +4 -4
  86. package/docs-user/hyperbolic-tree.md +6 -6
  87. package/docs-user/icicle-tree.md +2 -2
  88. package/docs-user/incr-search.md +1 -1
  89. package/docs-user/index.json +26 -2
  90. package/docs-user/infra-handle.md +122 -0
  91. package/docs-user/infra-incremental.md +154 -0
  92. package/docs-user/infra-theming.md +510 -0
  93. package/docs-user/infra-view-state.md +210 -0
  94. package/docs-user/list-state.md +656 -0
  95. package/docs-user/llms.txt +6 -0
  96. package/docs-user/miller.md +5 -1
  97. package/docs-user/multicol-list.md +6 -0
  98. package/docs-user/panel.md +3 -3
  99. package/docs-user/select-horiz.md +1 -1
  100. package/docs-user/split-layout.md +2 -2
  101. package/docs-user/sunburst-tree.md +3 -3
  102. package/docs-user/titlebar.md +2 -2
  103. package/docs-user/tree.md +8 -2
  104. package/docs-user/treemap.md +2 -2
  105. package/docs-user/twosplit-layout.md +3 -3
  106. package/docs-user/user-selection-intents.md +86 -0
  107. package/docs-user/user-theming.md +1 -1
  108. package/docs-user/vertical-list.md +3 -3
  109. package/llms.txt +6 -0
  110. package/package.json +1 -1
  111. /package/dist/{index-DV3iaUBW.d.ts → index-DV3iaUBW2.d.ts} +0 -0
@@ -0,0 +1,510 @@
1
+ # CSS Variables Specification
2
+
3
+ This document defines the semantic CSS variables for theming the application. Components should use these variables rather than hardcoded values to ensure consistent theming and easy customisation.
4
+
5
+ All colour values use [OKLCH](https://oklch.com/) for consistent perceptual brightness across hues.
6
+
7
+ ---
8
+
9
+ ## Surfaces
10
+
11
+ Surfaces represent UI depth. Higher numbers are more **elevated** — visually lighter in dark mode, following the standard elevation model (Material Design 3, Fluent UI 2).
12
+
13
+ **Depth scale:**
14
+
15
+ | Variable | Dark Theme | Light Theme | Semantic |
16
+ |---|---|---|---|
17
+ | `--surface-0` | `oklch(5% 0.02 240)` | `oklch(100% 0 0)` | True dark base (reserved, rarely used) |
18
+ | `--surface-1` | `oklch(8% 0.02 240)` | `oklch(99% 0.01 240)` | Near-black base (reserved, rarely used) |
19
+ | `--surface-2` | `oklch(12% 0.02 240)` | `oklch(95% 0.01 240)` | Deep wells, inset areas |
20
+ | `--surface-3` | `oklch(15% 0.02 240)` | `oklch(98% 0.01 240)` | **Default UI plane** — panels, cards |
21
+ | `--surface-4` | `oklch(22% 0.02 240)` | `oklch(92% 0.01 240)` | Interactive controls — buttons, inputs |
22
+ | `--surface-5` | `oklch(28% 0.02 240)` | `oklch(85% 0.01 240)` | Hover states; **popup/menu backgrounds** |
23
+ | `--surface-6` | `oklch(35% 0.02 240)` | `oklch(78% 0.01 240)` | Active/pressed states; hover within popups |
24
+
25
+ ### Text on surfaces
26
+
27
+ Each surface level has a paired text colour for legible contrast.
28
+
29
+ | Variable | Dark Theme | Light Theme | Usage |
30
+ |---|---|---|---|
31
+ | `--surface-2-text` | `#fff` | `oklch(15% 0.02 240)` | Text on `--surface-2` |
32
+ | `--surface-3-text` | `oklch(75% 0.02 240)` | `oklch(25% 0.02 240)` | Text on `--surface-3` |
33
+ | `--surface-4-text` | `#fff` | `oklch(15% 0.02 240)` | Text on `--surface-4` |
34
+ | `--surface-5-text` | `#fff` | `oklch(10% 0.02 240)` | Text on `--surface-5` |
35
+ | `--surface-6-text` | `#fff` | `oklch(10% 0.02 240)` | Text on `--surface-6` |
36
+ | `--surface-muted-text` | `oklch(55% 0.02 240)` | `oklch(45% 0.02 240)` | Secondary/hint text (not depth-specific) |
37
+
38
+ ### Depth model guidance
39
+
40
+ ```
41
+ surface-3 ← default panels (app chrome background)
42
+ surface-4 ← buttons, inputs (raised above panel)
43
+ surface-5 ← hover states; menus, popovers (floating above panel)
44
+ surface-6 ← active/pressed states; hover within floating layers
45
+ ```
46
+
47
+ A hover effect on an element at depth N should use depth N+1:
48
+ ```css
49
+ .btn { background: var(--surface-4); }
50
+ .btn:hover { background: var(--surface-5); }
51
+ .menu-item:hover { background: var(--surface-6); } /* menu is already at surface-5 */
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Content
57
+
58
+ Separate from surface (UI chrome), for user-generated content areas such as canvases, editors, or preview panes.
59
+
60
+ | Variable | Dark Theme | Light Theme | Usage |
61
+ |---|---|---|---|
62
+ | `--content` | `oklch(10% 0.01 240)` | `oklch(100% 0 0)` | User content area background |
63
+ | `--content-text` | `#fff` | `oklch(10% 0.02 240)` | Text on content backgrounds |
64
+
65
+ ---
66
+
67
+ ## Accent
68
+
69
+ Accent colours for primary actions, active states, and focus indicators.
70
+
71
+ | Variable | Dark Theme | Light Theme | Usage |
72
+ |---|---|---|---|
73
+ | `--accent` | `oklch(60% 0.15 140)` | `oklch(45% 0.15 140)` | Primary accent (active buttons, selected items) |
74
+ | `--accent-text` | `#fff` | `#fff` | Text on accent-coloured backgrounds |
75
+
76
+ There are also accent colours based on surface level, for example `--surface-2-accent` (used in `xy-pad.ts` as a convenience alias; not defined in `src/style.css`).
77
+ ---
78
+
79
+ ## Feedback / Status
80
+
81
+ | Variable | Dark Theme | Light Theme | Usage |
82
+ |---|---|---|---|
83
+ | `--error` | `oklch(60% 0.15 20)` | `oklch(55% 0.15 20)` | Error states, destructive actions |
84
+ | `--error-text` | `#fff` | `#fff` | Text on error backgrounds |
85
+
86
+ ---
87
+
88
+ ## Typography
89
+
90
+ | Variable | Value | Usage |
91
+ |---|---|---|
92
+ | `--font-family` | `system-ui, sans-serif` | Primary UI font |
93
+ | `--font-mono` | `ui-monospace, monospace` | Monospace for values, code |
94
+ | `--font-weight` | `400` | Normal weight |
95
+ | `--font-weight-bold` | `600` | Bold weight |
96
+ | `--text-xs` | `10px` | Tiny — icons, badges |
97
+ | `--text-s` | `11px` | Small — labels, captions |
98
+ | `--text-m` | `12px` | Medium — body, inputs |
99
+ | `--text-l` | `13px` | Large — titles |
100
+ | `--text-xl` | `16px` | Extra large — headings |
101
+ | `--text-2xl` | `20px` | Page headings |
102
+
103
+ ---
104
+
105
+ ## Spacing
106
+
107
+ | Variable | Value | Usage |
108
+ |---|---|---|
109
+ | `--space-xs` | `3px` | Tight spacing, icon padding |
110
+ | `--space-s` | `4px` | Default internal spacing |
111
+ | `--space-m` | `8px` | Standard padding/margins |
112
+ | `--space-l` | `12px` | Section spacing |
113
+ | `--space-xl` | `16px` | Large gaps |
114
+ | `--space-2xl` | `20px` | Section separation |
115
+
116
+ ---
117
+
118
+ ## Borders
119
+
120
+ | Variable | Value | Usage |
121
+ |---|---|---|
122
+ | `--border` | `1px solid oklch(25% 0.02 240)` (dark) | Standard border on `--surface-2` through `--surface-4` |
123
+ | `--border-subtle` | `1px solid oklch(18% 0.02 240)` (dark) | Subtle boundary |
124
+ | `--radius-s` | `3px` | Small — inputs, compact buttons |
125
+ | `--radius-m` | `4px` | Medium — buttons, cards |
126
+ | `--radius-l` | `6px` | Large — menus, popovers |
127
+ | `--radius-xl` | `8px` | Extra large — dialogs |
128
+
129
+ **Note:** For borders on elevated surfaces (`--surface-5` and above), use `1px solid var(--surface-6)` rather than `var(--border)` to maintain visible contrast. This is handled automatically by the shared `popupSurfaceStyles` in `src/styles/popup.ts`.
130
+
131
+ ---
132
+
133
+ ## Shadows
134
+
135
+ | Variable | Dark Theme | Light Theme | Usage |
136
+ |---|---|---|---|
137
+ | `--shadow-m` | `0 4px 12px rgba(0,0,0,0.3)` | `0 2px 8px rgba(0,0,0,0.1)` | Dropdowns, popovers |
138
+ | `--shadow-l` | `0 4px 24px rgba(0,0,0,0.5)` | `0 4px 16px rgba(0,0,0,0.15)` | Dialogs, large floating panels |
139
+
140
+ ---
141
+
142
+ ## Interaction
143
+
144
+ | Variable | Value | Usage |
145
+ |---|---|---|
146
+ | `--disabled-opacity` | `0.5` | Opacity for disabled elements |
147
+ | `--transition` | `0.15s ease-out` | Default transition timing |
148
+
149
+ ---
150
+
151
+ ## Transparent Pattern
152
+
153
+ | Variable | Dark Theme | Light Theme |
154
+ |---|---|---|
155
+ | `--transparent-bg` | `oklch(30% 0.02 240)` | `oklch(85% 0.01 240)` |
156
+
157
+ ---
158
+
159
+ ## Item States
160
+
161
+ CSS variables for styling "items" — logical content elements like file list items, tree items, or crumb navigation items. Items are distinct from UI controls like buttons.
162
+
163
+ ### Design Approach
164
+ - **Border-based states**: Uses border styling (colour, thickness, style) since background may not be visible for thumbnails.
165
+ - **Independent of surface depth**: States work regardless of which surface level the item sits on.
166
+ - **Combinable**: States can coexist (e.g., enabled + selected + tickled).
167
+
168
+ ### States
169
+
170
+ | Variable | Dark Theme | Light Theme | Border Style |
171
+ |---|---|---|---|
172
+ | `--item-border` | `surface-5` | `surface-4` | 1px solid |
173
+ | `--item-border-tickled` | accent 40% | accent 50% | 1px solid |
174
+ | `--item-border-selected` | accent | accent | 1px dashed |
175
+ | `--item-border-active` | accent | accent | 2px solid |
176
+ | `--item-border-dragged` | accent 80% | accent | 2px dotted |
177
+ | `--item-border-disabled` | `surface-4` | `surface-3` | 1px solid |
178
+
179
+ ### Background/Text Variants
180
+
181
+ | Variable | Dark Theme | Light Theme | Usage |
182
+ |---|---|---|---|
183
+ | `--item-bg` | transparent | transparent | Default item background (no state) |
184
+ | `--item-bg-tickled` | `surface-5` | `surface-4` | Hover/pointer state |
185
+ | `--item-bg-selected` | accent | accent | Selected item background |
186
+ | `--item-bg-selected-tickled` | accent 80% mixed with black | accent 80% mixed with black | Selected + hovered simultaneously |
187
+ | `--item-text-tickled` | `surface-5-text` | `surface-4-text` | Text on tickled items |
188
+ | `--item-text-selected` | accent-text | accent-text | Text on selected items |
189
+
190
+ ### Layout Variables
191
+
192
+ | Variable | Value | Usage |
193
+ |---|---|---|
194
+ | `--item-padding` | `var(--space-s) var(--space-m)` | Padding applied to `.item` elements by `tickledItemStyles` |
195
+ | `--item-border-radius` | `var(--radius-m)` | Border radius applied to `.item` elements |
196
+ | `--item-icon-spacing` | `var(--space-s)` | Gap between icon and label text inside an item |
197
+
198
+ These are layout defaults — override per-component if the geometry needs to differ (e.g. a compact list vs a card grid).
199
+
200
+ ### Item Label Styling
201
+
202
+ Variables for styling item label text (e.g., font style, text decoration). These allow overriding label appearance per state without CSS selector cascading issues in shadow DOM.
203
+
204
+ | Variable | Dark Theme | Light Theme | Usage |
205
+ |---|---|---|---|
206
+ | `--item-label-font-style-default` | `normal` | `normal` | Default font style |
207
+ | `--item-label-font-style-selected` | `italic` | `italic` | Font style when selected |
208
+ | `--item-label-font-style-tickled` | `normal` | `normal` | Font style when tickled/hovered |
209
+ | `--item-label-text-decoration-default` | `none` | `none` | Default text decoration |
210
+ | `--item-label-text-decoration-selected` | `none` | `none` | Text decoration when selected |
211
+ | `--item-label-text-decoration-tickled` | `underline` | `underline` | Text decoration when tickled/hovered |
212
+ | `--item-label-text-underline-offset-default` | `auto` | `auto` | Default underline offset |
213
+ | `--item-label-text-underline-offset-tickled` | `3px` | `3px` | Underline offset when tickled/hovered |
214
+
215
+ ### Usage
216
+
217
+ States are applied via CSS classes. Border variables use `color-mix()` for accent variations.
218
+
219
+ ```css
220
+ /* Base item */
221
+ .list-item {
222
+ background: var(--item-bg);
223
+ border: var(--item-border);
224
+ padding: var(--item-padding);
225
+ border-radius: var(--item-border-radius);
226
+ }
227
+
228
+ /* Hover/tickled state */
229
+ .list-item.tickled {
230
+ border: var(--item-border-tickled);
231
+ background: var(--item-bg-tickled);
232
+ color: var(--item-text-tickled);
233
+ }
234
+
235
+ /* Selected state */
236
+ .list-item.selected {
237
+ border: var(--item-border-selected);
238
+ background: var(--item-bg-selected);
239
+ color: var(--item-text-selected);
240
+ }
241
+
242
+ /* Selected + tickled simultaneously */
243
+ .list-item.selected.tickled {
244
+ background: var(--item-bg-selected-tickled);
245
+ }
246
+
247
+ /* Active/pressed state */
248
+ .list-item.active {
249
+ border: var(--item-border-active);
250
+ }
251
+
252
+ /* Dragged state */
253
+ .list-item.dragged {
254
+ border: var(--item-border-dragged);
255
+ }
256
+
257
+ /* Disabled state */
258
+ .list-item.disabled {
259
+ border: var(--item-border-disabled);
260
+ opacity: 0.5;
261
+ }
262
+ ```
263
+
264
+ In practice, most components should import `tickledItemStyles` from `src/interaction/index.js` rather than writing these rules manually — `tickledItemStyles` applies all of the above to `.item` class elements and is kept in sync with this variable contract.
265
+
266
+ ---
267
+
268
+ ## Prominence Styles
269
+
270
+ Prominence styles provide filter-based interaction feedback for components. They use CSS `filter` (opacity + saturation) to convey interaction state, reducing visual noise for idle components while restoring full vibrancy on interaction.
271
+
272
+ ### States
273
+
274
+ | State | Trigger | Effect |
275
+ |-------|---------|--------|
276
+ | **Idle** | No interaction | Reduced opacity + desaturated |
277
+ | **Tickled** | `:hover` / `:focus-within` | Elevated opacity + saturation |
278
+ | **Active** | `:active` / `[dragging]` | Full vibrancy (filter removed) |
279
+ | **Disabled** | `[disabled]` / `:disabled` | Heavily muted + near-grayscale, `pointer-events: none` |
280
+
281
+ ### CSS Variables
282
+
283
+ Defaults are defined on `:host` and can be overridden per-instance:
284
+
285
+ | Variable | Default | Meaning |
286
+ |---|---|---|
287
+ | `--prominence-disabled-opacity` | `0.3` | Opacity when disabled |
288
+ | `--prominence-disabled-saturate` | `0.1` | Saturation when disabled |
289
+ | `--prominence-idle-opacity` | `0.6` | Opacity when idle |
290
+ | `--prominence-idle-saturate` | `0.4` | Saturation when idle |
291
+ | `--prominence-tickled-opacity` | `0.85` | Opacity on hover/focus |
292
+ | `--prominence-tickled-saturate` | `0.8` | Saturation on hover/focus |
293
+
294
+ ### When to Apply
295
+
296
+ Use `prominenceStyles` for **interactive visual components** that benefit from reduced visual noise when idle — such as data visualization widgets (xy-pad, graphs), canvas-based components, or embedded media. Do not use for standard UI controls (buttons, inputs, menus) which already have their own interaction patterns via surface depth.
297
+
298
+ ### How to Apply
299
+
300
+ ```ts
301
+ import { prominenceStyles } from '../styles/prominence.js';
302
+ import { css } from 'lit';
303
+
304
+ class MyComponent extends LitElement {
305
+ static styles = [prominenceStyles, css`...`];
306
+ }
307
+ ```
308
+
309
+ To disable prominence effects entirely, override the variables on the component instance:
310
+
311
+ ```html
312
+ <my-component style="
313
+ --prominence-idle-opacity: 1;
314
+ --prominence-idle-saturate: 1;
315
+ --prominence-tickled-opacity: 1;
316
+ --prominence-tickled-saturate: 1;
317
+ "></my-component>
318
+ ```
319
+
320
+ ### Notes
321
+
322
+ - Uses CSS pseudo-classes only — no JavaScript tickled state management required.
323
+ - `:host([dragging])` supplements `:active` for components that set that attribute during pointer drags.
324
+ - Transition uses `--transition` from the theme.
325
+ - When disabled, `pointer-events: none` is enforced and cannot be overridden.
326
+
327
+ ---
328
+
329
+ ## Scroll Fade
330
+
331
+ Zero-JS edge-fade indicator for scrollable containers — signals "there's more content this way" by fading content out near an edge, only when that edge is actually scrollable.
332
+
333
+ Implemented with a CSS alpha mask driven by a scroll-linked animation (`scroll-timeline` + `animation-timeline` + `@property`-registered custom properties), not a JS scroll/resize listener. Two consequences of that:
334
+
335
+ - It can't go stale. A JS approach (recompute on `scroll`) misses cases where the scrollable range changes without the scroll position changing — e.g. widening a resizable container so content that used to overflow now fully fits. The fade only updates on the next `scroll` event, so it can be stuck showing a shadow with nothing left to scroll to. The CSS version reads live scroll-range state every frame for free.
336
+ - It's a genuine mask, not a background layer. `background-image` paints behind an element's children, so it disappears wherever a child has an opaque background of its own. A mask fades the whole composited element — background and children together — so it works regardless of item styling.
337
+
338
+ ### CSS Variables
339
+
340
+ | Variable | Default | Meaning |
341
+ |---|---|---|
342
+ | `--scroll-fade-width` | `30px` | How far the fade extends in from each edge |
343
+ | `--scroll-fade-strength` | `1` | How completely content fades at the edge, `0`–`1` (`1` = to transparent, `0` = no fade). Dense layouts usually want `0.7`–`0.8` |
344
+
345
+ ### When to Apply
346
+
347
+ Any custom component with a scrolling region (`overflow-x`/`overflow-y`: `scroll`/`auto`) where content can overflow — breadcrumb trails, tab strips, horizontal chip/pill lists, scrollable menus. New scrollable components should use this by default rather than rolling a one-off shadow/fade.
348
+
349
+ ### How to Apply
350
+
351
+ ```ts
352
+ import { scrollFadeStyles } from '../styles/scroll-fade.js';
353
+
354
+ class MyComponent extends LitElement {
355
+ static styles = [scrollFadeStyles(`x`), css`
356
+ .scroller {
357
+ overflow-x: scroll;
358
+ /* ... */
359
+ }
360
+ `];
361
+ }
362
+ ```
363
+
364
+ Apply the matching class (`scroll-fade-x` for a horizontally-scrolling element, `scroll-fade-y` for vertical) to the actual `overflow`-scrolling element:
365
+
366
+ ```ts
367
+ html`<div class="scroller scroll-fade-x">...</div>`
368
+ ```
369
+
370
+ If a component can scroll on either axis depending on a runtime property (e.g. an `orientation` attribute), include both `scrollFadeStyles(`x`)` and `scrollFadeStyles(`y`)` in `static styles` and switch which class is applied in `render()` — each axis uses independently-named `@property`/`@keyframes`/`scroll-timeline` internals, so having both present is safe even though only one is ever applied at a time.
371
+
372
+ Apply only one axis class per element — each sets `mask-image`, so a second axis class would overwrite the first rather than combine. For an element that scrolls on both axes at once (e.g. a grid viewport with `overflow: auto`), put the vertical fade on the viewport itself and handle the horizontal axis on a separate element (e.g. a synced header strip with its own `scroll-fade-x`). For a data grid, keep the mask on the row viewport below the header bar — not on a wrapper containing the headers — so the top fade never covers the column headers.
373
+
374
+ ### Notes
375
+
376
+ - Used by `ixfx-crumb-navigation` and `ixfx-crumb-path` (`src/crumbs/`) — reference implementations for the pattern above. `ixfx-data-grid` uses `.scroll-fade-y` on the row viewport and `.scroll-fade-x` on the header strip, with dense-data defaults (`--scroll-fade-width: 20px; --scroll-fade-strength: 0.8`).
377
+ - Depends on `scroll-timeline`, `animation-timeline`, and `@property`, all reasonably modern CSS features. Unsupported browsers simply get no fade (the mask fails to apply, not the scrolling) — verify this degrades acceptably for your target browser matrix before relying on it as anything more than a visual nicety.
378
+ - Don't set the animated custom properties (or their `@property initial-value`/keyframe values/`var()` fallbacks) as unitless `0` — always `0px`. A bare `0` inside the generated `calc(100% - var(...))` is a percentage/number type mismatch that silently invalidates the whole `mask-image`, which is exactly what caused this to intermittently render as "always on" during development.
379
+
380
+ ---
381
+
382
+ ## Quick Reference
383
+
384
+ ### Common component patterns
385
+
386
+ **Panel/Card** (surface-3 — default plane)
387
+ ```css
388
+ background: var(--surface-3);
389
+ color: var(--surface-3-text);
390
+ border: var(--border);
391
+ border-radius: var(--radius-l);
392
+ padding: var(--space-l);
393
+ ```
394
+
395
+ **Button** (surface-4 — raised above panel)
396
+ ```css
397
+ background: var(--surface-4);
398
+ color: var(--surface-4-text);
399
+ border: var(--border);
400
+ border-radius: var(--radius-m);
401
+ padding: var(--space-s) var(--space-m);
402
+ ```
403
+
404
+ **Button hover** (surface-5 — one level higher)
405
+ ```css
406
+ background: var(--surface-5);
407
+ ```
408
+
409
+ **Menu/Popover** (surface-5 — floating above panel)
410
+ ```css
411
+ /* Use the shared popupSurfaceStyles from src/styles/popup.ts */
412
+ background: var(--surface-5);
413
+ border: 1px solid var(--surface-6);
414
+ border-radius: var(--radius-l);
415
+ box-shadow: var(--shadow-m);
416
+ color: var(--surface-5-text);
417
+ ```
418
+
419
+ **Menu item hover** (surface-6 — one level above menu background)
420
+ ```css
421
+ background: var(--surface-6);
422
+ ```
423
+
424
+ **Input field** (surface-4 — same level as buttons)
425
+ ```css
426
+ background: var(--surface-4);
427
+ color: var(--surface-4-text);
428
+ border: var(--border);
429
+ border-radius: var(--radius-s);
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Implementation Notes
435
+
436
+ 1. **OKLCH format**: All colours use OKLCH for perceptual uniformity.
437
+ 2. **Semantic naming**: Variables describe *what* they represent and *where* in the depth hierarchy.
438
+ 3. **Theme Fallbacks**: Components must include the `themeFallbacks` style module from `src/styles/fallbacks.ts` in their `static styles` array. This registers `@property` rules for every theme variable with `inherits: true`, providing initial values in shadow DOM without shadowing inherited `:root` declarations.
439
+
440
+ ```ts
441
+ import { themeFallbacks } from '../styles/fallbacks.js';
442
+
443
+ class MyComponent extends LitElement {
444
+ static styles = [themeFallbacks, css`...`];
445
+ }
446
+ ```
447
+
448
+ Do **not** use per-usage CSS fallbacks like `var(--surface-3, #ccc)` — they would need updating if the theme changes and don't compose well. The `@property` approach is centralized, idempotent, and transparent to component authors.
449
+
450
+ 4. **Dark theme default**: The dark theme is the default. Light theme activates via `@media (prefers-color-scheme: light)`.
451
+ 5. **Theme source**: All variables are defined in `src/style.css`. Custom themes can be defined by overriding variables on a class (see `demo/shared/demo-themes.ts`).
452
+ 6. **Popup surface helper**: Floating components (menus, popovers, colour picker, crumb dropdown, tooltips) share `popupSurfaceStyles` from `src/styles/popup.ts` for consistent appearance.
453
+
454
+ ### Shadow DOM CSS Variable Bridging
455
+
456
+ CSS custom properties set on a parent element do not automatically pierce into Shadow DOM children. To allow external customisation of internal component styling, use a **bridge variable** pattern:
457
+
458
+ ```css
459
+ :host {
460
+ /* Internal alias: checks external var first, falls back to default */
461
+ --my-internal-var: var(--my-external-var, var(--surface-5));
462
+
463
+ /* Component's own internal defaults (can reference other :host vars) */
464
+ --my-other-var: var(--surface-3);
465
+ }
466
+
467
+ /* Use the internal alias in actual styles */
468
+ .my-element {
469
+ background: var(--my-internal-var);
470
+ color: var(--my-other-var);
471
+ }
472
+ ```
473
+
474
+ **How it works:**
475
+ - `--my-external-var` is the public API — consumers set this on the component element or parent
476
+ - `--my-internal-var` is the internal bridge defined in `:host`, which resolves to `--my-external-var` if set, otherwise the fallback
477
+ - The component's internal CSS rules use `--my-internal-var`
478
+
479
+ **Example from `ixfx-tab-list-item` (all bridge variables):**
480
+ ```css
481
+ :host {
482
+ /* Bridge variables: external ixfx-* var, fallback */
483
+ --selected-tab-color: var(--ixfx-selected-tab-color, var(--surface-2-text));
484
+ --selected-tab-border-width: var(--ixfx-selected-tab-border-width, 1px);
485
+ --selected-tab-weight: var(--ixfx-selected-tab-weight, bold);
486
+ --selected-background: var(--ixfx-tab-selected-background, var(--surface-5));
487
+ }
488
+
489
+ :host([selected]) > div {
490
+ color: var(--selected-tab-color);
491
+ border: var(--selected-tab-border-width) solid transparent;
492
+ background: var(--selected-background);
493
+ font-weight: var(--selected-tab-weight);
494
+ }
495
+ ```
496
+
497
+ **Usage:**
498
+ ```html
499
+ <!-- Set on parent to affect all tabs -->
500
+ <ixfx-tab-list style="
501
+ --ixfx-selected-tab-color: var(--accent-text);
502
+ --ixfx-selected-tab-weight: 600;
503
+ --ixfx-tab-selected-background: var(--accent);
504
+ ">
505
+
506
+ <!-- Or on individual tab for per-item override -->
507
+ <ixfx-tab-list-item style="--ixfx-tab-selected-background: var(--surface-2);">
508
+ ```
509
+
510
+ **Key insight:** Without the bridge variable, `--ixfx-*` properties set on `ixfx-tab-list` would not be visible inside `ixfx-tab-list-item`'s shadow DOM. The bridge variables defined in `:host` live in the same scope as the component's internal CSS rules, so they can resolve external variables while still providing fallbacks.