@adia-ai/web-components 0.0.1 → 0.0.3
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/a2ui/index.js +23 -17
- package/components/accordion/accordion.js +1 -1
- package/components/action-list/action-list.js +1 -1
- package/components/agent-artifact/agent-artifact.js +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
- package/components/agent-questions/agent-questions.js +1 -1
- package/components/agent-reasoning/agent-reasoning.a2ui.json +14 -8
- package/components/agent-reasoning/agent-reasoning.css +1 -1
- package/components/agent-reasoning/agent-reasoning.js +9 -9
- package/components/agent-reasoning/agent-reasoning.yaml +9 -6
- package/components/agent-suggestions/agent-suggestions.js +1 -1
- package/components/agent-trace/agent-trace.js +1 -1
- package/components/alert/alert.js +1 -1
- package/components/avatar/avatar.a2ui.json +0 -10
- package/components/avatar/avatar.css +1 -1
- package/components/avatar/avatar.js +1 -1
- package/components/avatar/avatar.yaml +0 -8
- package/components/badge/badge.a2ui.json +6 -6
- package/components/badge/badge.js +22 -1
- package/components/badge/badge.yaml +8 -5
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.a2ui.json +0 -11
- package/components/button/button.css +1 -1
- package/components/button/button.js +3 -3
- package/components/button/button.yaml +0 -11
- package/components/calendar-picker/calendar-picker.js +25 -19
- package/components/canvas/canvas.js +3 -3
- package/components/card/card.a2ui.json +0 -21
- package/components/card/card.js +2 -2
- package/components/card/card.yaml +0 -14
- package/components/chart/chart.a2ui.json +9 -8
- package/components/chart/chart.js +1 -1
- package/components/chart/chart.yaml +7 -5
- package/components/chat/chat-input.js +1 -1
- package/components/chat/chat.js +2 -2
- package/components/check/check.css +1 -1
- package/components/check/check.js +2 -2
- package/components/code/code.a2ui.json +0 -5
- package/components/code/code.js +1 -1
- package/components/code/code.yaml +0 -4
- package/components/col/col.a2ui.json +0 -20
- package/components/col/col.js +1 -1
- package/components/col/col.yaml +0 -20
- package/components/color-picker/color-picker.a2ui.json +1 -1
- package/components/color-picker/color-picker.js +5 -2
- package/components/color-picker/color-picker.yaml +1 -1
- package/components/command/command.js +22 -11
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.a2ui.json +0 -19
- package/components/divider/divider.js +1 -1
- package/components/divider/divider.yaml +0 -12
- package/components/drawer/drawer.a2ui.json +2 -2
- package/components/drawer/drawer.css +1 -1
- package/components/drawer/drawer.js +2 -2
- package/components/drawer/drawer.yaml +3 -3
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.a2ui.json +0 -13
- package/components/empty-state/empty-state.js +1 -1
- package/components/empty-state/empty-state.yaml +0 -5
- package/components/footer/footer.a2ui.json +1 -1
- package/components/footer/footer.yaml +1 -1
- package/components/grid/grid.a2ui.json +0 -5
- package/components/grid/grid.js +1 -1
- package/components/grid/grid.yaml +0 -4
- package/components/header/header.a2ui.json +1 -1
- package/components/header/header.yaml +1 -1
- package/components/heatmap/heatmap.a2ui.json +4 -4
- package/components/heatmap/heatmap.js +4 -4
- package/components/heatmap/heatmap.yaml +4 -4
- package/components/icon/icon.a2ui.json +13 -0
- package/components/icon/icon.js +2 -2
- package/components/icon/icon.yaml +6 -0
- package/components/image/image.a2ui.json +10 -21
- package/components/image/image.js +1 -1
- package/components/image/image.yaml +8 -16
- package/components/input/input.a2ui.json +0 -15
- package/components/input/input.js +2 -2
- package/components/input/input.yaml +0 -12
- package/components/inspector/inspector.a2ui.json +0 -5
- package/components/inspector/inspector.js +2 -2
- package/components/inspector/inspector.yaml +0 -4
- package/components/kbd/kbd.js +1 -1
- package/components/list/list.a2ui.json +0 -33
- package/components/list/list.js +1 -1
- package/components/list/list.yaml +0 -27
- package/components/menu/menu.a2ui.json +0 -10
- package/components/menu/menu.js +9 -3
- package/components/menu/menu.yaml +0 -8
- package/components/modal/modal.a2ui.json +1 -25
- package/components/modal/modal.js +1 -1
- package/components/modal/modal.yaml +1 -22
- package/components/noodles/noodles.a2ui.json +0 -5
- package/components/noodles/noodles.js +1 -1
- package/components/noodles/noodles.yaml +0 -5
- package/components/otp-input/otp-input.a2ui.json +0 -5
- package/components/otp-input/otp-input.js +1 -1
- package/components/otp-input/otp-input.yaml +0 -4
- package/components/pagination/pagination.a2ui.json +3 -13
- package/components/pagination/pagination.css +1 -1
- package/components/pagination/pagination.js +1 -1
- package/components/pagination/pagination.yaml +5 -14
- package/components/pane/pane.a2ui.json +0 -10
- package/components/pane/pane.js +1 -1
- package/components/pane/pane.yaml +0 -9
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.a2ui.json +0 -14
- package/components/popover/popover.js +9 -3
- package/components/popover/popover.yaml +0 -11
- package/components/progress/progress.a2ui.json +2 -12
- package/components/progress/progress.js +1 -1
- package/components/progress/progress.yaml +2 -11
- package/components/progress-row/progress-row.a2ui.json +3 -13
- package/components/progress-row/progress-row.js +1 -1
- package/components/progress-row/progress-row.yaml +3 -11
- package/components/radio/radio.css +1 -1
- package/components/radio/radio.js +2 -2
- package/components/range/range.js +1 -1
- package/components/rating/rating.js +1 -1
- package/components/richtext/richtext.js +2 -2
- package/components/row/row.a2ui.json +0 -5
- package/components/row/row.js +2 -2
- package/components/row/row.yaml +0 -4
- package/components/search/search.a2ui.json +0 -5
- package/components/search/search.js +1 -1
- package/components/search/search.yaml +0 -4
- package/components/section/section.a2ui.json +1 -1
- package/components/section/section.yaml +1 -1
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.css +1 -1
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.a2ui.json +5 -0
- package/components/select/select.js +13 -3
- package/components/select/select.yaml +5 -0
- package/components/skeleton/skeleton.a2ui.json +0 -11
- package/components/skeleton/skeleton.js +1 -1
- package/components/skeleton/skeleton.yaml +0 -5
- package/components/slider/slider.a2ui.json +0 -5
- package/components/slider/slider.js +1 -1
- package/components/slider/slider.yaml +0 -5
- package/components/stack/stack.js +1 -1
- package/components/stat/stat.js +1 -1
- package/components/stepper/stepper.js +1 -1
- package/components/stream/stream.js +1 -1
- package/components/swiper/swiper.a2ui.json +7 -7
- package/components/swiper/swiper.js +2 -2
- package/components/swiper/swiper.yaml +8 -6
- package/components/switch/switch.css +1 -1
- package/components/switch/switch.js +2 -2
- package/components/table/table.js +1 -1
- package/components/tabs/tab.js +1 -1
- package/components/tabs/tabs.js +1 -1
- package/components/tag/tag.a2ui.json +0 -5
- package/components/tag/tag.js +1 -1
- package/components/tag/tag.yaml +0 -4
- package/components/text/text.a2ui.json +5 -0
- package/components/text/text.css +2 -2
- package/components/text/text.js +1 -1
- package/components/text/text.yaml +5 -0
- package/components/textarea/textarea.js +1 -1
- package/components/timeline/timeline.a2ui.json +8 -13
- package/components/timeline/timeline.js +1 -1
- package/components/timeline/timeline.yaml +6 -11
- package/components/toast/toast.a2ui.json +0 -15
- package/components/toast/toast.js +1 -1
- package/components/toast/toast.yaml +0 -13
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toolbar/toolbar.a2ui.json +0 -23
- package/components/toolbar/toolbar.js +13 -3
- package/components/toolbar/toolbar.yaml +0 -19
- package/components/tooltip/tooltip.a2ui.json +0 -10
- package/components/tooltip/tooltip.js +2 -2
- package/components/tooltip/tooltip.yaml +0 -8
- package/components/tree/tree.js +1 -1
- package/components/upload/upload.js +1 -1
- package/core/markdown.js +1 -1
- package/core/provider.js +2 -2
- package/package.json +7 -3
- package/patterns/a2ui-root/a2ui-root.a2ui.json +118 -0
- package/{a2ui/root.js → patterns/a2ui-root/a2ui-root.js} +9 -4
- package/patterns/a2ui-root/a2ui-root.yaml +76 -0
- package/patterns/adia-chat/adia-chat.js +3 -3
- package/patterns/adia-chat/css/adia-chat.streaming.css +2 -2
- package/patterns/adia-editor/adia-editor.js +1 -1
- package/patterns/app-nav/app-nav.js +1 -1
- package/patterns/app-nav-group/app-nav-group.js +2 -2
- package/patterns/app-nav-item/app-nav-item.js +1 -1
- package/patterns/app-shell/app-shell.js +1 -1
- package/patterns/gen-ui/gen-ui.css +3 -3
- package/patterns/gen-ui/gen-ui.js +1 -1
- package/patterns/index.js +1 -0
- package/patterns/section-nav/section-nav.js +1 -1
- package/patterns/section-nav-group/section-nav-group.js +1 -1
- package/patterns/section-nav-item/section-nav-item.js +1 -1
- package/traits/define.js +1 -1
- package/a2ui/dockables/action.js +0 -152
- package/a2ui/dockables/base.js +0 -30
- package/a2ui/dockables/controller.js +0 -97
- package/a2ui/dockables/data-source.js +0 -103
- package/a2ui/dockables/index.js +0 -6
- package/a2ui/dockables/lifecycle.js +0 -84
- package/a2ui/dockables/provider.js +0 -59
- package/a2ui/manifest-runtime.js +0 -226
- package/a2ui/registry.js +0 -200
- package/a2ui/renderer.js +0 -361
- package/a2ui/stream.js +0 -243
- package/a2ui/surface-manifest.js +0 -294
- package/a2ui/surface.js +0 -222
- package/a2ui/wire-factory.js +0 -134
- package/a2ui/wiring-engine.js +0 -209
- package/a2ui/wiring-registry.js +0 -342
package/a2ui/wiring-engine.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wiring Engine — Processes wireComponents messages via the Surface dock system.
|
|
3
|
-
*
|
|
4
|
-
* This is the bridge between the renderer (which receives raw messages)
|
|
5
|
-
* and the Surface (which manages dockable objects). It:
|
|
6
|
-
*
|
|
7
|
-
* 1. Creates/retrieves a Surface for the surfaceId
|
|
8
|
-
* 2. Resolves params from the message
|
|
9
|
-
* 3. Translates the message sections into typed Dockables via wire-factory
|
|
10
|
-
* 4. Docks them in dependency order on the Surface
|
|
11
|
-
*
|
|
12
|
-
* The external API is unchanged: process(msg), teardown(surfaceId), teardownAll().
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { Surface } from './surface.js';
|
|
16
|
-
import { createDockables } from './wire-factory.js';
|
|
17
|
-
|
|
18
|
-
export class WiringEngine {
|
|
19
|
-
/** @type {Map<string, Surface>} */
|
|
20
|
-
#surfaces = new Map();
|
|
21
|
-
|
|
22
|
-
/** @type {(surfaceId: string, path: string, data: unknown) => void} */
|
|
23
|
-
#updateDataModel;
|
|
24
|
-
|
|
25
|
-
/** @type {(surfaceId: string, componentId: string) => HTMLElement | null} */
|
|
26
|
-
#getElement;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {object} opts
|
|
30
|
-
* @param {(surfaceId: string, path: string, data: unknown) => void} opts.updateDataModel
|
|
31
|
-
* @param {(surfaceId: string, componentId: string) => HTMLElement | null} opts.getElement
|
|
32
|
-
*/
|
|
33
|
-
constructor({ updateDataModel, getElement }) {
|
|
34
|
-
this.#updateDataModel = updateDataModel;
|
|
35
|
-
this.#getElement = getElement;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Process a wireComponents message.
|
|
40
|
-
* Multiple calls for the same surfaceId are additive (new dockables added,
|
|
41
|
-
* same-id dockables hot-swapped).
|
|
42
|
-
*
|
|
43
|
-
* @param {object} message
|
|
44
|
-
*/
|
|
45
|
-
async process(message) {
|
|
46
|
-
const { surfaceId } = message;
|
|
47
|
-
if (!surfaceId) return;
|
|
48
|
-
|
|
49
|
-
let surface = this.#surfaces.get(surfaceId);
|
|
50
|
-
if (!surface) {
|
|
51
|
-
surface = this.#createSurface(surfaceId);
|
|
52
|
-
this.#surfaces.set(surfaceId, surface);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
// Resolve params (before creating dockables, they may need params)
|
|
57
|
-
if (message.data?.params) {
|
|
58
|
-
surface.setParams(this.#resolveParams(message.data.params));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Translate message → dockables
|
|
62
|
-
const { dockables, initialModel } = createDockables(message);
|
|
63
|
-
|
|
64
|
-
// Set initial model state before docking
|
|
65
|
-
if (initialModel) {
|
|
66
|
-
surface.setInitialModel(initialModel);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Handle explicit undocking
|
|
70
|
-
if (message.undock) {
|
|
71
|
-
surface.undockMany(message.undock);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Dock all in dependency order
|
|
75
|
-
// Some dockables have async dock() (controllers), so we dock sequentially
|
|
76
|
-
// by kind to maintain order guarantees
|
|
77
|
-
for (const dockable of dockables) {
|
|
78
|
-
await surface.dock(dockable);
|
|
79
|
-
}
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.warn(`Wiring: error processing wireComponents for "${surfaceId}":`, err.message);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Tear down all wiring for a surface.
|
|
87
|
-
* @param {string} surfaceId
|
|
88
|
-
*/
|
|
89
|
-
teardown(surfaceId) {
|
|
90
|
-
const surface = this.#surfaces.get(surfaceId);
|
|
91
|
-
if (!surface) return;
|
|
92
|
-
surface.undockAll();
|
|
93
|
-
this.#surfaces.delete(surfaceId);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Tear down everything.
|
|
98
|
-
*/
|
|
99
|
-
teardownAll() {
|
|
100
|
-
for (const surfaceId of this.#surfaces.keys()) this.teardown(surfaceId);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get a surface for inspection/testing.
|
|
105
|
-
* @param {string} surfaceId
|
|
106
|
-
* @returns {Surface|null}
|
|
107
|
-
*/
|
|
108
|
-
getSurface(surfaceId) {
|
|
109
|
-
return this.#surfaces.get(surfaceId) || null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Get a wired surface's state (for debugging/inspection).
|
|
114
|
-
* @param {string} surfaceId
|
|
115
|
-
*/
|
|
116
|
-
getSurfaceState(surfaceId) {
|
|
117
|
-
const surface = this.#surfaces.get(surfaceId);
|
|
118
|
-
if (!surface) return null;
|
|
119
|
-
const dockables = surface.context.listDockables();
|
|
120
|
-
return {
|
|
121
|
-
surfaceId,
|
|
122
|
-
controllerCount: dockables.filter(d => d.kind === 'controller').length,
|
|
123
|
-
sourceCount: dockables.filter(d => d.kind === 'source').length,
|
|
124
|
-
actionCount: dockables.filter(d => d.kind === 'action').length,
|
|
125
|
-
providerCount: dockables.filter(d => d.kind === 'provider').length,
|
|
126
|
-
hasLifecycle: dockables.some(d => d.kind === 'lifecycle'),
|
|
127
|
-
totalDocked: dockables.length,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Private ────────────────────────────────────────────────
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Create a Surface wired to the renderer's element map.
|
|
135
|
-
*/
|
|
136
|
-
#createSurface(surfaceId) {
|
|
137
|
-
// Create an element proxy that delegates to the renderer's getElement
|
|
138
|
-
const elementsProxy = {
|
|
139
|
-
get: (componentId) => this.#getElement(surfaceId, componentId),
|
|
140
|
-
has: (componentId) => !!this.#getElement(surfaceId, componentId),
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// The root element is the surface container in the renderer
|
|
144
|
-
const rootElement = this.#getElement(surfaceId, 'root') || document.createElement('div');
|
|
145
|
-
|
|
146
|
-
// Build a Map-like wrapper so Surface can use elements.get()
|
|
147
|
-
const elements = new Proxy(new Map(), {
|
|
148
|
-
get(target, prop) {
|
|
149
|
-
if (prop === 'get') return (id) => elementsProxy.get(id);
|
|
150
|
-
if (prop === 'has') return (id) => elementsProxy.has(id);
|
|
151
|
-
return target[prop];
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Wrap updateDataModel so dockables that call ctx.setModel
|
|
156
|
-
// also flow through to the renderer's data binding system
|
|
157
|
-
const surface = new Surface(surfaceId, rootElement, elements);
|
|
158
|
-
|
|
159
|
-
// Monkey-patch setModel on context to also notify the renderer
|
|
160
|
-
const originalContext = surface.context;
|
|
161
|
-
const origSetModel = originalContext.setModel;
|
|
162
|
-
const updateDataModel = this.#updateDataModel;
|
|
163
|
-
Object.defineProperty(surface, 'context', {
|
|
164
|
-
get() {
|
|
165
|
-
const ctx = originalContext;
|
|
166
|
-
return {
|
|
167
|
-
...ctx,
|
|
168
|
-
setModel(path, value) {
|
|
169
|
-
origSetModel(path, value);
|
|
170
|
-
updateDataModel(surfaceId, path, value);
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
return surface;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Resolve parameter declarations to values.
|
|
181
|
-
* @param {Record<string, { from: string, key: string }>} params
|
|
182
|
-
* @returns {Record<string, string>}
|
|
183
|
-
*/
|
|
184
|
-
#resolveParams(params) {
|
|
185
|
-
const resolved = {};
|
|
186
|
-
for (const [name, spec] of Object.entries(params)) {
|
|
187
|
-
switch (spec.from) {
|
|
188
|
-
case 'route': {
|
|
189
|
-
const hash = location.hash.slice(1);
|
|
190
|
-
const match = hash.match(new RegExp(`${spec.key}/([^/]+)`));
|
|
191
|
-
if (match) resolved[name] = match[1];
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
case 'query': {
|
|
195
|
-
const url = new URL(location.href);
|
|
196
|
-
const val = url.searchParams.get(spec.key);
|
|
197
|
-
if (val) resolved[name] = val;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
case 'literal':
|
|
201
|
-
resolved[name] = spec.value;
|
|
202
|
-
break;
|
|
203
|
-
default:
|
|
204
|
-
if (typeof spec === 'string') resolved[name] = spec;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return resolved;
|
|
208
|
-
}
|
|
209
|
-
}
|
package/a2ui/wiring-registry.js
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wiring Registry — Runtime lookup for controllers, action handlers, and data resolvers.
|
|
3
|
-
*
|
|
4
|
-
* Parallel to standardRegistry (type → tag for components), the wiring registry
|
|
5
|
-
* maps controller types → factories, handler names → functions, and URI schemes → resolvers.
|
|
6
|
-
*
|
|
7
|
-
* All entries are lazily loaded — controllers are async factory functions, only imported
|
|
8
|
-
* when a surface actually declares them. A surface that only uses actions never loads
|
|
9
|
-
* any controller module.
|
|
10
|
-
*
|
|
11
|
-
* Extensible: consumers register custom controllers, handlers, and resolvers at startup.
|
|
12
|
-
* A healthcare app registers CheckinController; a CRM registers PipelineController.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// ═══════════════════════════════════════════════════════════════
|
|
16
|
-
// REGISTRY
|
|
17
|
-
// ═══════════════════════════════════════════════════════════════
|
|
18
|
-
|
|
19
|
-
export const wiringRegistry = {
|
|
20
|
-
/** Controller type → async factory function */
|
|
21
|
-
controllers: new Map([
|
|
22
|
-
['FormController', async () => (await import('../controllers/form.js')).FormController],
|
|
23
|
-
['DataStreamController', async () => (await import('../controllers/data-stream.js')).DataStreamController],
|
|
24
|
-
['SelectionController', async () => (await import('../controllers/selection.js')).SelectionController],
|
|
25
|
-
['ToggleController', async () => (await import('../controllers/toggle.js')).ToggleController],
|
|
26
|
-
['AccordionController', async () => (await import('../controllers/accordion.js')).AccordionController],
|
|
27
|
-
['StateMachineController', async () => (await import('../controllers/state-machine.js')).StateMachineController],
|
|
28
|
-
]),
|
|
29
|
-
|
|
30
|
-
/** Action handler name → handler function */
|
|
31
|
-
handlers: new Map([
|
|
32
|
-
['submit-resource', handleSubmitResource],
|
|
33
|
-
['update-model', handleUpdateModel],
|
|
34
|
-
['navigate', handleNavigate],
|
|
35
|
-
['navigate-back', handleNavigateBack],
|
|
36
|
-
['controller-command', handleControllerCommand],
|
|
37
|
-
['emit-event', handleEmitEvent],
|
|
38
|
-
['refresh-source', handleRefreshSource],
|
|
39
|
-
['notify', handleNotify],
|
|
40
|
-
]),
|
|
41
|
-
|
|
42
|
-
/** URI scheme → resolver function */
|
|
43
|
-
resolvers: new Map([
|
|
44
|
-
['resource', defaultResourceResolver],
|
|
45
|
-
['api', defaultApiResolver],
|
|
46
|
-
['mock', defaultMockResolver],
|
|
47
|
-
]),
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// ═══════════════════════════════════════════════════════════════
|
|
51
|
-
// REGISTRATION API
|
|
52
|
-
// ═══════════════════════════════════════════════════════════════
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Register a custom controller type.
|
|
56
|
-
* @param {string} name — Controller type name
|
|
57
|
-
* @param {() => Promise<new (...args: any[]) => any>} factory — Async factory returning the constructor
|
|
58
|
-
*/
|
|
59
|
-
export function registerController(name, factory) {
|
|
60
|
-
wiringRegistry.controllers.set(name, factory);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Register a custom action handler.
|
|
65
|
-
* @param {string} name — Handler name
|
|
66
|
-
* @param {(context: HandlerContext) => Promise<unknown>} fn — Handler function
|
|
67
|
-
*/
|
|
68
|
-
export function registerHandler(name, fn) {
|
|
69
|
-
wiringRegistry.handlers.set(name, fn);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Register a custom URI resolver.
|
|
74
|
-
* @param {string} scheme — URI scheme (e.g., 'resource', 'api', 'custom')
|
|
75
|
-
* @param {(uri: string, params: Record<string, string>) => Promise<unknown>} fn — Resolver function
|
|
76
|
-
*/
|
|
77
|
-
export function registerResolver(scheme, fn) {
|
|
78
|
-
wiringRegistry.resolvers.set(scheme, fn);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ═══════════════════════════════════════════════════════════════
|
|
82
|
-
// RESOLUTION API
|
|
83
|
-
// ═══════════════════════════════════════════════════════════════
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Resolve a controller type to its constructor.
|
|
87
|
-
* @param {string} type — Controller type name
|
|
88
|
-
* @returns {Promise<(new (...args: any[]) => any) | null>}
|
|
89
|
-
*/
|
|
90
|
-
export async function resolveController(type) {
|
|
91
|
-
const factory = wiringRegistry.controllers.get(type);
|
|
92
|
-
if (!factory) return null;
|
|
93
|
-
return factory();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Resolve a handler name to its function.
|
|
98
|
-
* @param {string} name — Handler name
|
|
99
|
-
* @returns {((context: HandlerContext) => Promise<unknown>) | null}
|
|
100
|
-
*/
|
|
101
|
-
export function resolveHandler(name) {
|
|
102
|
-
return wiringRegistry.handlers.get(name) || null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Resolve a resource URI to data.
|
|
107
|
-
* @param {string} uri — Resource URI (e.g., 'resource://patients/123')
|
|
108
|
-
* @param {Record<string, string>} [params] — Resolved parameters
|
|
109
|
-
* @returns {Promise<unknown>}
|
|
110
|
-
*/
|
|
111
|
-
export async function resolveData(uri, params = {}) {
|
|
112
|
-
// Interpolate params into URI
|
|
113
|
-
let resolved = uri;
|
|
114
|
-
for (const [key, value] of Object.entries(params)) {
|
|
115
|
-
resolved = resolved.replace(`{${key}}`, encodeURIComponent(value));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Extract scheme
|
|
119
|
-
const schemeEnd = resolved.indexOf('://');
|
|
120
|
-
if (schemeEnd === -1) throw new Error(`Invalid URI: ${uri}`);
|
|
121
|
-
const scheme = resolved.slice(0, schemeEnd);
|
|
122
|
-
|
|
123
|
-
const resolver = wiringRegistry.resolvers.get(scheme);
|
|
124
|
-
if (!resolver) throw new Error(`No resolver for scheme "${scheme}"`);
|
|
125
|
-
|
|
126
|
-
return resolver(resolved, params);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ═══════════════════════════════════════════════════════════════
|
|
130
|
-
// HANDLER CONTEXT (passed to every action handler)
|
|
131
|
-
// ═══════════════════════════════════════════════════════════════
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* @typedef {object} HandlerContext
|
|
135
|
-
* @property {Event} event — The DOM event that triggered the action
|
|
136
|
-
* @property {HTMLElement} source — The source element
|
|
137
|
-
* @property {object} action — The action declaration from wireComponents
|
|
138
|
-
* @property {object} dataModel — The surface's data model
|
|
139
|
-
* @property {(path: string, value: unknown) => void} updateModel — Update the data model
|
|
140
|
-
* @property {Record<string, any>} controllers — Map of controllerId → instance
|
|
141
|
-
* @property {Record<string, string>} params — Resolved parameters
|
|
142
|
-
* @property {(uri: string, params?: object) => Promise<unknown>} resolveData — Data resolver
|
|
143
|
-
*/
|
|
144
|
-
|
|
145
|
-
// ═══════════════════════════════════════════════════════════════
|
|
146
|
-
// BUILT-IN HANDLERS
|
|
147
|
-
// ═══════════════════════════════════════════════════════════════
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Submit to a resource URI (POST/PUT/PATCH/DELETE).
|
|
151
|
-
*/
|
|
152
|
-
async function handleSubmitResource(ctx) {
|
|
153
|
-
const { action, params } = ctx;
|
|
154
|
-
const body = extractValue(action.body, ctx);
|
|
155
|
-
|
|
156
|
-
let uri = action.uri;
|
|
157
|
-
for (const [key, value] of Object.entries(params)) {
|
|
158
|
-
uri = uri.replace(`{${key}}`, encodeURIComponent(value));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const schemeEnd = uri.indexOf('://');
|
|
162
|
-
const scheme = schemeEnd > -1 ? uri.slice(0, schemeEnd) : 'api';
|
|
163
|
-
const resolver = wiringRegistry.resolvers.get(scheme);
|
|
164
|
-
|
|
165
|
-
// For submit, we need a POST-capable resolver
|
|
166
|
-
// Default: convert resource:// to /api/ REST convention
|
|
167
|
-
const apiPath = uri.replace(/^\w+:\/\//, '/api/');
|
|
168
|
-
const response = await fetch(apiPath, {
|
|
169
|
-
method: action.method || 'POST',
|
|
170
|
-
headers: { 'Content-Type': 'application/json' },
|
|
171
|
-
body: JSON.stringify(body),
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
if (!response.ok) {
|
|
175
|
-
const error = await response.text().catch(() => 'Request failed');
|
|
176
|
-
throw new Error(error);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return response.json().catch(() => ({}));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Update the surface data model at a path.
|
|
184
|
-
*/
|
|
185
|
-
async function handleUpdateModel(ctx) {
|
|
186
|
-
const { action } = ctx;
|
|
187
|
-
const value = extractValue(action.value, ctx);
|
|
188
|
-
ctx.updateModel(action.path, value);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Navigate to a route.
|
|
193
|
-
*/
|
|
194
|
-
async function handleNavigate(ctx) {
|
|
195
|
-
const { action, params } = ctx;
|
|
196
|
-
let path = action.navigate || action.path || '';
|
|
197
|
-
for (const [key, value] of Object.entries(params)) {
|
|
198
|
-
path = path.replace(`{${key}}`, encodeURIComponent(value));
|
|
199
|
-
}
|
|
200
|
-
if (path === 'back') {
|
|
201
|
-
history.back();
|
|
202
|
-
} else if (path) {
|
|
203
|
-
location.hash = path;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Navigate back in browser history.
|
|
209
|
-
*/
|
|
210
|
-
async function handleNavigateBack() {
|
|
211
|
-
history.back();
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Invoke a controller command.
|
|
216
|
-
*/
|
|
217
|
-
async function handleControllerCommand(ctx) {
|
|
218
|
-
const { action, controllers } = ctx;
|
|
219
|
-
const controller = controllers[action.controllerId];
|
|
220
|
-
if (!controller?.commands?.[action.command]) {
|
|
221
|
-
console.warn(`Wiring: controller "${action.controllerId}" or command "${action.command}" not found`);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
return controller.commands[action.command](action.args);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Re-emit as a named custom event on the surface root.
|
|
229
|
-
*/
|
|
230
|
-
async function handleEmitEvent(ctx) {
|
|
231
|
-
const { action, source } = ctx;
|
|
232
|
-
const detail = extractValue(action.detail, ctx);
|
|
233
|
-
source.dispatchEvent(new CustomEvent(action.eventName || action.event, {
|
|
234
|
-
bubbles: true,
|
|
235
|
-
detail,
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Re-fetch a named data source.
|
|
241
|
-
*/
|
|
242
|
-
async function handleRefreshSource(ctx) {
|
|
243
|
-
const sourceId = ctx.action.sourceId;
|
|
244
|
-
// In the dock system, the surface context has getDockable.
|
|
245
|
-
// From the handler context, we can access it through the adapted ctx.
|
|
246
|
-
// For now, dispatch a custom event that the DataSourceDock can listen for.
|
|
247
|
-
document.dispatchEvent(new CustomEvent('a2ui-refresh-source', {
|
|
248
|
-
detail: { sourceId },
|
|
249
|
-
}));
|
|
250
|
-
return { refreshSource: sourceId };
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Show a toast/alert notification.
|
|
255
|
-
*/
|
|
256
|
-
async function handleNotify(ctx) {
|
|
257
|
-
const { action } = ctx;
|
|
258
|
-
const message = typeof action === 'string' ? action : action.message || action.notify;
|
|
259
|
-
// Dispatch a notification event that a toast-ui can listen for
|
|
260
|
-
document.dispatchEvent(new CustomEvent('a2ui-notify', {
|
|
261
|
-
bubbles: true,
|
|
262
|
-
detail: { message, variant: action.variant || 'info' },
|
|
263
|
-
}));
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ═══════════════════════════════════════════════════════════════
|
|
267
|
-
// VALUE EXTRACTION
|
|
268
|
-
// ═══════════════════════════════════════════════════════════════
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Extract a value based on a value descriptor.
|
|
272
|
-
* @param {object|undefined} descriptor — { from, key, path, value }
|
|
273
|
-
* @param {HandlerContext} ctx
|
|
274
|
-
* @returns {unknown}
|
|
275
|
-
*/
|
|
276
|
-
function extractValue(descriptor, ctx) {
|
|
277
|
-
if (!descriptor) return undefined;
|
|
278
|
-
if (typeof descriptor !== 'object') return descriptor;
|
|
279
|
-
|
|
280
|
-
switch (descriptor.from) {
|
|
281
|
-
case 'event-detail':
|
|
282
|
-
return descriptor.key ? ctx.event?.detail?.[descriptor.key] : ctx.event?.detail;
|
|
283
|
-
case 'event-target':
|
|
284
|
-
return descriptor.key ? ctx.source?.[descriptor.key] : ctx.source?.value;
|
|
285
|
-
case 'model':
|
|
286
|
-
return getModelValue(ctx.dataModel, descriptor.path);
|
|
287
|
-
case 'literal':
|
|
288
|
-
return descriptor.value;
|
|
289
|
-
case 'param':
|
|
290
|
-
return ctx.params?.[descriptor.key];
|
|
291
|
-
default:
|
|
292
|
-
return descriptor;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get a value from a data model by JSON Pointer path.
|
|
298
|
-
* @param {object} model
|
|
299
|
-
* @param {string} path — JSON Pointer (e.g., "/patient/name")
|
|
300
|
-
* @returns {unknown}
|
|
301
|
-
*/
|
|
302
|
-
function getModelValue(model, path) {
|
|
303
|
-
if (!path || !model) return undefined;
|
|
304
|
-
const segments = path.split('/').filter(Boolean);
|
|
305
|
-
let current = model;
|
|
306
|
-
for (const seg of segments) {
|
|
307
|
-
if (current == null || typeof current !== 'object') return undefined;
|
|
308
|
-
current = current[seg];
|
|
309
|
-
}
|
|
310
|
-
return current;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// ═══════════════════════════════════════════════════════════════
|
|
314
|
-
// DEFAULT RESOLVERS
|
|
315
|
-
// ═══════════════════════════════════════════════════════════════
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Default resource:// resolver — maps to REST convention: /api/{path}
|
|
319
|
-
*/
|
|
320
|
-
async function defaultResourceResolver(uri, params) {
|
|
321
|
-
const path = uri.replace(/^resource:\/\//, '/api/');
|
|
322
|
-
const response = await fetch(path);
|
|
323
|
-
if (!response.ok) throw new Error(`Resource fetch failed: ${response.status}`);
|
|
324
|
-
return response.json();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Default api:// resolver — direct URL passthrough.
|
|
329
|
-
*/
|
|
330
|
-
async function defaultApiResolver(uri, params) {
|
|
331
|
-
const url = uri.replace(/^api:\/\//, 'https://');
|
|
332
|
-
const response = await fetch(url);
|
|
333
|
-
if (!response.ok) throw new Error(`API fetch failed: ${response.status}`);
|
|
334
|
-
return response.json();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Default mock:// resolver — returns empty object with the URI as _source.
|
|
339
|
-
*/
|
|
340
|
-
async function defaultMockResolver(uri, params) {
|
|
341
|
-
return { _source: uri, _mock: true };
|
|
342
|
-
}
|