@chromvoid/headless-ui 0.1.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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/a11y-contracts/index.d.ts +23 -0
- package/dist/a11y-contracts/index.js +1 -0
- package/dist/accordion/index.d.ts +78 -0
- package/dist/accordion/index.js +264 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1 -0
- package/dist/alert/index.d.ts +33 -0
- package/dist/alert/index.js +54 -0
- package/dist/alert-dialog/index.d.ts +69 -0
- package/dist/alert-dialog/index.js +94 -0
- package/dist/badge/index.d.ts +48 -0
- package/dist/badge/index.js +89 -0
- package/dist/breadcrumb/index.d.ts +55 -0
- package/dist/breadcrumb/index.js +77 -0
- package/dist/button/index.d.ts +46 -0
- package/dist/button/index.js +86 -0
- package/dist/callout/index.d.ts +41 -0
- package/dist/callout/index.js +63 -0
- package/dist/card/index.d.ts +54 -0
- package/dist/card/index.js +103 -0
- package/dist/carousel/index.d.ts +98 -0
- package/dist/carousel/index.js +243 -0
- package/dist/checkbox/index.d.ts +50 -0
- package/dist/checkbox/index.js +87 -0
- package/dist/combobox/index.d.ts +114 -0
- package/dist/combobox/index.js +431 -0
- package/dist/command-palette/index.d.ts +73 -0
- package/dist/command-palette/index.js +147 -0
- package/dist/context-menu/index.d.ts +111 -0
- package/dist/context-menu/index.js +372 -0
- package/dist/copy-button/index.d.ts +62 -0
- package/dist/copy-button/index.js +183 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +2 -0
- package/dist/core/selection.d.ts +5 -0
- package/dist/core/selection.js +39 -0
- package/dist/core/value-range.d.ts +49 -0
- package/dist/core/value-range.js +134 -0
- package/dist/date-picker/index.d.ts +210 -0
- package/dist/date-picker/index.js +895 -0
- package/dist/dialog/index.d.ts +95 -0
- package/dist/dialog/index.js +153 -0
- package/dist/disclosure/index.d.ts +52 -0
- package/dist/disclosure/index.js +159 -0
- package/dist/drawer/index.d.ts +30 -0
- package/dist/drawer/index.js +39 -0
- package/dist/feed/index.d.ts +77 -0
- package/dist/feed/index.js +260 -0
- package/dist/grid/index.d.ts +103 -0
- package/dist/grid/index.js +415 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +51 -0
- package/dist/input/index.d.ts +86 -0
- package/dist/input/index.js +156 -0
- package/dist/interactions/composite-navigation.d.ts +69 -0
- package/dist/interactions/composite-navigation.js +169 -0
- package/dist/interactions/index.d.ts +15 -0
- package/dist/interactions/index.js +4 -0
- package/dist/interactions/keyboard-intents.d.ts +16 -0
- package/dist/interactions/keyboard-intents.js +33 -0
- package/dist/interactions/overlay-focus.d.ts +40 -0
- package/dist/interactions/overlay-focus.js +93 -0
- package/dist/interactions/typeahead.d.ts +20 -0
- package/dist/interactions/typeahead.js +41 -0
- package/dist/landmarks/index.d.ts +39 -0
- package/dist/landmarks/index.js +58 -0
- package/dist/link/index.d.ts +34 -0
- package/dist/link/index.js +39 -0
- package/dist/listbox/index.d.ts +92 -0
- package/dist/listbox/index.js +337 -0
- package/dist/menu/index.d.ts +132 -0
- package/dist/menu/index.js +541 -0
- package/dist/menu-button/index.d.ts +71 -0
- package/dist/menu-button/index.js +121 -0
- package/dist/meter/index.d.ts +45 -0
- package/dist/meter/index.js +106 -0
- package/dist/number/index.d.ts +113 -0
- package/dist/number/index.js +252 -0
- package/dist/popover/index.d.ts +70 -0
- package/dist/popover/index.js +126 -0
- package/dist/progress/index.d.ts +49 -0
- package/dist/progress/index.js +79 -0
- package/dist/radio-group/index.d.ts +61 -0
- package/dist/radio-group/index.js +150 -0
- package/dist/select/index.d.ts +92 -0
- package/dist/select/index.js +239 -0
- package/dist/sidebar/index.d.ts +74 -0
- package/dist/sidebar/index.js +186 -0
- package/dist/slider/index.d.ts +61 -0
- package/dist/slider/index.js +150 -0
- package/dist/slider-multi-thumb/index.d.ts +70 -0
- package/dist/slider-multi-thumb/index.js +222 -0
- package/dist/spinbutton/index.d.ts +75 -0
- package/dist/spinbutton/index.js +214 -0
- package/dist/spinner/index.d.ts +1 -0
- package/dist/spinner/index.js +1 -0
- package/dist/spinner/spinner.d.ts +23 -0
- package/dist/spinner/spinner.js +25 -0
- package/dist/switch/index.d.ts +40 -0
- package/dist/switch/index.js +61 -0
- package/dist/table/index.d.ts +117 -0
- package/dist/table/index.js +377 -0
- package/dist/tabs/index.d.ts +63 -0
- package/dist/tabs/index.js +174 -0
- package/dist/textarea/index.d.ts +68 -0
- package/dist/textarea/index.js +137 -0
- package/dist/toast/index.d.ts +67 -0
- package/dist/toast/index.js +145 -0
- package/dist/toolbar/index.d.ts +59 -0
- package/dist/toolbar/index.js +139 -0
- package/dist/tooltip/index.d.ts +52 -0
- package/dist/tooltip/index.js +169 -0
- package/dist/treegrid/index.d.ts +101 -0
- package/dist/treegrid/index.js +463 -0
- package/dist/treeview/index.d.ts +68 -0
- package/dist/treeview/index.js +370 -0
- package/dist/window-splitter/index.d.ts +65 -0
- package/dist/window-splitter/index.js +204 -0
- package/package.json +92 -0
- package/specs/ADR-001-headless-architecture.md +461 -0
- package/specs/ADR-002-repo-release-model.md +108 -0
- package/specs/ADR-003-public-api-versioning.md +136 -0
- package/specs/ADR-004-focus-selection-policy.md +117 -0
- package/specs/IMPLEMENTATION-ROADMAP.md +237 -0
- package/specs/ISSUE-BACKLOG.md +681 -0
- package/specs/RELEASE-CANDIDATE.md +30 -0
- package/specs/components/accordion.md +130 -0
- package/specs/components/alert-dialog.md +72 -0
- package/specs/components/alert.md +65 -0
- package/specs/components/badge.md +220 -0
- package/specs/components/breadcrumb.md +74 -0
- package/specs/components/button.md +115 -0
- package/specs/components/callout.md +195 -0
- package/specs/components/card.md +280 -0
- package/specs/components/carousel.md +140 -0
- package/specs/components/checkbox.md +172 -0
- package/specs/components/combobox.md +423 -0
- package/specs/components/command-palette.md +92 -0
- package/specs/components/context-menu.md +556 -0
- package/specs/components/copy-button.md +293 -0
- package/specs/components/date-picker.md +400 -0
- package/specs/components/dialog.md +298 -0
- package/specs/components/disclosure.md +257 -0
- package/specs/components/drawer.md +353 -0
- package/specs/components/feed.md +265 -0
- package/specs/components/grid.md +186 -0
- package/specs/components/input.md +254 -0
- package/specs/components/landmarks.md +136 -0
- package/specs/components/link.md +134 -0
- package/specs/components/listbox.md +351 -0
- package/specs/components/menu-button.md +76 -0
- package/specs/components/menu.md +623 -0
- package/specs/components/meter.md +149 -0
- package/specs/components/number.md +393 -0
- package/specs/components/popover.md +252 -0
- package/specs/components/progress.md +188 -0
- package/specs/components/radio-group.md +151 -0
- package/specs/components/select.md +144 -0
- package/specs/components/sidebar.md +321 -0
- package/specs/components/slider-multi-thumb.md +78 -0
- package/specs/components/slider.md +84 -0
- package/specs/components/spinbutton.md +140 -0
- package/specs/components/spinner.md +132 -0
- package/specs/components/switch.md +175 -0
- package/specs/components/table.md +403 -0
- package/specs/components/tabs.md +265 -0
- package/specs/components/textarea.md +185 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +278 -0
- package/specs/components/tooltip.md +252 -0
- package/specs/components/treegrid.md +281 -0
- package/specs/components/treeview.md +91 -0
- package/specs/components/window-splitter.md +297 -0
- package/specs/ops/git-shard-sync.md +107 -0
- package/specs/ops/release-checklist.md +76 -0
- package/specs/release/GAP-TO-GREEN-ISSUES.md +88 -0
- package/specs/release/api-freeze-candidate.md +54 -0
- package/specs/release/changelog-automation.md +76 -0
- package/specs/release/changelog.generated.md +53 -0
- package/specs/release/changelog.patch.generated.md +46 -0
- package/specs/release/consumer-integration.md +53 -0
- package/specs/release/migration-notes-pre-v1.md +40 -0
- package/specs/release/mvp-changelog.md +57 -0
- package/specs/release/release-notes-template.md +61 -0
- package/specs/release/release-rehearsal.md +113 -0
- package/specs/release/semver-deprecation-dry-run.md +89 -0
- package/specs/release/shard-release-drill-report.md +50 -0
- package/specs/release/shard-release-follow-ups.md +31 -0
- package/specs/signals.md +208 -0
package/specs/signals.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Signals Integration Blueprint for Headless + Lit
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Make `@chromvoid/headless-ui` reactive out of the box for UI libraries (Lit included),
|
|
6
|
+
without requiring manual subscriptions or imperative sync glue.
|
|
7
|
+
|
|
8
|
+
The key idea is simple:
|
|
9
|
+
|
|
10
|
+
- headless models expose **signals as first-class state**
|
|
11
|
+
- UI layer reads signals in render
|
|
12
|
+
- updates propagate automatically
|
|
13
|
+
|
|
14
|
+
## Why this matters
|
|
15
|
+
|
|
16
|
+
If headless returns plain mutable objects, UI adapters must re-render manually.
|
|
17
|
+
That causes drift and duplicate wiring.
|
|
18
|
+
|
|
19
|
+
If headless returns signals/derived signals, UI becomes declarative:
|
|
20
|
+
|
|
21
|
+
- read state in template
|
|
22
|
+
- call actions in handlers
|
|
23
|
+
- done
|
|
24
|
+
|
|
25
|
+
## Product Principle (Pre-v1)
|
|
26
|
+
|
|
27
|
+
The package currently has no external production consumers.
|
|
28
|
+
|
|
29
|
+
Therefore, pre-v1 API design should optimize for **best consumer ergonomics**,
|
|
30
|
+
not backward compatibility with interim internal shapes.
|
|
31
|
+
|
|
32
|
+
Rule:
|
|
33
|
+
|
|
34
|
+
- API redesign is allowed when it produces a cleaner and more consistent consumer DX.
|
|
35
|
+
|
|
36
|
+
## Design Principles
|
|
37
|
+
|
|
38
|
+
1. **Signal-first state**
|
|
39
|
+
|
|
40
|
+
- expose atoms/computed values, not snapshots
|
|
41
|
+
- never require `requestUpdate`-style manual sync in consumers
|
|
42
|
+
|
|
43
|
+
2. **Intent-first actions**
|
|
44
|
+
|
|
45
|
+
- keyboard/pointer handlers call model actions
|
|
46
|
+
- model owns state transitions and APG semantics
|
|
47
|
+
|
|
48
|
+
3. **Contract-first props**
|
|
49
|
+
|
|
50
|
+
- `get*Props` returns ARIA/role/tabindex contracts from current signal state
|
|
51
|
+
- no visual decisions in headless contracts
|
|
52
|
+
|
|
53
|
+
4. **Adapter-thinness**
|
|
54
|
+
|
|
55
|
+
- Lit adapter maps state and handlers only
|
|
56
|
+
- no business logic in adapters
|
|
57
|
+
|
|
58
|
+
## Recommended Public Shape per Component
|
|
59
|
+
|
|
60
|
+
Each component model should follow this structure:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
type XModel = {
|
|
64
|
+
state: {
|
|
65
|
+
// atoms/computed signals
|
|
66
|
+
activeId: Atom<string | null>
|
|
67
|
+
selectedIds: Atom<string[]>
|
|
68
|
+
isOpen: Atom<boolean>
|
|
69
|
+
}
|
|
70
|
+
actions: {
|
|
71
|
+
moveNext(): void
|
|
72
|
+
movePrev(): void
|
|
73
|
+
select(id: string): void
|
|
74
|
+
handleKeyDown(event: KeyboardEvent): void
|
|
75
|
+
}
|
|
76
|
+
contracts: {
|
|
77
|
+
getRootProps(): RootProps
|
|
78
|
+
getItemProps(id: string): ItemProps
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Important:
|
|
84
|
+
|
|
85
|
+
- `state` must be signal references
|
|
86
|
+
- `contracts` must derive from signals
|
|
87
|
+
- `actions` must be the only mutation path
|
|
88
|
+
|
|
89
|
+
## Reatom Implementation Pattern
|
|
90
|
+
|
|
91
|
+
Use Reatom for headless model internals:
|
|
92
|
+
|
|
93
|
+
- `atom` for mutable state
|
|
94
|
+
- `computed` for derived state and props-ready values
|
|
95
|
+
- `action` for transitions
|
|
96
|
+
|
|
97
|
+
Example (simplified):
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import {atom, action, computed} from '@reatom/core'
|
|
101
|
+
|
|
102
|
+
export const createListbox = () => {
|
|
103
|
+
const activeId = atom<string | null>(null, 'listbox.activeId')
|
|
104
|
+
const selectedIds = atom<string[]>([], 'listbox.selectedIds')
|
|
105
|
+
|
|
106
|
+
const isSelectionEmpty = computed(() => selectedIds().length === 0, 'listbox.isSelectionEmpty')
|
|
107
|
+
|
|
108
|
+
const moveNext = action(() => {
|
|
109
|
+
// transition logic
|
|
110
|
+
}, 'listbox.moveNext')
|
|
111
|
+
|
|
112
|
+
const select = action((id: string) => {
|
|
113
|
+
selectedIds.set([id])
|
|
114
|
+
activeId.set(id)
|
|
115
|
+
}, 'listbox.select')
|
|
116
|
+
|
|
117
|
+
const getRootProps = () => ({
|
|
118
|
+
role: 'listbox' as const,
|
|
119
|
+
tabindex: '0' as const,
|
|
120
|
+
'aria-activedescendant': activeId() ?? undefined,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
state: {activeId, selectedIds, isSelectionEmpty},
|
|
125
|
+
actions: {moveNext, select},
|
|
126
|
+
contracts: {getRootProps},
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Lit Consumption Pattern
|
|
132
|
+
|
|
133
|
+
For Lit-based UI library, the adapter should read signals directly in `render`.
|
|
134
|
+
|
|
135
|
+
If you use a signal-aware base element (for example an XLit-style base),
|
|
136
|
+
this gives automatic dependency tracking.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
class ListboxView extends XLitElement {
|
|
140
|
+
model = createListbox()
|
|
141
|
+
|
|
142
|
+
render() {
|
|
143
|
+
const rootProps = this.model.contracts.getRootProps()
|
|
144
|
+
return html`
|
|
145
|
+
<div
|
|
146
|
+
role=${rootProps.role}
|
|
147
|
+
tabindex=${rootProps.tabindex}
|
|
148
|
+
aria-activedescendant=${rootProps['aria-activedescendant'] ?? nothing}
|
|
149
|
+
@keydown=${this.model.actions.handleKeyDown}
|
|
150
|
+
>
|
|
151
|
+
Active: ${this.model.state.activeId() ?? 'none'}
|
|
152
|
+
</div>
|
|
153
|
+
`
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Packaging Strategy
|
|
159
|
+
|
|
160
|
+
Use two-layer package strategy:
|
|
161
|
+
|
|
162
|
+
1. `@chromvoid/headless-ui`
|
|
163
|
+
|
|
164
|
+
- pure headless contracts and Reatom models
|
|
165
|
+
- no Lit dependency
|
|
166
|
+
|
|
167
|
+
2. `@chromvoid/ui` (optional UI kit package)
|
|
168
|
+
|
|
169
|
+
- tiny wrappers/helpers for Lit ergonomics
|
|
170
|
+
- no business logic
|
|
171
|
+
|
|
172
|
+
This keeps core portable and gives Lit users first-class DX.
|
|
173
|
+
|
|
174
|
+
## Migration Path from Current Signal Models
|
|
175
|
+
|
|
176
|
+
Current models already use Reatom signals internally.
|
|
177
|
+
Migration should focus on API and adapter ergonomics:
|
|
178
|
+
|
|
179
|
+
1. keep public `state` fields explicitly typed as signal references (`Atom`/`Computed`)
|
|
180
|
+
2. keep contract derivation inside model (`contracts.get*Props`) only
|
|
181
|
+
3. ensure adapter-level reactive rendering coverage stays green
|
|
182
|
+
4. publish migration notes for any pre-v1 contract reshaping
|
|
183
|
+
|
|
184
|
+
## Testing Requirements for Signal Integration
|
|
185
|
+
|
|
186
|
+
For each component, add tests for:
|
|
187
|
+
|
|
188
|
+
1. signal updates after actions
|
|
189
|
+
2. computed contract consistency
|
|
190
|
+
3. keyboard action -> signal transition mapping
|
|
191
|
+
4. adapter-level reactive rendering behavior (Lit integration tests)
|
|
192
|
+
|
|
193
|
+
## Anti-Patterns to Avoid
|
|
194
|
+
|
|
195
|
+
- returning only snapshot objects from `state`
|
|
196
|
+
- deriving ARIA props outside the model
|
|
197
|
+
- mixing UI logic into model actions
|
|
198
|
+
- exposing mutable arrays/objects without signal wrappers
|
|
199
|
+
- requiring manual re-render calls from component consumers
|
|
200
|
+
|
|
201
|
+
## Practical Next Step
|
|
202
|
+
|
|
203
|
+
Start with one component migration end-to-end:
|
|
204
|
+
|
|
205
|
+
1. redesign `listbox` to a signal-first consumer API
|
|
206
|
+
2. align naming and return shape for future components
|
|
207
|
+
3. add one Lit integration demo proving out-of-box reactivity
|
|
208
|
+
4. use this as template for `combobox`, `menu`, `tabs`, `treeview`
|