@forgebuild/hotal-ui 0.23.48 → 0.23.88

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.
Files changed (3) hide show
  1. package/dist/hotal-ui.js +12079 -10232
  2. package/dist/index.d.ts +331 -0
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -111,9 +111,16 @@ export interface BookingFlowElement extends HTMLElement {
111
111
  }
112
112
 
113
113
  export interface MenuItem {
114
+ /** Needed to add this item to a cart (see MenuSectionElement.cartEnabled)
115
+ * -- an item missing this never gets a quantity stepper. */
116
+ id?: string;
114
117
  name: string;
115
118
  description?: string;
116
119
  price?: string;
120
+ /** Numeric price for cart math, independent of `price` above (which may
121
+ * be an arbitrary display override a cart can't add up) -- like `id`,
122
+ * required for this item to get a stepper. */
123
+ priceValue?: number;
117
124
  tagline?: string;
118
125
  group?: string;
119
126
  country?: string;
@@ -125,6 +132,18 @@ export interface MenuCategory {
125
132
  items: MenuItem[];
126
133
  }
127
134
 
135
+ export interface MenuCartItem {
136
+ id: string;
137
+ name: string;
138
+ price: number;
139
+ quantity: number;
140
+ }
141
+
142
+ export interface MenuSectionCartChangeEventDetail {
143
+ items: MenuCartItem[];
144
+ total: number;
145
+ }
146
+
128
147
  export interface MenuInfoItem {
129
148
  label: string;
130
149
  value: string;
@@ -156,6 +175,16 @@ export interface MenuSectionElement extends HTMLElement {
156
175
  countryFilter?: boolean;
157
176
  groupFilterAllLabel?: string;
158
177
  countryFilterAllLabel?: string;
178
+ /** Adds a +/- stepper next to any item with both `id` and `priceValue`
179
+ * (classic variant only, not `panel`) and emits `cart-change` on every
180
+ * change. Off by default -- most hosts use this purely as a read-only
181
+ * display. */
182
+ cartEnabled?: boolean;
183
+ /** Restore cart quantities, e.g. from localStorage: [{ id, quantity }].
184
+ * Reactive, not mount-only -- reassign it (a new array) to re-sync
185
+ * quantities a host changed elsewhere, same as
186
+ * ProshopCatalogueElement's own `initial`. */
187
+ initial?: { id: string; quantity: number }[];
159
188
  }
160
189
 
161
190
  export interface EventInquiry {
@@ -220,10 +249,36 @@ export interface EventInquiryPayload {
220
249
  eventType: string;
221
250
  format: string;
222
251
  message: string;
252
+ /** The order found via showOrderLookup, or null when the lookup is
253
+ * hidden / nothing was looked up. */
254
+ orderReference: string | null;
255
+ /** Ticked lines from that order (empty when none) -- the host decides
256
+ * what an enquiry with items on it means, e.g. a return request. */
257
+ returnItems: { orderItemId: string; quantity: number }[];
223
258
  }
224
259
 
225
260
  export type SubmitInquiryResult = { ok: true } | { ok: false; error?: string };
226
261
 
262
+ export interface ReturnableOrderItem {
263
+ /** The order line's own id -- echoed back as returnItems[].orderItemId. */
264
+ id: string;
265
+ name: string;
266
+ /** e.g. "Grey — L"; omitted for a plain item. */
267
+ variant?: string;
268
+ /** Quantity that can come back -- the cap on the stepper. */
269
+ quantity: number;
270
+ /** Unit price; null hides the price. */
271
+ price: number | null;
272
+ image?: string;
273
+ }
274
+
275
+ export interface ReturnableOrder {
276
+ reference: string;
277
+ items: ReturnableOrderItem[];
278
+ }
279
+
280
+ export type LookupOrderResult = { ok: true; order: ReturnableOrder } | { ok: false; error?: string };
281
+
227
282
  export interface EventContactFormStrings {
228
283
  nameLabel: string;
229
284
  namePlaceholder: string;
@@ -252,6 +307,18 @@ export interface EventContactFormStrings {
252
307
  notWiredError: string;
253
308
  genericSubmitError: string;
254
309
  successMessage: string;
310
+ orderLabel: string;
311
+ orderPlaceholder: string;
312
+ findOrderLabel: string;
313
+ findingOrderLabel: string;
314
+ clearOrderLabel: string;
315
+ orderEmailFirstError: string;
316
+ orderLookupError: string;
317
+ returnItemsLabel: string;
318
+ /** `{count}` is replaced with the quantity bought. */
319
+ boughtLabel: string;
320
+ decreaseReturnAriaLabel: string;
321
+ increaseReturnAriaLabel: string;
255
322
  }
256
323
 
257
324
  export interface EventContactFormElement extends HTMLElement {
@@ -299,8 +366,18 @@ export interface EventContactFormElement extends HTMLElement {
299
366
  /** BCP 47 locale (e.g. "fr-FR") used to format month/weekday names in the
300
367
  * calendar; defaults to the browser's own locale. */
301
368
  locale?: string;
369
+ /** Adds an "Order number" input with a Find button (shops: return or
370
+ * exchange enquiries). Finding an order via `lookupOrder` lists its lines
371
+ * as a tick list under the input; ticked lines go out on the payload as
372
+ * returnItems. Default false. */
373
+ showOrderLookup?: boolean;
374
+ /** ISO 4217 for the looked-up lines' unit prices. Default 'USD'. */
375
+ currency?: string;
302
376
  strings?: Partial<EventContactFormStrings>;
303
377
  submitInquiry?: (payload: EventInquiryPayload) => Promise<SubmitInquiryResult>;
378
+ /** Resolves an order number + the email typed in the form to its returnable
379
+ * lines. Only used when showOrderLookup is true. */
380
+ lookupOrder?: (reference: string, email: string) => Promise<LookupOrderResult>;
304
381
  }
305
382
 
306
383
  export interface SpaTreatment {
@@ -366,6 +443,10 @@ export interface SpaBookingWidgetElement extends HTMLElement {
366
443
  /** Shown as a "Staff" row in the summary panel once a service is picked.
367
444
  * Purely presentational -- there is no staff picker. Omit to hide the row. */
368
445
  staffLabel?: string;
446
+ /** Hides every price display (service list, summary rows, total) -- for
447
+ * a free service (e.g. a consultation call) where showing "£0.00"
448
+ * throughout would read oddly. */
449
+ hidePrice?: boolean;
369
450
  /** JS `Date#getDay()` values (0 = Sunday) to gray out on the calendar,
370
451
  * e.g. `[2]` for a Tuesday closure. Visual hint only -- fetchAvailability
371
452
  * is the real source of truth for what's bookable. */
@@ -698,6 +779,64 @@ export interface LiveDiningOrdersFeedElement extends HTMLElement {
698
779
  filterBookingId?: string;
699
780
  }
700
781
 
782
+ export interface ShopOrderItem {
783
+ id?: string;
784
+ item_id?: string;
785
+ item_name: string;
786
+ quantity: number;
787
+ unit_price?: string | number;
788
+ total?: string | number;
789
+ /** e.g. size/color ("M / Navy") for apparel-style items. */
790
+ variant?: string | null;
791
+ }
792
+
793
+ export interface ShopOrder {
794
+ id: string;
795
+ booking_id?: string | null;
796
+ guest_id?: string | null;
797
+ status: string;
798
+ notes?: string | null;
799
+ fulfillment_method?: 'pickup' | 'shipping' | null;
800
+ total_price: string | number;
801
+ created_at: string;
802
+ items: ShopOrderItem[];
803
+ }
804
+
805
+ export interface ShopOrderBookingRef {
806
+ id: string;
807
+ room_number?: string | null;
808
+ guest_name?: string | null;
809
+ }
810
+
811
+ export interface LiveShopOrdersFeedElement extends HTMLElement {
812
+ heading?: string;
813
+ channelName?: string;
814
+ ablyAuthUrl?: string;
815
+ /** Fetches one page of order history, newest-first; Ably only delivers
816
+ * events published after the subscription starts, so history has to
817
+ * come from a REST call. Omit `cursor` for the first page; each
818
+ * subsequent call passes the `created_at` of the oldest loaded order so
819
+ * only the next page is fetched. Return fewer than `limit` items to
820
+ * signal there's nothing older left. */
821
+ fetchOrders?: (params: { cursor?: string; limit: number }) => Promise<ShopOrder[]>;
822
+ /** Resolves booking_id -> room/guest, since the order payload carries
823
+ * only the id. */
824
+ bookings?: ShopOrderBookingRef[];
825
+ maxItems?: number;
826
+ /** Set false to stop fetching further pages once the first one loads --
827
+ * disables the scroll-to-bottom sentinel entirely rather than swapping
828
+ * in a manual button. Default true. */
829
+ autoLoadMore?: boolean;
830
+ emptyLabel?: string;
831
+ /** Symbol prefixed to money amounts ('€', '$', ...); the payload carries
832
+ * no currency of its own. Default empty (bare amount). */
833
+ currency?: string;
834
+ /** Restricts the feed to one booking's orders -- `fetchOrders` should
835
+ * already be scoped server-side; this only filters the live `new-order`
836
+ * events, which otherwise arrive unfiltered off the channel. */
837
+ filterBookingId?: string;
838
+ }
839
+
701
840
  export interface VenueCarouselSlide {
702
841
  key: string;
703
842
  src: string;
@@ -957,6 +1096,9 @@ export interface BookingCheckoutElement extends HTMLElement {
957
1096
  locations?: BookingCheckoutLocation[];
958
1097
  defaultPhoneCode?: string;
959
1098
  allowBookingForOther?: boolean;
1099
+ /** Hides the "Booking for myself/someone else" toggle entirely (not just
1100
+ * the "other" option) -- for checkouts that aren't a booking at all. */
1101
+ showBookingFor?: boolean;
960
1102
  termsHref?: string;
961
1103
  prefill?: BookingCheckoutPrefill;
962
1104
  resolveInitialLocation?: () => Promise<BookingCheckoutResolvedLocation | null>;
@@ -1161,6 +1303,11 @@ export interface GoogleReviewItem {
1161
1303
  text: string;
1162
1304
  relative_time?: string;
1163
1305
  published_at?: string | null;
1306
+ /** Photos the reviewer attached to their review (not the same as
1307
+ * author_photo, which is their profile picture). Only ever populated
1308
+ * when the host's fetchReviews is backed by SerpApi's Google Maps
1309
+ * Reviews API -- Google's own official Places API never exposes these. */
1310
+ photos?: string[] | null;
1164
1311
  }
1165
1312
 
1166
1313
  /** The payload <google-reviews> renders -- the shape served by the OTA's
@@ -1192,10 +1339,62 @@ export interface GoogleReviewsElement extends HTMLElement {
1192
1339
  /** Slider only: seconds between automatic advances; 0/unset = off. */
1193
1340
  autoAdvance?: number;
1194
1341
  maxReviews?: number;
1342
+ /** Sweeps a light band across the CTA on hover instead of the default
1343
+ * opacity dip. Off by default. */
1344
+ ctaShimmer?: boolean;
1195
1345
  strings?: Partial<GoogleReviewsStrings>;
1196
1346
  fetchReviews?: () => Promise<GoogleReviewsPayload>;
1197
1347
  }
1198
1348
 
1349
+ export interface TripadvisorReviewItem {
1350
+ author: string;
1351
+ author_photo?: string | null;
1352
+ rating: number;
1353
+ text: string;
1354
+ relative_time?: string;
1355
+ published_at?: string | null;
1356
+ /** Photos the reviewer attached to their review. */
1357
+ photos?: string[] | null;
1358
+ }
1359
+
1360
+ /** The payload <tripadvisor-reviews> renders. Same shape as
1361
+ * GoogleReviewsPayload (maps_url/write_review_url renamed to place_url,
1362
+ * since Tripadvisor has no equivalent one-click write-review deep link). */
1363
+ export interface TripadvisorReviewsPayload {
1364
+ rating: number | null;
1365
+ count: number;
1366
+ place_url?: string | null;
1367
+ write_review_url?: string | null;
1368
+ reviews: TripadvisorReviewItem[];
1369
+ fetched_at?: string;
1370
+ }
1371
+
1372
+ export interface TripadvisorReviewsStrings {
1373
+ reviewsLabel: string;
1374
+ ctaLabel: string;
1375
+ seeAllLabel: string;
1376
+ loadingLabel: string;
1377
+ errorLabel: string;
1378
+ }
1379
+
1380
+ /** <tripadvisor-reviews>: live Tripadvisor rating, count, and the latest
1381
+ * review cards. Same shape and behaviour as <google-reviews> (see its own
1382
+ * doc comment) -- a separate component rather than a shared one because
1383
+ * each review source gets its own summary doc, seed script, and branding.
1384
+ * Theme via --ta-*. */
1385
+ export interface TripadvisorReviewsElement extends HTMLElement {
1386
+ /** 'grid' (default) or 'slider' (scroll-snap track with prev/next arrows). */
1387
+ variant?: 'grid' | 'slider';
1388
+ /** Slider only: seconds between automatic advances; 0/unset = off. */
1389
+ autoAdvance?: number;
1390
+ maxReviews?: number;
1391
+ /** Sweeps a light band across the CTA on hover instead of the default
1392
+ * opacity dip. Off by default. */
1393
+ ctaShimmer?: boolean;
1394
+ strings?: Partial<TripadvisorReviewsStrings>;
1395
+ fetchReviews?: () => Promise<TripadvisorReviewsPayload>;
1396
+ }
1397
+
1199
1398
  /** <ai-chat-widget>: a floating chat launcher + panel that talks to a
1200
1399
  * host-provided chat API endpoint (any AI SDK v5+ UI-message-stream route).
1201
1400
  * Knows nothing about the model, tools, or business data behind `api` --
@@ -1214,10 +1413,138 @@ export interface AiChatWidgetElement extends HTMLElement {
1214
1413
  phone?: string;
1215
1414
  }
1216
1415
 
1416
+ /** One purchasable combination within a variant group -- a real, distinct
1417
+ * catalogue item (own id, own price, own stock on the host's backend), just
1418
+ * presented as a pill (or a pill pair, one per axis) under the group's
1419
+ * single card instead of a separate card of its own. */
1420
+ export interface ProshopVariant {
1421
+ id: string;
1422
+ /** First axis value, e.g. a color: "Blue". */
1423
+ option1: string;
1424
+ /** Second, independent axis value, e.g. a size: "Small". Omit when this
1425
+ * group only varies along one axis -- the second pill row never renders
1426
+ * unless at least one variant sets this. */
1427
+ option2?: string;
1428
+ price: number | null;
1429
+ /** Swaps the card's photo when this variant becomes selected -- e.g. a
1430
+ * different photo per color. Omit on a variant that doesn't change the
1431
+ * photo (e.g. a size-only difference): the card falls back to the group
1432
+ * item's own `image` whenever the selected variant has none of its own. */
1433
+ image?: string | null;
1434
+ }
1435
+
1436
+ export interface ProshopItem {
1437
+ id: string;
1438
+ name: string;
1439
+ description?: string;
1440
+ category?: string;
1441
+ image?: string | null;
1442
+ price: number | null;
1443
+ /** When set, the photo + name link here (e.g. a product detail page).
1444
+ * Omit for a catalogue with no such page -- the card stays static. */
1445
+ href?: string | null;
1446
+ /** Renders this card as a variant group: one pill row per axis (e.g.
1447
+ * color, and independently size) beneath the description, switching
1448
+ * which real item id/price the qty stepper and cart operate on.
1449
+ * `id`/`price` above are only the initial (first-variant) display
1450
+ * values. Each entry is a genuinely separate catalogue item, so per-
1451
+ * variant stock still works with no other change -- the host just
1452
+ * supplies N real items grouped under one display card instead of N
1453
+ * separate cards. */
1454
+ variants?: ProshopVariant[];
1455
+ /** Header shown above each pill row, e.g. ["Color", "Size"]. Purely
1456
+ * cosmetic -- omit for no header, or supply just the first to label
1457
+ * only a single-axis group. */
1458
+ variantAxisLabels?: [string, string?];
1459
+ }
1460
+
1461
+ export interface ProshopCartItem {
1462
+ id: string;
1463
+ name: string;
1464
+ price: number;
1465
+ quantity: number;
1466
+ }
1467
+
1468
+ export interface ProshopCatalogueChangeEventDetail {
1469
+ items: ProshopCartItem[];
1470
+ total: number;
1471
+ }
1472
+
1473
+ /** <proshop-catalogue>: browse + cart-build a shop/pro-shop catalogue (merch,
1474
+ * equipment, etc.) -- category tabs, a card grid (photo/name/price/qty
1475
+ * stepper), and a sticky cart-total bar. Stops at the cart on purpose:
1476
+ * checkout (contact details + Stripe payment) is <booking-checkout>'s job --
1477
+ * feed this component's `cart-change` total/items into that one rather than
1478
+ * building a second payment flow. Emits `cart-change` on every quantity
1479
+ * change. Theme via --pc-*. */
1480
+ export interface ProshopCatalogueElement extends HTMLElement {
1481
+ items?: ProshopItem[];
1482
+ currency?: string;
1483
+ /** Explicit category display order; categories not listed here are
1484
+ * appended in first-seen order. Omit to use first-seen order only. */
1485
+ categoryOrder?: string[];
1486
+ /** Overrides the auto-generated label for a category key -- otherwise a
1487
+ * category key is shown verbatim. */
1488
+ categoryLabels?: Record<string, string>;
1489
+ /** Restore cart quantities, e.g. from localStorage: [{ id, quantity }].
1490
+ * Reactive, not mount-only -- reassign it (a new array) after mount to
1491
+ * re-sync quantities a host changed elsewhere, e.g. a checkout sidebar. */
1492
+ initial?: { id: string; quantity: number }[];
1493
+ /** Adds a leading tab (before every real category) that shows every item
1494
+ * across all categories in one grid, and becomes the default active tab.
1495
+ * Omit (default false) to keep the default of the first real category
1496
+ * with no all-items tab. */
1497
+ showAllTab?: boolean;
1498
+ /** Label for the all-items tab. Only used when `showAllTab` is true.
1499
+ * Default: `'All'`. */
1500
+ allTabLabel?: string;
1501
+ }
1502
+
1503
+ export interface InstagramFeedPost {
1504
+ id: string;
1505
+ image: string;
1506
+ caption?: string | null;
1507
+ permalink?: string | null;
1508
+ likeCount?: number | null;
1509
+ commentCount?: number | null;
1510
+ }
1511
+
1512
+ export interface InstagramFeedStrings {
1513
+ followersLabel: string;
1514
+ viewProfileLabel: string;
1515
+ emptyLabel: string;
1516
+ }
1517
+
1518
+ /** <instagram-feed>: a curated grid/slider of Instagram posts plus a
1519
+ * follower-count header and "View profile" link. Unlike GoogleReviews/
1520
+ * TripadvisorReviews, `posts` arrives as a plain prop, not a fetchX
1521
+ * callback -- there's no live per-request fetch to make, so no loading/
1522
+ * error state. Same slider mechanics (continuous loop, auto-advance,
1523
+ * pause on hover/focus) as those two for consistency. No heading/eyebrow
1524
+ * of its own -- that's host-page copy, same split as <google-reviews>.
1525
+ * Theme via --if-*. */
1526
+ export interface InstagramFeedElement extends HTMLElement {
1527
+ posts?: InstagramFeedPost[];
1528
+ /** Shown as the header stat, e.g. "4.3K Followers". Omit to hide the stat entirely. */
1529
+ followers?: number;
1530
+ /** The real instagram.com/<handle> URL -- the "View profile" link, and a
1531
+ * post's link fallback when it has no permalink of its own. */
1532
+ profileUrl?: string;
1533
+ /** 'grid' (default) or 'slider' (scroll-snap track with prev/next arrows). */
1534
+ variant?: 'grid' | 'slider';
1535
+ /** Slider only: seconds between automatic advances; 0/unset = off. */
1536
+ autoAdvance?: number;
1537
+ /** Cap on rendered post tiles. Default 12. */
1538
+ maxPosts?: number;
1539
+ strings?: Partial<InstagramFeedStrings>;
1540
+ }
1541
+
1217
1542
  declare global {
1218
1543
  interface HTMLElementTagNameMap {
1219
1544
  'ai-chat-widget': AiChatWidgetElement;
1220
1545
  'google-reviews': GoogleReviewsElement;
1546
+ 'tripadvisor-reviews': TripadvisorReviewsElement;
1547
+ 'instagram-feed': InstagramFeedElement;
1221
1548
  'service-booking-bar': ServiceBookingBarElement;
1222
1549
  'reservation-bar': ReservationBarElement;
1223
1550
  'booking-flow': BookingFlowElement;
@@ -1231,6 +1558,7 @@ declare global {
1231
1558
  'live-spa-bookings-feed': LiveSpaBookingsFeedElement;
1232
1559
  'live-golf-bookings-feed': LiveGolfBookingsFeedElement;
1233
1560
  'live-dining-orders-feed': LiveDiningOrdersFeedElement;
1561
+ 'live-shop-orders-feed': LiveShopOrdersFeedElement;
1234
1562
  'venue-carousel': VenueCarouselElement;
1235
1563
  'room-grid': RoomGridElement;
1236
1564
  'island-map': IslandMapElement;
@@ -1247,12 +1575,14 @@ declare global {
1247
1575
  'searchable-select': SearchableSelectElement;
1248
1576
  'booking-confirmed': BookingConfirmedElement;
1249
1577
  'restaurant-order-form': RestaurantOrderFormElement;
1578
+ 'proshop-catalogue': ProshopCatalogueElement;
1250
1579
  }
1251
1580
 
1252
1581
  interface HTMLElementEventMap {
1253
1582
  reservationchange: CustomEvent<{ date: string; guests: number; time: string }>;
1254
1583
  'booking-confirmed': CustomEvent<{ date: string; time: string; guests: number }>;
1255
1584
  'inquiry-submitted': CustomEvent<EventInquiryPayload>;
1585
+ 'order-found': CustomEvent<{ reference: string }>;
1256
1586
  'booking-submitted': CustomEvent<SpaBookingPayload>;
1257
1587
  'cancel-requested': CustomEvent<ReservationCancelPayload>;
1258
1588
  'cancel-confirmed': CustomEvent<{ token: string }>;
@@ -1267,6 +1597,7 @@ declare global {
1267
1597
  'checkout-confirmed': CustomEvent<{ ref?: string }>;
1268
1598
  ready: CustomEvent<undefined>;
1269
1599
  'extras-change': CustomEvent<ExtrasCatalogueChangeEventDetail>;
1600
+ 'cart-change': CustomEvent<ProshopCatalogueChangeEventDetail>;
1270
1601
  'order-placed': CustomEvent<RestaurantOrderPlacedEventDetail>;
1271
1602
  'date-filter-change': CustomEvent<LiveReservationsFeedDateFilterEventDetail>;
1272
1603
  // Not declared here: `searchable-select` dispatches a CustomEvent named
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgebuild/hotal-ui",
3
- "version": "0.23.48",
3
+ "version": "0.23.88",
4
4
  "description": "Reusable Svelte UI components for booking and reservation experiences.",
5
5
  "type": "module",
6
6
  "license": "MIT",