@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.
- package/LICENSE +202 -0
- package/README.md +10 -2
- package/package.json +1 -6
- package/src/a2ui/app-element.ts +238 -0
- package/src/actions/action-button.ts +130 -0
- package/src/actions/index.ts +12 -0
- package/src/actions/lit-actions.ts +70 -0
- package/src/actions/mount-owner.ts +42 -0
- package/src/element-registration.ts +48 -0
- package/src/faithful-card/adopt-styles.ts +61 -0
- package/src/faithful-card/blocked-fragment.ts +58 -0
- package/src/faithful-card/card-surface.ts +181 -0
- package/src/faithful-card/catalog.ts +29 -0
- package/src/faithful-card/content-view.ts +102 -0
- package/src/faithful-card/faithful-card-element.ts +746 -0
- package/src/faithful-card/host-link.ts +118 -0
- package/src/faithful-card/index.ts +17 -0
- package/src/faithful-card/register.ts +30 -0
- package/src/index.ts +24 -0
|
@@ -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';
|