@honua/app-platform 0.1.4-beta.0 → 0.1.7-beta.0
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/app/index.d.ts.map +1 -1
- package/app/index.js +4 -0
- package/contract/types.d.ts +1 -1
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +4 -0
- package/core/client.d.ts +6 -0
- package/core/client.d.ts.map +1 -1
- package/core/client.js +11 -9
- package/core/error-classifications.d.ts +17 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +17 -0
- package/core/error-code-registry.d.ts +18 -1
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +17 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.d.ts.map +1 -1
- package/core/geoparquet-sql.d.ts +1 -1
- package/core/geoparquet-sql.js +2 -2
- package/core/stac.d.ts +2 -1
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +29 -11
- package/core/types.d.ts +15 -2
- package/core/types.d.ts.map +1 -1
- package/interactions/declarative-contract.d.ts +149 -0
- package/interactions/declarative-contract.d.ts.map +1 -0
- package/interactions/declarative-contract.js +293 -0
- package/interactions/declarative.d.ts +198 -0
- package/interactions/declarative.d.ts.map +1 -0
- package/interactions/declarative.js +470 -0
- package/interactions/feature-state.d.ts +7 -0
- package/interactions/feature-state.d.ts.map +1 -1
- package/interactions/feature-state.js +10 -2
- package/map/maplibre-target.js +1 -1
- package/package.json +2 -2
- package/realtime/sse.d.ts.map +1 -1
- package/realtime/sse.js +7 -1
- package/runtime/style-compose.d.ts.map +1 -1
- package/runtime/style-compose.js +78 -9
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The declarative interaction vocabulary from the open geospatial MCP
|
|
3
|
+
* standard (geospatial-mcp ADR-0030, `spec/schemas/common/interactions.schema.json`).
|
|
4
|
+
*
|
|
5
|
+
* This module owns ONLY the data shapes, the standard's closed sets, and the
|
|
6
|
+
* pure validation rules — no binding, no DOM, no imports — so the same rules
|
|
7
|
+
* can be reused by the agent-tool vocabulary (`@honua/sdk-js/agent-tools`'
|
|
8
|
+
* `bindInteraction` verb) without dragging the compiler
|
|
9
|
+
* (`./declarative.js`) and its exploration primitives into that bundle.
|
|
10
|
+
*
|
|
11
|
+
* The normative rules encoded here:
|
|
12
|
+
*
|
|
13
|
+
* - **Closed event set** — `featureSelect`/`featureHover` (layer sources),
|
|
14
|
+
* `selection` (widget sources), `change` (control sources),
|
|
15
|
+
* `viewportChange` (map source). Extending it requires a standard ADR.
|
|
16
|
+
* - **Closed verb set** — `setFilter`, `setViewport`, `selectFeature`,
|
|
17
|
+
* `runWidgetQuery`, `setVisibility`. Verbs mutate presentation/exploration
|
|
18
|
+
* state only, never source records.
|
|
19
|
+
* - **Arguments are static JSON plus `$event.*` path substitution.** There is
|
|
20
|
+
* no expression language: no arithmetic, no conditionals, no function
|
|
21
|
+
* calls. A string that starts with `$event` MUST be a well-formed
|
|
22
|
+
* `$event.<segment>[.<segment>…]` path.
|
|
23
|
+
* - **Unique ids** within one interactions block.
|
|
24
|
+
* - **Fan-out cap** — at most {@link HONUA_INTERACTION_FANOUT_CAP} (8,
|
|
25
|
+
* the standard's RECOMMENDED cap) interactions may share one
|
|
26
|
+
* (`on.ref`, `on.event`) pair. Blocks over the cap are rejected, never
|
|
27
|
+
* truncated.
|
|
28
|
+
*
|
|
29
|
+
* Reference resolution ("does `layer:parcels` exist in this document?") is a
|
|
30
|
+
* validation-gate responsibility the standard deliberately leaves to the
|
|
31
|
+
* host; the compiler in `./declarative.js` performs it against the concrete
|
|
32
|
+
* components it is handed.
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
/** Closed event vocabulary (`$defs/eventName`). */
|
|
37
|
+
export const HONUA_INTERACTION_EVENTS = [
|
|
38
|
+
"featureSelect",
|
|
39
|
+
"featureHover",
|
|
40
|
+
"selection",
|
|
41
|
+
"change",
|
|
42
|
+
"viewportChange",
|
|
43
|
+
];
|
|
44
|
+
/** Closed action-verb vocabulary (`$defs/actionVerb`). */
|
|
45
|
+
export const HONUA_INTERACTION_VERBS = [
|
|
46
|
+
"setFilter",
|
|
47
|
+
"setViewport",
|
|
48
|
+
"selectFeature",
|
|
49
|
+
"runWidgetQuery",
|
|
50
|
+
"setVisibility",
|
|
51
|
+
];
|
|
52
|
+
/** Component-reference kinds addressable by an interaction (`$defs/componentRef`). */
|
|
53
|
+
export const HONUA_INTERACTION_REF_KINDS = ["map", "layer", "widget", "control"];
|
|
54
|
+
/**
|
|
55
|
+
* The standard's RECOMMENDED per-(`on.ref`, `on.event`) fan-out cap. A block
|
|
56
|
+
* that exceeds it is rejected whole rather than truncated.
|
|
57
|
+
*/
|
|
58
|
+
export const HONUA_INTERACTION_FANOUT_CAP = 8;
|
|
59
|
+
/** The `$event.` prefix that marks the only dynamic element of an argument value. */
|
|
60
|
+
export const HONUA_INTERACTION_EVENT_PATH_PREFIX = "$event.";
|
|
61
|
+
const REF_PATTERN = /^(map|layer:.+|widget:.+|control:.+)$/;
|
|
62
|
+
const EVENT_PATH_PATTERN = /^\$event(\.[^.\s]+)+$/;
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
/** True for a syntactically valid `map` | `layer:{id}` | `widget:{id}` | `control:{id}` reference. */
|
|
67
|
+
export function isHonuaInteractionRef(value) {
|
|
68
|
+
return typeof value === "string" && REF_PATTERN.test(value);
|
|
69
|
+
}
|
|
70
|
+
/** Splits a component reference into its kind and id. Returns `undefined` for a malformed reference. */
|
|
71
|
+
export function parseHonuaInteractionRef(ref) {
|
|
72
|
+
if (ref === "map")
|
|
73
|
+
return { kind: "map" };
|
|
74
|
+
const separator = ref.indexOf(":");
|
|
75
|
+
if (separator <= 0)
|
|
76
|
+
return undefined;
|
|
77
|
+
const kind = ref.slice(0, separator);
|
|
78
|
+
const id = ref.slice(separator + 1);
|
|
79
|
+
if (id.length === 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (kind !== "layer" && kind !== "widget" && kind !== "control")
|
|
82
|
+
return undefined;
|
|
83
|
+
return { kind, id };
|
|
84
|
+
}
|
|
85
|
+
export function isHonuaInteractionEvent(value) {
|
|
86
|
+
return typeof value === "string" && HONUA_INTERACTION_EVENTS.includes(value);
|
|
87
|
+
}
|
|
88
|
+
export function isHonuaInteractionVerb(value) {
|
|
89
|
+
return typeof value === "string" && HONUA_INTERACTION_VERBS.includes(value);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* True when a string value is a `$event.*` substitution path. A string that
|
|
93
|
+
* starts with `$event` but is not a well-formed path (`"$event"`,
|
|
94
|
+
* `"$event."`, `"$eventfoo"`) is NOT a path — it is a validation failure,
|
|
95
|
+
* caught by {@link validateHonuaInteraction}.
|
|
96
|
+
*/
|
|
97
|
+
export function isHonuaInteractionEventPath(value) {
|
|
98
|
+
return EVENT_PATH_PATTERN.test(value);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Reads the value at a `$event.a.b` path out of an event payload. Returns
|
|
102
|
+
* `undefined` when any segment is missing — there is no error channel and no
|
|
103
|
+
* default, matching the standard's "substitution, not expression" posture.
|
|
104
|
+
*/
|
|
105
|
+
export function readHonuaInteractionEventPath(payload, path) {
|
|
106
|
+
const segments = path.slice("$event".length).split(".").filter(Boolean);
|
|
107
|
+
let current = payload;
|
|
108
|
+
for (const segment of segments) {
|
|
109
|
+
if (Array.isArray(current)) {
|
|
110
|
+
const index = Number(segment);
|
|
111
|
+
if (!Number.isInteger(index))
|
|
112
|
+
return undefined;
|
|
113
|
+
current = current[index];
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!isRecord(current))
|
|
117
|
+
return undefined;
|
|
118
|
+
current = current[segment];
|
|
119
|
+
}
|
|
120
|
+
return current;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Substitutes every `$event.*` string in `args` with the value at that path
|
|
124
|
+
* in `payload`, recursing through nested objects and arrays. Non-string
|
|
125
|
+
* values and strings without the prefix are copied through unchanged — this
|
|
126
|
+
* is the whole of the dynamic surface.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveHonuaInteractionArgs(args, payload) {
|
|
129
|
+
if (!args)
|
|
130
|
+
return {};
|
|
131
|
+
return substitute(args, payload);
|
|
132
|
+
}
|
|
133
|
+
function substitute(value, payload) {
|
|
134
|
+
if (typeof value === "string") {
|
|
135
|
+
return value.startsWith(HONUA_INTERACTION_EVENT_PATH_PREFIX)
|
|
136
|
+
? readHonuaInteractionEventPath(payload, value)
|
|
137
|
+
: value;
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(value))
|
|
140
|
+
return value.map((entry) => substitute(entry, payload));
|
|
141
|
+
if (isRecord(value)) {
|
|
142
|
+
const next = {};
|
|
143
|
+
for (const [key, child] of Object.entries(value))
|
|
144
|
+
next[key] = substitute(child, payload);
|
|
145
|
+
return next;
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Validates ONE interaction against the standard's schema-level rules
|
|
151
|
+
* (shape, closed sets, reference grammar, `$event.*` argument discipline).
|
|
152
|
+
* Fan-out and id uniqueness are block-level rules — see
|
|
153
|
+
* {@link validateHonuaInteractions}.
|
|
154
|
+
*/
|
|
155
|
+
export function validateHonuaInteraction(value, path = "interaction") {
|
|
156
|
+
const issues = [];
|
|
157
|
+
if (!isRecord(value)) {
|
|
158
|
+
return [{ code: "invalid-shape", path, message: "An interaction must be a JSON object." }];
|
|
159
|
+
}
|
|
160
|
+
const interactionId = typeof value.id === "string" ? value.id : undefined;
|
|
161
|
+
const issue = (code, field, message) => {
|
|
162
|
+
issues.push({ code, ...(interactionId ? { interactionId } : {}), path: `${path}.${field}`, message });
|
|
163
|
+
};
|
|
164
|
+
if (typeof value.id !== "string" || value.id.length === 0 || value.id.length > 200) {
|
|
165
|
+
issue("invalid-shape", "id", "`id` must be a non-empty string of at most 200 characters.");
|
|
166
|
+
}
|
|
167
|
+
if (value.disabled !== undefined && typeof value.disabled !== "boolean") {
|
|
168
|
+
issue("invalid-shape", "disabled", "`disabled` must be a boolean when present.");
|
|
169
|
+
}
|
|
170
|
+
if (!isRecord(value.on)) {
|
|
171
|
+
issue("invalid-shape", "on", "`on` must be an object with `ref` and `event`.");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (!isHonuaInteractionRef(value.on.ref)) {
|
|
175
|
+
issue("invalid-ref", "on.ref", "`on.ref` must be `map`, `layer:{id}`, `widget:{id}`, or `control:{id}`.");
|
|
176
|
+
}
|
|
177
|
+
if (!isHonuaInteractionEvent(value.on.event)) {
|
|
178
|
+
issue("unknown-event", "on.event", `\`on.event\` must be one of: ${HONUA_INTERACTION_EVENTS.join(", ")}.`);
|
|
179
|
+
}
|
|
180
|
+
for (const key of Object.keys(value.on)) {
|
|
181
|
+
if (key !== "ref" && key !== "event") {
|
|
182
|
+
issue("invalid-shape", `on.${key}`, "`on` accepts only `ref` and `event`.");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!isRecord(value.do)) {
|
|
187
|
+
issue("invalid-shape", "do", "`do` must be an object with `ref` and `verb`.");
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
if (!isHonuaInteractionRef(value.do.ref)) {
|
|
191
|
+
issue("invalid-ref", "do.ref", "`do.ref` must be `map`, `layer:{id}`, `widget:{id}`, or `control:{id}`.");
|
|
192
|
+
}
|
|
193
|
+
if (!isHonuaInteractionVerb(value.do.verb)) {
|
|
194
|
+
issue("unknown-verb", "do.verb", `\`do.verb\` must be one of: ${HONUA_INTERACTION_VERBS.join(", ")}.`);
|
|
195
|
+
}
|
|
196
|
+
if (value.do.args !== undefined) {
|
|
197
|
+
if (!isRecord(value.do.args)) {
|
|
198
|
+
issue("invalid-shape", "do.args", "`do.args` must be a JSON object when present.");
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
collectEventPathIssues(value.do.args, `${path}.do.args`, interactionId, issues);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const key of Object.keys(value.do)) {
|
|
205
|
+
if (key !== "ref" && key !== "verb" && key !== "args") {
|
|
206
|
+
issue("invalid-shape", `do.${key}`, "`do` accepts only `ref`, `verb`, and `args`.");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const key of Object.keys(value)) {
|
|
211
|
+
if (key !== "id" && key !== "on" && key !== "do" && key !== "disabled") {
|
|
212
|
+
issue("invalid-shape", key, "An interaction accepts only `id`, `on`, `do`, and `disabled`.");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return issues;
|
|
216
|
+
}
|
|
217
|
+
function collectEventPathIssues(value, path, interactionId, issues) {
|
|
218
|
+
if (typeof value === "string") {
|
|
219
|
+
if (value.startsWith("$event") && !isHonuaInteractionEventPath(value)) {
|
|
220
|
+
issues.push({
|
|
221
|
+
code: "invalid-event-path",
|
|
222
|
+
...(interactionId ? { interactionId } : {}),
|
|
223
|
+
path,
|
|
224
|
+
message: "A `$event` argument must be a `$event.<path>` substitution — the vocabulary has no expression language.",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(value)) {
|
|
230
|
+
value.forEach((entry, index) => collectEventPathIssues(entry, `${path}[${index}]`, interactionId, issues));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (isRecord(value)) {
|
|
234
|
+
for (const [key, child] of Object.entries(value)) {
|
|
235
|
+
collectEventPathIssues(child, `${path}.${key}`, interactionId, issues);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Validates a whole `interactions` block: every member's schema-level rules
|
|
241
|
+
* plus the two block-level ones (unique ids, fan-out cap). Disabled
|
|
242
|
+
* interactions are validated like any other — they are retained in the
|
|
243
|
+
* document, only skipped at dispatch — but they do NOT count toward the
|
|
244
|
+
* fan-out cap, which bounds live dispatch work.
|
|
245
|
+
*/
|
|
246
|
+
export function validateHonuaInteractions(interactions, options = {}) {
|
|
247
|
+
const cap = options.fanOutCap ?? HONUA_INTERACTION_FANOUT_CAP;
|
|
248
|
+
if (!Array.isArray(interactions)) {
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
issues: [{ code: "invalid-shape", path: "interactions", message: "`interactions` must be an array." }],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const issues = [];
|
|
255
|
+
const seenIds = new Set();
|
|
256
|
+
const fanOut = new Map();
|
|
257
|
+
interactions.forEach((interaction, index) => {
|
|
258
|
+
const path = `interactions[${index}]`;
|
|
259
|
+
issues.push(...validateHonuaInteraction(interaction, path));
|
|
260
|
+
if (!isRecord(interaction))
|
|
261
|
+
return;
|
|
262
|
+
const id = typeof interaction.id === "string" ? interaction.id : undefined;
|
|
263
|
+
if (id !== undefined) {
|
|
264
|
+
if (seenIds.has(id)) {
|
|
265
|
+
issues.push({
|
|
266
|
+
code: "duplicate-id",
|
|
267
|
+
interactionId: id,
|
|
268
|
+
path: `${path}.id`,
|
|
269
|
+
message: `Interaction id "${id}" is declared more than once in this block.`,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
seenIds.add(id);
|
|
273
|
+
}
|
|
274
|
+
if (interaction.disabled === true)
|
|
275
|
+
return;
|
|
276
|
+
const on = isRecord(interaction.on) ? interaction.on : undefined;
|
|
277
|
+
if (!isHonuaInteractionRef(on?.ref) || !isHonuaInteractionEvent(on?.event))
|
|
278
|
+
return;
|
|
279
|
+
const key = `${on.ref}${on.event}`;
|
|
280
|
+
fanOut.set(key, (fanOut.get(key) ?? 0) + 1);
|
|
281
|
+
});
|
|
282
|
+
for (const [key, count] of fanOut) {
|
|
283
|
+
if (count <= cap)
|
|
284
|
+
continue;
|
|
285
|
+
const [ref, event] = key.split("");
|
|
286
|
+
issues.push({
|
|
287
|
+
code: "fan-out-exceeded",
|
|
288
|
+
path: "interactions",
|
|
289
|
+
message: `${count} interactions share (${ref}, ${event}); the fan-out cap is ${cap}. The block is rejected, not truncated.`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return { ok: issues.length === 0, issues };
|
|
293
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@honua/sdk-js/interactions/declarative` — compiles a standard
|
|
3
|
+
* `interactions[]` block (geospatial-mcp ADR-0030) onto this SDK's existing
|
|
4
|
+
* imperative binding primitives.
|
|
5
|
+
*
|
|
6
|
+
* The standard constrains the *document*, not the dispatcher: "client
|
|
7
|
+
* runtimes implement dispatch by compiling bindings onto whatever imperative
|
|
8
|
+
* primitives they already have". That is exactly what this module does — it
|
|
9
|
+
* owns no event plumbing of its own, only a compile table from the closed
|
|
10
|
+
* (`on.event`, `do.verb`) vocabulary onto `createSelectionHandler`,
|
|
11
|
+
* `createHoverHandler`, `bindTableSelectionToExploration`,
|
|
12
|
+
* `bindFilterControlsToExploration`, and `subscribeExplorationSelector`.
|
|
13
|
+
*
|
|
14
|
+
* Three properties are load-bearing and asserted by the suite:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Validate before binding.** `compileHonuaInteractions` runs the whole
|
|
17
|
+
* block through {@link validateHonuaInteractions} plus reference
|
|
18
|
+
* resolution against the components it was handed. A block with issues
|
|
19
|
+
* binds *nothing* — the standard rejects over-cap and malformed documents
|
|
20
|
+
* rather than truncating them.
|
|
21
|
+
* 2. **Actions never emit events.** A verb-driven state change must not
|
|
22
|
+
* re-enter the dispatcher. Two mechanisms enforce it: every compiled
|
|
23
|
+
* handler is skipped while an action is in flight (`#dispatching`), and
|
|
24
|
+
* exploration-backed subscriptions run with the view controller's default
|
|
25
|
+
* `includeSelf: false`, so a change this compiler originated is never
|
|
26
|
+
* delivered back to this compiler. There is no cycle detection because
|
|
27
|
+
* there can be no cycle.
|
|
28
|
+
* 3. **Unsupported pairs are named, never dropped.** When a binding's
|
|
29
|
+
* (`on.event`, `do.verb`) pair — or the component behind a ref — has no
|
|
30
|
+
* primitive to compile onto, it lands in
|
|
31
|
+
* {@link HonuaCompiledInteractions.unsupported} with the pair spelled out,
|
|
32
|
+
* so a host can surface a real "this runtime cannot honor that binding"
|
|
33
|
+
* message instead of silently rendering an inert app.
|
|
34
|
+
*
|
|
35
|
+
* @experimental The compiler surface tracks geospatial-mcp ADR-0030 while
|
|
36
|
+
* that ADR is Proposed; the component-adapter shapes may gain fields.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
import type { SourceId } from "../contract/types.js";
|
|
41
|
+
import type { ExplorationViewController, FilterClause } from "../exploration/types.js";
|
|
42
|
+
import type { InteractiveMap } from "./feature-state.js";
|
|
43
|
+
import { type HonuaInteractionEvent, type HonuaInteractionIssue, type HonuaInteractionVerb } from "./declarative-contract.js";
|
|
44
|
+
export * from "./declarative-contract.js";
|
|
45
|
+
/**
|
|
46
|
+
* A layer addressed as `layer:{id}`. `map` is what the
|
|
47
|
+
* `featureSelect`/`featureHover` primitives bind to; the optional methods are
|
|
48
|
+
* the layer-scoped action targets.
|
|
49
|
+
*/
|
|
50
|
+
export interface HonuaInteractionLayerComponent {
|
|
51
|
+
/** MapLibre-style interactive map this layer renders on — the event source for `featureSelect`/`featureHover`. */
|
|
52
|
+
readonly map?: InteractiveMap;
|
|
53
|
+
/** Source id feature-state is written to. Defaults to the ref's id. */
|
|
54
|
+
readonly sourceId?: SourceId;
|
|
55
|
+
/** Rendered layer id clicks/hovers are observed on. Defaults to the ref's id. */
|
|
56
|
+
readonly layerId?: string;
|
|
57
|
+
/** Source layer, for vector-tile sources. */
|
|
58
|
+
readonly sourceLayer?: string;
|
|
59
|
+
/** Select multiple features rather than replacing on each click. @default false */
|
|
60
|
+
readonly multiSelect?: boolean;
|
|
61
|
+
/** `setVisibility` target. */
|
|
62
|
+
setVisibility?(visible: boolean): void;
|
|
63
|
+
/** `setFilter` target. When absent, `setFilter` falls back to the shared exploration filter keyed by this ref's id. */
|
|
64
|
+
setFilter?(clause: FilterClause | undefined): void;
|
|
65
|
+
}
|
|
66
|
+
export interface HonuaInteractionViewport {
|
|
67
|
+
readonly bbox?: readonly [number, number, number, number];
|
|
68
|
+
readonly center?: readonly [number, number];
|
|
69
|
+
readonly zoom?: number;
|
|
70
|
+
readonly pitch?: number;
|
|
71
|
+
readonly bearing?: number;
|
|
72
|
+
}
|
|
73
|
+
/** The `map` component. */
|
|
74
|
+
export interface HonuaInteractionMapComponent {
|
|
75
|
+
/**
|
|
76
|
+
* `setViewport` target. When absent, `setViewport` falls back to the
|
|
77
|
+
* exploration view's `setExtent` for `bbox`-shaped arguments.
|
|
78
|
+
*/
|
|
79
|
+
setViewport?(viewport: HonuaInteractionViewport): void;
|
|
80
|
+
}
|
|
81
|
+
/** A widget addressed as `widget:{id}`. */
|
|
82
|
+
export interface HonuaInteractionWidgetComponent {
|
|
83
|
+
/** `runWidgetQuery` target. There is no exploration-level fallback for this verb. */
|
|
84
|
+
runQuery?(request: Readonly<Record<string, unknown>>): void;
|
|
85
|
+
/** `setFilter` target. When absent, falls back to the shared exploration filter keyed by this ref's id. */
|
|
86
|
+
setFilter?(clause: FilterClause | undefined): void;
|
|
87
|
+
/** `setVisibility` target. */
|
|
88
|
+
setVisibility?(visible: boolean): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A control addressed as `control:{id}`. A control's `change` event is read
|
|
92
|
+
* off the shared exploration filter slice keyed by the control's id — that is
|
|
93
|
+
* the same seam `bindFilterControlsToExploration` writes through, so a
|
|
94
|
+
* filter bar wired with that primitive needs no extra adapter here.
|
|
95
|
+
*/
|
|
96
|
+
export interface HonuaInteractionControlComponent {
|
|
97
|
+
/** `setVisibility` target. */
|
|
98
|
+
setVisibility?(visible: boolean): void;
|
|
99
|
+
/** `setFilter` target. When absent, falls back to the shared exploration filter keyed by this ref's id. */
|
|
100
|
+
setFilter?(clause: FilterClause | undefined): void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The components an `interactions[]` block's refs resolve against. A ref that
|
|
104
|
+
* resolves to no component is a validation failure (`unresolved-ref`), matching
|
|
105
|
+
* the standard's "an implementation MUST reject a binding whose `ref` does not
|
|
106
|
+
* resolve within the document".
|
|
107
|
+
*
|
|
108
|
+
* `control:` and `widget:` refs may be declared with an empty object when the
|
|
109
|
+
* component needs no adapter — their events and their `setFilter` action both
|
|
110
|
+
* run through the shared exploration view.
|
|
111
|
+
*/
|
|
112
|
+
export interface HonuaInteractionComponents {
|
|
113
|
+
readonly map?: HonuaInteractionMapComponent;
|
|
114
|
+
readonly layers?: Readonly<Record<string, HonuaInteractionLayerComponent>>;
|
|
115
|
+
readonly widgets?: Readonly<Record<string, HonuaInteractionWidgetComponent>>;
|
|
116
|
+
readonly controls?: Readonly<Record<string, HonuaInteractionControlComponent>>;
|
|
117
|
+
}
|
|
118
|
+
/** One binding this runtime cannot honor, with the pair named. */
|
|
119
|
+
export interface HonuaUnsupportedInteraction {
|
|
120
|
+
readonly interactionId: string;
|
|
121
|
+
readonly on: {
|
|
122
|
+
readonly ref: string;
|
|
123
|
+
readonly event: HonuaInteractionEvent;
|
|
124
|
+
};
|
|
125
|
+
readonly do: {
|
|
126
|
+
readonly ref: string;
|
|
127
|
+
readonly verb: HonuaInteractionVerb;
|
|
128
|
+
};
|
|
129
|
+
/** `"{event} -> {verb}"`, the pair as the standard spells it. */
|
|
130
|
+
readonly pair: string;
|
|
131
|
+
readonly reason: string;
|
|
132
|
+
}
|
|
133
|
+
/** One binding that compiled onto a primitive. */
|
|
134
|
+
export interface HonuaCompiledInteractionBinding {
|
|
135
|
+
readonly interactionId: string;
|
|
136
|
+
readonly on: {
|
|
137
|
+
readonly ref: string;
|
|
138
|
+
readonly event: HonuaInteractionEvent;
|
|
139
|
+
};
|
|
140
|
+
readonly do: {
|
|
141
|
+
readonly ref: string;
|
|
142
|
+
readonly verb: HonuaInteractionVerb;
|
|
143
|
+
};
|
|
144
|
+
readonly pair: string;
|
|
145
|
+
/** Disposes just this binding. Idempotent. */
|
|
146
|
+
dispose(): void;
|
|
147
|
+
}
|
|
148
|
+
export interface HonuaCompiledInteractions {
|
|
149
|
+
/** True when the block validated and every non-disabled binding compiled. */
|
|
150
|
+
readonly ok: boolean;
|
|
151
|
+
/** Validation failures. When non-empty, nothing was bound. */
|
|
152
|
+
readonly issues: ReadonlyArray<HonuaInteractionIssue>;
|
|
153
|
+
readonly bindings: ReadonlyArray<HonuaCompiledInteractionBinding>;
|
|
154
|
+
readonly unsupported: ReadonlyArray<HonuaUnsupportedInteraction>;
|
|
155
|
+
/** Interaction ids skipped because `disabled: true`. */
|
|
156
|
+
readonly disabled: ReadonlyArray<string>;
|
|
157
|
+
/** Disposes every compiled binding. Idempotent. */
|
|
158
|
+
dispose(): void;
|
|
159
|
+
}
|
|
160
|
+
export interface CompileHonuaInteractionsOptions {
|
|
161
|
+
/**
|
|
162
|
+
* The exploration view this compiler owns — every action verb writes
|
|
163
|
+
* through it, and every exploration-backed event source subscribes on it.
|
|
164
|
+
*
|
|
165
|
+
* **Pass a view bound for the compiler alone** (`ctx.connectView({ id:
|
|
166
|
+
* "interactions", role: "custom" })`), not a view a widget also dispatches
|
|
167
|
+
* through. That is what makes "actions never emit events" structural rather
|
|
168
|
+
* than best-effort: an `ExplorationViewController` delivers a bound view its
|
|
169
|
+
* own notifications only under `includeSelf`, so a state change this
|
|
170
|
+
* compiler originated is never routed back into this compiler, while a real
|
|
171
|
+
* user gesture published by any *other* view is.
|
|
172
|
+
*/
|
|
173
|
+
readonly view: ExplorationViewController;
|
|
174
|
+
readonly components?: HonuaInteractionComponents;
|
|
175
|
+
/** Per-(`on.ref`, `on.event`) fan-out cap. @default 8 */
|
|
176
|
+
readonly fanOutCap?: number;
|
|
177
|
+
/**
|
|
178
|
+
* Observes each dispatched action. Called after the verb ran; never called
|
|
179
|
+
* for a suppressed (action-originated) event, which is the whole point of
|
|
180
|
+
* "actions never emit events".
|
|
181
|
+
*/
|
|
182
|
+
readonly onDispatch?: (dispatch: HonuaInteractionDispatch) => void;
|
|
183
|
+
}
|
|
184
|
+
export interface HonuaInteractionDispatch {
|
|
185
|
+
readonly interactionId: string;
|
|
186
|
+
readonly event: HonuaInteractionEvent;
|
|
187
|
+
readonly verb: HonuaInteractionVerb;
|
|
188
|
+
readonly ref: string;
|
|
189
|
+
/** Arguments after `$event.*` substitution. */
|
|
190
|
+
readonly args: Readonly<Record<string, unknown>>;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Compiles an `interactions[]` block onto live components. Returns disposers;
|
|
194
|
+
* never throws for a bad document — a malformed or over-cap block comes back
|
|
195
|
+
* as `{ ok: false, issues }` with nothing bound.
|
|
196
|
+
*/
|
|
197
|
+
export declare function compileHonuaInteractions(interactions: unknown, options: CompileHonuaInteractionsOptions): HonuaCompiledInteractions;
|
|
198
|
+
//# sourceMappingURL=declarative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declarative.d.ts","sourceRoot":"","sources":["../../../src/interactions/declarative.ts"],"names":[],"mappings":"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"}
|