@saga-a2ui/lit 0.0.0-stage → 0.7.7-rc.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.
@@ -0,0 +1,118 @@
1
+ import { requestLinkOpen } from '@saga-a2ui/components/faithful-card';
2
+ import {
3
+ describeLinkOutcome,
4
+ type LinkOpenOutcome,
5
+ } from '@saga-a2ui/core/host-links';
6
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
7
+ import { requestContentView, type ContentView } from './content-view.js';
8
+
9
+ /** What the host answered for a link, with the sentence a reader sees (empty when it opened). */
10
+ export interface HostLinkOutcome {
11
+ readonly kind: LinkOpenOutcome;
12
+ readonly message: string;
13
+ }
14
+
15
+ export interface HostLinkBinding {
16
+ /** Whether the enclosing view's policy lets this link be activated. False outside a view. */
17
+ readonly allowed: boolean;
18
+ /**
19
+ * Asks the host to open the link, if the policy still allows it: exactly one host request per
20
+ * call, and none when it does not. Resolves once the answer has been shown or dropped.
21
+ */
22
+ open(): Promise<void>;
23
+ /** The host's answer to the latest activation, until the binding is released or the view's selection is replaced. */
24
+ readonly outcome: HostLinkOutcome | undefined;
25
+ /** Ends the binding: a late answer is dropped and the element no longer updates for it. Idempotent. */
26
+ release(): void;
27
+ }
28
+
29
+ /** The binding as a Lit controller: it follows its element's connection and asks it to re-render. */
30
+ class HostLinkController implements HostLinkBinding, ReactiveController {
31
+ private generation = 0;
32
+ private answer: HostLinkOutcome | undefined;
33
+ private watching: { unsubscribe(): void } | undefined;
34
+ private released = false;
35
+
36
+ constructor(
37
+ private readonly element: ReactiveControllerHost & HTMLElement,
38
+ private readonly url: string,
39
+ ) {
40
+ element.addController(this);
41
+ this.watch();
42
+ }
43
+
44
+ get allowed(): boolean {
45
+ return this.view()?.policy.decideLink(this.url).allowed === true;
46
+ }
47
+
48
+ get outcome(): HostLinkOutcome | undefined {
49
+ return this.answer;
50
+ }
51
+
52
+ hostConnected(): void {
53
+ this.watch();
54
+ }
55
+
56
+ hostDisconnected(): void {
57
+ this.retire();
58
+ this.watching?.unsubscribe();
59
+ this.watching = undefined;
60
+ }
61
+
62
+ async open(): Promise<void> {
63
+ const view = this.view();
64
+ if (view === undefined) return;
65
+ const decision = view.policy.decideLink(this.url);
66
+ if (!decision.allowed) return;
67
+ const asked = this.generation;
68
+ const kind = await requestLinkOpen(view.hostLinks, decision.url);
69
+ // The answer is for the link as it stood when asked: not after release, removal or a new selection.
70
+ if (this.generation !== asked) return;
71
+ this.answer = { kind, message: describeLinkOutcome(kind, decision.url) };
72
+ this.element.requestUpdate();
73
+ }
74
+
75
+ release(): void {
76
+ if (this.released) return;
77
+ this.released = true;
78
+ this.hostDisconnected();
79
+ this.element.removeController(this);
80
+ }
81
+
82
+ /** The view of the element's place in the document, or none while it is detached or released. */
83
+ private view(): ContentView | undefined {
84
+ return this.released || !this.element.isConnected
85
+ ? undefined
86
+ : requestContentView(this.element);
87
+ }
88
+
89
+ /** A new selection in the view (it ended its image consent) replaces whatever this link said. */
90
+ private watch(): void {
91
+ if (this.watching !== undefined) return;
92
+ this.watching = this.view()?.consent.subscribe(() => {
93
+ this.retire();
94
+ this.element.requestUpdate();
95
+ });
96
+ }
97
+
98
+ private retire(): void {
99
+ this.generation += 1;
100
+ this.answer = undefined;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * The card's own link rules for a link an application renders itself, for example in its chrome.
106
+ * `element` is the Lit element that renders the link; it must be inside the element that provides
107
+ * the card's view (`provideContentView`). The view's policy decides, at activation; its host links
108
+ * are asked; the outcome is worded by `describeLinkOutcome` and `element` re-renders when it
109
+ * arrives. An answer is shown only while the link it was asked about stands: `release()`, the
110
+ * element's removal and a new selection (the view ending its consent) each drop it. Outside a view
111
+ * nothing is allowed and nothing throws. Create a new binding for a different `url`.
112
+ */
113
+ export function bindHostLink(
114
+ element: ReactiveControllerHost & HTMLElement,
115
+ url: string,
116
+ ): HostLinkBinding {
117
+ return new HostLinkController(element, url);
118
+ }
@@ -0,0 +1,17 @@
1
+ export { faithfulCardEntry, FaithfulCardElement } from './catalog.js';
2
+ export { registerFaithfulCard } from './register.js';
3
+ export { FaithfulCardContentElement } from './faithful-card-element.js';
4
+ export { openFaithfulCardSurface, type CardSurface } from './card-surface.js';
5
+ export {
6
+ bindHostLink,
7
+ type HostLinkBinding,
8
+ type HostLinkOutcome,
9
+ } from './host-link.js';
10
+ export {
11
+ CONTENT_VIEW_REQUEST,
12
+ ContentViewRequest,
13
+ createFaithfulCardView,
14
+ provideContentView,
15
+ requestContentView,
16
+ type ContentView,
17
+ } from './content-view.js';
@@ -0,0 +1,30 @@
1
+ import {
2
+ defineElements,
3
+ type ElementRegistry,
4
+ } from '../element-registration.js';
5
+ import {
6
+ CARD_SURFACE_TAG,
7
+ FaithfulCardSurfaceElement,
8
+ } from './card-surface.js';
9
+ import { faithfulCardEntry } from './catalog.js';
10
+ import { FaithfulCardContentElement } from './faithful-card-element.js';
11
+
12
+ /**
13
+ * Explicit setup: defines the catalog element, the card element it renders and the card surface
14
+ * element. Call it once at startup. Repeating it is harmless; a tag already bound to a different
15
+ * constructor fails with `ElementConflictError` and nothing from the failing call is defined.
16
+ * Importing this module registers nothing.
17
+ */
18
+ export function registerFaithfulCard(registry?: ElementRegistry): void {
19
+ defineElements(
20
+ [
21
+ faithfulCardEntry,
22
+ {
23
+ tagName: 'saga-faithful-card-content',
24
+ element: FaithfulCardContentElement,
25
+ },
26
+ { tagName: CARD_SURFACE_TAG, element: FaithfulCardSurfaceElement },
27
+ ],
28
+ registry,
29
+ );
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ export {
2
+ createA2uiAppElement,
3
+ type A2uiActions,
4
+ type A2uiAppDependencies,
5
+ type A2uiSession,
6
+ type A2uiSurfaceMount,
7
+ type A2uiUpdateFailure,
8
+ type A2uiUpdateResult,
9
+ } from './a2ui/app-element.js';
10
+ export {
11
+ defineElements,
12
+ ElementConflictError,
13
+ type ElementDefinition,
14
+ type ElementRegistry,
15
+ } from './element-registration.js';
16
+ // The catalog an application builds: its own components over upstream's component API, and the
17
+ // catalog value and schemas it declares them with. These are upstream A2UI v0.9 APIs, re-exported
18
+ // so an application that installs a stack names no upstream package for them.
19
+ export {
20
+ A2uiLitElement,
21
+ basicCatalog,
22
+ type LitComponentApi,
23
+ } from '@a2ui/lit/v0_9';
24
+ export { Catalog, CommonSchemas } from '@a2ui/web_core/v0_9';