@pie-players/pie-assessment-toolkit 0.3.66 → 0.3.68
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 +1 -1
- package/dist/attempt/AssessmentSession.d.ts +7 -27
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-38mhtjsq.js +51 -0
- package/dist/components/chunks/ItemToolBar-9ymm7pd1.js +46 -0
- package/dist/components/item-toolbar-element.js +1 -0
- package/dist/components/pie-assessment-toolkit-element.js +1 -0
- package/dist/components/section-toolbar-element.js +1 -0
- package/dist/context/assessment-toolkit-context.d.ts +28 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +5 -2
- package/dist/policy/core/ToolPolicyEngine.d.ts +12 -0
- package/dist/policy/core/ToolPolicyEngine.js +28 -2
- package/dist/policy/core/feature-decision.d.ts +28 -2
- package/dist/policy/core/feature-decision.js +35 -0
- package/dist/policy/engine.d.ts +1 -1
- package/dist/policy/engine.js +1 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +56 -0
- package/dist/runtime/SectionRuntimeEngine.js +66 -1
- package/dist/runtime/core/engine-resolver.d.ts +25 -1
- package/dist/runtime/registration-events.d.ts +52 -0
- package/dist/runtime/registration-events.js +2 -0
- package/dist/services/AccessibilityCatalogResolver.js +21 -5
- package/dist/services/I18nService.d.ts +28 -100
- package/dist/services/I18nService.js +43 -233
- package/dist/services/TTSService.d.ts +12 -0
- package/dist/services/TTSService.js +18 -17
- package/dist/services/ToolRegistry.d.ts +86 -2
- package/dist/services/ToolRegistry.js +30 -0
- package/dist/services/ToolkitCoordinator.d.ts +39 -37
- package/dist/services/ToolkitCoordinator.js +40 -0
- package/dist/services/audio-handoff.d.ts +39 -0
- package/dist/services/audio-handoff.js +58 -0
- package/dist/services/catalog-media.d.ts +35 -4
- package/dist/services/catalog-media.js +92 -3
- package/dist/services/framework-error.d.ts +15 -1
- package/dist/services/interfaces.d.ts +28 -0
- package/dist/services/pnp-standard-features.d.ts +1 -1
- package/dist/services/section-controller-types.d.ts +218 -7
- package/dist/services/selection-action.d.ts +49 -0
- package/dist/services/selection-action.js +10 -0
- package/dist/services/spoken-audio-cards.js +5 -1
- package/dist/services/tool-context.d.ts +6 -5
- package/dist/services/tool-context.js +197 -152
- package/dist/services/tool-icons.d.ts +18 -0
- package/dist/services/tool-icons.js +31 -0
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +6 -5
- package/dist/services/tool-request.d.ts +106 -0
- package/dist/services/tool-request.js +127 -0
- package/dist/services/toolbar-items.d.ts +6 -0
- package/dist/tools/client.d.ts +0 -1
- package/dist/tools/client.js +0 -2
- package/dist/tools/content-capability-resolution.d.ts +106 -0
- package/dist/tools/content-capability-resolution.js +136 -0
- package/dist/tools/internal.d.ts +8 -0
- package/dist/tools/internal.js +8 -0
- package/dist/tools/tool-surface-host.d.ts +57 -0
- package/dist/tools/tool-surface-host.js +610 -0
- package/package.json +14 -10
- package/dist/components/chunks/ItemToolBar-8jgdz50p.js +0 -51
- package/dist/components/chunks/ItemToolBar-cvs646j3.js +0 -36
- package/dist/tools/calculators/desmos-provider.d.ts +0 -46
- package/dist/tools/calculators/desmos-provider.js +0 -393
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool open requests.
|
|
3
|
+
*
|
|
4
|
+
* A surface that acts on the learner's current selection has to hand that selection
|
|
5
|
+
* to a tool it does not mount. The annotation strip is the case PIE ships: it is a
|
|
6
|
+
* section-scoped singleton in its own shadow root, and the tool it opens is mounted
|
|
7
|
+
* by a toolbar under a scoped instance id the strip cannot construct.
|
|
8
|
+
*
|
|
9
|
+
* Resolution is a claim, not a broadcast. Each toolbar registers as the target for its
|
|
10
|
+
* placement level, and a request reaches exactly one: the first target that currently
|
|
11
|
+
* hosts the tool, preferring section scope. A broadcast would open a panel in every
|
|
12
|
+
* toolbar whose scope contains the selection, which in a section player is the item
|
|
13
|
+
* card's toolbar and the section's both.
|
|
14
|
+
*
|
|
15
|
+
* `params` reaches the tool through the same seam a host-registered context resolver
|
|
16
|
+
* feeds, so receiving a request costs a tool nothing: whatever already reads
|
|
17
|
+
* `getToolRenderParams` sees it.
|
|
18
|
+
*
|
|
19
|
+
* Core names no capability. The requester supplies the tool id, which is why the
|
|
20
|
+
* pairing of a selection action to a dictionary lives in the composition layer.
|
|
21
|
+
*/
|
|
22
|
+
/** Level a request resolves against when the requester names none. */
|
|
23
|
+
export const DEFAULT_TOOL_REQUEST_LEVEL = "section";
|
|
24
|
+
/**
|
|
25
|
+
* Registry of the toolbars a request can reach.
|
|
26
|
+
*
|
|
27
|
+
* Insertion order is the tie-break within a level, so a target registered while an
|
|
28
|
+
* earlier one is still mounted does not displace it.
|
|
29
|
+
*/
|
|
30
|
+
export class ToolRequestRegistry {
|
|
31
|
+
targets = new Set();
|
|
32
|
+
changeListeners = new Set();
|
|
33
|
+
registerTarget(target) {
|
|
34
|
+
this.targets.add(target);
|
|
35
|
+
this.notifyChange();
|
|
36
|
+
return () => {
|
|
37
|
+
if (!this.targets.delete(target))
|
|
38
|
+
return;
|
|
39
|
+
this.notifyChange();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a request for this tool would reach a toolbar.
|
|
44
|
+
*
|
|
45
|
+
* A surface asks before offering the affordance: a button that silently does
|
|
46
|
+
* nothing is worse than an absent one, and availability moves with policy —
|
|
47
|
+
* hence {@link onTargetsChange}.
|
|
48
|
+
*/
|
|
49
|
+
canRequest(toolId, level) {
|
|
50
|
+
return this.findTarget(toolId, level) !== null;
|
|
51
|
+
}
|
|
52
|
+
/** Returns whether a target claimed the request. */
|
|
53
|
+
request(request) {
|
|
54
|
+
const target = this.findTarget(request.toolId, request.level);
|
|
55
|
+
if (!target)
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
target.open(request.toolId, request.params);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`[ToolRequestRegistry] Target failed to open "${request.toolId}":`, error);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fires when a toolbar registers or unregisters.
|
|
68
|
+
*
|
|
69
|
+
* Not when a registered toolbar's own visible set changes: `hostsTool` is read
|
|
70
|
+
* live, so a caller re-asking `canRequest` gets the current answer. A surface
|
|
71
|
+
* that needs to notice a policy change should also follow the policy signal it
|
|
72
|
+
* already has.
|
|
73
|
+
*/
|
|
74
|
+
onTargetsChange(listener) {
|
|
75
|
+
this.changeListeners.add(listener);
|
|
76
|
+
return () => {
|
|
77
|
+
this.changeListeners.delete(listener);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* An explicit level is a constraint; the default is a preference.
|
|
82
|
+
*
|
|
83
|
+
* Falling back off `"section"` is what lets a host place a tool at item scope only
|
|
84
|
+
* and still have a section-scoped gateway reach it. Requesting a level explicitly
|
|
85
|
+
* does not fall back, because a requester that named one meant it.
|
|
86
|
+
*/
|
|
87
|
+
findTarget(toolId, level) {
|
|
88
|
+
const preferred = this.findTargetAtLevel(toolId, level ?? DEFAULT_TOOL_REQUEST_LEVEL);
|
|
89
|
+
if (preferred || level !== undefined)
|
|
90
|
+
return preferred;
|
|
91
|
+
for (const target of this.targets) {
|
|
92
|
+
if (target.level === DEFAULT_TOOL_REQUEST_LEVEL)
|
|
93
|
+
continue;
|
|
94
|
+
if (this.hostsTool(target, toolId))
|
|
95
|
+
return target;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
findTargetAtLevel(toolId, level) {
|
|
100
|
+
for (const target of this.targets) {
|
|
101
|
+
if (target.level !== level)
|
|
102
|
+
continue;
|
|
103
|
+
if (this.hostsTool(target, toolId))
|
|
104
|
+
return target;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
hostsTool(target, toolId) {
|
|
109
|
+
try {
|
|
110
|
+
return target.hostsTool(toolId) === true;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.warn(`[ToolRequestRegistry] Target at level "${target.level}" failed the host check for "${toolId}":`, error);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
notifyChange() {
|
|
118
|
+
for (const listener of this.changeListeners) {
|
|
119
|
+
try {
|
|
120
|
+
listener();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.warn("[ToolRequestRegistry] Target change listener threw:", error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -3,6 +3,12 @@ export interface ToolbarItemBase {
|
|
|
3
3
|
label: string;
|
|
4
4
|
ariaLabel?: string;
|
|
5
5
|
icon?: string;
|
|
6
|
+
/**
|
|
7
|
+
* FontAwesome icon name, opting this item into `<nds-icon-button>` rendering
|
|
8
|
+
* where the host enables NDS icons. Set by a tool registration's button
|
|
9
|
+
* definition, or by a host contributing its own button.
|
|
10
|
+
*/
|
|
11
|
+
faIconName?: string;
|
|
6
12
|
tooltip?: string;
|
|
7
13
|
active?: boolean;
|
|
8
14
|
disabled?: boolean;
|
package/dist/tools/client.d.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* '@pie-players/pie-assessment-toolkit' to ensure
|
|
9
9
|
* they don't accidentally pull in server-side dependencies.
|
|
10
10
|
*/
|
|
11
|
-
export { DesmosCalculatorProvider } from "./calculators/desmos-provider.js";
|
|
12
11
|
export { COMMON_LIBRARIES, LibraryLoaderImpl, libraryLoader, } from "./library-loader.js";
|
|
13
12
|
export { ResponseDiscoveryServiceImpl, responseDiscovery, } from "./response-discovery.js";
|
|
14
13
|
export * from "./types.js";
|
package/dist/tools/client.js
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* '@pie-players/pie-assessment-toolkit' to ensure
|
|
9
9
|
* they don't accidentally pull in server-side dependencies.
|
|
10
10
|
*/
|
|
11
|
-
// Calculator providers (client-safe, have SSR guards)
|
|
12
|
-
export { DesmosCalculatorProvider } from "./calculators/desmos-provider.js";
|
|
13
11
|
// Library loader (client-safe, has SSR guards)
|
|
14
12
|
export { COMMON_LIBRARIES, LibraryLoaderImpl, libraryLoader, } from "./library-loader.js";
|
|
15
13
|
// Response discovery (client-safe, browser-only)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a content capability has anything to show, for one entity and one
|
|
3
|
+
* profile.
|
|
4
|
+
*
|
|
5
|
+
* Two independent halves, and a capability is in play only when both answer yes:
|
|
6
|
+
* policy granted one of its support ids, and its own `requiresAuthoredContent`
|
|
7
|
+
* found the resource in this entity's catalogs. Neither implies the other — a
|
|
8
|
+
* learner with an accommodation still sees nothing on an item carrying no
|
|
9
|
+
* resource, and an item carrying one shows nothing to a learner without the
|
|
10
|
+
* grant.
|
|
11
|
+
*
|
|
12
|
+
* Policy answers in three states rather than two, because a host gate is not the
|
|
13
|
+
* absence of a grant: `resolvesWithoutGrant` lets a capability answer from the
|
|
14
|
+
* content when nobody was granted anything, and a host that switched the
|
|
15
|
+
* capability off must not be read as nobody having spoken. Resolution order is
|
|
16
|
+
* denial, then grant, then the content exception.
|
|
17
|
+
*
|
|
18
|
+
* It lives here, data-only and DOM-free, because two renderers ask it. The
|
|
19
|
+
* section player asks continuously: policy and catalogs both change under a
|
|
20
|
+
* mounted card, and it re-resolves to reconcile what is on screen. Print asks
|
|
21
|
+
* once — one learner, one profile, decided before the page exists, with nothing
|
|
22
|
+
* to toggle. A second implementation for the one-shot case would be two
|
|
23
|
+
* renderers disagreeing about the same card, which is the failure this exists to
|
|
24
|
+
* prevent.
|
|
25
|
+
*
|
|
26
|
+
* Capability-neutral, and gated as such: nothing here names a support id, a
|
|
27
|
+
* catalog type, or a surface. The caller passes the registrations and the slot,
|
|
28
|
+
* a capability interprets its own cards, and the resolution owns only the
|
|
29
|
+
* two-halves rule and its one documented exception.
|
|
30
|
+
*/
|
|
31
|
+
import type { ToolRegistration } from "../services/ToolRegistry.js";
|
|
32
|
+
import type { CatalogOwnerSnapshot } from "../services/AccessibilityCatalogResolver.js";
|
|
33
|
+
/** What policy answered about one feature id. */
|
|
34
|
+
export type ContentCapabilityPolicy = {
|
|
35
|
+
outcome: "granted";
|
|
36
|
+
/** The support id that was granted — a capability may declare several. */
|
|
37
|
+
featureId: string;
|
|
38
|
+
/** Feature parameters carried by the decision, if any. */
|
|
39
|
+
parameters?: unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* No source granted it and none denied it. A capability declaring
|
|
43
|
+
* {@link ToolRegistration.resolvesWithoutGrant} may still answer from the
|
|
44
|
+
* content: silence is what an authored-presentation alternate looks like,
|
|
45
|
+
* since no profile speaks for one either way.
|
|
46
|
+
*/
|
|
47
|
+
| {
|
|
48
|
+
outcome: "silent";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A host gate denied it — the off switch, not the absence of a grant. It
|
|
52
|
+
* outranks `resolvesWithoutGrant`, because a host saying a capability has no
|
|
53
|
+
* place in this delivery is a statement authored content cannot overrule.
|
|
54
|
+
*/
|
|
55
|
+
| {
|
|
56
|
+
outcome: "denied";
|
|
57
|
+
};
|
|
58
|
+
/** Which half of a capability's resolution failed. */
|
|
59
|
+
export type ContentCapabilityPhase = "policy" | "content";
|
|
60
|
+
/** A capability that is in play, and everything its renderer needs to mount it. */
|
|
61
|
+
export interface ResolvedContentCapability {
|
|
62
|
+
registration: ToolRegistration;
|
|
63
|
+
/**
|
|
64
|
+
* The granted support id, or `""` when the capability resolved without a
|
|
65
|
+
* grant. Passed through to the render context unchanged, so a capability that
|
|
66
|
+
* serves both an authored-presentation case and an accommodation can still
|
|
67
|
+
* tell them apart at render time.
|
|
68
|
+
*/
|
|
69
|
+
featureId: string;
|
|
70
|
+
parameters?: unknown;
|
|
71
|
+
/** Whatever the capability's own `resolve` returned; never inspected here. */
|
|
72
|
+
content: unknown;
|
|
73
|
+
}
|
|
74
|
+
export interface ResolveContentCapabilitiesArgs {
|
|
75
|
+
/**
|
|
76
|
+
* The capabilities to consider. A caller with a registry passes
|
|
77
|
+
* `getToolsBySurface(surface)`, which is what keeps a renderer from naming
|
|
78
|
+
* one.
|
|
79
|
+
*/
|
|
80
|
+
registrations: readonly ToolRegistration[];
|
|
81
|
+
/** The entity's cards, or `null` when no resolver is available. */
|
|
82
|
+
catalogs: CatalogOwnerSnapshot | null;
|
|
83
|
+
/**
|
|
84
|
+
* Policy's answer about one feature id, in three states.
|
|
85
|
+
*
|
|
86
|
+
* Granting requires a documented need, so an unconfigured feature is
|
|
87
|
+
* `"silent"`, never granted. What the third state buys is the distinction
|
|
88
|
+
* `"silent"` cannot carry: a host that switched the capability off said
|
|
89
|
+
* something, and a capability allowed to answer from content alone must not
|
|
90
|
+
* treat that as nobody having spoken.
|
|
91
|
+
*/
|
|
92
|
+
policyFor: (featureId: string) => ContentCapabilityPolicy;
|
|
93
|
+
/**
|
|
94
|
+
* Report a capability that threw. It is dropped either way; this is how a
|
|
95
|
+
* caller surfaces it as its own recoverable warning rather than letting one
|
|
96
|
+
* capability's defect reach the learner's content.
|
|
97
|
+
*/
|
|
98
|
+
onError?: (registration: ToolRegistration, phase: ContentCapabilityPhase, error: unknown) => void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The capabilities in play, in the order the caller offered them.
|
|
102
|
+
*
|
|
103
|
+
* Registry order is preserved so a renderer's slot ordering stays a property of
|
|
104
|
+
* the registry rather than of resolution timing.
|
|
105
|
+
*/
|
|
106
|
+
export declare function resolveContentCapabilities(args: ResolveContentCapabilitiesArgs): ResolvedContentCapability[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a content capability has anything to show, for one entity and one
|
|
3
|
+
* profile.
|
|
4
|
+
*
|
|
5
|
+
* Two independent halves, and a capability is in play only when both answer yes:
|
|
6
|
+
* policy granted one of its support ids, and its own `requiresAuthoredContent`
|
|
7
|
+
* found the resource in this entity's catalogs. Neither implies the other — a
|
|
8
|
+
* learner with an accommodation still sees nothing on an item carrying no
|
|
9
|
+
* resource, and an item carrying one shows nothing to a learner without the
|
|
10
|
+
* grant.
|
|
11
|
+
*
|
|
12
|
+
* Policy answers in three states rather than two, because a host gate is not the
|
|
13
|
+
* absence of a grant: `resolvesWithoutGrant` lets a capability answer from the
|
|
14
|
+
* content when nobody was granted anything, and a host that switched the
|
|
15
|
+
* capability off must not be read as nobody having spoken. Resolution order is
|
|
16
|
+
* denial, then grant, then the content exception.
|
|
17
|
+
*
|
|
18
|
+
* It lives here, data-only and DOM-free, because two renderers ask it. The
|
|
19
|
+
* section player asks continuously: policy and catalogs both change under a
|
|
20
|
+
* mounted card, and it re-resolves to reconcile what is on screen. Print asks
|
|
21
|
+
* once — one learner, one profile, decided before the page exists, with nothing
|
|
22
|
+
* to toggle. A second implementation for the one-shot case would be two
|
|
23
|
+
* renderers disagreeing about the same card, which is the failure this exists to
|
|
24
|
+
* prevent.
|
|
25
|
+
*
|
|
26
|
+
* Capability-neutral, and gated as such: nothing here names a support id, a
|
|
27
|
+
* catalog type, or a surface. The caller passes the registrations and the slot,
|
|
28
|
+
* a capability interprets its own cards, and the resolution owns only the
|
|
29
|
+
* two-halves rule and its one documented exception.
|
|
30
|
+
*/
|
|
31
|
+
/** The support ids a capability answers to, defaulting to its own id. */
|
|
32
|
+
const supportIdsOf = (registration) => registration.pnpSupportIds?.length
|
|
33
|
+
? registration.pnpSupportIds
|
|
34
|
+
: [registration.toolId];
|
|
35
|
+
/**
|
|
36
|
+
* Everything policy has to say about one capability: a grant, an off switch, or
|
|
37
|
+
* nothing.
|
|
38
|
+
*
|
|
39
|
+
* Denial is checked ahead of a grant on each id rather than after the scan,
|
|
40
|
+
* because the two can only disagree when a host blocked one of a capability's ids
|
|
41
|
+
* while a profile granted another, and there the off switch is the later, more
|
|
42
|
+
* specific statement about this delivery.
|
|
43
|
+
*
|
|
44
|
+
* A host gate names *capabilities*, so the tool id is probed too when it is not
|
|
45
|
+
* already a declared support id — otherwise a capability whose id differs from
|
|
46
|
+
* its support ids would slip a host block. That probe is gate-only: a grant on
|
|
47
|
+
* the tool id is ignored, or blocking would double as a second way to switch a
|
|
48
|
+
* capability on.
|
|
49
|
+
*/
|
|
50
|
+
function policyForCapability(registration, args) {
|
|
51
|
+
const supportIds = supportIdsOf(registration);
|
|
52
|
+
for (const supportId of supportIds) {
|
|
53
|
+
const answer = args.policyFor(supportId);
|
|
54
|
+
if (answer.outcome === "denied")
|
|
55
|
+
return { grant: null, denied: true };
|
|
56
|
+
if (answer.outcome === "granted")
|
|
57
|
+
return { grant: answer, denied: false };
|
|
58
|
+
}
|
|
59
|
+
if (supportIds.includes(registration.toolId)) {
|
|
60
|
+
return { grant: null, denied: false };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
grant: null,
|
|
64
|
+
denied: args.policyFor(registration.toolId).outcome === "denied",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function resolveOne(registration, args) {
|
|
68
|
+
let grant = null;
|
|
69
|
+
try {
|
|
70
|
+
const answer = policyForCapability(registration, args);
|
|
71
|
+
// Nothing reopens a host denial — not a grant it outranked, and not the
|
|
72
|
+
// content exception below.
|
|
73
|
+
if (answer.denied)
|
|
74
|
+
return null;
|
|
75
|
+
grant = answer.grant;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
args.onError?.(registration, "policy", error);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
// The exception, and the only one: a capability whose content can declare
|
|
82
|
+
// itself authored presentation is consulted even when policy granted nothing,
|
|
83
|
+
// because an item family designed to be delivered with its alternate on screen
|
|
84
|
+
// is not an accommodation and no profile grants or revokes it. The capability
|
|
85
|
+
// still decides — `granted` is how it tells the two cases apart.
|
|
86
|
+
if (!grant && !registration.resolvesWithoutGrant)
|
|
87
|
+
return null;
|
|
88
|
+
if (!registration.requiresAuthoredContent) {
|
|
89
|
+
// Nothing to look for in the content, so the grant is the whole answer. A
|
|
90
|
+
// capability reaching here without one has no second half to supply it.
|
|
91
|
+
if (!grant)
|
|
92
|
+
return null;
|
|
93
|
+
return {
|
|
94
|
+
registration,
|
|
95
|
+
featureId: grant.featureId,
|
|
96
|
+
parameters: grant.parameters,
|
|
97
|
+
content: null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const context = {
|
|
101
|
+
featureId: grant?.featureId ?? "",
|
|
102
|
+
parameters: grant?.parameters,
|
|
103
|
+
catalogs: args.catalogs,
|
|
104
|
+
granted: Boolean(grant),
|
|
105
|
+
};
|
|
106
|
+
let content;
|
|
107
|
+
try {
|
|
108
|
+
content = registration.requiresAuthoredContent.resolve(context);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
args.onError?.(registration, "content", error);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
// Absent content is the honest answer to "is there anything to show", not a
|
|
115
|
+
// failure: the item carries no resource for this capability.
|
|
116
|
+
if (content === null || content === undefined)
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
registration,
|
|
120
|
+
featureId: context.featureId,
|
|
121
|
+
parameters: context.parameters,
|
|
122
|
+
content,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The capabilities in play, in the order the caller offered them.
|
|
127
|
+
*
|
|
128
|
+
* Registry order is preserved so a renderer's slot ordering stays a property of
|
|
129
|
+
* the registry rather than of resolution timing.
|
|
130
|
+
*/
|
|
131
|
+
export function resolveContentCapabilities(args) {
|
|
132
|
+
return args.registrations.flatMap((registration) => {
|
|
133
|
+
const resolved = resolveOne(registration, args);
|
|
134
|
+
return resolved ? [resolved] : [];
|
|
135
|
+
});
|
|
136
|
+
}
|
package/dist/tools/internal.d.ts
CHANGED
|
@@ -20,7 +20,15 @@
|
|
|
20
20
|
*/
|
|
21
21
|
export type { HostedToolContext, HostedToolSize, ResolvedToolContext, ToolActivation, ToolContentDependency, ToolContentDependencyContext, ToolModuleLoader, ToolProviderDescriptor, ToolRegistration, ToolRenderElement, ToolSingletonScope, ToolSurfaceRenderContext, ToolSurfaceRenderResult, ToolSurfaceServices, ToolToolbarButtonDefinition, ToolToolbarRenderResult, ToolWindowShellConfig, ToolbarContext, } from "../services/ToolRegistry.js";
|
|
22
22
|
export { ToolRegistry } from "../services/ToolRegistry.js";
|
|
23
|
+
export { resolveToolRegistrationName } from "../services/ToolRegistry.js";
|
|
23
24
|
export type { CatalogOwnerCard, CatalogOwnerSnapshot, } from "../services/AccessibilityCatalogResolver.js";
|
|
25
|
+
export type { ToolSelectionAction, ToolSelectionContext, } from "../services/selection-action.js";
|
|
26
|
+
export type { ToolOpenRequest, ToolRequestTarget, } from "../services/tool-request.js";
|
|
27
|
+
export { resolveFallbackToolIcon, TOOL_FALLBACK_ICONS, } from "../services/tool-icons.js";
|
|
28
|
+
export type { ContentCapabilityPhase, ContentCapabilityPolicy, ResolveContentCapabilitiesArgs, ResolvedContentCapability, } from "./content-capability-resolution.js";
|
|
29
|
+
export { resolveContentCapabilities } from "./content-capability-resolution.js";
|
|
30
|
+
export type { ToolSurfaceHost, ToolSurfaceHostInput, ToolSurfaceHostOptions, ToolSurfaceHostSnapshot, ToolSurfaceScope, } from "./tool-surface-host.js";
|
|
31
|
+
export { createToolSurfaceHost } from "./tool-surface-host.js";
|
|
24
32
|
export type { ToolContext, ToolLevel } from "../services/tool-context.js";
|
|
25
33
|
export { hasChoiceInteraction, hasMathContent, hasReadableText, hasScienceContent, } from "../services/tool-context.js";
|
|
26
34
|
export { createScopedToolId } from "../services/tool-instance-id.js";
|
package/dist/tools/internal.js
CHANGED
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
* point — the mechanism is the same one our own registrations use.
|
|
20
20
|
*/
|
|
21
21
|
export { ToolRegistry } from "../services/ToolRegistry.js";
|
|
22
|
+
// A registration's own display name in the interface locale. A tool window's
|
|
23
|
+
// title is the registration's name, so the shell needs the same `nameKey`-then-
|
|
24
|
+
// `name` precedence the toolbar uses rather than the raw English field.
|
|
25
|
+
export { resolveToolRegistrationName } from "../services/ToolRegistry.js";
|
|
26
|
+
// So a gateway button and the toolbar button for the same tool draw one icon.
|
|
27
|
+
export { resolveFallbackToolIcon, TOOL_FALLBACK_ICONS, } from "../services/tool-icons.js";
|
|
28
|
+
export { resolveContentCapabilities } from "./content-capability-resolution.js";
|
|
29
|
+
export { createToolSurfaceHost } from "./tool-surface-host.js";
|
|
22
30
|
export { hasChoiceInteraction, hasMathContent, hasReadableText, hasScienceContent, } from "../services/tool-context.js";
|
|
23
31
|
// Scoped tool instance ids, so two placements of one tool do not share state.
|
|
24
32
|
export { createScopedToolId } from "../services/tool-instance-id.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless host for capabilities that render into a renderer's surfaces.
|
|
3
|
+
*
|
|
4
|
+
* The interface deliberately exposes only current input, a two-boolean snapshot,
|
|
5
|
+
* and teardown. Discovery, policy/catalog invalidation, content resolution, lazy
|
|
6
|
+
* loading, DOM reconciliation, error isolation, and registry observation stay
|
|
7
|
+
* inside this module. Svelte callers are geometry adapters over this seam.
|
|
8
|
+
*
|
|
9
|
+
* It lives here rather than in `section-player`, where it was written, because a
|
|
10
|
+
* second renderer now opens a surface: the annotation toolbar hosts the
|
|
11
|
+
* capabilities that act on a text selection. Two copies of mount/reconcile/
|
|
12
|
+
* registry-observation would drift, and the drift would be invisible until one
|
|
13
|
+
* renderer stopped honouring the grant-AND-content rule. Nothing about the module
|
|
14
|
+
* was section-shaped — it already imported only from this package — so the move
|
|
15
|
+
* is a relocation, not a rewrite. The one thing that *was* section-shaped is the
|
|
16
|
+
* name it reported errors under, which is now {@link ToolSurfaceHostOptions.hostLabel}.
|
|
17
|
+
*/
|
|
18
|
+
import type { CatalogOwnerContext } from "../services/catalog-owner.js";
|
|
19
|
+
import type { ToolRegistry, ToolSurfaceServices } from "../services/ToolRegistry.js";
|
|
20
|
+
export type ToolSurfaceScope = {
|
|
21
|
+
kind: "content";
|
|
22
|
+
ownerContext: CatalogOwnerContext;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "section";
|
|
25
|
+
assessmentId: string;
|
|
26
|
+
sectionId: string;
|
|
27
|
+
};
|
|
28
|
+
export interface ToolSurfaceHostInput {
|
|
29
|
+
anchor: HTMLElement | null;
|
|
30
|
+
surface: string;
|
|
31
|
+
registry: ToolRegistry | null;
|
|
32
|
+
services: ToolSurfaceServices;
|
|
33
|
+
scope: ToolSurfaceScope;
|
|
34
|
+
}
|
|
35
|
+
export interface ToolSurfaceHostSnapshot {
|
|
36
|
+
/** At least one capability is eligible, including one still loading. */
|
|
37
|
+
mountable: boolean;
|
|
38
|
+
/** At least one capability successfully mounted an element. */
|
|
39
|
+
occupied: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ToolSurfaceHost {
|
|
42
|
+
update(input: ToolSurfaceHostInput): void;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
}
|
|
45
|
+
export interface ToolSurfaceHostOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Which renderer this host belongs to, for framework-error `source` and console
|
|
48
|
+
* warnings — `"pie-section-player"`, `"pie-tool-annotation-toolbar"`.
|
|
49
|
+
*
|
|
50
|
+
* A warning that named the module but not its renderer would be unactionable
|
|
51
|
+
* now that two of them mount surfaces: the same `render` failure on the same
|
|
52
|
+
* `toolId` means different things depending on which surface asked.
|
|
53
|
+
*/
|
|
54
|
+
hostLabel: string;
|
|
55
|
+
}
|
|
56
|
+
/** Create one lifecycle owner for one host surface anchor. */
|
|
57
|
+
export declare function createToolSurfaceHost(onSnapshot: (snapshot: ToolSurfaceHostSnapshot) => void, options: ToolSurfaceHostOptions): ToolSurfaceHost;
|