@forgebuild/hotal-ui

中文文档
0.48.0发布于 2026/11/13MITTypeScriptES Module近 30 天下载 5023

可重新使用Svelte UI组件用于预订和保留体验.

中文使用文档
文档目录20 节
  1. 安装
  2. 构成部分
  3. <reservation-bar>
  4. <booking-flow>
  5. <menu-section>
  6. <room-grid>
  7. <island-map>
  8. <floor-plan-hero>
  9. <stay-search>
  10. <price-grid>
  11. <availability-calendar>
  12. <booking-card>
  13. <stay-booking-flow>
  14. room-grid 行和特征变体
  15. <dining-teaser>
  16. <booking-checkout>
  17. <confirmation-card>
  18. <menu-items-editor>
  19. 发展
  20. 出版

@forgebuilding/hotal-ui (法语)

可重新使用的Web组件,用于预订和预订体验.

安装

npm install @forgebuild/hotal-ui

没有对等的依赖性——斯维尔特运行时间被捆绑入. 通过平地工作 <script type="module"> 标记也:

<script type="module" src="https://esm.sh/@forgebuild/hotal-ui"></script>

构成部分

<reservation-bar>

一个日期 / 来宾 / 有保留链接的取时栏, 弹出器通过 Floating UI 定位, 以及日历/列表面板 与本地的 Popover API 在一个开阔的阴影根内制成 .

<script type="module">
    import '@forgebuild/hotal-ui';

    const bar = document.querySelector('reservation-bar');

    // Function-valued and explicitly-false-when-default-is-true props must be
    // set as JS properties, not HTML attributes (attributes are strings only,
    // and HTML boolean-attribute semantics can't express "false" when the
    // prop's own default is true).
    bar.showReserveButton = false;
    bar.fetchAvailableDates = async (partySize) => {
        const res = await fetch(`/api/available-dates?party_size=${partySize}`);
        const { dates } = await res.json();
        return dates;
    };

    bar.addEventListener('reservationchange', (e) => console.log(e.detail));
</script>

<reservation-bar reserve-href="/reservations" sync-with-url closed-weekdays="[0]"></reservation-bar>

属性/ 属性

名称属性类型默认说明
minGuestsmin-guestsnumber1最小可选缔约方规模
maxGuestsmax-guestsnumber20最大可选政党规模
defaultGuestsdefault-guestsnumber2初始缔约方规模
timeSlotstime-slots (JSON阵列)string[]'7:00 PM'– 说吧 –'11:00 PM' 30分步骤可选择的时间选项
defaultTimedefault-timestring'7:00 PM'初次时间
showReserveButtonshow-reserve-buttonbooleantrue是否提供了后备链接 . 设定为 JS 属性以获取 false ——见上文注.
reserveHrefreserve-hrefstring'/reservations'基础 URL 构建的查询字符串被附加到
applyOnSelectapply-on-selectboolean!showReserveButton是否选择日期/客人/时间导航 reserveHref 马上 默认反向 showReserveButton (隐藏按钮使酒吧自动导航,它的历史行为); 设定明确解开两者——例如隐藏按钮但保留 applyOnSelect={false} 当主机页面同步内线预订时 reservationchange 不应该有导航. 设定为 JS 属性以获取 false。 。 。 .
syncWithUrlsync-with-urlbooleanfalse从 location.search 挂载
closedWeekdaysclosed-weekdays (JSON阵列)number[][]工作日数0太阳6=Sat)在日历中禁用
fetchAvailableDates- (仅限财产)(partySize: number) => Promise<string[]>undefined可选择日历日期的驱动器

活动

reservationchange —— 说 CustomEvent<{ date: string; guests: number; time: string }>,在选择变更时点火.

他们

reservation-bar {
    --rb-bar-bg: rgba(14, 58, 62, 0.9);
    --rb-bar-border: rgba(212, 154, 60, 0.4);
    --rb-text-color: #fff;
    --rb-accent-bg: #d49a3c;
    --rb-accent-hover-bg: #e0ac57;
    --rb-trigger-hover-bg: rgba(212, 154, 60, 0.15);
    --rb-panel-bg: rgba(10, 10, 12, 0.92);
    --rb-radius: 2px;
}

<booking-flow>

搜索结果,一个条纹支付表,预订提交,和确认——加一个没有可用日期的空状态日历. 与 <reservation-bar>: 页镜 date编号 :guests编号 :time 输入 <booking-flow> 本身,一般来自 reservation-bar是因为 reservationchange 事件或从页面本身的 URL.

<script type="module">
    import '@forgebuild/hotal-ui';

    const flow = document.querySelector('booking-flow');

    flow.closedWeekdays = [0];
    flow.fetchAvailableDates = async (partySize) => {
        const res = await fetch(`/api/available-dates?party_size=${partySize}`);
        const { dates } = await res.json();
        return dates;
    };
    flow.searchSlots = async (date, guests) => {
        const res = await fetch(`/api/search?date=${date}&party_size=${guests}`);
        const { slots } = await res.json();
        return slots;
    };
    flow.stripePublishableKey = 'pk_test_...';
    flow.stripeAppearance = { theme: 'stripe' };
    flow.createPaymentIntent = async (date, guests) => {
        const res = await fetch('/api/create-payment-intent', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ date, party_size: guests }),
        });
        return res.json();
    };
    flow.book = async (details) => {
        const res = await fetch('/api/book', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(details),
        });
        return res.status === 201 ? { ok: true } : { ok: false, status: res.status, message: 'Something went wrong.' };
    };

    flow.date = '2026-08-20';
    flow.guests = 2;

    flow.addEventListener('booking-confirmed', (e) => console.log(e.detail));
