@capillaryjs/capillary-ui 1.2.0 → 1.3.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 (92) hide show
  1. package/CHANGELOG.md +109 -2
  2. package/README.md +177 -35
  3. package/colors/README.md +53 -3
  4. package/colors/gray/colors.css +4 -2
  5. package/colors/green/colors.css +4 -2
  6. package/colors/iceblue/colors.css +4 -2
  7. package/colors/ocean/colors.css +4 -2
  8. package/colors/orange/colors.css +4 -2
  9. package/colors/purple/colors.css +4 -2
  10. package/colors/red/colors.css +7 -3
  11. package/colors/yellow/colors.css +8 -4
  12. package/dist/Components/app/app.d.ts +3 -3
  13. package/dist/Components/app/app.d.ts.map +1 -1
  14. package/dist/Components/component.d.ts +13 -2
  15. package/dist/Components/component.d.ts.map +1 -1
  16. package/dist/Components/data/descriptionList.d.ts.map +1 -1
  17. package/dist/Components/data/listview/listview.d.ts.map +1 -1
  18. package/dist/Components/data/table/DataTable.d.ts +8 -0
  19. package/dist/Components/data/table/DataTable.d.ts.map +1 -1
  20. package/dist/Components/data/table/FilterPanel.d.ts.map +1 -1
  21. package/dist/Components/data/table/TableHeader.d.ts +2 -0
  22. package/dist/Components/data/table/TableHeader.d.ts.map +1 -1
  23. package/dist/Components/data/table/TableHeaderCell.d.ts +17 -0
  24. package/dist/Components/data/table/TableHeaderCell.d.ts.map +1 -1
  25. package/dist/Components/data/table/tableDataSource.d.ts +6 -0
  26. package/dist/Components/data/table/tableDataSource.d.ts.map +1 -1
  27. package/dist/Components/data/treeview/treeview.d.ts.map +1 -1
  28. package/dist/Components/layout/groupBox.d.ts.map +1 -1
  29. package/dist/Components/layout/header.d.ts.map +1 -1
  30. package/dist/Components/layout/layout.d.ts +2 -2
  31. package/dist/Components/layout/layout.d.ts.map +1 -1
  32. package/dist/Components/layout/layoutTraits.d.ts +6 -0
  33. package/dist/Components/layout/layoutTraits.d.ts.map +1 -1
  34. package/dist/Components/layout/optionGroup.d.ts.map +1 -1
  35. package/dist/Components/layout/optionsBox.d.ts +2 -1
  36. package/dist/Components/layout/optionsBox.d.ts.map +1 -1
  37. package/dist/Components/layout/splitView.d.ts +2 -0
  38. package/dist/Components/layout/splitView.d.ts.map +1 -1
  39. package/dist/Components/lineinputs/CheckableControl.d.ts +2 -2
  40. package/dist/Components/lineinputs/CheckableControl.d.ts.map +1 -1
  41. package/dist/Components/lineinputs/LabeledInputControl.d.ts +2 -2
  42. package/dist/Components/lineinputs/LabeledInputControl.d.ts.map +1 -1
  43. package/dist/Components/lineinputs/LineControl.d.ts +7 -0
  44. package/dist/Components/lineinputs/LineControl.d.ts.map +1 -0
  45. package/dist/Components/lineinputs/SelectControl.d.ts.map +1 -1
  46. package/dist/Components/lineinputs/checkbox/Checkbox.d.ts +9 -3
  47. package/dist/Components/lineinputs/checkbox/Checkbox.d.ts.map +1 -1
  48. package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts +1 -0
  49. package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts.map +1 -1
  50. package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts +1 -0
  51. package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts.map +1 -1
  52. package/dist/Components/lineinputs/datetime/TemporalInput.d.ts.map +1 -1
  53. package/dist/Components/lineinputs/dropdown.d.ts +4 -2
  54. package/dist/Components/lineinputs/dropdown.d.ts.map +1 -1
  55. package/dist/Components/lineinputs/radio.d.ts +9 -4
  56. package/dist/Components/lineinputs/radio.d.ts.map +1 -1
  57. package/dist/Components/lineinputs/requiredPresentation.d.ts +21 -0
  58. package/dist/Components/lineinputs/requiredPresentation.d.ts.map +1 -0
  59. package/dist/Components/lineinputs/textbox.d.ts.map +1 -1
  60. package/dist/Components/lineinputs/toggle.d.ts +4 -2
  61. package/dist/Components/lineinputs/toggle.d.ts.map +1 -1
  62. package/dist/Components/menu/button.d.ts +2 -2
  63. package/dist/Components/menu/button.d.ts.map +1 -1
  64. package/dist/Components/status/progressBar.d.ts +2 -2
  65. package/dist/Components/status/progressBar.d.ts.map +1 -1
  66. package/dist/Components/status/statusPresentation.d.ts.map +1 -1
  67. package/dist/Components/theme/stylesheetPicker.d.ts +2 -1
  68. package/dist/Components/theme/stylesheetPicker.d.ts.map +1 -1
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +2697 -2079
  72. package/dist/index.js.map +1 -1
  73. package/dist/jsx-dev-runtime.js +1 -1
  74. package/dist/{jsx-runtime-BD_q3rPg.js → jsx-runtime-Ba-uafjl.js} +595 -475
  75. package/dist/jsx-runtime-Ba-uafjl.js.map +1 -0
  76. package/dist/jsx-runtime.js +1 -1
  77. package/dist/localization.d.ts +1 -1
  78. package/dist/localization.d.ts.map +1 -1
  79. package/dist/routing/RouteOutlet.d.ts.map +1 -1
  80. package/dist/styling/theme.d.ts +1 -1
  81. package/dist/styling/theme.d.ts.map +1 -1
  82. package/docs/application-composition-guide.md +22 -5
  83. package/package.json +3 -3
  84. package/styles/structural.css +704 -158
  85. package/themes/README.md +9 -2
  86. package/themes/base.css +114 -18
  87. package/themes/capillary/theme.css +300 -0
  88. package/themes/shiny/theme.css +33 -15
  89. package/themes/soft/theme.css +37 -0
  90. package/themes/white/theme.css +52 -0
  91. package/dist/jsx-runtime-BD_q3rPg.js.map +0 -1
  92. package/themes/java/theme.css +0 -21
