structured-context-map-views 0.0.0-stage → 0.1.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.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -2
  3. package/dist/definition-KuPoA1mO.js +582 -0
  4. package/dist/definition-KuPoA1mO.js.map +1 -0
  5. package/dist/index.js +889 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/node.js +106 -0
  8. package/dist/node.js.map +1 -0
  9. package/dist/styles.css +497 -0
  10. package/dist/types/core/definition.d.ts +112 -0
  11. package/dist/types/core/graph.d.ts +16 -0
  12. package/dist/types/core/index.d.ts +6 -0
  13. package/dist/types/core/landscape.d.ts +44 -0
  14. package/dist/types/core/occurrence.d.ts +11 -0
  15. package/dist/types/core/schemas/definition.d.ts +163 -0
  16. package/dist/types/core/schemas/index.d.ts +418 -0
  17. package/dist/types/core/schemas/landscape-layout.d.ts +102 -0
  18. package/dist/types/core/schemas/param-ref.d.ts +10 -0
  19. package/dist/types/core/schemas/schema-url.d.ts +7 -0
  20. package/dist/types/core/schemas/settings.d.ts +27 -0
  21. package/dist/types/core/schemas/tree-layout.d.ts +40 -0
  22. package/dist/types/core/schemas/xy-layout.d.ts +94 -0
  23. package/dist/types/core/tree.d.ts +45 -0
  24. package/dist/types/core/xy.d.ts +43 -0
  25. package/dist/types/index.d.ts +7 -0
  26. package/dist/types/layouts/EdgeOverlay.d.ts +36 -0
  27. package/dist/types/layouts/LandscapeLayout.d.ts +8 -0
  28. package/dist/types/layouts/MapView.d.ts +12 -0
  29. package/dist/types/layouts/NodeCard.d.ts +16 -0
  30. package/dist/types/layouts/ParameterControls.d.ts +13 -0
  31. package/dist/types/layouts/TreeLayout.d.ts +16 -0
  32. package/dist/types/layouts/XYLayout.d.ts +9 -0
  33. package/dist/types/node/bundled-definitions.d.ts +1 -0
  34. package/dist/types/node/index.d.ts +17 -0
  35. package/package.json +69 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roger Barnes
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 CHANGED
@@ -1,3 +1,20 @@
1
- # Temporary Holding Version
1
+ # structured-context-map-views
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Declarative map views of [structured-context](https://github.com/mindsocket/structured-context) spaces: a short JSON5 definition projects a space into a tree, nested containers or an XY plot, rendered by React components.
4
+
5
+ ```bash
6
+ npm install structured-context-map-views react react-dom structured-context
7
+ ```
8
+
9
+ ```tsx
10
+ import { MapView, ParameterControls } from 'structured-context-map-views';
11
+ import 'structured-context-map-views/styles.css';
12
+
13
+ <MapView definition={definition} graph={graph} values={values} onNodeSelect={(node) => open(node)} />
14
+ ```
15
+
16
+ `structured-context-map-views/node` finds definitions on disk (`loadMapViewDefinitions`). React 19 and `structured-context` are peer dependencies. Pre-1.0: exports may change between minor versions.
17
+
18
+ See the [documentation](https://github.com/mindsocket/structured-context-map-views#readme): [concepts](https://github.com/mindsocket/structured-context-map-views/blob/main/docs/concepts.md), [writing definitions](https://github.com/mindsocket/structured-context-map-views/blob/main/docs/definitions.md) and [the API](https://github.com/mindsocket/structured-context-map-views/blob/main/docs/api.md).
19
+
20
+ Licence: MIT.
@@ -0,0 +1,582 @@
1
+ import Ajv2020 from "ajv/dist/2020";
2
+ import JSON5 from "json5";
3
+ /** Base of the `$id`s the sources use: the `latest` folder. */
4
+ var SCHEMA_URL = `https://raw.githubusercontent.com/mindsocket/structured-context-map-views/main/schemas/generated/latest`;
5
+ //#endregion
6
+ //#region src/core/schemas/param-ref.ts
7
+ /** `$ref` target for a setting whose value comes from a declared parameter. */
8
+ var PARAM_REF = `${SCHEMA_URL}/definition.json#/$defs/paramRef`;
9
+ /** A setting that takes either a fixed value matching `valueSchema` or a `{ $param }` binding. */
10
+ function parameterisable(valueSchema, defaultValue, description) {
11
+ return {
12
+ description,
13
+ default: defaultValue,
14
+ anyOf: [valueSchema, { $ref: PARAM_REF }]
15
+ };
16
+ }
17
+ //#endregion
18
+ //#region src/core/schemas/definition.ts
19
+ /**
20
+ * The map view definition format: shared parameters, and the regions a map view is composed of.
21
+ *
22
+ * Written as TypeScript so the definition types derive from it (`FromSchema`); the JSON files
23
+ * for `$schema` references and editors are generated (`bun run generate:schemas`).
24
+ */
25
+ var definitionSchema = {
26
+ $schema: "https://json-schema.org/draft/2020-12/schema",
27
+ $id: `${SCHEMA_URL}/definition.json`,
28
+ title: "Map view definition",
29
+ description: "Declares how spaces of one schema are projected into a map view: shared parameters, and the regions the map view is composed of.",
30
+ type: "object",
31
+ required: [
32
+ "$id",
33
+ "schema",
34
+ "regions"
35
+ ],
36
+ additionalProperties: false,
37
+ properties: {
38
+ $schema: { type: "string" },
39
+ $id: {
40
+ type: "string",
41
+ pattern: "^sctx-map-view://[^/]+(/[^/]+)?$",
42
+ description: "Identifier, namespaced by where the definition is found: \"sctx-map-view://core/<name>\" (bundled), \"sctx-map-view://<plugin>/<name>\" (a plugin) or \"sctx-map-view://<name>\" (the config folder)."
43
+ },
44
+ title: {
45
+ type: "string",
46
+ minLength: 1,
47
+ description: "Display name. Defaults to the last segment of the `$id`."
48
+ },
49
+ description: { type: "string" },
50
+ schema: {
51
+ type: "string",
52
+ minLength: 1,
53
+ description: "`$id` of the structured-context schema whose spaces this map view applies to."
54
+ },
55
+ parameters: {
56
+ type: "object",
57
+ description: "Settings the reader can change. Each is a JSON Schema with a default, bound to settings anywhere in the definition with { \"$param\": \"<name>\" }.",
58
+ additionalProperties: { $ref: "#/$defs/parameter" }
59
+ },
60
+ regions: {
61
+ type: "array",
62
+ minItems: 1,
63
+ description: "Parts of the map view, each with its own layout, nodes and edges. Only the first region is rendered for now.",
64
+ items: { $ref: "#/$defs/region" }
65
+ }
66
+ },
67
+ $defs: {
68
+ parameter: {
69
+ type: "object",
70
+ description: "A JSON Schema for the parameter's values (it can $ref, narrow or extend the bundled setting definitions), plus a title for its control.",
71
+ required: ["default"],
72
+ properties: {
73
+ title: { type: "string" },
74
+ description: { type: "string" },
75
+ default: {}
76
+ }
77
+ },
78
+ paramRef: {
79
+ type: "object",
80
+ description: "Takes this setting's value from a declared parameter.",
81
+ required: ["$param"],
82
+ additionalProperties: false,
83
+ properties: { $param: {
84
+ type: "string",
85
+ minLength: 1
86
+ } }
87
+ },
88
+ region: {
89
+ type: "object",
90
+ required: ["id", "layout"],
91
+ additionalProperties: false,
92
+ properties: {
93
+ id: {
94
+ type: "string",
95
+ minLength: 1,
96
+ description: "Unique within the map view; part of every occurrence id in the region."
97
+ },
98
+ select: {
99
+ type: "object",
100
+ description: "Which nodes of the space the region shows. By default, all of them.",
101
+ additionalProperties: false,
102
+ properties: { filter: {
103
+ type: "string",
104
+ minLength: 1,
105
+ description: "A structured-context filter: a view name from the space's config, or an expression such as `WHERE …` or `SELECT … WHERE …`."
106
+ } }
107
+ },
108
+ layout: {
109
+ description: "How the region is arranged, chosen by `type`.",
110
+ type: "object",
111
+ required: ["type"],
112
+ discriminator: { propertyName: "type" },
113
+ oneOf: [
114
+ { $ref: `${SCHEMA_URL}/layouts/tree.json` },
115
+ { $ref: `${SCHEMA_URL}/layouts/landscape.json` },
116
+ { $ref: `${SCHEMA_URL}/layouts/xy.json` }
117
+ ]
118
+ },
119
+ nodes: {
120
+ type: "object",
121
+ additionalProperties: false,
122
+ default: {},
123
+ properties: {
124
+ style: parameterisable({ $ref: `${SCHEMA_URL}/settings.json#/$defs/nodeStyle` }, "card", "How nodes are drawn."),
125
+ fields: parameterisable({ $ref: `${SCHEMA_URL}/settings.json#/$defs/fieldList` }, [], "Schema fields shown on cards.")
126
+ }
127
+ },
128
+ edges: {
129
+ type: "object",
130
+ additionalProperties: false,
131
+ default: {},
132
+ properties: { show: parameterisable({ type: "boolean" }, true, "Whether connectors are drawn.") }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ };
138
+ //#endregion
139
+ //#region src/core/schemas/landscape-layout.ts
140
+ /** Settings of the landscape layout: nested containers following an authored grouping. */
141
+ var landscapeLayoutSchema = {
142
+ $schema: "https://json-schema.org/draft/2020-12/schema",
143
+ $id: `${SCHEMA_URL}/layouts/landscape.json`,
144
+ title: "Landscape layout",
145
+ description: "Nested containers: each grouping level becomes containers inside the level above, and the last level is drawn as nodes.",
146
+ type: "object",
147
+ required: ["type", "group"],
148
+ additionalProperties: false,
149
+ properties: {
150
+ type: { const: "landscape" },
151
+ orientation: parameterisable({ $ref: `${SCHEMA_URL}/settings.json#/$defs/orientation` }, "horizontal", "horizontal: top-level containers side by side, as columns. vertical: stacked, as rows."),
152
+ group: {
153
+ type: "array",
154
+ minItems: 1,
155
+ description: "Grouping levels, outermost first. The last level is drawn as nodes; the others as containers. Adjacent node levels are joined by any edge between their types, in either direction.",
156
+ items: {
157
+ type: "object",
158
+ if: {
159
+ type: "object",
160
+ required: ["field"]
161
+ },
162
+ then: {
163
+ type: "object",
164
+ description: "Containers labelled by the values of a field of the next node level's nodes. A node with several values appears under each.",
165
+ required: ["field"],
166
+ additionalProperties: false,
167
+ properties: { field: {
168
+ type: "string",
169
+ minLength: 1
170
+ } }
171
+ },
172
+ else: {
173
+ type: "object",
174
+ description: "A level of nodes of one type.",
175
+ required: ["type"],
176
+ additionalProperties: false,
177
+ properties: {
178
+ type: {
179
+ description: "Node type at this level.",
180
+ anyOf: [{
181
+ type: "string",
182
+ minLength: 1
183
+ }, { $ref: PARAM_REF }]
184
+ },
185
+ edge: {
186
+ type: "object",
187
+ description: "Which edges join this level to the node level above (or to the last `via` type). By default, any edge between the two types.",
188
+ required: ["field"],
189
+ additionalProperties: false,
190
+ properties: { field: {
191
+ type: "string",
192
+ minLength: 1
193
+ } }
194
+ },
195
+ via: {
196
+ type: "array",
197
+ minItems: 1,
198
+ description: "Types skipped between the node level above and this one, in order. Nodes are joined through a chain of edges across them. Ignored on the first level, which has no level above.",
199
+ items: {
200
+ type: "string",
201
+ minLength: 1
202
+ }
203
+ },
204
+ intermediate: parameterisable({ $ref: `${SCHEMA_URL}/layouts/landscape.json#/$defs/intermediate` }, "hidden", "How the skipped nodes of a `via` chain are shown.")
205
+ }
206
+ }
207
+ }
208
+ }
209
+ },
210
+ $defs: { intermediate: {
211
+ title: "Skipped levels",
212
+ description: "hidden: not shown. breadcrumb: listed on each node reached through them.",
213
+ enum: ["hidden", "breadcrumb"]
214
+ } }
215
+ };
216
+ //#endregion
217
+ //#region src/core/schemas/index.ts
218
+ /** The bundled schemas describing the map view definition format. */
219
+ var MAP_VIEW_SCHEMAS = [
220
+ definitionSchema,
221
+ {
222
+ $schema: "https://json-schema.org/draft/2020-12/schema",
223
+ $id: `${SCHEMA_URL}/settings.json`,
224
+ title: "Shared map view settings",
225
+ description: "Reusable setting definitions. Parameters can $ref these and narrow them, e.g. with allOf and enum.",
226
+ $defs: {
227
+ orientation: {
228
+ title: "Orientation",
229
+ enum: ["vertical", "horizontal"]
230
+ },
231
+ nodeStyle: {
232
+ title: "Node style",
233
+ description: "card: a box with the title and selected fields. shape: a compact marker with a tooltip.",
234
+ enum: ["card", "shape"]
235
+ },
236
+ fieldList: {
237
+ title: "Fields",
238
+ type: "array",
239
+ items: {
240
+ type: "string",
241
+ minLength: 1
242
+ },
243
+ uniqueItems: true
244
+ }
245
+ }
246
+ },
247
+ {
248
+ $schema: "https://json-schema.org/draft/2020-12/schema",
249
+ $id: `${SCHEMA_URL}/layouts/tree.json`,
250
+ title: "Tree layout",
251
+ description: "A tree over the schema's hierarchy: root-type nodes at the top, children via hierarchy edges.",
252
+ type: "object",
253
+ required: ["type"],
254
+ additionalProperties: false,
255
+ properties: {
256
+ type: { const: "tree" },
257
+ orientation: parameterisable({ $ref: `${SCHEMA_URL}/settings.json#/$defs/orientation` }, "vertical", "Direction the tree grows."),
258
+ dag: parameterisable({ $ref: `${SCHEMA_URL}/layouts/tree.json#/$defs/dag` }, "repeat", "How a node with several parents is shown.")
259
+ },
260
+ $defs: { dag: {
261
+ title: "Nodes with several parents",
262
+ description: "repeat: the node appears under each parent, its subtree shown once. cross-edge: the node appears under its first parent, with extra connectors from the others.",
263
+ enum: ["repeat", "cross-edge"]
264
+ } }
265
+ },
266
+ landscapeLayoutSchema,
267
+ {
268
+ $schema: "https://json-schema.org/draft/2020-12/schema",
269
+ $id: `${SCHEMA_URL}/layouts/xy.json`,
270
+ title: "XY layout",
271
+ description: "Nodes positioned on two axes by numeric fields. Nodes of the plotted types without a usable value on both axes are listed beside the plot, not dropped.",
272
+ type: "object",
273
+ required: [
274
+ "type",
275
+ "x",
276
+ "y"
277
+ ],
278
+ additionalProperties: false,
279
+ properties: {
280
+ type: { const: "xy" },
281
+ x: { $ref: `${SCHEMA_URL}/layouts/xy.json#/$defs/axis` },
282
+ y: { $ref: `${SCHEMA_URL}/layouts/xy.json#/$defs/axis` },
283
+ types: parameterisable({
284
+ type: "array",
285
+ items: {
286
+ type: "string",
287
+ minLength: 1
288
+ },
289
+ uniqueItems: true
290
+ }, [], "Node types to plot. Empty: every type with a value on either axis."),
291
+ edgeFields: parameterisable({ $ref: `${SCHEMA_URL}/settings.json#/$defs/fieldList` }, [], "Fields whose edges are drawn between plotted nodes. Empty: all edges.")
292
+ },
293
+ $defs: {
294
+ axis: {
295
+ title: "Axis",
296
+ type: "object",
297
+ required: ["field"],
298
+ additionalProperties: false,
299
+ properties: {
300
+ field: {
301
+ description: "Numeric field giving the position.",
302
+ anyOf: [{
303
+ type: "string",
304
+ minLength: 1
305
+ }, { $ref: PARAM_REF }]
306
+ },
307
+ domain: {
308
+ description: "Values at the two ends of the axis: [min, max], or \"data\" for the smallest and largest plotted values. Nodes outside a fixed domain are listed as unplaced.",
309
+ default: "data",
310
+ anyOf: [{ const: "data" }, {
311
+ type: "array",
312
+ items: { type: "number" },
313
+ minItems: 2,
314
+ maxItems: 2
315
+ }]
316
+ },
317
+ direction: parameterisable({ $ref: `${SCHEMA_URL}/layouts/xy.json#/$defs/direction` }, "ascending", "ascending: values grow rightwards (x) or upwards (y). descending: the reverse."),
318
+ label: {
319
+ type: "string",
320
+ description: "Axis title. Defaults to the field name."
321
+ }
322
+ }
323
+ },
324
+ direction: {
325
+ title: "Axis direction",
326
+ enum: ["ascending", "descending"]
327
+ }
328
+ }
329
+ }
330
+ ];
331
+ //#endregion
332
+ //#region src/core/definition.ts
333
+ var MapViewDefinitionError = class extends Error {
334
+ constructor(name, message) {
335
+ super(`Map view "${name}": ${message}`);
336
+ this.name = "MapViewDefinitionError";
337
+ }
338
+ };
339
+ var newAjv = (options = {}) => new Ajv2020({
340
+ allErrors: true,
341
+ discriminator: true,
342
+ ...options,
343
+ schemas: MAP_VIEW_SCHEMAS.map((s) => structuredClone(s))
344
+ });
345
+ /** Validates definitions as authored. */
346
+ var ajv = newAjv();
347
+ var validateDefinition = ajv.getSchema(definitionSchema.$id);
348
+ /** Validates resolved definitions, filling in setting defaults from the schemas. */
349
+ var fillDefaults = newAjv({ useDefaults: true }).getSchema(definitionSchema.$id);
350
+ /**
351
+ * Parse a JSON5 map view definition and check it against the bundled schemas. `name` labels
352
+ * error messages (hosts pass where the definition came from).
353
+ *
354
+ * Beyond schema validation: every parameter needs a control derivable from its schema, and
355
+ * every value that control can produce must satisfy the parameter's schema; every `$param`
356
+ * must name a declared parameter; and each parameter's values must be valid wherever it's
357
+ * bound, checked by substituting them and re-validating.
358
+ */
359
+ function parseMapViewDefinition(name, text) {
360
+ const fail = (message) => {
361
+ throw new MapViewDefinitionError(name, message);
362
+ };
363
+ let raw;
364
+ try {
365
+ raw = JSON5.parse(text);
366
+ } catch (err) {
367
+ return fail(`invalid JSON5: ${err instanceof Error ? err.message : String(err)}`);
368
+ }
369
+ if (!validateDefinition(raw)) return fail(formatErrors(validateDefinition.errors, raw));
370
+ const source = raw;
371
+ const parameterAjv = newAjv();
372
+ const declared = source.parameters ?? {};
373
+ const parameters = Object.entries(declared).map(([paramName, schema]) => parseParameter(paramName, schema, parameterAjv, (message) => fail(`parameter "${paramName}": ${message}`)));
374
+ for (const bound of paramNames(source.regions)) if (!(bound in declared)) fail(`"$param": "${bound}" is not a declared parameter`);
375
+ checkBindings(source, parameters, fail);
376
+ const regionIds = /* @__PURE__ */ new Set();
377
+ for (const region of source.regions) {
378
+ if (regionIds.has(region.id)) fail(`duplicate region id "${region.id}"`);
379
+ regionIds.add(region.id);
380
+ if (region.layout.type === "landscape" && !("type" in region.layout.group.at(-1))) fail(`region "${region.id}": the last grouping level must be a node type, not a field`);
381
+ if (region.layout.type === "xy") for (const axis of ["x", "y"]) {
382
+ const { domain } = region.layout[axis];
383
+ if (Array.isArray(domain) && !(domain[0] < domain[1])) fail(`region "${region.id}": ${axis} domain must be [min, max] with min < max`);
384
+ }
385
+ }
386
+ return {
387
+ id: source.$id,
388
+ title: source.title ?? source.$id.slice(source.$id.lastIndexOf("/") + 1),
389
+ ...source.description !== void 0 && { description: source.description },
390
+ schema: source.schema,
391
+ parameters,
392
+ source
393
+ };
394
+ }
395
+ /**
396
+ * The value each parameter takes: the given value if its control could produce it, otherwise
397
+ * (missing, or stale saved state) the default. Hosts' controls and rendering both use this.
398
+ */
399
+ function resolveParameterValues(definition, values = {}) {
400
+ return Object.fromEntries(definition.parameters.map((param) => {
401
+ const value = values[param.name];
402
+ const chosen = value !== void 0 && isProducible(param.control, value) ? value : param.default;
403
+ return [param.name, structuredClone(chosen)];
404
+ }));
405
+ }
406
+ /** Apply parameter values to a definition's regions and fill in setting defaults. */
407
+ function resolveRegions(definition, values = {}) {
408
+ const resolved = resolveParameterValues(definition, values);
409
+ const filled = substitute(definition.source, (paramName) => resolved[paramName]);
410
+ if (!fillDefaults(filled)) throw new MapViewDefinitionError(definition.id, `resolved definition is invalid: ${formatErrors(fillDefaults.errors, filled)}`);
411
+ return filled.regions;
412
+ }
413
+ /**
414
+ * The filter selecting the nodes a map view shows, if any. Hosts apply it when loading the space.
415
+ * PROTOTYPE: filters are evaluated by the host, not per region in the browser, because
416
+ * structured-context's filter evaluator can't yet be bundled for a browser (its only entry point,
417
+ * `structured-context/api`, imports Node built-ins). That's fine while only the first region renders.
418
+ */
419
+ function selectionFilter(definition, values = {}) {
420
+ return resolveRegions(definition, values)[0]?.select?.filter;
421
+ }
422
+ /** Map view definitions that apply to a space with the given schema. */
423
+ function mapViewsForSchema(definitions, schemaId) {
424
+ return schemaId === void 0 ? [] : definitions.filter((d) => d.schema === schemaId);
425
+ }
426
+ /** Resolve `$ref`s relative to the format schemas, so parameters can write `settings.json#/$defs/orientation`. */
427
+ function absoluteRefs(node) {
428
+ if (Array.isArray(node)) return node.map(absoluteRefs);
429
+ if (typeof node !== "object" || node === null) return node;
430
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, key === "$ref" && typeof value === "string" ? new URL(value, `${SCHEMA_URL}/`).href : absoluteRefs(value)]));
431
+ }
432
+ function parseParameter(name, schema, parameterAjv, fail) {
433
+ const { title, description, default: defaultValue, ...authored } = schema;
434
+ const valueSchema = absoluteRefs(authored);
435
+ let validate;
436
+ try {
437
+ validate = parameterAjv.compile(valueSchema);
438
+ } catch (err) {
439
+ return fail(`invalid schema: ${err instanceof Error ? err.message : String(err)}`);
440
+ }
441
+ if (!validate(defaultValue)) return fail(`default ${JSON.stringify(defaultValue)} doesn't match its schema`);
442
+ const control = controlFor(valueSchema, fail);
443
+ const invalid = producibleValues(control).filter((value) => !validate(value));
444
+ if (invalid.length > 0) fail(`its ${control.kind} control can produce ${invalid.map((v) => JSON.stringify(v)).join(", ")}, which its schema rejects; remove constraints the control can't enforce`);
445
+ return {
446
+ name,
447
+ title: typeof title === "string" ? title : titleFromRef(valueSchema) ?? name,
448
+ ...typeof description === "string" && { description },
449
+ default: defaultValue,
450
+ control
451
+ };
452
+ }
453
+ /**
454
+ * Values a control can produce, as checked against schemas. For a multi-select that's none,
455
+ * each option alone, and all of them. PROTOTYPE: other subsets are assumed valid if these are,
456
+ * which holds for the constraints in use (enum items, uniqueItems) but not, say, `contains`.
457
+ */
458
+ function producibleValues(control) {
459
+ switch (control.kind) {
460
+ case "toggle": return [true, false];
461
+ case "select": return [...control.options];
462
+ case "multiselect": return [
463
+ [],
464
+ ...control.options.map((option) => [option]),
465
+ [...control.options]
466
+ ];
467
+ }
468
+ }
469
+ /**
470
+ * Every `$param` binding must accept each value the parameter's control can produce.
471
+ * PROTOTYPE: parameters are checked one at a time, with the others at their defaults, which
472
+ * assumes their bindings don't constrain each other.
473
+ */
474
+ function checkBindings(source, parameters, fail) {
475
+ const defaults = new Map(parameters.map((p) => [p.name, p.default]));
476
+ for (const param of parameters) for (const candidate of producibleValues(param.control)) {
477
+ const substituted = substitute(source, (n) => n === param.name ? candidate : defaults.get(n));
478
+ if (!validateDefinition(substituted)) fail(`parameter "${param.name}" value ${JSON.stringify(candidate)} isn't valid where it's used: ${formatErrors(validateDefinition.errors, substituted)}`);
479
+ }
480
+ }
481
+ /**
482
+ * Derive a control from a parameter's value schema: `enum` (directly, via `$ref`, or narrowed
483
+ * with `allOf`) gives a select, an array of such items a multi-select, `boolean` a toggle.
484
+ */
485
+ function controlFor(schema, fail) {
486
+ if (typeOf(schema) === "boolean") return { kind: "toggle" };
487
+ const isArray = typeOf(schema) === "array";
488
+ const options = isArray ? intersect(itemSchemas(schema).map(enumOf)) : enumOf(schema);
489
+ if (!options) return fail("no control can be generated: give it enum options (directly, or via $ref/allOf), array items with enum options, or type boolean");
490
+ if (options.length === 0) return fail("its options narrow to nothing");
491
+ if (!options.every(isPrimitive)) return fail("options must be strings, numbers, booleans or null");
492
+ return {
493
+ kind: isArray ? "multiselect" : "select",
494
+ options
495
+ };
496
+ }
497
+ /** Schemas linked from this one through `$ref` and `allOf`. */
498
+ function linked(schema) {
499
+ const ref = typeof schema.$ref === "string" ? ajv.getSchema(schema.$ref)?.schema : void 0;
500
+ return [...typeof ref === "object" ? [ref] : [], ...schema.allOf ?? []];
501
+ }
502
+ /** Allowed values, intersecting `enum`/`const` with those reached through `$ref` and `allOf`. */
503
+ function enumOf(schema) {
504
+ return intersect([
505
+ Array.isArray(schema.enum) ? schema.enum : void 0,
506
+ "const" in schema ? [schema.const] : void 0,
507
+ ...linked(schema).map(enumOf)
508
+ ]);
509
+ }
510
+ function typeOf(schema) {
511
+ if (schema.type !== void 0) return schema.type;
512
+ return linked(schema).map(typeOf).find((type) => type !== void 0);
513
+ }
514
+ /** Every `items` schema applying to an array, so their constraints can be intersected. */
515
+ function itemSchemas(schema) {
516
+ return [...typeof schema.items === "object" ? [schema.items] : [], ...linked(schema).flatMap(itemSchemas)];
517
+ }
518
+ function intersect(sets) {
519
+ const defined = sets.filter((set) => set !== void 0);
520
+ if (defined.length === 0) return void 0;
521
+ const key = (v) => JSON.stringify(v);
522
+ return defined.reduce((acc, set) => acc.filter((v) => set.some((w) => key(w) === key(v))));
523
+ }
524
+ function titleFromRef(schema) {
525
+ const title = linked(schema)[0]?.title;
526
+ return typeof title === "string" ? title : void 0;
527
+ }
528
+ function isPrimitive(value) {
529
+ return value === null || [
530
+ "string",
531
+ "number",
532
+ "boolean"
533
+ ].includes(typeof value);
534
+ }
535
+ function isProducible(control, value) {
536
+ switch (control.kind) {
537
+ case "toggle": return typeof value === "boolean";
538
+ case "select": return control.options.includes(value);
539
+ case "multiselect": return Array.isArray(value) && value.every((v) => control.options.includes(v));
540
+ }
541
+ }
542
+ function isParamRef(value) {
543
+ return typeof value === "object" && value !== null && typeof value.$param === "string";
544
+ }
545
+ /** Copy a JSON value, replacing each `{ $param }` binding with its value. */
546
+ function substitute(value, valueFor) {
547
+ if (isParamRef(value)) return structuredClone(valueFor(value.$param));
548
+ if (Array.isArray(value)) return value.map((v) => substitute(v, valueFor));
549
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, substitute(v, valueFor)]));
550
+ return value;
551
+ }
552
+ function paramNames(value) {
553
+ if (isParamRef(value)) return [value.$param];
554
+ if (Array.isArray(value)) return value.flatMap(paramNames);
555
+ if (typeof value === "object" && value !== null) return Object.values(value).flatMap(paramNames);
556
+ return [];
557
+ }
558
+ /**
559
+ * Readable Ajv errors. A setting accepts a value or a `{ $param }` binding (`anyOf`), so a bad
560
+ * value also fails the binding branch; those errors, and the bare anyOf summary, are dropped
561
+ * unless the value actually is a binding. An `if` failure only repeats its branch's errors.
562
+ */
563
+ function formatErrors(errors, instance) {
564
+ return (errors ?? []).filter((e) => e.keyword !== "anyOf" && e.keyword !== "if" && !(e.schemaPath.includes("/$defs/paramRef") && !isParamRef(at(instance, e.instancePath)))).map((e) => `${e.instancePath || "/"} ${message(e)}${detail(e)}`).join("; ");
565
+ }
566
+ /** The value at a JSON pointer. */
567
+ function at(value, pointer) {
568
+ return pointer.split("/").slice(1).map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")).reduce((node, key) => typeof node === "object" && node !== null ? node[key] : void 0, value);
569
+ }
570
+ function message(error) {
571
+ if (error.keyword === "discriminator" && error.params.error === "mapping") return `has unknown ${error.params.tag} ${JSON.stringify(error.params.tagValue)}`;
572
+ return error.message;
573
+ }
574
+ function detail(error) {
575
+ if (error.keyword === "additionalProperties") return ` ("${error.params.additionalProperty}")`;
576
+ if (error.keyword === "enum") return ` (${error.params.allowedValues.map((v) => JSON.stringify(v)).join(", ")})`;
577
+ return "";
578
+ }
579
+ //#endregion
580
+ export { resolveRegions as a, resolveParameterValues as i, mapViewsForSchema as n, selectionFilter as o, parseMapViewDefinition as r, MAP_VIEW_SCHEMAS as s, MapViewDefinitionError as t };
581
+
582
+ //# sourceMappingURL=definition-KuPoA1mO.js.map