@v-office/website-headless 1.1.0 → 1.2.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/README.md CHANGED
@@ -1521,10 +1521,13 @@ flow.start();
1521
1521
 
1522
1522
  - `readCheckoutHandover(search, options?)` (`CheckoutHandoverOptions`: `defaultAdults`,
1523
1523
  `allowOption`) → `CheckoutHandover`: `stay` (`Stay` or null: the missing-stay card), `policyId` (a
1524
- seed, applied only while still offered), `kind` (`"option"` only with `allowOption`),
1525
- `paymentReturn` (`?return=1`). Reads `pets` and the legacy `petsCount`.
1526
- - `checkoutSearch({ stay, policyId, kind }, current?)` → the query string for `replaceState` after
1527
- an edit; every other parameter of `current` is kept.
1524
+ seed, applied only while still offered), `kind` (`"option"` only with `allowOption`), `services`
1525
+ (`?service=<id>:<count>`, repeatable, `<id>` alone is one: extras to add; a seed like `policyId`,
1526
+ added only as far as the quote offers them), `paymentReturn` (`?return=1`). Reads `pets` and the
1527
+ legacy `petsCount`.
1528
+ - `checkoutSearch({ stay, policyId, kind, counts? }, current?)` → the query string for `replaceState`
1529
+ after an edit; with `counts` the chosen extras replace the URL's `service` keys, without them those
1530
+ stay; every other parameter of `current` is kept.
1528
1531
  - `BookingKind`: `"booking" | "option"` (a non-binding option, SDK `kind: "OPTION"`).
1529
1532
  - `Stay`: `{ start, end, party }`, serializable.
1530
1533
 
@@ -1,4 +1,4 @@
1
- import { An as LngLat, Mn as MapPoint, _a as EnhancePart, a as PlaceMarker, ba as GateMarkers, ga as ENHANCE_PARTS, jn as MapBounds, s as ClusterOptions, va as EnhanceParts, wn as SameSpotLayout, ya as GATE_MARKERS } from "../index-CPMXjfFH.mjs";
1
+ import { An as LngLat, Mn as MapPoint, _a as EnhancePart, a as PlaceMarker, ba as GateMarkers, ga as ENHANCE_PARTS, jn as MapBounds, s as ClusterOptions, va as EnhanceParts, wn as SameSpotLayout, ya as GATE_MARKERS } from "../index-Dlf8Hlzc.mjs";
2
2
  //#region src/enhance/lifecycle.d.ts
3
3
  /** Removes everything a binder added. Safe to call more than once. */
4
4
  type Dispose = () => void;
