# @forgebuild/hotal-ui 中文文档

规范地址：https://npmcn.com/package/%40forgebuild/hotal-ui/

版本：0\.48\.0
npm 元数据更新时间：2026-11-13T10:52:48.016Z
中文译文更新时间：2026-11-15T07:39:10.409Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40forgebuild/hotal-ui
原文对照：https://npmcn.com/package/%40forgebuild/hotal-ui/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：3.5 MB，60 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：9 个
- 首次发布：2026-08-11T21:23:37.073Z
- 最新版本发布：2026-11-13T10:52:47.780Z
- 近 30 天下载量：5023（2026-10-16 至 2026-11-14，npm 官方下载统计）
- 已发布版本数：111（近 12 个月 111 个）

## 中文简介

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

## 安装

```sh
npm install @forgebuild/hotal-ui
```

也可以使用：`pnpm add @forgebuild/hotal-ui`、`yarn add @forgebuild/hotal-ui`、`bun add @forgebuild/hotal-ui`

## README

# @forgebuilding/hotal-ui (法语)

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

## 安装

```
npm install @forgebuild/hotal-ui
```

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

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

## 构成部分

### `<reservation-bar>`

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

```html
<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>
```

#### 属性/ 属性

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `minGuests` | `min-guests` | `number` | `1` | 最小可选缔约方规模 |
| `maxGuests` | `max-guests` | `number` | `20` | 最大可选政党规模 |
| `defaultGuests` | `default-guests` | `number` | `2` | 初始缔约方规模 |
| `timeSlots` | `time-slots` (JSON阵列) | `string[]` | `'7:00 PM'`– 说吧 –`'11:00 PM'` 30分步骤 | 可选择的时间选项 |
| `defaultTime` | `default-time` | `string` | `'7:00 PM'` | 初次时间 |
| `showReserveButton` | `show-reserve-button` | `boolean` | `true` | 是否提供了后备链接 . **设定为 JS 属性以获取 `false`** ——见上文注. |
| `reserveHref` | `reserve-href` | `string` | `'/reservations'` | 基础 URL 构建的查询字符串被附加到 |
| `applyOnSelect` | `apply-on-select` | `boolean` | `!showReserveButton` | 是否选择日期/客人/时间导航 `reserveHref` 马上 默认反向 `showReserveButton` (隐藏按钮使酒吧自动导航,它的历史行为); 设定明确解开两者——例如隐藏按钮但保留 `applyOnSelect={false}` 当主机页面同步内线预订时 `reservationchange` 不应该有导航. **设定为 JS 属性以获取 `false`。 。 。 .** |
| `syncWithUrl` | `sync-with-url` | `boolean` | `false` | 从 `location.search` 挂载 |
| `closedWeekdays` | `closed-weekdays` (JSON阵列) | `number[]` | `[]` | 工作日数`0`太阳`6`\=Sat)在日历中禁用 |
| `fetchAvailableDates` | \- (仅限财产) | `(partySize: number) => Promise<string[]>` | `undefined` | 可选择日历日期的驱动器 |

#### 活动

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

#### 他们

```css
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.

```html
<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 属性, 因为这里的每个属性要么是动态状态, 要么是回调)

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

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

```ts
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 }>`一旦订房成功就开枪.

#### 他们

```css
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"` ——以右侧为分页的眉相上方头条,全宽箱形信息网格,并相接的项卡网格,以即时分类切换为静态.

```html
<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>
```

#### 属性/ 属性

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

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

#### 移交

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

```js
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和价格,并传递平面数据.

```html
<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>
```

#### 属性/ 属性

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `rooms` | `rooms` (杰森语) | `RoomGridRoom[]` | `[]` | `{ slug, name, description?, price?, image?, tag?, href? }` |
| `filterTabs` | `filter-tabs` (杰森语) | `{label, slug}[]` | 来源于 `tag`编号 | 明确标签,每张标签以弹片匹配到一个房间 |
| `checkIn` 编号 : `checkOut` | `check-in` 编号 : `check-out` | `string` (国际标准化组织) | `''` | 选定日期;确定为JS属性 |
| `currency` | `currency` | `string` | `'USD'` | 价格上的后缀 |
| `allLabel` | `all-label` | `string` | `'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`。 。 。 .

