@adia-ai/a2ui-runtime 0.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.
- package/CHANGELOG.md +215 -0
- package/README.md +87 -0
- package/controllers/accordion.js +73 -0
- package/controllers/base.js +68 -0
- package/controllers/data-stream.js +281 -0
- package/controllers/form.js +81 -0
- package/controllers/index.js +6 -0
- package/controllers/selection.js +82 -0
- package/controllers/state-machine.js +135 -0
- package/controllers/toggle.js +40 -0
- package/dockables/action.js +152 -0
- package/dockables/base.js +30 -0
- package/dockables/controller.js +97 -0
- package/dockables/data-source.js +103 -0
- package/dockables/index.js +6 -0
- package/dockables/lifecycle.js +84 -0
- package/dockables/provider.js +59 -0
- package/index.js +45 -0
- package/package.json +31 -0
- package/registry.js +205 -0
- package/renderer.js +395 -0
- package/stream.js +243 -0
- package/surface-manifest.js +294 -0
- package/surface.js +222 -0
- package/wire-factory.js +134 -0
- package/wiring-engine.js +209 -0
- package/wiring-registry.js +342 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Changelog — @adia-ai/a2ui-runtime
|
|
2
|
+
|
|
3
|
+
Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
4
|
+
[Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
> **Renamed from `@adia-ai/a2ui-utils` in v0.3.0.** All releases
|
|
7
|
+
> through v0.2.5 published as `@adia-ai/a2ui-utils`. The package's own
|
|
8
|
+
> description has always called it the "A2UI runtime"; v0.3.0 aligns
|
|
9
|
+
> the package name with its purpose. See v0.3.0 entry for migration
|
|
10
|
+
> notes.
|
|
11
|
+
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
_No pending changes._
|
|
15
|
+
|
|
16
|
+
## [0.3.0] - 2026-05-05
|
|
17
|
+
|
|
18
|
+
**9-package lockstep cut + package rename + LLM extraction.** All 9 published `@adia-ai/*` packages bump 0.2.5 → 0.3.0 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges bump `^0.2.0` → `^0.3.0`.
|
|
19
|
+
|
|
20
|
+
This is a **minor cut on top of v0.2.5 with two BREAKING changes for npm consumers** (acceptable under pre-1.0 semver):
|
|
21
|
+
1. **Package renamed** — `@adia-ai/a2ui-utils` → `@adia-ai/a2ui-runtime` (this package).
|
|
22
|
+
2. New 9th lockstep package `@adia-ai/llm` extracted from `@adia-ai/a2ui-compose/llm` (sibling concern).
|
|
23
|
+
|
|
24
|
+
Lockstep policy expanded from 8 to 9 packages. The CI gate `lockstep-versioning` now enforces all 9 share one version.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Package name: `@adia-ai/a2ui-utils` → `@adia-ai/a2ui-runtime`.** Pure rename. No API change. Source path moved from `packages/a2ui/utils/` → `packages/a2ui/runtime/` to match the new name. The `name` field in package.json is the only consumer-visible change; all subpath exports (`./registry`, `./renderer`, `./streams`, `./surface`, `./wiring`, `./dockables`) preserved verbatim.
|
|
29
|
+
- `version`: `0.2.5` → `0.3.0`.
|
|
30
|
+
|
|
31
|
+
### Migration
|
|
32
|
+
|
|
33
|
+
Update `package.json`:
|
|
34
|
+
|
|
35
|
+
```diff
|
|
36
|
+
- "@adia-ai/a2ui-utils": "^0.2.0"
|
|
37
|
+
+ "@adia-ai/a2ui-runtime": "^0.3.0"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Update imports across the codebase:
|
|
41
|
+
|
|
42
|
+
```diff
|
|
43
|
+
- import { A2UIRenderer } from '@adia-ai/a2ui-utils/renderer';
|
|
44
|
+
+ import { A2UIRenderer } from '@adia-ai/a2ui-runtime/renderer';
|
|
45
|
+
|
|
46
|
+
- import { registry } from '@adia-ai/a2ui-utils/registry';
|
|
47
|
+
+ import { registry } from '@adia-ai/a2ui-runtime/registry';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `@adia-ai/a2ui-utils@0.2.5` and earlier remain on npm and continue to work; the rename only affects new installs and explicit upgrades. There is no deprecated stub on the old name — consumers who don't upgrade keep their existing `@adia-ai/a2ui-utils@0.2.5` installation.
|
|
51
|
+
|
|
52
|
+
### Why
|
|
53
|
+
|
|
54
|
+
"utils" undersold what this package is and collided visually with `web-modules/runtime/` (which holds `<a2ui-root>`, the surface element). The package's own description has always called it "A2UI runtime — renderer, registry, streams"; the name now matches.
|
|
55
|
+
|
|
56
|
+
## [0.2.5] - 2026-05-04
|
|
57
|
+
|
|
58
|
+
**8-package lockstep cut.** All 8 published `@adia-ai/*` packages bump 0.2.4 → 0.2.5 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.2.0` (covers 0.2.5 under semver — patch-cut asymmetry).
|
|
59
|
+
|
|
60
|
+
This is a **patch cut on top of v0.2.4, no BREAKING changes.** Substantive content lives in `web-components` (new `<fields-ui>` form-grid primitive + `draggable-list-item` last-item drop-zone fix, both surfaced by the Tasks UI Playground at `docs/projects/tasks-playground/`); `a2ui-corpus` ships a catalog regen for the new `<fields-ui>` yaml. `a2ui-utils` rides along at version bump only — no source change.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- `version`: `0.2.4` → `0.2.5`.
|
|
65
|
+
- Internal `@adia-ai/*` dep ranges: unchanged at `^0.2.0` (covers `0.2.5` under semver — patch-cut asymmetry).
|
|
66
|
+
|
|
67
|
+
## [0.2.4] - 2026-05-04
|
|
68
|
+
|
|
69
|
+
**8-package lockstep cut.** All 8 published `@adia-ai/*` packages bump 0.2.3 → 0.2.4 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.2.0` (covers 0.2.4 under semver — patch-cut asymmetry).
|
|
70
|
+
|
|
71
|
+
This is a **patch cut on top of v0.2.3, no BREAKING changes.** Substantive content lives in `web-components` (Tier 4 trait library lift, 11 new traits + 2 architectural rewrites, plus accumulated bug fixes); `a2ui-corpus` ships a catalog regen for the new `<demo-toggle-ui>` primitive. Six packages ride along at version + dep-range bump.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- `version`: `0.2.3` → `0.2.4`.
|
|
76
|
+
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.4`).
|
|
77
|
+
|
|
78
|
+
This package's source tree is byte-identical to 0.2.3. The cut bumps version + the internal dep range only, per the lockstep policy.
|
|
79
|
+
|
|
80
|
+
_Nothing yet._
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## [0.2.3] - 2026-05-04
|
|
87
|
+
|
|
88
|
+
**Lockstep cut.** All 8 published `@adia-ai/*` packages bump
|
|
89
|
+
0.2.2 → 0.2.3 per
|
|
90
|
+
[`docs/specs/package-architecture.md` § 15](../../../../docs/specs/package-architecture.md#15-versioning-policy).
|
|
91
|
+
Patch cut — no breaking changes.
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
|
|
95
|
+
- `version`: `0.2.2` → `0.2.3`.
|
|
96
|
+
|
|
97
|
+
### No source changes
|
|
98
|
+
|
|
99
|
+
`@adia-ai/a2ui-utils` source is byte-identical to `0.2.2`. The cut bumps version only.
|
|
100
|
+
|
|
101
|
+
## [0.2.2] — 2026-05-02
|
|
102
|
+
|
|
103
|
+
**Lockstep cut.** All 8 published `@adia-ai/*` packages bump 0.2.1 → 0.2.2 per [`docs/specs/package-architecture.md` § 15](../../../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
|
|
107
|
+
- `version`: `0.2.1` → `0.2.2`.
|
|
108
|
+
|
|
109
|
+
### No source changes
|
|
110
|
+
|
|
111
|
+
`a2ui-utils` source is byte-identical to `0.2.1`. The cut bumps version only.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## [0.2.1] — 2026-05-02
|
|
116
|
+
|
|
117
|
+
**Lockstep cut + A2UI registry mappings (`Field`/`Rating`/`ListItem`/`NavItem`/`NavGroup`).** All 8 published `@adia-ai/*` packages bump 0.2.0 → 0.2.1 per [`docs/specs/package-architecture.md` § 15](../../../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
|
|
118
|
+
|
|
119
|
+
### Changed
|
|
120
|
+
|
|
121
|
+
- `version`: `0.2.0` → `0.2.1`.
|
|
122
|
+
|
|
123
|
+
### Added — A2UI registry mappings (2026-05-02)
|
|
124
|
+
|
|
125
|
+
Five new A2UI type → AdiaUI tag mappings in `registry.js`:
|
|
126
|
+
|
|
127
|
+
- `Field` → `field-ui`
|
|
128
|
+
- `Rating` → `rating-ui`
|
|
129
|
+
- `ListItem` → `list-item-ui`
|
|
130
|
+
- `NavItem` → `nav-item-ui`
|
|
131
|
+
- `NavGroup` → `nav-group-ui`
|
|
132
|
+
|
|
133
|
+
All 5 components existed in `@adia-ai/web-components`; the registry hadn't been kept in sync. Closing this gap fixes 28/113 hand-authored patterns that previously rendered as `[unknown: X]` placeholders when imported into `/site/playground/a2ui-editor`, plus the render-preview's `streamed-list` example. The `NavItem` and `NavGroup` additions also align with [ADR-0015](../../../.brain/adrs/0015-three-tier-naming-convention.md)'s nav consolidation (the retired `SectionNav`/`SectionNavItem` types are migrated in `@adia-ai/a2ui-corpus`).
|
|
134
|
+
|
|
135
|
+
Smoke side-effect: zettel score 91 → 92; `smoke:engines` flipped from `valid=false score=98/91` to `valid=true score=100/92` because the unregistered types were tripping validation. Per-finding triage in [`docs/reports/playground-examples-review-2026-05-02.md`](../../../docs/reports/playground-examples-review-2026-05-02.md).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## [0.2.0] — 2026-05-02
|
|
140
|
+
|
|
141
|
+
**Lockstep cut.** All 8 published `@adia-ai/*` packages now share one
|
|
142
|
+
version, governed by [`docs/specs/package-architecture.md` § 15
|
|
143
|
+
(Versioning Policy)](../../../../docs/specs/package-architecture.md#15-versioning-policy).
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- `version`: `0.0.2` → `0.2.0`.
|
|
148
|
+
|
|
149
|
+
### No source changes
|
|
150
|
+
|
|
151
|
+
A2UI runtime source (renderer, registry, streams, surface manifest,
|
|
152
|
+
wiring primitives, controllers, dockables) is byte-identical to 0.0.2.
|
|
153
|
+
The cut bumps the version only — utils has no internal `@adia-ai/*`
|
|
154
|
+
dependencies. Five sibling packages depend on this package via
|
|
155
|
+
`^0.0.2` (now `^0.2.0`), which under npm pre-1.0 semver locks them to
|
|
156
|
+
the exact version; the lockstep cut switches them to a working range.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## [0.0.2] — 2026-04-22
|
|
161
|
+
|
|
162
|
+
Controllers-move fix. `0.0.1` published but was broken for any
|
|
163
|
+
surface that declared a controller: `wiring-registry.js` held lazy
|
|
164
|
+
`await import('../controllers/*.js')` calls that resolved to
|
|
165
|
+
`packages/a2ui/controllers/` (nonexistent) because the controllers
|
|
166
|
+
subdir still lived under `packages/web-components/` at the time of
|
|
167
|
+
the initial split. `0.0.2` moves `controllers/` into this package
|
|
168
|
+
alongside the wiring code that uses them and retargets the dynamic
|
|
169
|
+
imports to `./controllers/`. **Consumers on `0.0.1` should upgrade
|
|
170
|
+
to `0.0.2`; `0.0.1` is broken for any surface that declares a
|
|
171
|
+
controller.**
|
|
172
|
+
|
|
173
|
+
### Included
|
|
174
|
+
|
|
175
|
+
- **Controllers** — `controllers/` subdir with `FormController`,
|
|
176
|
+
`DataStreamController`, `SelectionController`, `ToggleController`,
|
|
177
|
+
`AccordionController`, `StateMachineController`, plus the shared
|
|
178
|
+
`BaseController`. Lazy-loaded via dynamic imports from
|
|
179
|
+
`wiringRegistry`. Pure JS — no web-component dependency; only
|
|
180
|
+
extend `BaseController` (host management + subscription + notify).
|
|
181
|
+
Moved in from `packages/web-components/controllers/` after the
|
|
182
|
+
initial carve-out surfaced that `wiring-registry.js` lazy-imports
|
|
183
|
+
these at runtime and the imports couldn't resolve — the six types
|
|
184
|
+
are registered in `wiringRegistry` for lazy resolution so a
|
|
185
|
+
surface only loads what it declares.
|
|
186
|
+
- **`A2UIRenderer`** — processes A2UI protocol messages (`createSurface`,
|
|
187
|
+
`updateComponents`, `wireComponents`, `updateDataModel`, `destroySurface`)
|
|
188
|
+
and writes custom elements into a container. Batched via
|
|
189
|
+
`requestAnimationFrame`.
|
|
190
|
+
- **Registry** — `registry`, `resolveTag`, `registerType`. A2UI protocol
|
|
191
|
+
component name → custom-element tag map, extensible at runtime.
|
|
192
|
+
- **Transports** — `sseStream`, `wsStream`, `mockStream`, `mcpStream`,
|
|
193
|
+
`jsonlStream`. Normalize inbound A2UI messages from the respective
|
|
194
|
+
transports into a consistent async iterable.
|
|
195
|
+
- **`SurfaceManifest`** — design-time manifest model for multi-surface
|
|
196
|
+
apps.
|
|
197
|
+
- **`Surface`** — runtime surface lifecycle primitive.
|
|
198
|
+
- **Wiring layer** — `WiringEngine`, `wiringRegistry`, `createDockables`,
|
|
199
|
+
and the dockable base classes (`Dockable`, `ControllerDock`,
|
|
200
|
+
`DataSourceDock`, `ActionDock`, `ProviderDock`, `LifecycleDock`).
|
|
201
|
+
|
|
202
|
+
### Notes on the carve-out
|
|
203
|
+
|
|
204
|
+
- Dead exports from the old `web-components/a2ui/index.js`
|
|
205
|
+
(`ContextStore`, `ManifestRuntime`, `A2UIValidator`, `A2UIGenerator`,
|
|
206
|
+
`savePattern`, `importPattern`, `buildPatternJSON`, `retrieval/*`,
|
|
207
|
+
`feedbackStore`) were not carried forward — the referenced source
|
|
208
|
+
files did not exist in this repo. The new `index.js` exports only
|
|
209
|
+
live symbols.
|
|
210
|
+
- `manifest-runtime.js` was deleted during the move — it referenced
|
|
211
|
+
`./context-store.js` which had never been authored. Will return if
|
|
212
|
+
and when a context-store surface is designed.
|
|
213
|
+
- The `ActionDock` constant that mapped A2UI event names to DOM event
|
|
214
|
+
names was renamed to `A2UI_EVENT_TO_DOM` for clarity. Its shape is
|
|
215
|
+
unchanged.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @adia-ai/a2ui-runtime
|
|
2
|
+
|
|
3
|
+
A2UI runtime — renderer, registry, streams, surface manifest, and wiring
|
|
4
|
+
primitives for the A2UI (Agent-to-UI) protocol. Framework-agnostic;
|
|
5
|
+
pairs with any A2UI-conformant component set.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @adia-ai/a2ui-runtime
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Typically paired with `@adia-ai/web-components` (which provides the
|
|
14
|
+
custom-element catalog the renderer resolves against):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @adia-ai/a2ui-runtime @adia-ai/web-components
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## What's in the box
|
|
21
|
+
|
|
22
|
+
- **`A2UIRenderer`** — processes A2UI protocol messages and writes
|
|
23
|
+
custom elements to a container.
|
|
24
|
+
- **`registry`** / **`resolveTag`** / **`registerType`** — A2UI protocol
|
|
25
|
+
component name → custom-element tag map. Extensible via
|
|
26
|
+
`registerType`.
|
|
27
|
+
- **Transports** — `sseStream`, `wsStream`, `mockStream`, `mcpStream`,
|
|
28
|
+
`jsonlStream`. Normalize inbound A2UI messages from SSE, WebSocket,
|
|
29
|
+
mock fixtures, MCP tool calls, or JSONL logs.
|
|
30
|
+
- **`SurfaceManifest`** / **`Surface`** — design-time surface shape +
|
|
31
|
+
runtime lifecycle for cross-surface data flow.
|
|
32
|
+
- **Wiring primitives** — `WiringEngine`, `wiringRegistry`,
|
|
33
|
+
`createDockables`, plus the dockable base classes
|
|
34
|
+
(`ControllerDock`, `DataSourceDock`, `ActionDock`, `ProviderDock`,
|
|
35
|
+
`LifecycleDock`).
|
|
36
|
+
- **Controllers** — lazy-loaded runtime state managers at
|
|
37
|
+
`controllers/`: `FormController`, `DataStreamController`,
|
|
38
|
+
`SelectionController`, `ToggleController`, `AccordionController`,
|
|
39
|
+
`StateMachineController`, plus `BaseController`. Registered in the
|
|
40
|
+
wiring registry for lazy resolution; surface only imports what it
|
|
41
|
+
declares.
|
|
42
|
+
|
|
43
|
+
## Minimal usage
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { A2UIRenderer, jsonlStream } from '@adia-ai/a2ui-runtime';
|
|
47
|
+
import '@adia-ai/web-components'; // register the custom elements
|
|
48
|
+
|
|
49
|
+
const container = document.querySelector('#app');
|
|
50
|
+
const renderer = new A2UIRenderer(container);
|
|
51
|
+
|
|
52
|
+
for await (const msg of jsonlStream(fetch('/api/ui-trace.jsonl').then(r => r.body))) {
|
|
53
|
+
renderer.process(msg);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For declarative embedding in HTML, use the `<a2ui-root>` element from
|
|
58
|
+
`@adia-ai/web-modules/runtime` — it wraps `A2UIRenderer` + stream
|
|
59
|
+
wiring in a custom element. (Was `@adia-ai/web-components/patterns/`
|
|
60
|
+
prior to the package split per ADR-0012.)
|
|
61
|
+
|
|
62
|
+
## Public entry points
|
|
63
|
+
|
|
64
|
+
The default export bundles the common surface. Granular subpath
|
|
65
|
+
imports are available if you want to tree-shake:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { A2UIRenderer } from '@adia-ai/a2ui-runtime/renderer';
|
|
69
|
+
import { resolveTag } from '@adia-ai/a2ui-runtime/registry';
|
|
70
|
+
import { sseStream } from '@adia-ai/a2ui-runtime/streams';
|
|
71
|
+
import { SurfaceManifest } from '@adia-ai/a2ui-runtime/surface';
|
|
72
|
+
import { WiringEngine } from '@adia-ai/a2ui-runtime/wiring';
|
|
73
|
+
import { ActionDock } from '@adia-ai/a2ui-runtime/dockables';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Relationship to other packages
|
|
77
|
+
|
|
78
|
+
- **`@adia-ai/web-components`** — the custom-element catalog the
|
|
79
|
+
renderer resolves tags against. Depends on this package at runtime
|
|
80
|
+
(as of web-components v0.0.4).
|
|
81
|
+
- **`@adia-ai/web-modules/runtime/a2ui-root`** — the declarative
|
|
82
|
+
`<a2ui-root>` custom element; wraps this package's renderer + stream.
|
|
83
|
+
Extracted from `@adia-ai/web-components/patterns/` per ADR-0012.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
(See repository root.)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BaseController } from './base.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Accordion controller — manages expandable section state.
|
|
5
|
+
* Sets [data-accordion-expanded] on expanded sections.
|
|
6
|
+
*/
|
|
7
|
+
export class AccordionController extends BaseController {
|
|
8
|
+
static schema = Object.freeze({
|
|
9
|
+
name: 'accordion',
|
|
10
|
+
state: { expanded: 'Set', multiple: 'boolean' },
|
|
11
|
+
commands: ['toggle', 'expand', 'collapse', 'collapseAll', 'expandAll'],
|
|
12
|
+
attributes: ['data-accordion-expanded'],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
#expanded = new Set();
|
|
16
|
+
#multiple = false;
|
|
17
|
+
|
|
18
|
+
constructor({ multiple = false, initial = [] } = {}) {
|
|
19
|
+
super();
|
|
20
|
+
this.#multiple = multiple;
|
|
21
|
+
for (const key of initial) this.#expanded.add(String(key));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onDisconnect(host) {
|
|
25
|
+
const sections = host.querySelectorAll('[data-accordion-expanded]');
|
|
26
|
+
for (const s of sections) s.removeAttribute('data-accordion-expanded');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getState() {
|
|
30
|
+
return { expanded: new Set(this.#expanded), multiple: this.#multiple };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
reflect() {
|
|
34
|
+
if (!this.host) return;
|
|
35
|
+
const sections = this.host.querySelectorAll('[data-accordion-key]');
|
|
36
|
+
for (const section of sections) {
|
|
37
|
+
const key = section.getAttribute('data-accordion-key');
|
|
38
|
+
if (this.#expanded.has(key)) section.setAttribute('data-accordion-expanded', '');
|
|
39
|
+
else section.removeAttribute('data-accordion-expanded');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
commands = {
|
|
44
|
+
toggle: (key) => {
|
|
45
|
+
key = String(key);
|
|
46
|
+
if (this.#expanded.has(key)) this.#expanded.delete(key);
|
|
47
|
+
else {
|
|
48
|
+
if (!this.#multiple) this.#expanded.clear();
|
|
49
|
+
this.#expanded.add(key);
|
|
50
|
+
}
|
|
51
|
+
this.notify();
|
|
52
|
+
},
|
|
53
|
+
expand: (key) => {
|
|
54
|
+
key = String(key);
|
|
55
|
+
if (!this.#multiple) this.#expanded.clear();
|
|
56
|
+
this.#expanded.add(key);
|
|
57
|
+
this.notify();
|
|
58
|
+
},
|
|
59
|
+
collapse: (key) => {
|
|
60
|
+
this.#expanded.delete(String(key));
|
|
61
|
+
this.notify();
|
|
62
|
+
},
|
|
63
|
+
collapseAll: () => {
|
|
64
|
+
this.#expanded.clear();
|
|
65
|
+
this.notify();
|
|
66
|
+
},
|
|
67
|
+
expandAll: (keys) => {
|
|
68
|
+
if (!this.#multiple) return;
|
|
69
|
+
for (const k of keys) this.#expanded.add(String(k));
|
|
70
|
+
this.notify();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseController — base class for all controllers.
|
|
3
|
+
*
|
|
4
|
+
* Provides: host management, subscription, notification, reflection.
|
|
5
|
+
* Subclass implements: onConnect, onDisconnect, getState, reflect, commands.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* class ToggleController extends BaseController {
|
|
9
|
+
* static schema = { name: 'toggle', state: { on: 'boolean' }, commands: ['toggle', 'set'], attributes: ['data-toggle-on'] };
|
|
10
|
+
* #on = false;
|
|
11
|
+
* getState() { return { on: this.#on }; }
|
|
12
|
+
* reflect() { ... }
|
|
13
|
+
* commands = { toggle: () => { ... this.notify(); } };
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const validated = new WeakSet();
|
|
18
|
+
|
|
19
|
+
export class BaseController {
|
|
20
|
+
#host = null;
|
|
21
|
+
#subs = new Set();
|
|
22
|
+
|
|
23
|
+
get host() { return this.#host; }
|
|
24
|
+
|
|
25
|
+
connect(host) {
|
|
26
|
+
const ctor = this.constructor;
|
|
27
|
+
if (!validated.has(ctor) && ctor !== BaseController) {
|
|
28
|
+
validated.add(ctor);
|
|
29
|
+
if (!ctor.schema) {
|
|
30
|
+
console.warn(`AdiaUI: ${ctor.name} extends BaseController without static schema`);
|
|
31
|
+
}
|
|
32
|
+
if (this.getState === BaseController.prototype.getState) {
|
|
33
|
+
console.error(`AdiaUI: ${ctor.schema?.name ?? ctor.name} must implement getState()`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (this.#host && this.#host !== host) {
|
|
37
|
+
console.warn(`AdiaUI: ${ctor.schema?.name ?? ctor.name} already connected to a different host`);
|
|
38
|
+
}
|
|
39
|
+
this.#host = host;
|
|
40
|
+
this.onConnect(host);
|
|
41
|
+
this.reflect();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
disconnect(host) {
|
|
45
|
+
const h = host ?? this.#host;
|
|
46
|
+
if (!h) return;
|
|
47
|
+
this.onDisconnect(h);
|
|
48
|
+
this.#host = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
subscribe(fn) {
|
|
52
|
+
this.#subs.add(fn);
|
|
53
|
+
return () => this.#subs.delete(fn);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
notify() {
|
|
57
|
+
this.reflect();
|
|
58
|
+
for (const fn of this.#subs) fn();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onConnect(host) {}
|
|
62
|
+
onDisconnect(host) {}
|
|
63
|
+
reflect() {}
|
|
64
|
+
|
|
65
|
+
getState() {
|
|
66
|
+
throw new Error(`${this.constructor.schema?.name ?? this.constructor.name}: getState() not implemented`);
|
|
67
|
+
}
|
|
68
|
+
}
|