@ericchen1990/pano-view 3.9.0 → 4.0.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 +207 -95
- package/README.zh-CN.md +170 -75
- package/dist/data.cjs +64 -17
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.mts +24 -12
- package/dist/data.d.ts +24 -12
- package/dist/data.mjs +64 -17
- package/dist/data.mjs.map +1 -1
- package/dist/index.cjs +1998 -1031
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +162 -48
- package/dist/index.d.ts +162 -48
- package/dist/index.mjs +1789 -822
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +87 -74
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.d.mts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.mjs.map +1 -1
- package/docs/dialogs.md +52 -42
- package/docs/dialogs.zh-CN.md +52 -42
- package/package.json +1 -1
package/README.zh-CN.md
CHANGED
|
@@ -226,17 +226,18 @@ export function Tour() {
|
|
|
226
226
|
|
|
227
227
|
```tsx
|
|
228
228
|
import { useState } from "react";
|
|
229
|
-
import { PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
229
|
+
import { PanoOverlay, PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
230
230
|
import { ProjectPopupDialog } from "@ericchen1990/pano-view/ui";
|
|
231
231
|
import "@ericchen1990/pano-view/ui/styles.css";
|
|
232
232
|
|
|
233
233
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
234
234
|
const [popup, setPopup] = useState<ProjectPopupDefinition | null>(null);
|
|
235
|
-
return <PanoViewer
|
|
236
|
-
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
237
|
-
)}>
|
|
235
|
+
return <PanoViewer>
|
|
238
236
|
<ProjectScenes project={project} onPopupRequest={({ popup }) => setPopup(popup)}
|
|
239
237
|
onActiveSceneIdChange={() => setPopup(null)} />
|
|
238
|
+
<PanoOverlay>
|
|
239
|
+
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
240
|
+
</PanoOverlay>
|
|
240
241
|
</PanoViewer>;
|
|
241
242
|
}
|
|
242
243
|
```
|
|
@@ -254,12 +255,14 @@ export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
|
254
255
|
"maxScale": 4,
|
|
255
256
|
"iconSize": 28,
|
|
256
257
|
"presentation": {
|
|
257
|
-
"mode": "
|
|
258
|
-
"
|
|
258
|
+
"mode": "panel",
|
|
259
|
+
"anchor": {
|
|
260
|
+
"align": "bottom-left",
|
|
261
|
+
"axis": "horizontal",
|
|
262
|
+
"inset": 16
|
|
263
|
+
},
|
|
259
264
|
"width": 240,
|
|
260
|
-
"height": 180
|
|
261
|
-
"offsetX": 16,
|
|
262
|
-
"offsetY": 16
|
|
265
|
+
"height": 180
|
|
263
266
|
},
|
|
264
267
|
"controls": { "collapse": true, "zoom": true },
|
|
265
268
|
"sector": {
|
|
@@ -285,70 +288,70 @@ export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
|
285
288
|
|
|
286
289
|
`image` 是相对项目目录的路径或绝对 URL。`spots` 必填。点位 `id` 唯一,每个 `sceneId` 必须指向项目中的场景。`x`、`y` 是归一化图像坐标,范围 [0, 1],原点在左上角。`heading` 是查看器 yaw 为 0 时、从图像向上方向起算的顺时针角度;扇形实际旋转为 `heading + yaw`。多个点位可以指向同一场景,扇形画在第一个 `sceneId` 与当前场景相同的点位上。`icon` 可选;省略时使用内置标记。
|
|
287
290
|
|
|
288
|
-
`interaction` 为 `"fixed"`(默认)或 `"zoom"`。固定模式把图像适配进面板,忽略滚轮、拖拽平移和缩放按钮。缩放模式允许滚轮缩放,并在比例大于适配尺寸后拖拽平移。地图上的滚轮不会带动页面滚动。`maxScale` 是相对适配尺寸的最大倍数,至少为 1,默认 8。`dragMotion` 控制放大后的平移惯性与边界回弹(默认均开启)。设为 `false` 可同时关闭,或传 `{ inertia?: boolean; bounce?: boolean }` 分别开关。点位与朝向编辑仍是直接拖动。`controls.zoom` 仅在 `"zoom"` 时显示缩放按钮。`controls.collapse`
|
|
291
|
+
`interaction` 为 `"fixed"`(默认)或 `"zoom"`。固定模式把图像适配进面板,忽略滚轮、拖拽平移和缩放按钮。缩放模式允许滚轮缩放,并在比例大于适配尺寸后拖拽平移。地图上的滚轮不会带动页面滚动。`maxScale` 是相对适配尺寸的最大倍数,至少为 1,默认 8。`dragMotion` 控制放大后的平移惯性与边界回弹(默认均开启)。设为 `false` 可同时关闭,或传 `{ inertia?: boolean; bounce?: boolean }` 分别开关。点位与朝向编辑仍是直接拖动。`controls.zoom` 仅在 `"zoom"` 时显示缩放按钮。`controls.collapse` 显示边缘拉手,把面板滑出屏幕。`iconSize` 是标记边长,单位为 CSS 像素,默认 28。图像缩放时,图标和扇形保持屏幕尺寸。
|
|
289
292
|
|
|
290
293
|
`sector` 可选。`angle` 是开口角度,大于 0 且小于 360。`radius` 单位为 CSS 像素,不随图像缩放。`followFov` 用查看器视场角和地图面板宽高比换算水平视场,代替 `angle`。没有 `gradient` 时,填充为纯色 `color`(默认 `#ffffff`),不透明度为 `opacity`(默认 0.55)。`gradient.type` 为 `"radial"` 或 `"linear"`,至少两个色标。每个色标包含 [0, 1] 的 `offset`、`color`,以及可选 `opacity`。色标不透明度只描述渐隐形状。`sector.opacity` 在纯色和渐变两种模式下都缩放整块填充。`strokeColor`、`strokeWidth`、`strokeOpacity` 在宽度大于 0 时绘制描边,描边宽度保持 CSS 像素。
|
|
291
294
|
|
|
292
|
-
`presentation.mode` 为 `"
|
|
295
|
+
`presentation.mode` 为 `"panel"`(默认)或 `"dialog"`。两者都接受 `anchor`,以及 `width` 和 `height`。`anchor` 是场景菜单与地图共用的锚定:`align` 为 `top`、`bottom`、`left`、`right` 或四个角之一(默认 `bottom-left`);`axis` 为 `"horizontal"` 或 `"vertical"`,只在四角生效(默认 `"horizontal"`);`inset` 可以是数字、CSS 长度字符串,或 `{ top, right, bottom, left }`(默认 `16`)。计算时会加上安全区。面板默认 220×160。对话框模式放置一个打开按钮(未写宽高时为 48×48),并在查看器内部打开同一张地图。选中点位后关闭对话框。地图界面锚定在整个查看器 overlay 上,父页面滚动不会移动或改变它的尺寸。四边在给定主轴尺寸时沿该边居中。四角贴住相邻两边。收起时面板保持尺寸,沿外侧滑出:四边朝该边,四角在水平轴向时朝上或下,垂直轴向时朝左或右。拉手留在该边内侧。
|
|
293
296
|
|
|
294
|
-
文档保存默认值(`image`、`spots`、`sector`、`presentation`、`interaction`、`maxScale`、`dragMotion`、`controls`、`iconSize`)。挂载 `PanoMapOverlay` 时可用组件 props 覆盖本次展示的尺寸、位置和缩放,不必克隆整份 `map`。优先级为:内置默认值 → 地图文档 → `presentationOverride`(形状与 `map.presentation` 相同)→ 单独 props。点位几何与扇形样式仍写在文档里。`selectedSpotId` / `onSelectedSpotIdChange` 会向下传给内部 `PanoMap
|
|
297
|
+
文档保存默认值(`image`、`spots`、`sector`、`presentation`、`interaction`、`maxScale`、`dragMotion`、`controls`、`iconSize`)。挂载 `PanoMapOverlay` 时可用组件 props 覆盖本次展示的尺寸、位置和缩放,不必克隆整份 `map`。优先级为:内置默认值 → 地图文档 → `presentationOverride`(形状与 `map.presentation` 相同)→ 单独 props。点位几何与扇形样式仍写在文档里。`selectedSpotId` / `onSelectedSpotIdChange` 会向下传给内部 `PanoMap`。面板的收起状态由 overlay 内部管理;对话框模式与 `editorSurface` 会强制关掉边缘拉手。直接使用 `PanoMap` 时,`collapsed` 仍只是在宿主框内藏起图面。
|
|
295
298
|
|
|
296
299
|
#### 挂载 `PanoMapOverlay`
|
|
297
300
|
|
|
298
|
-
在 `
|
|
301
|
+
在 `PanoOverlay` 中挂载,并把交给 `ProjectScenes` 的同一个 `activeSceneId` 传进去。内置 `pano-map-*` 类名来自 `@ericchen1990/pano-view/styles.css`。overlay 从查看器读取 yaw 和视场角,不会自己切换场景:在 `onSelectScene` 里设置当前场景。相机状态来自查看器之外时传入 `view`;查看器外又不传 `view` 时,图面停在 yaw `0`、pitch `0`、fov `75`。`PanoMap` 只负责图面,由宿主自己安排外框。
|
|
299
302
|
|
|
300
|
-
`PanoMap` 还可受控选中与收起:`selectedSpotId` / `onSelectedSpotIdChange`(省略时内部选中,默认 `null`);`collapsed` / `onCollapsedChange
|
|
303
|
+
`PanoMap` 还可受控选中与收起:`selectedSpotId` / `onSelectedSpotIdChange`(省略时内部选中,默认 `null`);`collapsed` / `onCollapsedChange`(非受控初始为展开,并在宿主框内藏起图面);`renderChrome` 在图面之上渲染宿主控件。在 `PanoMapOverlay` 上,`showCollapseControl` 显示共用的边缘拉手。`className` / `style` 挂在表面根节点上。
|
|
301
304
|
|
|
302
305
|
```tsx
|
|
303
306
|
import { useState } from "react";
|
|
304
307
|
import {
|
|
305
|
-
PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
308
|
+
PanoOverlay, PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
306
309
|
} from "@ericchen1990/pano-view";
|
|
307
310
|
import "@ericchen1990/pano-view/styles.css";
|
|
308
311
|
|
|
309
312
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
310
313
|
const [activeSceneId, setActiveSceneId] = useState(project.initialSceneId);
|
|
311
314
|
const map = project.map;
|
|
312
|
-
return <PanoViewer style={{ height: 600 }}
|
|
313
|
-
<PanoMapOverlay
|
|
314
|
-
map={map}
|
|
315
|
-
activeSceneId={activeSceneId}
|
|
316
|
-
onSelectScene={setActiveSceneId}
|
|
317
|
-
/>
|
|
318
|
-
) : undefined}>
|
|
315
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
319
316
|
<ProjectScenes
|
|
320
317
|
project={project}
|
|
321
318
|
activeSceneId={activeSceneId}
|
|
322
319
|
onActiveSceneIdChange={setActiveSceneId}
|
|
323
320
|
/>
|
|
321
|
+
{map ? (
|
|
322
|
+
<PanoOverlay>
|
|
323
|
+
<PanoMapOverlay
|
|
324
|
+
map={map}
|
|
325
|
+
activeSceneId={activeSceneId}
|
|
326
|
+
onSelectScene={setActiveSceneId}
|
|
327
|
+
/>
|
|
328
|
+
</PanoOverlay>
|
|
329
|
+
) : null}
|
|
324
330
|
</PanoViewer>;
|
|
325
331
|
}
|
|
326
332
|
```
|
|
327
333
|
|
|
328
|
-
####
|
|
334
|
+
#### 尺寸、对齐与边距
|
|
329
335
|
|
|
330
|
-
- `presentation` — `"
|
|
331
|
-
- `
|
|
332
|
-
- `width` / `height` —
|
|
333
|
-
- `offsetX` / `offsetY` — 相对所选角落的边距,单位为 CSS 像素(默认 16)。
|
|
336
|
+
- `presentation` — `"panel"` | `"dialog"`(默认取自文档,否则为 `"panel"`)。
|
|
337
|
+
- `anchor` — 共用的 `PanoOverlayAnchor`。`align` 是八个位置之一(默认 `"bottom-left"`)。`axis` 用于四角。`inset` 默认 `16`。
|
|
338
|
+
- `width` / `height` — 面板尺寸,或对话框打开按钮尺寸,单位为 CSS 像素。面板默认 220×160;打开按钮默认 48×48。
|
|
334
339
|
|
|
335
340
|
```tsx
|
|
336
341
|
<PanoMapOverlay
|
|
337
342
|
map={map}
|
|
338
343
|
activeSceneId={activeSceneId}
|
|
339
344
|
onSelectScene={setActiveSceneId}
|
|
340
|
-
presentation="
|
|
341
|
-
|
|
345
|
+
presentation="panel"
|
|
346
|
+
anchor={{ align: "bottom-right", axis: "vertical", inset: 20 }}
|
|
342
347
|
width={280}
|
|
343
348
|
height={200}
|
|
344
|
-
offsetX={20}
|
|
345
|
-
offsetY={20}
|
|
346
349
|
/>
|
|
347
350
|
```
|
|
348
351
|
|
|
349
352
|
#### 对话框模式
|
|
350
353
|
|
|
351
|
-
|
|
354
|
+
对话框模式放置一个打开按钮,并在查看器内部打开地图。打开按钮尺寸使用 `width` / `height`,位置使用同一个 `anchor`。对话框本体使用另一组尺寸:
|
|
352
355
|
|
|
353
356
|
- `dialogWidth` / `dialogHeight` — 期望的对话框宽高,单位为 CSS 像素(默认 720×520)。两者都会按查看器可用区域夹紧,四周各留 16 像素边距。
|
|
354
357
|
- `openLabel` / `closeLabel` / `title` — 打开按钮无障碍名称、关闭按钮文案与对话框标题(默认分别为 `"Map"`、`"Close"`、`"Map"`)。
|
|
@@ -361,7 +364,7 @@ export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
|
361
364
|
activeSceneId={activeSceneId}
|
|
362
365
|
onSelectScene={setActiveSceneId}
|
|
363
366
|
presentation="dialog"
|
|
364
|
-
|
|
367
|
+
anchor={{ align: "bottom-left", inset: 16 }}
|
|
365
368
|
width={52}
|
|
366
369
|
height={52}
|
|
367
370
|
dialogWidth={640}
|
|
@@ -397,6 +400,36 @@ export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
|
397
400
|
/>
|
|
398
401
|
```
|
|
399
402
|
|
|
403
|
+
面板边缘拉手可用 `toggle` 换图标或改样式。对话框模式没有这条拉手。省略 `toggle` 时仍是默认箭头。`icon` 传元素(`<Icon />`),不要传组件类型。图标要随收起状态变化时传函数,参数为 `{ collapsed }`。`className` 追加到按钮上。`style` 覆盖背景、边框、颜色和尺寸;位置和 `transform` 由贴边布局负责,不要在 `style` 里改。默认箭头使用 `currentColor`,只改 `style.color` 就会变色。
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
<PanoMapOverlay
|
|
407
|
+
map={map}
|
|
408
|
+
activeSceneId={activeSceneId}
|
|
409
|
+
onSelectScene={setActiveSceneId}
|
|
410
|
+
showCollapseControl
|
|
411
|
+
toggle={{ style: { color: "#7dd3fc" } }}
|
|
412
|
+
/>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
<PanoMapOverlay
|
|
417
|
+
map={map}
|
|
418
|
+
activeSceneId={activeSceneId}
|
|
419
|
+
onSelectScene={setActiveSceneId}
|
|
420
|
+
showCollapseControl
|
|
421
|
+
toggle={{
|
|
422
|
+
className: "map-tab",
|
|
423
|
+
style: {
|
|
424
|
+
background: "#0c4a6e",
|
|
425
|
+
color: "#7dd3fc",
|
|
426
|
+
border: "1px solid #7dd3fc",
|
|
427
|
+
},
|
|
428
|
+
icon: ({ collapsed }) => (collapsed ? "+" : "–"),
|
|
429
|
+
}}
|
|
430
|
+
/>
|
|
431
|
+
```
|
|
432
|
+
|
|
400
433
|
#### 编辑模式
|
|
401
434
|
|
|
402
435
|
`mode="edit"` 用于编辑:设置 `placementSceneId` 后点击地图会新增点位,其 `heading` 取当前 yaw 的相反数,因此在该 yaw 下扇形指向图像上方;拖动点位改变位置,拖动扇形设置 `heading`。`onEditChange` 返回 `{ spots?, selectedSpotId? }`。访客播放保持默认的 `mode="view"`。`editorSurface` 会放大编辑面板;传入 `width` / `height` 可指定尺寸,否则按查看器比例计算。
|
|
@@ -652,14 +685,15 @@ export function Tour() {
|
|
|
652
685
|
### 事件与界面元素
|
|
653
686
|
|
|
654
687
|
- [`PanoEvents`](#panoevents) — 查看器级生命周期与交互回调
|
|
688
|
+
- [`PanoOverlay`](#dom-overlay-与弹窗) — 查看器平面上的 DOM 包裹,全屏时仍可见
|
|
655
689
|
- [`PanoContextMenu`](#panocontextmenu) — 右键重置 / 全屏菜单
|
|
656
|
-
- [`PanoSceneMenu`](#panoscenemenu) — 供 `
|
|
690
|
+
- [`PanoSceneMenu`](#panoscenemenu) — 供 `PanoOverlay` 使用的可滚动缩略图场景菜单
|
|
657
691
|
- [`PanoVideoControls`](#panovideo) — `PanoVideo` 的播放界面(默认挂载)
|
|
658
692
|
|
|
659
693
|
### 平面图
|
|
660
694
|
|
|
661
695
|
- [`PanoMap`](#平面图地图) — 平面图、场景点位,以及随水平角转动的扇形
|
|
662
|
-
- [`PanoMapOverlay`](#平面图地图) — 供 `
|
|
696
|
+
- [`PanoMapOverlay`](#平面图地图) — 供 `PanoOverlay` 使用的停靠面板或对话框按钮
|
|
663
697
|
|
|
664
698
|
### 热点
|
|
665
699
|
|
|
@@ -684,7 +718,25 @@ export function Tour() {
|
|
|
684
718
|
|
|
685
719
|
## DOM Overlay 与弹窗
|
|
686
720
|
|
|
687
|
-
普通 DOM
|
|
721
|
+
普通 DOM 界面优先使用 `PanoOverlay`,位于 Three.js 渲染树之外,挂在全屏根节点内的查看器平面上,因此全屏时仍可见。把它作为 `PanoViewer` 的直接子节点(或包在其下的 Fragment 里),与场景内容并列。`renderOverlay` 仍可用,适合回调式挂载:
|
|
722
|
+
|
|
723
|
+
```tsx
|
|
724
|
+
<PanoViewer style={{ height: 560 }}>
|
|
725
|
+
<Sphere src="/pano.jpg" />
|
|
726
|
+
<PanoOverlay>
|
|
727
|
+
<PanoSceneMenu items={items} activeId={id} onSelect={setId} />
|
|
728
|
+
<button
|
|
729
|
+
type="button"
|
|
730
|
+
style={{ position: "absolute", right: 16, bottom: 16, pointerEvents: "auto" }}
|
|
731
|
+
onClick={() => void viewer.toggleFullscreen()}
|
|
732
|
+
>
|
|
733
|
+
Fullscreen
|
|
734
|
+
</button>
|
|
735
|
+
</PanoOverlay>
|
|
736
|
+
</PanoViewer>
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
该层已是 `position: absolute; inset: 0; pointer-events: none`。子节点自行定位,可点击节点设置 `pointerEvents: "auto"`。同时使用时,`renderOverlay` 先渲染,其后按出现顺序渲染各个 `PanoOverlay`。`usePanoOverlay()` 必须在 `PanoOverlay`(或 `renderOverlay`)子树内调用,返回 `portalContainer`、`size`(viewer 宽高)、`visibleRect`(可见矩形)与 `isFullscreen`。`usePanoModal(open)` 在 `open === true` 时占用该 viewer 的模态锁(暂停输入与自动旋转、背景 inert、关闭右键菜单);不负责焦点陷阱。`PanoOverlayPortalProvider` 接收必填的 `container` 与 `children`,只替换后代读到的 `portalContainer`;`size` / `visibleRect` 仍是整个 viewer。字段含义、引用计数与第三方 Dialog 接线见独立的 **[弹窗使用与自定义样式指南](./docs/dialogs.zh-CN.md)**。可选 `/ui` 入口提供 `PanoDialog` 与组成组件。指南随 npm 包分发;在本仓库运行 `pnpm dev`,打开 `/overlays` 可体验示例。
|
|
688
740
|
|
|
689
741
|
### 默认弹窗
|
|
690
742
|
|
|
@@ -769,7 +821,7 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
|
|
|
769
821
|
|
|
770
822
|
**场景**(需 `<Scenes />`)— `setScene(id)`、`nextScene`、`previousScene`、`getActiveSceneId`、`getSceneIds`、`isSceneTransitioning`。`Scenes` 支持 `defaultActiveSceneId`(非受控)或 `activeSceneId`(受控)。受控模式下命令式调用会触发 `onActiveSceneIdChange`。
|
|
771
823
|
|
|
772
|
-
**WebVR**(需 `<WebVR />`)— `enterVR`、`exitVR`、`toggleVR`、`isVRAvailable`、`isVREnabled`、`getVRMode`、`requestVRPermission
|
|
824
|
+
**WebVR**(需 `<WebVR />`)— `enterVR`、`exitVR`、`toggleVR`、`isVRAvailable`、`isVREnabled`、`getVRMode`、`getVRState`、`subscribeVR`、`requestVRPermission`。用 `subscribeVR` 配合 `useSyncExternalStore` 做响应式进入 / 退出按钮;内置 chrome 不再渲染「进入 VR」。
|
|
773
825
|
|
|
774
826
|
**视频**(需 `<PanoVideo />`)— `getVideo` 返回 `PanoVideoController`,`subscribeVideo` 订阅视频控制器与播放状态变更,另有快捷方法 `playVideo`、`pauseVideo`、`toggleVideo`、`seekVideo`、`setVideoVolume`、`setVideoMuted`、`toggleVideoMuted`。
|
|
775
827
|
|
|
@@ -785,7 +837,7 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
|
|
|
785
837
|
- `cursors` — `true` / 省略使用 grab / grabbing / pointer / move;`false` 不改画布光标;对象可覆盖四个状态。
|
|
786
838
|
- `contextMenu` — `true` / 省略挂载「重置视图 + 全屏」;`false` 不挂默认菜单;对象可改外观或 `items` / `prepend` / `append`。详见 [PanoContextMenu](#panocontextmenu)。
|
|
787
839
|
- `mediaActivation` — 有播放意图的子组件时默认开启;`false` 关闭自动激活。
|
|
788
|
-
- `renderOverlay` —
|
|
840
|
+
- `renderOverlay` — 可选的回调式 DOM UI,在画布外、全屏内渲染;回调收到 `portalContainer`、`size`、`visibleRect`、`isFullscreen`。优先用子组件 `<PanoOverlay>`。
|
|
789
841
|
- `onViewChange` — 参数为 `{ yaw, pitch, fov }`(度)。
|
|
790
842
|
- `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` — 参数为全景指针事件:`position`(yaw / pitch)、`nativeEvent`、`button`、`buttons`、`clientX`、`clientY`,以及可选的 `pointerId`。
|
|
791
843
|
- 其余标准 `div` 属性(如 `className`、`style`、`id`)可原样传入;类型已去掉冲突的 `onChange` 与 `contextMenu`。
|
|
@@ -883,26 +935,30 @@ function SceneExample() {
|
|
|
883
935
|
|
|
884
936
|
### WebVR
|
|
885
937
|
|
|
886
|
-
`enterVR` 与 `requestVRPermission`
|
|
938
|
+
`enterVR` 与 `requestVRPermission` 须在用户手势中调用。通过 `getVRState` /
|
|
939
|
+
`subscribeVR` 由宿主 UI 驱动进入;会话 chrome 只显示退出 VR 与 MobileVR 设置
|
|
940
|
+
(空闲数秒后淡出)。
|
|
887
941
|
|
|
888
942
|
```tsx
|
|
943
|
+
import { useSyncExternalStore } from "react";
|
|
889
944
|
import { PanoViewer, Sphere, WebVR, usePanoViewer } from "@ericchen1990/pano-view";
|
|
890
945
|
|
|
891
946
|
function VRExample() {
|
|
892
947
|
const viewer = usePanoViewer();
|
|
948
|
+
const vr = useSyncExternalStore(
|
|
949
|
+
viewer.subscribeVR,
|
|
950
|
+
viewer.getVRState,
|
|
951
|
+
viewer.getVRState,
|
|
952
|
+
);
|
|
893
953
|
|
|
894
954
|
return (
|
|
895
955
|
<>
|
|
896
956
|
<button
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
await viewer.enterVR();
|
|
900
|
-
}}
|
|
957
|
+
disabled={!vr.available}
|
|
958
|
+
onClick={() => void (vr.enabled ? viewer.exitVR() : viewer.enterVR())}
|
|
901
959
|
>
|
|
902
|
-
进入 VR
|
|
960
|
+
{vr.enabled ? "退出 VR" : "进入 VR"}
|
|
903
961
|
</button>
|
|
904
|
-
<button onClick={() => void viewer.exitVR()}>退出 VR</button>
|
|
905
|
-
<button onClick={() => console.log(viewer.getVRMode())}>当前模式?</button>
|
|
906
962
|
<PanoViewer ref={viewer.ref} style={{ height: 560 }}>
|
|
907
963
|
<WebVR />
|
|
908
964
|
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
@@ -1348,7 +1404,7 @@ const scenes: Scene[] = [
|
|
|
1348
1404
|
|
|
1349
1405
|
## PanoSceneMenu
|
|
1350
1406
|
|
|
1351
|
-
`PanoSceneMenu` 是可滚动的缩略图场景菜单。通过 `
|
|
1407
|
+
`PanoSceneMenu` 是可滚动的缩略图场景菜单。通过 `PanoOverlay` 挂载,这样在普通布局和全屏里都会留在 viewer 内。默认布局需导入 `@ericchen1990/pano-view/styles.css`。
|
|
1352
1408
|
|
|
1353
1409
|
菜单由宿主驱动:传入 `items`、`activeId`、`onSelect`。它不读取 `Scenes` 状态,也不使用场景的 `previewUrl`。立方体场景的 `previewUrl` 通常是 1×6 预览条,不适合直接当菜单缩略图;请改用独立缩略图(转换器输出可优先用 `previews/200.webp` 这类小图)。
|
|
1354
1410
|
|
|
@@ -1359,6 +1415,7 @@ const scenes: Scene[] = [
|
|
|
1359
1415
|
```tsx
|
|
1360
1416
|
import { useState } from "react";
|
|
1361
1417
|
import {
|
|
1418
|
+
PanoOverlay,
|
|
1362
1419
|
PanoSceneMenu,
|
|
1363
1420
|
PanoViewer,
|
|
1364
1421
|
Scenes,
|
|
@@ -1383,21 +1440,19 @@ export function Tour() {
|
|
|
1383
1440
|
const [activeSceneId, setActiveSceneId] = useState("lobby");
|
|
1384
1441
|
|
|
1385
1442
|
return (
|
|
1386
|
-
<PanoViewer
|
|
1387
|
-
style={{ height: 560 }}
|
|
1388
|
-
renderOverlay={() => (
|
|
1389
|
-
<PanoSceneMenu
|
|
1390
|
-
activeId={activeSceneId}
|
|
1391
|
-
items={menuItems}
|
|
1392
|
-
onSelect={setActiveSceneId}
|
|
1393
|
-
/>
|
|
1394
|
-
)}
|
|
1395
|
-
>
|
|
1443
|
+
<PanoViewer style={{ height: 560 }}>
|
|
1396
1444
|
<Scenes
|
|
1397
1445
|
scenes={scenes}
|
|
1398
1446
|
activeSceneId={activeSceneId}
|
|
1399
1447
|
onActiveSceneIdChange={setActiveSceneId}
|
|
1400
1448
|
/>
|
|
1449
|
+
<PanoOverlay>
|
|
1450
|
+
<PanoSceneMenu
|
|
1451
|
+
activeId={activeSceneId}
|
|
1452
|
+
items={menuItems}
|
|
1453
|
+
onSelect={setActiveSceneId}
|
|
1454
|
+
/>
|
|
1455
|
+
</PanoOverlay>
|
|
1401
1456
|
</PanoViewer>
|
|
1402
1457
|
);
|
|
1403
1458
|
}
|
|
@@ -1405,19 +1460,19 @@ export function Tour() {
|
|
|
1405
1460
|
|
|
1406
1461
|
每项需要 `id` 与 `thumbnailUrl`。可选 `title` 显示在缩略图底部标签和控制提示上;省略时用 `id`。当前项带叠在缩略图上的选择边框和 `data-active="true"`。场景名默认叠在缩略图底部的暗色半透明条上,可用 `appearance.labelAlign` 改为居中或右对齐。
|
|
1407
1462
|
|
|
1408
|
-
###
|
|
1463
|
+
### 对齐、边距与尺寸
|
|
1464
|
+
|
|
1465
|
+
`anchor` 是场景菜单和平面图共用的 `PanoOverlayAnchor`。
|
|
1409
1466
|
|
|
1410
|
-
- `
|
|
1411
|
-
- `
|
|
1412
|
-
- `inset` — 与 viewer 边缘的间隙(默认 `12`)。数字按像素;字符串可带单位(如 `"1rem"`);对象可分别设置 `top` / `right` / `bottom` / `left
|
|
1413
|
-
- `width` / `height` — 菜单宽高。数字按像素;字符串会原样写入 CSS,因此可用 `%`、`rem`、`calc()`、`min()`、`max()`、`clamp()`
|
|
1467
|
+
- `anchor.align` — `"top"` | `"bottom"` | `"left"` | `"right"` | `"top-left"` | `"top-right"` | `"bottom-left"` | `"bottom-right"`(默认 `"bottom"`)。上下是横条,左右是竖条。四角再看 `anchor.axis`。
|
|
1468
|
+
- `anchor.axis` — `"horizontal"` | `"vertical"`。四边忽略它。四角默认 `"horizontal"`:水平条带朝上或下收起,垂直条带朝左或右收起。
|
|
1469
|
+
- `anchor.inset` — 与 viewer 边缘的间隙(默认 `12`)。数字按像素;字符串可带单位(如 `"1rem"`);对象可分别设置 `top` / `right` / `bottom` / `left`。计算时会加上安全区。
|
|
1470
|
+
- `width` / `height` — 菜单宽高。数字按像素;字符串会原样写入 CSS,因此可用 `%`、`rem`、`calc()`、`min()`、`max()`、`clamp()` 等。四边给了主轴尺寸就沿该边居中,没给就在两侧 inset 之间撑满。四角贴住相邻两边,没给尺寸时收缩到内容。较短的列表靠向角落,在边上则居中。
|
|
1414
1471
|
- `dragMotion` — 滚动惯性与边界回弹。省略则两者都开(自定义指针滚动,桌面也可拖)。设为 `false` 使用原生 overflow 滚动。可传 `{ inertia?: boolean; bounce?: boolean }` 分别开关。短按仍会选中;拖过数像素后会抑制 click。
|
|
1415
1472
|
|
|
1416
1473
|
```tsx
|
|
1417
1474
|
<PanoSceneMenu
|
|
1418
|
-
|
|
1419
|
-
align="center"
|
|
1420
|
-
inset={16}
|
|
1475
|
+
anchor={{ align: "bottom", inset: 16 }}
|
|
1421
1476
|
width="min(720px, 100%)"
|
|
1422
1477
|
items={menuItems}
|
|
1423
1478
|
activeId={activeSceneId}
|
|
@@ -1425,9 +1480,11 @@ export function Tour() {
|
|
|
1425
1480
|
/>
|
|
1426
1481
|
|
|
1427
1482
|
<PanoSceneMenu
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1483
|
+
anchor={{
|
|
1484
|
+
align: "top-left",
|
|
1485
|
+
axis: "vertical",
|
|
1486
|
+
inset: { top: 24, right: 12, bottom: 24, left: 12 },
|
|
1487
|
+
}}
|
|
1431
1488
|
height="70%"
|
|
1432
1489
|
width={160}
|
|
1433
1490
|
items={menuItems}
|
|
@@ -1442,7 +1499,7 @@ export function Tour() {
|
|
|
1442
1499
|
|
|
1443
1500
|
### 展开收起与箭头
|
|
1444
1501
|
|
|
1445
|
-
默认始终展开。设 `collapsible`
|
|
1502
|
+
默认始终展开。设 `collapsible` 后会出现开关,位于收起边的内侧正中。收起时列表按自身尺寸加上该边 inset 滑出屏幕,展开按钮留在查看器边缘:
|
|
1446
1503
|
|
|
1447
1504
|
```tsx
|
|
1448
1505
|
<PanoSceneMenu
|
|
@@ -1471,7 +1528,7 @@ export function Tour() {
|
|
|
1471
1528
|
showArrows
|
|
1472
1529
|
loop
|
|
1473
1530
|
collapsible
|
|
1474
|
-
|
|
1531
|
+
anchor={{ align: "bottom" }}
|
|
1475
1532
|
width="min(720px, 100%)"
|
|
1476
1533
|
items={menuItems}
|
|
1477
1534
|
activeId={activeSceneId}
|
|
@@ -1518,6 +1575,36 @@ export function Tour() {
|
|
|
1518
1575
|
|
|
1519
1576
|
根节点还带 `data-placement`、`data-align`、`data-orientation`(`horizontal` | `vertical`)、`data-expanded`、`data-collapsible`;当前项带 `data-active="true"`。
|
|
1520
1577
|
|
|
1578
|
+
`collapsible` 时,边缘拉手可用 `toggle` 换图标或改样式。省略 `toggle` 时仍是默认箭头。`icon` 传元素(`<Icon />`),不要传组件类型。图标要随收起状态变化时传函数,参数为 `{ collapsed }`(菜单展开时 `collapsed` 为 `false`)。`className` 追加到按钮上。`style` 覆盖背景、边框、颜色和尺寸;位置和 `transform` 由贴边布局负责,不要在 `style` 里改。默认箭头使用 `currentColor`,只改 `style.color` 就会变色。
|
|
1579
|
+
|
|
1580
|
+
```tsx
|
|
1581
|
+
<PanoSceneMenu
|
|
1582
|
+
collapsible
|
|
1583
|
+
toggle={{ style: { color: "#7dd3fc" } }}
|
|
1584
|
+
items={menuItems}
|
|
1585
|
+
activeId={activeSceneId}
|
|
1586
|
+
onSelect={setActiveSceneId}
|
|
1587
|
+
/>
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
```tsx
|
|
1591
|
+
<PanoSceneMenu
|
|
1592
|
+
collapsible
|
|
1593
|
+
toggle={{
|
|
1594
|
+
className: "scene-tab",
|
|
1595
|
+
style: {
|
|
1596
|
+
background: "#0c4a6e",
|
|
1597
|
+
color: "#7dd3fc",
|
|
1598
|
+
border: "1px solid #7dd3fc",
|
|
1599
|
+
},
|
|
1600
|
+
icon: ({ collapsed }) => (collapsed ? "+" : "–"),
|
|
1601
|
+
}}
|
|
1602
|
+
items={menuItems}
|
|
1603
|
+
activeId={activeSceneId}
|
|
1604
|
+
onSelect={setActiveSceneId}
|
|
1605
|
+
/>
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1521
1608
|
## PanoFilter
|
|
1522
1609
|
|
|
1523
1610
|
在 `PanoViewer` 内渲染 `PanoFilter` 可对全景源(`Sphere`、`Tile`、`SphereTile`、`PanoVideo`)调色或风格化。内置预设仅作用于全景材质;`colorMatrix` 默认仅作用于全景,设 `colorMatrixScope="all"` 可同时作用于热点纹理。tooltip、视频控件与 iframe 等 DOM 叠加层不受影响。WebXR / MobileVR 使用相同的全景着色器,因此继承相同的滤镜外观。
|
|
@@ -1706,7 +1793,7 @@ export function GyroExample() {
|
|
|
1706
1793
|
|
|
1707
1794
|
## WebVR
|
|
1708
1795
|
|
|
1709
|
-
`WebVR` 为最近的 `PanoViewer` 增加沉浸式观看。支持的头戴设备优先使用 `immersive-vr` WebXR 会话。无 WebXR 的手机使用设备方向的立体分屏;桌面可提供鼠标驱动的模拟 VR
|
|
1796
|
+
`WebVR` 为最近的 `PanoViewer` 增加沉浸式观看。支持的头戴设备优先使用 `immersive-vr` WebXR 会话。无 WebXR 的手机使用设备方向的立体分屏;桌面可提供鼠标驱动的模拟 VR 预览。进入 VR 由宿主通过查看器句柄触发;内置 chrome 仅在会话中显示退出 VR 与 MobileVR 设置(空闲后淡出)。
|
|
1710
1797
|
|
|
1711
1798
|
```tsx
|
|
1712
1799
|
import { useRef } from "react";
|
|
@@ -1735,25 +1822,33 @@ export function WebVRExample() {
|
|
|
1735
1822
|
}
|
|
1736
1823
|
```
|
|
1737
1824
|
|
|
1738
|
-
句柄暴露 `enterVR()`、`exitVR()`、`toggleVR()`、`isAvailable()`、`isEnabled()`、`getMode()` 与 `
|
|
1825
|
+
句柄暴露 `enterVR()`、`exitVR()`、`toggleVR()`、`isAvailable()`、`isEnabled()`、`getMode()`、`getState()`、`subscribe()`、`requestPermission()` 与 `resetSettings()`。模式为 `"webxr"`、`"mobilevr"`、`"fake"`。`profile` 接受 `"none"`、`"cardboard-v1"`、`"cardboard-v2"`、`"gear-vr"`、`"daydream"`,或自定义 `{ fov, ipdMm, k1, k2, screenToLensMm?, trayDistanceMm? }` 镜头配置。在已知物理屏幕尺寸时,MobileVR 会用瞳距与可选的托盘到镜片高度(`trayDistanceMm`;Cardboard V1 为 `35`)放置每只眼睛的光轴;`0` 或省略则保持垂直方向居中。当 `screensize` 为 `"auto"` 时,MobileVR 会加载 Cardboard Device Parameter Database(`databaseUrl`,默认 Google 公开 DPDB)估算对角线英寸;传 `""` 或 `null` 可跳过请求。无法识别时触发 `onUnknownDevice`,并继续使用镜头预设 FOV(尺寸未知前不做托盘偏移)。MobileVR 设置值会版本化并保存在当前源的 `localStorage` 中;`resetSettings()` 会清除该存储并恢复 props 或内置默认(Setup 面板也有 Reset)。VR 中中心准星显示十字;注视可交互热点时绘制顺时针填充环,经 `cursorDwellMs`(默认 1500)后触发热点点击。设 `cursorDwellMs={0}` 可保留准星但不自动点击。模拟桌面 VR(`fake` 模式)默认请求指针锁定(`mousePointerLock`),隐藏光标并用鼠标环视;若浏览器释放锁定需再次点击视图。回退会话通过 Screen Wake Lock API 或在不支持时的循环隐藏视频保持屏幕常亮(`wakelock`)。
|
|
1739
1826
|
|
|
1740
1827
|
WebXR 需要 HTTPS 与用户手势。移动端传感器访问也需要 HTTPS,iOS 可能需显式权限提示。嵌入查看器需要适当的 `xr-spatial-tracking`、`gyroscope`、`accelerometer` 权限。iframe 热点、tooltip、视频控件等 DOM 叠加层在沉浸式头戴会话中不可见;基于 mesh 的热点仍可通过默认 WebXR 控制器射线交互。
|
|
1741
1828
|
|
|
1742
1829
|
### 属性
|
|
1743
1830
|
|
|
1744
|
-
- `chrome` — 默认 `true`
|
|
1831
|
+
- `chrome` — 默认 `true` 挂载会话内退出 / 设置界面(不含进入 VR 按钮)。也可传外观对象:`background`(默认 `rgba(16, 24, 32, 0.78)`)、`color`(`#ffffff`)、`accent`(`#7dd3fc`)、`borderRadius`(`6`)、`fontSize`(`14`)、可选 `setupTitle`。会话按钮约 3 秒无操作后淡出,与查看器的指针 / 滚轮 / 键盘交互后再次显示。
|
|
1745
1832
|
- `mobileVr` — 默认 `true`。
|
|
1746
1833
|
- `desktopSupport` — 默认 `false`(与 `Gyro` 的同名 prop 独立)。
|
|
1747
1834
|
- `fakeSupport` — 默认 `true`,允许鼠标驱动的桌面模拟 VR。
|
|
1748
1835
|
- `screensize` — `"auto"`(出厂 / 省略时的存储默认)或英寸数;数字会被夹到 **3–20** 并保留一位小数。
|
|
1749
|
-
- `
|
|
1836
|
+
- `databaseUrl` — `"auto"` 屏幕尺寸时使用的 Cardboard DPDB URL。默认 `https://storage.googleapis.com/cardboard-dpdb/dpdb.json`。传 `""` 或 `null` 可关闭在线查询。
|
|
1837
|
+
- `profile` — 省略时用已存设置,出厂为 `"cardboard-v1"`。也可传 `"none"`、`"cardboard-v2"`、`"gear-vr"`、`"daydream"`,或自定义 `{ fov, ipdMm, k1, k2, screenToLensMm?, trayDistanceMm?, label?, id? }`。`trayDistanceMm` 为头显托盘底边到镜片中心的毫米距离;`0` / 省略时垂直光轴保持屏幕中心。
|
|
1838
|
+
- `absolute` — 默认 `false`。MobileVR 使用罗盘绝对朝向(对 WebXR 无效)。
|
|
1839
|
+
- `north` — 默认 `0`。启用 `absolute` 时,全景中代表北方的 yaw。
|
|
1840
|
+
- `friction` — 默认 `0`(范围 `0–0.99`)。MobileVR 陀螺仪平滑;正常观看保持 `0`。
|
|
1841
|
+
- `touchSupport` — 默认 `false`。为 `true` 时允许水平拖动修正 MobileVR 朝向(`horizontaloffset`)。默认关闭,因为镜筒托盘可能误触。
|
|
1842
|
+
- `oversampling` — 默认 `1`,夹到 **0.5–2**。仅放大 MobileVR 畸变离屏目标(视口不变)。WebXR 则在会话开始前设置 framebuffer 缩放(会话中途不可改)。
|
|
1843
|
+
- `foveation` — 可选 WebXR 固定注视点渲染,范围 **0–1**。省略则用设备默认;`0` 为全分辨率,`1` 为边缘最大降采样。
|
|
1844
|
+
- `frameRate` — 可选 WebXR 目标刷新率:`"low" | "mid" | "high"` 或具体 Hz(取最接近的支持值)。省略则保持 store 默认(`"high"`)。
|
|
1750
1845
|
- `cursor` — 默认 `true`;`false` 关闭中心准星。
|
|
1751
1846
|
- `cursorDwellMs` — 默认 `1500`;`0` 不自动点击。
|
|
1752
1847
|
- `wakelock` / `mousePointerLock` — 默认均为 `true`。
|
|
1753
1848
|
- `onAvailable` / `onUnavailable` — VR 能力变化。
|
|
1754
1849
|
- `onEnterVR` / `onExitVR` — 参数为模式 `"webxr" | "mobilevr" | "fake"`。
|
|
1755
1850
|
- `onDenied` — 权限或会话被拒绝,参数为可选 `error`。
|
|
1756
|
-
- `onUnknownDevice` —
|
|
1851
|
+
- `onUnknownDevice` — `"auto"` 屏幕尺寸无法从 DPDB 解析时(URL 关闭、无匹配或请求失败)。
|
|
1757
1852
|
|
|
1758
1853
|
## 鼠标、触控与键盘控件
|
|
1759
1854
|
|
package/dist/data.cjs
CHANGED
|
@@ -846,13 +846,56 @@ function resolveHotspotAssets(hotspot, baseUrl, sceneId, resolver) {
|
|
|
846
846
|
return result;
|
|
847
847
|
}
|
|
848
848
|
|
|
849
|
-
// src/
|
|
850
|
-
var
|
|
849
|
+
// src/overlay/align.ts
|
|
850
|
+
var PANO_OVERLAY_ALIGNS = [
|
|
851
|
+
"top",
|
|
852
|
+
"bottom",
|
|
853
|
+
"left",
|
|
854
|
+
"right",
|
|
851
855
|
"top-left",
|
|
852
856
|
"top-right",
|
|
853
857
|
"bottom-left",
|
|
854
858
|
"bottom-right"
|
|
855
|
-
]
|
|
859
|
+
];
|
|
860
|
+
var ALIGN_LIST = PANO_OVERLAY_ALIGNS.join(", ");
|
|
861
|
+
function isRecord2(value) {
|
|
862
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
863
|
+
}
|
|
864
|
+
function parseInsetLength(value, field) {
|
|
865
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
866
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
867
|
+
throw new Error(`${field}: expected a finite number or a nonempty string`);
|
|
868
|
+
}
|
|
869
|
+
function parseInset(value, field) {
|
|
870
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
871
|
+
return parseInsetLength(value, field);
|
|
872
|
+
}
|
|
873
|
+
if (!isRecord2(value)) throw new Error(`${field}: expected a number, string, or object`);
|
|
874
|
+
const sides = ["top", "right", "bottom", "left"];
|
|
875
|
+
const inset = {};
|
|
876
|
+
for (const side of sides) {
|
|
877
|
+
if (value[side] === void 0) continue;
|
|
878
|
+
inset[side] = parseInsetLength(value[side], `${field}.${side}`);
|
|
879
|
+
}
|
|
880
|
+
return inset;
|
|
881
|
+
}
|
|
882
|
+
function parseOverlayAnchor(value, field) {
|
|
883
|
+
if (!isRecord2(value)) throw new Error(`${field}: expected an object`);
|
|
884
|
+
const align = value.align === void 0 ? void 0 : typeof value.align === "string" && PANO_OVERLAY_ALIGNS.includes(value.align) ? value.align : (() => {
|
|
885
|
+
throw new Error(`${field}.align: expected ${ALIGN_LIST}`);
|
|
886
|
+
})();
|
|
887
|
+
const axis = value.axis === void 0 ? void 0 : value.axis === "horizontal" || value.axis === "vertical" ? value.axis : (() => {
|
|
888
|
+
throw new Error(`${field}.axis: expected "horizontal" or "vertical"`);
|
|
889
|
+
})();
|
|
890
|
+
const inset = value.inset === void 0 ? void 0 : parseInset(value.inset, `${field}.inset`);
|
|
891
|
+
return {
|
|
892
|
+
...align === void 0 ? {} : { align },
|
|
893
|
+
...axis === void 0 ? {} : { axis },
|
|
894
|
+
...inset === void 0 ? {} : { inset }
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/data/map.ts
|
|
856
899
|
function optionalNumber(record, key, options = {}) {
|
|
857
900
|
const value = record[key];
|
|
858
901
|
if (value === void 0) return void 0;
|
|
@@ -930,25 +973,29 @@ function parseSector(value, field) {
|
|
|
930
973
|
function parsePresentation(value, field) {
|
|
931
974
|
if (!isRecord(value)) throw new Error(`${field}: expected an object`);
|
|
932
975
|
const mode = value.mode;
|
|
933
|
-
if (mode
|
|
934
|
-
throw new Error(`${field}.mode: expected "
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
throw new Error(
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
976
|
+
if (mode === "docked") {
|
|
977
|
+
throw new Error(`${field}.mode: "docked" was removed; expected "panel" or "dialog"`);
|
|
978
|
+
}
|
|
979
|
+
if (mode !== "panel" && mode !== "dialog") {
|
|
980
|
+
throw new Error(`${field}.mode: expected "panel" or "dialog"`);
|
|
981
|
+
}
|
|
982
|
+
if ("corner" in value) {
|
|
983
|
+
throw new Error(`${field}.corner: use anchor.align`);
|
|
984
|
+
}
|
|
985
|
+
if ("offsetX" in value) {
|
|
986
|
+
throw new Error(`${field}.offsetX: use anchor.inset`);
|
|
987
|
+
}
|
|
988
|
+
if ("offsetY" in value) {
|
|
989
|
+
throw new Error(`${field}.offsetY: use anchor.inset`);
|
|
990
|
+
}
|
|
991
|
+
const anchor = value.anchor === void 0 ? void 0 : parseOverlayAnchor(value.anchor, `${field}.anchor`);
|
|
941
992
|
const width = optionalNumber(value, "width", { exclusiveMin: 0 });
|
|
942
993
|
const height = optionalNumber(value, "height", { exclusiveMin: 0 });
|
|
943
|
-
const offsetX = optionalNumber(value, "offsetX", { min: 0 });
|
|
944
|
-
const offsetY = optionalNumber(value, "offsetY", { min: 0 });
|
|
945
994
|
return {
|
|
946
995
|
mode,
|
|
947
|
-
...
|
|
996
|
+
...anchor === void 0 ? {} : { anchor },
|
|
948
997
|
...width === void 0 ? {} : { width },
|
|
949
|
-
...height === void 0 ? {} : { height }
|
|
950
|
-
...offsetX === void 0 ? {} : { offsetX },
|
|
951
|
-
...offsetY === void 0 ? {} : { offsetY }
|
|
998
|
+
...height === void 0 ? {} : { height }
|
|
952
999
|
};
|
|
953
1000
|
}
|
|
954
1001
|
function parseOptionalBoolean(value, field) {
|