@proto.ui/adapter-base 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Proto UI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @proto.ui/adapter-base
2
+
3
+ Base package for building Proto UI adapters.
4
+
5
+ ## Purpose
6
+
7
+ Provides the base template, shared host wiring, and common runtime bridges for building Proto UI adapters.
8
+
9
+ ## Package Role
10
+
11
+ Adapter foundation package used to translate Proto UI contracts into concrete host integrations.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @proto.ui/adapter-base@0.0.1
17
+ ```
18
+
19
+ ## Internal Structure
20
+
21
+ - `src/events/`
22
+ - `src/gate/`
23
+ - `src/host/`
24
+ - `src/index.ts`
25
+ - `src/lifecycle/`
26
+ - `src/types.ts`
27
+ - `src/wiring/`
28
+
29
+ ## Related Internal Packages
30
+
31
+ - `@proto.ui/core`
32
+ - `@proto.ui/module-anatomy`
33
+ - `@proto.ui/module-as-trigger`
34
+ - `@proto.ui/module-context`
35
+ - `@proto.ui/module-event`
36
+ - `@proto.ui/module-expose`
37
+ - `@proto.ui/module-expose-state-web`
38
+ - `@proto.ui/module-feedback`
39
+ - `@proto.ui/module-focus`
40
+ - `@proto.ui/module-props`
41
+ - `@proto.ui/module-rule-expose-state-web`
42
+ - `@proto.ui/module-rule-meta`
43
+ - `@proto.ui/runtime`
44
+ - `@proto.ui/types`
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,10 @@
1
+ export declare function createWebProtoEventRouter(opt: {
2
+ rootEl: HTMLElement;
3
+ globalEl?: EventTarget;
4
+ isEnabled: () => boolean;
5
+ }): {
6
+ /** Inject these into EVENT_*_TARGET_CAP */
7
+ rootTarget: EventTarget;
8
+ globalTarget: EventTarget;
9
+ dispose(): void;
10
+ };
@@ -0,0 +1,195 @@
1
+ // packages/adapters/base/src/events/web-event-router.ts
2
+ export function createWebProtoEventRouter(opt) {
3
+ // proto semantic bus: press.*, pointer.*, key.*, context.menu...
4
+ const protoRootBus = new EventTarget();
5
+ const protoGlobalBus = new EventTarget();
6
+ const rootEl = opt.rootEl;
7
+ const globalEl = opt.globalEl ?? window;
8
+ // --- helper: emit proto event to proto bus ---
9
+ function emit(target, type, native) {
10
+ const ev = new CustomEvent(type, { detail: native });
11
+ target.dispatchEvent(ev);
12
+ }
13
+ // -------------------------
14
+ // (A) 固定监听:解释“协议语义事件”
15
+ // -------------------------
16
+ // 这些是你愿意为每个组件实例支付的固定成本(尽量少)
17
+ const unsubs = [];
18
+ // pointer -> pointer.*
19
+ unsubs.push(listen(rootEl, 'pointerdown', (e) => {
20
+ if (!opt.isEnabled())
21
+ return;
22
+ emit(protoRootBus, 'pointer.down', e);
23
+ }));
24
+ unsubs.push(listen(rootEl, 'pointermove', (e) => {
25
+ if (!opt.isEnabled())
26
+ return;
27
+ emit(protoRootBus, 'pointer.move', e);
28
+ }));
29
+ unsubs.push(listen(rootEl, 'pointerup', (e) => {
30
+ if (!opt.isEnabled())
31
+ return;
32
+ emit(protoRootBus, 'pointer.up', e);
33
+ }));
34
+ unsubs.push(listen(rootEl, 'pointercancel', (e) => {
35
+ if (!opt.isEnabled())
36
+ return;
37
+ emit(protoRootBus, 'pointer.cancel', e);
38
+ }));
39
+ unsubs.push(listen(rootEl, 'pointerenter', (e) => {
40
+ if (!opt.isEnabled())
41
+ return;
42
+ emit(protoRootBus, 'pointer.enter', e);
43
+ }));
44
+ unsubs.push(listen(rootEl, 'pointerleave', (e) => {
45
+ if (!opt.isEnabled())
46
+ return;
47
+ emit(protoRootBus, 'pointer.leave', e);
48
+ }));
49
+ // key -> key.* (global)
50
+ unsubs.push(listen(globalEl, 'keydown', (e) => {
51
+ if (!opt.isEnabled())
52
+ return;
53
+ emit(protoGlobalBus, 'key.down', e);
54
+ // minimal v0 mapping: activate keys => press.commit
55
+ if (e.key === 'Enter' || e.key === ' ') {
56
+ emit(protoRootBus, 'press.commit', e);
57
+ }
58
+ }));
59
+ unsubs.push(listen(globalEl, 'keyup', (e) => {
60
+ if (!opt.isEnabled())
61
+ return;
62
+ emit(protoGlobalBus, 'key.up', e);
63
+ }));
64
+ // click -> press.commit (root)
65
+ unsubs.push(listen(rootEl, 'click', (e) => {
66
+ if (!opt.isEnabled())
67
+ return;
68
+ emit(protoRootBus, 'press.commit', e);
69
+ }));
70
+ // contextmenu -> context.menu
71
+ unsubs.push(listen(rootEl, 'contextmenu', (e) => {
72
+ if (!opt.isEnabled())
73
+ return;
74
+ emit(protoRootBus, 'context.menu', e);
75
+ }));
76
+ // -------------------------
77
+ // (B) 懒绑定:native:* / host.*
78
+ // -------------------------
79
+ const rootProxy = createProxyTarget({
80
+ protoBus: protoRootBus,
81
+ nativeTarget: rootEl,
82
+ // hostTarget:先工作假设= nativeTarget;未来你可以换成更准确的 host 专用 target
83
+ hostTarget: rootEl,
84
+ isEnabled: opt.isEnabled,
85
+ // 注:rootProxy 不做“解释”,只做“路由 + gating”
86
+ });
87
+ const globalProxy = createProxyTarget({
88
+ protoBus: protoGlobalBus,
89
+ nativeTarget: globalEl,
90
+ hostTarget: globalEl,
91
+ isEnabled: opt.isEnabled,
92
+ });
93
+ return {
94
+ /** Inject these into EVENT_*_TARGET_CAP */
95
+ rootTarget: rootProxy,
96
+ globalTarget: globalProxy,
97
+ dispose() {
98
+ for (const u of unsubs.splice(0))
99
+ u();
100
+ rootProxy.__dispose?.();
101
+ globalProxy.__dispose?.();
102
+ },
103
+ };
104
+ }
105
+ function listen(t, type, cb) {
106
+ t.addEventListener(type, cb);
107
+ return () => t.removeEventListener(type, cb);
108
+ }
109
+ function createProxyTarget(args) {
110
+ // 记录已转发到 native/host 的监听器,便于 remove 时精确解绑
111
+ const nativeListeners = [];
112
+ const hostListeners = [];
113
+ // 为 native/host 分支加 gating:eventGate disable 后,这些也不应该再进 proto 回调
114
+ // 这点非常关键,否则“unmount 后还能触发回调”的竞态会回来。
115
+ function wrapWithGate(cb) {
116
+ return (ev) => {
117
+ if (!args.isEnabled())
118
+ return;
119
+ cb(ev);
120
+ };
121
+ }
122
+ function parseType(type) {
123
+ if (type.startsWith('native:')) {
124
+ return { kind: 'native', inner: type.slice('native:'.length) };
125
+ }
126
+ if (type.startsWith('host.')) {
127
+ return { kind: 'host', inner: type.slice('host.'.length) };
128
+ }
129
+ return { kind: 'proto', inner: type };
130
+ }
131
+ const api = {
132
+ addEventListener(type, cb, options) {
133
+ const p = parseType(String(type));
134
+ if (p.kind === 'proto') {
135
+ // proto semantic event: on proto bus
136
+ args.protoBus.addEventListener(p.inner, cb, options);
137
+ return;
138
+ }
139
+ if (p.kind === 'native') {
140
+ const wrapped = wrapWithGate(cb);
141
+ nativeListeners.push({ type: p.inner, cb, options, wrapped });
142
+ args.nativeTarget.addEventListener(p.inner, wrapped, options);
143
+ return;
144
+ }
145
+ // host.*
146
+ const wrapped = wrapWithGate(cb);
147
+ hostListeners.push({ type: p.inner, cb, options, wrapped });
148
+ args.hostTarget.addEventListener(p.inner, wrapped, options);
149
+ },
150
+ removeEventListener(type, cb, options) {
151
+ const p = parseType(String(type));
152
+ if (p.kind === 'proto') {
153
+ args.protoBus.removeEventListener(p.inner, cb, options);
154
+ return;
155
+ }
156
+ const list = p.kind === 'native' ? nativeListeners : hostListeners;
157
+ const target = p.kind === 'native' ? args.nativeTarget : args.hostTarget;
158
+ // latest-first removal aligns with your v0 matching习惯
159
+ for (let i = list.length - 1; i >= 0; i--) {
160
+ const r = list[i];
161
+ if (r.type !== p.inner)
162
+ continue;
163
+ if (r.cb !== cb)
164
+ continue;
165
+ // options 匹配这里先用 Object.is(和 DOM 一样复杂就复杂了)
166
+ // 你若坚持“plain object shallow compare”,可以把 sameOptions 搬进来
167
+ if (!Object.is(r.options, options))
168
+ continue;
169
+ target.removeEventListener(p.inner, r.wrapped, options);
170
+ list.splice(i, 1);
171
+ return;
172
+ }
173
+ },
174
+ dispatchEvent(ev) {
175
+ // 对外暴露的 dispatch:默认只派发到 protoBus
176
+ // (nativeTarget/hostTarget 的 dispatch 由真实 DOM 自己完成)
177
+ return args.protoBus.dispatchEvent(ev);
178
+ },
179
+ // best-effort cleanup (not required by EventTarget)
180
+ __dispose() {
181
+ // 主动解绑所有已懒绑定的 native/host listener,避免残留
182
+ for (const r of nativeListeners.splice(0)) {
183
+ args.nativeTarget.removeEventListener(r.type,
184
+ // @ts-ignore
185
+ r.wrapped, r.options);
186
+ }
187
+ for (const r of hostListeners.splice(0)) {
188
+ args.hostTarget.removeEventListener(r.type,
189
+ // @ts-ignore
190
+ r.wrapped, r.options);
191
+ }
192
+ },
193
+ };
194
+ return api;
195
+ }
@@ -0,0 +1,8 @@
1
+ export type EventGate = {
2
+ enable(): void;
3
+ disable(): void;
4
+ isEnabled(): boolean;
5
+ assertEnabled(hint?: string): void;
6
+ dispose(): void;
7
+ };
8
+ export declare function createEventGate(): EventGate;
@@ -0,0 +1,26 @@
1
+ export function createEventGate() {
2
+ let enabled = false;
3
+ let disposed = false;
4
+ return {
5
+ enable() {
6
+ if (disposed)
7
+ return;
8
+ enabled = true;
9
+ },
10
+ disable() {
11
+ enabled = false;
12
+ },
13
+ isEnabled() {
14
+ return enabled && !disposed;
15
+ },
16
+ assertEnabled(hint) {
17
+ if (!enabled || disposed) {
18
+ throw new Error(`[EventGate] event is not effective${hint ? `: ${hint}` : ''}`);
19
+ }
20
+ },
21
+ dispose() {
22
+ disposed = true;
23
+ enabled = false;
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,15 @@
1
+ import type { Prototype } from '@proto.ui/core';
2
+ import type { PropsBaseType } from '@proto.ui/types';
3
+ import { type RuntimeHost, type ExecuteWithHostResult } from '@proto.ui/runtime';
4
+ export type AdapterHostHooks<P extends PropsBaseType> = {
5
+ onRuntimeReady?: RuntimeHost<P>['onRuntimeReady'];
6
+ onUnmountBegin?: RuntimeHost<P>['onUnmountBegin'];
7
+ afterUnmount?: () => void;
8
+ };
9
+ export type AdapterHostInput<P extends PropsBaseType> = Pick<RuntimeHost<P>, 'commit' | 'schedule' | 'getRawProps'>;
10
+ export type AdapterHostSession<P extends PropsBaseType> = {
11
+ controller: ExecuteWithHostResult['controller'];
12
+ dispose(): void;
13
+ caps: ExecuteWithHostResult['caps'];
14
+ };
15
+ export declare function createAdapterHost<P extends PropsBaseType>(proto: Prototype<P>, host: AdapterHostInput<P>, hooks?: AdapterHostHooks<P>): AdapterHostSession<P>;
@@ -0,0 +1,23 @@
1
+ import { executeWithHost } from '@proto.ui/runtime';
2
+ import { createTeardown } from '../lifecycle/teardown';
3
+ export function createAdapterHost(proto, host, hooks = {}) {
4
+ const teardown = createTeardown();
5
+ const res = executeWithHost(proto, {
6
+ prototypeName: proto.name,
7
+ getRawProps: host.getRawProps,
8
+ commit: host.commit,
9
+ schedule: host.schedule,
10
+ onRuntimeReady: hooks.onRuntimeReady,
11
+ onUnmountBegin: hooks.onUnmountBegin,
12
+ });
13
+ return {
14
+ controller: res.controller,
15
+ caps: res.caps,
16
+ dispose() {
17
+ teardown.run(() => {
18
+ res.invokeUnmounted();
19
+ hooks.afterUnmount?.();
20
+ });
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,7 @@
1
+ export * from './wiring/host-wiring';
2
+ export * from './wiring/caps-builder';
3
+ export * from './gate/event-gate';
4
+ export * from './lifecycle/teardown';
5
+ export * from './host/adapter-host';
6
+ export * from './events/web-event-router';
7
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // packages/adapters/base/src/index.ts
2
+ export * from './wiring/host-wiring';
3
+ export * from './wiring/caps-builder';
4
+ export * from './gate/event-gate';
5
+ export * from './lifecycle/teardown';
6
+ export * from './host/adapter-host';
7
+ export * from './events/web-event-router';
8
+ export * from './types';
@@ -0,0 +1,4 @@
1
+ export declare function createTeardown(): {
2
+ run(fn: () => void): void;
3
+ isDone(): boolean;
4
+ };
@@ -0,0 +1,15 @@
1
+ // packages/adapters/base/src/lifecycle/teardown.ts
2
+ export function createTeardown() {
3
+ let done = false;
4
+ return {
5
+ run(fn) {
6
+ if (done)
7
+ return;
8
+ done = true;
9
+ fn();
10
+ },
11
+ isDone() {
12
+ return done;
13
+ },
14
+ };
15
+ }
@@ -0,0 +1,15 @@
1
+ import { CapEntries } from '@proto.ui/core';
2
+ import type { ModuleWiring } from '@proto.ui/runtime';
3
+ export type ModuleName = string;
4
+ export type WiringSpec = Record<ModuleName, (init: {
5
+ prototypeName: string;
6
+ }) => CapEntries>;
7
+ export type HostWiring = {
8
+ onRuntimeReady(wiring: ModuleWiring): void;
9
+ onUnmountBegin?(): void;
10
+ /**
11
+ * Called AFTER invokeUnmounted() completed (modules are disposed),
12
+ * for adapter-owned cleanup only.
13
+ */
14
+ afterUnmount(): void;
15
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import type { CapEntries } from '@proto.ui/core';
2
+ import type { WiringSpec } from '../types';
3
+ import { type RawPropsSource } from '@proto.ui/module-props';
4
+ import { type EventEmitSink, type EventTargetGetter } from '@proto.ui/module-event';
5
+ import { type ExposeHostSink } from '@proto.ui/module-expose';
6
+ import { type ExposeStateWebMode, type ExposeStateWebNameMap } from '@proto.ui/module-expose-state-web';
7
+ import { type AnatomyInstanceToken, type AnatomyParentGetter, type AnatomyPrototypeGetter } from '@proto.ui/module-anatomy';
8
+ import { type ContextParentGetter, type ContextInstanceToken } from '@proto.ui/module-context';
9
+ import { type AsTriggerInstanceToken, type AsTriggerParentGetter, type AsTriggerPrototypeGetter } from '@proto.ui/module-as-trigger';
10
+ import { type RuleMetaGetter } from '@proto.ui/module-rule-meta';
11
+ import { type RuleExposeStateWebNativeVariantPolicy } from '@proto.ui/module-rule-expose-state-web';
12
+ import { type FocusBlur, type FocusInstanceToken, type FocusIsNativelyFocusable, type FocusParentGetter, type FocusRequestFocus, type FocusRootTargetGetter, type FocusSetFocusable } from '@proto.ui/module-focus';
13
+ import type { EffectsPort } from '@proto.ui/core';
14
+ import type { PropsBaseType } from '@proto.ui/types';
15
+ export type CapsWiringBuilder = {
16
+ add(moduleName: string, provide: () => CapEntries): CapsWiringBuilder;
17
+ useProps<P extends PropsBaseType>(source: RawPropsSource<P>): CapsWiringBuilder;
18
+ useFeedback(effects: EffectsPort): CapsWiringBuilder;
19
+ useEventTargets(args: {
20
+ root: EventTargetGetter;
21
+ global: EventTargetGetter;
22
+ emit?: EventEmitSink;
23
+ }): CapsWiringBuilder;
24
+ useExposeState(setExposes: ExposeHostSink): CapsWiringBuilder;
25
+ useExposeStateWeb(args: {
26
+ host: HTMLElement;
27
+ nameMap: ExposeStateWebNameMap;
28
+ mode?: ExposeStateWebMode;
29
+ }): CapsWiringBuilder;
30
+ useContext(args: {
31
+ instance: ContextInstanceToken;
32
+ parent: ContextParentGetter;
33
+ }): CapsWiringBuilder;
34
+ useAnatomy(args: {
35
+ instance: AnatomyInstanceToken;
36
+ parent: AnatomyParentGetter;
37
+ getPrototype: AnatomyPrototypeGetter;
38
+ }): CapsWiringBuilder;
39
+ useAsTrigger(args: {
40
+ instance: AsTriggerInstanceToken;
41
+ parent: AsTriggerParentGetter;
42
+ getPrototype: AsTriggerPrototypeGetter;
43
+ }): CapsWiringBuilder;
44
+ useFocus(args: {
45
+ instance: FocusInstanceToken;
46
+ parent: FocusParentGetter;
47
+ root: FocusRootTargetGetter;
48
+ isNativelyFocusable: FocusIsNativelyFocusable;
49
+ setFocusable: FocusSetFocusable;
50
+ requestFocus: FocusRequestFocus;
51
+ blur: FocusBlur;
52
+ }): CapsWiringBuilder;
53
+ useRuleMeta(getMeta: RuleMetaGetter): CapsWiringBuilder;
54
+ useRuleExposeStateWeb(args: {
55
+ nativeVariantPolicy: RuleExposeStateWebNativeVariantPolicy;
56
+ }): CapsWiringBuilder;
57
+ build(): WiringSpec;
58
+ };
59
+ export declare function createCapsWiring(): CapsWiringBuilder;
@@ -0,0 +1,87 @@
1
+ import { RAW_PROPS_SOURCE_CAP } from '@proto.ui/module-props';
2
+ import { EFFECTS_CAP } from '@proto.ui/module-feedback';
3
+ import { EVENT_GLOBAL_TARGET_CAP, EVENT_EMIT_CAP, EVENT_ROOT_TARGET_CAP, } from '@proto.ui/module-event';
4
+ import { EXPOSE_SET_EXPOSES_CAP } from '@proto.ui/module-expose';
5
+ import { EXPOSE_STATE_WEB_MAP_CAP, EXPOSE_STATE_WEB_MODE_CAP, HOST_ELEMENT_CAP, } from '@proto.ui/module-expose-state-web';
6
+ import { ANATOMY_GET_PROTO_CAP, ANATOMY_INSTANCE_TOKEN_CAP, ANATOMY_PARENT_CAP, } from '@proto.ui/module-anatomy';
7
+ import { CONTEXT_INSTANCE_TOKEN_CAP, CONTEXT_PARENT_CAP, } from '@proto.ui/module-context';
8
+ import { AS_TRIGGER_GET_PROTO_CAP, AS_TRIGGER_INSTANCE_CAP, AS_TRIGGER_PARENT_CAP, } from '@proto.ui/module-as-trigger';
9
+ import { RULE_META_GET_CAP } from '@proto.ui/module-rule-meta';
10
+ import { RULE_EXPOSE_STATE_WEB_NATIVE_VARIANT_POLICY_CAP, } from '@proto.ui/module-rule-expose-state-web';
11
+ import { FOCUS_BLUR_CAP, FOCUS_INSTANCE_TOKEN_CAP, FOCUS_IS_NATIVELY_FOCUSABLE_CAP, FOCUS_PARENT_CAP, FOCUS_REQUEST_FOCUS_CAP, FOCUS_ROOT_TARGET_CAP, FOCUS_SET_FOCUSABLE_CAP, } from '@proto.ui/module-focus';
12
+ export function createCapsWiring() {
13
+ const modules = {};
14
+ const add = (moduleName, provide) => {
15
+ modules[moduleName] = provide;
16
+ return api;
17
+ };
18
+ const api = {
19
+ add,
20
+ useProps(source) {
21
+ return add('props', () => [[RAW_PROPS_SOURCE_CAP, source]]);
22
+ },
23
+ useFeedback(effects) {
24
+ return add('feedback', () => [[EFFECTS_CAP, effects]]);
25
+ },
26
+ useEventTargets({ root, global, emit }) {
27
+ return add('event', () => [
28
+ [EVENT_ROOT_TARGET_CAP, root],
29
+ [EVENT_GLOBAL_TARGET_CAP, global],
30
+ ...(emit ? [[EVENT_EMIT_CAP, emit]] : []),
31
+ ]);
32
+ },
33
+ useExposeState(setExposes) {
34
+ return add('expose-state', () => [[EXPOSE_SET_EXPOSES_CAP, setExposes]]);
35
+ },
36
+ useExposeStateWeb({ host, nameMap, mode }) {
37
+ return add('expose-state-web', () => [
38
+ [HOST_ELEMENT_CAP, host],
39
+ [EXPOSE_STATE_WEB_MAP_CAP, nameMap],
40
+ ...(mode ? [[EXPOSE_STATE_WEB_MODE_CAP, mode]] : []),
41
+ ]);
42
+ },
43
+ useContext({ instance, parent }) {
44
+ return add('context', () => [
45
+ [CONTEXT_INSTANCE_TOKEN_CAP, instance],
46
+ [CONTEXT_PARENT_CAP, parent],
47
+ ]);
48
+ },
49
+ useAnatomy({ instance, parent, getPrototype }) {
50
+ return add('anatomy', () => [
51
+ [ANATOMY_INSTANCE_TOKEN_CAP, instance],
52
+ [ANATOMY_PARENT_CAP, parent],
53
+ [ANATOMY_GET_PROTO_CAP, getPrototype],
54
+ ]);
55
+ },
56
+ useAsTrigger({ instance, parent, getPrototype }) {
57
+ return add('as-trigger', () => [
58
+ [AS_TRIGGER_INSTANCE_CAP, instance],
59
+ [AS_TRIGGER_PARENT_CAP, parent],
60
+ [AS_TRIGGER_GET_PROTO_CAP, getPrototype],
61
+ ]);
62
+ },
63
+ useFocus({ instance, parent, root, isNativelyFocusable, setFocusable, requestFocus, blur }) {
64
+ return add('focus', () => [
65
+ [FOCUS_INSTANCE_TOKEN_CAP, instance],
66
+ [FOCUS_PARENT_CAP, parent],
67
+ [FOCUS_ROOT_TARGET_CAP, root],
68
+ [FOCUS_IS_NATIVELY_FOCUSABLE_CAP, isNativelyFocusable],
69
+ [FOCUS_SET_FOCUSABLE_CAP, setFocusable],
70
+ [FOCUS_REQUEST_FOCUS_CAP, requestFocus],
71
+ [FOCUS_BLUR_CAP, blur],
72
+ ]);
73
+ },
74
+ useRuleMeta(getMeta) {
75
+ return add('rule-meta', () => [[RULE_META_GET_CAP, getMeta]]);
76
+ },
77
+ useRuleExposeStateWeb({ nativeVariantPolicy }) {
78
+ return add('rule-expose-state-web', () => [
79
+ [RULE_EXPOSE_STATE_WEB_NATIVE_VARIANT_POLICY_CAP, nativeVariantPolicy],
80
+ ]);
81
+ },
82
+ build() {
83
+ return modules;
84
+ },
85
+ };
86
+ return api;
87
+ }
@@ -0,0 +1,5 @@
1
+ import type { HostWiring, WiringSpec } from '../types';
2
+ export declare function createHostWiring(args: {
3
+ prototypeName: string;
4
+ modules: WiringSpec;
5
+ }): HostWiring;
@@ -0,0 +1,30 @@
1
+ export function createHostWiring(args) {
2
+ const { prototypeName, modules } = args;
3
+ const wired = new Set();
4
+ let wiringApi = null;
5
+ return {
6
+ onRuntimeReady(wiring) {
7
+ wiringApi = wiring;
8
+ for (const [name, provide] of Object.entries(modules)) {
9
+ const entries = provide({ prototypeName });
10
+ const ok = wiring.attach(name, entries);
11
+ if (ok)
12
+ wired.add(name);
13
+ }
14
+ },
15
+ afterUnmount() {
16
+ if (!wiringApi)
17
+ return;
18
+ for (const name of wired) {
19
+ try {
20
+ wiringApi.reset(name);
21
+ }
22
+ catch {
23
+ // ignore v0
24
+ }
25
+ }
26
+ wired.clear();
27
+ wiringApi = null;
28
+ },
29
+ };
30
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@proto.ui/adapter-base",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@proto.ui/core": "0.0.1",
7
+ "@proto.ui/runtime": "0.0.1",
8
+ "@proto.ui/types": "0.0.1",
9
+ "@proto.ui/module-props": "0.0.1",
10
+ "@proto.ui/module-feedback": "0.0.1",
11
+ "@proto.ui/module-event": "0.0.1",
12
+ "@proto.ui/module-expose": "0.0.1",
13
+ "@proto.ui/module-expose-state-web": "0.0.1",
14
+ "@proto.ui/module-rule-expose-state-web": "0.0.1",
15
+ "@proto.ui/module-rule-meta": "0.0.1",
16
+ "@proto.ui/module-context": "0.0.1",
17
+ "@proto.ui/module-anatomy": "0.0.1",
18
+ "@proto.ui/module-as-trigger": "0.0.1",
19
+ "@proto.ui/module-focus": "0.0.1"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "description": "Base package for building Proto UI adapters.",
28
+ "license": "MIT",
29
+ "homepage": "https://github.com/guangliang2019/Prototype-UI/tree/main/packages/adapters/base",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/guangliang2019/Prototype-UI.git",
33
+ "directory": "packages/adapters/base"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/guangliang2019/Prototype-UI/issues"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "keywords": [
42
+ "proto-ui",
43
+ "proto",
44
+ "ui",
45
+ "adapter",
46
+ "base",
47
+ "custom-adapter-hosts"
48
+ ],
49
+ "files": [
50
+ "dist",
51
+ "README.md",
52
+ "LICENSE"
53
+ ]
54
+ }