package/CHANGELOG.md CHANGED
@@ -6,8 +6,92 @@ Versioning.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ### Fixed
10
+
11
+ - Set the default GroupBox content gap independently from island spacing, using
12
+ a compact 2px rhythm for controls inside a GroupBox.
13
+
14
+ - Render unfilled required controls with a transparent negative ✲ marker:
15
+ inside native input surfaces and beside a pseudo-element checkable outline
16
+ extension that does not change the label's layout width.
17
+ - Place checkbox and radio validation halos around the complete labelled
18
+ choice while retaining the error border on the painted shell.
19
+ - Keep busy buttons visually normal and layer only the working animation over
20
+ their ordinary chrome. Gate ProgressBar's busy animation on an emitter-backed
21
+ `FetchState.Loading` state, applying the working texture to both determinate
22
+ and indeterminate bars so literal indeterminate values remain static.
23
+ - Make ProgressBar fill its available inline width while remaining shrinkable
24
+ inside constrained layouts.
25
+ - Omit island content padding on direct app-root headers, letting their navbar
26
+ and toolbar own spacing. Preserve nested headers, other islands, outer
27
+ gutters, borders, and shadows across themes and managed/legacy layouts.
28
+ - Keep island borders and shadows visible through nested scrollports by using
29
+ existing gutter space inside the clipping boundary. Preserve surface
30
+ alignment, shared gaps, and split resizing without theme clearance tokens.
31
+ Add browser pixel checks for painted borders/shadows and clipping regressions.
32
+ - Publish only the maintained Capillary, Shiny, Soft, White, and Minimal themes.
33
+ The former Glossy, Original, Java, Dark, and Sci-fi assets and picker entries
34
+ are removed from the package and applications.
35
+ - Make named-theme surfaces, text, borders, and fills palette-derived. In
36
+ particular, Shiny progress chrome now follows the muted surface and primary
37
+ ramps instead of fixed Ice blue stops. Neutral black/white gloss and shadow
38
+ overlays remain intentionally literal.
39
+ - Replace hue-named palette status primitives with semantic negative, positive,
40
+ and neutral status colors. Error and success aliases follow the negative and
41
+ positive colors; multi-state checkbox deny, require, and prefer chrome uses
42
+ negative, positive, and neutral respectively.
43
+ - Derive shared chrome backgrounds from muted primary palette tones, so panels,
44
+ table headers, button gradients, and disabled surfaces follow palette changes.
45
+ Ice blue preserves exactly `#f5f7f8`, `#ebeff3`, and `#d7dee3`. The derivation
46
+ uses relative HSL colors and sRGB mixing; existing UI aliases remain overridable.
47
+ - Add palette-level `--palette-primary-surface-saturation`: `0` removes primary
48
+ hue from the three chrome surfaces, `1` retains their calibrated default.
49
+ - Keep unchanged DataTable headers out of row-update renders and avoid redundant
50
+ renderer marker, attribute, dataset, and style writes.
51
+ - Preserve table-part names after minification and identify header cells by column.
52
+ - Preserve table/header/body identities when loading/error messages or captions change.
53
+
54
+ - `DataTable` filterable columns no longer keep a permanent document-level
55
+ click listener: the outside-click listener that closes the filter panel is
56
+ now attached only while the panel is open. Previously every click in the
57
+ document fired it, which also flooded event traces with a
58
+ "Table header cell: click" interaction per click. The listener uses the new
59
+ `Component.listenWhile`, so it stays on the normal traced native-event path
60
+ and coalesces with other listeners observing the same click.
61
+
9
62
  ### Added
10
63
 
64
+ - Add a live `readOnly` state to Dropdown, checkbox variants, RadioButton,
65
+ RadioGroup, Toggle, and stylesheet pickers. Read-only value controls stay
66
+ focusable and readable, expose native `readonly` or `aria-readonly` as
67
+ appropriate, and reject attempted user changes without becoming disabled.
68
+ - Add palette-derived base read-only chrome tokens for inputs, dropdowns,
69
+ checkables, and toggles. Themes can override the tokens but no longer need to
70
+ reproduce disabled styling to represent a fixed value.
71
+ - Add inherited `islands` layout mode to `CapillaryUiApp` and `Layout`, with
72
+ native `cap-island-layout` / `cap-island-layout-off` traits. One outer inset
73
+ and shared gutters replace accumulated island margins inside opt-in scopes;
74
+ ordinary island content and legacy layouts keep their existing spacing.
75
+ The `--island-gap` and `--island-inset` tokens support theme/application
76
+ overrides, RouteOutlet carries the mode, and SplitView puts its accessible
77
+ separator in one gutter, including zero-gap and RTL resizing.
78
+ - Add the light `capillary` theme: bright rounded cards, restrained neutral
79
+ elevation, compact controls, and palette-primary selected states.
80
+ - Add an overridable instance diagnostic label and a named table-body consumer.
81
+ Consumer facts report render triggers/passes and own renderer DOM-write counts,
82
+ distinguishing execution from DOM work without changing update scheduling.
83
+ - Add `Component.listenWhile(target, type, listener, options)`: like
84
+ `listen`, but returns a function that detaches the listener, for listeners
85
+ that are only active some of the time. It runs through the same traced
86
+ native-event coalescing as `listen` and is removed on destroy if still
87
+ attached.
88
+ - Add a `filterable` column flag to `DataTable`: a column marked
89
+ `filterable` derives its header filter options from the field's distinct
90
+ values across the source's unfiltered rows, so options stay stable while
91
+ a filter is applied. Explicit `filterOptions` still take precedence.
92
+ Local and caller-query data sources expose their pre-filter rows through
93
+ the new `TableDataSource.sourceRows`; remote sources fall back to the
94
+ displayed rows.
11
95
  - Add diagnostic ownership scopes and stable diagnostic labels, automatic
12
96
  native interaction roots, component/read/watch and function-consumer facts,
13
97
  and reactive child, live-property, and native-binding endpoints. Synchronous
