@ixfx/components 0.6.2 → 0.7.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 (152) hide show
  1. package/bundle/index.d.ts +1490 -615
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +20477 -15855
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +1 -1
  6. package/dist/ac-text.js +3 -2
  7. package/dist/ac-text.js.map +1 -1
  8. package/dist/ac-token.js +3 -2
  9. package/dist/ac-token.js.map +1 -1
  10. package/dist/{button-Bn0BuLpO.js → button-Dat2yHAU.js} +4 -3
  11. package/dist/button-Dat2yHAU.js.map +1 -0
  12. package/dist/button.js +1 -1
  13. package/dist/checkbox.js +1 -1
  14. package/dist/{colour-picker-0stHB90J.js → colour-picker-BSDBL-QL.js} +4 -3
  15. package/dist/{colour-picker-0stHB90J.js.map → colour-picker-BSDBL-QL.js.map} +1 -1
  16. package/dist/colour-picker.js +1 -1
  17. package/dist/crumbs.js +4 -2
  18. package/dist/crumbs.js.map +1 -1
  19. package/dist/data-display.js +1 -1
  20. package/dist/data-grid.js +4 -2
  21. package/dist/data-grid.js.map +1 -1
  22. package/dist/{defaults-BCiDhZbF.js → defaults-BkWCPJFL.js} +3 -20
  23. package/dist/defaults-BkWCPJFL.js.map +1 -0
  24. package/dist/editable-label-rzXkjf1H.d.ts +75 -0
  25. package/dist/editable-label-rzXkjf1H.d.ts.map +1 -0
  26. package/dist/editable-label.d.ts +2 -75
  27. package/dist/editable-label.js +2 -1
  28. package/dist/editable-label.js.map +1 -1
  29. package/dist/{fallbacks-DEE1nCTt.js → fallbacks-DNI9xt0F.js} +2 -2
  30. package/dist/{fallbacks-DEE1nCTt.js.map → fallbacks-DNI9xt0F.js.map} +1 -1
  31. package/dist/{grouped-item-lister-_pMDwIo1.d.ts → grouped-item-lister-DnWB-eFT.d.ts} +2 -2
  32. package/dist/{grouped-item-lister-_pMDwIo1.d.ts.map → grouped-item-lister-DnWB-eFT.d.ts.map} +1 -1
  33. package/dist/grouped-item-lister.d.ts +1 -1
  34. package/dist/{icons-Bm2ByQ3s.js → icon-CvnPanhA.js} +3 -6
  35. package/dist/icon-CvnPanhA.js.map +1 -0
  36. package/dist/icons.js +8 -2
  37. package/dist/icons.js.map +1 -0
  38. package/dist/index-CG2vIgPv.d.ts +101 -0
  39. package/dist/index-CG2vIgPv.d.ts.map +1 -0
  40. package/dist/index.d.ts +655 -65
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +3682 -924
  43. package/dist/index.js.map +1 -1
  44. package/dist/interaction-D5XdKzbR.js +2 -0
  45. package/dist/labelled-radial-input.js +2 -1
  46. package/dist/labelled-radial-input.js.map +1 -1
  47. package/dist/labelled-slider-input-DzigjMwP.js +808 -0
  48. package/dist/labelled-slider-input-DzigjMwP.js.map +1 -0
  49. package/dist/labelled-slider-input.d.ts +2 -0
  50. package/dist/labelled-slider-input.js +2 -0
  51. package/dist/{types-DlVNJez_.d.ts → list-selection-types-DSuRNWpx.d.ts} +10 -2
  52. package/dist/list-selection-types-DSuRNWpx.d.ts.map +1 -0
  53. package/dist/menu-BjRvlgPy.js +825 -0
  54. package/dist/menu-BjRvlgPy.js.map +1 -0
  55. package/dist/menu-item-B-zYrTTi.js +797 -0
  56. package/dist/menu-item-B-zYrTTi.js.map +1 -0
  57. package/dist/menu.js +2 -1
  58. package/dist/miller.d.ts +1 -1
  59. package/dist/miller.js +3 -2
  60. package/dist/miller.js.map +1 -1
  61. package/dist/narrowed-text.js +4 -2
  62. package/dist/narrowed-text.js.map +1 -1
  63. package/dist/notifier.js +3 -2
  64. package/dist/notifier.js.map +1 -1
  65. package/dist/panel.d.ts +13 -13
  66. package/dist/panel.d.ts.map +1 -1
  67. package/dist/panel.js +37 -253
  68. package/dist/panel.js.map +1 -1
  69. package/dist/plots.js +1 -1
  70. package/dist/polar-pad.js +1 -1
  71. package/dist/{radial-input-Dk8Wydp5.js → radial-input-Dp5qz0ly.js} +2 -2
  72. package/dist/{radial-input-Dk8Wydp5.js.map → radial-input-Dp5qz0ly.js.map} +1 -1
  73. package/dist/radial-input.js +1 -1
  74. package/dist/range-input.js +1 -1
  75. package/dist/range.js +1 -1
  76. package/dist/registry-CkW7q09Q.js +21 -0
  77. package/dist/registry-CkW7q09Q.js.map +1 -0
  78. package/dist/select-horiz.js +3 -2
  79. package/dist/select-horiz.js.map +1 -1
  80. package/dist/{slider-input-XppH3vcy.d.ts → slider-input-BfRUW2ZB.d.ts} +16 -3
  81. package/dist/slider-input-BfRUW2ZB.d.ts.map +1 -0
  82. package/dist/{slider-input-CT4gcF-o.js → slider-input-De4eoT11.js} +382 -67
  83. package/dist/slider-input-De4eoT11.js.map +1 -0
  84. package/dist/slider-input.d.ts +1 -1
  85. package/dist/slider-input.js +1 -1
  86. package/dist/snackbar.js +1 -1
  87. package/dist/split-layout-BFSbUZQv.js +969 -0
  88. package/dist/split-layout-BFSbUZQv.js.map +1 -0
  89. package/dist/split-layout-JK3xWVX2.d.ts +179 -0
  90. package/dist/split-layout-JK3xWVX2.d.ts.map +1 -0
  91. package/dist/split-layout.d.ts +2 -60
  92. package/dist/split-layout.js +1 -617
  93. package/dist/style.css +1 -1
  94. package/dist/swipe.js +1 -1
  95. package/dist/{tab-list-D_1cYhB_.d.ts → tab-list-MPwq_1Jj.d.ts} +2 -1
  96. package/dist/tab-list-MPwq_1Jj.d.ts.map +1 -0
  97. package/dist/tabs.d.ts +1 -1
  98. package/dist/tabs.js +96 -14
  99. package/dist/tabs.js.map +1 -1
  100. package/dist/{tickled-controller-BtevrRLV.d.ts → tickled-controller-CRzdIEjJ.d.ts} +7 -1
  101. package/dist/tickled-controller-CRzdIEjJ.d.ts.map +1 -0
  102. package/dist/{tickled-controller-DkAU-0qL.js → tickled-controller-h9GmJ_bU.js} +8 -2
  103. package/dist/tickled-controller-h9GmJ_bU.js.map +1 -0
  104. package/dist/titlebar-DaCfpliv.d.ts +50 -0
  105. package/dist/titlebar-DaCfpliv.d.ts.map +1 -0
  106. package/dist/titlebar.d.ts +2 -0
  107. package/dist/titlebar.js +324 -0
  108. package/dist/titlebar.js.map +1 -0
  109. package/dist/{tree-BO6BXI2b.js → tree-Ch4W8qXQ.js} +4 -3
  110. package/dist/{tree-BO6BXI2b.js.map → tree-Ch4W8qXQ.js.map} +1 -1
  111. package/dist/tree.d.ts +1 -1
  112. package/dist/tree.js +1 -1
  113. package/dist/vertical-list-ClnqTkF3.js +814 -0
  114. package/dist/vertical-list-ClnqTkF3.js.map +1 -0
  115. package/dist/vertical-list.d.ts +8 -9
  116. package/dist/vertical-list.d.ts.map +1 -1
  117. package/dist/vertical-list.js +1 -736
  118. package/dist/{xy-axis-B6yMjckW.js → xy-axis-CxHC8z5E.js} +109 -3
  119. package/dist/xy-axis-CxHC8z5E.js.map +1 -0
  120. package/dist/xy-pad.js +1 -1
  121. package/docs-user/README.md +5 -0
  122. package/docs-user/detail-list.md +398 -0
  123. package/docs-user/dock.md +310 -0
  124. package/docs-user/index.json +27 -2
  125. package/docs-user/labelled-slider-input.md +198 -0
  126. package/docs-user/llms.txt +6 -1
  127. package/docs-user/panel.md +20 -1
  128. package/docs-user/slider-input.md +55 -15
  129. package/docs-user/split-layout.md +136 -170
  130. package/docs-user/tabs.md +34 -0
  131. package/docs-user/titlebar.md +102 -0
  132. package/docs-user/toolbar.md +95 -22
  133. package/docs-user/twosplit-layout.md +254 -0
  134. package/docs-user/user-catalog.md +8 -1
  135. package/llms.txt +6 -1
  136. package/package.json +1 -2
  137. package/dist/button-Bn0BuLpO.js.map +0 -1
  138. package/dist/defaults-BCiDhZbF.js.map +0 -1
  139. package/dist/editable-label.d.ts.map +0 -1
  140. package/dist/icons-Bm2ByQ3s.js.map +0 -1
  141. package/dist/menu-DIfU70AA.js +0 -1609
  142. package/dist/menu-DIfU70AA.js.map +0 -1
  143. package/dist/slider-input-CT4gcF-o.js.map +0 -1
  144. package/dist/slider-input-XppH3vcy.d.ts.map +0 -1
  145. package/dist/split-layout.d.ts.map +0 -1
  146. package/dist/split-layout.js.map +0 -1
  147. package/dist/tab-list-D_1cYhB_.d.ts.map +0 -1
  148. package/dist/tickled-controller-BtevrRLV.d.ts.map +0 -1
  149. package/dist/tickled-controller-DkAU-0qL.js.map +0 -1
  150. package/dist/types-DlVNJez_.d.ts.map +0 -1
  151. package/dist/vertical-list.js.map +0 -1
  152. package/dist/xy-axis-B6yMjckW.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "version": "0.6.2",