</script>

<booking-flow></booking-flow>

属性( 所有 JS- 只使用 —— 没有 HTML 属性, 因为这里的每个属性要么是动态状态, 要么是回调)

名称类型说明
datestring当前搜索日期( ISO)
guestsnumber当前政党规模
preferredTimestring用于预选最接近可用的槽
closedWeekdaysnumber[]公约与 reservation-bar数字 : 0太阳6=Sat,用于空态日历
fetchAvailableDates(partySize: number) => Promise<string[]>驱动空状态取日期器:最多14个开放日期显示为药丸(例如停留日),更多显示为日历
searchSlots(date: string, guests: number) => Promise<BookingSlot[]>返回可用的时段
stripePublishableKeystring条形可公布的密钥
stripeAppearanceobject直通史莱克家 elements({ appearance })
createPaymentIntent(date: string, guests: number) => Promise<PaymentIntentResult>创建条纹支付意向以持有
book(details: BookingDetails) => Promise<BookResult>提出保留

何时 stripePublishableKey 编号 : createPaymentIntent 未设定,付款部分未交付,没有扣卡 stripePaymentIntentId 删除 BookingDetails。 。 。 .

interface BookingSlot { time: string; availableTables: number; }
interface PaymentIntentResult { clientSecret: string; paymentIntentId: string; amountCents: number; }
interface BookingDetails {
    date: string; startTime: string; timeDisplay: string; partySize: number;
    contactName: string; contactEmail?: string; contactPhone?: string; notes?: string;
    stripePaymentIntentId: string; clerkUserId?: string;
}
type BookResult = { ok: true } | { ok: false; status: number; message: string };

活动

booking-confirmed —— 说 CustomEvent<{ date: string; time: string; guests: number }>一旦订房成功就开枪.

他们

booking-flow {
    --bf-text-color: #1a1a1a;
    --bf-muted-text: rgba(0, 0, 0, 0.6);
    --bf-border-color: rgba(0, 0, 0, 0.15);
    --bf-accent-bg: #1a1a1a;
    --bf-accent-text: #fff;
    --bf-spinner-track: rgba(0, 0, 0, 0.15);
    --bf-spinner-active: #1a1a1a;
}

<menu-section>

包含分类标签(动画滑动指示器)的餐厅菜单,信息网格,带有工具提示的每个项目阳性图标,以及可选的PDF-菜单对话框. 两个视觉变体: 经典的中心设计,和 variant="panel" ——以右侧为分页的眉相上方头条,全宽箱形信息网格,并相接的项卡网格,以即时分类切换为静态.

<script type="module">import '@forgebuild/hotal-ui';</script>

<menu-section
    variant="panel"
    title="On the menu"
    eyebrow="ZUMA"
    categories='[{"label":"Bites","items":[{"name":"BBQ Chicken Sliders","description":"Jerk-glazed, brioche bun","price":"$17"}]}]'
    info='[{"label":"Hours","value":"12:00 pm – late"}]'
></menu-section>

属性/ 属性