@@ -16,6 +100,29 @@ Versioning.
16
100
 
17
101
  ### Changed
18
102
 
103
+ - Use flexbox for single-axis component stacks and simple centered chrome,
104
+ retaining grid only where its track model is needed. This does not change the
105
+ public DOM or layout contract.
106
+ - Give all single-line controls the same minimum row footprint with symmetric
107
+ breathing room, including checkbox/radio variants, temporal inputs, and
108
+ ProgressBar. Add `--control-row-min-height` and `--control-row-padding-block`
109
+ without changing painted control shapes or Capillary's `6px 5px` padding.
110
+ Only OptionsBox retains compact rows and owns a fixed 2px vertical option
111
+ rhythm across themes; RadioGroup no longer adds a default gap on top of the
112
+ common row spacing elsewhere.
113
+ - Empty required text, temporal, and dropdown controls now have a subtle
114
+ themeable dashed required-value outline. The same outline appears on an
115
+ unchecked two-state `Checkbox`; tri- and quad-state checkboxes intentionally
116
+ do not use native required validity because no single semantic state is the
117
+ required value.
118
+ - Retained `Loading` results in `DataTable`, `ListView`, and `TreeView` now
119
+ show a non-blocking translucent animated working texture while their existing
120
+ rows remain visible.
121
+
122
+ ## 1.2.0 - 2026-09-15
123
+
124
+ ### Changed
125
+
19
126
  - Make DataTable, ListView, and TreeView shrink and own overflow before a
20
127
  bounded flex layout container must scroll. DataTable header cells now remain
21
128
  sticky within the table's scrollport.
@@ -42,8 +149,8 @@ Versioning.
42
149
  `cap-header:has(+ cap-toolbar)` shadow override together with its
43
150
  `--section-header-with-toolbar-shadow` variable.
44
151
  - Normalize line-control sizing and text centering in structural CSS: 24px
45
- default textbox, dropdown, toggle, and button bodies at 12px UI text, with
46
- compact checkbox/radio rows. Remove Shiny's label/select text offsets and
152
+ default textbox, dropdown, toggle, and button bodies at 12px UI text.
153
+ Remove Shiny's label/select text offsets and
47
154
  prevent toggle border/selection changes from shifting segment text.
48
155
  - Refine Shiny's input depth, toolbar/table-header chrome, navigation-to-toolbar
49
156
  seam, and loading BlockGraph treatment without moving structural layout
package/README.md CHANGED
@@ -149,6 +149,14 @@ A class component has explicit phases:
149
149
  `onCleanup()` registers listeners or other cleanup functions that Capillary UI invokes
150
150
  on destruction.
151
151
 
152
+ Diagnostic labels default to static `diagnosticLabel`, `hostName`, then the class
153
+ name. Override the instance `diagnosticLabel` getter when instances need distinct
154
+ names; identity labels are captured at first observation. Table diagnostics expose
155
+ `Table header`, column-specific header cells, and `Table body`. Unchanged header
156
+ props skip parent-driven rendering, while sort/filter subscriptions remain active.
157
+ Consumer events distinguish render passes from own renderer DOM writes; see the
158
+ [diagnostics guide](../../docs/diagnostics.md) for the exact counting boundary.
159
+
152
160
  ```tsx
153
161
  class Counter extends Component {
154
162
  readonly count = new Emitter(0)
@@ -197,7 +205,7 @@ class Badge extends Component<BadgeProps> {
197
205
  static override hostName = 'badge'
198
206
  static override css = css`
199
207
  & { display: inline-flex; }
200
- &[data-tone="positive"] { color: var(--palette-green); }
208
+ &[data-tone="positive"] { color: var(--palette-status-positive); }
201
209
  `
202
210
  }
203
211
  ```
@@ -313,11 +321,11 @@ the tables below denotes that TypeScript type parameter.
313
321
  | `Toolbar` | Named action group | `label`, `orientation` |
314
322
  | `Label` | Native label for rich or live text | `text`, `htmlFor`; live: `text` |
315
323
  | `Textbox` | Labelled native text input with validation | `label`, `valueEmitter`, `defaultValue`, `type`, `name`, `placeholder`, `disabled`, `required`, `readOnly`, `busy`, `error`, native text constraints, `inputRef`, `onInput`, `onChange`; live: availability, `busy`, and `error` |
316
- | `Dropdown<T>` | Labelled native select | `options`, `label`, `valueEmitter`, `defaultValue`, `placeholder`, `disabled`, `required`, `busy`, `error`, `onChange`; `options` may be static or a readable emitter whose fetch state supplies loading/error feedback |
317
- | `RadioButton` | Standalone native radio and label | `label`, `name`, `value`, `checked`, `disabled`, `required`, `busy`, `error`, `onChange`; live: state, availability, `busy`, `error` |
318
- | `RadioGroup<T>` | Named native-radio fieldset owning one value | `options` as `[value, label]` tuples, `label`, `valueEmitter`, `defaultValue`, `disabled`, `required`, `busy`, `error`, `onChange`; options are ordinary render data |
319
- | `Toggle<T>` | ARIA radio group rendered as toggle buttons | `options` as `[value, label]` tuples, `label`, `valueEmitter`, `defaultValue`, `disabled`, `required`, `busy`, `error`, `onChange` |
320
- | `Checkbox<T>` | Configurable keyboard-operable semantic state cycle | `symbols` as `[content, value]` tuples, `label`/`ariaLabel`, `valueEmitter`, `defaultValue`, `disabled`, `required`, `busy`, `error`, `onChange` |
324
+ | `Dropdown<T>` | Labelled native select | `options`, `label`, `valueEmitter`, `defaultValue`, `placeholder`, `disabled`, `required`, `readOnly`, `busy`, `error`, `onChange`; `readOnly` retains focusability and restores the selected value after attempted changes; `options` may be static or a readable emitter whose fetch state supplies loading/error feedback |
325
+ | `RadioButton` | Standalone native radio and label | `label`, `name`, `value`, `checked`, `disabled`, `required`, `readOnly`, `busy`, `error`, `onChange`; live: state, availability, `busy`, `error` |
326
+ | `RadioGroup<T>` | Named native-radio fieldset owning one value | `options` as `[value, label]` tuples, `label`, `valueEmitter`, `defaultValue`, `disabled`, `required`, `readOnly`, `busy`, `error`, `onChange`; options are ordinary render data |
327
+ | `Toggle<T>` | ARIA radio group rendered as toggle buttons | `options` as `[value, label]` tuples, `label`, `valueEmitter`, `defaultValue`, `disabled`, `required`, `readOnly`, `busy`, `error`, `onChange` |
328
+ | `Checkbox<T>` | Configurable keyboard-operable semantic state cycle | `symbols` as `[content, value]` tuples, `label`/`ariaLabel`, `valueEmitter`, `defaultValue`, `disabled`, `required`, `readOnly`, `busy`, `error`, `onChange` |
321
329
  | `TriCheckbox` | Neutral/prefer/deny `FilterMode` cycle | Same public props as `Checkbox`, except fixed symbols |