4
- "generated": "2026-08-30T20:26:05.718Z",
4
+ "generated": "2026-09-05T22:33:41.608Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
@@ -43,6 +43,16 @@
43
43
  "summary": "A virtualised data grid with sortable/resizable/reorderable columns, single/multi selection, grouping, and inline cell editing. Handles 200+ rows with windowed rendering and keyboard navigation.",
44
44
  "path": "docs-user/data-grid.md"
45
45
  },
46
+ {
47
+ "name": "detail-list",
48
+ "summary": "A list that scales horizontally, not vertically. Items are laid out in equal-width columns; they flow top→bottom down a column, then wrap to the top of the next column to the right. The component has ",
49
+ "path": "docs-user/detail-list.md"
50
+ },
51
+ {
52
+ "name": "dock",
53
+ "summary": "Dockable, movable panels like VS Code / Photoshop. Drag a panel by its",
54
+ "path": "docs-user/dock.md"
55
+ },
46
56
  {
47
57
  "name": "editable-label",
48
58
  "summary": "Inline editable text components. `ixfx-editable-number` extends `ixfx-editable-label` with drag-to-adjust and fill-bar visualization.",
@@ -78,6 +88,11 @@
78
88
  "summary": "A composite range input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-range-input`). The two controls are always in sync — dragging the slider updates the number, and edit",
79
89
  "path": "docs-user/labelled-range-input.md"
80
90
  },
91
+ {
92
+ "name": "labelled-slider-input",
93
+ "summary": "A composite slider input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-slider-input`). The two controls are always in sync — dragging the slider updates the number, and ed",
94
+ "path": "docs-user/labelled-slider-input.md"
95
+ },
81
96
  {
82
97
  "name": "led",
83
98
  "summary": "An LED indicator component with color mixing and optional blinking animation.",
@@ -160,7 +175,7 @@
160
175
  },