```html
<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>
```

#### 属性/ 属性

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `pins` | `pins` (杰森语) | `IslandMapPin[]` | `[]` | `{ label, sub?, lat, lng, slug? }` |
| `center` | `center` | `string` | `'0,0'` | `"lat,lng"` |
| `zoom` | `zoom` | `number` | `14` | 初始缩放 |
| `mapId` | `map-id` | `string` | `''` | Google Cloud 映射id (样式) |
| `apiKey` | `api-key` | `string` | `''` | 仅在页面上没有地图加载器时使用 |
| `colorScheme` | `color-scheme` | `'' | 'DARK' | 'LIGHT' | 'FOLLOW_SYSTEM'` | `''` | 保留样式 `map-id` 设置它以 Google 的基础主题取代了云的样式 |
| `fitPins` | `fit-pins` | `boolean` | `true` | 将取景口装入负载的针口 |
| `fitPadding` 编号 : `panY` | `fit-padding` 编号 : `pan-y` | `number` | `-50` 编号 : `-100` | 适值绑定和后适值垂直平板 |
| `interactionLock` | `interaction-lock` | `boolean` | `true` | 吞下地图,直到访客点击一次 |
| `pinsVisible` | `pins-visible` | `boolean` | `false` | 永久显示所有 Pin 牌 |
| `lazy` | `lazy` | `boolean` | `false` | 推迟映射创建直至元素接近视图端口 |
| `zoomControl` | `zoom-control` | `boolean` | `false` | 显示 +/ - 缩放按钮 |

#### 方法

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

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

### `<floor-plan-hero>`

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

```html
<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>
```

#### 属性/ 属性

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

#### 方法与活动

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

### `<stay-search>`

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

```html
<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>
```

#### 属性/ 属性

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `rooms` | `rooms` (杰森语) | `StaySearchRoom[]` | `[]` | `{ roomTypeId, name, description?, image?, href? }` ——结果合并过滤器 |
| `checkIn` 编号 : `checkOut` | `check-in` 编号 : `check-out` | `string` (国际标准化组织) | `''` | 选定日期;确定为JS属性 |
| `guests` | `guests` | `number` | `2` | 传到 `searchStays` |
| `currency` | `currency` | `string` | `'USD'` | ISO 4217 代码;卡片总计格式化于其中 |
| `locale` | `locale` | `string` | 浏览器 | 日期、月/周名称和价格格式 |
| `strings` | `strings` (杰森语) | `object` | 联合国 | 界面文本 : `checkIn`, (中文(简体) ), `checkOut`, (中文(简体) ), `addDate`, (中文(简体) ), `guests`, (中文(简体) ), `fewerGuests`, (中文(简体) ), `moreGuests`, (中文(简体) ), `chooseDates`, (中文(简体) ), `previousMonth`, (中文(简体) ), `nextMonth`, (中文(简体) ), `loadError`, (中文(简体) ), `none`, (中文(简体) ), `availableOne`, (中文(简体) ), `availableMany`, (中文(简体) ), `totalOne`, (中文(简体) ), `totalMany` (单位:千美元)`{n}` = 夜间) |
| `appendDatesToHref` | `append-dates-to-href` | `boolean` | `true` | 附录 `?check_in=…&check_out=…` 切入到卡片内 |
| `showGuests` | `show-guests` | `boolean` | `false` | 显示来宾步进; 更改它会重运行搜索 |
| `showRooms` | `show-rooms` | `boolean` | `false` | 显示一个“客人和房间”字段:成人和儿童每间,增加/去掉房间(最多 `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`。在狭长的屏幕上,表格会用被钉住的名称栏向后滚动.

```html
<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>
```

#### 属性/ 属性

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `rows` | `rows` (杰森语) | `PriceGridRow[]` | `[]` | `{ id, name, meta?, href? }` 显示顺序; `meta` 填入可选第二列 |
| `startDate` | `start-date` | `string` (国际标准化组织) | {\\fn华文楷体\\fs16\\1che0e0e0}今天 | 首个晚上显示; 夹住今天 |
| `days` | `days` | `number` | `7` | 一次显示的夜晚和箭的步(1-31) |
| `guests` | `guests` | `number` | `2` | 运入售出价格链接 |
| `currency` | `currency` | `string` | `'USD'` | ISO 4217 代码 |
| `locale` | `locale` | `string` | 浏览器 | 日期和价格格式 |
| `strings` | `strings` (杰森语) | `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-渐变范围选择。 两个月并肩在桌面上,一个在移动上.

```html
<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` 编号 : `checkOut` | `check-in` 编号 : `check-out` | `string` (国际标准化组织) | `''` | 选定范围;作为 JS 属性设置 |
| `horizonMonths` | `horizon-months` | `number` | `12` | 请求加载器提供的窗口 |
| `fetchAvailability` | \- (仅限财产) | `(fromISO, toISO) => Promise<AvailabilityDay[]>` | —— 说 | 因无结果而缺席的日期 |

输出 `dates-change` 每个选择.

### `<booking-card>`

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

```html
<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>
```

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `price` | `price` | `number` | `0` | 夜从率;每晚按此率计价 |
| `maxGuests` | `max-guests` | `number` | `4` | B. 宾客台阶上限 |
| `resortFeeRate` | `resort-fee-rate` | `number` | `0.03` | 费用作为小计的一小部分 |
| `ratingText` 编号 : `finePrint` | `rating-text` 编号 : `fine-print` | `string` | —— 说 | 页眉和页脚副本 |
| `conciergeTitle` 编号 : `conciergeSub` 编号 : `conciergeCtaLabel` 编号 : `conciergeCtaHref` | 弹匣 | `string` | 岛礁岛默认值 | 页脚块 |
| `checkIn` 编号 : `checkOut` 编号 : `guests` | `check-in` 编号 : `check-out` 编号 : `guests` | ISO / 编号 | `''` 编号 : `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),当来宾或外出者改变时,重新召回(被取消,最新回应获胜).