名称属性类型默认说明
titletitlestring'MENU'主要标题
eyebroweyebrowstring''二级线(标题上方为 panel,否则)
categoriescategories (杰森语)MenuCategory[][]{ label, aside?, note?, items: [{ name, description?, price?, tagline?, group?, country?, allergens?, badges?: [{ label, tone?: 'accent' }], notes?: string[] }] }
infoinfo (杰森语){label, value}[][]信息行(小时,着装码,
allergensallergens (杰森语){id, label, icon}[][]图例解决项目 allergens 图标的编号
pdfUrl 编号 : pdfLabelpdf-url 编号 : pdf-labelstring—— 说在对话框中打开的可选 PDF 菜单
headingAlignheading-align'center' | 'left''center'只有经典变体
layoutlayout'stacked' | 'split''stacked'只有经典变体
variantvariant'default' | 'panel''default'视觉变体
themetheme (杰森语)object{}色彩覆盖, 作为 CSS 自定义属性应用
groupFiltergroup-filterbooleanfalse按项目过滤活动类别 group (例如,在酒类名单上的国家)
countryFiltercountry-filterbooleanfalse按项目过滤药片行 country (如区域),缩小至活动群体
groupFilterAllLabel 编号 : countryFilterAllLabelgroup-filter-all-label 编号 : country-filter-all-labelstring'All'"展示一切"药丸的标签
footnotefootnotestring''面板下的小印数
headingLevelheading-level1 | 2 | 3h1 (经典), h2(平面)标题级别; 在页上设置 2 有自己的 h1
prerenderedprerenderedbooleanfalse设定 renderMenuSection();跳过入口动议

槽数 : heading-title, (中文(简体) ), eyebrow, (中文(简体) ), info,以及 intro (轻-DOM内容在标题下,在标签上方).

移交

@forgebuild/hotal-ui/server 使构建时的元素变成一个声明式的阴影根,因此菜单在页面的HTML中,在脚本到达时不移动(脚本后只添加分页切换) :

import { renderMenuSection } from '@forgebuild/hotal-ui/server';
const { attributes, shadowRoot } = renderMenuSection({ title: 'Menu', headingLevel: 2, categories });
// <menu-section {...attributes}>{shadowRoot}<span slot="heading-title">Menu</span></menu-section>

attributes 未脱逃(让模板逃离)并包含 prerendered。 。 。 . title 也成为宿主的本土 title 属性,浏览器将它作为工具提示显示在整个元素上;将它自己插槽的主机 heading-title 可以保留它未设置(标签列表由标题任意命名)。 装入元素的脚本( R)@forgebuild/hotal-ui/menu-section每当诉讼; 预发内容自成一体直到那时。 设置颜色与 --ms-* cSS 中的自定义属性,而非 theme 属性 : theme 由脚本应用,所以预发菜单会从默认中闪出。 按脚本互换的页面(如Astro的客户端Router)不解析声明阴影根;做一个完整的页面负载.

这个 panel 变量预期主机页面会加载Syne和Inter字体.

<room-grid>

室型上市:过滤分页,取出入/出出行范围分表取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取 后端-可知性——由主机解决图像URL和价格,并传递平面数据.

<script type="module">
    import '@forgebuild/hotal-ui';

    const grid = document.querySelector('room-grid');
    // Sync dates with storage / other pages
    grid.checkIn = localStorage.getItem('checkin') ?? '';
    grid.addEventListener('dates-change', (e) => console.log(e.detail)); // { checkIn, checkOut }
    // Pair with <island-map>
    grid.addEventListener('room-hover', (e) => map.highlightPin(e.detail.slug));
    grid.addEventListener('room-leave', (e) => map.unhighlightPin(e.detail.slug));
</script>

<room-grid
    rooms='[{"slug":"beachfront","name":"Beach Front Room","description":"Steps from the shore.","price":1200,"image":"/img/beachfront.jpg","tag":"Beach Front","href":"/accommodations/beachfront"}]'
    filter-tabs='[{"label":"Beach Front","slug":"beachfront"}]'
    currency="USD"
></room-grid>

属性/ 属性

名称属性类型默认说明
roomsrooms (杰森语)RoomGridRoom[][]{ slug, name, description?, price?, image?, tag?, href? }
filterTabsfilter-tabs (杰森语){label, slug}[]来源于 tag编号明确标签,每张标签以弹片匹配到一个房间
checkIn 编号 : checkOutcheck-in 编号 : check-outstring (国际标准化组织)''选定日期;确定为JS属性
currencycurrencystring'USD'价格上的后缀
allLabelall-labelstring'All'第一个标签的标签

活动

  • dates-change —— 说 CustomEvent<{ checkIn: string; checkOut: string }> 在每个日历选择中
  • room-hover 编号 : room-leave —— 说 CustomEvent<{ slug: string }> 纸牌指针的出入口

期望主机页面装入 Syne 和 Inter 字体 .

<island-map>

有名牌弹出针卡的谷歌地图. 自我启动:重新使用网页上的Google Maps加载器(gmpx-api-loader, (中文(简体) ), @googlemaps/js-api-loader,另一个 island-map),并给正式的靴子本身注入 api-key 否则。 柱被隐藏到突出显示( 有弹性的流行) —— 配对 <room-grid>徘徊事件 - 或永久显示 pins-visible。 。 。 .

<script type="module">import '@forgebuild/hotal-ui';</script>

<island-map
    class="h-[480px] w-full"
    pins='[{"label":"Beach Front Room","sub":"From USD 1,200","lat":18.3465,"lng":-64.5905,"slug":"beachfront"}]'
    center="18.352,-64.591"
    zoom="14"
    map-id="YOUR_CLOUD_MAP_ID"
    api-key="YOUR_MAPS_KEY"
></island-map>

属性/ 属性

名称属性类型默认说明
pinspins (杰森语)IslandMapPin[][]{ label, sub?, lat, lng, slug? }
centercenterstring'0,0'"lat,lng"
zoomzoomnumber14初始缩放
mapIdmap-idstring''Google Cloud 映射id (样式)
apiKeyapi-keystring''仅在页面上没有地图加载器时使用
colorSchemecolor-scheme'' | 'DARK' | 'LIGHT' | 'FOLLOW_SYSTEM'''保留样式 map-id 设置它以 Google 的基础主题取代了云的样式
fitPinsfit-pinsbooleantrue将取景口装入负载的针口
fitPadding 编号 : panYfit-padding 编号 : pan-ynumber-50 编号 : -100适值绑定和后适值垂直平板
interactionLockinteraction-lockbooleantrue吞下地图,直到访客点击一次
pinsVisiblepins-visiblebooleanfalse永久显示所有 Pin 牌
lazylazybooleanfalse推迟映射创建直至元素接近视图端口
zoomControlzoom-controlbooleanfalse显示 +/ - 缩放按钮

方法

highlightPin(slug) 编号 : unhighlightPin(slug) 弹出或藏起那发子弹下注册的接头卡.

默认高度是 480px 通过 :host; 在元素上带有类或内置样式的覆盖.

<floor-plan-hero>

制定方案管理系统的平面计划room_type.floor_plan数字 : { rooms, items, w, h })作为SVG——带标签/m2,家具资产,可选建筑背景的区域矩. 默认形式是英雄覆盖面板(标题行,关闭按钮,滑动过渡,开始隐藏——用主机类定位并打开自触发器); bare 只需要把计划 嵌入一个主机型的容器。 区域悬浮释放事件,主机可以用来交换英雄图像.

<script type="module">
    import '@forgebuild/hotal-ui';

    const fp = document.querySelector('floor-plan-hero');
    layoutButton.addEventListener('click', () => fp.toggle());
    fp.addEventListener('zone-hover', (e) => { if (e.detail.img) heroImg.src = e.detail.img; });
    fp.addEventListener('zone-leave', () => (heroImg.src = defaultSrc));
</script>

<floor-plan-hero
    class="absolute right-[120px] bottom-[200px] w-[520px]"
    plan='{"w":760,"h":480,"rooms":[{"id":"r1","type":"bedroom","label":"Bedroom","x":60,"y":60,"w":180,"h":140}],"items":[{"id":"i1","asset":"asset4","x":0,"y":0,"w":71,"h":80}]}'
></floor-plan-hero>