322
330
  | `QuadCheckbox` | Neutral/prefer/require/deny `FilterMode` cycle | Same public props as `Checkbox`, except fixed symbols |
323
331
  | `DatePicker` (experimental) | Native `<input type="date">` | `CivilDate` value props, `label`/`ariaLabel`, `name`, `autoComplete`, `disabled`, `required`, `readOnly`, `busy`, `error`, `min`/`max`/`step`, `inputRef`, input/change callbacks |
@@ -330,7 +338,12 @@ the native forward cycle.
330
338
 
331
339
  `busy` is presentational state: it sets native/ARIA busy semantics and paints
332
340
  the theme's moving working texture without disabling an input or choice.
333
- `Button` remains the exception: a busy action is unavailable until it settles.
341
+ `Button` remains the exception: a busy action is unavailable until it settles,
342
+ but keeps its normal button colors, border, and elevation with the working
343
+ texture layered on top. `ProgressBar` only paints that working texture when it
344
+ has a `valueEmitter` whose fetch state is `Loading`; this applies to both
345
+ determinate and indeterminate bars. A literal `value={null}` is simply an
346
+ unfilled indeterminate bar.
334
347
  When `error` is also present, error presentation wins over the animation.
335
348
  Every error-bearing control describes its native surface with a focusable
336
349
  `role="alert"` overlay. Its icon and initially hidden message are absolutely
@@ -355,12 +368,12 @@ const view = new Emitter<'list' | 'grid'>('list')
355
368
 
356
369
  | Component | Purpose | Key props and state |
357
370
  | --- | --- | --- |
358
- | `CapillaryUiApp` | Fixed `cap-app` application shell and theme-text boundary | `sizing`: `embedded`/viewport axes; `layout`: `horizontal`/`vertical`; `landmark`: `main`/`none`; content or overridden `renderContent()` |
371
+ | `CapillaryUiApp` | Fixed `cap-app` application shell and theme-text boundary | `sizing`: `embedded`/viewport axes; `layout`: `horizontal`/`vertical`; `islands`: inherited surface spacing; `landmark`: `main`/`none`; content or overridden `renderContent()` |
359
372
  | `Header` | Styled native heading surface | `level` (1–6), `headingId`, content |
360
- | `GroupBox` | Labelled group; defaults to a bordered vertical-header group and may be a section or column | required `header`, content; optional `variant`: `section` or `column` |
361
- | `OptionGroup` | Labelled native fieldset for related controls | `label`/`ariaLabel`, `OptionGroupHeaderEnd` and ordinary content children, `disabled`, `required`, `busy`, `error`; state props are live |
362
- | `OptionsBox` | GroupBox specialization arranging option groups | required `header`, `OptionGroup` content |
363
- | `Layout` | Presentation-only arrangement of arbitrary children | exactly one of `horizontal`/`vertical`; `allocation`, `scroll`, optional accessible-region configuration |
373
+ | `GroupBox` | Labelled group; defaults to a bordered vertical-header group and may be a section or column | required `header`, direct content arranged vertically (`section` arranges direct content horizontally); optional `variant`: `section` or `column` |
374
+ | `OptionGroup` | Labelled native fieldset for related controls | `label`/`ariaLabel`, `OptionGroupHeaderEnd` and ordinary content children, `disabled`, `required`, `busy`, `error`; state props are live. Use for related direct checkboxes or multiple distinct controls. |
375
+ | `OptionsBox` | GroupBox specialization arranging compact choice groups | required `header`, direct `OptionGroup` and/or `RadioGroup` content |
376
+ | `Layout` | Presentation-only arrangement of arbitrary children | exactly one of `horizontal`/`vertical`; `allocation`, `scroll`, inherited `islands` spacing, optional accessible-region configuration |
364
377
  | `Panel` | Optional labelled, themed region composed over a Layout body | `header`, `horizontal`/`vertical`, `allocation`, `scroll`, `disabled`; `PanelToolbar` and ordinary content children; live: `disabled` |
365
378
  | `Sidebar` | Labelled complementary region with fixed header/toolbar and scrolling content | `header`, `ariaLabel`; `SidebarToolbar` and ordinary content children |
366
379
  | `SplitView` | Resizable two-pane layout | required `SplitPrimary` and `SplitSecondary` Layout panes; `horizontal`/`vertical`, `allocation`, initial/minimum sizes, separator label, `onResize` |
@@ -475,12 +488,13 @@ range while retaining selections outside that range.
475
488
  On an initial snapshot, or a loading snapshot with no result, all three
476
489
  collection views render deterministic, `aria-hidden` placeholder rows;
477
490
  `placeholderCount` selects their count. A loading snapshot with retained rows
478
- keeps those rows in place and marks the collection busy, avoiding flicker during
479
- sort and filter refinements. Application renderers never receive dummy values,
480
- and refreshes preserve valid keyed selections and expansions. Error snapshots
481
- retain any available rows, add an error edge and overlay detail icon, and stop
482
- the loading animation. A `DataTable` data source with `retry` also renders its
483
- localized retry action.
491
+ keeps those rows in place, marks the collection busy, and displays a thin
492
+ animated translucent working texture, avoiding flicker during sort and filter
493
+ refinements.
494
+ Application renderers never receive dummy values, and refreshes preserve valid
495
+ keyed selections and expansions. Error snapshots retain any available rows,
496
+ add an error edge and overlay detail icon, and stop the loading animation. A
497
+ `DataTable` data source with `retry` also renders its localized retry action.
484
498
 
