@riebeckite/plugin-map 0.0.6

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/dist/index.js ADDED
@@ -0,0 +1,628 @@
1
+ // index.ts
2
+ import {
3
+ createClientEntry,
4
+ createStyleAsset,
5
+ definePlugin
6
+ } from "@riebeckite/core";
7
+
8
+ // src/options.ts
9
+ var DEFAULT_MAP_CLASS_NAME = "rr-map";
10
+ var DEFAULT_MAP_HEIGHT = 320;
11
+ var DEFAULT_MAP_LANGUAGE = "map";
12
+ var DEFAULT_MAP_ZOOM = 13;
13
+ var DEFAULT_MAP_MIN_ZOOM = 1;
14
+ var DEFAULT_MAP_MAX_ZOOM = 19;
15
+ var DEFAULT_MAP_FRONTMATTER_KEY = "map";
16
+ var DEFAULT_MAP_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
17
+ var DEFAULT_MAP_ATTRIBUTION = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
18
+ function resolveMapOptions(options = {}) {
19
+ return {
20
+ language: normalizeText(options.language, DEFAULT_MAP_LANGUAGE),
21
+ className: normalizeText(options.className, DEFAULT_MAP_CLASS_NAME),
22
+ height: normalizePositive(options.height, DEFAULT_MAP_HEIGHT),
23
+ zoom: clampZoom(options.zoom ?? DEFAULT_MAP_ZOOM),
24
+ minZoom: clampZoom(options.minZoom ?? DEFAULT_MAP_MIN_ZOOM, 0),
25
+ maxZoom: clampZoom(options.maxZoom ?? DEFAULT_MAP_MAX_ZOOM, 0),
26
+ tileUrl: normalizeText(options.tileUrl, DEFAULT_MAP_TILE_URL),
27
+ attribution: normalizeText(options.attribution, DEFAULT_MAP_ATTRIBUTION),
28
+ fallback: options.fallback !== false,
29
+ staticFallback: options.staticFallback !== false,
30
+ staticImageUrl: normalizeOptionalText(options.staticImageUrl),
31
+ frontmatterKey: normalizeText(
32
+ options.frontmatterKey,
33
+ DEFAULT_MAP_FRONTMATTER_KEY
34
+ )
35
+ };
36
+ }
37
+ function normalizeText(value, fallback) {
38
+ if (typeof value !== "string") return fallback;
39
+ const trimmed = value.trim();
40
+ return trimmed === "" ? fallback : trimmed;
41
+ }
42
+ function normalizeOptionalText(value) {
43
+ if (typeof value !== "string") return null;
44
+ const trimmed = value.trim();
45
+ return trimmed === "" ? null : trimmed;
46
+ }
47
+ function normalizePositive(value, fallback) {
48
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
49
+ }
50
+ function clampZoom(value, minimum = 1) {
51
+ if (!Number.isFinite(value)) return minimum;
52
+ const rounded = Math.round(value);
53
+ if (rounded < minimum) return minimum;
54
+ if (rounded > 19) return 19;
55
+ return rounded;
56
+ }
57
+
58
+ // src/hast.ts
59
+ function visitElements(node, visitor, parent, index) {
60
+ if (isElementNode(node)) visitor(node, parent, index);
61
+ for (const [childIndex, child] of [...getChildren(node)].entries()) {
62
+ visitElements(
63
+ child,
64
+ visitor,
65
+ hasChildren(node) ? node : parent,
66
+ childIndex
67
+ );
68
+ }
69
+ }
70
+ function element(tagName, properties = {}, children = []) {
71
+ return { type: "element", tagName, properties, children };
72
+ }
73
+ function text(value) {
74
+ return { type: "text", value };
75
+ }
76
+ function getTextContent(node) {
77
+ if (node.type === "text" && "value" in node && typeof node.value === "string") {
78
+ return node.value;
79
+ }
80
+ return getChildren(node).map(getTextContent).join("");
81
+ }
82
+ function getStringProperty(node, key) {
83
+ const value = node.properties?.[key];
84
+ if (typeof value === "string") return value;
85
+ if (Array.isArray(value)) return value.join(" ");
86
+ return null;
87
+ }
88
+ function hasClass(node, className) {
89
+ const value = node.properties?.className;
90
+ const classes = Array.isArray(value) ? value.map(String) : typeof value === "string" ? value.split(/\s+/) : [];
91
+ return classes.includes(className);
92
+ }
93
+ function isElementNode(node) {
94
+ return node.type === "element";
95
+ }
96
+ function hasChildren(node) {
97
+ return Array.isArray(node.children);
98
+ }
99
+ function getChildren(node) {
100
+ const children = node.children;
101
+ return Array.isArray(children) ? children : [];
102
+ }
103
+
104
+ // src/parse.ts
105
+ var KEY_VALUE = /^([A-Za-z][\w-]*)\s*:\s*(.*)$/;
106
+ var BULLET = /^\s*[-*]\s+(.*)$/;
107
+ var COORDINATES = /^(-?\d+(?:\.\d+)?)\s*[,\s]\s*(-?\d+(?:\.\d+)?)$/;
108
+ function parseMapSource(source) {
109
+ const markers = [];
110
+ let center;
111
+ let zoom;
112
+ let label;
113
+ let tiles;
114
+ let attribution;
115
+ let image;
116
+ for (const rawLine of source.split(/\r?\n/)) {
117
+ const line = rawLine.trim();
118
+ if (line === "" || line.startsWith("#")) continue;
119
+ const bullet = BULLET.exec(rawLine);
120
+ if (bullet) {
121
+ const marker = parseMarker(bullet[1]);
122
+ if (marker) markers.push(marker);
123
+ continue;
124
+ }
125
+ const keyValue = KEY_VALUE.exec(line);
126
+ if (keyValue) {
127
+ const key = keyValue[1].toLowerCase();
128
+ const value = keyValue[2].trim();
129
+ switch (key) {
130
+ case "center": {
131
+ const parsed = parseCoordinates(value);
132
+ if (parsed) center = parsed;
133
+ break;
134
+ }
135
+ case "zoom": {
136
+ const parsed = parseZoom(value);
137
+ if (parsed !== void 0) zoom = parsed;
138
+ break;
139
+ }
140
+ case "label":
141
+ case "title":
142
+ case "place":
143
+ label = value || void 0;
144
+ break;
145
+ case "tiles":
146
+ case "tile":
147
+ case "tileurl":
148
+ tiles = value || void 0;
149
+ break;
150
+ case "attribution":
151
+ attribution = value || void 0;
152
+ break;
153
+ case "image":
154
+ case "staticimage":
155
+ image = value || void 0;
156
+ break;
157
+ default:
158
+ break;
159
+ }
160
+ continue;
161
+ }
162
+ const coordinates = parseCoordinates(line);
163
+ if (coordinates) markers.push(coordinates);
164
+ }
165
+ if (markers.length === 0 && !center) return null;
166
+ const data = { markers };
167
+ if (center) data.center = center;
168
+ else if (markers[0])
169
+ data.center = { lat: markers[0].lat, lng: markers[0].lng };
170
+ if (zoom !== void 0) data.zoom = zoom;
171
+ if (label) data.label = label;
172
+ if (tiles) data.tiles = tiles;
173
+ if (attribution) data.attribution = attribution;
174
+ if (image) data.image = image;
175
+ return data;
176
+ }
177
+ function normalizeMapInput(value) {
178
+ if (typeof value === "string") {
179
+ const direct = parseCoordinates(value);
180
+ return direct ? { markers: [direct], center: direct } : null;
181
+ }
182
+ if (Array.isArray(value)) {
183
+ const pair = coordinateFromArray(value);
184
+ if (pair) return { markers: [pair], center: pair };
185
+ const markers2 = value.map((item) => markerFromUnknown(item)).filter((marker) => marker !== null);
186
+ if (markers2.length === 0) return null;
187
+ return { markers: markers2, center: { lat: markers2[0].lat, lng: markers2[0].lng } };
188
+ }
189
+ if (!isRecord(value)) return null;
190
+ const markers = Array.isArray(value.markers) ? value.markers.map((item) => markerFromUnknown(item)).filter((marker) => marker !== null) : [];
191
+ const center = (readNumber(value, "lat", "latitude") !== void 0 && readNumber(value, "lng", "lon", "longitude") !== void 0 ? {
192
+ lat: readNumber(value, "lat", "latitude"),
193
+ lng: readNumber(value, "lng", "lon", "longitude")
194
+ } : void 0) ?? parseCoordinatesValue(value.coordinates) ?? (markers[0] ? { lat: markers[0].lat, lng: markers[0].lng } : void 0);
195
+ if (!center && markers.length === 0) return null;
196
+ const data = { markers };
197
+ if (center) data.center = center;
198
+ const zoom = readNumber(value, "zoom");
199
+ if (zoom !== void 0) {
200
+ const normalized = normalizeZoom(zoom);
201
+ if (normalized !== void 0) data.zoom = normalized;
202
+ }
203
+ const label = readString(value, "label", "title", "place", "name");
204
+ if (label) data.label = label;
205
+ const tiles = readString(value, "tiles", "tile", "tileUrl", "tile_url");
206
+ if (tiles) data.tiles = tiles;
207
+ const attribution = readString(value, "attribution");
208
+ if (attribution) data.attribution = attribution;
209
+ const image = readString(value, "image", "staticImage", "static_image");
210
+ if (image) data.image = image;
211
+ return data;
212
+ }
213
+ function formatCoordinates(coordinates) {
214
+ return `${formatNumber(coordinates.lat)}, ${formatNumber(coordinates.lng)}`;
215
+ }
216
+ function describeMap(data) {
217
+ if (data.label) return data.label;
218
+ const named = data.markers.find((marker) => marker.label);
219
+ if (named?.label) return named.label;
220
+ if (data.center) return formatCoordinates(data.center);
221
+ return "";
222
+ }
223
+ function openStreetMapUrl(coordinates, zoom) {
224
+ const level = normalizeZoom(zoom) ?? 13;
225
+ return `https://www.openstreetmap.org/?mlat=${coordinates.lat}&mlon=${coordinates.lng}#map=${level}/${coordinates.lat}/${coordinates.lng}`;
226
+ }
227
+ function parseMarker(value) {
228
+ const [coordinatesPart, label, description] = value.split("|").map((part) => part.trim());
229
+ const coordinates = parseCoordinates(coordinatesPart ?? "");
230
+ if (!coordinates) return null;
231
+ const marker = { ...coordinates };
232
+ if (label) marker.label = label;
233
+ if (description) marker.description = description;
234
+ return marker;
235
+ }
236
+ function markerFromUnknown(value) {
237
+ const stringMarker = typeof value === "string" ? parseMarker(value) : null;
238
+ if (stringMarker) return stringMarker;
239
+ if (Array.isArray(value)) {
240
+ const coordinates2 = coordinateFromArray(value);
241
+ if (!coordinates2) return null;
242
+ const label2 = typeof value[2] === "string" ? value[2].trim() : "";
243
+ const marker2 = { ...coordinates2 };
244
+ if (label2) marker2.label = label2;
245
+ return marker2;
246
+ }
247
+ if (!isRecord(value)) return null;
248
+ const lat = readNumber(value, "lat", "latitude");
249
+ const lng = readNumber(value, "lng", "lon", "longitude");
250
+ const coordinates = lat !== void 0 && lng !== void 0 ? validateCoordinates(lat, lng) : parseCoordinatesValue(value.coordinates);
251
+ if (!coordinates) return null;
252
+ const marker = { ...coordinates };
253
+ const label = readString(value, "label", "title", "name");
254
+ if (label) marker.label = label;
255
+ const description = readString(value, "description", "desc");
256
+ if (description) marker.description = description;
257
+ return marker;
258
+ }
259
+ function parseCoordinatesValue(value) {
260
+ if (typeof value === "string") return parseCoordinates(value);
261
+ if (Array.isArray(value)) return coordinateFromArray(value);
262
+ if (isRecord(value)) {
263
+ const lat = readNumber(value, "lat", "latitude");
264
+ const lng = readNumber(value, "lng", "lon", "longitude");
265
+ if (lat !== void 0 && lng !== void 0)
266
+ return validateCoordinates(lat, lng);
267
+ }
268
+ return null;
269
+ }
270
+ function coordinateFromArray(value) {
271
+ const lat = typeof value[0] === "number" ? value[0] : void 0;
272
+ const lng = typeof value[1] === "number" ? value[1] : void 0;
273
+ if (lat === void 0 || lng === void 0) return null;
274
+ return validateCoordinates(lat, lng);
275
+ }
276
+ function parseCoordinates(value) {
277
+ const normalized = value.trim().replace(/^[[(]\s*/, "").replace(/\s*[\])]$/, "").trim();
278
+ const match = COORDINATES.exec(normalized);
279
+ if (!match) return null;
280
+ return validateCoordinates(Number(match[1]), Number(match[2]));
281
+ }
282
+ function validateCoordinates(lat, lng) {
283
+ if (!Number.isFinite(lat) || !Number.isFinite(lng)) return null;
284
+ if (lat < -90 || lat > 90) return null;
285
+ if (lng < -180 || lng > 180) return null;
286
+ return { lat, lng };
287
+ }
288
+ function parseZoom(value) {
289
+ const parsed = Number(value.trim());
290
+ if (!Number.isFinite(parsed)) return void 0;
291
+ return normalizeZoom(parsed);
292
+ }
293
+ function normalizeZoom(value) {
294
+ if (!Number.isFinite(value)) return void 0;
295
+ const rounded = Math.round(value);
296
+ if (rounded < 0) return 0;
297
+ if (rounded > 19) return 19;
298
+ return rounded;
299
+ }
300
+ function readNumber(record, ...keys) {
301
+ for (const key of keys) {
302
+ const value = record[key];
303
+ if (typeof value === "number" && Number.isFinite(value)) return value;
304
+ if (typeof value === "string" && value.trim() !== "") {
305
+ const parsed = Number(value);
306
+ if (Number.isFinite(parsed)) return parsed;
307
+ }
308
+ }
309
+ return void 0;
310
+ }
311
+ function readString(record, ...keys) {
312
+ for (const key of keys) {
313
+ const value = record[key];
314
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
315
+ }
316
+ return void 0;
317
+ }
318
+ function formatNumber(value) {
319
+ return String(Math.round(value * 1e5) / 1e5);
320
+ }
321
+ function isRecord(value) {
322
+ return typeof value === "object" && value !== null && !Array.isArray(value);
323
+ }
324
+
325
+ // src/rehype.ts
326
+ var DIAGNOSTIC_SOURCE = "@riebeckite/plugin-map";
327
+ var STATIC_IMAGE_WIDTH = 640;
328
+ function rehypeMap(options = {}) {
329
+ const resolved = resolveMapOptions(options);
330
+ return (tree, file) => {
331
+ visitElements(tree, (node, parent, index) => {
332
+ if (!parent || index === void 0) return;
333
+ if (!isMapCodeBlock(node, resolved.language)) return;
334
+ replaceMapBlock(parent, index, node, file, resolved);
335
+ });
336
+ const frontmatter = readFrontmatter(file);
337
+ if (frontmatter) {
338
+ const data = normalizeMapInput(frontmatter[resolved.frontmatterKey]);
339
+ if (data) {
340
+ const figure = buildFigure({
341
+ data,
342
+ options: resolved,
343
+ caption: data.label ?? null
344
+ });
345
+ getRootChildren(tree).unshift(figure);
346
+ }
347
+ }
348
+ };
349
+ }
350
+ function replaceMapBlock(parent, index, pre, file, options) {
351
+ const code = findDirectChild(pre, "code");
352
+ const source = code ? getTextContent(code).trim() : getTextContent(pre).trim();
353
+ const data = parseMapSource(source);
354
+ if (data === null) {
355
+ reportDiagnostic(
356
+ file,
357
+ "Expected at least one coordinate or a `center` value."
358
+ );
359
+ return;
360
+ }
361
+ const caption = extractCaption(pre, code) ?? data.label ?? null;
362
+ parent.children = parent.children ?? [];
363
+ parent.children[index] = buildFigure({
364
+ data,
365
+ options,
366
+ caption,
367
+ source
368
+ });
369
+ }
370
+ function buildFigure(input) {
371
+ const { data, options } = input;
372
+ const center = data.center ?? data.markers[0];
373
+ if (!center) {
374
+ return element("div", { className: options.className });
375
+ }
376
+ const tiles = data.tiles ?? options.tileUrl;
377
+ const attribution = data.attribution ?? options.attribution;
378
+ const image = resolveStaticImage(
379
+ data.image ?? options.staticImageUrl,
380
+ center,
381
+ data.zoom ?? options.zoom,
382
+ options.height
383
+ );
384
+ const payload = {
385
+ markers: data.markers,
386
+ center,
387
+ zoom: data.zoom ?? options.zoom,
388
+ label: data.label ?? null,
389
+ tiles,
390
+ attribution,
391
+ image
392
+ };
393
+ const described = describeMap(data);
394
+ const label = input.caption ?? (described || "Map");
395
+ const labelId = `${options.className}-caption`;
396
+ const children = [];
397
+ if (input.caption) {
398
+ children.push(
399
+ element(
400
+ "figcaption",
401
+ { id: labelId, className: `${options.className}__caption` },
402
+ [text(input.caption)]
403
+ )
404
+ );
405
+ }
406
+ children.push(
407
+ element(
408
+ "div",
409
+ {
410
+ className: `${options.className}__canvas`,
411
+ dataRrMapCanvas: "true",
412
+ style: `height:${options.height}px`,
413
+ role: "application",
414
+ ariaLabelledby: input.caption ? labelId : void 0,
415
+ ariaLabel: input.caption ? void 0 : label
416
+ },
417
+ []
418
+ )
419
+ );
420
+ if (options.staticFallback) {
421
+ children.push(buildStatic(data, payload, options.className));
422
+ }
423
+ if (options.fallback && input.source) {
424
+ children.push(
425
+ element("details", { className: `${options.className}__fallback` }, [
426
+ element("summary", {}, [text("Map source")]),
427
+ element("pre", {}, [element("code", {}, [text(input.source)])])
428
+ ])
429
+ );
430
+ }
431
+ return element(
432
+ "figure",
433
+ {
434
+ className: options.className,
435
+ dataRrMap: "pending",
436
+ dataRrMapPayload: JSON.stringify(payload),
437
+ dataRrMapHeight: String(options.height)
438
+ },
439
+ children
440
+ );
441
+ }
442
+ function buildStatic(data, payload, base) {
443
+ const children = [];
444
+ if (payload.image) {
445
+ children.push(
446
+ element("img", {
447
+ className: `${base}__image`,
448
+ src: payload.image,
449
+ alt: describeMap(data) || "Map",
450
+ loading: "lazy",
451
+ decoding: "async"
452
+ })
453
+ );
454
+ }
455
+ if (payload.label) {
456
+ children.push(
457
+ element("p", { className: `${base}__place` }, [text(payload.label)])
458
+ );
459
+ }
460
+ const markers = payload.markers.length > 0 ? payload.markers : [payload.center];
461
+ children.push(
462
+ element(
463
+ "ul",
464
+ { className: `${base}__markers` },
465
+ markers.map((marker) => buildMarker(marker, payload.zoom, base))
466
+ )
467
+ );
468
+ const attribution = stripHtml(payload.attribution);
469
+ if (attribution) {
470
+ children.push(
471
+ element("p", { className: `${base}__attribution` }, [text(attribution)])
472
+ );
473
+ }
474
+ return element(
475
+ "div",
476
+ { className: `${base}__static`, dataRrMapStatic: "true" },
477
+ children
478
+ );
479
+ }
480
+ function buildMarker(marker, zoom, base) {
481
+ const coordinates = formatCoordinates(marker);
482
+ const label = marker.label ? `${marker.label} · ${coordinates}` : coordinates;
483
+ const children = [
484
+ element(
485
+ "a",
486
+ {
487
+ className: `${base}__link`,
488
+ href: openStreetMapUrl(marker, zoom),
489
+ target: "_blank",
490
+ rel: "noopener noreferrer"
491
+ },
492
+ [text(label)]
493
+ )
494
+ ];
495
+ if (marker.description) {
496
+ children.push(
497
+ element("span", { className: `${base}__description` }, [
498
+ text(` — ${marker.description}`)
499
+ ])
500
+ );
501
+ }
502
+ return element("li", { className: `${base}__marker` }, children);
503
+ }
504
+ function resolveStaticImage(template, center, zoom, height) {
505
+ if (!template) return null;
506
+ return template.replaceAll("{lat}", String(center.lat)).replaceAll("{lng}", String(center.lng)).replaceAll("{zoom}", String(zoom)).replaceAll("{width}", String(STATIC_IMAGE_WIDTH)).replaceAll("{height}", String(height));
507
+ }
508
+ function extractCaption(pre, code) {
509
+ const title = getStringProperty(pre, "title") ?? (code ? getStringProperty(code, "title") : null);
510
+ return title?.trim() || null;
511
+ }
512
+ function isMapCodeBlock(node, language) {
513
+ if (node.tagName !== "pre") return false;
514
+ const code = findDirectChild(node, "code");
515
+ return Boolean(code && hasClass(code, `language-${language}`));
516
+ }
517
+ function findDirectChild(node, tagName) {
518
+ return node.children?.find(
519
+ (child) => child.type === "element" && child.tagName === tagName
520
+ ) ?? null;
521
+ }
522
+ function readFrontmatter(file) {
523
+ const matter = file?.data?.matter;
524
+ if (typeof matter !== "object" || matter === null || Array.isArray(matter)) {
525
+ return null;
526
+ }
527
+ return matter;
528
+ }
529
+ function getRootChildren(tree) {
530
+ const children = tree.children;
531
+ if (!Array.isArray(children)) {
532
+ tree.children = [];
533
+ return tree.children;
534
+ }
535
+ return children;
536
+ }
537
+ function reportDiagnostic(file, message) {
538
+ const reporter = file?.message;
539
+ if (typeof reporter !== "function") return;
540
+ const diagnostic = reporter.call(file, `Invalid map source: ${message}`);
541
+ if (diagnostic && typeof diagnostic === "object") {
542
+ Object.assign(diagnostic, {
543
+ source: DIAGNOSTIC_SOURCE,
544
+ ruleId: "invalid-source"
545
+ });
546
+ }
547
+ }
548
+ function stripHtml(value) {
549
+ return value.replace(/<[^>]*>/g, "").replace(/&copy;/gi, "©").replace(/&nbsp;/gi, " ").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/g, "'").replace(/&amp;/gi, "&").replace(/\s+/g, " ").trim();
550
+ }
551
+
552
+ // index.ts
553
+ var MAP_PLUGIN_NAME = "map";
554
+ function map(options = {}) {
555
+ const resolved = resolveMapOptions(options);
556
+ return definePlugin({
557
+ name: MAP_PLUGIN_NAME,
558
+ order: -10,
559
+ options,
560
+ validateOptions: validateMapOptions,
561
+ extendHtmlPipeline: (pipeline) => {
562
+ pipeline.use(rehypeMap, options);
563
+ },
564
+ assets: [createStyleAsset(MAP_PLUGIN_NAME)],
565
+ clientEntries: [
566
+ createClientEntry(MAP_PLUGIN_NAME, "initMap", {
567
+ tileUrl: resolved.tileUrl,
568
+ attribution: resolved.attribution,
569
+ minZoom: resolved.minZoom,
570
+ maxZoom: resolved.maxZoom
571
+ })
572
+ ]
573
+ });
574
+ }
575
+ var mapPlugin = map;
576
+ function validateMapOptions(options) {
577
+ if (!options) return [];
578
+ const issues = [];
579
+ for (const key of [
580
+ "language",
581
+ "className",
582
+ "tileUrl",
583
+ "attribution",
584
+ "staticImageUrl",
585
+ "frontmatterKey"
586
+ ]) {
587
+ const value = options[key];
588
+ if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) {
589
+ issues.push({ path: key, message: "Expected a non-empty string." });
590
+ }
591
+ }
592
+ if (options.height !== void 0 && (typeof options.height !== "number" || !Number.isFinite(options.height) || options.height <= 0)) {
593
+ issues.push({ path: "height", message: "Expected a positive number." });
594
+ }
595
+ for (const key of ["zoom", "minZoom", "maxZoom"]) {
596
+ const value = options[key];
597
+ if (value !== void 0 && (!Number.isInteger(value) || value < 0 || value > 19)) {
598
+ issues.push({
599
+ path: key,
600
+ message: "Expected an integer between 0 and 19."
601
+ });
602
+ }
603
+ }
604
+ for (const key of ["fallback", "staticFallback"]) {
605
+ if (options[key] !== void 0 && typeof options[key] !== "boolean") {
606
+ issues.push({ path: key, message: "Expected a boolean." });
607
+ }
608
+ }
609
+ if (typeof options.minZoom === "number" && typeof options.maxZoom === "number" && options.minZoom > options.maxZoom) {
610
+ issues.push({
611
+ path: "maxZoom",
612
+ message: "Expected maxZoom to be greater than or equal to minZoom."
613
+ });
614
+ }
615
+ return issues;
616
+ }
617
+ export {
618
+ MAP_PLUGIN_NAME,
619
+ describeMap,
620
+ formatCoordinates,
621
+ map,
622
+ mapPlugin,
623
+ normalizeMapInput,
624
+ openStreetMapUrl,
625
+ parseMapSource,
626
+ rehypeMap,
627
+ resolveMapOptions
628
+ };
@@ -0,0 +1,7 @@
1
+ import type { ElementNode, HastNode, ParentNode, TextNode } from "./types.js";
2
+ export declare function visitElements(node: HastNode, visitor: (node: ElementNode, parent?: ParentNode, index?: number) => void, parent?: ParentNode, index?: number): void;
3
+ export declare function element(tagName: string, properties?: Record<string, unknown>, children?: HastNode[]): ElementNode;
4
+ export declare function text(value: string): TextNode;
5
+ export declare function getTextContent(node: HastNode): string;
6
+ export declare function getStringProperty(node: ElementNode, key: string): string | null;
7
+ export declare function hasClass(node: ElementNode, className: string): boolean;
@@ -0,0 +1,11 @@
1
+ import type { MapClientOptions } from "./types.js";
2
+ /**
3
+ * Upgrades every server-emitted `[data-rr-map="pending"]` figure to an
4
+ * interactive Leaflet map.
5
+ *
6
+ * Leaflet is fetched lazily from the CDN only when at least one map is
7
+ * present, so pages without maps pay nothing. The map data travels in
8
+ * `data-rr-map-payload`, which means the page still renders its static
9
+ * fallback when JavaScript is disabled or the CDN is unreachable.
10
+ */
11
+ export declare function initMap(options?: MapClientOptions): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import type { MapOptions, ResolvedMapOptions } from "./types.js";
2
+ /** Default base class for the generated figure. */
3
+ export declare const DEFAULT_MAP_CLASS_NAME = "rr-map";
4
+ /** Default map height in pixels. */
5
+ export declare const DEFAULT_MAP_HEIGHT = 320;
6
+ /** Default fenced-code language. */
7
+ export declare const DEFAULT_MAP_LANGUAGE = "map";
8
+ /** Default zoom level. */
9
+ export declare const DEFAULT_MAP_ZOOM = 13;
10
+ /** Default minimum zoom level. */
11
+ export declare const DEFAULT_MAP_MIN_ZOOM = 1;
12
+ /** Default maximum zoom level (OpenStreetMap's standard maximum). */
13
+ export declare const DEFAULT_MAP_MAX_ZOOM = 19;
14
+ /** Frontmatter property read for coordinates by default. */
15
+ export declare const DEFAULT_MAP_FRONTMATTER_KEY = "map";
16
+ /**
17
+ * Default OpenStreetMap tile template. It is keyless and safe to expose to the
18
+ * browser, but sites with meaningful traffic should follow the tile usage
19
+ * policy or point `tileUrl` at their own tile server.
20
+ */
21
+ export declare const DEFAULT_MAP_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
22
+ /** Attribution required by the default tile provider. */
23
+ export declare const DEFAULT_MAP_ATTRIBUTION = "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors";
24
+ /**
25
+ * Applies defaults to the user-provided options.
26
+ *
27
+ * Shared by the plugin factory (for validation and `publicConfig`) and the
28
+ * rehype transformer (for figure emission), so both agree on one resolution.
29
+ */
30
+ export declare function resolveMapOptions(options?: MapOptions): ResolvedMapOptions;