<!-- Chromeless, on a light surface -->
<floor-plan-hero theme="light" bare plan="..."></floor-plan-hero>

属性/ 属性

名称属性类型默认说明
planplan (杰森语)FloorPlan{}{ rooms: [{id, type?, label?, x, y, w, h, sqm?, imgUrl?}], items: [{id, asset, x, y, w, h}], w?, h? }
bgUrlbg-urlstring—— 说建筑背景图像,以30%的不透明方式渲染
assetBaseasset-basestring'/SVG/'项目资产 ${assetBase}Asset {n}.svg (资产密钥) "asset4" → Asset 4.svg(中文(简体) )
label 编号 : closeLabellabel 编号 : close-labelstring'Floor plan' 编号 : 'Close ×'面板头复制件
themetheme'dark' | 'light''dark'区域/ 标签调色板
barebarebooleanfalse计划 SVG 仅 - 没有标题、 铬 或 打开/关闭状态

方法与活动

open() 编号 : close() 编号 : toggle() ——面板能见度(无 bare 模式,总是可见的). zone-hover 编号 : zone-leave —— 说 CustomEvent<{ img?: string; label?: string; type?: string }> 区域指针进入/离开. panel-close 当使用内部关闭按钮时起火.

英雄可用性搜索:玻璃入站/出站吧,两个月范围日历,搜索状态回旋/tick,以及房间卡(图像,姓名,描述,停留总计)的结果倒放. 结果为: 合并对 rooms pMS室型编号的道具 ——任何不是上市房间的搜索回文被删除,所以PMS-side测试数据从未到达来宾. 卡片图像和拷贝来自主机(CMS),检索的费率.

<script type="module">
    import '@forgebuild/hotal-ui';

    const search = document.querySelector('stay-search');
    search.checkIn = localStorage.getItem('checkin') ?? '';
    search.searchStays = async (checkIn, checkOut, guests) => {
        const res = await fetch(`/api/ota/availability/search?check_in=${checkIn}&check_out=${checkOut}&guests=${guests}`);
        return res.json(); // [{ room_type_id, from_rate, ... }]
    };
    search.addEventListener('dates-change', (e) => console.log(e.detail));
</script>

<stay-search
    class="block w-full max-w-[680px]"
    rooms='[{"roomTypeId":"92fc4ca8-…","name":"Beach Front Room","description":"Steps from the shore.","image":"/img/beachfront.jpg","href":"/accommodations/beachfront"}]'
    currency="USD"
></stay-search>

属性/ 属性

名称属性类型默认说明
roomsrooms (杰森语)StaySearchRoom[][]{ roomTypeId, name, description?, image?, href? } ——结果合并过滤器
checkIn 编号 : checkOutcheck-in 编号 : check-outstring (国际标准化组织)''选定日期;确定为JS属性
guestsguestsnumber2传到 searchStays
currencycurrencystring'USD'ISO 4217 代码;卡片总计格式化于其中
localelocalestring浏览器日期、月/周名称和价格格式
stringsstrings (杰森语)object联合国界面文本 : checkIn, (中文(简体) ), checkOut, (中文(简体) ), addDate, (中文(简体) ), guests, (中文(简体) ), fewerGuests, (中文(简体) ), moreGuests, (中文(简体) ), chooseDates, (中文(简体) ), previousMonth, (中文(简体) ), nextMonth, (中文(简体) ), loadError, (中文(简体) ), none, (中文(简体) ), availableOne, (中文(简体) ), availableMany, (中文(简体) ), totalOne, (中文(简体) ), totalMany (单位:千美元){n} = 夜间)
appendDatesToHrefappend-dates-to-hrefbooleantrue附录 ?check_in=…&check_out=… 切入到卡片内
showGuestsshow-guestsbooleanfalse显示来宾步进; 更改它会重运行搜索
showRoomsshow-roomsbooleanfalse显示一个“客人和房间”字段:成人和儿童每间,增加/去掉房间(最多 max-rooms,默认 4)
searchStays- (仅限财产)(checkIn, checkOut, guests, rooms) => Promise<StaySearchResult[]>—— 说选择完整范围时运行; 结果需要 room_type_id 财务报告和审定财务报表 from_rate。 。 。 . rooms 实值 {adults, children}[],每个房间一个

活动

dates-change —— 说 CustomEvent<{ checkIn: string; checkOut: string }> 每个选择. search-results —— 说 CustomEvent<{ checkIn, checkOut, guests, count }> 在每次搜索完成之后.

期望主机页面装入 Syne 和 Inter 字体 .

<price-grid>

价格提示表:每间酒店或房间类型一行,每晚一行一列,每间牢房的夜价. 箭头会将窗口(在今天之前从未出现过),本地日期输入会跳到任何日期,每个列头会将行排序. 每个价格链接到行 href 留守一夜间?room=<id>&check_in=…&check_out=…&guests=…),或当行无时是按钮 href。在狭长的屏幕上,表格会用被钉住的名称栏向后滚动.

<price-grid currency="GBP" days="7">
    <strong slot="heading">Best prices this week</strong>
</price-grid>
<script type="module">
    import '@forgebuild/hotal-ui/price-grid';
    const grid = document.querySelector('price-grid');
    grid.rows = [{ id: '92fc4ca8-…', name: 'Beach Front Room', meta: 'Sleeps 4', href: '/book' }];
    grid.fetchRates = async (from, to) => (await fetch(`/api/rates?from=${from}&to=${to}`)).json(); // [{ id, date, rate, available? }]
    grid.addEventListener('rate-select', (e) => console.log(e.detail)); // { id, date, rate }
</script>

属性/ 属性