161
176
  {
162
177
  "name": "split-layout",
163
- "summary": "A Lit web component that lays out two elements within a container with a split bar to adjust their proportions.",
178
+ "summary": "A Lit web component that lays out any number of panels along one axis with draggable gutters between them.",
164
179
  "path": "docs-user/split-layout.md"
165
180
  },
166
181
  {
@@ -178,6 +193,11 @@
178
193
  "summary": "A canvas-based timeline component with tracks, events, zoom/pan, and vertical track resizing. Suitable for visualizing time-series data, event logs, or scheduling.",
179
194
  "path": "docs-user/timeline.md"
180
195
  },
196
+ {
197
+ "name": "titlebar",
198
+ "summary": "Standalone titlebar chrome extracted from `ixfx-panel`: title text, icon, toolbar slot, close button and an optional collapse/expand caret. Use it when you need a panel-style header without the collap",
199
+ "path": "docs-user/titlebar.md"
200
+ },
181
201
  {
182
202
  "name": "toolbar",
183
203
  "summary": "Two components for building toolbars:",
@@ -193,6 +213,11 @@
193
213
  "summary": "Three components share a common `TreeComponent` interface: **`ixfx-tree-list`**, **`ixfx-miller-list`**, and **`ixfx-crumb-navigation`**. This document describes the shared API, plus component-specifi",
194
214
  "path": "docs-user/tree.md"
195
215
  },
