@tileflow/interactions 0.0.0-bootstrap.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/GENERATED_OUTPUT_LICENSE.md +21 -0
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +95 -0
- package/TRADEMARKS.md +10 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +639 -0
- package/dist/maplibre-entry.d.ts +298 -0
- package/dist/maplibre-entry.js +2892 -0
- package/dist/validation-Cy6Tz3lz.d.ts +506 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
// src/contracts.ts
|
|
2
|
+
import { z } from "zod/mini";
|
|
3
|
+
var tileflowInteractionSchemaVersion = 1;
|
|
4
|
+
var tileflowPoiCategories = [
|
|
5
|
+
"arts-entertainment",
|
|
6
|
+
"education",
|
|
7
|
+
"food-drink",
|
|
8
|
+
"landmark",
|
|
9
|
+
"lodging",
|
|
10
|
+
"medical",
|
|
11
|
+
"park-nature",
|
|
12
|
+
"public-services",
|
|
13
|
+
"religion",
|
|
14
|
+
"retail",
|
|
15
|
+
"sport-leisure",
|
|
16
|
+
"transport",
|
|
17
|
+
"visitor-amenity"
|
|
18
|
+
];
|
|
19
|
+
var tileflowInteractionLimits = {
|
|
20
|
+
maxAnnotations: 1e3,
|
|
21
|
+
maxAriaLabelLength: 256,
|
|
22
|
+
maxBindings: 100,
|
|
23
|
+
maxCategories: 32,
|
|
24
|
+
maxColorLength: 64,
|
|
25
|
+
maxContentTextLength: 4096,
|
|
26
|
+
maxDocumentBytes: 256e3,
|
|
27
|
+
maxDocumentDepth: 64,
|
|
28
|
+
maxDocumentNodes: 2e4,
|
|
29
|
+
maxDocumentProperties: 5e4,
|
|
30
|
+
maxFeatureIdLength: 128,
|
|
31
|
+
maxFieldSelectorLength: 128,
|
|
32
|
+
maxIdLength: 64,
|
|
33
|
+
maxTargetNameLength: 128,
|
|
34
|
+
maxViewNameLength: 64
|
|
35
|
+
};
|
|
36
|
+
function auditTileflowInteractionJsonDocument(input) {
|
|
37
|
+
try {
|
|
38
|
+
const stack = [{ depth: 0, value: input }];
|
|
39
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
40
|
+
if (isObject(input)) seen.add(input);
|
|
41
|
+
let bytes = 0;
|
|
42
|
+
let nodes = 1;
|
|
43
|
+
let properties = 0;
|
|
44
|
+
const consumeBytes = (addition) => {
|
|
45
|
+
if (!Number.isSafeInteger(addition) || addition < 0 || addition > tileflowInteractionLimits.maxDocumentBytes - bytes) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
bytes += addition;
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const enqueue = (value, depth) => {
|
|
52
|
+
if (depth > tileflowInteractionLimits.maxDocumentDepth) return "depth";
|
|
53
|
+
if (isObject(value)) {
|
|
54
|
+
if (seen.has(value)) return "invalid";
|
|
55
|
+
seen.add(value);
|
|
56
|
+
}
|
|
57
|
+
stack.push({ depth, value });
|
|
58
|
+
return void 0;
|
|
59
|
+
};
|
|
60
|
+
while (stack.length > 0) {
|
|
61
|
+
const entry = stack.pop();
|
|
62
|
+
if (!entry) return { ok: false, reason: "invalid" };
|
|
63
|
+
const { depth, value } = entry;
|
|
64
|
+
if (value === null) {
|
|
65
|
+
if (!consumeBytes(4)) return { ok: false, reason: "bytes" };
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (typeof value === "boolean") {
|
|
69
|
+
if (!consumeBytes(value ? 4 : 5)) return { ok: false, reason: "bytes" };
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (typeof value === "number") {
|
|
73
|
+
if (!Number.isFinite(value)) return { ok: false, reason: "invalid" };
|
|
74
|
+
const length = value === 0 ? 1 : String(value).length;
|
|
75
|
+
if (!consumeBytes(length)) return { ok: false, reason: "bytes" };
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
const length = jsonStringByteLength(
|
|
80
|
+
value,
|
|
81
|
+
tileflowInteractionLimits.maxDocumentBytes - bytes
|
|
82
|
+
);
|
|
83
|
+
if (length === void 0) return { ok: false, reason: "bytes" };
|
|
84
|
+
bytes += length;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!isObject(value)) return { ok: false, reason: "invalid" };
|
|
88
|
+
const array = Array.isArray(value);
|
|
89
|
+
const prototype = Object.getPrototypeOf(value);
|
|
90
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) {
|
|
91
|
+
return { ok: false, reason: "invalid" };
|
|
92
|
+
}
|
|
93
|
+
if (array) {
|
|
94
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
95
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor) || typeof lengthDescriptor.value !== "number" || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) {
|
|
96
|
+
return { ok: false, reason: "invalid" };
|
|
97
|
+
}
|
|
98
|
+
const length = lengthDescriptor.value;
|
|
99
|
+
if (length > tileflowInteractionLimits.maxDocumentProperties - properties) {
|
|
100
|
+
return { ok: false, reason: "properties" };
|
|
101
|
+
}
|
|
102
|
+
properties += length;
|
|
103
|
+
if (length > 0 && depth >= tileflowInteractionLimits.maxDocumentDepth) {
|
|
104
|
+
return { ok: false, reason: "depth" };
|
|
105
|
+
}
|
|
106
|
+
const syntaxBytes = length === 0 ? 2 : length + 1;
|
|
107
|
+
if (!consumeBytes(syntaxBytes)) return { ok: false, reason: "bytes" };
|
|
108
|
+
const keys2 = Reflect.ownKeys(value);
|
|
109
|
+
const children2 = [];
|
|
110
|
+
let sawLength = false;
|
|
111
|
+
for (const key of keys2) {
|
|
112
|
+
if (typeof key !== "string") return { ok: false, reason: "invalid" };
|
|
113
|
+
if (key === "length") {
|
|
114
|
+
sawLength = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!isCanonicalArrayIndex(key, length)) return { ok: false, reason: "invalid" };
|
|
118
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
119
|
+
if (!isEnumerableDataDescriptor(descriptor)) return { ok: false, reason: "invalid" };
|
|
120
|
+
children2.push(descriptor.value);
|
|
121
|
+
}
|
|
122
|
+
if (!sawLength || children2.length > length) return { ok: false, reason: "invalid" };
|
|
123
|
+
if (children2.length > tileflowInteractionLimits.maxDocumentNodes - nodes) {
|
|
124
|
+
return { ok: false, reason: "nodes" };
|
|
125
|
+
}
|
|
126
|
+
nodes += children2.length;
|
|
127
|
+
const holes = length - children2.length;
|
|
128
|
+
if (!consumeBytes(holes * 4)) return { ok: false, reason: "bytes" };
|
|
129
|
+
for (const child of children2) {
|
|
130
|
+
const failure = enqueue(child, depth + 1);
|
|
131
|
+
if (failure) return { ok: false, reason: failure };
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const keys = Reflect.ownKeys(value);
|
|
136
|
+
if (keys.length > tileflowInteractionLimits.maxDocumentProperties - properties) {
|
|
137
|
+
return { ok: false, reason: "properties" };
|
|
138
|
+
}
|
|
139
|
+
if (keys.length > tileflowInteractionLimits.maxDocumentNodes - nodes) {
|
|
140
|
+
return { ok: false, reason: "nodes" };
|
|
141
|
+
}
|
|
142
|
+
if (keys.length > 0 && depth >= tileflowInteractionLimits.maxDocumentDepth) {
|
|
143
|
+
return { ok: false, reason: "depth" };
|
|
144
|
+
}
|
|
145
|
+
properties += keys.length;
|
|
146
|
+
nodes += keys.length;
|
|
147
|
+
if (!consumeBytes(keys.length === 0 ? 2 : keys.length * 2 + 1)) {
|
|
148
|
+
return { ok: false, reason: "bytes" };
|
|
149
|
+
}
|
|
150
|
+
const children = [];
|
|
151
|
+
for (const key of keys) {
|
|
152
|
+
if (typeof key !== "string" || key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
153
|
+
return { ok: false, reason: "invalid" };
|
|
154
|
+
}
|
|
155
|
+
const keyBytes = jsonStringByteLength(
|
|
156
|
+
key,
|
|
157
|
+
tileflowInteractionLimits.maxDocumentBytes - bytes
|
|
158
|
+
);
|
|
159
|
+
if (keyBytes === void 0) return { ok: false, reason: "bytes" };
|
|
160
|
+
bytes += keyBytes;
|
|
161
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
162
|
+
if (!isEnumerableDataDescriptor(descriptor)) return { ok: false, reason: "invalid" };
|
|
163
|
+
children.push(descriptor.value);
|
|
164
|
+
}
|
|
165
|
+
for (const child of children) {
|
|
166
|
+
const failure = enqueue(child, depth + 1);
|
|
167
|
+
if (failure) return { ok: false, reason: failure };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { bytes, ok: true };
|
|
171
|
+
} catch {
|
|
172
|
+
return { ok: false, reason: "invalid" };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function isObject(value) {
|
|
176
|
+
return value !== null && typeof value === "object";
|
|
177
|
+
}
|
|
178
|
+
function isEnumerableDataDescriptor(descriptor) {
|
|
179
|
+
return Boolean(descriptor?.enumerable && "value" in descriptor);
|
|
180
|
+
}
|
|
181
|
+
function isCanonicalArrayIndex(key, length) {
|
|
182
|
+
if (!/^(?:0|[1-9]\d*)$/u.test(key)) return false;
|
|
183
|
+
const index = Number(key);
|
|
184
|
+
return Number.isSafeInteger(index) && index >= 0 && index < length && String(index) === key;
|
|
185
|
+
}
|
|
186
|
+
function jsonStringByteLength(value, maximum) {
|
|
187
|
+
let bytes = 2;
|
|
188
|
+
if (bytes > maximum) return void 0;
|
|
189
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
190
|
+
const code = value.charCodeAt(index);
|
|
191
|
+
let addition;
|
|
192
|
+
if (code === 34 || code === 92) addition = 2;
|
|
193
|
+
else if (code <= 31) {
|
|
194
|
+
addition = code === 8 || code === 9 || code === 10 || code === 12 || code === 13 ? 2 : 6;
|
|
195
|
+
} else if (code <= 127) addition = 1;
|
|
196
|
+
else if (code <= 2047) addition = 2;
|
|
197
|
+
else if (code >= 55296 && code <= 56319) {
|
|
198
|
+
const next = value.charCodeAt(index + 1);
|
|
199
|
+
if (next >= 56320 && next <= 57343) {
|
|
200
|
+
addition = 4;
|
|
201
|
+
index += 1;
|
|
202
|
+
} else addition = 6;
|
|
203
|
+
} else if (code >= 56320 && code <= 57343) addition = 6;
|
|
204
|
+
else addition = 3;
|
|
205
|
+
if (addition > maximum - bytes) return void 0;
|
|
206
|
+
bytes += addition;
|
|
207
|
+
}
|
|
208
|
+
return bytes;
|
|
209
|
+
}
|
|
210
|
+
var portableIdPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u;
|
|
211
|
+
var semanticDomainPattern = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/u;
|
|
212
|
+
var fieldSelectorPattern = /^[A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)*$/u;
|
|
213
|
+
var unsafeFieldSegments = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
214
|
+
var safeCssColorPattern = /^(?:#(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})|[a-z]+|(?:color|hsla?|hwb|lab|lch|oklab|oklch|rgba?)\([A-Za-z0-9.,%+\- /]+\))$/iu;
|
|
215
|
+
var portableIdSchema = z.string().check(
|
|
216
|
+
z.minLength(1),
|
|
217
|
+
z.maxLength(tileflowInteractionLimits.maxIdLength),
|
|
218
|
+
z.regex(portableIdPattern, "Expected a portable identifier")
|
|
219
|
+
);
|
|
220
|
+
var targetNameSchema = z.string().check(
|
|
221
|
+
z.trim(),
|
|
222
|
+
z.minLength(1),
|
|
223
|
+
z.maxLength(tileflowInteractionLimits.maxTargetNameLength),
|
|
224
|
+
z.refine((value) => !hasControlCharacters(value), {
|
|
225
|
+
message: "Target names may not contain control characters"
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
function hasControlCharacters(value) {
|
|
229
|
+
return [...value].some((character) => {
|
|
230
|
+
const code = character.charCodeAt(0);
|
|
231
|
+
return code <= 31 || code === 127;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
var coordinateSchema = z.tuple([z.number(), z.number()]).check(
|
|
235
|
+
z.refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
|
|
236
|
+
message: "Expected [longitude, latitude] within world bounds"
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
var featureIdSchema = z.union([
|
|
240
|
+
z.int(),
|
|
241
|
+
z.string().check(z.minLength(1), z.maxLength(tileflowInteractionLimits.maxFeatureIdLength))
|
|
242
|
+
]);
|
|
243
|
+
var tileflowInteractionJsonValueSchema = z.custom(
|
|
244
|
+
(value) => auditTileflowInteractionJsonDocument(value).ok,
|
|
245
|
+
"Expected a bounded, finite JSON value"
|
|
246
|
+
);
|
|
247
|
+
var textContentSchema = z.strictObject({
|
|
248
|
+
kind: z.literal("text"),
|
|
249
|
+
text: z.string().check(z.maxLength(tileflowInteractionLimits.maxContentTextLength))
|
|
250
|
+
});
|
|
251
|
+
var fieldContentSchema = z.strictObject({
|
|
252
|
+
fallback: z.optional(
|
|
253
|
+
z.string().check(z.maxLength(tileflowInteractionLimits.maxContentTextLength))
|
|
254
|
+
),
|
|
255
|
+
field: z.string().check(
|
|
256
|
+
z.minLength(1),
|
|
257
|
+
z.maxLength(tileflowInteractionLimits.maxFieldSelectorLength),
|
|
258
|
+
z.regex(fieldSelectorPattern, "Expected a dotted declarative field selector"),
|
|
259
|
+
z.refine((field) => field.split(".").every((segment) => !unsafeFieldSegments.has(segment)), {
|
|
260
|
+
message: "Field selectors may not contain unsafe object keys"
|
|
261
|
+
})
|
|
262
|
+
),
|
|
263
|
+
kind: z.literal("field")
|
|
264
|
+
});
|
|
265
|
+
var viewContentSchema = z.strictObject({
|
|
266
|
+
kind: z.literal("view"),
|
|
267
|
+
name: z.string().check(
|
|
268
|
+
z.minLength(1),
|
|
269
|
+
z.maxLength(tileflowInteractionLimits.maxViewNameLength),
|
|
270
|
+
z.regex(portableIdPattern, "Expected a portable view name")
|
|
271
|
+
)
|
|
272
|
+
});
|
|
273
|
+
var tileflowInteractionContentSchema = z.discriminatedUnion("kind", [
|
|
274
|
+
textContentSchema,
|
|
275
|
+
fieldContentSchema,
|
|
276
|
+
viewContentSchema
|
|
277
|
+
]);
|
|
278
|
+
var annotationMarkerSchema = z.strictObject({
|
|
279
|
+
color: z.optional(
|
|
280
|
+
z.string().check(
|
|
281
|
+
z.trim(),
|
|
282
|
+
z.minLength(1),
|
|
283
|
+
z.maxLength(tileflowInteractionLimits.maxColorLength),
|
|
284
|
+
z.regex(safeCssColorPattern, "Expected a bounded CSS color without URL or variable input")
|
|
285
|
+
)
|
|
286
|
+
),
|
|
287
|
+
content: z.optional(tileflowInteractionContentSchema)
|
|
288
|
+
});
|
|
289
|
+
var annotationSurfaceSchema = z.strictObject({ content: tileflowInteractionContentSchema });
|
|
290
|
+
var tileflowAnnotationSchema = z.strictObject({
|
|
291
|
+
ariaLabel: z.string().check(z.trim(), z.minLength(1), z.maxLength(tileflowInteractionLimits.maxAriaLabelLength)),
|
|
292
|
+
coordinate: coordinateSchema,
|
|
293
|
+
data: z.optional(tileflowInteractionJsonValueSchema),
|
|
294
|
+
id: portableIdSchema,
|
|
295
|
+
kind: z.literal("marker"),
|
|
296
|
+
marker: z.optional(annotationMarkerSchema),
|
|
297
|
+
popup: z.optional(annotationSurfaceSchema),
|
|
298
|
+
tooltip: z.optional(annotationSurfaceSchema)
|
|
299
|
+
});
|
|
300
|
+
var tileflowAnnotationsSchema = z.array(tileflowAnnotationSchema).check(z.maxLength(tileflowInteractionLimits.maxAnnotations));
|
|
301
|
+
var annotationTargetSelectorSchema = z.strictObject({
|
|
302
|
+
id: portableIdSchema,
|
|
303
|
+
kind: z.literal("annotation")
|
|
304
|
+
});
|
|
305
|
+
var semanticFeatureTargetSelectorSchema = z.strictObject({
|
|
306
|
+
categories: z.optional(
|
|
307
|
+
z.array(z.enum(tileflowPoiCategories)).check(z.minLength(1), z.maxLength(tileflowInteractionLimits.maxCategories))
|
|
308
|
+
),
|
|
309
|
+
domain: z.string().check(
|
|
310
|
+
z.minLength(1),
|
|
311
|
+
z.maxLength(tileflowInteractionLimits.maxTargetNameLength),
|
|
312
|
+
z.regex(semanticDomainPattern, "Expected a dotted semantic domain")
|
|
313
|
+
),
|
|
314
|
+
kind: z.literal("semantic-feature")
|
|
315
|
+
});
|
|
316
|
+
var styleLayerTargetSelectorSchema = z.strictObject({
|
|
317
|
+
kind: z.literal("style-layer"),
|
|
318
|
+
layerId: targetNameSchema
|
|
319
|
+
});
|
|
320
|
+
var mapTargetSelectorSchema = z.strictObject({ kind: z.literal("map") });
|
|
321
|
+
var tileflowInteractionTargetSchema = z.discriminatedUnion("kind", [
|
|
322
|
+
annotationTargetSelectorSchema,
|
|
323
|
+
semanticFeatureTargetSelectorSchema,
|
|
324
|
+
styleLayerTargetSelectorSchema,
|
|
325
|
+
mapTargetSelectorSchema
|
|
326
|
+
]);
|
|
327
|
+
var tileflowInteractionBindingSchema = z.strictObject({
|
|
328
|
+
id: portableIdSchema,
|
|
329
|
+
popup: z.optional(annotationSurfaceSchema),
|
|
330
|
+
target: tileflowInteractionTargetSchema,
|
|
331
|
+
tooltip: z.optional(annotationSurfaceSchema)
|
|
332
|
+
});
|
|
333
|
+
var tileflowInteractionBindingsSchema = z.array(tileflowInteractionBindingSchema).check(z.maxLength(tileflowInteractionLimits.maxBindings));
|
|
334
|
+
var annotationTargetSchema = z.strictObject({
|
|
335
|
+
id: portableIdSchema,
|
|
336
|
+
kind: z.literal("annotation")
|
|
337
|
+
});
|
|
338
|
+
var semanticFeatureTargetSchema = z.strictObject({
|
|
339
|
+
domain: z.string().check(
|
|
340
|
+
z.minLength(1),
|
|
341
|
+
z.maxLength(tileflowInteractionLimits.maxTargetNameLength),
|
|
342
|
+
z.regex(semanticDomainPattern, "Expected a dotted semantic domain")
|
|
343
|
+
),
|
|
344
|
+
featureId: featureIdSchema,
|
|
345
|
+
kind: z.literal("semantic-feature")
|
|
346
|
+
});
|
|
347
|
+
var styleFeatureTargetSchema = z.strictObject({
|
|
348
|
+
featureId: featureIdSchema,
|
|
349
|
+
kind: z.literal("style-feature"),
|
|
350
|
+
layerId: targetNameSchema
|
|
351
|
+
});
|
|
352
|
+
var mapTargetSchema = z.strictObject({ coordinate: coordinateSchema, kind: z.literal("map") });
|
|
353
|
+
var tileflowInteractionTargetRefSchema = z.discriminatedUnion("kind", [
|
|
354
|
+
annotationTargetSchema,
|
|
355
|
+
semanticFeatureTargetSchema,
|
|
356
|
+
styleFeatureTargetSchema,
|
|
357
|
+
mapTargetSchema
|
|
358
|
+
]);
|
|
359
|
+
var tileflowInteractionStateSchema = z.strictObject({
|
|
360
|
+
popup: z.nullable(tileflowInteractionTargetRefSchema)
|
|
361
|
+
});
|
|
362
|
+
var tileflowInteractionActionSchema = z.discriminatedUnion("type", [
|
|
363
|
+
z.strictObject({ target: tileflowInteractionTargetRefSchema, type: z.literal("open-popup") }),
|
|
364
|
+
z.strictObject({ type: z.literal("close-popup") })
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
// src/reducer.ts
|
|
368
|
+
var initialTileflowInteractionState = Object.freeze({
|
|
369
|
+
popup: null
|
|
370
|
+
});
|
|
371
|
+
function reduceTileflowInteractionState(state, action) {
|
|
372
|
+
if (action.type === "close-popup") {
|
|
373
|
+
return state.popup === null ? state : { popup: null };
|
|
374
|
+
}
|
|
375
|
+
if (tileflowInteractionTargetRefsEqual(state.popup, action.target)) {
|
|
376
|
+
return state;
|
|
377
|
+
}
|
|
378
|
+
return { popup: action.target };
|
|
379
|
+
}
|
|
380
|
+
function tileflowInteractionTargetRefsEqual(left, right) {
|
|
381
|
+
if (left === right) return true;
|
|
382
|
+
if (left === null || right === null || left.kind !== right.kind) return false;
|
|
383
|
+
switch (left.kind) {
|
|
384
|
+
case "annotation":
|
|
385
|
+
return left.id === right.id;
|
|
386
|
+
case "semantic-feature": {
|
|
387
|
+
const candidate = right;
|
|
388
|
+
return left.domain === candidate.domain && left.featureId === candidate.featureId;
|
|
389
|
+
}
|
|
390
|
+
case "style-feature": {
|
|
391
|
+
const candidate = right;
|
|
392
|
+
return left.layerId === candidate.layerId && left.featureId === candidate.featureId;
|
|
393
|
+
}
|
|
394
|
+
case "map": {
|
|
395
|
+
const candidate = right;
|
|
396
|
+
return left.coordinate[0] === candidate.coordinate[0] && left.coordinate[1] === candidate.coordinate[1];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/reference.ts
|
|
402
|
+
var tileflowInteractionReference = Object.freeze({
|
|
403
|
+
actions: ["open-popup", "close-popup"],
|
|
404
|
+
annotationKinds: ["marker"],
|
|
405
|
+
contentKinds: ["text", "field", "view"],
|
|
406
|
+
entrypoints: ["@tileflow/interactions", "@tileflow/interactions/maplibre"],
|
|
407
|
+
eventTypes: [
|
|
408
|
+
"target:enter",
|
|
409
|
+
"target:leave",
|
|
410
|
+
"target:focus",
|
|
411
|
+
"target:blur",
|
|
412
|
+
"target:activate",
|
|
413
|
+
"popup:open",
|
|
414
|
+
"popup:close"
|
|
415
|
+
],
|
|
416
|
+
limits: tileflowInteractionLimits,
|
|
417
|
+
semanticDomains: ["poi"],
|
|
418
|
+
state: { popup: "TileflowInteractionTargetRef | null" },
|
|
419
|
+
targetAvailability: {
|
|
420
|
+
annotation: {
|
|
421
|
+
binding: "reserved",
|
|
422
|
+
inlineSurfaces: "available",
|
|
423
|
+
stateReference: "available"
|
|
424
|
+
},
|
|
425
|
+
map: "reserved",
|
|
426
|
+
"semantic-feature": { availableDomains: ["poi"] },
|
|
427
|
+
"style-layer": "reserved"
|
|
428
|
+
},
|
|
429
|
+
targetKinds: ["annotation", "semantic-feature", "style-layer", "map"],
|
|
430
|
+
version: tileflowInteractionSchemaVersion
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// src/validation.ts
|
|
434
|
+
function validateTileflowAnnotations(input) {
|
|
435
|
+
const invalidDocument = () => invalidAnnotationsDocument();
|
|
436
|
+
try {
|
|
437
|
+
const topLevelLength = inspectTopLevelArrayLength(input);
|
|
438
|
+
if (!topLevelLength.ok) return invalidDocument();
|
|
439
|
+
if (topLevelLength.length !== void 0 && topLevelLength.length > tileflowInteractionLimits.maxAnnotations) {
|
|
440
|
+
return {
|
|
441
|
+
diagnostics: [
|
|
442
|
+
diagnostic(
|
|
443
|
+
"LIMIT_EXCEEDED",
|
|
444
|
+
"",
|
|
445
|
+
`Annotations exceed the ${tileflowInteractionLimits.maxAnnotations}-item limit.`
|
|
446
|
+
)
|
|
447
|
+
],
|
|
448
|
+
ok: false
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const audit = auditTileflowInteractionJsonDocument(input);
|
|
452
|
+
if (!audit.ok) {
|
|
453
|
+
return {
|
|
454
|
+
diagnostics: [documentAuditDiagnostic("Annotations", audit, invalidDocumentMessage)],
|
|
455
|
+
ok: false
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
let parsed;
|
|
459
|
+
try {
|
|
460
|
+
parsed = tileflowAnnotationsSchema.safeParse(input);
|
|
461
|
+
} catch {
|
|
462
|
+
return invalidDocument();
|
|
463
|
+
}
|
|
464
|
+
if (!parsed.success) {
|
|
465
|
+
return {
|
|
466
|
+
diagnostics: parsed.error.issues.map(
|
|
467
|
+
(issue) => diagnostic("INVALID_ANNOTATION", jsonPointer(issue.path), issue.message)
|
|
468
|
+
),
|
|
469
|
+
ok: false
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const duplicateDiagnostics = [];
|
|
473
|
+
const firstIndexById = /* @__PURE__ */ new Map();
|
|
474
|
+
for (const [index, annotation] of parsed.data.entries()) {
|
|
475
|
+
const firstIndex = firstIndexById.get(annotation.id);
|
|
476
|
+
if (firstIndex === void 0) {
|
|
477
|
+
firstIndexById.set(annotation.id, index);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
duplicateDiagnostics.push(
|
|
481
|
+
diagnostic(
|
|
482
|
+
"DUPLICATE_ANNOTATION_ID",
|
|
483
|
+
`/${index}/id`,
|
|
484
|
+
`Annotation id "${annotation.id}" duplicates /${firstIndex}/id.`
|
|
485
|
+
)
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (duplicateDiagnostics.length > 0) {
|
|
489
|
+
return { diagnostics: duplicateDiagnostics, ok: false };
|
|
490
|
+
}
|
|
491
|
+
return { annotations: parsed.data, diagnostics: [], ok: true };
|
|
492
|
+
} catch {
|
|
493
|
+
return invalidDocument();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function validateTileflowInteractionBindings(input) {
|
|
497
|
+
const invalidDocument = () => invalidBindingsDocument();
|
|
498
|
+
try {
|
|
499
|
+
const topLevelLength = inspectTopLevelArrayLength(input);
|
|
500
|
+
if (!topLevelLength.ok) return invalidDocument();
|
|
501
|
+
if (topLevelLength.length !== void 0 && topLevelLength.length > tileflowInteractionLimits.maxBindings) {
|
|
502
|
+
return {
|
|
503
|
+
diagnostics: [
|
|
504
|
+
diagnostic(
|
|
505
|
+
"LIMIT_EXCEEDED",
|
|
506
|
+
"",
|
|
507
|
+
`Interaction bindings exceed the ${tileflowInteractionLimits.maxBindings}-item limit.`
|
|
508
|
+
)
|
|
509
|
+
],
|
|
510
|
+
ok: false
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const audit = auditTileflowInteractionJsonDocument(input);
|
|
514
|
+
if (!audit.ok) {
|
|
515
|
+
return {
|
|
516
|
+
diagnostics: [
|
|
517
|
+
documentAuditDiagnostic("Interaction bindings", audit, invalidBindingsDocumentMessage)
|
|
518
|
+
],
|
|
519
|
+
ok: false
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
let parsed;
|
|
523
|
+
try {
|
|
524
|
+
parsed = tileflowInteractionBindingsSchema.safeParse(input);
|
|
525
|
+
} catch {
|
|
526
|
+
return invalidDocument();
|
|
527
|
+
}
|
|
528
|
+
if (!parsed.success) {
|
|
529
|
+
return {
|
|
530
|
+
diagnostics: parsed.error.issues.map(
|
|
531
|
+
(issue) => diagnostic("INVALID_DOCUMENT", jsonPointer(issue.path), issue.message)
|
|
532
|
+
),
|
|
533
|
+
ok: false
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
const firstIndexById = /* @__PURE__ */ new Map();
|
|
537
|
+
const diagnostics = [];
|
|
538
|
+
for (const [index, binding] of parsed.data.entries()) {
|
|
539
|
+
const firstIndex = firstIndexById.get(binding.id);
|
|
540
|
+
if (firstIndex === void 0) {
|
|
541
|
+
firstIndexById.set(binding.id, index);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
diagnostics.push(
|
|
545
|
+
diagnostic(
|
|
546
|
+
"INVALID_DOCUMENT",
|
|
547
|
+
`/${index}/id`,
|
|
548
|
+
`Interaction id "${binding.id}" duplicates /${firstIndex}/id.`
|
|
549
|
+
)
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
return diagnostics.length > 0 ? { diagnostics, ok: false } : { bindings: parsed.data, diagnostics: [], ok: true };
|
|
553
|
+
} catch {
|
|
554
|
+
return invalidDocument();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function inspectTopLevelArrayLength(input) {
|
|
558
|
+
try {
|
|
559
|
+
if (!Array.isArray(input)) return { ok: true };
|
|
560
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, "length");
|
|
561
|
+
if (!descriptor || !("value" in descriptor) || typeof descriptor.value !== "number" || !Number.isSafeInteger(descriptor.value) || descriptor.value < 0) {
|
|
562
|
+
return { ok: false };
|
|
563
|
+
}
|
|
564
|
+
return { length: descriptor.value, ok: true };
|
|
565
|
+
} catch {
|
|
566
|
+
return { ok: false };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
var invalidDocumentMessage = "Annotations must be a finite, acyclic JSON document.";
|
|
570
|
+
var invalidBindingsDocumentMessage = "Interaction bindings must be a finite, acyclic JSON document.";
|
|
571
|
+
function invalidAnnotationsDocument() {
|
|
572
|
+
return {
|
|
573
|
+
diagnostics: [diagnostic("INVALID_DOCUMENT", "", invalidDocumentMessage)],
|
|
574
|
+
ok: false
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function invalidBindingsDocument() {
|
|
578
|
+
return {
|
|
579
|
+
diagnostics: [diagnostic("INVALID_DOCUMENT", "", invalidBindingsDocumentMessage)],
|
|
580
|
+
ok: false
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function documentAuditDiagnostic(subject, audit, invalidMessage) {
|
|
584
|
+
switch (audit.reason) {
|
|
585
|
+
case "bytes":
|
|
586
|
+
return diagnostic(
|
|
587
|
+
"LIMIT_EXCEEDED",
|
|
588
|
+
"",
|
|
589
|
+
`${subject} exceed the ${tileflowInteractionLimits.maxDocumentBytes}-byte document limit.`
|
|
590
|
+
);
|
|
591
|
+
case "depth":
|
|
592
|
+
return diagnostic(
|
|
593
|
+
"LIMIT_EXCEEDED",
|
|
594
|
+
"",
|
|
595
|
+
`${subject} exceed the ${tileflowInteractionLimits.maxDocumentDepth}-level document depth limit.`
|
|
596
|
+
);
|
|
597
|
+
case "nodes":
|
|
598
|
+
return diagnostic(
|
|
599
|
+
"LIMIT_EXCEEDED",
|
|
600
|
+
"",
|
|
601
|
+
`${subject} exceed the ${tileflowInteractionLimits.maxDocumentNodes}-node document limit.`
|
|
602
|
+
);
|
|
603
|
+
case "properties":
|
|
604
|
+
return diagnostic(
|
|
605
|
+
"LIMIT_EXCEEDED",
|
|
606
|
+
"",
|
|
607
|
+
`${subject} exceed the ${tileflowInteractionLimits.maxDocumentProperties}-property document limit.`
|
|
608
|
+
);
|
|
609
|
+
case "invalid":
|
|
610
|
+
return diagnostic("INVALID_DOCUMENT", "", invalidMessage);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function diagnostic(code, path, message) {
|
|
614
|
+
return { code, level: "error", message, path };
|
|
615
|
+
}
|
|
616
|
+
function jsonPointer(path) {
|
|
617
|
+
return path.map((segment) => String(segment).replaceAll("~", "~0").replaceAll("/", "~1")).map((segment) => `/${segment}`).join("");
|
|
618
|
+
}
|
|
619
|
+
export {
|
|
620
|
+
initialTileflowInteractionState,
|
|
621
|
+
reduceTileflowInteractionState,
|
|
622
|
+
tileflowAnnotationSchema,
|
|
623
|
+
tileflowAnnotationsSchema,
|
|
624
|
+
tileflowInteractionActionSchema,
|
|
625
|
+
tileflowInteractionBindingSchema,
|
|
626
|
+
tileflowInteractionBindingsSchema,
|
|
627
|
+
tileflowInteractionContentSchema,
|
|
628
|
+
tileflowInteractionJsonValueSchema,
|
|
629
|
+
tileflowInteractionLimits,
|
|
630
|
+
tileflowInteractionReference,
|
|
631
|
+
tileflowInteractionSchemaVersion,
|
|
632
|
+
tileflowInteractionStateSchema,
|
|
633
|
+
tileflowInteractionTargetRefSchema,
|
|
634
|
+
tileflowInteractionTargetRefsEqual,
|
|
635
|
+
tileflowInteractionTargetSchema,
|
|
636
|
+
tileflowPoiCategories,
|
|
637
|
+
validateTileflowAnnotations,
|
|
638
|
+
validateTileflowInteractionBindings
|
|
639
|
+
};
|