名称属性类型默认说明
rowsrows (杰森语)PriceGridRow[][]{ id, name, meta?, href? } 显示顺序; meta 填入可选第二列
startDatestart-datestring (国际标准化组织){\fn华文楷体\fs16\1che0e0e0}今天首个晚上显示; 夹住今天
daysdaysnumber7一次显示的夜晚和箭的步(1-31)
guestsguestsnumber2运入售出价格链接
currencycurrencystring'USD'ISO 4217 代码
localelocalestring浏览器日期和价格格式
stringsstrings (杰森语)object联合国界面文本 : caption, (中文(简体) ), name, (中文(简体) ), meta, (中文(简体) ), from, (中文(简体) ), previous, (中文(简体) ), next, (中文(简体) ), sortBy (单位:千美元){x} =栏, unavailable, (中文(简体) ), loadError, (中文(简体) ), retry
fetchRates- (仅限财产)(fromISO, toISO) => Promise<PriceGridRate[]>—— 说两个日期都包括在内;每个窗口都呼叫一次。 一个晚上,没有,没有率,或者有 available 0/假显示为不可用 . room 替换交接器中的行 id 当行集成一些东西时(当晚酒店最便宜的房间)

活动

rate-select —— 说 CustomEvent<{ id: string; date: string; rate: number; room: string }> 当一个价格被点击时(room = 费率 room,否则行号. window-change —— 说 CustomEvent<{ from: string; to: string }> 当窗口移动时.

槽 heading 坐在日期控件的左边。 主题 --pg-* 自定义属性( 完整列表于 llms.txt; 继承主机页面的字体.

<availability-calendar>

停留的内含范围日历:每个日期的每日夜薪(PMS) min_rate),无可用性的日期被禁用, tal-渐变范围选择。 两个月并肩在桌面上,一个在移动上.

<script type="module">
    import '@forgebuild/hotal-ui';

    const cal = document.querySelector('availability-calendar');
    cal.fetchAvailability = async (from, to) => {
        const res = await fetch(`/api/availability?room_type_id=…&from=${from}&to=${to}`);
        return res.json(); // [{ date, available_rooms, min_rate }]
    };
    cal.addEventListener('dates-change', (e) => console.log(e.detail)); // { checkIn, checkOut }
</script>

<availability-calendar></availability-calendar>
名称属性类型默认说明
checkIn 编号 : checkOutcheck-in 编号 : check-outstring (国际标准化组织)''选定范围;作为 JS 属性设置
horizonMonthshorizon-monthsnumber12请求加载器提供的窗口
fetchAvailability- (仅限财产)(fromISO, toISO) => Promise<AvailabilityDay[]>—— 说因无结果而缺席的日期

输出 dates-change 每个选择.

<booking-card>

室型页面的预留卡:从价格头到有白/teal弹出日历的报到/出站字段(通过与装入器合同相同的可使用性-意识) <availability-calendar>),嘉宾步道,活价分解(小计,度假地费,总额),有绿色擦拭悬浮的储备CTA,精细的印花,并有收押脚.

<script type="module">
    import '@forgebuild/hotal-ui';

    const card = document.querySelector('booking-card');
    card.fetchAvailability = async (from, to) => (await fetch(`/api/availability?…`)).json();
    card.addEventListener('dates-change', (e) => console.log(e.detail));
    card.addEventListener('reserve', (e) => {
        const { checkIn, checkOut, guests } = e.detail;
        location.href = `/book?checkin=${checkIn}&checkout=${checkOut}&guests=${guests}`;
    });
</script>

<booking-card class="sticky top-[100px] block" price="3800" max-guests="4" currency="USD"></booking-card>
名称属性类型默认说明
pricepricenumber0夜从率;每晚按此率计价
maxGuestsmax-guestsnumber4B. 宾客台阶上限
resortFeeRateresort-fee-ratenumber0.03费用作为小计的一小部分
ratingText 编号 : finePrintrating-text 编号 : fine-printstring—— 说页眉和页脚副本
conciergeTitle 编号 : conciergeSub 编号 : conciergeCtaLabel 编号 : conciergeCtaHref弹匣string岛礁岛默认值页脚块
checkIn 编号 : checkOut 编号 : guestscheck-in 编号 : check-out 编号 : guestsISO / 编号'' 编号 : 2目前停留;作为JS财产固定
fetchAvailability- (仅限财产)见上文—— 说弹出时禁用无法使用的日期

输出 dates-change, (中文(简体) ), guests-change,以及 reserve (单位:千美元){ checkIn, checkOut, guests } ——导航是主持人的工作.

<stay-booking-flow>

一个房间的停留预订页,或几个一起预订的页&rooms=<room>:<adults>:<children>,… 在 URL 中,以 selection.rooms:可选的外加步骤,然后是细节和支付,然后是确认. 读取 room, (中文(简体) ), check_in, (中文(简体) ), check_out, (中文(简体) ), guests 从 URL (或匹配属性, 胜出) 显示重新开始的链接 start-href 如果他们失踪,无效,或者 room 没有在 rooms所有价格来自您 quote 召回(callback),当来宾或外出者改变时,重新召回(被取消,最新回应获胜).