@@ -1646,6 +1646,8 @@ interface SearchParamKeys {
1646
1646
  readonly policy: "policy";
1647
1647
  /** A booking kind handed to the checkout. */
1648
1648
  readonly kind: "kind";
1649
+ /** An extra handed to the checkout, `id:count`, repeatable (the website chat puts a stay together). */
1650
+ readonly service: "service";
1649
1651
  /** The payment-return marker on the checkout page. */
1650
1652
  readonly return: "return";
1651
1653
  /** An offer label carried from a result to the rental page, repeatable. */
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as describeOutcome, $i as RentalFlag, $n as fetchRentalFlags, $r as CardSource, $t as GuestBound, A as QueryShape, Ai as SdkMode, An as LngLat, Ar as SdkConfigForInput, At as quickFilterDefs, B as toSdkQuery, Bi as RentalDistance, Bn as resultCardSource, Br as RouteParams, Bt as SEARCH_PARAMS, C as SearchState, Ci as isIsoDay, Cn as SameSpotGroup, Cr as EnvRecord, Ct as ResolvedFilters, D as createSearchStore, Di as Locale, Dn as circleOffsets, Dr as HUB_ENDPOINTS, Dt as facetOptions, E as SearchStoreOptions, Ei as Backend, En as SameSpotOptions, Er as sdkConfigFromEnv, Et as activeFilters, F as externalSearchHref, Fi as WebsiteSdk, Fn as mapPoints, Fr as CardSourceExtras, Ft as writeSelection, G as sortFromSearch, Gi as parseCounts, Gn as PreviewStrategy, Gr as CardBadge, Gt as SearchQuery, H as SORT_URL_VALUE, Hi as distanceFact, Hn as shortReviewerName, Hr as routeHref, Ht as SearchParamAliases, I as handoverQuery, Ii as resolveSdk, In as ResultLinkOptions, Ir as cardSource, It as partyPairs, J as EmptyCause, Ji as BedRun, Jn as DEFAULT_FLAG_FILTERS, Jr as CardImage, Jt as searchHref, K as sortResults, Ki as parseDistances, Kn as descriptionPreview, Kr as CardContext, Kt as WriteSearchOptions, L as queryShape, Li as ADJACENT_BELOW_M, Ln as SearchResultItem, Lr as loadRentalFacts, Lt as readParty, M as SanitizedSearch, Mi as SdkSetup, Mn as MapPoint, Mr as VofficeEndpointTable, Mt as resolveFilters, N as SdkQueryOptions, Ni as SdkSource, Nn as PinLabelOptions, Nr as VofficeEndpoints, Nt as withoutFilter, O as ExternalSearchLink, Oi as STAGES, On as distanceMeters, Or as HubEndpointTable, Ot as measureFilters, P as SdkSearchQuery, Pi as Stage, Pn as hasMapPoints, Pr as sdkConfigFor, Pt as withoutFilters, Q as SearchedParts, Qi as RentalDetail, Qn as assignSlugs, Qr as CardRating, Qt as DEFAULT_GUEST_LIMITS, R as resultsQueryFlagScript, Ri as DistanceKind, Rn as catalogueResultItem, Rr as rentalFactsMap, Rt as FlexibleStay, S as SearchScopeKind, Si as formatIsoDate, Sn as PixelOffset, Sr as createSdk, St as ResolveFiltersOptions, T as SearchStore, Ti as todayIso, Tn as SameSpotMode, Tr as SdkEnvError, Tt as WhenEmpty, U as SortKey, Ui as isAdjacent, Un as splitReviewAuthor, Ur as AlternativePeriod, Ut as SearchParamKeys, V as DEFAULT_SORT_OPTIONS, Vi as WATER_KINDS, Vn as newestReviewsFirst, Vr as RouteTemplate, Vt as SearchContract, W as SortOptionState, Wi as isSpecEcho, Wn as DescriptionPreview, Wr as BadgeRule, Wt as SearchPeriod, X as Relaxation, Xi as DescriptionContent, Xn as LoadRentalCatalogueOptions, Xr as CardModelOptions, Xt as ChildAgeRange, Y as OutcomeInput, Yi as CatalogueRental, Yn as FetchRentalFlagsOptions, Yr as CardLiveField, Yt as writeSearch, Z as SearchOutcome, Zi as RentalAddress, Zn as RentalFlagsResult, Zr as CardPill, Zt as DEFAULT_CHILD_AGE, _ as FAVORITE_LEAVE_MS, _a as EnhancePart, _i as Glyph, _n as DEFAULT_FIT, _r as fetchSearchPage, _t as FilterGroupSpec, a as PlaceMarker, aa as ReviewCriterion, ai as RentalFactsMap, an as Party, ar as AmenityPreviewItem, at as OffersWindowSpec, b as SearchActions, ba as GateMarkers, bi as IsoMonth, bn as frameFor, br as SharedSdk, bt as FilterSpec, c as DEFAULT_CLUSTER, ca as PlainTextOptions, ci as toCardModel, cn as clampParty, cr as AdaptEvent, ct as findCampaignStart, d as ResultsMapController, da as StructuredDocumentOptions, di as Range, dn as stayKeyOf, dr as adaptRental, dt as offersWindowQuery, ea as RentalImage, ei as CardStatus, en as GuestBounds, er as loadRentalCatalogue, et as CampaignChip, f as ResultsMapOptions, fa as TextMarkup, fi as matches, fn as Listener, fr as RawSearchPage, ft as ActiveFilter, g as CampaignOf, ga as ENHANCE_PARTS, gi as GLYPHS, gn as createStore, gr as WalkSummary, gt as FilterEntry, h as createResultsMapController, ha as plainText, hi as parsePriceLabel, hn as WritableStore, hr as WalkOptions, ht as FilterDef, i as MapPlace, ia as RentalRoom, ii as RentalFacts, in as GuestOptions, ir as ALWAYS_EXCLUDED_CATEGORIES, it as OfferWindow, j as ResultHandoverOptions, ji as SdkOptions, jn as MapBounds, jr as VOFFICE_ENDPOINTS, jt as readSelection, k as ExternalSearchSource, ki as SdkConfig, kn as layoutSameSpot, kr as HubEndpoints, kt as minimumSteps, l as MapResultsSource, la as RichBlock, li as CardMatch, ln as guestBounds, lr as AdaptRentalOptions, lt as findOfferWindows, m as ResultsMapSurface, ma as parseTextBlocks, mi as formatPriceLabel, mn as Unsubscribe, mr as SearchPageRequest, mt as FacetOptionsOptions, n as RentalAddressModeError, na as RentalReview, ni as PillOptions, nn as GuestKind, nr as slugify, nt as FindCampaignStartOptions, o as mergePlaces, oa as ReviewSummary, oi as StayPrice, on as QuoteOccupancy, or as AmenityPreviewOptions, ot as campaignChips, p as ResultsMapState, pa as normalizeStructuredDocument, pi as PriceCents, pn as Store, pr as SEARCH_PAGE_SIZE, pt as FacetOption, q as toSdkSort, qi as AmenityGroup, qn as descriptionSummary, qr as CardFact, qt as readSearch, r as rentalAddressLine, ra as RentalReviews, ri as RentalCardModel, rn as GuestLimits, rr as HIDDEN_ROOM_ATTRIBUTES, rt as FindOfferWindowsOptions, s as ClusterOptions, sa as RoomKind, si as StayWindow, sn as agesComplete, sr as amenityPreview, st as campaignGroup, t as RentalAddressMode, ta as RentalPropertyInfo, ti as FactKind, tn as GuestCaps, tr as rankHighlights, tt as CampaignChipsInput, u as MapViewReport, ua as RichInline, ui as CardSignal, un as occupancyOf, ur as RentalContentError, ut as offerWindowHref, v as FavoritesPort, va as EnhanceParts, vi as GlyphList, vn as DefaultFit, vr as walkSearchPages, vt as FilterMeasurement, w as SearchStatus, wi as nightsBetween, wn as SameSpotLayout, wr as SDK_ENV, wt as SdkFilterDefinition, x as SearchScope, xi as addDays, xn as idsInView, xr as shareSdk, xt as ImplicitScope, y as HistoryPort, ya as GATE_MARKERS, yi as IsoDay, yn as FitOptions, yr as loadSharedSdk, yt as FilterSelection, z as sanitizeSearch, zi as RentalCounts, zn as compareUndatedSearchOrder, zr as withRentalFacts, zt as QueryPair } from "./index-CPMXjfFH.mjs";
1
+ import { $ as describeOutcome, $i as RentalFlag, $n as fetchRentalFlags, $r as CardSource, $t as GuestBound, A as QueryShape, Ai as SdkMode, An as LngLat, Ar as SdkConfigForInput, At as quickFilterDefs, B as toSdkQuery, Bi as RentalDistance, Bn as resultCardSource, Br as RouteParams, Bt as SEARCH_PARAMS, C as SearchState, Ci as isIsoDay, Cn as SameSpotGroup, Cr as EnvRecord, Ct as ResolvedFilters, D as createSearchStore, Di as Locale, Dn as circleOffsets, Dr as HUB_ENDPOINTS, Dt as facetOptions, E as SearchStoreOptions, Ei as Backend, En as SameSpotOptions, Er as sdkConfigFromEnv, Et as activeFilters, F as externalSearchHref, Fi as WebsiteSdk, Fn as mapPoints, Fr as CardSourceExtras, Ft as writeSelection, G as sortFromSearch, Gi as parseCounts, Gn as PreviewStrategy, Gr as CardBadge, Gt as SearchQuery, H as SORT_URL_VALUE, Hi as distanceFact, Hn as shortReviewerName, Hr as routeHref, Ht as SearchParamAliases, I as handoverQuery, Ii as resolveSdk, In as ResultLinkOptions, Ir as cardSource, It as partyPairs, J as EmptyCause, Ji as BedRun, Jn as DEFAULT_FLAG_FILTERS, Jr as CardImage, Jt as searchHref, K as sortResults, Ki as parseDistances, Kn as descriptionPreview, Kr as CardContext, Kt as WriteSearchOptions, L as queryShape, Li as ADJACENT_BELOW_M, Ln as SearchResultItem, Lr as loadRentalFacts, Lt as readParty, M as SanitizedSearch, Mi as SdkSetup, Mn as MapPoint, Mr as VofficeEndpointTable, Mt as resolveFilters, N as SdkQueryOptions, Ni as SdkSource, Nn as PinLabelOptions, Nr as VofficeEndpoints, Nt as withoutFilter, O as ExternalSearchLink, Oi as STAGES, On as distanceMeters, Or as HubEndpointTable, Ot as measureFilters, P as SdkSearchQuery, Pi as Stage, Pn as hasMapPoints, Pr as sdkConfigFor, Pt as withoutFilters, Q as SearchedParts, Qi as RentalDetail, Qn as assignSlugs, Qr as CardRating, Qt as DEFAULT_GUEST_LIMITS, R as resultsQueryFlagScript, Ri as DistanceKind, Rn as catalogueResultItem, Rr as rentalFactsMap, Rt as FlexibleStay, S as SearchScopeKind, Si as formatIsoDate, Sn as PixelOffset, Sr as createSdk, St as ResolveFiltersOptions, T as SearchStore, Ti as todayIso, Tn as SameSpotMode, Tr as SdkEnvError, Tt as WhenEmpty, U as SortKey, Ui as isAdjacent, Un as splitReviewAuthor, Ur as AlternativePeriod, Ut as SearchParamKeys, V as DEFAULT_SORT_OPTIONS, Vi as WATER_KINDS, Vn as newestReviewsFirst, Vr as RouteTemplate, Vt as SearchContract, W as SortOptionState, Wi as isSpecEcho, Wn as DescriptionPreview, Wr as BadgeRule, Wt as SearchPeriod, X as Relaxation, Xi as DescriptionContent, Xn as LoadRentalCatalogueOptions, Xr as CardModelOptions, Xt as ChildAgeRange, Y as OutcomeInput, Yi as CatalogueRental, Yn as FetchRentalFlagsOptions, Yr as CardLiveField, Yt as writeSearch, Z as SearchOutcome, Zi as RentalAddress, Zn as RentalFlagsResult, Zr as CardPill, Zt as DEFAULT_CHILD_AGE, _ as FAVORITE_LEAVE_MS, _a as EnhancePart, _i as Glyph, _n as DEFAULT_FIT, _r as fetchSearchPage, _t as FilterGroupSpec, a as PlaceMarker, aa as ReviewCriterion, ai as RentalFactsMap, an as Party, ar as AmenityPreviewItem, at as OffersWindowSpec, b as SearchActions, ba as GateMarkers, bi as IsoMonth, bn as frameFor, br as SharedSdk, bt as FilterSpec, c as DEFAULT_CLUSTER, ca as PlainTextOptions, ci as toCardModel, cn as clampParty, cr as AdaptEvent, ct as findCampaignStart, d as ResultsMapController, da as StructuredDocumentOptions, di as Range, dn as stayKeyOf, dr as adaptRental, dt as offersWindowQuery, ea as RentalImage, ei as CardStatus, en as GuestBounds, er as loadRentalCatalogue, et as CampaignChip, f as ResultsMapOptions, fa as TextMarkup, fi as matches, fn as Listener, fr as RawSearchPage, ft as ActiveFilter, g as CampaignOf, ga as ENHANCE_PARTS, gi as GLYPHS, gn as createStore, gr as WalkSummary, gt as FilterEntry, h as createResultsMapController, ha as plainText, hi as parsePriceLabel, hn as WritableStore, hr as WalkOptions, ht as FilterDef, i as MapPlace, ia as RentalRoom, ii as RentalFacts, in as GuestOptions, ir as ALWAYS_EXCLUDED_CATEGORIES, it as OfferWindow, j as ResultHandoverOptions, ji as SdkOptions, jn as MapBounds, jr as VOFFICE_ENDPOINTS, jt as readSelection, k as ExternalSearchSource, ki as SdkConfig, kn as layoutSameSpot, kr as HubEndpoints, kt as minimumSteps, l as MapResultsSource, la as RichBlock, li as CardMatch, ln as guestBounds, lr as AdaptRentalOptions, lt as findOfferWindows, m as ResultsMapSurface, ma as parseTextBlocks, mi as formatPriceLabel, mn as Unsubscribe, mr as SearchPageRequest, mt as FacetOptionsOptions, n as RentalAddressModeError, na as RentalReview, ni as PillOptions, nn as GuestKind, nr as slugify, nt as FindCampaignStartOptions, o as mergePlaces, oa as ReviewSummary, oi as StayPrice, on as QuoteOccupancy, or as AmenityPreviewOptions, ot as campaignChips, p as ResultsMapState, pa as normalizeStructuredDocument, pi as PriceCents, pn as Store, pr as SEARCH_PAGE_SIZE, pt as FacetOption, q as toSdkSort, qi as AmenityGroup, qn as descriptionSummary, qr as CardFact, qt as readSearch, r as rentalAddressLine, ra as RentalReviews, ri as RentalCardModel, rn as GuestLimits, rr as HIDDEN_ROOM_ATTRIBUTES, rt as FindOfferWindowsOptions, s as ClusterOptions, sa as RoomKind, si as StayWindow, sn as agesComplete, sr as amenityPreview, st as campaignGroup, t as RentalAddressMode, ta as RentalPropertyInfo, ti as FactKind, tn as GuestCaps, tr as rankHighlights, tt as CampaignChipsInput, u as MapViewReport, ua as RichInline, ui as CardSignal, un as occupancyOf, ur as RentalContentError, ut as offerWindowHref, v as FavoritesPort, va as EnhanceParts, vi as GlyphList, vn as DefaultFit, vr as walkSearchPages, vt as FilterMeasurement, w as SearchStatus, wi as nightsBetween, wn as SameSpotLayout, wr as SDK_ENV, wt as SdkFilterDefinition, x as SearchScope, xi as addDays, xn as idsInView, xr as shareSdk, xt as ImplicitScope, y as HistoryPort, ya as GATE_MARKERS, yi as IsoDay, yn as FitOptions, yr as loadSharedSdk, yt as FilterSelection, z as sanitizeSearch, zi as RentalCounts, zn as compareUndatedSearchOrder, zr as withRentalFacts, zt as QueryPair } from "./index-Dlf8Hlzc.mjs";
2
2
  import { BookingPaymentOption, BookingPaymentScheduleKind, ContactSubmitInput, GuestQuote, GuestQuoteResult, SubmittableBookingPaymentOption } from "@v-office/website-sdk";
