riverbankcms 0.23.0 → 0.24.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/acceptance/matrix.mjs +3 -3
- package/dist/cli/index.mjs +921 -539
- package/dist/cli/init-docs/content/content-management.md +31 -6
- package/dist/{entryCommands-BdN5YrqI.mjs → entryCommands-C0xbLW5L.mjs} +69 -6
- package/dist/{eventUpsertInputSchema-kp-U13TZ.mjs → eventUpsertInputSchema-C-cDv9vR.mjs} +6 -2
- package/dist/load-config-BGzlsmtt.mjs +4 -0
- package/dist/{load-config-D5DDfmv1.mjs → load-config-h2Ri_tDO.mjs} +2 -2
- package/dist/{plan-6m8gsGI8.mjs → plan-Dyv1rMTT.mjs} +1 -1
- package/dist/scaffold/index.mjs +2 -2
- package/dist/{sdk-event-upsert-BnBxyTR6.mjs → sdk-event-upsert-DmNSeHgP.mjs} +3714 -2745
- package/dist/{src-BPh_OivC.mjs → src-B03aw4Tv.mjs} +1034 -75
- package/dist/testing.mjs +4 -4
- package/package.json +1 -1
- package/dist/load-config-C7Rd822n.mjs +0 -4
|
@@ -1478,7 +1478,7 @@ function toPositiveTicketQuantity(value) {
|
|
|
1478
1478
|
* null when the total is not a positive whole number.
|
|
1479
1479
|
*/
|
|
1480
1480
|
function deriveTotalTicketQuantity(lines) {
|
|
1481
|
-
const total = lines.reduce((sum, line) => sum + line.quantity, 0);
|
|
1481
|
+
const total = lines.reduce((sum, line$1) => sum + line$1.quantity, 0);
|
|
1482
1482
|
const parsed = toPositiveTicketQuantity(total);
|
|
1483
1483
|
return parsed.ok ? parsed.value : null;
|
|
1484
1484
|
}
|
|
@@ -2001,8 +2001,8 @@ function resolveBookingTimezone(venueTimezone, siteDefaultTimezone) {
|
|
|
2001
2001
|
return venueTimezone ?? siteDefaultTimezone;
|
|
2002
2002
|
}
|
|
2003
2003
|
function resolveBookingTimeZoneId(...candidates) {
|
|
2004
|
-
for (const candidate of candidates) {
|
|
2005
|
-
const normalized = typeof candidate === "string" ? candidate.trim() : "";
|
|
2004
|
+
for (const candidate$1 of candidates) {
|
|
2005
|
+
const normalized = typeof candidate$1 === "string" ? candidate$1.trim() : "";
|
|
2006
2006
|
if (normalized && isValidBookingTimeZone(normalized)) {
|
|
2007
2007
|
return normalized;
|
|
2008
2008
|
}
|
|
@@ -2222,8 +2222,8 @@ function parseBookingLocalDate(value) {
|
|
|
2222
2222
|
const year = Number(match.groups.year);
|
|
2223
2223
|
const month = Number(match.groups.month);
|
|
2224
2224
|
const day = Number(match.groups.day);
|
|
2225
|
-
const candidate = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
2226
|
-
if (Number.isNaN(candidate.getTime()) || candidate.getUTCFullYear() !== year || candidate.getUTCMonth() + 1 !== month || candidate.getUTCDate() !== day) {
|
|
2225
|
+
const candidate$1 = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
2226
|
+
if (Number.isNaN(candidate$1.getTime()) || candidate$1.getUTCFullYear() !== year || candidate$1.getUTCMonth() + 1 !== month || candidate$1.getUTCDate() !== day) {
|
|
2227
2227
|
throw new Error(`Invalid booking date format: "${value}" (expected YYYY-MM-DD)`);
|
|
2228
2228
|
}
|
|
2229
2229
|
return {
|
|
@@ -2567,12 +2567,12 @@ function decideRangeLabels(span, context, profile) {
|
|
|
2567
2567
|
label: start
|
|
2568
2568
|
};
|
|
2569
2569
|
}
|
|
2570
|
-
function presentRangeEndpoint(value, context, profile, label) {
|
|
2570
|
+
function presentRangeEndpoint(value, context, profile, label$1) {
|
|
2571
2571
|
const date = presentDate({
|
|
2572
2572
|
kind: "instant",
|
|
2573
2573
|
value
|
|
2574
2574
|
}, context, profile);
|
|
2575
|
-
const time = label === undefined ? presentTime(value, context, profile) : `${presentClock(value, context, profile.clock)} ${label}`;
|
|
2575
|
+
const time = label$1 === undefined ? presentTime(value, context, profile) : `${presentClock(value, context, profile.clock)} ${label$1}`;
|
|
2576
2576
|
return `${date}, ${time}`;
|
|
2577
2577
|
}
|
|
2578
2578
|
function presentNativeRange(span, context, format) {
|
|
@@ -3024,7 +3024,7 @@ function selectCategoryView(categories, services, filterCategoryId) {
|
|
|
3024
3024
|
layout: publicLayout()
|
|
3025
3025
|
};
|
|
3026
3026
|
}
|
|
3027
|
-
const category = categories.find((candidate) => candidate.id === filterCategoryId);
|
|
3027
|
+
const category = categories.find((candidate$1) => candidate$1.id === filterCategoryId);
|
|
3028
3028
|
if (category === undefined) {
|
|
3029
3029
|
return {
|
|
3030
3030
|
kind: "fallback-all",
|
|
@@ -3113,6 +3113,169 @@ const RECTIFICATION_HISTORY_PROPAGATION_PLANS = {
|
|
|
3113
3113
|
blocked_immutable_history: { kind: "blocked_immutable_history" }
|
|
3114
3114
|
};
|
|
3115
3115
|
|
|
3116
|
+
//#endregion
|
|
3117
|
+
//#region ../core/src/map-drawing/model.ts
|
|
3118
|
+
const MAX_DRAWING_BYTES = 1024 * 1024;
|
|
3119
|
+
const DRAWING_SIZE = 800;
|
|
3120
|
+
const DRAWING_SCHEMA_VERSION = 2;
|
|
3121
|
+
const DRAWING_GENERATOR_VERSION = 3;
|
|
3122
|
+
/** Current real city extracts fit with >2x headroom; reservations precede allocation. */
|
|
3123
|
+
const DRAWING_DECODE_LIMITS = {
|
|
3124
|
+
pathsPerFeature: 2048,
|
|
3125
|
+
verticesPerPath: 8192,
|
|
3126
|
+
verticesPerFeature: 32768,
|
|
3127
|
+
featuresPerLayer: 1e4,
|
|
3128
|
+
featuresPerTile: 16e3,
|
|
3129
|
+
featuresPerAttempt: 6e4,
|
|
3130
|
+
verticesPerAttempt: 1e6
|
|
3131
|
+
};
|
|
3132
|
+
/**
|
|
3133
|
+
* The two fixed framings. Neighbourhood draws 1 m per view-box unit, wider 4 m, so the same stroke and label
|
|
3134
|
+
* sizes read well in both. The wider view leaves out buildings, distant paths and small green patches, which turn to
|
|
3135
|
+
* noise at that scale. `minParkArea` is in square view-box units. Labels go to the nearest streets first, with a
|
|
3136
|
+
* minor street counted as `minorLabelDistance` times further away than it is, so the wider view names main roads.
|
|
3137
|
+
*/
|
|
3138
|
+
const FRAMINGS = [{
|
|
3139
|
+
name: "neighbourhood",
|
|
3140
|
+
radiusMeters: 400,
|
|
3141
|
+
zoom: 15,
|
|
3142
|
+
roads: [
|
|
3143
|
+
"major",
|
|
3144
|
+
"minor",
|
|
3145
|
+
"path"
|
|
3146
|
+
],
|
|
3147
|
+
maxPathDistance: 800,
|
|
3148
|
+
buildings: true,
|
|
3149
|
+
minParkArea: .3,
|
|
3150
|
+
maxLabels: 6,
|
|
3151
|
+
minorLabelDistance: 1.5
|
|
3152
|
+
}, {
|
|
3153
|
+
name: "wider",
|
|
3154
|
+
radiusMeters: 1600,
|
|
3155
|
+
zoom: 14,
|
|
3156
|
+
roads: [
|
|
3157
|
+
"major",
|
|
3158
|
+
"minor",
|
|
3159
|
+
"path"
|
|
3160
|
+
],
|
|
3161
|
+
maxPathDistance: 100,
|
|
3162
|
+
buildings: false,
|
|
3163
|
+
minParkArea: 40,
|
|
3164
|
+
maxLabels: 5,
|
|
3165
|
+
minorLabelDistance: 4
|
|
3166
|
+
}];
|
|
3167
|
+
const tilePoint = z.tuple([z.number().finite().min(-8192).max(12288), z.number().finite().min(-8192).max(12288)]).readonly();
|
|
3168
|
+
const paths = z.array(z.array(tilePoint).min(2).max(DRAWING_DECODE_LIMITS.verticesPerPath).readonly()).min(1).max(DRAWING_DECODE_LIMITS.pathsPerFeature).readonly();
|
|
3169
|
+
const featureFields = {
|
|
3170
|
+
kind: z.string().max(80),
|
|
3171
|
+
name: z.string().trim().max(60).regex(/^[^<>\u0000-\u001f\u007f]*$/).nullish().transform((value) => value === undefined || value === null || value === "" ? null : value),
|
|
3172
|
+
sortRank: z.number().finite().min(-1e4).max(1e4).default(0),
|
|
3173
|
+
paths: paths.refine((value) => value.reduce((n, path) => n + path.length, 0) <= DRAWING_DECODE_LIMITS.verticesPerFeature)
|
|
3174
|
+
};
|
|
3175
|
+
/** An unusable optional label must not discard otherwise valid road geometry. */
|
|
3176
|
+
function normalizeMapDrawingName(value) {
|
|
3177
|
+
const parsed = featureFields.name.safeParse(value);
|
|
3178
|
+
return parsed.success ? parsed.data : null;
|
|
3179
|
+
}
|
|
3180
|
+
const feature = z.discriminatedUnion("geometry", [z.strictObject({
|
|
3181
|
+
...featureFields,
|
|
3182
|
+
layer: z.literal("roads"),
|
|
3183
|
+
geometry: z.literal("line")
|
|
3184
|
+
}).readonly(), z.strictObject({
|
|
3185
|
+
...featureFields,
|
|
3186
|
+
layer: z.enum([
|
|
3187
|
+
"earth",
|
|
3188
|
+
"water",
|
|
3189
|
+
"landuse",
|
|
3190
|
+
"buildings"
|
|
3191
|
+
]),
|
|
3192
|
+
geometry: z.literal("polygon")
|
|
3193
|
+
}).refine((value) => value.paths.every((ring) => ring.length >= 3)).readonly()]);
|
|
3194
|
+
const tile = z.strictObject({
|
|
3195
|
+
z: z.union([z.literal(14), z.literal(15)]),
|
|
3196
|
+
x: z.number().int().nonnegative(),
|
|
3197
|
+
y: z.number().int().nonnegative(),
|
|
3198
|
+
extent: z.literal(4096),
|
|
3199
|
+
features: z.array(feature).max(DRAWING_DECODE_LIMITS.featuresPerTile).readonly()
|
|
3200
|
+
}).refine((value) => value.x < 2 ** value.z && value.y < 2 ** value.z).readonly();
|
|
3201
|
+
const mapDrawingInputSchema = z.strictObject({
|
|
3202
|
+
buildId: z.string().regex(/^\d{8}$/),
|
|
3203
|
+
tileSchemaVersion: z.string().regex(/^4\.\d+\.\d+$/),
|
|
3204
|
+
pin: z.lazy(() => locationPinSchema),
|
|
3205
|
+
tiles: z.array(tile).min(1).max(96).readonly()
|
|
3206
|
+
}).superRefine((value, ctx) => {
|
|
3207
|
+
const features = value.tiles.flatMap((item) => item.features);
|
|
3208
|
+
if (features.length > DRAWING_DECODE_LIMITS.featuresPerAttempt || features.reduce((sum, item) => sum + item.paths.reduce((count, path) => count + path.length, 0), 0) > DRAWING_DECODE_LIMITS.verticesPerAttempt) {
|
|
3209
|
+
ctx.addIssue({
|
|
3210
|
+
code: "custom",
|
|
3211
|
+
message: "Decoded geometry exceeds the attempt budget."
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
}).readonly();
|
|
3215
|
+
const point = z.tuple([z.number().min(0).max(DRAWING_SIZE), z.number().min(0).max(DRAWING_SIZE)]).readonly();
|
|
3216
|
+
const polygon = z.strictObject({
|
|
3217
|
+
kind: z.literal("polygon"),
|
|
3218
|
+
fillRule: z.literal("evenodd"),
|
|
3219
|
+
className: z.enum([
|
|
3220
|
+
"map-land",
|
|
3221
|
+
"map-water",
|
|
3222
|
+
"map-park",
|
|
3223
|
+
"map-building"
|
|
3224
|
+
]),
|
|
3225
|
+
rings: z.array(z.array(point).min(3).max(8192).readonly()).min(1).max(DRAWING_DECODE_LIMITS.pathsPerFeature).readonly()
|
|
3226
|
+
}).readonly();
|
|
3227
|
+
const line = z.strictObject({
|
|
3228
|
+
kind: z.literal("line"),
|
|
3229
|
+
className: z.enum([
|
|
3230
|
+
"map-road-major-casing",
|
|
3231
|
+
"map-road-major",
|
|
3232
|
+
"map-road-minor-casing",
|
|
3233
|
+
"map-road-minor",
|
|
3234
|
+
"map-road-path"
|
|
3235
|
+
]),
|
|
3236
|
+
points: z.array(point).min(2).max(8192).readonly()
|
|
3237
|
+
}).readonly();
|
|
3238
|
+
const drawingPathSchema = z.discriminatedUnion("kind", [polygon, line]);
|
|
3239
|
+
const label = z.strictObject({
|
|
3240
|
+
className: z.literal("map-label"),
|
|
3241
|
+
haloClassName: z.literal("map-label-halo"),
|
|
3242
|
+
text: z.string().trim().min(1).max(60).regex(/^[^<>\u0000-\u001f\u007f]*$/),
|
|
3243
|
+
point,
|
|
3244
|
+
angle: z.number().min(-90).max(90)
|
|
3245
|
+
}).readonly();
|
|
3246
|
+
const drawingFrameSchema = z.strictObject({
|
|
3247
|
+
radiusMeters: z.union([z.literal(FRAMINGS[0].radiusMeters), z.literal(FRAMINGS[1].radiusMeters)]),
|
|
3248
|
+
viewBox: z.tuple([
|
|
3249
|
+
z.literal(0),
|
|
3250
|
+
z.literal(0),
|
|
3251
|
+
z.literal(800),
|
|
3252
|
+
z.literal(800)
|
|
3253
|
+
]).readonly(),
|
|
3254
|
+
backgroundClass: z.literal("map-water"),
|
|
3255
|
+
paths: z.array(drawingPathSchema).max(1800).readonly(),
|
|
3256
|
+
labels: z.array(label).max(12).readonly(),
|
|
3257
|
+
pin: z.strictObject({
|
|
3258
|
+
className: z.literal("map-pin"),
|
|
3259
|
+
point: z.tuple([z.literal(400), z.literal(400)]).readonly()
|
|
3260
|
+
}).readonly()
|
|
3261
|
+
}).superRefine((value, ctx) => {
|
|
3262
|
+
const count = value.paths.reduce((sum, path) => sum + (path.kind === "line" ? path.points.length : path.rings.reduce((n, ring) => n + ring.length, 0)), 0);
|
|
3263
|
+
if (count > 5e4) ctx.addIssue({
|
|
3264
|
+
code: "custom",
|
|
3265
|
+
message: "Drawing exceeds its vertex budget."
|
|
3266
|
+
});
|
|
3267
|
+
}).readonly();
|
|
3268
|
+
const mapDrawingSchema = z.strictObject({
|
|
3269
|
+
schemaVersion: z.literal(DRAWING_SCHEMA_VERSION),
|
|
3270
|
+
generatorVersion: z.literal(DRAWING_GENERATOR_VERSION),
|
|
3271
|
+
buildId: z.string().regex(/^\d{8}$/),
|
|
3272
|
+
tileSchemaVersion: z.string().regex(/^4\.\d+\.\d+$/),
|
|
3273
|
+
frames: z.strictObject({
|
|
3274
|
+
neighbourhood: drawingFrameSchema,
|
|
3275
|
+
wider: drawingFrameSchema
|
|
3276
|
+
}).readonly()
|
|
3277
|
+
}).refine((value) => value.frames.neighbourhood.radiusMeters === FRAMINGS[0].radiusMeters && value.frames.wider.radiusMeters === FRAMINGS[1].radiusMeters).refine((value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= MAX_DRAWING_BYTES, { error: "Drawing exceeds its byte budget." }).readonly();
|
|
3278
|
+
|
|
3116
3279
|
//#endregion
|
|
3117
3280
|
//#region ../core/src/string.ts
|
|
3118
3281
|
/**
|
|
@@ -3200,6 +3363,7 @@ const locationValueSchema = z.strictObject({
|
|
|
3200
3363
|
pin: locationPinSchema.nullable().default(null),
|
|
3201
3364
|
doorNote: z.string().trim().max(1e3).nullish().transform(trimmedStringOrNull),
|
|
3202
3365
|
addressMatch: locationAddressMatchSchema.nullable().default(null),
|
|
3366
|
+
addressCheckSettled: z.literal(true).optional(),
|
|
3203
3367
|
ownerConfirmedPinAndAddress: z.boolean().default(false)
|
|
3204
3368
|
}).superRefine((value, context) => {
|
|
3205
3369
|
if (value.address === null && value.pin === null) {
|
|
@@ -3216,6 +3380,13 @@ const locationValueSchema = z.strictObject({
|
|
|
3216
3380
|
message: "Address evidence requires an address."
|
|
3217
3381
|
});
|
|
3218
3382
|
}
|
|
3383
|
+
if (value.address === null && value.addressCheckSettled) {
|
|
3384
|
+
context.addIssue({
|
|
3385
|
+
code: "custom",
|
|
3386
|
+
path: ["addressCheckSettled"],
|
|
3387
|
+
message: "A settled address check requires an address."
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3219
3390
|
if (value.ownerConfirmedPinAndAddress && (value.address === null || value.pin === null)) {
|
|
3220
3391
|
context.addIssue({
|
|
3221
3392
|
code: "custom",
|
|
@@ -3232,7 +3403,8 @@ function parseLocationValue(input) {
|
|
|
3232
3403
|
issues: parsed.error.issues
|
|
3233
3404
|
});
|
|
3234
3405
|
}
|
|
3235
|
-
|
|
3406
|
+
/** One display formatter: a known matching site country is redundant. */
|
|
3407
|
+
function formatLocationAddress(address, siteCountry) {
|
|
3236
3408
|
if (address === null) return null;
|
|
3237
3409
|
const parts = [
|
|
3238
3410
|
address.building,
|
|
@@ -3242,15 +3414,32 @@ function formatLocationAddress(address) {
|
|
|
3242
3414
|
address.locality,
|
|
3243
3415
|
address.region,
|
|
3244
3416
|
address.postalCode,
|
|
3245
|
-
address.country
|
|
3417
|
+
sameLocationCountry(address.country, siteCountry) ? null : address.country
|
|
3246
3418
|
];
|
|
3247
3419
|
return parts.filter((part) => part !== null).join(", ") || null;
|
|
3248
3420
|
}
|
|
3421
|
+
const regionNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
3422
|
+
const countryIdentity = (country) => {
|
|
3423
|
+
const text = country.trim();
|
|
3424
|
+
const code = text.toUpperCase() === "UK" ? "GB" : text.toUpperCase();
|
|
3425
|
+
return (code.length === 2 && /^[A-Z]{2}$/.test(code) ? regionNames.of(code) ?? text : text).toLowerCase();
|
|
3426
|
+
};
|
|
3427
|
+
function sameLocationCountry(country, siteCountry) {
|
|
3428
|
+
return country !== null && Boolean(siteCountry?.trim()) && countryIdentity(country) === countryIdentity(siteCountry ?? "");
|
|
3429
|
+
}
|
|
3430
|
+
const publicLocationSchema = z.strictObject({
|
|
3431
|
+
drawing: z.lazy(() => mapDrawingSchema).optional().catch(undefined),
|
|
3432
|
+
address: locationAddressSchema.nullable(),
|
|
3433
|
+
displayAddress: z.string().nullable().default(null),
|
|
3434
|
+
pin: locationPinSchema.nullable(),
|
|
3435
|
+
doorNote: z.string().max(1e3).nullable(),
|
|
3436
|
+
attribution: z.array(locationAddressMatchSchema.unwrap().shape.attribution.unwrap().element).max(4).readonly()
|
|
3437
|
+
}).readonly();
|
|
3249
3438
|
/** Permission belongs to the resource/source owner before this projection. */
|
|
3250
|
-
function projectPublicLocation(location) {
|
|
3439
|
+
function projectPublicLocation(location, siteCountry) {
|
|
3251
3440
|
return {
|
|
3252
3441
|
address: location.address,
|
|
3253
|
-
displayAddress: formatLocationAddress(location.address),
|
|
3442
|
+
displayAddress: formatLocationAddress(location.address, siteCountry),
|
|
3254
3443
|
pin: location.pin,
|
|
3255
3444
|
doorNote: location.doorNote,
|
|
3256
3445
|
attribution: location.addressMatch?.attribution ?? []
|
|
@@ -3274,6 +3463,73 @@ function toLocationCoordinates(value) {
|
|
|
3274
3463
|
lng: value.lng
|
|
3275
3464
|
};
|
|
3276
3465
|
}
|
|
3466
|
+
/** Decide permission before loading or projecting the selected source. */
|
|
3467
|
+
function decideLocationSourcePermission(source, businessAddressVisibility) {
|
|
3468
|
+
if (source === "business_address" && businessAddressVisibility === undefined) return { kind: "unresolved" };
|
|
3469
|
+
return source === "business_address" && businessAddressVisibility === "hidden" ? { kind: "hidden" } : {
|
|
3470
|
+
kind: "permitted",
|
|
3471
|
+
source
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
function resolvePermittedLocation(permission, location, siteCountry) {
|
|
3475
|
+
if (permission.kind !== "permitted") return permission;
|
|
3476
|
+
return location === null ? {
|
|
3477
|
+
kind: "missing",
|
|
3478
|
+
source: permission.source
|
|
3479
|
+
} : {
|
|
3480
|
+
kind: "visible",
|
|
3481
|
+
source: permission.source,
|
|
3482
|
+
location: projectPublicLocation(location, siteCountry)
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
//#endregion
|
|
3487
|
+
//#region ../core/src/locationLinks.ts
|
|
3488
|
+
/** Both outbound actions use the arrival pin, never match evidence. */
|
|
3489
|
+
function locationMapLinks(input) {
|
|
3490
|
+
const target = input.location != null ? `${input.location.lat},${input.location.lng}` : normalizeLocationAddressText(input.addressText);
|
|
3491
|
+
if (target === null) return {
|
|
3492
|
+
directions: null,
|
|
3493
|
+
googleMaps: null
|
|
3494
|
+
};
|
|
3495
|
+
const directions = new URL("https://www.google.com/maps/dir/");
|
|
3496
|
+
directions.searchParams.set("api", "1");
|
|
3497
|
+
directions.searchParams.set("destination", target);
|
|
3498
|
+
const search = new URL("https://www.google.com/maps/search/");
|
|
3499
|
+
search.searchParams.set("api", "1");
|
|
3500
|
+
search.searchParams.set("query", target);
|
|
3501
|
+
return {
|
|
3502
|
+
directions: directions.toString(),
|
|
3503
|
+
googleMaps: search.toString()
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
//#endregion
|
|
3508
|
+
//#region ../core/src/stable-json.ts
|
|
3509
|
+
function stableJsonStringify(value) {
|
|
3510
|
+
return JSON.stringify(sortStableJsonValue(value));
|
|
3511
|
+
}
|
|
3512
|
+
function sortStableJsonValue(value) {
|
|
3513
|
+
if (value === null) return null;
|
|
3514
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
3515
|
+
if (typeof value === "number") {
|
|
3516
|
+
if (!Number.isFinite(value)) {
|
|
3517
|
+
throw new TypeError("Stable JSON only supports finite numbers");
|
|
3518
|
+
}
|
|
3519
|
+
return value;
|
|
3520
|
+
}
|
|
3521
|
+
if (Array.isArray(value)) {
|
|
3522
|
+
return value.map(sortStableJsonValue);
|
|
3523
|
+
}
|
|
3524
|
+
if (typeof value === "object") {
|
|
3525
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3526
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
3527
|
+
throw new TypeError("Stable JSON only supports plain objects");
|
|
3528
|
+
}
|
|
3529
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortStableJsonValue(child)]));
|
|
3530
|
+
}
|
|
3531
|
+
throw new TypeError(`Stable JSON does not support ${typeof value}`);
|
|
3532
|
+
}
|
|
3277
3533
|
|
|
3278
3534
|
//#endregion
|
|
3279
3535
|
//#region ../core/src/location-relationship.ts
|
|
@@ -3293,15 +3549,15 @@ function decideLocationRelationship(location) {
|
|
|
3293
3549
|
if (location === null) return { kind: "empty" };
|
|
3294
3550
|
if (location.pin === null) return { kind: "address_only" };
|
|
3295
3551
|
if (location.address === null) return { kind: "pin_only" };
|
|
3296
|
-
if (location.addressMatch === null
|
|
3552
|
+
if (location.addressMatch === null) {
|
|
3297
3553
|
return { kind: "unchecked" };
|
|
3298
3554
|
}
|
|
3299
|
-
const { point, precision } = location.addressMatch;
|
|
3300
|
-
const distanceMetres = locationDistanceMetres(point, location.pin);
|
|
3301
|
-
const thresholdMetres = LOCATION_MATCH_THRESHOLDS_METRES[precision];
|
|
3555
|
+
const { point: point$1, precision } = location.addressMatch;
|
|
3556
|
+
const distanceMetres = locationDistanceMetres(point$1, location.pin);
|
|
3557
|
+
const thresholdMetres = LOCATION_MATCH_THRESHOLDS_METRES[precision === "unknown" ? "building" : precision];
|
|
3302
3558
|
const beyondThreshold = distanceMetres > thresholdMetres * (1 + 8 * Number.EPSILON);
|
|
3303
3559
|
return {
|
|
3304
|
-
kind: beyondThreshold ? "far" : precision === "building" ? "near" : "approximate",
|
|
3560
|
+
kind: beyondThreshold ? "far" : precision === "building" || precision === "unknown" ? "near" : "approximate",
|
|
3305
3561
|
distanceMetres,
|
|
3306
3562
|
thresholdMetres,
|
|
3307
3563
|
precision
|
|
@@ -3315,6 +3571,7 @@ const spatialAddressParts = [
|
|
|
3315
3571
|
"country"
|
|
3316
3572
|
];
|
|
3317
3573
|
const comparisonText = (text) => (text ?? "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
3574
|
+
/** Address evidence identity excludes refinements such as unit, floor and region. */
|
|
3318
3575
|
function sameMeaningfulAddress(a, b) {
|
|
3319
3576
|
if (a === null || b === null) return a === b;
|
|
3320
3577
|
return spatialAddressParts.every((part) => comparisonText(a[part]) === comparisonText(b[part]));
|
|
@@ -3322,12 +3579,21 @@ function sameMeaningfulAddress(a, b) {
|
|
|
3322
3579
|
function samePin(a, b) {
|
|
3323
3580
|
return a === null || b === null ? a === b : a.lat === b.lat && a.lng === b.lng;
|
|
3324
3581
|
}
|
|
3582
|
+
/** Spatial identity ignores unit/floor/region/note edits; picker reply tags share this rule. */
|
|
3583
|
+
function sameLocationSpatialPair(a, b) {
|
|
3584
|
+
return a === null || b === null ? a === b : sameMeaningfulAddress(a.address, b.address) && samePin(a.pin, b.pin);
|
|
3585
|
+
}
|
|
3325
3586
|
/** Edits cannot carry forward old confirmation or address evidence by accident. */
|
|
3326
3587
|
function editLocationValue(previous, details) {
|
|
3327
3588
|
if (details.address === null && details.pin === null) return ok(null);
|
|
3589
|
+
const authored = {
|
|
3590
|
+
address: details.address,
|
|
3591
|
+
pin: details.pin,
|
|
3592
|
+
doorNote: details.doorNote
|
|
3593
|
+
};
|
|
3328
3594
|
if (previous === null) {
|
|
3329
3595
|
return parseLocationValue({
|
|
3330
|
-
...
|
|
3596
|
+
...authored,
|
|
3331
3597
|
addressMatch: null,
|
|
3332
3598
|
ownerConfirmedPinAndAddress: false
|
|
3333
3599
|
});
|
|
@@ -3335,8 +3601,9 @@ function editLocationValue(previous, details) {
|
|
|
3335
3601
|
const sameAddress = sameMeaningfulAddress(previous.address, details.address);
|
|
3336
3602
|
const samePair = sameAddress && samePin(previous.pin, details.pin);
|
|
3337
3603
|
return parseLocationValue({
|
|
3338
|
-
...
|
|
3604
|
+
...authored,
|
|
3339
3605
|
addressMatch: sameAddress ? previous.addressMatch : null,
|
|
3606
|
+
...sameAddress && previous.addressCheckSettled ? { addressCheckSettled: true } : {},
|
|
3340
3607
|
ownerConfirmedPinAndAddress: samePair && previous.ownerConfirmedPinAndAddress
|
|
3341
3608
|
});
|
|
3342
3609
|
}
|
|
@@ -3365,6 +3632,21 @@ function decideLocationSave(location, activity) {
|
|
|
3365
3632
|
location
|
|
3366
3633
|
};
|
|
3367
3634
|
}
|
|
3635
|
+
/** Opening lookup evidence may change provider facts only, never the owner's saved place. */
|
|
3636
|
+
function planLocationOpeningEvidence(previous, evidence) {
|
|
3637
|
+
const parsed = parseLocationValue(evidence);
|
|
3638
|
+
if (previous === null || !parsed.ok || parsed.value === null) return err({ kind: "invalid_location" });
|
|
3639
|
+
const next = parsed.value;
|
|
3640
|
+
const authored = (value) => ({
|
|
3641
|
+
address: value.address,
|
|
3642
|
+
pin: value.pin,
|
|
3643
|
+
doorNote: value.doorNote,
|
|
3644
|
+
ownerConfirmedPinAndAddress: value.ownerConfirmedPinAndAddress
|
|
3645
|
+
});
|
|
3646
|
+
if (stableJsonStringify(authored(previous)) !== stableJsonStringify(authored(next)) || next.addressMatch === null && next.addressCheckSettled !== true) return err({ kind: "invalid_location" });
|
|
3647
|
+
if (stableJsonStringify(previous) === stableJsonStringify(next)) return ok(next);
|
|
3648
|
+
return previous.addressMatch !== null || previous.addressCheckSettled === true ? err({ kind: "invalid_location" }) : ok(next);
|
|
3649
|
+
}
|
|
3368
3650
|
|
|
3369
3651
|
//#endregion
|
|
3370
3652
|
//#region ../core/src/location-migration.ts
|
|
@@ -3475,17 +3757,25 @@ function mapVenueAddress(address) {
|
|
|
3475
3757
|
};
|
|
3476
3758
|
return Object.values(structured).some((part) => displayComparison(part) !== "") ? structured : { street: firstText(address.formatted, address.summary) };
|
|
3477
3759
|
}
|
|
3478
|
-
|
|
3760
|
+
const displayTokens = (text) => (text ?? "").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
3761
|
+
/** Only redundant trailing country text is disposable; ordered address words must all survive. */
|
|
3762
|
+
function comparableDisplay(text, countries) {
|
|
3763
|
+
const tokens = displayTokens(text);
|
|
3764
|
+
const suffixIndex = tokens.findIndex((_token, index) => countries.some((country) => sameLocationCountry(tokens.slice(index).join(" "), country)));
|
|
3765
|
+
return (suffixIndex < 0 ? tokens : tokens.slice(0, suffixIndex)).join(" ");
|
|
3766
|
+
}
|
|
3767
|
+
function checkDisplayText(result, text, siteCountry) {
|
|
3479
3768
|
if (!result.ok) return result;
|
|
3480
3769
|
const original = text?.trim() ?? "";
|
|
3481
3770
|
const derived = formatLocationAddress(result.value?.address ?? null);
|
|
3482
|
-
|
|
3771
|
+
const countries = [result.value?.address?.country, siteCountry];
|
|
3772
|
+
return original.length > 0 && comparableDisplay(original, countries) !== comparableDisplay(derived, countries) ? err({
|
|
3483
3773
|
kind: "display_address_conflict",
|
|
3484
3774
|
original,
|
|
3485
3775
|
derived
|
|
3486
3776
|
}) : result;
|
|
3487
3777
|
}
|
|
3488
|
-
function convertBusiness(value) {
|
|
3778
|
+
function convertBusiness(value, siteCountry) {
|
|
3489
3779
|
if (value == null) return ok(null);
|
|
3490
3780
|
const parsed = businessAddressSchema.safeParse(value);
|
|
3491
3781
|
if (!parsed.success) return err({
|
|
@@ -3500,7 +3790,7 @@ function convertBusiness(value) {
|
|
|
3500
3790
|
postalCode: parsed.data.postalCode,
|
|
3501
3791
|
country: parsed.data.countryCode
|
|
3502
3792
|
}, parsed.data);
|
|
3503
|
-
return checkDisplayText(result, parsed.data.displayAddress);
|
|
3793
|
+
return checkDisplayText(result, parsed.data.displayAddress, siteCountry);
|
|
3504
3794
|
}
|
|
3505
3795
|
function convertCustom(value) {
|
|
3506
3796
|
if (value == null) return ok(null);
|
|
@@ -3514,7 +3804,7 @@ function convertCustom(value) {
|
|
|
3514
3804
|
function convertLegacyLocation(input) {
|
|
3515
3805
|
switch (input.kind) {
|
|
3516
3806
|
case "venue": return convertVenue(input);
|
|
3517
|
-
case "business": return convertBusiness(input.value);
|
|
3807
|
+
case "business": return convertBusiness(input.value, input.siteCountry);
|
|
3518
3808
|
case "custom": return convertCustom(input.value);
|
|
3519
3809
|
}
|
|
3520
3810
|
}
|
|
@@ -3526,7 +3816,7 @@ function normalizeCountryCode(value) {
|
|
|
3526
3816
|
return trimmed !== null ? trimmed.toUpperCase() : null;
|
|
3527
3817
|
}
|
|
3528
3818
|
function normalizeCoordinate(value) {
|
|
3529
|
-
return typeof value === "number"
|
|
3819
|
+
return typeof value === "number" ? value : null;
|
|
3530
3820
|
}
|
|
3531
3821
|
function isLatitude(value) {
|
|
3532
3822
|
return value >= -90 && value <= 90;
|
|
@@ -3534,7 +3824,7 @@ function isLatitude(value) {
|
|
|
3534
3824
|
function isLongitude(value) {
|
|
3535
3825
|
return value >= -180 && value <= 180;
|
|
3536
3826
|
}
|
|
3537
|
-
function buildBusinessAddressDisplayText(input) {
|
|
3827
|
+
function buildBusinessAddressDisplayText(input, siteCountry) {
|
|
3538
3828
|
return formatLocationAddress({
|
|
3539
3829
|
building: null,
|
|
3540
3830
|
street: trimmedStringOrNull(input.addressLine1),
|
|
@@ -3544,7 +3834,7 @@ function buildBusinessAddressDisplayText(input) {
|
|
|
3544
3834
|
region: trimmedStringOrNull(input.region),
|
|
3545
3835
|
postalCode: trimmedStringOrNull(input.postalCode),
|
|
3546
3836
|
country: normalizeCountryCode(input.countryCode)
|
|
3547
|
-
});
|
|
3837
|
+
}, siteCountry);
|
|
3548
3838
|
}
|
|
3549
3839
|
function validateBusinessAddressDraft(input) {
|
|
3550
3840
|
const addressLine1 = trimmedStringOrNull(input.addressLine1);
|
|
@@ -3556,16 +3846,13 @@ function validateBusinessAddressDraft(input) {
|
|
|
3556
3846
|
const displayAddress = trimmedStringOrNull(input.displayAddress) ?? buildBusinessAddressDisplayText(input);
|
|
3557
3847
|
const lat = normalizeCoordinate(input.lat);
|
|
3558
3848
|
const lng = normalizeCoordinate(input.lng);
|
|
3559
|
-
const errors = [
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
if (lng === null) errors.push("missing_longitude");
|
|
3567
|
-
else if (!isLongitude(lng)) errors.push("invalid_longitude");
|
|
3568
|
-
if (errors.length > 0 || addressLine1 === null || locality === null || countryCode === null || displayAddress === null || lat === null || lng === null) {
|
|
3849
|
+
const errors = [...missingPostalFields({
|
|
3850
|
+
addressLine1,
|
|
3851
|
+
locality,
|
|
3852
|
+
countryCode,
|
|
3853
|
+
displayAddress
|
|
3854
|
+
}), ...businessPinErrors(lat, lng)];
|
|
3855
|
+
if (errors.length > 0 || addressLine1 === null || locality === null || countryCode === null || displayAddress === null) {
|
|
3569
3856
|
return {
|
|
3570
3857
|
success: false,
|
|
3571
3858
|
errors
|
|
@@ -3598,6 +3885,95 @@ function businessAddressToAddressedLocation(address) {
|
|
|
3598
3885
|
lng: address.lng
|
|
3599
3886
|
};
|
|
3600
3887
|
}
|
|
3888
|
+
/** Temporary postal/editor response adapter. Canonical storage never uses this shape. */
|
|
3889
|
+
function businessAddressFromLocation(location, siteCountry) {
|
|
3890
|
+
if (location === null || location.address === null) return null;
|
|
3891
|
+
const address = location.address;
|
|
3892
|
+
const displayAddress = formatLocationAddress(address, siteCountry);
|
|
3893
|
+
if (displayAddress === null) return null;
|
|
3894
|
+
const pin = location.pin ?? {
|
|
3895
|
+
lat: null,
|
|
3896
|
+
lng: null
|
|
3897
|
+
};
|
|
3898
|
+
return {
|
|
3899
|
+
addressLine1: [address.building, address.street].filter((part) => part !== null).join(", "),
|
|
3900
|
+
addressLine2: [address.unit, address.floor].filter((part) => part !== null).join(", ") || null,
|
|
3901
|
+
locality: address.locality ?? "",
|
|
3902
|
+
region: address.region,
|
|
3903
|
+
postalCode: address.postalCode,
|
|
3904
|
+
countryCode: address.country ?? "",
|
|
3905
|
+
displayAddress,
|
|
3906
|
+
lat: pin.lat,
|
|
3907
|
+
lng: pin.lng
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
/** Legacy editor inputs are explicit write adapters, never fallback storage readers. */
|
|
3911
|
+
function planBusinessLocationWrite(previous, input) {
|
|
3912
|
+
if ("locationValue" in input && input.expectedLocationValue !== undefined) {
|
|
3913
|
+
if (stableJsonStringify(previous) !== stableJsonStringify(input.expectedLocationValue)) return err({ kind: "invalid_location" });
|
|
3914
|
+
return planLocationOpeningEvidence(previous, input.locationValue);
|
|
3915
|
+
}
|
|
3916
|
+
const planned = "locationValue" in input ? parseLocationValue(input.locationValue) : planLegacyBusinessLocation(previous, input.businessAddress);
|
|
3917
|
+
if (!planned.ok) return err({ kind: "locationValue" in input ? "invalid_location" : "invalid_legacy_location" });
|
|
3918
|
+
const decision = decideLocationSave(planned.value, "idle");
|
|
3919
|
+
return decision.kind === "blocked" ? err({ kind: "unconfirmed_mismatch" }) : ok(decision.location);
|
|
3920
|
+
}
|
|
3921
|
+
function planLegacyBusinessLocation(previous, address) {
|
|
3922
|
+
if (address === null) return ok(null);
|
|
3923
|
+
if (previous === null) return convertLegacyBusinessEdit(previous, address);
|
|
3924
|
+
const projection = businessAddressFromLocation(previous);
|
|
3925
|
+
return projection !== null && sameBusinessPostalProjection(projection, address) ? editBusinessPin(previous, address) : convertLegacyBusinessEdit(previous, address);
|
|
3926
|
+
}
|
|
3927
|
+
function editBusinessPin(previous, address) {
|
|
3928
|
+
const pin = locationPinSchema.nullable().safeParse(address.lat === null && address.lng === null ? null : {
|
|
3929
|
+
lat: address.lat,
|
|
3930
|
+
lng: address.lng
|
|
3931
|
+
});
|
|
3932
|
+
return pin.success ? editLocationValue(previous, {
|
|
3933
|
+
address: previous.address,
|
|
3934
|
+
pin: pin.data,
|
|
3935
|
+
doorNote: previous.doorNote
|
|
3936
|
+
}) : err({ kind: "invalid_location" });
|
|
3937
|
+
}
|
|
3938
|
+
function convertLegacyBusinessEdit(previous, address) {
|
|
3939
|
+
const converted = convertLegacyLocation({
|
|
3940
|
+
kind: "business",
|
|
3941
|
+
value: address
|
|
3942
|
+
});
|
|
3943
|
+
if (!converted.ok || converted.value === null) return converted;
|
|
3944
|
+
return editLocationValue(previous, {
|
|
3945
|
+
address: converted.value.address,
|
|
3946
|
+
pin: converted.value.pin,
|
|
3947
|
+
doorNote: previous?.doorNote ?? null
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
function sameBusinessPostalProjection(a, b) {
|
|
3951
|
+
const fields = [
|
|
3952
|
+
"addressLine1",
|
|
3953
|
+
"addressLine2",
|
|
3954
|
+
"locality",
|
|
3955
|
+
"region",
|
|
3956
|
+
"postalCode",
|
|
3957
|
+
"countryCode",
|
|
3958
|
+
"displayAddress"
|
|
3959
|
+
];
|
|
3960
|
+
return fields.every((field) => (a[field] ?? "").trim().replace(/\s+/g, " ").toLowerCase() === (b[field] ?? "").trim().replace(/\s+/g, " ").toLowerCase());
|
|
3961
|
+
}
|
|
3962
|
+
function missingPostalFields(fields) {
|
|
3963
|
+
const required = [
|
|
3964
|
+
[fields.addressLine1, "missing_address_line_1"],
|
|
3965
|
+
[fields.locality, "missing_locality"],
|
|
3966
|
+
[fields.countryCode, "missing_country_code"],
|
|
3967
|
+
[fields.displayAddress, "missing_display_address"]
|
|
3968
|
+
];
|
|
3969
|
+
return required.flatMap(([value, code]) => value === null ? [code] : []);
|
|
3970
|
+
}
|
|
3971
|
+
function businessPinErrors(lat, lng) {
|
|
3972
|
+
if (lat === null && lng === null) return [];
|
|
3973
|
+
const latitude = lat === null ? ["missing_latitude"] : !isLatitude(lat) ? ["invalid_latitude"] : [];
|
|
3974
|
+
const longitude = lng === null ? ["missing_longitude"] : !isLongitude(lng) ? ["invalid_longitude"] : [];
|
|
3975
|
+
return [...latitude, ...longitude];
|
|
3976
|
+
}
|
|
3601
3977
|
|
|
3602
3978
|
//#endregion
|
|
3603
3979
|
//#region ../core/src/try-async.ts
|
|
@@ -4702,12 +5078,12 @@ function parsePublicSocialLinks(value) {
|
|
|
4702
5078
|
});
|
|
4703
5079
|
continue;
|
|
4704
5080
|
}
|
|
4705
|
-
const label = trimmedOptionalString(rawLink.label);
|
|
4706
|
-
if (rawLink.kind === "custom" && label !== null && label !== undefined) {
|
|
5081
|
+
const label$1 = trimmedOptionalString(rawLink.label);
|
|
5082
|
+
if (rawLink.kind === "custom" && label$1 !== null && label$1 !== undefined) {
|
|
4707
5083
|
links.push({
|
|
4708
5084
|
id: id.value,
|
|
4709
5085
|
kind: "custom",
|
|
4710
|
-
label,
|
|
5086
|
+
label: label$1,
|
|
4711
5087
|
url: url.value
|
|
4712
5088
|
});
|
|
4713
5089
|
continue;
|
|
@@ -5892,10 +6268,10 @@ function parseFooterEngagementSection(value) {
|
|
|
5892
6268
|
});
|
|
5893
6269
|
}
|
|
5894
6270
|
function upsertFooterEngagementSection(composition, section) {
|
|
5895
|
-
const hasEngagement = composition.sections.some((candidate) => candidate.kind === "engagement");
|
|
6271
|
+
const hasEngagement = composition.sections.some((candidate$1) => candidate$1.kind === "engagement");
|
|
5896
6272
|
return validateFooterComposition({
|
|
5897
6273
|
...composition,
|
|
5898
|
-
sections: hasEngagement ? composition.sections.map((candidate) => candidate.kind === "engagement" ? section : candidate) : [...composition.sections, section]
|
|
6274
|
+
sections: hasEngagement ? composition.sections.map((candidate$1) => candidate$1.kind === "engagement" ? section : candidate$1) : [...composition.sections, section]
|
|
5899
6275
|
});
|
|
5900
6276
|
}
|
|
5901
6277
|
function removeFooterEngagementSection(composition) {
|
|
@@ -6956,9 +7332,9 @@ function selectSubmissionPreviewFields(schemaFields, submissions) {
|
|
|
6956
7332
|
if (id === null) return null;
|
|
6957
7333
|
const type = (field.type ?? "text").toString().toLowerCase();
|
|
6958
7334
|
if (shouldHideInPreview(type)) return null;
|
|
6959
|
-
const label = trimmedStringOrNull(field.label?.toString()) ?? humanizeSubmissionFieldKey(id);
|
|
7335
|
+
const label$1 = trimmedStringOrNull(field.label?.toString()) ?? humanizeSubmissionFieldKey(id);
|
|
6960
7336
|
const normalizedId = id.toLowerCase();
|
|
6961
|
-
const normalizedLabel = label.toLowerCase();
|
|
7337
|
+
const normalizedLabel = label$1.toLowerCase();
|
|
6962
7338
|
let score = 0;
|
|
6963
7339
|
if (PRIORITY_IDS.has(normalizedId)) score += 500;
|
|
6964
7340
|
if (normalizedId.includes("name") || normalizedLabel.includes("name")) score += 550;
|
|
@@ -6981,7 +7357,7 @@ function selectSubmissionPreviewFields(schemaFields, submissions) {
|
|
|
6981
7357
|
}
|
|
6982
7358
|
return {
|
|
6983
7359
|
id,
|
|
6984
|
-
label,
|
|
7360
|
+
label: label$1,
|
|
6985
7361
|
type,
|
|
6986
7362
|
score,
|
|
6987
7363
|
index
|
|
@@ -7514,33 +7890,6 @@ function generateUuidV4(cryptoSource = globalThis.crypto) {
|
|
|
7514
7890
|
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
7515
7891
|
}
|
|
7516
7892
|
|
|
7517
|
-
//#endregion
|
|
7518
|
-
//#region ../core/src/stable-json.ts
|
|
7519
|
-
function stableJsonStringify(value) {
|
|
7520
|
-
return JSON.stringify(sortStableJsonValue(value));
|
|
7521
|
-
}
|
|
7522
|
-
function sortStableJsonValue(value) {
|
|
7523
|
-
if (value === null) return null;
|
|
7524
|
-
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
7525
|
-
if (typeof value === "number") {
|
|
7526
|
-
if (!Number.isFinite(value)) {
|
|
7527
|
-
throw new TypeError("Stable JSON only supports finite numbers");
|
|
7528
|
-
}
|
|
7529
|
-
return value;
|
|
7530
|
-
}
|
|
7531
|
-
if (Array.isArray(value)) {
|
|
7532
|
-
return value.map(sortStableJsonValue);
|
|
7533
|
-
}
|
|
7534
|
-
if (typeof value === "object") {
|
|
7535
|
-
const prototype = Object.getPrototypeOf(value);
|
|
7536
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
7537
|
-
throw new TypeError("Stable JSON only supports plain objects");
|
|
7538
|
-
}
|
|
7539
|
-
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortStableJsonValue(child)]));
|
|
7540
|
-
}
|
|
7541
|
-
throw new TypeError(`Stable JSON does not support ${typeof value}`);
|
|
7542
|
-
}
|
|
7543
|
-
|
|
7544
7893
|
//#endregion
|
|
7545
7894
|
//#region ../core/src/secret.ts
|
|
7546
7895
|
/**
|
|
@@ -7943,14 +8292,14 @@ const BOOKING_DEFAULTS_BY_BEHAVIOUR = {
|
|
|
7943
8292
|
memberships: "off"
|
|
7944
8293
|
}
|
|
7945
8294
|
};
|
|
7946
|
-
function verticalBookingPreset(id, verticalId, canonicalBehaviour, label, pluralLabel, description, availability$1 = "onboarding") {
|
|
8295
|
+
function verticalBookingPreset(id, verticalId, canonicalBehaviour, label$1, pluralLabel, description, availability$1 = "onboarding") {
|
|
7947
8296
|
return {
|
|
7948
8297
|
id,
|
|
7949
8298
|
verticalId,
|
|
7950
8299
|
canonicalBehaviour,
|
|
7951
8300
|
engine: BOOKING_ENGINE_BY_BEHAVIOUR[canonicalBehaviour],
|
|
7952
8301
|
availability: availability$1,
|
|
7953
|
-
label,
|
|
8302
|
+
label: label$1,
|
|
7954
8303
|
pluralLabel,
|
|
7955
8304
|
description
|
|
7956
8305
|
};
|
|
@@ -8826,6 +9175,616 @@ function resolvePublicSiteVisibility(input) {
|
|
|
8826
9175
|
return "live";
|
|
8827
9176
|
}
|
|
8828
9177
|
|
|
9178
|
+
//#endregion
|
|
9179
|
+
//#region ../core/src/map-drawing/geometry.ts
|
|
9180
|
+
const VIEW_BOUNDS = {
|
|
9181
|
+
left: 0,
|
|
9182
|
+
top: 0,
|
|
9183
|
+
right: 800,
|
|
9184
|
+
bottom: 800
|
|
9185
|
+
};
|
|
9186
|
+
function samePoint(a, b) {
|
|
9187
|
+
return a[0] === b[0] && a[1] === b[1];
|
|
9188
|
+
}
|
|
9189
|
+
function distance(a, b) {
|
|
9190
|
+
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
9191
|
+
}
|
|
9192
|
+
function roundCoordinate(value) {
|
|
9193
|
+
const rounded = Math.round(value * 10) / 10;
|
|
9194
|
+
return rounded === 0 ? 0 : rounded;
|
|
9195
|
+
}
|
|
9196
|
+
function roundPoint(point$1) {
|
|
9197
|
+
return [roundCoordinate(point$1[0]), roundCoordinate(point$1[1])];
|
|
9198
|
+
}
|
|
9199
|
+
/** Liang–Barsky: one bounded segment, no fabricated connecting line across the viewport. */
|
|
9200
|
+
function clipSegment(a, b, bounds) {
|
|
9201
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
9202
|
+
const edges = [
|
|
9203
|
+
[-dx, a[0] - bounds.left],
|
|
9204
|
+
[dx, bounds.right - a[0]],
|
|
9205
|
+
[-dy, a[1] - bounds.top],
|
|
9206
|
+
[dy, bounds.bottom - a[1]]
|
|
9207
|
+
];
|
|
9208
|
+
const interval = edges.reduce((range, [p, q]) => {
|
|
9209
|
+
if (range === null || p === 0 && q < 0) return null;
|
|
9210
|
+
if (p === 0) return range;
|
|
9211
|
+
const ratio = q / p;
|
|
9212
|
+
const next = p < 0 ? [Math.max(range[0], ratio), range[1]] : [range[0], Math.min(range[1], ratio)];
|
|
9213
|
+
return next[0] > next[1] ? null : next;
|
|
9214
|
+
}, [0, 1]);
|
|
9215
|
+
return interval === null ? null : [[a[0] + interval[0] * dx, a[1] + interval[0] * dy], [a[0] + interval[1] * dx, a[1] + interval[1] * dy]];
|
|
9216
|
+
}
|
|
9217
|
+
function segments(points, closed = false) {
|
|
9218
|
+
return points.flatMap((start, index) => {
|
|
9219
|
+
const end = points[index + 1] ?? (closed ? points[0] : undefined);
|
|
9220
|
+
return end === undefined ? [] : [{
|
|
9221
|
+
start,
|
|
9222
|
+
end
|
|
9223
|
+
}];
|
|
9224
|
+
});
|
|
9225
|
+
}
|
|
9226
|
+
function clipLine(points, bounds) {
|
|
9227
|
+
return segments(points).reduce((paths$1, { start, end }) => {
|
|
9228
|
+
const segment = clipSegment(start, end, bounds);
|
|
9229
|
+
if (segment === null || samePoint(segment[0], segment[1])) return paths$1;
|
|
9230
|
+
const previous = paths$1[paths$1.length - 1];
|
|
9231
|
+
const last = previous?.[previous.length - 1];
|
|
9232
|
+
return previous !== undefined && last !== undefined && samePoint(last, segment[0]) ? [...paths$1.slice(0, -1), [...previous, segment[1]]] : [...paths$1, segment];
|
|
9233
|
+
}, []);
|
|
9234
|
+
}
|
|
9235
|
+
function polygonEdges(bounds) {
|
|
9236
|
+
const vertical = (x) => (a, b) => [x, a[1] + (b[1] - a[1]) * (x - a[0]) / (b[0] - a[0])];
|
|
9237
|
+
const horizontal = (y) => (a, b) => [a[0] + (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]), y];
|
|
9238
|
+
return [
|
|
9239
|
+
{
|
|
9240
|
+
inside: (p) => p[0] >= bounds.left,
|
|
9241
|
+
cross: vertical(bounds.left)
|
|
9242
|
+
},
|
|
9243
|
+
{
|
|
9244
|
+
inside: (p) => p[0] <= bounds.right,
|
|
9245
|
+
cross: vertical(bounds.right)
|
|
9246
|
+
},
|
|
9247
|
+
{
|
|
9248
|
+
inside: (p) => p[1] >= bounds.top,
|
|
9249
|
+
cross: horizontal(bounds.top)
|
|
9250
|
+
},
|
|
9251
|
+
{
|
|
9252
|
+
inside: (p) => p[1] <= bounds.bottom,
|
|
9253
|
+
cross: horizontal(bounds.bottom)
|
|
9254
|
+
}
|
|
9255
|
+
];
|
|
9256
|
+
}
|
|
9257
|
+
function clipPolygonEdge(points, edge) {
|
|
9258
|
+
return segments(points, true).flatMap(({ start, end }) => {
|
|
9259
|
+
const wasInside = edge.inside(start), isInside = edge.inside(end);
|
|
9260
|
+
if (wasInside && isInside) return [end];
|
|
9261
|
+
if (wasInside) return [edge.cross(start, end)];
|
|
9262
|
+
return isInside ? [edge.cross(start, end), end] : [];
|
|
9263
|
+
});
|
|
9264
|
+
}
|
|
9265
|
+
function clipPolygon(points, bounds) {
|
|
9266
|
+
const first = points[0], last = points[points.length - 1];
|
|
9267
|
+
const open = first !== undefined && last !== undefined && samePoint(first, last) ? points.slice(0, -1) : points;
|
|
9268
|
+
return polygonEdges(bounds).reduce((ring, edge) => clipPolygonEdge(ring, edge), open);
|
|
9269
|
+
}
|
|
9270
|
+
function deviation(point$1, start, end) {
|
|
9271
|
+
const dx = end[0] - start[0], dy = end[1] - start[1];
|
|
9272
|
+
const fraction = Math.max(0, Math.min(1, ((point$1[0] - start[0]) * dx + (point$1[1] - start[1]) * dy) / (dx * dx + dy * dy || 1)));
|
|
9273
|
+
return distance(point$1, [start[0] + fraction * dx, start[1] + fraction * dy]);
|
|
9274
|
+
}
|
|
9275
|
+
function simplifyChunk(points) {
|
|
9276
|
+
const start = points[0], end = points[points.length - 1];
|
|
9277
|
+
if (start === undefined || end === undefined || points.length <= 2) return points;
|
|
9278
|
+
const furthest = points.slice(1, -1).reduce((best, point$1, index) => {
|
|
9279
|
+
const error = deviation(point$1, start, end);
|
|
9280
|
+
return error > best.error ? {
|
|
9281
|
+
error,
|
|
9282
|
+
index: index + 1
|
|
9283
|
+
} : best;
|
|
9284
|
+
}, {
|
|
9285
|
+
error: .8,
|
|
9286
|
+
index: -1
|
|
9287
|
+
});
|
|
9288
|
+
return furthest.index < 0 ? [start, end] : [...simplifyChunk(points.slice(0, furthest.index + 1)).slice(0, -1), ...simplifyChunk(points.slice(furthest.index))];
|
|
9289
|
+
}
|
|
9290
|
+
/** Douglas–Peucker in overlapping 256-point chunks bounds recursion/work on hostile zigzags. */
|
|
9291
|
+
function simplify(points, closed) {
|
|
9292
|
+
const rounded = points.map(roundPoint);
|
|
9293
|
+
const open = rounded.filter((point$1, index) => rounded[index - 1] === undefined || !samePoint(point$1, rounded[index - 1] ?? point$1));
|
|
9294
|
+
const first = open[0], last = open[open.length - 1];
|
|
9295
|
+
if (first === undefined || last === undefined) return [];
|
|
9296
|
+
const input = closed && !samePoint(first, last) ? [...open, first] : open;
|
|
9297
|
+
const kept = Array.from({ length: Math.ceil((input.length - 1) / 255) }, (_, index) => simplifyChunk(input.slice(index * 255, index * 255 + 256))).flatMap((chunk, index) => index === 0 ? chunk : chunk.slice(1));
|
|
9298
|
+
const start = kept[0], end = kept[kept.length - 1];
|
|
9299
|
+
return closed && start !== undefined && end !== undefined && samePoint(start, end) ? kept.slice(0, -1) : kept;
|
|
9300
|
+
}
|
|
9301
|
+
function expandBounds(bounds, by) {
|
|
9302
|
+
return {
|
|
9303
|
+
left: bounds.left - by,
|
|
9304
|
+
top: bounds.top - by,
|
|
9305
|
+
right: bounds.right + by,
|
|
9306
|
+
bottom: bounds.bottom + by
|
|
9307
|
+
};
|
|
9308
|
+
}
|
|
9309
|
+
function polygonArea(points) {
|
|
9310
|
+
return Math.abs(segments(points, true).reduce((area, { start, end }) => area + start[0] * end[1] - end[0] * start[1], 0)) / 2;
|
|
9311
|
+
}
|
|
9312
|
+
|
|
9313
|
+
//#endregion
|
|
9314
|
+
//#region ../core/src/map-drawing/projection.ts
|
|
9315
|
+
const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
|
|
9316
|
+
const MERCATOR_LIMIT = 85.0511287798066;
|
|
9317
|
+
function createProjection(pin, radius) {
|
|
9318
|
+
const latitude = pin.lat * Math.PI / 180;
|
|
9319
|
+
const center = [(pin.lng + 180) / 360, (1 - Math.asinh(Math.tan(latitude)) / Math.PI) / 2];
|
|
9320
|
+
const span = 2 * radius / (EARTH_CIRCUMFERENCE * Math.cos(latitude));
|
|
9321
|
+
return {
|
|
9322
|
+
center,
|
|
9323
|
+
span,
|
|
9324
|
+
project: (tile$1, point$1) => {
|
|
9325
|
+
const x = (tile$1.x + point$1[0] / tile$1.extent) / 2 ** tile$1.z;
|
|
9326
|
+
const y = (tile$1.y + point$1[1] / tile$1.extent) / 2 ** tile$1.z;
|
|
9327
|
+
const wrapped = (x - center[0] + 1.5) % 1 - .5;
|
|
9328
|
+
return [400 + wrapped / span * 800, 400 + (y - center[1]) / span * 800];
|
|
9329
|
+
}
|
|
9330
|
+
};
|
|
9331
|
+
}
|
|
9332
|
+
function tileBounds(tile$1, projection) {
|
|
9333
|
+
const a = projection.project(tile$1, [0, 0]), b = projection.project(tile$1, [tile$1.extent, tile$1.extent]);
|
|
9334
|
+
return {
|
|
9335
|
+
left: a[0],
|
|
9336
|
+
top: a[1],
|
|
9337
|
+
right: b[0],
|
|
9338
|
+
bottom: b[1]
|
|
9339
|
+
};
|
|
9340
|
+
}
|
|
9341
|
+
function supportsProjection(pin, projection) {
|
|
9342
|
+
return Math.abs(pin.lat) <= MERCATOR_LIMIT && projection.center[1] - projection.span / 2 >= 0 && projection.center[1] + projection.span / 2 <= 1;
|
|
9343
|
+
}
|
|
9344
|
+
|
|
9345
|
+
//#endregion
|
|
9346
|
+
//#region ../core/src/map-drawing/features.ts
|
|
9347
|
+
const compareKey = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
9348
|
+
const greenKinds = [
|
|
9349
|
+
"park",
|
|
9350
|
+
"forest",
|
|
9351
|
+
"wood",
|
|
9352
|
+
"grass",
|
|
9353
|
+
"meadow",
|
|
9354
|
+
"garden",
|
|
9355
|
+
"nature_reserve",
|
|
9356
|
+
"protected_area",
|
|
9357
|
+
"recreation_ground",
|
|
9358
|
+
"village_green"
|
|
9359
|
+
];
|
|
9360
|
+
function polygonRole(feature$1) {
|
|
9361
|
+
if (feature$1.layer === "earth") return "map-land";
|
|
9362
|
+
if (feature$1.layer === "water") return feature$1.kind === "swimming_pool" ? null : "map-water";
|
|
9363
|
+
if (feature$1.layer === "buildings") return "map-building";
|
|
9364
|
+
return feature$1.layer === "landuse" && greenKinds.includes(feature$1.kind) ? "map-park" : null;
|
|
9365
|
+
}
|
|
9366
|
+
function roadRole(kind) {
|
|
9367
|
+
if (kind === "highway" || kind === "major_road") return "major";
|
|
9368
|
+
if (kind === "minor_road") return "minor";
|
|
9369
|
+
return kind === "path" ? "path" : null;
|
|
9370
|
+
}
|
|
9371
|
+
function intersection(a, b) {
|
|
9372
|
+
const bounds = {
|
|
9373
|
+
left: Math.max(a.left, b.left),
|
|
9374
|
+
top: Math.max(a.top, b.top),
|
|
9375
|
+
right: Math.min(a.right, b.right),
|
|
9376
|
+
bottom: Math.min(a.bottom, b.bottom)
|
|
9377
|
+
};
|
|
9378
|
+
return bounds.left >= bounds.right || bounds.top >= bounds.bottom ? null : bounds;
|
|
9379
|
+
}
|
|
9380
|
+
function canonicalRing(points) {
|
|
9381
|
+
const head = points[0];
|
|
9382
|
+
if (head === undefined) return [];
|
|
9383
|
+
const first = points.reduce((best, point$1, index) => point$1[0] < best.point[0] || point$1[0] === best.point[0] && point$1[1] < best.point[1] ? {
|
|
9384
|
+
point: point$1,
|
|
9385
|
+
index
|
|
9386
|
+
} : best, {
|
|
9387
|
+
point: head,
|
|
9388
|
+
index: 0
|
|
9389
|
+
});
|
|
9390
|
+
const forward = [...points.slice(first.index), ...points.slice(0, first.index)];
|
|
9391
|
+
const backward = [first.point, ...forward.slice(1).reverse()];
|
|
9392
|
+
return compareKey(JSON.stringify(forward), JSON.stringify(backward)) <= 0 ? forward : backward;
|
|
9393
|
+
}
|
|
9394
|
+
function canonicalLine(points) {
|
|
9395
|
+
const reversed = points.slice().reverse();
|
|
9396
|
+
return compareKey(JSON.stringify(points), JSON.stringify(reversed)) <= 0 ? points : reversed;
|
|
9397
|
+
}
|
|
9398
|
+
const CENTRE = [400, 400];
|
|
9399
|
+
function pinDistance(geometry) {
|
|
9400
|
+
if (geometry.kind === "road") return segments(geometry.points).reduce((nearest, { start, end }) => Math.min(nearest, deviation(CENTRE, start, end)), Infinity);
|
|
9401
|
+
return geometry.rings.reduce((best, ring) => ring.reduce((nearest, point$1) => Math.min(nearest, distance(point$1, CENTRE)), best), Infinity);
|
|
9402
|
+
}
|
|
9403
|
+
function candidate(geometry, rank) {
|
|
9404
|
+
const key = JSON.stringify(geometry.kind === "polygon" ? geometry : {
|
|
9405
|
+
kind: "road",
|
|
9406
|
+
role: geometry.role,
|
|
9407
|
+
points: geometry.points
|
|
9408
|
+
});
|
|
9409
|
+
return {
|
|
9410
|
+
geometry,
|
|
9411
|
+
rank,
|
|
9412
|
+
key,
|
|
9413
|
+
distance: pinDistance(geometry)
|
|
9414
|
+
};
|
|
9415
|
+
}
|
|
9416
|
+
/**
|
|
9417
|
+
* Polygons are clipped a fraction past their tile edge so neighbouring tiles' fills overlap; clipped exactly,
|
|
9418
|
+
* anti-aliasing leaves a hairline of background along every tile seam. Fills must be opaque for this to hold.
|
|
9419
|
+
*/
|
|
9420
|
+
const SEAM_OVERLAP = .5;
|
|
9421
|
+
function projectFeature(tile$1, feature$1, projection) {
|
|
9422
|
+
const bounds = intersection(expandBounds(tileBounds(tile$1, projection), feature$1.geometry === "polygon" ? SEAM_OVERLAP : 0), VIEW_BOUNDS);
|
|
9423
|
+
if (bounds === null) return [];
|
|
9424
|
+
const paths$1 = feature$1.paths.map((path) => path.map((point$1) => projection.project(tile$1, point$1)));
|
|
9425
|
+
if (feature$1.geometry === "line") {
|
|
9426
|
+
const role = roadRole(feature$1.kind);
|
|
9427
|
+
return role ? paths$1.flatMap((path) => clipLine(path, bounds)).map((path) => canonicalLine(simplify(canonicalLine(path), false))).filter((path) => path.length >= 2 && segments(path).reduce((sum, { start, end }) => sum + distance(start, end), 0) >= 1).map((points) => candidate({
|
|
9428
|
+
kind: "road",
|
|
9429
|
+
role,
|
|
9430
|
+
points,
|
|
9431
|
+
name: feature$1.name
|
|
9432
|
+
}, feature$1.sortRank)) : [];
|
|
9433
|
+
}
|
|
9434
|
+
const className = polygonRole(feature$1);
|
|
9435
|
+
if (!className) return [];
|
|
9436
|
+
const rings = paths$1.map((path) => simplify(canonicalRing(clipPolygon(path, bounds)), true)).filter((ring) => ring.length >= 3 && polygonArea(ring) >= .3).map(canonicalRing).slice().sort((a, b) => compareKey(JSON.stringify(a), JSON.stringify(b)));
|
|
9437
|
+
return rings.length ? [candidate({
|
|
9438
|
+
kind: "polygon",
|
|
9439
|
+
fillRule: "evenodd",
|
|
9440
|
+
className,
|
|
9441
|
+
rings
|
|
9442
|
+
}, feature$1.sortRank)] : [];
|
|
9443
|
+
}
|
|
9444
|
+
function deduplicate(candidates) {
|
|
9445
|
+
return candidates.slice().sort((a, b) => compareKey(a.key, b.key) || a.rank - b.rank || compareKey(JSON.stringify(a.geometry), JSON.stringify(b.geometry))).filter((item, index, sorted) => item.key !== sorted[index - 1]?.key);
|
|
9446
|
+
}
|
|
9447
|
+
|
|
9448
|
+
//#endregion
|
|
9449
|
+
//#region ../core/src/map-drawing/labels.ts
|
|
9450
|
+
/**
|
|
9451
|
+
* Collision budget for a ~12.5-unit label in an ordinary sans-serif, with room for a heavier site font.
|
|
9452
|
+
* Renderers can set label type smaller than this, never larger.
|
|
9453
|
+
*/
|
|
9454
|
+
const CHARACTER_WIDTH = 7;
|
|
9455
|
+
const HALF_HEIGHT = 8;
|
|
9456
|
+
const PADDING = 5;
|
|
9457
|
+
/** How far a street may bend away from the straight line its label is written along. */
|
|
9458
|
+
const MAX_BEND = 4.5;
|
|
9459
|
+
/** Preferred places along a street for its label, as fractions of its visible length. */
|
|
9460
|
+
const POSITIONS = [
|
|
9461
|
+
.5,
|
|
9462
|
+
.4,
|
|
9463
|
+
.6,
|
|
9464
|
+
.3,
|
|
9465
|
+
.7,
|
|
9466
|
+
.2,
|
|
9467
|
+
.8,
|
|
9468
|
+
.1,
|
|
9469
|
+
.9
|
|
9470
|
+
];
|
|
9471
|
+
/** Street pieces whose ends are this close are one street (OSM ways and tile clipping both split streets). */
|
|
9472
|
+
const JOIN_DISTANCE = 1;
|
|
9473
|
+
/**
|
|
9474
|
+
* Every label lies wholly inside the central 400 × 300 units. Renderers always show at least that much (a phone
|
|
9475
|
+
* at 0.85 px per unit or more, a 1200 × 500 band at 1.5), so a crop never cuts a street name in half.
|
|
9476
|
+
*/
|
|
9477
|
+
const LABEL_WINDOW = {
|
|
9478
|
+
left: 200,
|
|
9479
|
+
right: 600,
|
|
9480
|
+
top: 250,
|
|
9481
|
+
bottom: 550
|
|
9482
|
+
};
|
|
9483
|
+
/** The pin is a teardrop 26 units wide and 38 tall with its tip on the point; labels keep clear of it. */
|
|
9484
|
+
const PIN_KEEP_OUT = {
|
|
9485
|
+
left: 382,
|
|
9486
|
+
right: 418,
|
|
9487
|
+
top: 358,
|
|
9488
|
+
bottom: 406
|
|
9489
|
+
};
|
|
9490
|
+
const LABELLED_ROLES = ["major", "minor"];
|
|
9491
|
+
const textLength = (text) => Array.from(text).length * CHARACTER_WIDTH;
|
|
9492
|
+
/** The label's rotated rectangle, which collision checks treat as solid. */
|
|
9493
|
+
function corners(label$1) {
|
|
9494
|
+
const radians = label$1.angle * Math.PI / 180, cos = Math.cos(radians), sin = Math.sin(radians);
|
|
9495
|
+
const half = textLength(label$1.text) / 2 + PADDING;
|
|
9496
|
+
const at = (x, y) => [label$1.point[0] + x * cos - y * sin, label$1.point[1] + x * sin + y * cos];
|
|
9497
|
+
return [
|
|
9498
|
+
at(-half, -HALF_HEIGHT),
|
|
9499
|
+
at(half, -HALF_HEIGHT),
|
|
9500
|
+
at(half, HALF_HEIGHT),
|
|
9501
|
+
at(-half, HALF_HEIGHT)
|
|
9502
|
+
];
|
|
9503
|
+
}
|
|
9504
|
+
const boxCorners = (box) => [
|
|
9505
|
+
[box.left, box.top],
|
|
9506
|
+
[box.right, box.top],
|
|
9507
|
+
[box.right, box.bottom],
|
|
9508
|
+
[box.left, box.bottom]
|
|
9509
|
+
];
|
|
9510
|
+
/** The label's axis-aligned extent once rotated. */
|
|
9511
|
+
function labelBounds(label$1) {
|
|
9512
|
+
const points = corners(label$1), xs = points.map((point$1) => point$1[0]), ys = points.map((point$1) => point$1[1]);
|
|
9513
|
+
return {
|
|
9514
|
+
left: Math.min(...xs),
|
|
9515
|
+
right: Math.max(...xs),
|
|
9516
|
+
top: Math.min(...ys),
|
|
9517
|
+
bottom: Math.max(...ys)
|
|
9518
|
+
};
|
|
9519
|
+
}
|
|
9520
|
+
function overlaps(a, b) {
|
|
9521
|
+
return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top;
|
|
9522
|
+
}
|
|
9523
|
+
/** Separating-axis test for two rectangles; diagonal labels would block far too much as bounding boxes. */
|
|
9524
|
+
function rectanglesMeet(a, b) {
|
|
9525
|
+
const axes = [a, b].flatMap((box) => [[box[1][0] - box[0][0], box[1][1] - box[0][1]], [box[3][0] - box[0][0], box[3][1] - box[0][1]]]);
|
|
9526
|
+
return axes.every(([x, y]) => {
|
|
9527
|
+
const project = (box) => box.map((point$1) => point$1[0] * x + point$1[1] * y);
|
|
9528
|
+
const first = project(a), second = project(b);
|
|
9529
|
+
return Math.max(...first) >= Math.min(...second) && Math.max(...second) >= Math.min(...first);
|
|
9530
|
+
});
|
|
9531
|
+
}
|
|
9532
|
+
function labelsCollide(a, b) {
|
|
9533
|
+
return rectanglesMeet(corners(a), corners(b));
|
|
9534
|
+
}
|
|
9535
|
+
function labelCoversPin(label$1) {
|
|
9536
|
+
return rectanglesMeet(corners(label$1), boxCorners(PIN_KEEP_OUT));
|
|
9537
|
+
}
|
|
9538
|
+
const inside = (inner, outer) => inner.left >= outer.left && inner.right <= outer.right && inner.top >= outer.top && inner.bottom <= outer.bottom;
|
|
9539
|
+
function pointAlong(points, at) {
|
|
9540
|
+
return segments(points).reduce((state, { start, end }) => {
|
|
9541
|
+
if (state.point !== null) return state;
|
|
9542
|
+
const size = distance(start, end);
|
|
9543
|
+
if (size === 0 || state.remaining > size) return {
|
|
9544
|
+
remaining: state.remaining - size,
|
|
9545
|
+
point: null
|
|
9546
|
+
};
|
|
9547
|
+
const fraction = state.remaining / size;
|
|
9548
|
+
return {
|
|
9549
|
+
remaining: 0,
|
|
9550
|
+
point: [start[0] + (end[0] - start[0]) * fraction, start[1] + (end[1] - start[1]) * fraction]
|
|
9551
|
+
};
|
|
9552
|
+
}, {
|
|
9553
|
+
remaining: at,
|
|
9554
|
+
point: null
|
|
9555
|
+
}).point;
|
|
9556
|
+
}
|
|
9557
|
+
/** The run of street a label of `size` would cover around `centre`, with the vertices inside it. */
|
|
9558
|
+
function stretch(points, centre, size) {
|
|
9559
|
+
const start = pointAlong(points, centre - size / 2), end = pointAlong(points, centre + size / 2), middle = pointAlong(points, centre);
|
|
9560
|
+
if (start === null || end === null || middle === null) return null;
|
|
9561
|
+
const offsets = points.reduce((sums, point$1, index) => [...sums, index === 0 ? 0 : (sums[index - 1] ?? 0) + distance(points[index - 1] ?? point$1, point$1)], []);
|
|
9562
|
+
return {
|
|
9563
|
+
start,
|
|
9564
|
+
end,
|
|
9565
|
+
middle,
|
|
9566
|
+
vertices: points.filter((_, index) => Math.abs((offsets[index] ?? 0) - centre) < size / 2)
|
|
9567
|
+
};
|
|
9568
|
+
}
|
|
9569
|
+
function uprightAngle(start, end) {
|
|
9570
|
+
const degrees = Math.atan2(end[1] - start[1], end[0] - start[0]) * 180 / Math.PI;
|
|
9571
|
+
const upright = degrees > 90 ? degrees - 180 : degrees <= -90 ? degrees + 180 : degrees;
|
|
9572
|
+
return Math.round(upright * 10) / 10 || 0;
|
|
9573
|
+
}
|
|
9574
|
+
/** Joins `line` onto `chain` when one end of each meets, keeping the chain's direction where it can. */
|
|
9575
|
+
function join(chain, line$1) {
|
|
9576
|
+
const meets = (a, b) => a !== undefined && b !== undefined && distance(a, b) <= JOIN_DISTANCE;
|
|
9577
|
+
const reversed = line$1.slice().reverse();
|
|
9578
|
+
if (meets(chain[chain.length - 1], line$1[0])) return [...chain, ...line$1.slice(1)];
|
|
9579
|
+
if (meets(chain[chain.length - 1], reversed[0])) return [...chain, ...reversed.slice(1)];
|
|
9580
|
+
if (meets(line$1[line$1.length - 1], chain[0])) return [...line$1, ...chain.slice(1)];
|
|
9581
|
+
return meets(reversed[reversed.length - 1], chain[0]) ? [...reversed, ...chain.slice(1)] : null;
|
|
9582
|
+
}
|
|
9583
|
+
function stitchPass(streets) {
|
|
9584
|
+
return streets.reduce((chains, street) => {
|
|
9585
|
+
const index = chains.findIndex((chain$1) => chain$1.name === street.name && chain$1.major === street.major && join(chain$1.points, street.points) !== null);
|
|
9586
|
+
const chain = chains[index];
|
|
9587
|
+
const joined = chain === undefined ? null : join(chain.points, street.points);
|
|
9588
|
+
return chain === undefined || joined === null ? [...chains, street] : chains.map((item, at) => at === index ? {
|
|
9589
|
+
...item,
|
|
9590
|
+
distance: Math.min(item.distance, street.distance),
|
|
9591
|
+
points: joined
|
|
9592
|
+
} : item);
|
|
9593
|
+
}, []);
|
|
9594
|
+
}
|
|
9595
|
+
/** Whole named streets from their pieces, so a name is written along the street rather than one short way of it. */
|
|
9596
|
+
function stitch(streets) {
|
|
9597
|
+
const next = stitchPass(streets);
|
|
9598
|
+
return next.length === streets.length ? next : stitch(next);
|
|
9599
|
+
}
|
|
9600
|
+
function namedStreets(roads) {
|
|
9601
|
+
return roads.slice().sort((a, b) => compareKey(a.key, b.key)).flatMap(({ geometry, distance: distance$1 }) => geometry.kind === "road" && geometry.name !== null && LABELLED_ROLES.includes(geometry.role) ? [{
|
|
9602
|
+
name: geometry.name,
|
|
9603
|
+
major: geometry.role === "major",
|
|
9604
|
+
distance: distance$1,
|
|
9605
|
+
points: geometry.points
|
|
9606
|
+
}] : []);
|
|
9607
|
+
}
|
|
9608
|
+
/** Label options for one street, best first: only where the street runs straight enough to write along. */
|
|
9609
|
+
function streetLabels({ name: text, points }) {
|
|
9610
|
+
const size = textLength(text);
|
|
9611
|
+
const length = segments(points).reduce((sum, { start, end }) => sum + distance(start, end), 0);
|
|
9612
|
+
if (length < size) return [];
|
|
9613
|
+
return POSITIONS.flatMap((position) => {
|
|
9614
|
+
const centre = Math.min(Math.max(length * position, size / 2), length - size / 2);
|
|
9615
|
+
const run = stretch(points, centre, size);
|
|
9616
|
+
if (run === null || run.vertices.some((vertex) => deviation(vertex, run.start, run.end) > MAX_BEND)) return [];
|
|
9617
|
+
return [{
|
|
9618
|
+
className: "map-label",
|
|
9619
|
+
haloClassName: "map-label-halo",
|
|
9620
|
+
text,
|
|
9621
|
+
point: roundPoint(run.middle),
|
|
9622
|
+
angle: uprightAngle(run.start, run.end)
|
|
9623
|
+
}];
|
|
9624
|
+
});
|
|
9625
|
+
}
|
|
9626
|
+
function fits(label$1, placed) {
|
|
9627
|
+
return inside(labelBounds(label$1), LABEL_WINDOW) && !labelCoversPin(label$1) && placed.every((other) => other.text !== label$1.text && !labelsCollide(label$1, other));
|
|
9628
|
+
}
|
|
9629
|
+
/** Nearest streets first, as the framing weighs them: the ones around the door are the names a visitor needs. */
|
|
9630
|
+
function placeLabels(roads, framing) {
|
|
9631
|
+
const { maxLabels, minorLabelDistance } = framing;
|
|
9632
|
+
const weighted = (street) => street.major ? street.distance : street.distance * minorLabelDistance;
|
|
9633
|
+
return stitch(namedStreets(roads)).slice().sort((a, b) => weighted(a) - weighted(b) || compareKey(a.name, b.name)).reduce((placed, street) => {
|
|
9634
|
+
if (placed.length >= maxLabels) return placed;
|
|
9635
|
+
const label$1 = streetLabels(street).find((option) => fits(option, placed));
|
|
9636
|
+
return label$1 === undefined ? placed : [...placed, label$1];
|
|
9637
|
+
}, []);
|
|
9638
|
+
}
|
|
9639
|
+
|
|
9640
|
+
//#endregion
|
|
9641
|
+
//#region ../core/src/map-drawing/coverage.ts
|
|
9642
|
+
/** The bounded mixed-zoom coverage decision shared by extraction and completeness validation. */
|
|
9643
|
+
function drawingTileCoverage(pin) {
|
|
9644
|
+
const contexts = FRAMINGS.map((framing) => ({
|
|
9645
|
+
framing,
|
|
9646
|
+
projection: createProjection(pin, framing.radiusMeters)
|
|
9647
|
+
}));
|
|
9648
|
+
if (contexts.some(({ projection }) => !supportsProjection(pin, projection))) return err("unsupported-latitude");
|
|
9649
|
+
const bounds = contexts.map(({ framing, projection }) => {
|
|
9650
|
+
const n = 2 ** framing.zoom;
|
|
9651
|
+
const left = Math.floor((projection.center[0] - projection.span / 2) * n), right = Math.floor((projection.center[0] + projection.span / 2) * n);
|
|
9652
|
+
const top = Math.floor((projection.center[1] - projection.span / 2) * n), bottom = Math.floor((projection.center[1] + projection.span / 2) * n);
|
|
9653
|
+
return {
|
|
9654
|
+
z: framing.zoom,
|
|
9655
|
+
n,
|
|
9656
|
+
left,
|
|
9657
|
+
top,
|
|
9658
|
+
bottom,
|
|
9659
|
+
width: right - left + 1,
|
|
9660
|
+
count: (right - left + 1) * (bottom - top + 1)
|
|
9661
|
+
};
|
|
9662
|
+
});
|
|
9663
|
+
if (bounds.some((value) => !Number.isFinite(value.count) || value.count < 1 || value.top < 0 || value.bottom >= value.n) || bounds.reduce((sum, value) => sum + value.count, 0) > 96) return err("geometry-limit");
|
|
9664
|
+
return ok(bounds.flatMap(({ z: z$1, n, left, top, width, count }) => Array.from({ length: count }, (_, index) => ({
|
|
9665
|
+
z: z$1,
|
|
9666
|
+
x: ((left + index % width) % n + n) % n,
|
|
9667
|
+
y: top + Math.floor(index / width)
|
|
9668
|
+
}))));
|
|
9669
|
+
}
|
|
9670
|
+
|
|
9671
|
+
//#endregion
|
|
9672
|
+
//#region ../core/src/map-drawing/generate.ts
|
|
9673
|
+
const ROLE_ORDER = [
|
|
9674
|
+
"major",
|
|
9675
|
+
"minor",
|
|
9676
|
+
"path"
|
|
9677
|
+
];
|
|
9678
|
+
const MAX_BUILDINGS = 1e3;
|
|
9679
|
+
const MAX_ROADS = 300;
|
|
9680
|
+
/** What this framing draws: its road roles, buildings or not, and no green patches too small to read. */
|
|
9681
|
+
function belongsIn(framing, item) {
|
|
9682
|
+
const geometry = item.geometry;
|
|
9683
|
+
if (geometry.kind === "road") return framing.roads.some((role) => role === geometry.role) && (geometry.role !== "path" || item.distance <= framing.maxPathDistance);
|
|
9684
|
+
if (geometry.className === "map-building") return framing.buildings;
|
|
9685
|
+
return geometry.className !== "map-park" || geometry.rings.some((ring) => polygonArea(ring) >= framing.minParkArea);
|
|
9686
|
+
}
|
|
9687
|
+
function selectFeatures(framing, candidates) {
|
|
9688
|
+
const ordered = candidates.filter((item) => belongsIn(framing, item)).sort((a, b) => a.distance - b.distance || a.rank - b.rank || compareKey(a.key, b.key));
|
|
9689
|
+
const buildings = ordered.filter((item) => item.geometry.kind === "polygon" && item.geometry.className === "map-building").slice(0, MAX_BUILDINGS);
|
|
9690
|
+
const roleOrder = (item) => item.geometry.kind === "road" ? ROLE_ORDER.indexOf(item.geometry.role) : 0;
|
|
9691
|
+
const roads = ordered.filter((item) => item.geometry.kind === "road").sort((a, b) => roleOrder(a) - roleOrder(b) || a.distance - b.distance || compareKey(a.key, b.key)).slice(0, MAX_ROADS);
|
|
9692
|
+
const ground = ordered.filter((item) => item.geometry.kind === "polygon" && item.geometry.className !== "map-building");
|
|
9693
|
+
return [
|
|
9694
|
+
...ground,
|
|
9695
|
+
...buildings,
|
|
9696
|
+
...roads
|
|
9697
|
+
];
|
|
9698
|
+
}
|
|
9699
|
+
/** Water over green, so a wood or park running to the bank never hides the river. */
|
|
9700
|
+
const POLYGON_ORDER = [
|
|
9701
|
+
"map-land",
|
|
9702
|
+
"map-park",
|
|
9703
|
+
"map-water",
|
|
9704
|
+
"map-building"
|
|
9705
|
+
];
|
|
9706
|
+
function paintingOrder(candidate$1) {
|
|
9707
|
+
const geometry = candidate$1.geometry;
|
|
9708
|
+
return geometry.kind === "road" ? POLYGON_ORDER.length + ROLE_ORDER.length - ROLE_ORDER.indexOf(geometry.role) : POLYGON_ORDER.indexOf(geometry.className);
|
|
9709
|
+
}
|
|
9710
|
+
const casingClass = {
|
|
9711
|
+
major: "map-road-major-casing",
|
|
9712
|
+
minor: "map-road-minor-casing"
|
|
9713
|
+
};
|
|
9714
|
+
const strokeClass = {
|
|
9715
|
+
major: "map-road-major",
|
|
9716
|
+
minor: "map-road-minor",
|
|
9717
|
+
path: "map-road-path"
|
|
9718
|
+
};
|
|
9719
|
+
/** Casings under every stroke; paths are a single thin line with no casing. */
|
|
9720
|
+
function roadPaths(roads) {
|
|
9721
|
+
const lines = roads.flatMap((item) => item.geometry.kind === "road" ? [item.geometry] : []);
|
|
9722
|
+
const casings = lines.flatMap(({ role, points }) => role === "path" ? [] : [{
|
|
9723
|
+
kind: "line",
|
|
9724
|
+
className: casingClass[role],
|
|
9725
|
+
points
|
|
9726
|
+
}]);
|
|
9727
|
+
return [...casings, ...lines.map(({ role, points }) => ({
|
|
9728
|
+
kind: "line",
|
|
9729
|
+
className: strokeClass[role],
|
|
9730
|
+
points
|
|
9731
|
+
}))];
|
|
9732
|
+
}
|
|
9733
|
+
function generateFrame(input, framing, projection) {
|
|
9734
|
+
const candidates = input.tiles.filter((tile$1) => tile$1.z === framing.zoom).flatMap((tile$1) => tile$1.features.flatMap((feature$1) => projectFeature(tile$1, feature$1, projection)));
|
|
9735
|
+
const selected = selectFeatures(framing, deduplicate(candidates)).slice().sort((a, b) => paintingOrder(a) - paintingOrder(b) || a.rank - b.rank || compareKey(a.key, b.key));
|
|
9736
|
+
const polygons = selected.flatMap((item) => item.geometry.kind === "polygon" ? [item.geometry] : []);
|
|
9737
|
+
const roads = selected.filter((item) => item.geometry.kind === "road");
|
|
9738
|
+
return {
|
|
9739
|
+
radiusMeters: framing.radiusMeters,
|
|
9740
|
+
viewBox: [
|
|
9741
|
+
0,
|
|
9742
|
+
0,
|
|
9743
|
+
800,
|
|
9744
|
+
800
|
|
9745
|
+
],
|
|
9746
|
+
backgroundClass: "map-water",
|
|
9747
|
+
paths: [...polygons, ...roadPaths(roads)],
|
|
9748
|
+
labels: placeLabels(roads, framing),
|
|
9749
|
+
pin: {
|
|
9750
|
+
className: "map-pin",
|
|
9751
|
+
point: [400, 400]
|
|
9752
|
+
}
|
|
9753
|
+
};
|
|
9754
|
+
}
|
|
9755
|
+
/** Pure: parse decoded input once, decide both framings, return only closed numeric geometry. */
|
|
9756
|
+
function generateMapDrawing(raw) {
|
|
9757
|
+
const parsed = mapDrawingInputSchema.safeParse(raw);
|
|
9758
|
+
if (!parsed.success) return err("invalid-input");
|
|
9759
|
+
const input = parsed.data;
|
|
9760
|
+
const coverage = drawingTileCoverage(input.pin);
|
|
9761
|
+
if (!coverage.ok) return err(coverage.error);
|
|
9762
|
+
const neighbourhood = {
|
|
9763
|
+
framing: FRAMINGS[0],
|
|
9764
|
+
projection: createProjection(input.pin, FRAMINGS[0].radiusMeters)
|
|
9765
|
+
};
|
|
9766
|
+
const wider = {
|
|
9767
|
+
framing: FRAMINGS[1],
|
|
9768
|
+
projection: createProjection(input.pin, FRAMINGS[1].radiusMeters)
|
|
9769
|
+
};
|
|
9770
|
+
const keys = input.tiles.map((tile$1) => `${tile$1.z}/${tile$1.x}/${tile$1.y}`);
|
|
9771
|
+
const present = new Set(keys);
|
|
9772
|
+
if (present.size !== keys.length) return err("invalid-input");
|
|
9773
|
+
if (coverage.value.some((tile$1) => !present.has(`${tile$1.z}/${tile$1.x}/${tile$1.y}`))) return err("missing-tiles");
|
|
9774
|
+
const drawing = mapDrawingSchema.safeParse({
|
|
9775
|
+
schemaVersion: DRAWING_SCHEMA_VERSION,
|
|
9776
|
+
generatorVersion: DRAWING_GENERATOR_VERSION,
|
|
9777
|
+
buildId: input.buildId,
|
|
9778
|
+
tileSchemaVersion: input.tileSchemaVersion,
|
|
9779
|
+
frames: {
|
|
9780
|
+
neighbourhood: generateFrame(input, neighbourhood.framing, neighbourhood.projection),
|
|
9781
|
+
wider: generateFrame(input, wider.framing, wider.projection)
|
|
9782
|
+
}
|
|
9783
|
+
});
|
|
9784
|
+
if (!drawing.success) return err("geometry-limit");
|
|
9785
|
+
return ok(drawing.data);
|
|
9786
|
+
}
|
|
9787
|
+
|
|
8829
9788
|
//#endregion
|
|
8830
9789
|
//#region ../core/src/authored-values.ts
|
|
8831
9790
|
/** Canonical authored equality: empty storage values and absent optional members agree. */
|
|
@@ -8855,4 +9814,4 @@ function isPlainRecord(value) {
|
|
|
8855
9814
|
}
|
|
8856
9815
|
|
|
8857
9816
|
//#endregion
|
|
8858
|
-
export {
|
|
9817
|
+
export { selectCategoryView as $, parseNonNegativeCents as $t, DEFAULT_MAILING_LIST_SUCCESS_MESSAGE as A, unsafeAsEventOfferingId as An, clampConfiguredAmount as At, parseWeeklyOpeningHours as B, unsafeAsProductId as Bn, formatBookingPriceDisplay as Bt, parseManagementKey as C, unsafeAsBookingSessionId as Cn, toUtcIsoDateTimeString as Ct, parseMailingListSignedSourceToken as D, unsafeAsDetectedMimeType as Dn, isSettleableFlexibleBalanceContractStatus as Dt, parseMailingListContentVersionId as E, unsafeAsDemoCapability as En, isLiveOrderInstalmentPlanStatus as Et, DEFAULT_MAILING_LIST_TARGETING as F, unsafeAsOriginalFilename as Fn, splitAmountWithRemainderInFirst as Ft, locationValueSchema as G, unsafeAsStaffMemberId as Gn, publicEventPricingOptions as Gt, decideLocationSave as H, unsafeAsSiteFontId as Hn, isPublicEventPricingOptionDisplayable as Ht, planMailingListChoicePresentation as I, unsafeAsPassId as In, resolveFlexibleBalanceDueAt as It, projectPublicLocation as J, unsafeAsUploadSessionId as Jn, resolvePricingOptionsPriceDisplay as Jt, normalizeLocationAddressText as K, unsafeAsStorageBucketName as Kn, publicEventPricingType as Kt, isCanonicalRichTextDoc as L, unsafeAsPositiveByteCount as Ln, resolveFlexibleBalanceInitialPaymentCents as Lt, parseFooterCompositionV1 as M, unsafeAsFileChecksumSha256 as Mn, getBookingInstalmentPlanIntervalCount as Mt, footerNavigationLayouts as N, unsafeAsMediaAssetId as Nn, getBookingInstalmentProtectedDepositAmountCents as Nt, parseMailingListRuntimeContext as O, unsafeAsEntitlementId as On, isSettleableOrderInstalmentPlanStatus as Ot, parseMailingListPlacementTargeting as P, unsafeAsOccurrenceId as Pn, omitZeroAmountFutureRows as Pt, mapDrawingSchema as Q, isNonNegativeWholeCents as Qt, formatOpeningHoursRows as R, unsafeAsPositiveSeconds as Rn, deriveTotalTicketQuantity as Rt, exposeSecret as S, unsafeAsBookingRequirementId as Sn, toScheduledBookingDateTime as St, parseMailingListBlockId as T, unsafeAsDeclaredMimeType as Tn, validateBookingPaymentOptionKinds as Tt, stableJsonStringify as U, unsafeAsSiteId as Un, publicEventPricingCurrency as Ut, assertAllowedPublicUrl as V, unsafeAsSafeDownloadFilename as Vn, formatEventPricing as Vt, locationMapLinks as W, unsafeAsSiteScopedStoragePath as Wn, publicEventPricingDisplay as Wt, toLocationCoordinates as X, err as Xn, mapNonEmptyArray as Xt, publicLocationSchema as Y, unsafeAsUserId as Yn, fromArray as Yt, isPresent as Z, ok as Zn, mapReadonlyNonEmptyArray as Zt, nextLiveRunSessionStartMs as _, parseRetiredCourseId as _n, parseLocalScheduleTime as _t, areConfiguredBookingVerticalConfigsEqual as a, formatCurrencyAmount as an, formatTimeRange as at, resolveWholeSeriesRunLifecycle as b, parseUserId as bn, resolveBookingTimeZoneId as bt, parseVerticalPresetId as c, formatDurationUnitLabel as cn, buildBookingUtcDateWindow as ct, EVENT_PRESET_VALUES as d, parseDatedOfferingCapacityHoldAuditRef as dn, formatLocalScheduleTime as dt, parsePositiveCents as en, formatDate as et, ORDINARY_EVENT_PRESET_VALUES as f, parseDatedOfferingCapacityHoldId as fn, getBookingLocalDateKey as ft, CANCELLED_SESSION_STATUS as g, parsePageId as gn, parseBookingLocalDate as gt, publicReadFreshnessSeconds as h, parseMembershipId as hn, localScheduleTimePattern as ht, NAVIGATION_LOCATIONS as i, unsafeAsPositiveCents as in, formatTime as it, parseMailingListProviderScope as j, unsafeAsEventSeriesId as jn, getBookingInstalmentDepositRefundability as jt, assessMailingListTargeting as k, unsafeAsEventInstanceId as kn, addIntervalPreservingMonthAnchor as kt, resolveSiteVerticalBookingConfig as l, parseApiKeyId as ln, compareLocalScheduleDates as lt, WHOLE_SERIES_EVENT_PRESET_VALUES as m, parseMediaAssetId as mn, getLocalScheduleTime as mt, resolveEventCatalogue as n, toNonNegativeCents as nn, formatDateTime as nt, canonicalizeConfiguredBookingVerticalConfig as o, parseCurrencyCode as on, inferSiteDateLocale as ot, SURFACE_EVENT_PRESET_VALUES as p, parseEventSeriesId as pn, getLocalScheduleDate as pt, parseLocationValue as q, unsafeAsStoragePath as qn, resolveExactOrFreePriceDisplay as qt, EVENT_PRESET_VOCABULARY as r, unsafeAsNonNegativeCents as rn, formatDateTimeRange as rt, parseVerticalId as s, assertNever as sn, resolveBookingDisplayContext as st, authoredValuesEqual as t, toCents as tn, formatDateRange as tt, DEFAULT_EVENT_TYPE_PRESETS as u, parseCustomerId as un, formatLocalScheduleDate as ut, orderedLiveRunSessions as v, parseSiteId as vn, parseUtcIsoDateTimeString as vt, slugify as w, unsafeAsCheckoutLineId as wn, parseBookingTimeZone as wt, createSecret as x, unsafeAsAppointmentResourceId as xn, toBookingTimeZone as xt, resolveWholeSeriesBookingPhase as y, parseSiteTermsVersionId as yn, resolveBookingBusinessTimeZone as yt, parsePublicContactDetails as z, unsafeAsPricingOptionId as zn, parseEventPageSurfaceSubject as zt };
|