<stay-booking-flow currency="USD" start-href="/rooms"></stay-booking-flow>
<script type="module">
    import '@forgebuild/hotal-ui';

    const flow = document.querySelector('stay-booking-flow');
    flow.rooms = [{ id: 'junior-suite', name: 'Junior Suite', image: '/img/junior.jpg', maxGuests: 3 }];
    flow.extras = [{ id: 'spa-1', name: 'Couples Massage', category: 'Wellness', price: 220 }];
    flow.quote = async (sel) => (await fetch('/api/quote', { method: 'POST', body: JSON.stringify(sel) })).json(); // { lines, total, currency }
    flow.stripePublishableKey = 'pk_live_…';
    // Server: recalculate the amount from `sel` (never trust a client total) and create the intent with
    // automatic_payment_methods: { enabled: true, allow_redirects: 'never' }.
    flow.createPaymentIntent = async (sel) => (await fetch('/api/stay-intent', { method: 'POST', body: JSON.stringify(sel) })).json(); // { clientSecret }
    flow.submitBooking = async (payload) => (await fetch('/api/stay-book', { method: 'POST', body: JSON.stringify(payload) })).json(); // { ok: true, ref }
    flow.addEventListener('booking-confirmed', (e) => console.log(e.detail)); // { ref, selection, total }
</script>

道具 : room, (中文(简体) ), checkIn编号 :check-in, (中文(简体) ), checkOut编号 :check-out, (中文(简体) ), guests, (中文(简体) ), rooms, (中文(简体) ), extras, (中文(简体) ), categories, (中文(简体) ), fallbackCategory, (中文(简体) ), currency, (中文(简体) ), startHref编号 :start-href, (中文(简体) ), quote, (中文(简体) ), createPaymentIntent, (中文(简体) ), submitBooking (只有JS属性), strings附加时 booking-checkout 通行证(stripePublishableKey, (中文(简体) ), stripeAppearance, (中文(简体) ), termsHref, (中文(简体) ), cancellationPolicy, (中文(简体) ), locations, (中文(简体) ), allowBookingForOther, (中文(简体) ), showBookingFor, (中文(简体) ), prefill, (中文(简体) ), resolveInitialLocation),并给嵌入式摘要和进步栏打上通关牌(resortName, (中文(简体) ), logoSrc, (中文(简体) ), logoHref, (中文(简体) ), secureLabel, (中文(简体) ), needHelpHref) (中文(简体) ). createPaymentIntent 也可以还原意图 total; 与引文重引不匹配。 输出 step-change (单位:千美元){ step }, (中文(简体) ), quote-change (单位:千美元){ quote }和 booking-confirmed (单位:千美元){ ref, selection, total }) (中文(简体) ). 浏览器后移在步数之间; 步数和外值在刷新后幸存 。 在确认后,背面无法重启支付,刷新显示确认的屏幕. 如果付款成功,但 submitBooking 失败时,流会锁定在重新发送相同有效载荷的"再试一次"上(因此制作 submitBooking 专辑: paymentIntentId幸存的新鲜。 全文参见 llms.txt。 。 。 .

room-grid 行和特征变体

<room-grid variant="rows" rooms='…' currency="USD"> 将同紧凑的横向卡片(图像左出、标记/名称/描述、每晚价格右出)相同的房间数据放在光面上——两列放在桌面上,没有过滤栏、日期或入口运动。 为"更多住宿"风格上市.

<room-grid variant="feature" rooms='…' currency="USD"> 制作主页特性卡:在图像上贴上标记徽章,全卡链接,移动上的一个卷轴-snap 旋转木马,成为桌面上3个栏的网格,以及一个卷轴触发的已淡出入口. 发音相同 room-hover编号 :room-leave 事件——与 <island-map>。 。 。 .

<dining-teaser>

双栏主页取笑器:标题+身体复制,可悬浮/可聚焦的有色码状态点的场地列表,以及CTA——旁边的交叉分解相片可以跟踪任何活动地点. 滚动起的淡出/滑出入口,跳过 prefers-reduced-motion。 。 。 .

<script type="module">
    import '@forgebuild/hotal-ui';

    const teaser = document.querySelector('dining-teaser');
    teaser.addEventListener('venue-change', (e) => console.log(e.detail)); // { index, key }
</script>

<dining-teaser
    class="mx-auto max-w-[1530px]"
    heading="Restaurants."
    heading-accent="No compromise."
    body="From open-fire dry-aged steaks to fresh catch and rum cocktails — dining here is never an afterthought."
    cta-label="Explore dining"
    cta-href="/dining"
    venues='[{"key":"beefbar","name":"beefbar","tag":"Dinner · Fine Dining","image":"/img/beefbar.jpg"}]'
></dining-teaser>

属性/ 属性

名称属性类型默认说明
venuesvenues (杰森语){key, name, tag?, image}[][]按顺序渲染; 徘徊、 聚焦或点击一行使其活动
heading 编号 : headingAccentheading 编号 : heading-accentstring'Restaurants.' 编号 : 'No compromise.'二行标题, 二行口音颜色
bodybodystring—— 说标题下的段落
ctaLabel 编号 : ctaHrefcta-label 编号 : cta-hrefstring'Explore dining' 编号 : '/dining'以下会议地点清单概述的链接
imageLabelimage-labelstring—— 说固定的照片标题。 让它追踪活动地点 name 换成
activeIndexactive-indexnumber0活动场地指数;作为联谊会财产设置,在访客互动时保持同步

活动

venue-change —— 说 CustomEvent<{ index: number; key: string }>当活动地点改变时.

他们

dining-teaser {
    --dt-bg: #f5f2ec;
    --dt-heading-color: #0a0814;
    --dt-accent-color: rgb(180, 20, 80);
    --dt-body-color: rgba(10, 8, 20, 0.65);
    --dt-venue-bg: rgba(10, 8, 20, 0.04);
    --dt-venue-bg-hover: rgba(10, 8, 20, 0.07);
    --dt-venue-border: rgba(10, 8, 20, 0.08);
    --dt-venue-border-hover: rgba(10, 8, 20, 0.14);
    --dt-venue-name-color: #0a0814;
    --dt-venue-type-color: rgba(10, 8, 20, 0.35);
    --dt-dot-color: rgb(155, 93, 229);
    --dt-dot-active-color: rgb(247, 37, 133);
    --dt-cta-border: rgba(10, 8, 20, 0.15);
    --dt-cta-text: #0a0814;
    --dt-cta-hover-bg: #1a1a18;
    --dt-cta-hover-text: #f0eeff;
    --dt-image-height: 480px;
}

期望主机页面可以装入Syne,Inter,和Cormorant Garamond字体.

<booking-checkout>

取出表:"为自己订入/别人"取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取出取的取出取出取出取的取出取出取出取出取出取的取出取出取出取出取出取的取出取出取取出取出取的取出取出取出取出取出取出取出取出取出取的取出取出取的取出取出取出取出取出取的取出取出取出取出取出取的取出取出取出取出取出取出取出取的取出取出取出取出取出取的取出取出取的取出取出取的取出取取出取出取出取的取的取出取出取的取取出取 <searchable-select>,双向交叉,加上电话类型的国家推论),一个条纹支付元素,提交按钮,和一个可选的取消政策块. 后端- 不可知—— 主机创建 Paying Intent并进行实际预订 POST; 此组件只调用 stripe.confirmPayment 将结果交给 submitBooking。 。 。 .