485
499
  Advanced compositions may use `BaseSelectionHandler`,
486
500
  `SingleSelectionHandler`, `MultiSelectionHandler`, and
@@ -503,13 +517,21 @@ expander needs a reusable presentation trait such as `colored`.
503
517
  For reusable sources, use `createLocalTableDataSource`,
504
518
  `createQueryTableDataSource`, `createHandlerTableDataSource`, or
505
519
  `createRestTableDataSource`. Sources expose `query`, `sortEmitter`,
506
- `filtersEmitter`, optional `retry`, and `dispose()`. Both direct data and
507
- query-shaped sources pass their rows through an emitter-derived local table
508
- view, so their sort and filter state always changes the rendered rows.
520
+ `filtersEmitter`, optional `retry`, optional `sourceRows`, and `dispose()`.
521
+ Both direct data and query-shaped sources pass their rows through an
522
+ emitter-derived local table view, so their sort and filter state always
523
+ changes the rendered rows. `sourceRows` exposes the rows before local
524
+ sort/filter when the source can provide them — local and caller-query
525
+ sources do; remote sources leave it absent because filtering is
526
+ server-side.
509
527
 
510
528
  `TableColumn` definitions own display and local comparison/filter functions.
511
529
  When a column's visible `label` is rich content, supply its textual
512
530
  `ariaLabel` for Capillary UI-generated sort and filter control names.
531
+ Set a column's `filterable` flag to derive its header filter options from
532
+ the field's distinct values across `sourceRows` (or the displayed rows when
533
+ the source cannot expose them), so the options stay stable while a filter
534
+ is applied. Explicit `filterOptions` take precedence over `filterable`.
513
535
  The pure `applyLocalTableState`, `serializeTableQuery`, and related table-query
514
536
  helpers keep local behavior and remote encoding explicit. Pagination,
515
537
  virtualization, and server-specific wire policy remain application concerns.
@@ -519,7 +541,7 @@ virtualization, and server-specific wire policy remain application concerns.
519
541
  | Component | Purpose | Key props and state |
520
542
  | --- | --- | --- |
521
543
  | `Dialog` | Controlled native modal with focus containment and restoration | `title`, `description`, `DialogActions` and ordinary content children, `valueEmitter`/`defaultValue`, `closeLabel`, `showCloseButton`, `initialFocusRef`, `onClose` |
522
- | `ProgressBar` | Labelled native progress with visual track | required `label`, `value` or `valueEmitter`, `max`, `valueText`; `null` is indeterminate |
544
+ | `ProgressBar` | Labelled native progress with visual track | required `label`, `value` or `valueEmitter`, `max`, `valueText`; fills its available width; `null` is indeterminate; emitter-backed busy chrome (including determinate bars) requires `FetchState.Loading` |
523
545
  | `ThemePicker` | Select and replace a Capillary UI theme link | value props, `options`, `label`/`ariaLabel`, `disabled`, `targetDocument`, `onChange` |
524
546
  | `ColorPicker` | Select and replace a Capillary UI color link | same contract as `ThemePicker` |
525
547
 
@@ -738,6 +760,14 @@ component selectors. Color files provide anchors and endpoints. Component
738
760
  `static css` owns selectors, layout, pseudo-elements, native states, and
739
761
  interaction mechanics.
740
762
 