3
3
  import { BackendKind } from "@v-office/website-sdk/capabilities/search";
4
4
  //#region src/map/constants.d.ts
@@ -1666,6 +1666,10 @@ interface CheckoutHandover {
1666
1666
  policyId: string | null;
1667
1667
  /** `?kind=option`, honoured only with `allowOption`; otherwise "booking". */
1668
1668
  kind: BookingKind;
1669
+ /** `?service=<id>:<count>` (repeatable; `<id>` alone is one): the extras to add, by service id. A
1670
+ * seed like `policyId`: the flow adds only what the quote offers, up to the count the PMS allows,
1671
+ * and keeps what it refuses out. Empty without any. */
1672
+ services?: Readonly<Record<string, number>> | undefined;
1669
1673
  /** `?return=1`: a payment provider sent the guest back to this page (sites that return to the
1670
1674
  * checkout itself; the package's own return page is a separate route). */
1671
1675
  paymentReturn: boolean;
@@ -1682,11 +1686,14 @@ interface CheckoutHandoverOptions {
1682
1686
  type ReadCheckoutHandover = (search: string, options?: CheckoutHandoverOptions) => CheckoutHandover;
1683
1687
  /** The query string (no leading "?") for `history.replaceState` after an edit: the stay (pets as
1684
1688
  * `pets`), an explicit policy only (never today's pre-selection, which would pin it), `kind=option`
1685
- * for an option. Every other parameter in `current` (tracking, site keys) is preserved after them. */
1689
+ * for an option, and with `counts` the chosen extras (`service=<id>:<count>`; without `counts` the
1690
+ * URL's `service` keys stay as they are). Every other parameter in `current` (tracking, site keys)
1691
+ * is preserved after them. */
1686
1692
  type CheckoutSearch = (input: {
1687
1693
  stay: Stay | null;
1688
1694
  policyId: string | null;
1689
1695
  kind: BookingKind;
1696
+ counts?: Readonly<Record<string, number>> | undefined;
1690
1697
  }, current?: string) => string;
1691
1698
  /** See {@link ReadCheckoutHandover}. */
1692
1699
  export declare const readCheckoutHandover: ReadCheckoutHandover;
package/dist/index.mjs CHANGED
@@ -1469,6 +1469,7 @@ const SEARCH_PARAMS = {
1469
1469
  campaign: "campaign",
1470
1470
  policy: "policy",
1471
1471
  kind: "kind",
1472
+ service: "service",
1472
1473
  return: "return",
1473
1474
  offer: "offer"
1474
1475
  };
@@ -7136,6 +7137,83 @@ const payWithOption = (sdk, option) => sdk.live.booking.submitPaymentOption(opti
7136
7137
  /** The window CustomEvent name. Renaming it is a major. */
7137
7138
  const CHECKOUT_EVENT = "voffice:checkout";
7138
7139
  //#endregion
7140
+ //#region src/checkout/handover.ts
7141
+ const DEFAULT_ADULTS$1 = 2;
7142
+ /** The most distinct extras a URL hands over: more than any quote offers, a bound for junk links. */
7143
+ const MAX_SERVICES = 50;
7144
+ const MAX_SERVICE_ID = 100;
7145
+ /** `id:count` or `id` (one) per `service` key; unreadable entries are skipped, a repeated id adds up
7146
+ * to the cap. */
7147
+ function readServices(params) {
7148
+ const services = /* @__PURE__ */ new Map();
7149
+ for (const raw of params.getAll(SEARCH_PARAMS.service)) {
7150
+ const at = raw.lastIndexOf(":");
7151
+ const id = (at === -1 ? raw : raw.slice(0, at)).trim();
7152
+ const count = at === -1 ? 1 : Number(raw.slice(at + 1));
7153
+ if (!id || id.length > MAX_SERVICE_ID || !Number.isInteger(count) || count < 1) continue;
7154
+ if (!services.has(id) && services.size >= MAX_SERVICES) continue;
7155
+ services.set(id, Math.min(99, (services.get(id) ?? 0) + count));
7156
+ }
7157
+ return Object.fromEntries(services);
7158
+ }
7159
+ /** The keys the write-back owns; everything else in the URL is carried through untouched. */
7160
+ const OWNED_KEYS = /* @__PURE__ */ new Set([
7161
+ SEARCH_PARAMS.start,
7162
+ SEARCH_PARAMS.end,
7163
+ SEARCH_PARAMS.adults,
7164
+ SEARCH_PARAMS.children,
7165
+ SEARCH_PARAMS.childrenAges,
7166
+ SEARCH_PARAMS.babies,
7167
+ SEARCH_PARAMS.pets,
7168
+ SEARCH_PARAMS.petsCount,
7169
+ SEARCH_PARAMS.policy,
7170
+ SEARCH_PARAMS.kind
7171
+ ]);
7172
+ function readHandover(search, options) {
7173
+ const params = new URLSearchParams(search);
7174
+ const start = params.get(SEARCH_PARAMS.start);
7175
+ const end = params.get(SEARCH_PARAMS.end);
7176
+ const dated = isIsoDay(start) && isIsoDay(end) && nightsBetween(start, end) > 0;
7177
+ const party = readParty(params);
7178
+ const adults = party && party.adults > 0 ? party.adults : options.defaultAdults ?? DEFAULT_ADULTS$1;
7179
+ const withAdults = {
7180
+ ...party ?? {
7181
+ children: 0,
7182
+ childrenAges: [],
7183
+ babies: 0,
7184
+ pets: 0
7185
+ },
7186
+ adults
7187
+ };
7188
+ return {
7189
+ stay: dated ? {
7190
+ start,
7191
+ end,
7192
+ party: withAdults
7193
+ } : null,
7194
+ policyId: params.get(SEARCH_PARAMS.policy) || null,
7195
+ kind: options.allowOption === true && params.get(SEARCH_PARAMS.kind) === "option" ? "option" : "booking",
7196
+ services: readServices(params),
7197
+ paymentReturn: params.get(SEARCH_PARAMS.return) === "1"
7198
+ };
7199
+ }
7200
+ function writeHandover({ stay, policyId, kind, counts }, current) {
7201
+ const pairs = [];
7202
+ if (stay) pairs.push([SEARCH_PARAMS.start, stay.start], [SEARCH_PARAMS.end, stay.end], ...partyPairs(stay.party, SEARCH_PARAMS.pets));
7203
+ if (policyId) pairs.push([SEARCH_PARAMS.policy, policyId]);
7204
+ if (kind === "option") pairs.push([SEARCH_PARAMS.kind, "option"]);
7205
+ if (counts) {
7206
+ for (const [id, count] of Object.entries(counts)) if (count > 0) pairs.push([SEARCH_PARAMS.service, `${id}:${count}`]);
7207
+ }
7208
+ const owned = (key) => OWNED_KEYS.has(key) || counts !== void 0 && key === SEARCH_PARAMS.service;
7209
+ const carried = [...new URLSearchParams(current)].filter(([key]) => !owned(key));
7210
+ return new URLSearchParams([...pairs, ...carried].map(([key, value]) => [key, value])).toString();
7211
+ }
7212
+ /** See {@link ReadCheckoutHandover}. */
7213
+ const readCheckoutHandover = (search, options) => readHandover(search, options ?? {});
7214
+ /** See {@link CheckoutSearch}. */
7215
+ const checkoutSearch = (input, current) => writeHandover(input, current ?? "");
7216
+ //#endregion
7139
7217
  //#region src/checkout/insurance.ts
7140
7218
  /** Nothing opened, nothing confirmed. */
7141
7219
  const NO_INSURANCE_DOCS = {
@@ -7202,11 +7280,11 @@ async function run(sdk, input) {
7202
7280
  };
7203
7281
  const cleared = await clearQuoteServices(sdk, result.live);
7204
7282
  if (!cleared.ok) throw new Error("clearAdditionalServices failed: the extras baseline is unknown");
7205
- const stepped = await stepServices(sdk, cleared.quote, result.view, input.counts);
7206
- const policy = await applyPolicy(sdk, stepped.quote, input);
7207
- const reapply = insuranceIdToReapply(adaptGuestQuote(policy.quote), input.insurance);
7208
- const insured = reapply === void 0 ? null : await selectQuoteInsurance(sdk, policy.quote, reapply);
7209
- const quote = insured?.ok ? insured.quote : policy.quote;
7283
+ const policy = await applyPolicy(sdk, cleared.quote, input);
7284
+ const stepped = await stepServices(sdk, policy.quote, adaptGuestQuote(policy.quote), input.counts);
7285
+ const reapply = insuranceIdToReapply(adaptGuestQuote(stepped.quote), input.insurance);
7286
+ const insured = reapply === void 0 ? null : await selectQuoteInsurance(sdk, stepped.quote, reapply);
7287
+ const quote = insured?.ok ? insured.quote : stepped.quote;
7210
7288
  return {
7211
7289
  kind: "priced",
7212
7290
  quote,
@@ -7266,8 +7344,7 @@ const requote = (sdk, input) => run(sdk, input);
7266
7344
  //#region src/checkout/flow.ts
7267
7345
  /** Later edits coalesce (a burst of stepper clicks prices once); the first quote runs at once. */
7268
7346
  const REQUOTE_DELAY_MS = 350;
7269
- const DEFAULT_ADULTS$1 = 2;
7270
- const DEFAULT_MAX_PER_BOOKING = 99;
7347
+ const DEFAULT_ADULTS = 2;
7271
7348
  const chosenInsurance = (view) => selectedInsuranceOption(view?.insurance);
7272
7349
  /** The handed-over stay under the same party rules as the quote: clamped to the bounds and every age
7273
7350
  * outside the band unchosen (the child-ages blocker then asks for it). */
@@ -7281,7 +7358,7 @@ function handedOverStay(options, how) {
7281
7358
  };
7282
7359
  }
7283
7360
  function initialRaw(options, how) {
7284
- const { policyId } = options.handover;
7361
+ const { policyId, services } = options.handover;
7285
7362
  const stay = handedOverStay(options, how);
7286
7363
  return {
7287
7364
  phase: stay ? { kind: "loading" } : { kind: "missing-stay" },
@@ -7289,7 +7366,7 @@ function initialRaw(options, how) {
7289
7366
  live: null,
7290
7367
  view: null,
7291
7368
  quoted: null,
7292
- counts: {},
7369
+ counts: services ?? {},
7293
7370
  issue: null,
7294
7371
  chosenPolicyId: policyId,
7295
7372
  voucher: {
@@ -7311,7 +7388,7 @@ function initialRaw(options, how) {
7311
7388
  function derivationOf(options) {
7312
7389
  const { guests, caps } = options;
7313
7390
  const emptyParty = {
7314
- adults: guests?.defaultAdults ?? DEFAULT_ADULTS$1,
7391
+ adults: guests?.defaultAdults ?? DEFAULT_ADULTS,
7315
7392
  children: 0,
7316
7393
  childrenAges: [],
7317
7394
  babies: 0,
@@ -7341,7 +7418,7 @@ function derivationOf(options) {
7341
7418
  function servicesOf(raw, busy) {
7342
7419
  return (raw.view?.services ?? []).map((service) => {
7343
7420
  const count = raw.counts[service.id] ?? 0;
7344
- const max = service.maxPerBooking ?? DEFAULT_MAX_PER_BOOKING;
7421
+ const max = service.maxPerBooking ?? 99;
7345
7422
  return {
7346
7423
  ...service,
7347
7424
  count,
@@ -8088,61 +8165,6 @@ function guestForm(options) {
8088
8165
  /** See {@link CreateGuestForm}. */
8089
8166
  const createGuestForm = (options) => guestForm(options);
8090
8167
  //#endregion
8091
- //#region src/checkout/handover.ts
8092
- const DEFAULT_ADULTS = 2;
8093
- /** The keys the write-back owns; everything else in the URL is carried through untouched. */
8094
- const OWNED_KEYS = /* @__PURE__ */ new Set([
8095
- SEARCH_PARAMS.start,
8096
- SEARCH_PARAMS.end,
8097
- SEARCH_PARAMS.adults,
8098
- SEARCH_PARAMS.children,
8099
- SEARCH_PARAMS.childrenAges,
8100
- SEARCH_PARAMS.babies,
8101
- SEARCH_PARAMS.pets,
8102
- SEARCH_PARAMS.petsCount,
8103
- SEARCH_PARAMS.policy,
8104
- SEARCH_PARAMS.kind
8105
- ]);
8106
- function readHandover(search, options) {
8107
- const params = new URLSearchParams(search);
8108
- const start = params.get(SEARCH_PARAMS.start);
8109
- const end = params.get(SEARCH_PARAMS.end);
8110
- const dated = isIsoDay(start) && isIsoDay(end) && nightsBetween(start, end) > 0;
8111
- const party = readParty(params);
8112
- const adults = party && party.adults > 0 ? party.adults : options.defaultAdults ?? DEFAULT_ADULTS;
8113
- const withAdults = {
8114
- ...party ?? {
8115
- children: 0,
8116
- childrenAges: [],
8117
- babies: 0,
8118
- pets: 0
8119
- },
8120
- adults
8121
- };
8122
- return {
8123
- stay: dated ? {
8124
- start,
8125
- end,
8126
- party: withAdults
8127
- } : null,
8128
- policyId: params.get(SEARCH_PARAMS.policy) || null,
8129
- kind: options.allowOption === true && params.get(SEARCH_PARAMS.kind) === "option" ? "option" : "booking",
8130
- paymentReturn: params.get(SEARCH_PARAMS.return) === "1"
8131
- };
8132
- }
8133
- function writeHandover({ stay, policyId, kind }, current) {
8134
- const pairs = [];
8135
- if (stay) pairs.push([SEARCH_PARAMS.start, stay.start], [SEARCH_PARAMS.end, stay.end], ...partyPairs(stay.party, SEARCH_PARAMS.pets));
8136
- if (policyId) pairs.push([SEARCH_PARAMS.policy, policyId]);
8137
- if (kind === "option") pairs.push([SEARCH_PARAMS.kind, "option"]);
8138
- const carried = [...new URLSearchParams(current)].filter(([key]) => !OWNED_KEYS.has(key));
8139
- return new URLSearchParams([...pairs, ...carried].map(([key, value]) => [key, value])).toString();
8140
- }
8141
- /** See {@link ReadCheckoutHandover}. */
8142
- const readCheckoutHandover = (search, options) => readHandover(search, options ?? {});
8143
- /** See {@link CheckoutSearch}. */
8144
- const checkoutSearch = (input, current) => writeHandover(input, current ?? "");
8145
- //#endregion
8146
8168
  //#region src/checkout/structure.ts
8147
8169
  /** The legal `data-part` vocabulary. Imported components carry it; a custom checkout must keep it.
8148
8170
  * Style-only hooks are not here (components `BOOKING_PARTS`). Renaming a part is a major. */
@@ -4,6 +4,8 @@
4
4
 
5
5
  Release notes of `@v-office/website-headless`, newest first. Each version has its own directory.
6
6
 
7
+ - `versions/1.2.0/CHANGELOG.md`: release notes for 1.2.0
8
+ - `versions/1.1.1/CHANGELOG.md`: release notes for 1.1.1
7
9
  - `versions/1.1.0/CHANGELOG.md`: release notes for 1.1.0
8
10
  - `versions/1.0.0/CHANGELOG.md`: release notes for 1.0.0
9
11
  - `versions/0.2.0/CHANGELOG.md`: release notes for 0.2.0
@@ -4,6 +4,8 @@
4
4
 
5
5
  Migration guides of `@v-office/website-headless`, newest first. Apply them in order when skipping versions.
6
6
 
7
+ - `versions/1.2.0/MIGRATION.md`: Migration: 1.1.1 to 1.2.0
8
+ - `versions/1.1.1/MIGRATION.md`: Migration: 1.1.0 to 1.1.1
7
9
  - `versions/1.1.0/MIGRATION.md`: Migration: 1.0.0 to 1.1.0
8
10
  - `versions/1.0.0/MIGRATION.md`: Migration: 0.2.0 to 1.0.0
9
11
  - `versions/0.2.0/MIGRATION.md`: Migration: 0.1.1 to 0.2.0
@@ -0,0 +1,5 @@
1
+ # Changelog: 1.1.1
2
+
3
+ The package's `package.json` now names its source repository (`repository`), which npm
4
+ requires to publish through trusted publishing, the way every release of this package is
5
+ published from now on. The code is the same as in 1.1.0.
@@ -0,0 +1,9 @@
1
+ # Migration: 1.1.0 to 1.1.1
2
+
3
+ ## 1. Upgrade
4
+
5
+ ```sh
6
+ pnpm add @v-office/website-headless@1.1.1
7
+ ```
8
+
9
+ No changes required.
@@ -0,0 +1,3 @@
1
+ {
2
+ "componentsAffectedByHeadlessChange": []
3
+ }
@@ -0,0 +1,24 @@
1
+ # Changelog: 1.2.0
2
+
3
+ The booking page takes the extras a link hands over. AI Studio's website chat puts a stay together
4
+ with extras (bed linen, a cot, a second dog, …) and links to the booking page with them chosen.
5
+
6
+ ## Added
7
+
8
+ - `readCheckoutHandover` reads `?service=<id>:<count>` (repeatable; `<id>` alone is one) into
9
+ `services`. The checkout flow adds them with its first quote, like the handed-over rate: only extras
10
+ the quote offers, only as many as the PMS allows. What it refuses stays out; the price on the
11
+ booking page is always the PMS's for what is selected.
12
+ - `checkoutSearch` takes `counts`, the chosen extras, and writes them as `service` keys; without
13
+ `counts` the URL's `service` keys stay as they are.
14
+
15
+ ## Changed
16
+
17
+ - Nothing for existing links: a URL without `service` keys works as before.
18
+
19
+ ## Fixed
20
+
21
+ - The checkout keeps the rate the guest chose when the extras already chosen exceed that rate's
22
+ limits. Each rate has its own extras and limits, and the checkout used to add the extras first,
23
+ so the PMS refused the rate and the price fell back to the standard rate. It now applies the rate
24
+ first and adds the extras within that rate's limits; what it refuses stays out.
@@ -0,0 +1,11 @@
1
+ # Migration: 1.1.1 to 1.2.0
2
+
3
+ No changes required. A site with the platform's `Booking` gets the extras through
4
+ `@v-office/website-components` 1.1.0, which writes the chosen extras back into the URL. A site
5
+ with its own checkout passes `counts` (the chosen extras by id) to `checkoutSearch` to do the same.
6
+
7
+ ## 1. Upgrade
8
+
9
+ ```sh
10
+ pnpm add --save-exact @v-office/website-headless@1.2.0
11
+ ```
@@ -0,0 +1,3 @@
1
+ {
2
+ "componentsAffectedByHeadlessChange": ["booking"]
3
+ }
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@v-office/website-headless",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Framework-free behaviour layer for v-office websites, never UI: stores, pure rules and SDK runners for the search bar, results, map, showcase, quote, checkout and contact form, the rental and card models, and progressive-enhancement engines for server-rendered markup (./enhance)",
5
5
  "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/vofficeGmbh/ai-studio.git",
9
+ "directory": "packages/website-headless"
10
+ },
6
11
  "files": [
7
12
  "dist",
8
13
  "README.md",
@@ -21,7 +26,7 @@
21
26
  },
22
27
  "devDependencies": {
23
28
  "@types/node": "26.5.1",
24
- "@v-office/website-sdk": "2.30.0",
29
+ "@v-office/website-sdk": "2.34.0",
25
30
  "effect": "4.0.0-rc.115",
26
31
  "jsdom": "^30.0.1",
27
32
  "oxfmt": "0.67.0",