<script type="module">
    import '@forgebuild/hotal-ui';

    const checkout = document.querySelector('booking-checkout');
    checkout.resolveInitialLocation = async () => {
        const res = await fetch('https://api.geoapify.com/v1/ipinfo?apiKey=...');
        const data = await res.json();
        return data?.country?.name ? { countryName: data.country.name, iso2: data.country.iso_code } : null;
    };
    checkout.beforeSubmit = async () => {
        // e.g. re-price the PaymentIntent for extras added after it was created
    };
    checkout.submitBooking = async (payload) => {
        const res = await fetch('/api/book', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload),
        });
        const data = await res.json();
        return data.success ? { ok: true, ref: data.ref } : { ok: false, message: data.error ?? 'Something went wrong.' };
    };
    checkout.addEventListener('checkout-confirmed', (e) => {
        window.location.href = `/book/confirm?ref=${e.detail.ref}`;
    });
</script>

<booking-checkout
    total="3800"
    currency="USD"
    client-secret="pi_..._secret_..."
    stripe-publishable-key="pk_test_..."
    cancellation-policy="Full refund up to 14 days before check-in."
    locations='[{"iso2":"GB","countryName":"United Kingdom","dialCode":"+44"}]'
    default-phone-code="+44"
></booking-checkout>

属性/ 属性

名称属性类型默认说明
totaltotalnumber0驱动提交按钮的金额标签; 设置为 JS 属性, 这样主机可以在额外更改后更新
currencycurrencystring'USD'用于量标签
clientSecretclient-secretstring—— 说Strape Paying Intent 客户端秘密, 由主机创建服务器侧
stripePublishableKeystripe-publishable-keystring—— 说条形可公布的密钥
stripeAppearancestripe-appearance (杰森语)object{}传到 stripe.elements({ appearance })
cancellationPolicycancellation-policystring—— 说显示在显示时的专栏; 省略以完全隐藏
locationslocations (杰森语){iso2, countryName, dialCode}[]每个国家两种选择(空取=每个国家,在浏览器的语言中命名)的出处 – 国家选项保留每个条目,电话编码选项由 dialCode
defaultPhoneCodedefault-phone-codestring'+1'选择两个电话编码的初始值
allowBookingForOtherallow-booking-for-otherbooleantrue隐藏切换和预订子窗体 false
termsHrefterms-hrefstring'/terms'术语链接和“全面政策细节”链接目标
prefill- (仅限财产){firstName?, lastName?, email?}—— 说设置后挂载( 如一旦 Aync 认证检查解析后); 只填入空出字段
resolveInitialLocation- (仅限财产)() => Promise<{countryName, iso2?, dialCode?} | null>—— 说在挂载时被调用为所选择的国家/电话编码种子
beforeSubmit- (仅限财产)() => Promise<void>—— 说在卡片充电前等着
submitBooking- (仅限财产)(payload) => Promise<{ok:true,ref?} | {ok:false,message}>—— 说在成功收费后调用; 组件从未与预订的API本身交谈
stringsstrings (杰森语)object—— 说标签覆盖 – 完整的密钥列表参见组件源

活动

checkout-confirmed —— 说 CustomEvent<{ ref?: string }>有一次会起火 submitBooking 决定 { ok: true }。导航是东道主的工作. submitting-change —— 说 CustomEvent<{ submitting: boolean }>, (中文(简体) ), true 从付费点击(在验证之后),直到尝试结束.

ready ——在初始挂载尝试后放出一次( 配置或不配置、 成功或失败) —— 不与 Pay Element 的自挂载相匹配而丢弃页面级加载状态的钩子 .

他们

booking-checkout {
    --bc-text-color: #05040f;
    --bc-muted-text: rgba(5, 4, 15, 0.4);
    --bc-muted-text-strong: rgba(5, 4, 15, 0.55);
    --bc-border-color: rgba(5, 4, 15, 0.15);
    --bc-input-bg: #f5f2ec;
    --bc-input-border: rgba(5, 4, 15, 0.12);
    --bc-input-border-focus: rgba(5, 4, 15, 0.4);
    --bc-accent-bg: #05040f;
    --bc-accent-text: #f5f2ec;
}

期望主机页面装入 Inter 字体,并且需要 <searchable-select> 注册(自动导入此软件包).

<confirmation-card>

