@adia-ai/web-components 0.0.2 → 0.0.4
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/README.md +29 -22
- 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.js +1 -1
- 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.js +1 -1
- package/components/badge/badge.js +1 -1
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.js +2 -2
- package/components/calendar-picker/calendar-picker.js +2 -2
- package/components/canvas/canvas.js +2 -2
- package/components/card/card.js +2 -2
- package/components/chart/chart.js +1 -1
- package/components/chat/chat-input.js +1 -1
- package/components/chat/chat.css +1 -2
- package/components/chat/chat.js +2 -2
- package/components/check/check.js +2 -2
- package/components/code/code.css +2 -0
- package/components/code/code.js +1 -1
- package/components/col/col.js +1 -1
- package/components/color-picker/color-picker.js +1 -1
- package/components/command/command.js +1 -1
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.js +1 -1
- package/components/drawer/drawer.js +1 -1
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.js +1 -1
- package/components/grid/grid.js +1 -1
- package/components/heatmap/heatmap.js +1 -1
- package/components/icon/icon.js +2 -2
- package/components/image/image.js +1 -1
- package/components/input/input.js +2 -2
- package/components/inspector/inspector.js +2 -2
- package/components/kbd/kbd.js +1 -1
- package/components/list/list.js +1 -1
- package/components/menu/menu.js +2 -2
- package/components/modal/modal.js +1 -1
- package/components/noodles/noodles.js +1 -1
- package/components/otp-input/otp-input.js +1 -1
- package/components/pagination/pagination.js +1 -1
- package/components/pane/pane.css +2 -2
- package/components/pane/pane.js +1 -1
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.js +2 -2
- package/components/progress/progress.js +1 -1
- package/components/progress-row/progress-row.js +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.js +2 -2
- package/components/search/search.js +1 -1
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.js +2 -2
- package/components/skeleton/skeleton.js +1 -1
- package/components/slider/slider.js +1 -1
- 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.js +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.js +1 -1
- package/components/text/text.js +1 -1
- package/components/textarea/textarea.js +1 -1
- package/components/timeline/timeline.js +1 -1
- package/components/toast/toast.js +1 -1
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toolbar/toolbar.js +2 -2
- package/components/tooltip/tooltip.js +2 -2
- 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.tokens.css +1 -1
- package/patterns/adia-editor/adia-editor.a2ui.json +12 -0
- package/patterns/adia-editor/adia-editor.js +1 -1
- package/patterns/adia-editor/adia-editor.yaml +17 -0
- package/patterns/adia-editor/css/adia-editor.layout.css +70 -3
- package/patterns/adia-editor/css/adia-editor.tokens.css +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/app-shell/css/app-shell.tokens.css +1 -1
- 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/styles/tokens.css +12 -0
- 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/patterns/adia-chat/index.html +0 -93
- package/patterns/adia-editor/index.html +0 -179
- package/patterns/app-shell/index.html +0 -112
|
@@ -39,8 +39,20 @@
|
|
|
39
39
|
"Toolbar"
|
|
40
40
|
],
|
|
41
41
|
"slots": {
|
|
42
|
+
"description": {
|
|
43
|
+
"description": "Secondary metadata inside <header> or <footer> — document name, artboard size, zoom level, etc. Muted color, --a-ui-sm size."
|
|
44
|
+
},
|
|
42
45
|
"default": {
|
|
43
46
|
"description": "Author provides header, [data-editor-body] wrapping pane-ui[data-left], [data-canvas], pane-ui[data-right], and an optional footer."
|
|
47
|
+
},
|
|
48
|
+
"action": {
|
|
49
|
+
"description": "Trailing control cluster inside <header> or <footer>. The first [slot=\"action\"] child pushes itself (and siblings) to the end of the bar; subsequent [slot=\"action\"] siblings flow with gap."
|
|
50
|
+
},
|
|
51
|
+
"heading": {
|
|
52
|
+
"description": "Primary label inside <header> or <footer>. Rendered with --editor-title-weight + the strong foreground token."
|
|
53
|
+
},
|
|
54
|
+
"icon": {
|
|
55
|
+
"description": "Leading glyph inside <header> or <footer> — status dot, app icon, zoom badge, etc. Muted color, size-aware."
|
|
44
56
|
}
|
|
45
57
|
},
|
|
46
58
|
"states": [
|
|
@@ -20,6 +20,23 @@ slots:
|
|
|
20
20
|
description: >-
|
|
21
21
|
Author provides header, [data-editor-body] wrapping pane-ui[data-left],
|
|
22
22
|
[data-canvas], pane-ui[data-right], and an optional footer.
|
|
23
|
+
icon:
|
|
24
|
+
description: >-
|
|
25
|
+
Leading glyph inside <header> or <footer> — status dot, app icon,
|
|
26
|
+
zoom badge, etc. Muted color, size-aware.
|
|
27
|
+
heading:
|
|
28
|
+
description: >-
|
|
29
|
+
Primary label inside <header> or <footer>. Rendered with
|
|
30
|
+
--editor-title-weight + the strong foreground token.
|
|
31
|
+
description:
|
|
32
|
+
description: >-
|
|
33
|
+
Secondary metadata inside <header> or <footer> — document name,
|
|
34
|
+
artboard size, zoom level, etc. Muted color, --a-ui-sm size.
|
|
35
|
+
action:
|
|
36
|
+
description: >-
|
|
37
|
+
Trailing control cluster inside <header> or <footer>. The first
|
|
38
|
+
[slot="action"] child pushes itself (and siblings) to the end of
|
|
39
|
+
the bar; subsequent [slot="action"] siblings flow with gap.
|
|
23
40
|
|
|
24
41
|
states:
|
|
25
42
|
- name: idle
|
|
@@ -20,7 +20,16 @@ adia-editor-ui {
|
|
|
20
20
|
background: var(--editor-bg);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/* ── Top bar ──
|
|
23
|
+
/* ── Top bar ──
|
|
24
|
+
Slot contract (shared with > footer below):
|
|
25
|
+
[slot="icon"] leading glyph (status dot, app icon, etc.)
|
|
26
|
+
[slot="heading"] primary label; strong-weight token
|
|
27
|
+
[slot="description"] secondary metadata; muted
|
|
28
|
+
[slot="action"] trailing control cluster; first pushes to end
|
|
29
|
+
Authors may also place raw inline content (spans, buttons) directly
|
|
30
|
+
— those flow in source order. `[data-title]` / `<span data-spacer>`
|
|
31
|
+
are kept working for one release as deprecated hooks; migrate to
|
|
32
|
+
slots. */
|
|
24
33
|
adia-editor-ui > header {
|
|
25
34
|
display: flex;
|
|
26
35
|
align-items: center;
|
|
@@ -33,11 +42,36 @@ adia-editor-ui > header {
|
|
|
33
42
|
flex-shrink: 0;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
adia-editor-ui > header [data-title]
|
|
45
|
+
adia-editor-ui > header [data-title],
|
|
46
|
+
adia-editor-ui > header > [slot="heading"] {
|
|
37
47
|
font-weight: var(--editor-title-weight);
|
|
38
48
|
color: var(--editor-bar-fg-strong);
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
adia-editor-ui > header > [slot="icon"] {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
color: var(--editor-bar-fg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
adia-editor-ui > header > [slot="description"] {
|
|
59
|
+
color: var(--editor-bar-fg);
|
|
60
|
+
font-size: var(--a-ui-sm);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
adia-editor-ui > header > [slot="action"] {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--editor-bar-gap);
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
margin-inline-start: auto;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
adia-editor-ui > header > [slot="action"] ~ [slot="action"] {
|
|
72
|
+
margin-inline-start: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
41
75
|
/* ── Body: pane | canvas | pane ── */
|
|
42
76
|
adia-editor-ui > [data-editor-body] {
|
|
43
77
|
display: flex;
|
|
@@ -72,7 +106,11 @@ adia-editor-ui pane-ui[data-right] {
|
|
|
72
106
|
width: var(--editor-pane-width-right);
|
|
73
107
|
}
|
|
74
108
|
|
|
75
|
-
/* ── Bottom bar ──
|
|
109
|
+
/* ── Bottom bar ──
|
|
110
|
+
Same slot contract as > header above (icon / heading / description
|
|
111
|
+
/ action). Statusbar is structurally identical to the topbar —
|
|
112
|
+
both are 36px-min chrome bands with left-aligned label content and
|
|
113
|
+
a trailing action cluster. */
|
|
76
114
|
adia-editor-ui > footer {
|
|
77
115
|
display: flex;
|
|
78
116
|
align-items: center;
|
|
@@ -84,3 +122,32 @@ adia-editor-ui > footer {
|
|
|
84
122
|
color: var(--editor-bar-fg);
|
|
85
123
|
flex-shrink: 0;
|
|
86
124
|
}
|
|
125
|
+
|
|
126
|
+
adia-editor-ui > footer > [slot="heading"] {
|
|
127
|
+
font-weight: var(--editor-title-weight);
|
|
128
|
+
color: var(--editor-bar-fg-strong);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
adia-editor-ui > footer > [slot="icon"] {
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
flex-shrink: 0;
|
|
135
|
+
color: var(--editor-bar-fg);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
adia-editor-ui > footer > [slot="description"] {
|
|
139
|
+
color: var(--editor-bar-fg);
|
|
140
|
+
font-size: var(--a-ui-sm);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
adia-editor-ui > footer > [slot="action"] {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: var(--editor-bar-gap);
|
|
147
|
+
flex-shrink: 0;
|
|
148
|
+
margin-inline-start: auto;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
adia-editor-ui > footer > [slot="action"] ~ [slot="action"] {
|
|
152
|
+
margin-inline-start: 0;
|
|
153
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
--editor-border: 1px solid var(--a-border-subtle);
|
|
9
9
|
|
|
10
10
|
/* Top/bottom bars */
|
|
11
|
-
--editor-bar-height:
|
|
11
|
+
--editor-bar-height: var(--a-chrome-pane-header-height);
|
|
12
12
|
--editor-bar-px: var(--a-space-3);
|
|
13
13
|
--editor-bar-gap: var(--a-space-2);
|
|
14
14
|
--editor-bar-font: var(--a-ui-size);
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* popover with its children instead of toggling inline expansion.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { AdiaElement } from '
|
|
10
|
-
import { anchorPopover } from '
|
|
9
|
+
import { AdiaElement } from '../../core/element.js';
|
|
10
|
+
import { anchorPopover } from '../../core/anchor.js';
|
|
11
11
|
|
|
12
12
|
class AdiaAppNavGroup extends AdiaElement {
|
|
13
13
|
static properties = {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
--page-main-border: var(--page-border); /* main inline borders (separate so modes can override) */
|
|
20
20
|
|
|
21
21
|
/* Header / footer bars — shared by topbar + bottombar */
|
|
22
|
-
--page-header-height:
|
|
22
|
+
--page-header-height: var(--a-chrome-app-header-height);
|
|
23
23
|
--page-header-px: var(--a-space-3);
|
|
24
24
|
--page-header-gap: var(--a-space-3);
|
|
25
25
|
--page-header-font: var(--a-ui-size);
|
package/patterns/index.js
CHANGED
|
@@ -8,3 +8,4 @@ export { AdiaSectionNavItem } from './section-nav-item/section-nav-item.js';
|
|
|
8
8
|
export { AdiaGenUI } from './gen-ui/gen-ui.js';
|
|
9
9
|
export { AdiaChatElement } from './adia-chat/adia-chat.js';
|
|
10
10
|
export { AdiaEditorElement } from './adia-editor/adia-editor.js';
|
|
11
|
+
export { AdiaA2UIRoot } from './a2ui-root/a2ui-root.js';
|
package/styles/tokens.css
CHANGED
|
@@ -102,6 +102,18 @@
|
|
|
102
102
|
--a-size-lg: calc(var(--a-density) * 2.25rem); /* 36px at d=1 */
|
|
103
103
|
--a-size: var(--a-size-md);
|
|
104
104
|
|
|
105
|
+
/* ── Chrome heights — global app/feature UI ──
|
|
106
|
+
App-level chrome (top-level headers, footers, toolbars) is 48px;
|
|
107
|
+
pane-level chrome (pane headers, footers, toolbars) is 36px.
|
|
108
|
+
Fixed values — these are structural measurements, not typographic,
|
|
109
|
+
so they don't scale with --a-density. */
|
|
110
|
+
--a-chrome-app-header-height: 48px;
|
|
111
|
+
--a-chrome-app-footer-height: 48px;
|
|
112
|
+
--a-chrome-app-toolbar-height: 48px;
|
|
113
|
+
--a-chrome-pane-header-height: 36px;
|
|
114
|
+
--a-chrome-pane-footer-height: 36px;
|
|
115
|
+
--a-chrome-pane-toolbar-height: 36px;
|
|
116
|
+
|
|
105
117
|
/* ── Toggle-control size (check, radio, switch) ──
|
|
106
118
|
Tighter additive scale than --a-size: 16 / 20 / 24 px at d=1. */
|
|
107
119
|
--a-toggle-size: calc(var(--a-size-sm) - var(--a-space-1)); /* 20px at d=1 — md default */
|
package/traits/define.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A trait is a reusable behavior package: event listeners, attribute management,
|
|
5
5
|
* and cleanup — defined once, attached to any element.
|
|
6
6
|
*
|
|
7
|
-
* import { defineTrait } from '
|
|
7
|
+
* import { defineTrait } from './define.js';
|
|
8
8
|
*
|
|
9
9
|
* export const pressable = defineTrait({
|
|
10
10
|
* name: 'pressable',
|
package/a2ui/dockables/action.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ActionDock — Binds a AdiaEvent on a component to a handler.
|
|
3
|
-
*
|
|
4
|
-
* The AdiaEvent is a typed object: { event, target?, debounce?, throttle?, condition? }
|
|
5
|
-
* When the DOM event fires, the handler runs with the surface context.
|
|
6
|
-
* onSuccess/onError chains run follow-up handlers based on the outcome.
|
|
7
|
-
*/
|
|
8
|
-
import { Dockable } from './base.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* AdiaEvent type → DOM event name.
|
|
12
|
-
* AdiaUI components emit semantic events; these map to the actual DOM event to listen on.
|
|
13
|
-
*/
|
|
14
|
-
const NANO_TO_DOM = {
|
|
15
|
-
press: 'click',
|
|
16
|
-
submit: 'submit',
|
|
17
|
-
input: 'input',
|
|
18
|
-
change: 'change',
|
|
19
|
-
select: 'select',
|
|
20
|
-
toggle: 'change',
|
|
21
|
-
dismiss: 'close',
|
|
22
|
-
navigate: 'click',
|
|
23
|
-
mount: 'connectedCallback',
|
|
24
|
-
unmount: 'disconnectedCallback',
|
|
25
|
-
focus: 'focusin',
|
|
26
|
-
blur: 'focusout',
|
|
27
|
-
drag: 'dragstart',
|
|
28
|
-
drop: 'drop',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export { NANO_TO_DOM };
|
|
32
|
-
|
|
33
|
-
export class ActionDock extends Dockable {
|
|
34
|
-
kind = 'action';
|
|
35
|
-
|
|
36
|
-
/** @type {string} */
|
|
37
|
-
id;
|
|
38
|
-
|
|
39
|
-
/** @type {{ event: string, target?: string, debounce?: number, throttle?: number, condition?: object }} */
|
|
40
|
-
event;
|
|
41
|
-
|
|
42
|
-
/** @type {string} handler name */
|
|
43
|
-
handler;
|
|
44
|
-
|
|
45
|
-
/** @type {object} handler config */
|
|
46
|
-
config;
|
|
47
|
-
|
|
48
|
-
/** @type {Array|null} follow-up actions on success */
|
|
49
|
-
onSuccess;
|
|
50
|
-
|
|
51
|
-
/** @type {Array|null} follow-up actions on error */
|
|
52
|
-
onError;
|
|
53
|
-
|
|
54
|
-
/** @type {Function} (handlerName) => handlerFn */
|
|
55
|
-
#resolveHandler;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param {object} decl — { event, handler, config?, onSuccess?, onError? }
|
|
59
|
-
* @param {Function} resolveHandler — (name) => async (config, ctx) => result
|
|
60
|
-
*/
|
|
61
|
-
constructor(decl, resolveHandler) {
|
|
62
|
-
super();
|
|
63
|
-
this.event = decl.event;
|
|
64
|
-
this.handler = decl.handler;
|
|
65
|
-
this.config = decl.config || {};
|
|
66
|
-
this.onSuccess = decl.onSuccess || null;
|
|
67
|
-
this.onError = decl.onError || null;
|
|
68
|
-
this.#resolveHandler = resolveHandler;
|
|
69
|
-
|
|
70
|
-
// Auto-generate id from event + target
|
|
71
|
-
this.id = `action:${this.event.event}:${this.event.target || 'root'}:${this.handler}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
dock(ctx) {
|
|
75
|
-
const target = this.event.target
|
|
76
|
-
? ctx.getElement(this.event.target)
|
|
77
|
-
: ctx.getRootElement();
|
|
78
|
-
|
|
79
|
-
if (!target) {
|
|
80
|
-
console.warn(`ActionDock: target "${this.event.target}" not found`);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const domEventName = NANO_TO_DOM[this.event.event] || this.event.event;
|
|
85
|
-
const handlerFn = this.#resolveHandler(this.handler);
|
|
86
|
-
|
|
87
|
-
if (!handlerFn) {
|
|
88
|
-
console.warn(`ActionDock: unknown handler "${this.handler}"`);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let listener = async (domEvent) => {
|
|
93
|
-
// Condition guard
|
|
94
|
-
if (this.event.condition) {
|
|
95
|
-
const val = ctx.getModel(this.event.condition.path);
|
|
96
|
-
if ('equals' in this.event.condition && val !== this.event.condition.equals) return;
|
|
97
|
-
if ('notEquals' in this.event.condition && val === this.event.condition.notEquals) return;
|
|
98
|
-
if ('exists' in this.event.condition && this.event.condition.exists && val == null) return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const result = await handlerFn(this.config, ctx, domEvent);
|
|
103
|
-
|
|
104
|
-
// Run onSuccess chain
|
|
105
|
-
if (this.onSuccess) {
|
|
106
|
-
for (const follow of this.onSuccess) {
|
|
107
|
-
const followFn = this.#resolveHandler(follow.handler);
|
|
108
|
-
if (followFn) await followFn(follow.config || {}, ctx, result);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} catch (err) {
|
|
112
|
-
// Run onError chain
|
|
113
|
-
if (this.onError) {
|
|
114
|
-
for (const follow of this.onError) {
|
|
115
|
-
const followFn = this.#resolveHandler(follow.handler);
|
|
116
|
-
if (followFn) await followFn(follow.config || {}, ctx, err);
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
console.error(`ActionDock: handler "${this.handler}" failed`, err);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Debounce
|
|
125
|
-
if (this.event.debounce > 0) {
|
|
126
|
-
const origListener = listener;
|
|
127
|
-
let timer;
|
|
128
|
-
listener = (...args) => {
|
|
129
|
-
clearTimeout(timer);
|
|
130
|
-
timer = setTimeout(() => origListener(...args), this.event.debounce);
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Throttle
|
|
135
|
-
if (this.event.throttle > 0) {
|
|
136
|
-
const origListener = listener;
|
|
137
|
-
let last = 0;
|
|
138
|
-
listener = (...args) => {
|
|
139
|
-
const now = Date.now();
|
|
140
|
-
if (now - last >= this.event.throttle) {
|
|
141
|
-
last = now;
|
|
142
|
-
origListener(...args);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
target.addEventListener(domEventName, listener);
|
|
148
|
-
return () => target.removeEventListener(domEventName, listener);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
undock() {}
|
|
152
|
-
}
|
package/a2ui/dockables/base.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dockable — Base class for objects that attach to a Surface.
|
|
3
|
-
*
|
|
4
|
-
* Every dockable has:
|
|
5
|
-
* kind — which port type it uses (controller, source, action, provider, lifecycle)
|
|
6
|
-
* id — unique identity within the surface
|
|
7
|
-
* dock(context) — attach and return cleanup function
|
|
8
|
-
* undock() — teardown
|
|
9
|
-
*/
|
|
10
|
-
export class Dockable {
|
|
11
|
-
/** @type {'controller'|'source'|'action'|'provider'|'lifecycle'} */
|
|
12
|
-
get kind() { throw new Error('Dockable subclass must define kind'); }
|
|
13
|
-
|
|
14
|
-
/** @type {string} */
|
|
15
|
-
get id() { throw new Error('Dockable subclass must define id'); }
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Attach to a surface. Receives the surface context.
|
|
19
|
-
* Return a cleanup function, or void.
|
|
20
|
-
* @param {import('../surface.js').SurfaceContext} ctx
|
|
21
|
-
* @returns {(() => void)|void}
|
|
22
|
-
*/
|
|
23
|
-
dock(ctx) { throw new Error('Dockable subclass must implement dock()'); }
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Detach from the surface. Cleanup fn from dock() is called
|
|
27
|
-
* automatically before this.
|
|
28
|
-
*/
|
|
29
|
-
undock() {}
|
|
30
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ControllerDock — Wraps a AdiaUI controller as a dockable.
|
|
3
|
-
*
|
|
4
|
-
* Instantiates the controller, connects it to its host element,
|
|
5
|
-
* and sets up two-way binding between controller state and model paths.
|
|
6
|
-
*/
|
|
7
|
-
import { Dockable } from './base.js';
|
|
8
|
-
|
|
9
|
-
export class ControllerDock extends Dockable {
|
|
10
|
-
kind = 'controller';
|
|
11
|
-
|
|
12
|
-
/** @type {string} */
|
|
13
|
-
id;
|
|
14
|
-
|
|
15
|
-
/** @type {string} controller type name, e.g., 'FormController' */
|
|
16
|
-
type;
|
|
17
|
-
|
|
18
|
-
/** @type {string} component id to attach to */
|
|
19
|
-
hostId;
|
|
20
|
-
|
|
21
|
-
/** @type {object} controller config */
|
|
22
|
-
config;
|
|
23
|
-
|
|
24
|
-
/** @type {object|null} model path bindings { stateKey: '/model/path' } */
|
|
25
|
-
bind;
|
|
26
|
-
|
|
27
|
-
/** @type {import('../../controllers/base.js').BaseController|null} */
|
|
28
|
-
controller = null;
|
|
29
|
-
|
|
30
|
-
/** @type {Function|null} resolve controller class */
|
|
31
|
-
#resolveClass;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {object} decl — { id, type, host, config?, bind? }
|
|
35
|
-
* @param {Function} resolveClass — async (type) => ControllerClass
|
|
36
|
-
*/
|
|
37
|
-
constructor(decl, resolveClass) {
|
|
38
|
-
super();
|
|
39
|
-
this.id = decl.id;
|
|
40
|
-
this.type = decl.type;
|
|
41
|
-
this.hostId = decl.host;
|
|
42
|
-
this.config = decl.config || {};
|
|
43
|
-
this.bind = decl.bind || null;
|
|
44
|
-
this.#resolveClass = resolveClass;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async dock(ctx) {
|
|
48
|
-
const host = ctx.getElement(this.hostId);
|
|
49
|
-
if (!host) {
|
|
50
|
-
console.warn(`ControllerDock: host element "${this.hostId}" not found`);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const ControllerClass = await this.#resolveClass(this.type);
|
|
55
|
-
if (!ControllerClass) {
|
|
56
|
-
console.warn(`ControllerDock: unknown controller type "${this.type}"`);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
this.controller = new ControllerClass(this.config);
|
|
61
|
-
this.controller.connect(host);
|
|
62
|
-
|
|
63
|
-
// Two-way bind: controller state ↔ model paths
|
|
64
|
-
const unbinders = [];
|
|
65
|
-
if (this.bind) {
|
|
66
|
-
for (const [stateKey, modelPath] of Object.entries(this.bind)) {
|
|
67
|
-
// Controller → model
|
|
68
|
-
if (this.controller.subscribe) {
|
|
69
|
-
const unsub = this.controller.subscribe(stateKey, (value) => {
|
|
70
|
-
ctx.setModel(modelPath, value);
|
|
71
|
-
});
|
|
72
|
-
if (unsub) unbinders.push(unsub);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Model → controller (initial sync)
|
|
76
|
-
const initial = ctx.getModel(modelPath);
|
|
77
|
-
if (initial !== undefined && this.controller.setState) {
|
|
78
|
-
this.controller.setState(stateKey, initial);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
host.controller = this.controller;
|
|
84
|
-
|
|
85
|
-
return () => {
|
|
86
|
-
host.controller = null;
|
|
87
|
-
unbinders.forEach(u => u());
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
undock() {
|
|
92
|
-
if (this.controller) {
|
|
93
|
-
this.controller.disconnect?.();
|
|
94
|
-
this.controller = null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DataSourceDock — Fetches data and pushes it into the surface model.
|
|
3
|
-
*
|
|
4
|
-
* Supports refresh strategies: once, on-focus, interval:{ms}, stream.
|
|
5
|
-
* Re-fetch on demand via refetch() (used by refresh-source handler).
|
|
6
|
-
*/
|
|
7
|
-
import { Dockable } from './base.js';
|
|
8
|
-
|
|
9
|
-
export class DataSourceDock extends Dockable {
|
|
10
|
-
kind = 'source';
|
|
11
|
-
|
|
12
|
-
/** @type {string} */
|
|
13
|
-
id;
|
|
14
|
-
|
|
15
|
-
/** @type {string} URI template, e.g., "resource://users/{userId}" */
|
|
16
|
-
uri;
|
|
17
|
-
|
|
18
|
-
/** @type {string} model path to write to */
|
|
19
|
-
path;
|
|
20
|
-
|
|
21
|
-
/** @type {string} refresh strategy */
|
|
22
|
-
refresh;
|
|
23
|
-
|
|
24
|
-
/** @type {Function} (uri, params) => data */
|
|
25
|
-
#resolveData;
|
|
26
|
-
|
|
27
|
-
/** @type {import('../surface.js').SurfaceContext|null} */
|
|
28
|
-
#ctx = null;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {object} decl — { id, uri, path, refresh? }
|
|
32
|
-
* @param {Function} resolveData — async (uri, params) => data
|
|
33
|
-
*/
|
|
34
|
-
constructor(decl, resolveData) {
|
|
35
|
-
super();
|
|
36
|
-
this.id = decl.id;
|
|
37
|
-
this.uri = decl.uri;
|
|
38
|
-
this.path = decl.path;
|
|
39
|
-
this.refresh = decl.refresh || 'once';
|
|
40
|
-
this.#resolveData = resolveData;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
dock(ctx) {
|
|
44
|
-
this.#ctx = ctx;
|
|
45
|
-
|
|
46
|
-
// Initial fetch
|
|
47
|
-
this.#fetch(ctx);
|
|
48
|
-
|
|
49
|
-
// Refresh strategy
|
|
50
|
-
if (this.refresh === 'on-focus') {
|
|
51
|
-
const handler = () => this.#fetch(ctx);
|
|
52
|
-
window.addEventListener('focus', handler);
|
|
53
|
-
return () => window.removeEventListener('focus', handler);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (this.refresh.startsWith('interval:')) {
|
|
57
|
-
const ms = parseInt(this.refresh.split(':')[1], 10);
|
|
58
|
-
if (ms > 0) {
|
|
59
|
-
const id = setInterval(() => this.#fetch(ctx), ms);
|
|
60
|
-
return () => clearInterval(id);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (this.refresh === 'stream') {
|
|
65
|
-
const resolvedUri = this.#resolveUri(ctx);
|
|
66
|
-
try {
|
|
67
|
-
const source = new EventSource(resolvedUri);
|
|
68
|
-
source.onmessage = (e) => {
|
|
69
|
-
try { ctx.setModel(this.path, JSON.parse(e.data)); }
|
|
70
|
-
catch { /* malformed data */ }
|
|
71
|
-
};
|
|
72
|
-
return () => source.close();
|
|
73
|
-
} catch {
|
|
74
|
-
console.warn(`DataSourceDock: stream failed for ${resolvedUri}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
undock() {
|
|
80
|
-
this.#ctx = null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Re-fetch on demand (called by refresh-source handler). */
|
|
84
|
-
refetch() {
|
|
85
|
-
if (this.#ctx) this.#fetch(this.#ctx);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async #fetch(ctx) {
|
|
89
|
-
const resolvedUri = this.#resolveUri(ctx);
|
|
90
|
-
try {
|
|
91
|
-
const data = await this.#resolveData(resolvedUri, ctx);
|
|
92
|
-
if (data !== undefined) ctx.setModel(this.path, data);
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.warn(`DataSourceDock: fetch failed for ${resolvedUri}`, err);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
#resolveUri(ctx) {
|
|
99
|
-
return this.uri.replace(/\{(\w+)\}/g, (_, key) => {
|
|
100
|
-
return ctx.getParam(key) ?? ctx.getModel('/' + key) ?? '';
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
package/a2ui/dockables/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { Dockable } from './base.js';
|
|
2
|
-
export { ControllerDock } from './controller.js';
|
|
3
|
-
export { DataSourceDock } from './data-source.js';
|
|
4
|
-
export { ActionDock, NANO_TO_DOM } from './action.js';
|
|
5
|
-
export { ProviderDock } from './provider.js';
|
|
6
|
-
export { LifecycleDock } from './lifecycle.js';
|