216
+ {
217
+ "name": "twosplit-layout",
218
+ "summary": "A Lit web component that lays out two elements within a container with a split bar to adjust their proportions.",
219
+ "path": "docs-user/twosplit-layout.md"
220
+ },
196
221
  {
197
222
  "name": "util",
198
223
  "summary": "A tooltip component that renders a value with optional icon and formatted number display. Uses native render root (no shadow DOM).",
@@ -0,0 +1,198 @@
1
+ # `<ixfx-labelled-slider-input>`
2
+
3
+ A composite slider input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-slider-input`). The two controls are always in sync — dragging the slider updates the number, and editing the number moves the slider.
4
+
5
+ ## Basic usage
6
+
7
+ ```html
8
+ <ixfx-labelled-slider-input label="Volume" value="0.5" min="0" max="1" step="0.01">
9
+ </ixfx-labelled-slider-input>
10
+ ```
11
+
12
+ ## Layout
13
+
14
+ Layout is driven by a container query on the component's own height.
15
+
16
+ | Height | Layout |
17
+ |-----------------|---------------------------------|
18
+ | >= 80px (default) | **Vertical** — label above slider |
19
+ | < 80px | **Horizontal** — label beside slider |
20
+
21
+ The component defaults to `height: 100px`, so unconstrained usage shows the vertical layout. Constrain the height in your CSS to switch to horizontal.
22
+
23
+ ### Force a layout with `--flow`
24
+
25
+ Override the container query entirely:
26
+
27
+ ```html
28
+ <ixfx-labelled-slider-input style="--flow: row" ...> <!-- always horizontal -->
29
+ <ixfx-labelled-slider-input style="--flow: column" ...> <!-- always vertical -->
30
+ ```
31
+
32
+ ### Control label width (horizontal layout)
33
+
34
+ ```html
35
+ <ixfx-labelled-slider-input style="--label-width: 8em" ...>
36
+ ```
37
+
38
+ ## Mini mode
39
+
40
+ When the component is very small the label is hidden and only the slider is shown.
41
+
42
+ Mini mode activates when **both** of the following are true:
43
+ - `width < mini-width-threshold` (default 120px)
44
+ - `height < mini-height-threshold` (default 40px)
45
+
46
+ Override with a CSS variable:
47
+
48
+ ```html
49
+ <ixfx-labelled-slider-input style="--mini: 1" ...> <!-- always mini -->
50
+ <ixfx-labelled-slider-input style="--mini: 0" ...> <!-- never mini -->
51
+ ```
52
+
53
+ ## Formatter
54
+
55
+ Apply a function to format the displayed value. Set it as a JS property — it cannot be set as an HTML attribute.
56
+
57
+ ```js
58
+ const el = document.querySelector('ixfx-labelled-slider-input');
59
+ el.formatter = (v) => `${Math.round(Number(v))}%`;
60
+ ```
61
+
62
+ The formatter receives the raw string value and returns the string to display in the editable-number label. Note the inner slider has no tooltip — the label already shows the value, so a tooltip would be redundant.
63
+
64
+ ## Properties / attributes
65
+
66
+ | Attribute | Type | Default | Description |
67
+ |------------------------|-----------------------------------------------|--------------|-----------------------------------------------------|
68
+ | `label` | `string` | `""` | Label text shown in the editable-number |
69
+ | `value` | `number` | `0` | Current value |
70
+ | `min` | `number` | `0` | Minimum value |
71
+ | `max` | `number` | `1` | Maximum value |
72
+ | `step` | `number` | `0.01` | Step increment |
73
+ | `stops` | `number` | — | Quantise to N discrete positions |
74
+ | `drag-sensitivity` | `number` | `1` | Steps per pixel when dragging the number label |
75
+ | `precision` | `number` | `0` | Decimal places shown |
76
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Slider track orientation |
77
+ | `single-style` | `"range" \| "bipolar" \| "unipolar"` | `"range"` | How the value bar is drawn |
78
+ | `marker-style` | `"needle" \| "dot" \| "triangle" \| "none"` | `"needle"` | Marker shape |
79
+ | `disabled` | `boolean` | `false` | Disables all interaction |
80
+ | `swipeable` | `boolean` | `false` | Swipe-to-draw gesture (step-sequencer layouts) |
81
+ | `switch-height` | `number` | `80` | Height (px) at which layout switches vertical↔horizontal |
82
+ | `mini-width-threshold` | `number` | `120` | Width (px) below which mini mode can activate |
83
+ | `mini-height-threshold`| `number` | `40` | Height (px) below which mini mode can activate |
84
+ | `display-text-logic` | `"hidden" \| "show" \| "override" \| "transient"` | `"transient"` | Controls when label text vs value is shown |
85
+ | `formatter` | `(value: string) => string` | — | JS property only — formats value for display |
86
+ | `lighttouch` | `boolean` | `false` | Wheel adjusts the value on hover without focus |
87
+
88
+ ### `display-text-logic` values
89
+
90
+ | Value | Behaviour |
91
+ |-------------|--------------------------------------------------------|
92
+ | `transient` | Shows the label when idle, value on hover/interaction |
93
+ | `show` | Always shows the value |
94
+ | `hidden` | Always shows the label text, never the value |
95
+ | `override` | Label text replaces value at all times |
96
+
97
+ ## Events
98
+
99
+ | Event | Detail | Description |
100
+ |----------|---------------------------|----------------------------------------------------------|
101
+ | `change` | `{ value: string }` | Fired when the value is committed (slider release or number edit) |
102
+ | `input` | `{ value: number }` | Fired on every slider move |
103
+
104
+ Both events bubble and are composed (cross shadow boundary).
105
+
106
+ ## CSS variables
107
+
108
+ ### Layout
109
+
110
+ | Variable | Default | Description |
111
+ |--------------------------|--------------|-----------------------------------------------------------|
112
+ | `--height` | `100px` | Component height. Set to `100%` to fill a constrained parent |
113
+ | `--width` | `auto` | Component width |
114
+ | `--min-height` | `20px` | Minimum height |
115
+ | `--min-width` | `50px` | Minimum width |
116
+ | `--flow` | *(container query)* | `row` or `column` — overrides the automatic layout |
117
+ | `--label-width` | `5ch` | Width of the editable-number in horizontal layout |
118
+ | `--mini` | *(auto)* | `1` to force mini mode, `0` to disable it |
119
+
120
+ ### Label (passed to `ixfx-editable-number`)
121
+
122
+ | Variable | Default | Description |
123
+ |-----------------------|----------------------------|--------------------------|
124
+ | `--label-font-size` | `var(--text-m)` | Font size |
125
+ | `--label-font-family` | `var(--font-family)` | Font family |
126
+ | `--label-color` | `var(--surface-text)` | Text colour |
127
+ | `--label-muted-color` | `var(--surface-muted-text)`| Muted text colour |
128
+ | `--label-padding` | `var(--space-xs) var(--space-s)` | Padding |
129
+ | `--label-radius` | `var(--radius-s)` | Border radius |
130
+ | `--label-align` | `center` | Text alignment |
131
+
132
+ ### Slider (passed to `ixfx-slider-input`)
133
+
134
+ | Variable | Default | Description |
135
+ |-----------------------------------|--------------------------|------------------------------------------|
136
+ | `--lane-track-color` | `var(--surface-4)` | Colour of the background track lane |
137
+ | `--lane-value-color` | `var(--accent)` | Colour of the value lane |
138
+ | `--lane-color` | `var(--accent)` | Fallback colour for custom lanes |
139
+ | `--lane-dasharray` | `none` | Dash pattern for lanes |
140
+ | `--marker-color` | `var(--accent)` | Marker colour |
141
+ | `--marker-thickness` | `0.15` | Marker thickness (fraction of cross axis)|
142
+ | `--marker-length` | `0.8` | Needle length (fraction of cross axis) |
143
+ | `--marker-offset` | `0` | Perpendicular offset from centreline |
144
+ | `--track-color` | `var(--surface-4)` | Track stroke colour |
145
+ | `--track-width` | `2` | Track stroke width (SVG units) |
146
+
147
+ See `ixfx-slider-input` for the full list of lane, marker, and midpoint variables.
148
+
149
+ ## Examples
150
+
151
+ ### Horizontal layout with percentage formatter
152
+
153
+ ```html
154
+ <ixfx-labelled-slider-input
155
+ id="my-slider"
156
+ label="Mix"
157
+ value="0.75"
158
+ style="height: 50px"
159
+ ></ixfx-labelled-slider-input>
160
+
161
+ <script type="module">
162
+ document.querySelector('#my-slider').formatter = (v) =>
163
+ `${Math.round(Number(v) * 100)}%`;
164
+ </script>
165
+ ```
166
+
167
+ ### Custom slider colours
168
+
169
+ ```html
170
+ <ixfx-labelled-slider-input
171
+ label="Intensity"
172
+ value="0.7"
173
+ style="
174
+ --lane-value-color: hsl(280, 80%, 60%);
175
+ --lane-track-color: hsl(280, 30%, 30%);
176
+ --marker-color: hsl(280, 100%, 70%);
177
+ "
178
+ ></ixfx-labelled-slider-input>
179
+ ```
180
+
181
+ ### Force mini mode
182
+
183
+ ```html
184
+ <ixfx-labelled-slider-input label="Vol" value="0.5" style="--mini: 1">
185
+ </ixfx-labelled-slider-input>
186
+ ```
187
+
188
+ ### Listen for changes
189
+
190
+ ```js
191
+ const el = document.querySelector('ixfx-labelled-slider-input');
192
+
193
+ // Committed value (slider release or number edit)
194
+ el.addEventListener('change', (e) => console.log(e.detail.value));
195
+
196
+ // Every slider move
197
+ el.addEventListener('input', (e) => console.log(e.detail.value));
198
+ ```
@@ -19,6 +19,8 @@ import '@ixfx/components/data-grid';
19
19
  - [crumbs](docs-user/crumbs.md): Breadcrumb navigation with lazy-loading child support. Uses the same `TreeNode` / `TreeItem` data model as `ixfx-tree-list`, allowing easy swapping between the two components.
20
20
  - [data-display](docs-user/data-display.md): A debug data display component that renders key-value pairs from a plain object. Supports 7 inline render modes (`led`, `plot`, `plot-xy`, `plot-heat`, `plot-histogram`, `time-elapsed`, `hide`) via `c
21
21
  - [data-grid](docs-user/data-grid.md): A virtualised data grid with sortable/resizable/reorderable columns, single/multi selection, grouping, and inline cell editing. Handles 200+ rows with windowed rendering and keyboard navigation.
22
+ - [detail-list](docs-user/detail-list.md): A list that scales horizontally, not vertically. Items are laid out in equal-width columns; they flow top→bottom down a column, then wrap to the top of the next column to the right. The component has
23
+ - [dock](docs-user/dock.md): Dockable, movable panels like VS Code / Photoshop. Drag a panel by its
22
24
  - [editable-label](docs-user/editable-label.md): Inline editable text components. `ixfx-editable-number` extends `ixfx-editable-label` with drag-to-adjust and fill-bar visualization.
23
25
  - [form](docs-user/form.md): Primitives for typical form layout with responsive label positioning and data binding.
24
26
  - [grouped-item-lister](docs-user/grouped-item-lister.md): A meta-component that renders a typed item list using any sub-component you supply. Items can be displayed in a single sub-component (_ungrouped_ mode) or split into labelled groups, each with its own
@@ -26,6 +28,7 @@ import '@ixfx/components/data-grid';
26
28
  - [incr-search](docs-user/incr-search.md): Utilities for incremental (type-ahead) search over data collections or DOM-backed lists.
27
29
  - [labelled-radial-input](docs-user/labelled-radial-input.md): A composite radial input that pairs an editable label (`ixfx-editable-number`) with a rotary dial (`ixfx-radial-input`). The two controls are always in sync — turning the dial updates the number, and
28
30
  - [labelled-range-input](docs-user/labelled-range-input.md): A composite range input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-range-input`). The two controls are always in sync — dragging the slider updates the number, and edit
31
+ - [labelled-slider-input](docs-user/labelled-slider-input.md): A composite slider input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-slider-input`). The two controls are always in sync — dragging the slider updates the number, and ed
29
32
  - [led](docs-user/led.md): An LED indicator component with color mixing and optional blinking animation.