763
+ The three chrome backgrounds (`--ui-primary-bg-color`, `--ui-medium-bg-color`,
764
+ and `--ui-dark-bg-color`) alias muted primary surface tones derived from the
765
+ palette's primary anchor and light endpoint. Ice blue retains the exact legacy
766
+ colors `#f5f7f8`, `#ebeff3`, and `#d7dee3`; other palettes recolor these surfaces.
767
+ See the [palette contract](colors/README.md#muted-primary-surfaces) for the
768
+ relative-HSL/sRGB calibration, including the `0`–`1`
769
+ `--palette-primary-surface-saturation` palette control and override points.
770
+
741
771
  Theme files provide intentional overrides. Custom properties are the primary
742
772
  instrument and belong on `:root` inside `@layer theme`, with `color-scheme` as
743
773
  the only ordinary property in that block. A theme may also write ordinary CSS
@@ -746,6 +776,11 @@ be placed after the `@layer theme` block: component CSS is injected as an
746
776
  unlayered `<style>` element prepended to `<head>`, so unlayered theme rules win
747
777
  by document order while layered ones would always lose.
748
778
 
779
+ The built-in theme options are Capillary, Shiny, Soft, White, and Minimal.
780
+ Capillary is a bright, compact card treatment with restrained elevation and
781
+ palette-primary selections. Minimal is adaptive; the other options are fixed
782
+ light treatments.
783
+
749
784
  `capillaryUiThemeVariableCatalog` describes the supported palette and semantic
750
785
  variable hierarchy. `findCapillaryUiStylesheetOption`, `replaceCapillaryUiStylesheet`,
751
786
  `setCapillaryUiAppearance`, and `getCapillaryUiAppearance` support application-controlled
@@ -753,10 +788,23 @@ runtime selection.
753
788
 
754
789
  ### Line-control sizing
755
790
 
756
- Textbox, Dropdown, Toggle, and Button use `--control-min-height: 2em` by
757
- default: a 24px border-box minimum at the default 12px `--ui-font-size`.
758
- This minimum is independent of general UI padding and the text line height.
759
- Their labels and native controls inherit the font family and share a unitless
791
+ All single-line controls reserve the same vertical row space: Textbox,
792
+ Dropdown (including theme/color pickers), Toggle, Button, checkbox variants,
793
+ RadioButton, date/time controls, and ProgressBar. Their existing painted
794
+ bodies remain centered within that row; small checkbox/radio shapes do not
795
+ grow to look like text fields.
796
+
797
+ `--control-min-height: 2em` sets the body minimum (24px at the default 12px
798
+ `--ui-font-size`). The row uses the greater of that minimum and
799
+ `--control-row-min-height` (default `0px`), plus
800
+ `--control-row-padding-block: .25em` on each side. Default rows are therefore
801
+ 30px with 3px above and below the body. Capillary supplies a `2.75em` row
802
+ floor (39px including padding) to accommodate its roomier chrome and native
803
+ temporal inputs while retaining its `6px 5px` control padding. Additional
804
+ layout `gap` is optional and adds to this built-in breathing room.
805
+
806
+ These are minimum sizes: multiline/rich content can grow without clipping.
807
+ Labels and native controls inherit the font family and share a unitless
760
808
  1.2 authored text line height. Native single-line inputs may clamp the used
761
809
  line-height to platform font metrics (notably Firefox on Linux); their bodies
762
810
  and text remain centered. Larger content can increase the minimum-sized body.
@@ -765,6 +813,29 @@ minimum-width/shrinking behavior. Field and button inline padding is 5px;
765
813
  toggle segments use 6px. General `--space-xs`/`--space-sm` no longer determine
766
814
  these line controls' padding.
767
815
 
816
+ `OptionsBox` deliberately keeps compact descendants, including through nested
817
+ `OptionGroup` and `Layout` wrappers: it removes the shared row floor and
818
+ block padding while retaining each control's natural body size. Its checkbox
819
+ and radio rows are normally 1.2em, or the theme's larger painted-shell size.
820
+ OptionsBox owns a fixed 2px vertical option rhythm—between nested groups,
821
+ checkboxes, and radio options—regardless of theme. Ordinary GroupBox and
822
+ Toolbar do not opt into compact spacing. RadioGroup elsewhere adds no
823
+ inter-option gap by default, so the same number of radio and checkbox rows
824
+ occupies the same height (excluding any group legend). Set
825
+ `--radio-group-gap` to add spacing explicitly outside OptionsBox.
826
+
827
+ An `OptionGroup` and a labelled `RadioGroup` both produce a native
828
+ `fieldset`/`legend`. For one mutually exclusive choice, put the `RadioGroup`
829
+ directly in an `OptionsBox` and give it the one visible label. Use an
830
+ `OptionGroup` for a collection of direct checkboxes (or when its legend adds a
831
+ separate, meaningful concept around several controls). Do not wrap a single
832
+ labelled `RadioGroup` in an `OptionGroup` merely for layout; that creates
833
+ redundant legends and nested fieldsets.
834
+
835
+ Maintained themes add chrome without text offsets. Shadows do not participate
836
+ in centering. Toggle borders and selected overlap are painted independently
837
+ of segment layout, so selection does not move text.
838
+
768
839
  Horizontal form `GroupBox` children use their natural content size: GroupBox
769
840
  does not contribute a synthetic preferred width or an artificial size floor.
770
841
  Intrinsic label/body tracks distinguish preferred field widths from the
@@ -794,14 +865,6 @@ their normal inset. In this composition, omit a `DataTable` `caption`: the
794
865
  labelled Panel header supplies the surrounding data-region name. Application
795
866
  data-surface components can opt in by declaring the same static trait.
796
867
 
797
- Checkbox variants and RadioButton retain compact 1.2em label rows with 1em
798
- squares/circles. They center within stretched horizontal hosts without making
799
- vertical lists as tall as text fields. Date/time controls consume the same
800
- shared sizing rules. Themes may deliberately override the minimum (Java does),
801
- but Shiny uses the structural defaults and adds chrome without text offsets.
802
- Shadows do not participate in centering. Toggle borders and selected overlap
803
- are painted independently of segment layout, so selection does not move text.
804
-
805
868
  ### Root sizing and typography
806
869
 
807
870
  `CapillaryUiApp` is block-level and always applies `--application-background`,
@@ -837,7 +900,7 @@ semantics.
837
900
  Applications may explicitly opt native elements into Capillary UI presentation and
838
901
  layout contracts by applying public Capillary UI traits such as `island`,
839
902
  `cap-layout-horizontal`, `cap-layout-vertical`, `cap-size-natural`,
840
- `cap-size-flexible`, and `cap-scroll`. These traits are intentionally
903
+ `cap-size-flexible`, `cap-scroll`, and `cap-island-layout`. These traits are intentionally
841
904
  element-agnostic and may style application-owned native markup as well as
842
905
  Capillary UI-owned hosts.
843
906
 
@@ -893,6 +956,85 @@ provide breakpoint variants.
893
956
  native markup. Capillary UI rejects nested component islands; application markup must
894
957
  preserve the same one-layer invariant.
895
958
 
959
+ ### Composing islands without margin doubling
960
+
961
+ Enable `islands` once on the app or the Layout that contains the composition:
962
+
963
+ ```tsx
964
+ <CapillaryUiApp sizing="viewport" islands>
965
+ <Header island>Application header</Header>
966
+ <Layout horizontal allocation="flexible">
967
+ <Sidebar island header="Navigation">...</Sidebar>
968
+ <Layout vertical allocation="flexible">
969
+ <Panel island header="Overview">...</Panel>
970
+ <Panel island header="Results" allocation="flexible">...</Panel>
971
+ </Layout>
972
+ </Layout>
973
+ <footer className="island cap-size-natural">Status</footer>
974
+ </CapillaryUiApp>
975
+ ```
976
+
977
+ `islands` is a **layout mode**; `island` still marks each **surface**. The
978
+ outermost enabled container gets one perimeter inset. Participating layouts
979
+ then place a single shared gutter between their children, with no island
980
+ margins to add up. Nested Layouts inherit the mode automatically, including
981
+ through ordinary wrappers and `RouteOutlet`. Repeating `islands` on an already
982
+ enabled nested Layout does not add another inset. An app with `islands` and no
983
+ explicit `layout` defaults to vertical; Layout still requires an axis.
984
+
985
+ The public `CapillaryUiIslandLayoutProps` contract is static `islands?: boolean`:
986
+
987
+ - `true` enables managed composition;
988
+ - omitted or `undefined` inherits the surrounding mode;
989
+ - `false` stops inheritance and restores legacy spacing in that subtree.
990
+
991
+ Surface boundaries stop managed layout gaps from reaching controls inside
992
+ them. Panel padding, toolbar gaps, and other internal spacing keep their own
993
+ contracts. A later explicit enabled scope below an opt-out starts a fresh
994
+ perimeter inset. Neither this mode nor nested layouts permit nested islands.
995
+
996
+ Use `--island-gap` for gutters and `--island-inset` for the outer inset. Base
997
+ defaults both to `1rem`; White uses zero. Override these on the composition,
998
+ and optionally `--island-gap` on a nested Layout for a local gutter.
999
+ `--island-padding` remains the padding **inside** each surface, except a direct
1000
+ app-root header: `cap-app > header.island` and `cap-app > cap-header.island`
1001
+ have zero padding. These shell headers delegate internal spacing to their
1002
+ navbar, toolbar, and branding rows. Their border, shadow, outer inset, and
1003
+ gutter remain unchanged. Nested headers and non-island headings are unaffected;
1004
+ this rule also applies outside managed island mode. Use a native `<header
1005
+ className="island">` for a shell containing navigation/toolbars; `Header`
1006
+ remains the component for a native heading.
1007
+
1008
+ `SplitView`
1009
+ inherits the mode and places its separator in one shared gutter; a zero gutter
1010
+ retains an overlapping drag target and keyboard resizing.
1011
+
1012
+ Scrolling layouts automatically include surrounding gutter space in their
1013
+ scrollport, so an island's border and shadow can paint there. Equal padding
1014
+ and negative margin extend the clip by half a gutter without moving the
1015
+ surfaces or increasing their separation. Nested scrollports reuse this space;
1016
+ RouteOutlet and SplitView leave clipping to their content/panes. Themes only
1017
+ declare their ordinary borders and shadows: no clearance token is required.
1018
+ Surface contents and opt-outs retain ordinary scrolling behavior. A zero
1019
+ gutter deliberately leaves no paint space between adjacent surfaces.
1020
+
1021
+ Native markup can use `cap-island-layout` and `cap-island-layout-off`. Combine
1022
+ them with a direction trait, or with your own `display: grid` rules. A wrapper
1023
+ around one region needs no new trait. A native wrapper arranging multiple
1024
+ regions must opt into a layout: inheritance does not turn arbitrary elements
1025
+ into flex/grid containers. Axes, dimensions, responsive wrapping, allocation,
1026
+ and scroll ownership remain explicit application choices.
1027
+
1028
+ Migration is opt-in. Existing layouts outside managed scopes keep
1029
+ `--island-margin` and their former behavior. When enabling a scope, remove
1030
+ margin-cancellation selectors and redundant gap rules, and move any intended
1031
+ spacing override to `--island-gap`/`--island-inset` on the layout. A direct CSS
1032
+ `margin` or `gap` override still wins through the normal cascade; the framework
1033
+ does not measure or rewrite application styles. `islands={false}` can isolate
1034
+ a legacy region during incremental migration.
1035
+
1036
+ ### Semantic category color
1037
+
896
1038
  `colored` consumes an explicit `--c1`, `--c2`, `--c3` triplet for the shared
897
1039
  gradient and `--colored-shadow` treatment. It does not choose semantic colors
898
1040
  for the application.
package/colors/README.md CHANGED
@@ -14,9 +14,13 @@ A palette supplies:
14
14
 
15
15
  - `--palette-light` and `--palette-dark` endpoints;
16
16
  - `--palette-contrast-light` and `--palette-contrast-dark` foregrounds;
17
- - `--palette-red` and `--palette-green` status primitives;
17
+ - `--palette-status-negative`, `--palette-status-positive`, and
18
+ `--palette-status-neutral` status primitives. The last is deliberately
19
+ distinct from the `--palette-neutral-*` tonal ramp;
18
20
  - `--palette-primary-500`, `--palette-secondary-500`, and
19
21
  `--palette-neutral-500` anchors.
22
+ - palette-primary-surface-saturation, a 0–1 multiplier for the three muted
23
+ primary chrome surfaces.
20
24
 
21
25
  `themes/base.css` derives the remaining numeric ramp stops, ordinary
22
26
  `primary`/`light`/`dark` aliases, and transparent light variants with
@@ -31,9 +35,11 @@ ramp intentionally changes hue.
31
35
  --palette-dark: #111827;
32
36
  --palette-contrast-light: #fff;
33
37
  --palette-contrast-dark: #111827;
34
- --palette-red: #c62828;
35
- --palette-green: #2e7d32;
38
+ --palette-status-negative: #c62828;
39
+ --palette-status-positive: #2e7d32;
40
+ --palette-status-neutral: #64748b;
36
41
  --palette-primary-500: #2989d8;
42
+ --palette-primary-surface-saturation: 1;
37
43
  --palette-secondary-500: #7137a8;
38
44
  --palette-neutral-500: #7892aa;
39
45
  }
