@geolibre/core 2.7.0 → 2.8.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/dist/index.mjs CHANGED
@@ -2270,14 +2270,24 @@ function batchDecodePolylines(input, options = {}) {
2270
2270
  }
2271
2271
  //#endregion
2272
2272
  //#region src/runtime-env.ts
2273
+ const buildEnv = typeof __GEOLIBRE_BUILD_ENV__ === "undefined" ? {} : __GEOLIBRE_BUILD_ENV__;
2273
2274
  /**
2274
- * Resolves runtime environment variables shared by the external-service clients
2275
- * (geocoding, routing). Build-time Vite vars (`import.meta.env`) are overlaid
2276
- * with project-supplied runtime vars (`window.__GEOLIBRE_RUNTIME_ENV__`, set
2277
- * from project preferences) so a self-hosted endpoint can be configured without
2278
- * a rebuild. Carries no React/MapLibre dependency so callers stay unit-testable.
2275
+ * The allowlisted build-time environment, with no runtime overlay.
2276
+ *
2277
+ * For settings that must be fixed by the build and NOT overridable by a
2278
+ * `.geolibre.json` a user opened — the auth gate, the deployment profile, the
2279
+ * embed origin allowlist. Routing those through {@link getRuntimeEnvironment}
2280
+ * would let project-supplied `__GEOLIBRE_RUNTIME_ENV__` values relax them.
2281
+ *
2282
+ * Callers should prefer this over reading `import.meta.env` directly: a
2283
+ * whole-object read defeats Vite's per-key replacement and inlines every
2284
+ * `VITE_` var on the build machine into the chunk.
2285
+ *
2286
+ * @returns The build-time environment record.
2279
2287
  */
2280
- const buildEnv = import.meta.env;
2288
+ function getBuildEnvironment() {
2289
+ return buildEnv;
2290
+ }
2281
2291
  /**
2282
2292
  * Merges build-time env with project runtime env (the latter wins). Falls back
2283
2293
  * to build-time env alone outside a browser (e.g. in tests).
@@ -2285,9 +2295,9 @@ const buildEnv = import.meta.env;
2285
2295
  * @returns The resolved environment variables.
2286
2296
  */
2287
2297
  function getRuntimeEnvironment() {
2288
- if (typeof window === "undefined") return buildEnv ?? {};
2298
+ if (typeof window === "undefined") return buildEnv;
2289
2299
  return {
2290
- ...buildEnv ?? {},
2300
+ ...buildEnv,
2291
2301
  ...window.__GEOLIBRE_RUNTIME_ENV__ ?? {}
2292
2302
  };
2293
2303
  }
@@ -4135,6 +4145,302 @@ function featureForEvaluation(properties, feature) {
4135
4145
  };
4136
4146
  }
4137
4147
  //#endregion