30
33
  - [menu](docs-user/menu.md): A custom menu system built on native HTML popovers with keyboard navigation, tickled focus management, and command routing.
31
34
  - [miller](docs-user/miller.md): A Miller column component displays hierarchical data as multiple vertical columns, like macOS Finder or email clients. Each column shows the children of the selected item in the previous column.
@@ -42,13 +45,15 @@ import '@ixfx/components/data-grid';
42
45
  - [slider-input](docs-user/slider-input.md): A linear SVG-based slider. Supports horizontal and vertical orientations, three value modes (single/dual/triple), three single-mode styles (range/bipolar/unipolar), triangle/needle/dot markers, and `v
43
46
  - [snackbar](docs-user/snackbar.md): A small floating toolbar anchored in visual proximity to another element on the page (the "target"), built on the native Popover and CSS Anchor Positioning APIs.
44
47
  - [snap-container](docs-user/snap-container.md): A container that positions its child element at configurable snap positions along an axis. The child can be dragged, clicked to snap, or controlled via keyboard.
45
- - [split-layout](docs-user/split-layout.md): A Lit web component that lays out two elements within a container with a split bar to adjust their proportions.
48
+ - [split-layout](docs-user/split-layout.md): A Lit web component that lays out any number of panels along one axis with draggable gutters between them.
46
49
  - [swipe](docs-user/swipe.md): A touch-friendly swipe/nudge component. User taps and drags to generate a value in range [-1, 1]. Value is emitted only on pointer release.
47
50
  - [tabs](docs-user/tabs.md): A complete tabbed interface system using several coordinated components:
48
51
  - [timeline](docs-user/timeline.md): A canvas-based timeline component with tracks, events, zoom/pan, and vertical track resizing. Suitable for visualizing time-series data, event logs, or scheduling.
52
+ - [titlebar](docs-user/titlebar.md): Standalone titlebar chrome extracted from `ixfx-panel`: title text, icon, toolbar slot, close button and an optional collapse/expand caret. Use it when you need a panel-style header without the collap
49
53
  - [toolbar](docs-user/toolbar.md): Two components for building toolbars:
50
54
  - [transitory-label](docs-user/transitory-label.md): An inline text label that animates between values instead of changing instantly. Displays as `inline-block` and inherits text styling from its parent.
51
55
  - [tree](docs-user/tree.md): Three components share a common `TreeComponent` interface: **`ixfx-tree-list`**, **`ixfx-miller-list`**, and **`ixfx-crumb-navigation`**. This document describes the shared API, plus component-specifi
56
+ - [twosplit-layout](docs-user/twosplit-layout.md): A Lit web component that lays out two elements within a container with a split bar to adjust their proportions.
52
57
  - [util](docs-user/util.md): A tooltip component that renders a value with optional icon and formatted number display. Uses native render root (no shadow DOM).
53
58
  - [vertical-list](docs-user/vertical-list.md): A scrollable vertical list of items with selection, keyboard navigation, and incremental search. Items can be plain `<li>` elements declared in HTML or rich custom elements added programmatically.
54
59
  - [xy-pad](docs-user/xy-pad.md): A 2D touch-friendly XY pad for controlling two parameters simultaneously. Each axis supports independent linear/logarithmic scaling.
@@ -2,6 +2,8 @@
2
2
 
3
3
  An collapsible panel component with support for titles, icons, toolbars, and multiple visual variants.
4
4
 
5
+ The header chrome is an `ixfx-titlebar` (see [`ixfx-titlebar`](../titlebar/README.md)); the panel adds the collapsible content area beneath it.
6
+
5
7
  ```html
6
8
  <ixfx-panel title="Settings">
7
9
  <p>Panel content goes here.</p>
@@ -17,6 +19,7 @@ An collapsible panel component with support for titles, icons, toolbars, and mul
17
19
  | Attribute / Property | Type | Default | Description |
18
20
  |--------------------------|---------------------------------------------------|---------------|--------------------------------------------------|
19
21
  | `collapsed` | boolean | `false` | Whether the panel is collapsed (content hidden) |
22
+ | `collapsible` | boolean | `true` | Whether the caret is shown and the panel can be toggled (opt-out) |
20
23
  | `orientation` | `'horizontal'` \| `'vertical'` | `'horizontal'`| Layout direction of title bar |
21
24
  | `title` | string | `''` | Panel title text |