@@ -44,6 +50,50 @@ Color files must not declare semantic roles such as `--button-*`,
44
50
  `--input-*`, or `--panel-*`. The base and active theme map derived palette
45
51
  values to those roles.
46
52
 
53
+ ## Muted primary surfaces
54
+
55
+ The base also derives `--palette-primary-surface-light`,
56
+ `--palette-primary-surface-medium`, and `--palette-primary-surface-dark` from
57
+ `--palette-primary-500` and `--palette-light`. These are pale, desaturated
58
+ surface tones, separate from the accent ramp: Ice blue's cyan
59
+ `--palette-primary-light-mix` intentionally does not tint these surfaces.
60
+ The existing `--ui-primary-bg-color`, `--ui-medium-bg-color`, and
61
+ `--ui-dark-bg-color` variables alias them, respectively. Shiny panels, table
62
+ headers, button gradients, and other consumers retain their existing wiring.
63
+
64
+ The derivation uses relative HSL to reduce primary saturation and apply a small
65
+ hue offset, followed by `color-mix(in srgb, ..., var(--palette-light))`. Its
66
+ rational coefficients preserve the historical Ice blue colors exactly:
67
+
68
+ | Surface | Ice blue sRGB | Hue offset | Saturation multiplier | Primary tint weight |
69
+ | --- | --- | --- | --- | --- |
70
+ | light | `#f5f7f8` | `-248/35` degrees | `759/2975` | `17/253` |
71
+ | medium | `#ebeff3` | `102/35` degrees | `253/700` | `32/253` |
72
+ | dark | `#d7dee3` | `-73/35` degrees | `759/2975` | `68/253` |
73
+
74
+ These are calibration ratios, not arbitrary rounded percentages: the reference
75
+ primary `#2989d8` has HSL hue `7248/35`, saturation `17500/253` percent, and
76
+ lightness `12850/255` percent. The three legacy targets have different hues
77
+ and saturations, so one white/primary ramp cannot reproduce all three. The
78
+ same calibrated recipe runs for every palette; there is no Ice blue override.
79
+ Changing the primary anchor or light endpoint recomputes all three surfaces.
80
+ Applications may still override the surface tokens or existing UI aliases.
81
+
82
+ `--palette-primary-surface-saturation` controls how much primary hue the three
83
+ surfaces retain. It is a palette number from `0` through `1`: `1` is the
84
+ calibrated default (and preserves Ice blue exactly), `0.5` halves the primary
85
+ saturation, and `0` makes all three surfaces completely neutral. It affects
86
+ only these muted surfaces, never the primary accent ramp or component semantic
87
+ roles that use it.
88
+
89
+ Shiny's chrome and progress-track gradients explicitly interpolate in sRGB,
90
+ preserving their legacy appearance when stops become derived `color()` values.
91
+ Custom gradients that need the same legacy interpolation should likewise declare
92
+ `in srgb` instead of relying on the browser's choice of interpolation space.
93
+
94
+ This derivation requires relative HSL colors and `color-mix()`, verified in
95
+ the package's Chromium, Firefox, and WebKit browser matrix.
96
+
47
97
  ## Loading and replacement