4148
+ //#region src/popup.ts
4149
+ /** Match an inline base64 raster image (excludes SVG, which can carry scripts). */
4150
+ const INLINE_IMAGE_DATA_URL = /^data:image\/(?!svg)[\w.+-]+;base64,/i;
4151
+ /**
4152
+ * A trimmed non-empty string, or `undefined` for anything else.
4153
+ *
4154
+ * Popup configs arrive from untrusted JSON — a hand-edited `.geolibre.json`, an
4155
+ * imported layer-library bundle, an MCP-authored project — so a field typed as
4156
+ * `string` may hold a number, an object or null. `value.trim()` on one of those
4157
+ * throws and takes the whole popup render with it, so every string read here
4158
+ * goes through this.
4159
+ */
4160
+ function trimmedString(value) {
4161
+ if (typeof value !== "string") return void 0;
4162
+ const trimmed = value.trim();
4163
+ return trimmed === "" ? void 0 : trimmed;
4164
+ }
4165
+ /** Whether the Identify popup should open for a layer. Default: yes. */
4166
+ function isPopupClickEnabled(popup) {
4167
+ return popup?.click !== false;
4168
+ }
4169
+ /** Whether a hover tooltip should follow the pointer. Default: no. */
4170
+ function isPopupHoverEnabled(popup) {
4171
+ return popup?.hover === true;
4172
+ }
4173
+ /** The label a popup shows for a configured field. */
4174
+ function popupFieldLabel(config) {
4175
+ return trimmedString(config.label) ?? config.field;
4176
+ }
4177
+ /**
4178
+ * Property keys that never belong in a popup: GeoLibre's own internal columns
4179
+ * and the full-resolution companion of a photo thumbnail (an internal twin of
4180
+ * a value already shown, and often multiple megabytes of data URL).
4181
+ */
4182
+ function isInternalPopupField(key) {
4183
+ return key === "photo_full" || key.startsWith("__geolibre_");
4184
+ }
4185
+ /**
4186
+ * The property keys a popup may show, before any popup config narrows them:
4187
+ * the feature's own keys minus the internal ones and anything the author
4188
+ * marked `"hidden"` or `"excluded"`.
4189
+ */
4190
+ function visiblePopupFields(properties, fieldVisibility) {
4191
+ return Object.keys(properties).filter((key) => !isInternalPopupField(key) && !fieldVisibility?.[key]);
4192
+ }
4193
+ function formatNumber(value, format, locale) {
4194
+ const options = { useGrouping: format.thousands === true };
4195
+ if (format.decimals != null && Number.isFinite(format.decimals)) {
4196
+ const digits = Math.max(0, Math.min(20, Math.trunc(format.decimals)));
4197
+ options.minimumFractionDigits = digits;
4198
+ options.maximumFractionDigits = digits;
4199
+ }
4200
+ return new Intl.NumberFormat(locale, options).format(value);
4201
+ }
4202
+ /**
4203
+ * Coerce a property value to a Date. A number is epoch milliseconds (what a
4204
+ * GeoJSON timestamp column normally holds); a string goes through `Date`'s own
4205
+ * parser. Anything that does not parse comes back as `null` so the caller can
4206
+ * fall back to the raw text rather than printing "Invalid Date".
4207
+ */
4208
+ function toDate(value) {
4209
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
4210
+ if (typeof value === "number" && Number.isFinite(value)) {
4211
+ const fromEpoch = new Date(value);
4212
+ return Number.isNaN(fromEpoch.getTime()) ? null : fromEpoch;
4213
+ }
4214
+ if (typeof value === "string" && value.trim() !== "") {
4215
+ const parsed = new Date(value);
4216
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
4217
+ }
4218
+ return null;
4219
+ }
4220
+ function formatDate(date, dateFormat, locale) {
4221
+ switch (dateFormat) {
4222
+ case "datetime": return date.toLocaleString(locale);
4223
+ case "time": return date.toLocaleTimeString(locale);
4224
+ case "iso": return date.toISOString();
4225
+ case "year": return String(date.getFullYear());
4226
+ default: return date.toLocaleDateString(locale);
4227
+ }
4228
+ }
4229
+ /** Stringify a value the way the untyped popup always has. */
4230
+ function stringifyPopupValue(value) {
4231
+ if (value == null) return "";
4232
+ if (typeof value === "object") return JSON.stringify(value);
4233
+ return String(value);
4234
+ }
4235
+ /**
4236
+ * Render one value as text under a field's kind and format. `"auto"` and
4237
+ * `"image"` values are returned verbatim (the renderer draws them itself);
4238
+ * every other kind is stringified, formatted, then wrapped in the configured
4239
+ * prefix/suffix.
4240
+ */
4241
+ function formatPopupValue(value, config = {}, options = {}) {
4242
+ const kind = config.kind ?? "auto";
4243
+ const format = config.format ?? {};
4244
+ if (kind === "auto" || kind === "image") return stringifyPopupValue(value);
4245
+ if (value == null || typeof value === "string" && value.trim() === "") return "";
4246
+ let body;
4247
+ if (kind === "number") {
4248
+ const numeric = typeof value === "number" ? value : Number(stringifyPopupValue(value));
4249
+ body = Number.isFinite(numeric) ? formatNumber(numeric, format, options.locale) : stringifyPopupValue(value);
4250
+ } else if (kind === "date") {
4251
+ const date = toDate(value);
4252
+ body = date ? formatDate(date, format.dateFormat ?? "date", options.locale) : stringifyPopupValue(value);
4253
+ } else body = stringifyPopupValue(value);
4254
+ if (body === "") return "";
4255
+ return `${format.prefix ?? ""}${body}${format.suffix ?? ""}`;
4256
+ }
4257
+ /** Whether a row would draw as a picture rather than as text. */
4258
+ function rendersAsImage(kind, value) {
4259
+ return kind === "image" || kind === "auto" && isInlineImageValue(value);
4260
+ }
4261
+ /**
4262
+ * The properties a popup may read, with the author's hidden and excluded
4263
+ * fields and GeoLibre's internal columns removed.
4264
+ *
4265
+ * Expressions get this rather than the raw record. `["get", "ssn"]` in a title
4266
+ * or body would otherwise reach a column the author hid, printing it into the
4267
+ * popup — and onto the hover tooltip, which needs no click at all. The rest of
4268
+ * this module already honors `fieldVisibility` field by field; an expression
4269
+ * has no field list to filter, so the filtering has to happen to its input.
4270
+ */
4271
+ function visiblePopupProperties(properties, fieldVisibility) {
4272
+ const visible = {};
4273
+ for (const key of visiblePopupFields(properties, fieldVisibility)) visible[key] = properties[key];
4274
+ return visible;
4275
+ }
4276
+ /**
4277
+ * The rows a popup (or hover tooltip) should render for one feature's
4278
+ * properties.
4279
+ *
4280
+ * With no `popup.fields`, every visible property is returned in the feature's
4281
+ * own key order and left untyped, which is exactly what the popup did before
4282
+ * the designer existed. With a list, only the listed fields appear, in the
4283
+ * listed order, under their labels and formats — and a listed field the
4284
+ * feature does not carry is skipped rather than printed empty, so one config
4285
+ * can cover a layer whose features have ragged properties.
4286
+ *
4287
+ * `hover: true` narrows the list to fields flagged `hover`. A hover request
4288
+ * with no flagged field returns nothing: an author who turned the tooltip on
4289
+ * without choosing a field gets no tip rather than the whole table following
4290
+ * the pointer.
4291
+ */
4292
+ function resolvePopupRows(properties, options = {}) {
4293
+ const { popup, fieldVisibility, hover = false, locale } = options;
4294
+ const configured = (Array.isArray(popup?.fields) ? popup.fields : []).filter((config) => Boolean(config) && typeof config.field === "string");
4295
+ if (configured.length === 0) {
4296
+ if (hover) return [];
4297
+ return visiblePopupFields(properties, fieldVisibility).map((field) => ({
4298
+ field,
4299
+ label: field,
4300
+ value: properties[field],
4301
+ text: stringifyPopupValue(properties[field]),
4302
+ kind: "auto"
4303
+ }));
4304
+ }
4305
+ const rows = [];
4306
+ const seen = /* @__PURE__ */ new Set();
4307
+ for (const config of configured) {
4308
+ if (hover && config.hover !== true) continue;
4309
+ if (fieldVisibility?.[config.field]) continue;
4310
+ if (isInternalPopupField(config.field)) continue;
4311
+ if (!(config.field in properties)) continue;
4312
+ if (seen.has(config.field)) continue;
4313
+ const value = properties[config.field];
4314
+ if (hover && rendersAsImage(config.kind ?? "auto", value)) continue;
4315
+ seen.add(config.field);
4316
+ rows.push({
4317
+ field: config.field,
4318
+ label: popupFieldLabel(config),
4319
+ value,
4320
+ text: formatPopupValue(value, config, { locale }),
4321
+ kind: config.kind ?? "auto",
4322
+ ...trimmedString(config.format?.linkLabel) ? { linkLabel: trimmedString(config.format?.linkLabel) } : {}
4323
+ });
4324
+ }
4325
+ return rows;
4326
+ }
4327
+ /**
4328
+ * Compiled title/body expressions, keyed by zoom and source.
4329
+ *
4330
+ * `createHoverTooltipElement` calls {@link resolvePopupTitle} once per
4331
+ * animation frame for as long as the pointer sits on a hovered feature, and
4332
+ * compiling means a `JSON.parse` plus a full style-spec validation. Without
4333
+ * this the same unchanged source string would be re-parsed sixty times a
4334
+ * second. Zoom is part of the key because a compiled expression bakes it in
4335
+ * for `["zoom"]`; it is constant through a hover, so the cache still hits.
4336
+ */
4337
+ const EXPRESSION_CACHE = /* @__PURE__ */ new Map();
4338
+ /** Bound on the cache, so a project full of layers cannot grow it without end. */
4339
+ const EXPRESSION_CACHE_LIMIT = 64;
4340
+ function compiledPopupExpression(source, zoom) {
4341
+ const key = `${zoom}\u0000${source}`;
4342
+ const cached = EXPRESSION_CACHE.get(key);
4343
+ if (cached) return cached;
4344
+ const compiled = compileFeatureExpression(source, { zoom });
4345
+ if (EXPRESSION_CACHE.size >= EXPRESSION_CACHE_LIMIT) {
4346
+ const oldest = EXPRESSION_CACHE.keys().next().value;
4347
+ if (oldest !== void 0) EXPRESSION_CACHE.delete(oldest);
4348
+ }
4349
+ EXPRESSION_CACHE.set(key, compiled);
4350
+ return compiled;
4351
+ }
4352
+ /**
4353
+ * Evaluate a popup expression against a feature's properties, or return
4354
+ * `undefined` when it does not compile or throws. Both failures mean the same
4355
+ * thing to every caller here: fall back to what the popup would show without
4356
+ * the expression.
4357
+ */
4358
+ function evaluatePopupExpression(source, properties, options) {
4359
+ const compiled = compiledPopupExpression(source, options.zoom ?? 0);
4360
+ if (!compiled.ok || !compiled.evaluate) return void 0;
4361
+ try {
4362
+ return compiled.evaluate(featureFor(visiblePopupProperties(properties, options.fieldVisibility), options.feature));
4363
+ } catch {
4364
+ return;
4365
+ }
4366
+ }
4367
+ /** Build the synthetic feature an expression is evaluated against. */
4368
+ function featureFor(properties, feature) {
4369
+ if (feature) return {
4370
+ ...feature,
4371
+ properties
4372
+ };
4373
+ return {
4374
+ type: "Feature",
4375
+ geometry: null,
4376
+ properties
4377
+ };
4378
+ }
4379
+ /**
4380
+ * The heading a popup should show: the title expression's value, else the
4381
+ * title field's value, else the layer name. An expression that fails to
4382
+ * compile or evaluate, and a title field that is empty or hidden, both fall
4383
+ * back to the layer name rather than leaving the popup headless.
4384
+ */
4385
+ function resolvePopupTitle(layerName, properties, popup, options = {}) {
4386
+ return resolveConfiguredPopupTitle(properties, popup, options) ?? layerName;
4387
+ }
4388
+ /**
4389
+ * The title the author's own configuration produced, or `null` when there is
4390
+ * none — no title field or expression, or one that evaluated to nothing, or a
4391
+ * field the author hid.
4392
+ *
4393
+ * {@link resolvePopupTitle} is this plus the layer-name fallback. The hover
4394
+ * tooltip needs the two apart: a tip with no fields and no configured title
4395
+ * would be a box repeating the layer name, so it is suppressed — but that
4396
+ * decision has to rest on whether a title was *configured*, not on whether the
4397
+ * resolved text happens to equal the layer name. A city layer named "Olympia"
4398
+ * whose feature is also called "Olympia" is a real title, not a fallback.
4399
+ */
4400
+ function resolveConfiguredPopupTitle(properties, popup, options = {}) {
4401
+ const source = trimmedString(popup?.titleExpression);
4402
+ if (source) {
4403
+ const text = stringifyPopupValue(evaluatePopupExpression(source, properties, options)).trim();
4404
+ if (text) return text;
4405
+ }
4406
+ const field = trimmedString(popup?.titleField);
4407
+ if (field && !options.fieldVisibility?.[field] && !isInternalPopupField(field)) {
4408
+ const text = stringifyPopupValue(properties[field]).trim();
4409
+ if (text) return text;
4410
+ }
4411
+ return null;
4412
+ }
4413
+ /**
4414
+ * The popup body text when the author supplied a body expression, or `null`
4415
+ * when there is none (or it failed) and the field rows should render instead.
4416
+ */
4417
+ function resolvePopupBody(properties, popup, options = {}) {
4418
+ const source = trimmedString(popup?.bodyExpression);
4419
+ if (!source) return null;
4420
+ const preview = evaluatePopupExpression(source, properties, options);
4421
+ if (preview === void 0) return null;
4422
+ const text = stringifyPopupValue(preview);
4423
+ return text.trim() ? text : null;
4424
+ }
4425
+ /**
4426
+ * Whether a value should render as an inline image thumbnail under `"auto"`.
4427
+ * Kept here so the renderer and the designer's preview agree on what counts.
4428
+ */
4429
+ function isInlineImageValue(value) {
4430
+ return typeof value === "string" && INLINE_IMAGE_DATA_URL.test(value);
4431
+ }
4432
+ /**
4433
+ * Whether a URL is safe to put in a popup `href`/`src`. Only http(s) and
4434
+ * inline raster data URLs: a `javascript:` or `data:text/html` value coming
4435
+ * from an untrusted GeoJSON must never become a live link.
4436
+ */
4437
+ function isSafePopupUrl(value, allowDataImage = false) {
4438
+ if (typeof value !== "string") return false;
4439
+ const trimmed = value.trim();
4440
+ if (/^https?:\/\//i.test(trimmed)) return true;
4441
+ return allowDataImage && INLINE_IMAGE_DATA_URL.test(trimmed);
4442
+ }
4443
+ //#endregion
4138
4444
  //#region src/virtual-fields.ts
4139
4445
  /**
4140
4446
  * Virtual fields (QGIS Field Calculator → "Create virtual field", issue
@@ -4604,6 +4910,246 @@ function reapplyLayerJoins(layers) {
4604
4910
  return current;
4605
4911
  }
4606
4912
  //#endregion
4913
+ //#region src/quick-filters.ts
4914
+ /**
4915
+ * Quick filters (issue #2114): per-layer, data-driven filter controls that
4916
+ * compile down to the MapLibre filter a layer already supports.
4917
+ *
4918
+ * The persisted record is the *control state* (field, kind, chosen values),
4919
+ * never the compiled expression. Storing the compiled output would make a
4920
+ * filter unreadable and un-editable the moment it is saved, which is exactly
4921
+ * the wall this feature exists to remove: a rule filter, Select by Expression,
4922
+ * and the attribute table's filter box all already accept a hand-written
4923
+ * MapLibre expression.
4924
+ *
4925
+ * Compilation happens at sync time in `@geolibre/map`'s layer sync, which
4926
+ * combines the result with the transient `timeFilter`, the embed API's
4927
+ * `embedFilter`, and the rule-based visibility filter under one `["all", …]`,
4928
+ * so a host page's filter and a user's filter narrow the layer together
4929
+ * instead of clobbering each other.
4930
+ *
4931
+ * Every clause reads the property in place, so the same expression is correct
4932
+ * for a GeoJSON source and for a tile-backed source whose tiles decode later.
4933
+ */
4934
+ /** Day length in milliseconds, used to make a date filter's end day inclusive. */
4935
+ const DAY_MS = 864e5;
4936
+ /** The `YYYY-MM-DD` prefix length every ISO date comparison slices to. */
4937
+ const ISO_DAY_LENGTH = 10;
4938
+ /** Whether a filter is switched on (the field defaults to enabled). */
4939
+ function isEnabled(filter) {
4940
+ return filter.enabled !== false;
4941
+ }
4942
+ /**
4943
+ * Validate a `YYYY-MM-DD` bound, returning its canonical text and UTC midnight,
4944
+ * or `null` when the text is blank, malformed, or not a real calendar day.
4945
+ *
4946
+ * The round-trip check is what rejects a day that does not exist: `Date.parse`
4947
+ * happily reads `2026-02-30` as March 2, so shape-checking alone would compile
4948
+ * a bound the user never chose. Rejecting here means a half-typed or impossible
4949
+ * bound compiles to no clause at all, leaving that side unconstrained rather
4950
+ * than filtering on a date nobody asked for.
4951
+ */
4952
+ function parseIsoDayBound(day) {
4953
+ if (typeof day !== "string") return null;
4954
+ const trimmed = day.trim();
4955
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return null;
4956
+ const ms = Date.parse(`${trimmed}T00:00:00Z`);
4957
+ if (!Number.isFinite(ms)) return null;
4958
+ return new Date(ms).toISOString().slice(0, ISO_DAY_LENGTH) === trimmed ? {
4959
+ day: trimmed,
4960
+ ms
4961
+ } : null;
4962
+ }
4963
+ /** A finite number, or `null` for every other input (including `NaN`). */
4964
+ function finite(value) {
4965
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
4966
+ }
4967
+ /**
4968
+ * `["get", field]` coerced for comparison, guarded by `["has", field]` where a
4969
+ * missing property would otherwise coerce into range (`to-number` turns a
4970
+ * missing property into `0`, and `to-string` into `""`, either of which can sit
4971
+ * inside a one-sided bound).
4972
+ */
4973
+ function presenceGuard(field) {
4974
+ return ["has", field];
4975
+ }
4976
+ function compileCategorical(filter) {
4977
+ const values = filter.values ?? [];
4978
+ if (values.length === 0) return null;
4979
+ return [
4980
+ "in",
4981
+ ["get", filter.field],
4982
+ ["literal", values]
4983
+ ];
4984
+ }
4985
+ function compileRange(filter) {
4986
+ const first = finite(filter.min);
4987
+ const second = finite(filter.max);
4988
+ if (first === null && second === null) return null;
4989
+ const min = first !== null && second !== null ? Math.min(first, second) : first;
4990
+ const max = first !== null && second !== null ? Math.max(first, second) : second;
4991
+ const value = ["to-number", ["get", filter.field]];
4992
+ const clauses = [presenceGuard(filter.field)];
4993
+ if (min !== null) clauses.push([
4994
+ ">=",
4995
+ value,
4996
+ min
4997
+ ]);
4998
+ if (max !== null) clauses.push([
4999
+ "<=",
5000
+ value,
5001
+ max
5002
+ ]);
5003
+ return ["all", ...clauses];
5004
+ }
5005
+ function compileDate(filter) {
5006
+ const first = parseIsoDayBound(filter.start);
5007
+ const second = parseIsoDayBound(filter.end);
5008
+ if (first === null && second === null) return null;
5009
+ const inverted = first !== null && second !== null && first.ms > second.ms;
5010
+ const start = inverted ? second : first;
5011
+ const end = inverted ? first : second;
5012
+ const clauses = [presenceGuard(filter.field)];
5013
+ if ((filter.dateKind ?? "iso") === "iso") {
5014
+ const value = [
5015
+ "slice",
5016
+ ["to-string", ["get", filter.field]],
5017
+ 0,
5018
+ ISO_DAY_LENGTH
5019
+ ];
5020
+ if (start !== null) clauses.push([
5021
+ ">=",
5022
+ value,
5023
+ start.day
5024
+ ]);
5025
+ if (end !== null) clauses.push([
5026
+ "<=",
5027
+ value,
5028
+ end.day
5029
+ ]);
5030
+ return ["all", ...clauses];
5031
+ }
5032
+ const scale = filter.dateKind === "epochS" ? .001 : 1;
5033
+ const value = ["to-number", ["get", filter.field]];
5034
+ if (start !== null) clauses.push([
5035
+ ">=",
5036
+ value,
5037
+ start.ms * scale
5038
+ ]);
5039
+ if (end !== null) clauses.push([
5040
+ "<",
5041
+ value,
5042
+ (end.ms + DAY_MS) * scale
5043
+ ]);
5044
+ return ["all", ...clauses];
5045
+ }
5046
+ function compileText(filter) {
5047
+ const needle = filter.text?.trim().toLowerCase() ?? "";
5048
+ if (needle === "") return null;
5049
+ const haystack = ["downcase", ["to-string", ["get", filter.field]]];
5050
+ switch (filter.operator ?? "contains") {
5051
+ case "equals": return [
5052
+ "==",
5053
+ haystack,
5054
+ needle
5055
+ ];
5056
+ case "startsWith": return [
5057
+ "==",
5058
+ [
5059
+ "index-of",
5060
+ needle,
5061
+ haystack
5062
+ ],
5063
+ 0
5064
+ ];
5065
+ default: return [
5066
+ "!=",
5067
+ [
5068
+ "index-of",
5069
+ needle,
5070
+ haystack
5071
+ ],
5072
+ -1
5073
+ ];
5074
+ }
5075
+ }
5076
+ /**
5077
+ * Compile one quick filter to a MapLibre filter expression, or `null` when it
5078
+ * places no constraint (switched off, no field, or nothing chosen yet). A
5079
+ * control that has been added but not yet answered must never hide features.
5080
+ *
5081
+ * @param filter - The persisted control state.
5082
+ * @returns A MapLibre filter expression, or `null`.
5083
+ */
5084
+ function compileQuickFilter(filter) {
5085
+ if (!isEnabled(filter) || typeof filter.field !== "string" || filter.field === "") return null;
5086
+ switch (filter.kind) {
5087
+ case "categorical": return compileCategorical(filter);
5088
+ case "range": return compileRange(filter);
5089
+ case "date": return compileDate(filter);
5090
+ case "text": return compileText(filter);
5091
+ default: return null;
5092
+ }
5093
+ }
5094
+ /**
5095
+ * Compile a layer's quick filters into a single MapLibre filter expression.
5096
+ * Multiple controls narrow the data together (`["all", …]`), matching how a
5097
+ * viewer reads a stack of filters. Returns `null` when no control constrains
5098
+ * anything, so the common path adds no filter at all.
5099
+ *
5100
+ * @param filters - The layer's persisted quick filters.
5101
+ * @returns A MapLibre filter expression, or `null`.
5102
+ */
5103
+ function compileQuickFilters(filters) {
5104
+ if (!filters || filters.length === 0) return null;
5105
+ const clauses = [];
5106
+ for (const filter of filters) {
5107
+ const compiled = compileQuickFilter(filter);
5108
+ if (compiled) clauses.push(compiled);
5109
+ }
5110
+ if (clauses.length === 0) return null;
5111
+ return clauses.length === 1 ? clauses[0] : ["all", ...clauses];
5112
+ }
5113
+ /**
5114
+ * The quick filters that actually constrain the layer right now. Drives the
5115
+ * "filtered" badge on the layer row and its count, so an active filter is
5116
+ * never mistaken for missing data.
5117
+ *
5118
+ * @param layer - The layer to inspect.
5119
+ * @returns The constraining filters, in their configured order.
5120
+ */
5121
+ function activeQuickFilters(layer) {
5122
+ return (layer.quickFilters ?? []).filter((filter) => compileQuickFilter(filter) !== null);
5123
+ }
5124
+ /**
5125
+ * Whether the layer is currently narrowed by at least one quick filter.
5126
+ *
5127
+ * @param layer - The layer to inspect.
5128
+ * @returns `true` when a quick filter constrains the rendered features.
5129
+ */
5130
+ function hasActiveQuickFilter(layer) {
5131
+ return (layer.quickFilters ?? []).some((filter) => compileQuickFilter(filter) !== null);
5132
+ }
5133
+ /**
5134
+ * Clear every chosen value while keeping the controls themselves, so "Clear
5135
+ * filters" is one action that leaves the layer's configured controls in place
5136
+ * for the next question.
5137
+ *
5138
+ * @param filters - The layer's persisted quick filters.
5139
+ * @returns The same controls with their selections emptied.
5140
+ */
5141
+ function clearQuickFilterValues(filters) {
5142
+ return (filters ?? []).map((filter) => ({
5143
+ ...filter,
5144
+ values: filter.kind === "categorical" ? [] : filter.values,
5145
+ min: filter.kind === "range" ? null : filter.min,
5146
+ max: filter.kind === "range" ? null : filter.max,
5147
+ start: filter.kind === "date" ? null : filter.start,
5148
+ end: filter.kind === "date" ? null : filter.end,
5149
+ text: filter.kind === "text" ? "" : filter.text
5150
+ }));
5151
+ }
5152
+ //#endregion
4607
5153
  //#region src/capabilities.ts
4608
5154
  /**
4609
5155
  * Default inferred capabilities for a layer based on its type and metadata.
@@ -8373,7 +8919,8 @@ function captureLayerLibraryEntry(layer, options) {
8373
8919
  ...localPath ? { sourcePath: localPath } : {},
8374
8920
  ...layer.joins?.length ? { joins: structuredClone(layer.joins) } : {},
8375
8921
  ...layer.virtualFields?.length ? { virtualFields: structuredClone(layer.virtualFields) } : {},
8376
- ...layer.attributeForm ? { attributeForm: structuredClone(layer.attributeForm) } : {}
8922
+ ...layer.attributeForm ? { attributeForm: structuredClone(layer.attributeForm) } : {},
8923
+ ...layer.popup ? { popup: structuredClone(layer.popup) } : {}
8377
8924
  };
8378
8925
  const withFeatures = embed && !controlPainted ? {
8379
8926
  ...base,
@@ -8429,7 +8976,8 @@ function planLayerLibraryAdd(entry, options) {
8429
8976
  ...entry.geojson ? { geojson: structuredClone(entry.geojson) } : {},
8430
8977
  ...entry.joins ? { joins: structuredClone(entry.joins) } : {},
8431
8978
  ...entry.virtualFields ? { virtualFields: structuredClone(entry.virtualFields) } : {},
8432
- ...entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {}
8979
+ ...entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {},
8980
+ ...entry.popup ? { popup: structuredClone(entry.popup) } : {}
8433
8981
  }
8434
8982
  };
8435
8983
  }
@@ -8447,7 +8995,8 @@ function layerLibraryConfigPatch(entry) {
8447
8995
  opacity: entry.opacity,
8448
8996
  ...entry.joins ? { joins: structuredClone(entry.joins) } : {},
8449
8997
  ...entry.virtualFields ? { virtualFields: structuredClone(entry.virtualFields) } : {},
8450
- ...entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {}
8998
+ ...entry.attributeForm ? { attributeForm: structuredClone(entry.attributeForm) } : {},
8999
+ ...entry.popup ? { popup: structuredClone(entry.popup) } : {}
8451
9000
  };
8452
9001
  }
8453
9002
  /**
@@ -8533,6 +9082,24 @@ function validAttributeForm(value) {
8533
9082
  fields
8534
9083
  });
8535
9084
  }
9085
+ /**
9086
+ * A structurally plausible popup design, or undefined. `fields` is optional (a
9087
+ * config may carry only a title or the hover flag), so unlike the attribute
9088
+ * form an empty field list does not disqualify the block — but a config with
9089
+ * nothing set at all is dropped rather than stored as an empty object.
9090
+ */
9091
+ function validPopup(value) {
9092
+ const config = plainObject(value);
9093
+ if (!config) return void 0;
9094
+ const fields = Array.isArray(config.fields) ? config.fields.filter((field) => plainObject(field) !== void 0 && nonEmptyString(field.field)) : void 0;
9095
+ const rest = { ...config };
9096
+ delete rest.fields;
9097
+ if (!fields?.length && Object.keys(rest).length === 0) return void 0;
9098
+ return structuredClone({
9099
+ ...rest,
9100
+ ...fields?.length ? { fields } : {}
9101
+ });
9102
+ }
8536
9103
  /** An array of plain JSON objects, or undefined when nothing usable is present. */
8537
9104
  function objectArray(value) {
8538
9105
  if (!Array.isArray(value)) return void 0;
@@ -8598,10 +9165,12 @@ function normalizeLayerLibraryEntries(value) {
8598
9165
  const joins = validJoins(candidate.joins);
8599
9166
  const virtualFields = validVirtualFields(candidate.virtualFields);
8600
9167
  const attributeForm = validAttributeForm(candidate.attributeForm);
9168
+ const popup = validPopup(candidate.popup);
8601
9169
  return {
8602
9170
  ...joins ? { joins } : {},
8603
9171
  ...virtualFields ? { virtualFields } : {},
8604
- ...attributeForm ? { attributeForm } : {}
9172
+ ...attributeForm ? { attributeForm } : {},
9173
+ ...popup ? { popup } : {}
8605
9174
  };
8606
9175
  })(),
8607
9176
  ...geojson ? { geojson: structuredClone(geojson) } : {},
@@ -9729,6 +10298,7 @@ const useAppStore = create()(temporal((set, get) => ({
9729
10298
  };
9730
10299
  }),
9731
10300
  setLayerAttributeForm: (id, attributeForm) => get().updateLayer(id, { attributeForm }),
10301
+ setLayerPopup: (id, popup) => get().updateLayer(id, { popup }),
9732
10302
  setLayerEditorTracking: (id, editorTracking) => get().updateLayer(id, { editorTracking }),
9733
10303
  setLayerVirtualFields: (id, fields) => set((s) => {
9734
10304
  let layers = s.layers.map((l) => l.id === id ? applyJoinsToLayer(l, s.layers, void 0, fields) : l);
@@ -9738,6 +10308,7 @@ const useAppStore = create()(temporal((set, get) => ({
9738
10308
  isDirty: true
9739
10309
  };
9740
10310
  }),
10311
+ setLayerQuickFilters: (id, filters) => get().updateLayer(id, { quickFilters: filters.length > 0 ? filters : void 0 }),
9741
10312
  setLayerVisibility: (id, visible) => get().updateLayer(id, { visible }),
9742
10313
  setLayerOpacity: (id, opacity) => get().updateLayer(id, { opacity }),
9743
10314
  setLayerStyle: (id, style) => set((s) => ({
@@ -10853,12 +11424,14 @@ function parseCsvRows(text) {
10853
11424
  const input = text.replace(/\r\n?/g, "\n");
10854
11425
  for (let i = 0; i < input.length; i++) {
10855
11426
  const ch = input[i];
10856
- if (inQuotes) if (ch === "\"") if (input[i + 1] === "\"") {
10857
- field += "\"";
10858
- i++;
10859
- } else inQuotes = false;
10860
- else field += ch;
10861
- else if (ch === "\"") inQuotes = true;
11427
+ if (inQuotes) {
11428
+ if (ch === "\"") {
11429
+ if (input[i + 1] === "\"") {
11430
+ field += "\"";
11431
+ i++;
11432
+ } else inQuotes = false;
11433
+ } else field += ch;
11434
+ } else if (ch === "\"") inQuotes = true;
10862
11435
  else if (ch === ",") {
10863
11436
  row.push(field);
10864
11437
  field = "";
@@ -12058,6 +12631,6 @@ function currentEditorIdentity() {
12058
12631
  return pickEditorIdentity(isActive ? selfName : void 0, readStoredAuthorName());
12059
12632
  }
12060
12633
  //#endregion
12061
- export { BLANK_BASEMAP, BLEND_MODES, BUILT_IN_STYLE_PRESETS, CARTOCIUDAD_MIN_INTERVAL_MS, CARTOCIUDAD_PUBLIC_HOST, CHINA_BASEMAPS, DEFAULT_BASEMAP, DEFAULT_BLEND_MODE, DEFAULT_COLLABORATION_STATE, DEFAULT_DASHBOARD_COLUMNS, DEFAULT_EDITOR_IDENTITY, DEFAULT_EDITOR_TRACKING_CONFIG, DEFAULT_ELLIPSOID_ID, DEFAULT_FORWARD_GEOCODE_ENDPOINT, DEFAULT_GEOCODING_PROVIDER_ID, DEFAULT_LAYER_GROUP_OPACITY, DEFAULT_LAYER_STYLE, DEFAULT_LEGEND_CONFIG, DEFAULT_MAP_GRID_LAYOUT, DEFAULT_PRINT_LAYOUT, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_PREFERENCES, DEFAULT_REVERSE_GEOCODE_ENDPOINT, DEFAULT_ROUTING_ENDPOINT, DEFAULT_STORY_MAP, DUCKDB_VECTOR_FEATURE_WARN_COUNT, DUCKDB_VECTOR_ROUTE_BYTES, EARTH_MEAN_RADIUS_METERS, EDITOR_IDENTITY_STORAGE_KEY, ELEVATION_REQUEST_TIMEOUT_MS, ELLIPSOIDS, EXPRESSION_FUNCTION_CATEGORIES, ElevationFetchError, FEET_PER_METER, FEET_PER_MILE, GEOCODE_DISPLAY_NAME_KEY, GEOCODE_LAT_KEY, GEOCODE_LON_KEY, GEOCODE_PROVIDER_KEY, GEOCODE_SCORE_KEY, GEOCODE_STATUS_KEY, GEOCODING_PROVIDERS, GOOGLE_MAPS_API_KEY_HEADER, LARGE_VECTOR_FEATURE_THRESHOLD, LAYER_LIBRARY_BUNDLE_TYPE, LAYER_LIBRARY_BUNDLE_VERSION, LAYER_PALETTE, LAYER_TYPES, MAX_DASHBOARD_COLUMNS, MAX_DIAGRAM_FEATURES, MAX_DIAGRAM_SCAN_FEATURES, MAX_LAYER_LIBRARY_ENTRIES, MAX_LAYER_LIBRARY_ENTRY_BYTES, MAX_MAP_GRID_DIM, MAX_POINTS_PER_REQUEST, MAX_PROCESSING_HISTORY, MAX_REDACT_DEPTH, MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0, METERS_PER_FOOT, METERS_PER_MILE, METERS_PER_NAUTICAL_MILE, MIN_DASHBOARD_COLUMNS, MIN_DIAGRAM_SIZE, NETCDF_IMAGE_SOURCE_KIND, NOMINATIM_MIN_INTERVAL_MS, NOMINATIM_PUBLIC_HOST, OPENFREEMAP_BASEMAPS, PHOTO_FULL_PROPERTY, PHOTO_PROPERTY, PLANETARY_BASEMAPS, PLANETARY_BASEMAP_GROUPS, PLANETARY_BASEMAP_SENTINEL_PREFIX, PLANET_SWITCHER_OPTIONS, POINTER_ELEVATION_CACHE_LIMIT, POINTER_ELEVATION_DEBOUNCE_MS, PROJECT_CREDENTIAL_FIELDS, PROJECT_VERSION, PROTOMAPS_BASEMAPS, PUBLIC_GEOCODE_ROW_CAP, PUBLISHABLE_PLUGIN_SETTINGS, RAMP_STYLE_KEYS, RASTER_APPEARANCE_STATE_KEYS, REGIONAL_BASEMAPS, REGIONAL_BASEMAP_GROUPS, REGIONAL_BASEMAP_SENTINEL_PREFIX, RoutingRequestError, SELECTION_MODES, SQL_QUERY_SOURCE_KIND, STYLE_LIBRARY_BUNDLE_TYPE, STYLE_LIBRARY_BUNDLE_VERSION, SYMBOL_STYLE_KEYS, VECTOR_COLOR_RAMPS, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, attributeFormFieldLabel, attributeLinkUrl, batchDecodePolylines, bodyLengthToEarth, buildForwardGeocodeUrl, buildIsochroneRequest, buildLayerPanelUnits, buildLayerTree, buildMatrixRequest, buildReverseGeocodeUrl, buildRouteRequest, canRedoProjectRestore, canSaveLayerToLibrary, canUndoProjectRestore, captureLayerLibraryEntry, cascadeLayerJoinRefresh, circleRadiusValue, clearExternalNativePaintBridge, clearHistory, coerceAttributeFormValue, collectDiagramData, collectTransitiveJoinSourceIds, compareSequenceValues, compileFeatureExpression, controlRendersLayer, copyableLayerStyleKind, createDefaultMapView, createDefaultPrintLayout, createEmptyProject, createEqualIntervalBreaks, createGraduatedClassBreaks, createLayerLibraryEntryId, createPointerElevationResolver, createProjectTemplate, createQuantileBreaks, createSampleStoryMap, createStyleLibraryEntryId, csvRowsToGeocodeRequests, currentEditorIdentity, darkenHex, decodePolyline, decodePolylineDetailed, detachProjectCopy, detectNonGeographicCoordinates, diagramAnchor, diagramPixelSize, diagramsSuppressedByPointRenderer, dominantGeometry, drawMarkerPath, earthAreaToBody, earthLengthToBody, editorTrackingFieldNames, effectiveLayerRenderState, effectiveVectorRules, encodePolyline, ensureEditorTrackingFields, evaluateMapExpression, excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject, extractCopiedLayerStyle, extractStyleLibraryStyle, extrusionColorValue, extrusionHeightValue, featureSelectionId, fetchElevations, formatCameraAltitude, formatExpressionPreviewValue, formatRoundNum, generatorCircleRadiusValue, generatorSizeRange, geoJSONToPolylineStr, geocodeForward, geocodeMatchToFeature, geocodeReverse, geocoderMinIntervalMs, geocoderNeedsApiKey, geojsonHasZCoordinates, getActiveBodyRadiusRatio, getActiveEllipsoid, getActiveMeanRadiusMeters, getActiveSemiMajorAxisMeters, getAttributeFormField, getCesiumIonToken, getEllipsoid, getExternalNativePaintBridge, getGeocoderConfig, getGeocodingProvider, getGoogleMapsApiKey, getHistoryCoalesceMs, getMapboxAccessToken, getMaxHistoryFeatureCount, getPlanetaryBasemapById, getPlanetaryBasemapByStyleUrl, getProtomapsApiKey, getProtomapsStyleUrl, getRegionalBasemapById, getRegionalBasemapByStyleUrl, getRoundNum, getRoutingConfig, getRuntimeEnvironment, getSpatialExtensionPath, getVectorColorRamp, googleMapsApiKeyHeaderValue, groupMemberIndices, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, invertSelection, isAbsoluteFilesystemPath, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCredentialFieldName, isDefaultPrintLayout, isDiagramStyleEnabled, isDuckDBQueryLayer, isExternalNativeLayerRecord, isGooglePhotorealisticTilesetUrl, isHexColor, isInitialLayerStyle, isIpadDesktopUserAgent, isMaintainedEditorTrackingField, isRegionalBasemapSentinel, isStyleLibraryTargetLayer, isStyleSpecColor, isVectorColorExpression, isochroneResponseToFeatures, layerGroupDepth, layerGroupMoveability, layerJoinKey, layerLibraryConfigPatch, layerLibraryEntryNeedsLocalFile, layerPanelGroupHeaders, lineWidthValue, mapZoomStepOutputs, matchFeaturesByExpression, matrixResponseToFeatures, meanRadiusMeters, mercatorMetersPerPixelAtZoom0, metersWidthExpression, nextDelayMs, nextLayerPaletteColor, nominatimResultToFeature, nominatimReverseResultToDisplay, nonEmptyRecord, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, parseContours, parseHexColor, parseHexColorList, parseJsonExpression, parseLayerLibrary, parseProject, parseStoryMapCsv, parseStoryMapJson, parseStyleLibrary, parseValueMapText, persistedThreeDTilesRequestHeaders, pickEditorIdentity, planLayerLibraryAdd, pluginOwnsPaint, polylineStrToGeoJSON, printLayoutConfigsEqual, projectFromStore, projectPathLabel, proportionalRadiusExpression, proportionalSizeRange, readStoredAuthorName, reapplyLayerJoins, redactCredentials, redactProjectCredentials, redactUrlCredentials, redo, registerProjectRestoreHistory, removeTrailingJsonCommas, reorderLayerGroupInPanel, requestIsochrone, requestMatrix, requestRoute, resolveEditorTrackingConfig, resolveGeocoderConfig, resolveLayerCapabilities, resolveMapGrid, resolveSvgSource, resolveThreeDTilesRequestHeaders, rgbToHex, routeResponseToFeatures, rowCap, ruleBasedColorExpression, ruleBasedVisibilityFilter, sampleMapTerrain, sampleMapTerrainPoint, sampleRemoteElevations, sanitizeLayerStylePatch, scaleAltitudeToActiveBody, scaleDenomination, scaleSpan, scrubCommentsForRemovedLayers, scrubLegendForRemovedLayers, scrubPrintLayoutForLayers, scrubPrintLayoutForRemovedLayers, scrubWidgetsForRemovedLayers, serializeLayerLibrary, serializeProject, serializeStoryMapCsv, serializeStoryMapJson, serializeStyleLibrary, setActiveEllipsoidId, setExternalNativePaintBridge, setGeocodingFetch, setHistoryCoalesceMs, setMaxHistoryFeatureCount, setStoredAuthorName, shouldThrottle, shouldUseTiledRendering, simpleStyleNumberValue, stampFeatureCollectionEditorTracking, stampFeatureEditorTracking, stampFeaturePropertiesEditorTracking, storyMapHasContent, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, useAppStore, validateAttributeFormField, validateAttributeFormValues, validateMapExpression, valueMapLabelFor, valueMapToText, vectorCircleColorValue, vectorColorExpression, vectorFillColorValue, vectorFillOpacityValue, vectorLineColorValue, vectorOutlineColorValue, vectorStrokeWidthValue };
12634
+ export { BLANK_BASEMAP, BLEND_MODES, BUILT_IN_STYLE_PRESETS, CARTOCIUDAD_MIN_INTERVAL_MS, CARTOCIUDAD_PUBLIC_HOST, CHINA_BASEMAPS, DEFAULT_BASEMAP, DEFAULT_BLEND_MODE, DEFAULT_COLLABORATION_STATE, DEFAULT_DASHBOARD_COLUMNS, DEFAULT_EDITOR_IDENTITY, DEFAULT_EDITOR_TRACKING_CONFIG, DEFAULT_ELLIPSOID_ID, DEFAULT_FORWARD_GEOCODE_ENDPOINT, DEFAULT_GEOCODING_PROVIDER_ID, DEFAULT_LAYER_GROUP_OPACITY, DEFAULT_LAYER_STYLE, DEFAULT_LEGEND_CONFIG, DEFAULT_MAP_GRID_LAYOUT, DEFAULT_PRINT_LAYOUT, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_PREFERENCES, DEFAULT_REVERSE_GEOCODE_ENDPOINT, DEFAULT_ROUTING_ENDPOINT, DEFAULT_STORY_MAP, DUCKDB_VECTOR_FEATURE_WARN_COUNT, DUCKDB_VECTOR_ROUTE_BYTES, EARTH_MEAN_RADIUS_METERS, EDITOR_IDENTITY_STORAGE_KEY, ELEVATION_REQUEST_TIMEOUT_MS, ELLIPSOIDS, EXPRESSION_FUNCTION_CATEGORIES, ElevationFetchError, FEET_PER_METER, FEET_PER_MILE, GEOCODE_DISPLAY_NAME_KEY, GEOCODE_LAT_KEY, GEOCODE_LON_KEY, GEOCODE_PROVIDER_KEY, GEOCODE_SCORE_KEY, GEOCODE_STATUS_KEY, GEOCODING_PROVIDERS, GOOGLE_MAPS_API_KEY_HEADER, LARGE_VECTOR_FEATURE_THRESHOLD, LAYER_LIBRARY_BUNDLE_TYPE, LAYER_LIBRARY_BUNDLE_VERSION, LAYER_PALETTE, LAYER_TYPES, MAX_DASHBOARD_COLUMNS, MAX_DIAGRAM_FEATURES, MAX_DIAGRAM_SCAN_FEATURES, MAX_LAYER_LIBRARY_ENTRIES, MAX_LAYER_LIBRARY_ENTRY_BYTES, MAX_MAP_GRID_DIM, MAX_POINTS_PER_REQUEST, MAX_PROCESSING_HISTORY, MAX_REDACT_DEPTH, MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0, METERS_PER_FOOT, METERS_PER_MILE, METERS_PER_NAUTICAL_MILE, MIN_DASHBOARD_COLUMNS, MIN_DIAGRAM_SIZE, NETCDF_IMAGE_SOURCE_KIND, NOMINATIM_MIN_INTERVAL_MS, NOMINATIM_PUBLIC_HOST, OPENFREEMAP_BASEMAPS, PHOTO_FULL_PROPERTY, PHOTO_PROPERTY, PLANETARY_BASEMAPS, PLANETARY_BASEMAP_GROUPS, PLANETARY_BASEMAP_SENTINEL_PREFIX, PLANET_SWITCHER_OPTIONS, POINTER_ELEVATION_CACHE_LIMIT, POINTER_ELEVATION_DEBOUNCE_MS, PROJECT_CREDENTIAL_FIELDS, PROJECT_VERSION, PROTOMAPS_BASEMAPS, PUBLIC_GEOCODE_ROW_CAP, PUBLISHABLE_PLUGIN_SETTINGS, RAMP_STYLE_KEYS, RASTER_APPEARANCE_STATE_KEYS, REGIONAL_BASEMAPS, REGIONAL_BASEMAP_GROUPS, REGIONAL_BASEMAP_SENTINEL_PREFIX, RoutingRequestError, SELECTION_MODES, SQL_QUERY_SOURCE_KIND, STYLE_LIBRARY_BUNDLE_TYPE, STYLE_LIBRARY_BUNDLE_VERSION, SYMBOL_STYLE_KEYS, VECTOR_COLOR_RAMPS, activeQuickFilters, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, attributeFormFieldLabel, attributeLinkUrl, batchDecodePolylines, bodyLengthToEarth, buildForwardGeocodeUrl, buildIsochroneRequest, buildLayerPanelUnits, buildLayerTree, buildMatrixRequest, buildReverseGeocodeUrl, buildRouteRequest, canRedoProjectRestore, canSaveLayerToLibrary, canUndoProjectRestore, captureLayerLibraryEntry, cascadeLayerJoinRefresh, circleRadiusValue, clearExternalNativePaintBridge, clearHistory, clearQuickFilterValues, coerceAttributeFormValue, collectDiagramData, collectTransitiveJoinSourceIds, compareSequenceValues, compileFeatureExpression, compileQuickFilter, compileQuickFilters, controlRendersLayer, copyableLayerStyleKind, createDefaultMapView, createDefaultPrintLayout, createEmptyProject, createEqualIntervalBreaks, createGraduatedClassBreaks, createLayerLibraryEntryId, createPointerElevationResolver, createProjectTemplate, createQuantileBreaks, createSampleStoryMap, createStyleLibraryEntryId, csvRowsToGeocodeRequests, currentEditorIdentity, darkenHex, decodePolyline, decodePolylineDetailed, detachProjectCopy, detectNonGeographicCoordinates, diagramAnchor, diagramPixelSize, diagramsSuppressedByPointRenderer, dominantGeometry, drawMarkerPath, earthAreaToBody, earthLengthToBody, editorTrackingFieldNames, effectiveLayerRenderState, effectiveVectorRules, encodePolyline, ensureEditorTrackingFields, evaluateMapExpression, excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject, extractCopiedLayerStyle, extractStyleLibraryStyle, extrusionColorValue, extrusionHeightValue, featureSelectionId, fetchElevations, formatCameraAltitude, formatExpressionPreviewValue, formatPopupValue, formatRoundNum, generatorCircleRadiusValue, generatorSizeRange, geoJSONToPolylineStr, geocodeForward, geocodeMatchToFeature, geocodeReverse, geocoderMinIntervalMs, geocoderNeedsApiKey, geojsonHasZCoordinates, getActiveBodyRadiusRatio, getActiveEllipsoid, getActiveMeanRadiusMeters, getActiveSemiMajorAxisMeters, getAttributeFormField, getBuildEnvironment, getCesiumIonToken, getEllipsoid, getExternalNativePaintBridge, getGeocoderConfig, getGeocodingProvider, getGoogleMapsApiKey, getHistoryCoalesceMs, getMapboxAccessToken, getMaxHistoryFeatureCount, getPlanetaryBasemapById, getPlanetaryBasemapByStyleUrl, getProtomapsApiKey, getProtomapsStyleUrl, getRegionalBasemapById, getRegionalBasemapByStyleUrl, getRoundNum, getRoutingConfig, getRuntimeEnvironment, getSpatialExtensionPath, getVectorColorRamp, googleMapsApiKeyHeaderValue, groupMemberIndices, hasActiveQuickFilter, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, invertSelection, isAbsoluteFilesystemPath, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCredentialFieldName, isDefaultPrintLayout, isDiagramStyleEnabled, isDuckDBQueryLayer, isExternalNativeLayerRecord, isGooglePhotorealisticTilesetUrl, isHexColor, isInitialLayerStyle, isInlineImageValue, isInternalPopupField, isIpadDesktopUserAgent, isMaintainedEditorTrackingField, isPopupClickEnabled, isPopupHoverEnabled, isRegionalBasemapSentinel, isSafePopupUrl, isStyleLibraryTargetLayer, isStyleSpecColor, isVectorColorExpression, isochroneResponseToFeatures, layerGroupDepth, layerGroupMoveability, layerJoinKey, layerLibraryConfigPatch, layerLibraryEntryNeedsLocalFile, layerPanelGroupHeaders, lineWidthValue, mapZoomStepOutputs, matchFeaturesByExpression, matrixResponseToFeatures, meanRadiusMeters, mercatorMetersPerPixelAtZoom0, metersWidthExpression, nextDelayMs, nextLayerPaletteColor, nominatimResultToFeature, nominatimReverseResultToDisplay, nonEmptyRecord, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, parseContours, parseHexColor, parseHexColorList, parseJsonExpression, parseLayerLibrary, parseProject, parseStoryMapCsv, parseStoryMapJson, parseStyleLibrary, parseValueMapText, persistedThreeDTilesRequestHeaders, pickEditorIdentity, planLayerLibraryAdd, pluginOwnsPaint, polylineStrToGeoJSON, popupFieldLabel, printLayoutConfigsEqual, projectFromStore, projectPathLabel, proportionalRadiusExpression, proportionalSizeRange, readStoredAuthorName, reapplyLayerJoins, redactCredentials, redactProjectCredentials, redactUrlCredentials, redo, registerProjectRestoreHistory, removeTrailingJsonCommas, reorderLayerGroupInPanel, requestIsochrone, requestMatrix, requestRoute, resolveConfiguredPopupTitle, resolveEditorTrackingConfig, resolveGeocoderConfig, resolveLayerCapabilities, resolveMapGrid, resolvePopupBody, resolvePopupRows, resolvePopupTitle, resolveSvgSource, resolveThreeDTilesRequestHeaders, rgbToHex, routeResponseToFeatures, rowCap, ruleBasedColorExpression, ruleBasedVisibilityFilter, sampleMapTerrain, sampleMapTerrainPoint, sampleRemoteElevations, sanitizeLayerStylePatch, scaleAltitudeToActiveBody, scaleDenomination, scaleSpan, scrubCommentsForRemovedLayers, scrubLegendForRemovedLayers, scrubPrintLayoutForLayers, scrubPrintLayoutForRemovedLayers, scrubWidgetsForRemovedLayers, serializeLayerLibrary, serializeProject, serializeStoryMapCsv, serializeStoryMapJson, serializeStyleLibrary, setActiveEllipsoidId, setExternalNativePaintBridge, setGeocodingFetch, setHistoryCoalesceMs, setMaxHistoryFeatureCount, setStoredAuthorName, shouldThrottle, shouldUseTiledRendering, simpleStyleNumberValue, stampFeatureCollectionEditorTracking, stampFeatureEditorTracking, stampFeaturePropertiesEditorTracking, storyMapHasContent, stringifyPopupValue, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, useAppStore, validateAttributeFormField, validateAttributeFormValues, validateMapExpression, valueMapLabelFor, valueMapToText, vectorCircleColorValue, vectorColorExpression, vectorFillColorValue, vectorFillOpacityValue, vectorLineColorValue, vectorOutlineColorValue, vectorStrokeWidthValue, visiblePopupFields, visiblePopupProperties };
12062
12635
 
12063
12636
  //# sourceMappingURL=index.mjs.map