22
25
  | `icon-name` | string | `''` | Icon name from the registry |
@@ -32,6 +35,18 @@ An collapsible panel component with support for titles, icons, toolbars, and mul
32
35
  | `explicit` | Only clicking the caret button toggles the panel |
33
36
  | `disabled` | Caret is hidden; no user toggle possible (programmatic only) |
34
37
 
38
+ Set `collapsible` to `false` (via property binding or JS — `collapsible` defaults to `true`, so a bare `collapsible="false"` attribute would still count as present) or use `toggle-interaction="disabled"` for a fixed header with no user toggle:
39
+
40
+ ```html
41
+ <ixfx-panel id="fixed" title="Fixed" toggle-interaction="disabled">
42
+ <p>This panel cannot be collapsed by the user.</p>
43
+ </ixfx-panel>
44
+ <script>
45
+ // Opt out of collapsing entirely (hides the caret):
46
+ document.querySelector('#fixed').collapsible = false;
47
+ </script>
48
+ ```
49
+
35
50
  ### Slots
36
51
 
37
52
  | Slot | Description |
@@ -184,6 +199,7 @@ A container component that manages multiple panels with exclusive open behavior
184
199
  | `variant` | `'titlebar'` \| `'midline'` | `'titlebar'` | Visual style applied to all child panels |
185
200
  | `toggle-interaction` | `'implicit'` \| `'explicit'` \| `'disabled'` | `'implicit'` | Toggle behavior applied to all child panels |
186
201
  | `allow-multiple` | boolean | `false` | Allow multiple panels to be open simultaneously |
202
+ | `collapsible` | boolean | `true` | Collapse behaviour propagated to child panels |
187
203
  | `gap` | string | `'var(--space-m)'` | CSS gap between panels |
188
204
 
189
205
  ### Group orientation vs child panel orientation
@@ -225,7 +241,7 @@ By default, only one panel can be open at a time. Set `allow-multiple` to allow
225
241
 
226
242
  ### Group-propagated properties
227
243
 
228
- When you set `variant` or `toggle-interaction` on the group, those values are automatically applied to all child panels that don't have an explicit value set. This simplifies declaration when all panels in a group share the same styling:
244
+ When you set `variant`, `toggle-interaction` or `collapsible` on the group, those values are automatically applied to all child panels that don't have an explicit value set. This simplifies declaration when all panels in a group share the same styling:
229
245
 
230
246
  ```html
231
247
  <ixfx-panel-group variant="midline" toggle-interaction="explicit">
@@ -241,10 +257,13 @@ When you set `variant` or `toggle-interaction` on the group, those values are au
241
257
 
242
258
  ```ts
243
259
  import type { PanelOrientation, PanelVariant, PanelToggleInteraction, PanelToggleDetail, PanelGroupOrientation } from 'ixfxfun-components';
260
+ // Canonical titlebar types (Panel* are aliases):
261
+ import type { TitlebarOrientation, TitlebarVariant, TitlebarToggleInteraction, TitlebarToggleDetail } from 'ixfxfun-components';
244
262
  ```
245
263
 
246
264
  ---
247
265
 
248
266
  ## See also
249
267
 
268
+ - [`ixfx-titlebar`](../titlebar/README.md) — Standalone titlebar chrome composed by this panel
250
269
  - [`ixfx-icon`](../icons/README.md) — Icon component used by panel
@@ -10,7 +10,7 @@ A linear SVG-based slider. Supports horizontal and vertical orientations, three
10
10
  <ixfx-slider-input min="0" max="100" value-a="50"></ixfx-slider-input>
11
11
  ```
12
12
 
13
- The component defaults to `200px` wide and `24px` tall (the horizontal cross axis). Set width and height via CSS:
13
+ The component defaults to `200px` wide and `12px` tall (the horizontal cross axis). The SVG box hugs its content (see Implementation Notes), so rows pack densely — e.g. in step-sequencer layouts. Set width and height via CSS:
14
14
 
15
15
  ```html
16
16
  <ixfx-slider-input min="0" max="100" value-a="50" style="width: 320px;"></ixfx-slider-input>
@@ -22,14 +22,14 @@ Vertical orientation swaps the dimensions and rotates interaction to up/down:
22
22
  <ixfx-slider-input
23
23
  orientation="vertical"
24
24
  min="0" max="100" value-a="30"
25
- style="width: 24px; height: 200px;"
25
+ style="width: 12px; height: 200px;"
26
26
  ></ixfx-slider-input>
27
27
  ```
28
28
 
29
29
  To fill a container, set both `width: 100%` and `height: 100%`:
30
30
 
31
31
  ```html
32
- <div style="width: 100%; height: 24px;">
32
+ <div style="width: 100%; height: 12px;">
33
33
  <ixfx-slider-input min="0" max="100" value-a="50" style="width: 100%; height: 100%;"></ixfx-slider-input>
34
34
  </div>
35
35
  ```
@@ -57,14 +57,16 @@ To fill a container, set both `width: 100%` and `height: 100%`:
57
57
  | `value-min` | `number` | `1` | Minimum gap between A and B in value units (dual/triple) |
58
58
  | `value-c-follow` | `"none"` \| `"proportional"` | `"none"` | How C follows when A/B move the band (triple mode) |
59
59
  | `disabled` | `boolean` | `false` | Disables all interaction. Reflected as attribute |
60
+ | `swipeable` | `boolean` | `false` | Swipe-to-draw gesture (no pointer capture; press or enter with pointer down sets value). Reflected as attribute |
61
+ | `lighttouch` | `boolean` | `false` | Wheel adjusts the value on hover instead of requiring focus |
60
62
 
61
63
  ### Sizing
62
64
 
63
- Size via CSS `width`/`height`. The horizontal default is 200×24, vertical 24×200. Both can be overridden freely.
65
+ Size via CSS `width`/`height`. The horizontal default is 200×12, vertical 12×200. Both can be overridden freely.
64
66
 