48
98
 
49
99
  Load one palette after Capillary UI's base and structural CSS and before the active
@@ -6,10 +6,12 @@
6
6
  --palette-dark: #111827;
7
7
  --palette-contrast-light: var(--palette-light);
8
8
  --palette-contrast-dark: var(--palette-dark);
9
- --palette-red: #b3261e;
10
- --palette-green: #287a3c;
9
+ --palette-status-negative: #b3261e;
10
+ --palette-status-positive: #287a3c;
11
+ --palette-status-neutral: #64748b;
11
12
 
12
13
  --palette-primary-500: #5d6873;
14
+ --palette-primary-surface-saturation: 1;
13
15
  --palette-secondary-500: #1769aa;
14
16
  --palette-neutral-500: #858d95;
15
17
  }
@@ -6,10 +6,12 @@
6
6
  --palette-dark: #111827;
7
7
  --palette-contrast-light: var(--palette-light);
8
8
  --palette-contrast-dark: var(--palette-dark);
9
- --palette-red: #b3261e;
10
- --palette-green: #287a3c;
9
+ --palette-status-negative: #b3261e;
10
+ --palette-status-positive: #287a3c;
11
+ --palette-status-neutral: #64748b;
11
12
 
12
13
  --palette-primary-500: #287a3c;
14
+ --palette-primary-surface-saturation: 1;
13
15
  --palette-secondary-500: #087f78;
14
16
  --palette-neutral-500: #75947a;
15
17
  }
@@ -6,13 +6,15 @@
6
6
  --palette-dark: #111827;
7
7
  --palette-contrast-light: var(--palette-light);
8
8
  --palette-contrast-dark: var(--palette-dark);
9
- --palette-red: #b3261e;
10
- --palette-green: #287a3c;
9
+ --palette-status-negative: #b3261e;
10
+ --palette-status-positive: #287a3c;
11
+ --palette-status-neutral: #64748b;
11
12
 
12
13
  /* Preserve the legacy Shiny movement from navy through blue to cyan. */
13
14
  --palette-primary-light-mix: #00b9e8;
14
15
  --palette-primary-dark-mix: #00193f;
15
16
  --palette-primary-500: #2989d8;
17
+ --palette-primary-surface-saturation: 1;
16
18
  --palette-secondary-500: #7137a8;
17
19
  --palette-neutral-500: #7892aa;
18
20
  }
@@ -6,10 +6,12 @@
6
6
  --palette-dark: #111827;
7
7
  --palette-contrast-light: var(--palette-light);
8
8
  --palette-contrast-dark: var(--palette-dark);
9
- --palette-red: #b3261e;
10
- --palette-green: #287a3c;
9
+ --palette-status-negative: #b3261e;
10
+ --palette-status-positive: #287a3c;
11
+ --palette-status-neutral: #64748b;
11
12
 
12
13
  --palette-primary-500: #087f78;
14
+ --palette-primary-surface-saturation: 1;
13
15
  --palette-secondary-500: #1769aa;
14
16
  --palette-neutral-500: #668f8a;
15
17
  }
@@ -6,10 +6,12 @@
6
6
  --palette-dark: #111827;
7
7
  --palette-contrast-light: var(--palette-light);
8
8
  --palette-contrast-dark: var(--palette-dark);
9
- --palette-red: #b3261e;
10
- --palette-green: #287a3c;
9
+ --palette-status-negative: #b3261e;
10
+ --palette-status-positive: #287a3c;
11
+ --palette-status-neutral: #64748b;
11
12
 
12
13
  --palette-primary-500: #a94b00;
14
+ --palette-primary-surface-saturation: 1;
13
15
  --palette-secondary-500: #7137a8;
14
16
  --palette-neutral-500: #a87a56;
15
17
  }
@@ -6,10 +6,12 @@
6
6
  --palette-dark: #111827;
7
7
  --palette-contrast-light: var(--palette-light);
8
8
  --palette-contrast-dark: var(--palette-dark);
9
- --palette-red: #b3261e;
10
- --palette-green: #287a3c;
9
+ --palette-status-negative: #b3261e;
10
+ --palette-status-positive: #287a3c;
11
+ --palette-status-neutral: #64748b;
11
12
 
12
13
  --palette-primary-500: #7137a8;
14
+ --palette-primary-surface-saturation: 1;
13
15
  --palette-secondary-500: #1769aa;
14
16
  --palette-neutral-500: #9273a2;
15
17
  }