```html
<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`。 。 。 .

```html
<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>
```

#### 属性/ 属性

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

#### 活动

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

#### 他们

```css
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`。 。 。 .

```html
<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>
```

#### 属性/ 属性

| 名称 | 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `total` | `total` | `number` | `0` | 驱动提交按钮的金额标签; 设置为 JS 属性, 这样主机可以在额外更改后更新 |
| `currency` | `currency` | `string` | `'USD'` | 用于量标签 |
| `clientSecret` | `client-secret` | `string` | —— 说 | Strape Paying Intent 客户端秘密, 由主机创建服务器侧 |
| `stripePublishableKey` | `stripe-publishable-key` | `string` | —— 说 | 条形可公布的密钥 |
| `stripeAppearance` | `stripe-appearance` (杰森语) | `object` | `{}` | 传到 `stripe.elements({ appearance })` |
| `cancellationPolicy` | `cancellation-policy` | `string` | —— 说 | 显示在显示时的专栏; 省略以完全隐藏 |
| `locations` | `locations` (杰森语) | `{iso2, countryName, dialCode}[]` | 每个国家 | 两种选择(空取=每个国家,在浏览器的语言中命名)的出处 – 国家选项保留每个条目,电话编码选项由 `dialCode` |
| `defaultPhoneCode` | `default-phone-code` | `string` | `'+1'` | 选择两个电话编码的初始值 |
| `allowBookingForOther` | `allow-booking-for-other` | `boolean` | `true` | 隐藏切换和预订子窗体 `false` |
| `termsHref` | `terms-href` | `string` | `'/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本身交谈 |
| `strings` | `strings` (杰森语) | `object` | —— 说 | 标签覆盖 – 完整的密钥列表参见组件源 |

#### 活动

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

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

#### 他们

```css
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`围绕它.

```html
<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>
```

#### 属性/ 属性

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

#### 他们

```css
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的属性.

```html
<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 是什么？

可重新使用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 和提交记录判断。