65
67
  ```html
66
- <!-- Thin (16px tall) -->
67
- <ixfx-slider-input style="height: 16px;" min="0" max="100" value-a="60"></ixfx-slider-input>
68
+ <!-- Thin (8px tall) -->
69
+ <ixfx-slider-input style="height: 8px;" min="0" max="100" value-a="60"></ixfx-slider-input>
68
70
 
69
71
  <!-- Large (32px tall) -->
70
72
  <ixfx-slider-input style="height: 32px;" min="0" max="100" value-a="60"></ixfx-slider-input>
@@ -104,10 +106,28 @@ Size via CSS `width`/`height`. The horizontal default is 200×24, vertical 24×2
104
106
 
105
107
  **Wheel** (when focused or `lighttouch` is on):
106
108
 
107
- Wheel forward increases the value, wheel backward decreases it (sign is platform-agnostic — both `deltaY` and `deltaX` are considered so Mac shift-scrolls are handled correctly). Shift narrows the step to 1/10th.
109
+ Wheel forward increases the value, wheel backward decreases it (sign is platform-agnostic — both `deltaY` and `deltaX` are considered so Mac shift-scrolls are handled correctly). Shift narrows the step to 1/10th. Add the `lighttouch` attribute to step on hover without focusing first:
110
+
111
+ ```html
112
+ <ixfx-slider-input lighttouch min="0" max="100" step="5" value-a="50"></ixfx-slider-input>
113
+ ```
108
114
 
109
115
  Step size priority: `stops` > explicit `step` > 10% of range.
110
116
 
117
+ ### Swipeable mode (step sequencer)
118
+
119
+ Mirrors `ixfx-range-input[swipeable]`. When the `swipeable` attribute is present, pressing down on the slider or entering it while the pointer is pressed sets the value from the cursor coordinate. Pointer capture is NOT held, so the cursor can move to adjacent swipeable sliders and update them too. Leaving the slider (above/below for horizontal, left/right for vertical) freezes the value; re-entering re-engages. `input` fires continuously; `change` fires debounced 150 ms after interaction, as usual.
120
+
121
+ Single mode sets value-A directly. Dual/triple modes move the nearest handle to the cursor.
122
+
123
+ ```html
124
+ <div id="sequencer" style="display:flex; flex-direction:column; gap:2px;">
125
+ <ixfx-slider-input swipeable min="0" max="1" step="0.05" value-a="0.5"></ixfx-slider-input>
126
+ <ixfx-slider-input swipeable min="0" max="1" step="0.05" value-a="0.3"></ixfx-slider-input>
127
+ <ixfx-slider-input swipeable min="0" max="1" step="0.05" value-a="0.7"></ixfx-slider-input>
128
+ </div>
129
+ ```
130
+
111
131
  ---
112
132
 
113
133
  ## Value Modes
@@ -256,8 +276,11 @@ slider.addEventListener('change', (e: SliderInputChangeEvent) => {
256
276
  | Variable | Default | Description |
257
277
  |---|---|---|
258
278
  | `--track-color` | `var(--surface-4)` | Stroke colour of the central track rect |
259
- | `--track-width` | `2` | Stroke width of the track (SVG units) |
279
+ | `--track-width` | `0` | Stroke width of the track (SVG units). Defaults to 0 — the `track`/`value` lanes carry the default visuals, so the rect is an opt-in backdrop (combine with `--track-fill`). Set it for an outlined band behind the lanes |
260
280
  | `--track-fill` | `transparent` | Fill colour of the track rect |
281
+ | `--track-margin` | `0` | Extra inline-direction margin at both track ends (SVG units), added to the computed marker inset |
282
+ | `--track-margin-start` | `--track-margin` | Margin at the low-value end (left / bottom) |
283
+ | `--track-margin-end` | `--track-margin` | Margin at the high-value end (right / top) |
261
284
 
262
285
  ```html
263
286
  <ixfx-slider-input