中心确认卡:梯度检查图标,眉毛,标题,机构副本,一个可选的预订参考框,最多可有2个动作链接,以及围绕一个嵌入式链接构建的可选页脚注. 静态——没有道具是回叫,没有事件被释放. 主办方拥有全页中枢( display: flex; align-items: center; justify-content: center围绕它.

<script type="module">import '@forgebuild/hotal-ui';</script>

<confirmation-card
    eyebrow="Booking Confirmed"
    heading="You're all set."
    body="Your reservation at HOTAL has been confirmed. A confirmation email is on its way to you."
    reference="d82cdc93-54fe-4f6a-ae77-8fb2dc7ad1a8"
    primary-label="View My Bookings"
    primary-href="/account"
    secondary-label="Back to Home"
    secondary-href="/"
    note-prefix="Questions about your stay?"
    note-link-label="Contact our concierge team"
    note-link-href="/contact"
    note-suffix="— we're here to help with anything you need before you arrive."
></confirmation-card>

属性/ 属性

名称属性类型默认说明
eyebroweyebrowstring'Booking Confirmed'标题上行; 省略以隐藏
headingheadingstring"You're all set."
bodybodystring—— 说
reference 编号 : referenceLabelreference 编号 : reference-labelstring'' 编号 : 'Booking Reference'框式引用编号;当 reference 为空
primaryLabel 编号 : primaryHrefprimary-label 编号 : primary-hrefstring—— 说已填充按钮; 省略 primaryLabel 为空
secondaryLabel 编号 : secondaryHrefsecondary-label 编号 : secondary-hrefstring—— 说外观按钮; 两者必须设定为渲染
notePrefix 编号 : noteLinkLabel 编号 : noteLinkHref 编号 : noteSuffix弹匣string—— 说脚注文本围绕一个嵌入式链接进行分拆; 当两者兼有时完全省略 notePrefix 链接为空

他们

confirmation-card {
    --cc-card-bg: #fff;
    --cc-border-color: rgba(5, 4, 15, 0.08);
    --cc-icon-bg: linear-gradient(135deg, rgb(0, 245, 212) 0%, rgb(79, 142, 247) 100%);
    --cc-text-color: #05040f;
    --cc-muted-text: rgba(5, 4, 15, 0.4);
    --cc-muted-text-strong: rgba(5, 4, 15, 0.55);
    --cc-ref-bg: rgba(5, 4, 15, 0.03);
    --cc-accent-bg: #05040f;
    --cc-accent-text: #fff;
    --cc-note-border: rgba(5, 4, 15, 0.06);
    --cc-max-width: 560px;
}

期望主机页面装入 Syne 和 Inter 字体 。 不要被混淆 与 <booking-confirmed> ——其中一项是用于离职的紧凑内在步骤 模式 (icon + 标题 + 将事件发回主机的“ 完成” 按钮); 此张是独立的全页卡片, 没有主机调回, 为专用 /confirm- 典型的路线.

<menu-items-editor>

按类别分类的餐厅菜单项目工作人员侧编辑。 每个字段都通过 saveItem,如果拒绝,则乐观地应用,取消。 Allergen and language chips 为 Alergen 切换和每个语言名称/描述扩展行. 每行的"打开"开关保存 { sold_out }所有回电都只是JS的属性.

<menu-items-editor></menu-items-editor>
<script type="module">
    const el = document.querySelector('menu-items-editor');
    el.items = menuItems; // [{ id, name, description, category, price, allergens, translations, sold_out }]
    el.saveItem = (item, patch) => api.put(`/menu/${item.id}`, patch); // reject to undo + flag
    el.createItem = (fields) => api.post('/menu', fields); // omit to hide add buttons
    el.deleteItem = async (item) => (confirm(`Delete ${item.name}?`) ? api.delete(`/menu/${item.id}`) : false);
    el.reorderItems = (items) => api.put('/menu/reorder', { ids: items.map((i) => i.id) }); // one category, first to last
    el.reorderCategories = (categories) => api.put('/menu/category-order', { categories }); // omit either to hide its drag handles and move buttons
</script>

主题 --mie-* (见 llms.txt) (中文(简体) ). 默认出自所继承的文字颜色,因此在光和暗页上作为-is工作.

发展

npm install
npm run dev      # serves demo/
npm run check    # type-check
npm run build    # library build → dist/

出版

npm run build
npm publish

需要登录到 npm (npm whoami(a) 作为拥有出版权的账户 @forgebuild 范围.

版本与发布记录

@forgebuild/hotal-ui 共发布 111 个版本,首次发布于 2026/08/12;过去 12 个月发布了 111 个版本。

版本发布日期距今
0.48.0latest2026/11/132 天前
0.46.12026/11/105 天前
0.46.02026/11/106 天前
0.45.72026/11/078 天前
0.45.52026/11/078 天前
0.45.42026/11/078 天前

关于 @forgebuild/hotal-ui 的常见问题

@forgebuild/hotal-ui 是什么?

可重新使用Svelte UI组件用于预订和保留体验.

如何安装 @forgebuild/hotal-ui?

在项目目录运行 npm install @forgebuild/hotal-ui,或使用 pnpm add / yarn add / bun add。

@forgebuild/hotal-ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@forgebuild/hotal-ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@forgebuild/hotal-ui 可以商用吗?

@forgebuild/hotal-ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@forgebuild/hotal-ui 的下载量有多少?

根据 npm 官方下载统计,@forgebuild/hotal-ui 在 2026/10/16 – 2026/11/14 这 30 天内被下载 5,023 次,日均约 167 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@forgebuild/hotal-ui 还在维护吗?

最新版本 0.48.0 发布于 2026/11/13(2 天前);至今共发布 111 个版本,其中过去 12 个月发布了 111 个;首次发布于 2026/08/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。