@@ -266,6 +289,17 @@ slider.addEventListener('change', (e: SliderInputChangeEvent) => {
266
289
  ></ixfx-slider-input>
267
290
  ```
268
291
 
292
+ ### Track margins (inline direction)
293
+
294
+ The track hugs the host by default, inset only for marker clearance. If a custom lane overhangs the value at the extremes — e.g. an `anchored` tolerance band centred on the marker — reserve room so it never leaves the component bounds. Margins shorten and offset the track along its axis (start = low-value end):
295
+
296
+ ```html
297
+ <ixfx-slider-input
298
+ min="0" max="100" value-a="50"
299
+ style="--track-margin: 7;"
300
+ ></ixfx-slider-input>
301
+ ```
302
+
269
303
  ### Lanes
270
304
 
271
305
  Lanes are line segments drawn over the track. The built-in lanes are `track` (the grey background) and `value` (the accent-coloured progress bar). Custom lanes can be added at runtime (see [Data Lanes](#data-lanes-programmatic-api)).
@@ -277,6 +311,8 @@ Lanes are line segments drawn over the track. The built-in lanes are `track` (th
277
311
  | `--lane-color` | `var(--accent)` | Fallback colour for custom lanes |
278
312
  | `--lane-<key>-color` | — | Per-lane colour override (e.g. `--lane-sweep-color`) |
279
313
  | `--lane-<key>-width` | — | Per-lane width override (e.g. `--lane-sweep-width`) |
314
+ | `--lane-track-width` | `0.08` | Thickness of the `track` lane (fraction of cross axis) |
315
+ | `--lane-value-width` | follows track (`0.12`) | Thickness of the `value` lane. Falls back to `--lane-track-width` when unset, so a thick track automatically gets a matching value bar; set it explicitly to diverge |
280
316
  | `--lane-dasharray` | `none` | SVG `stroke-dasharray` applied to all lanes |
281
317
  | `--lane-dashoffset` | `0` | SVG `stroke-dashoffset` — animate for marching-ants effect |
282
318
 
@@ -295,10 +331,12 @@ Dashed lanes:
295
331
  ```html
296
332
  <ixfx-slider-input
297
333
  min="0" max="100" value-a="60"
298
- style="--track-width: 6; --lane-track-width: 0.4; --lane-value-width: 0.4;"
334
+ style="--track-width: 6; --lane-track-width: 0.7;"
299
335
  ></ixfx-slider-input>
300
336
  ```
301
337
 
338
+ The `value` lane follows `--lane-track-width` here — no need to set `--lane-value-width` unless you want it to differ from the track.
339
+
302
340
  ### Marker (value indicator)
303
341
 
304
342
  | Variable | Default | Description |
@@ -387,7 +425,7 @@ With a border halo (useful for contrast against coloured lanes):
387
425
 
388
426
  ### `dot`
389
427
 
390
- A filled circle placed at the value's track position. `--marker-offset` shifts it perpendicular to the track (0 = on the centreline, positive = away from the centreline in the track's perpendicular direction).
428
+ A filled circle placed at the value's track position. `--marker-offset` shifts it perpendicular to the track (0 = on the centreline, positive = away from the centreline in the track's perpendicular direction). The radius follows `--marker-thickness` with a screen-px floor (widest lane + 2px), so the dot always reads larger than the value track it sits on. It renders as an aspect-corrected ellipse in viewBox units, so it stays circular on screen despite the stretched viewBox.
391
429
 
392
430
  ```html
393
431
  <!-- Dot on the track -->
@@ -595,12 +633,12 @@ The track, value bar, and markers all use the same `CROSS` axis for their perpen
595
633
 
596
634
  ### SVG gradient on the value bar
597
635
 
598
- Define gradients in a hidden `<svg>` at the top of the page using `gradientUnits="userSpaceOnUse"` anchored to the slider's viewBox, then reference them via `--lane-value-color`:
636
+ Define gradients in a hidden `<svg>` at the top of the page using `gradientUnits="userSpaceOnUse"` spanning the full viewBox long axis (`0`–`100`; the track insets itself slightly for marker clearance, so full-bleed coordinates stay correct for any marker style), then reference them via `--lane-value-color`:
599
637
 
600
638
  ```html
601
639
  <svg width="0" height="0" style="position:absolute" aria-hidden="true">
602
640
  <defs>
603
- <linearGradient id="slider-grad-h" gradientUnits="userSpaceOnUse" x1="5" y1="0" x2="95" y2="0">
641
+ <linearGradient id="slider-grad-h" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="100" y2="0">
604
642
  <stop offset="0%" stop-color="oklch(70% 0.22 25)" />
605
643
  <stop offset="100%" stop-color="oklch(75% 0.22 145)" />
606
644
  </linearGradient>
@@ -618,13 +656,15 @@ Define gradients in a hidden `<svg>` at the top of the page using `gradientUnits
618
656
 
619
657
  These are lessons learned from building the component — useful context when building a similar thing from scratch.
620
658
 
621
- ### SVG in a fixed `100×24` viewBox
659
+ ### SVG in a dynamic viewBox (tight cross axis + tight end inset)
660
+
661
+ All geometry is computed in a `100×cross` viewBox coordinate space for horizontal, or `cross×100` for vertical, with the track running along the long axis. Only `LONG = 100` stays fixed. Each render computes the tightest cross that fits the actual content — markers (`--marker-length/thickness/offset`), lane widths/offsets, and `--track-width` — as the fixed point `cross = pad / (0.5 − F)` where `F` is the largest fractional half-extent and `pad = 2` covers strokes/linecaps (clamped to 4…40, fractions capped at 0.45). The default needle slider lands around `cross ≈ 6.7` instead of the legacy `CROSS = 24`, so the element's block size hugs the track instead of reserving 7 units of dead air per side. `marker-style="none"` with thin lanes collapses to the minimum. The long-axis end insets are computed the same way from the markers' along-axis extent (dot/triangle caps extend past the track ends; needles need only the stroke pad), clamped to 1…5, so the track uses the full available width. Authored `--track-margin` / `--track-margin-start` / `--track-margin-end` are added on top per end (scaled back proportionally only if the track span would drop below 10 units). Pointer mapping is calibrated to the drawn track span, not the host edges, so the visible track ends hit min/max exactly. The SVG `viewBox` attribute handles scaling to actual DOM pixels, so every calculation stays coordinate-system-agnostic. Because the inset is dynamic, gradients should span the full `0`–`100` long axis rather than the old fixed `5`–`95` track span.
622
662
 
623
- All geometry is computed in a fixed `100×24` viewBox coordinate space for horizontal, or `24×100` for vertical, with the track running along the long axis. The `CROSS = 24` constant sets the perpendicular dimension, and `LONG = 100` sets the long axis. The SVG `viewBox` attribute handles scaling to actual DOM pixels, so every calculation stays coordinate-system-agnostic. The path is always `(viewBox-relative) × (host size / viewBox size)` regardless of the host's rendered size.
663
+ Lane stroke widths resolve to screen px per render (`width fraction × measured cross px`, emitted as a px number with `vector-effect: non-scaling-stroke` so it paints verbatim). A `calc(var * 24%)` percentage cannot do this job: percentages resolve against the host-size-independent viewBox diagonal (~71 units), so documented 0..1 fractions painted ~10x too thin and never scaled with the host. Because strokes are px-per-render, a host `ResizeObserver` re-renders on size changes to keep them (and the measured box) exact. Dashes stay frozen via the same `non-scaling-stroke`, so `--lane-dasharray` values keep their px meaning.
624
664
 
625
665
  ### Lane offset needs a CSS unit
626
666
 
627
- `transform: translateY(${offset})` (unitless) is **invalid** CSS — a `<length>` is required, otherwise the whole `transform` declaration is dropped and the lane renders on the centreline. The code uses `translateY(${offset * CROSS / 2}px)` so the offset is expressed in viewBox units (with `px` suffix for CSS), making the offset stable across host sizes.
667
+ `transform: translateY(${offset})` (unitless) is **invalid** CSS — a `<length>` is required, otherwise the whole `transform` declaration is dropped and the lane renders on the centreline. The code uses `translateY(${offset * crossPx / 2}px)` so the offset stays a true fraction of the measured cross axis (with `px` suffix for CSS).
628
668
 
629
669
  ### Marker rendering uses `part` + per-handle CSS variable patterns
630
670