@wcstack/fullscreen 1.16.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.ja.md +131 -0
- package/README.md +131 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +1 -0
- package/dist/index.d.ts +180 -0
- package/dist/index.esm.js +392 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.esm.min.js +2 -0
- package/dist/index.esm.min.js.map +1 -0
- package/package.json +70 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @wcstack/fullscreen
|
|
2
|
+
|
|
3
|
+
`@wcstack/fullscreen` は wcstack エコシステム向けのヘッドレスな Fullscreen API コンポーネントです。
|
|
4
|
+
|
|
5
|
+
視覚的な UI ウィジェットではありません。
|
|
6
|
+
**制御ノード**です。大半の wcstack IO ノードは自分自身を操作対象としますが、`<wcs-fullscreen>` は `@wcstack/intersection` が自分自身ではなく参照先の要素を観測するのと同じように、`target` で指し示した**参照先の要素**に対して `requestFullscreen()` / `exitFullscreen()` を実行します。
|
|
7
|
+
|
|
8
|
+
`@wcstack/state` と組み合わせると、`<wcs-fullscreen>` はパス契約で直接バインドできます:
|
|
9
|
+
|
|
10
|
+
- **入力サーフェス**: `target`(操作対象の要素。下記参照)
|
|
11
|
+
- **出力 state サーフェス**: `active`、`error`
|
|
12
|
+
- **コマンド**: `requestFullscreen()`、`exitFullscreen()`
|
|
13
|
+
|
|
14
|
+
`@wcstack/fullscreen` は [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md)(Core / Shell / Binding Contract)アーキテクチャに従います:
|
|
15
|
+
|
|
16
|
+
- **Core**(`FullscreenCore`)が Fullscreen API を操作し、`document` の `fullscreenchange` イベントを追従
|
|
17
|
+
- **Shell**(`<wcs-fullscreen target="...">`)が `target` を DOM 要素へ解決し、Core の state を DOM ライフサイクルに接続
|
|
18
|
+
- **Binding Contract**(`static wcBindable`)が観測可能な `active` プロパティと `requestFullscreen`/`exitFullscreen` コマンドを宣言
|
|
19
|
+
|
|
20
|
+
## なぜ存在するか — 操作対象は「タグ自身」ではなく「参照先」
|
|
21
|
+
|
|
22
|
+
`Element.requestFullscreen()` はfullscreen化したい要素(画像・動画・カードUIなど)に対するメソッドであり、`<wcs-fullscreen>` 自身に対するものではありません。そのため本タグは非表示の制御要素(`display` はターゲット解決モードに応じて決まります — 下表参照)として存在し、`<wcs-intersect>` と全く同じ規則で `target` 属性を介して別の要素を指し示します:
|
|
23
|
+
|
|
24
|
+
| `target` | 操作対象 | display | 典型的な用途 |
|
|
25
|
+
| --------------------- | -------------------------- | ------------ | ---------------------------- |
|
|
26
|
+
| 省略 | 最初の子要素 | `contents` | ギャラリー画像/動画をラップ |
|
|
27
|
+
| `"#hero"` / セレクタ | マッチした要素 | `none` | 離れた要素を指し示す |
|
|
28
|
+
| `"self"` | 自分自身 | `block` | ラッパー自体をfullscreen化 |
|
|
29
|
+
|
|
30
|
+
## インストール
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @wcstack/fullscreen
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## クイックスタート
|
|
37
|
+
|
|
38
|
+
### 1. ボタンクリックで画像をfullscreen化
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
42
|
+
<script type="module" src="https://esm.run/@wcstack/fullscreen/auto"></script>
|
|
43
|
+
|
|
44
|
+
<wcs-state>
|
|
45
|
+
<script type="module">
|
|
46
|
+
export default {
|
|
47
|
+
$commandTokens: ["goFullscreen"],
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
50
|
+
</wcs-state>
|
|
51
|
+
|
|
52
|
+
<wcs-fullscreen target="#hero" data-wcs="command.requestFullscreen: $command.goFullscreen"></wcs-fullscreen>
|
|
53
|
+
<img id="hero" src="/photo.jpg">
|
|
54
|
+
<button data-wcs="onclick: $command.goFullscreen">Fullscreen</button>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
ボタンは `<wcs-fullscreen>` を直接操作しません。クリックで `goFullscreen` コマンドトークンをemitし、`<wcs-fullscreen>` は `command.requestFullscreen: $command.goFullscreen` でそのトークンを購読します([command-tokenプロトコル](../state/) — コマンドメソッドを持つ要素側が購読者、emitする側はボタン)。
|
|
58
|
+
|
|
59
|
+
### 2. 動画をラップし、fullscreen中のみ終了ボタンを表示
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<wcs-state>
|
|
63
|
+
<script type="module">
|
|
64
|
+
export default {
|
|
65
|
+
$commandTokens: ["exitFs"],
|
|
66
|
+
isFullscreen: false,
|
|
67
|
+
};
|
|
68
|
+
</script>
|
|
69
|
+
</wcs-state>
|
|
70
|
+
|
|
71
|
+
<wcs-fullscreen data-wcs="active: isFullscreen; command.exitFullscreen: $command.exitFs">
|
|
72
|
+
<video src="/movie.mp4" controls></video>
|
|
73
|
+
</wcs-fullscreen>
|
|
74
|
+
<button data-wcs="hidden: isFullscreen|not; onclick: $command.exitFs">終了</button>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
バインドする state パスは事前宣言が必須です(ここでは `isFullscreen: false`。未宣言パスへのバインドは初期化時に throw します)。`data-wcs` のパスでの否定は先頭 `!` ではなく `|not` フィルタで行います(`isFullscreen|not`)— パス構文に前置演算子は存在しません。
|
|
78
|
+
|
|
79
|
+
## 観測可能プロパティ(出力)
|
|
80
|
+
|
|
81
|
+
| プロパティ | イベント | 説明 |
|
|
82
|
+
| ----------- | ------------------------ | ---- |
|
|
83
|
+
| `active` | `wcs-fullscreen:change` | `document.fullscreenElement` が**このインスタンスが解決したtarget**と一致している間 `true`、それ以外は `false`。 |
|
|
84
|
+
| `error` | *(無し — 単純なgetter、data-wcsでバインド不可)* | 直近の失敗: rejectされたPromise(gesture外呼び出しなら `TypeError` 等)、プラットフォームAPI非対応なら `{ message: "Fullscreen API is not supported." }`、`target` が要素へ未解決なら `{ message: "Fullscreen target could not be resolved." }`。直近の呼び出しが成功済み・まだ何も失敗していない場合は `null`。 |
|
|
85
|
+
|
|
86
|
+
## コマンド
|
|
87
|
+
|
|
88
|
+
| コマンド | 非同期 | 説明 |
|
|
89
|
+
| ----------------------- | ------ | ---- |
|
|
90
|
+
| `requestFullscreen()` | あり | `target` を解決し、その要素に対して `requestFullscreen()` を呼ぶ。 |
|
|
91
|
+
| `exitFullscreen()` | あり | `document.exitFullscreen()` を呼ぶ。何もfullscreenでなければ何もせず終了する(silent no-op)。 |
|
|
92
|
+
|
|
93
|
+
## 属性 / 入力
|
|
94
|
+
|
|
95
|
+
| 属性 | 説明 |
|
|
96
|
+
| ---------- | ---- |
|
|
97
|
+
| `target` | `@wcstack/intersection` の `target` と同じ3モード解決: `"self"`、CSSセレクタ、または省略(最初の子要素)。 |
|
|
98
|
+
|
|
99
|
+
## 注意・制限
|
|
100
|
+
|
|
101
|
+
- **user gesture制約。** `requestFullscreen()` は実際のuser gesture(クリックハンドラ等)内から同期的に呼ばれた場合のみ成功します。本ノードはgestureを生成できません — command-tokenプロトコル経由(`<wcs-fullscreen>` 側の `command.requestFullscreen: $command.<token>`、ボタン側の `onclick: $command.<token>`)で呼び出す場合は、**起動元のイベント自体**が本物のuser gestureであることを確認してください。`setTimeout` の中やPromiseチェーンの奥深くから呼び出すと、呼び出し方法に関わらず `TypeError`(WHATWG Fullscreen仕様のtransient-activationチェックによる。`NotAllowedError` ではない)でrejectされます — これはブラウザレベルの制約であり、wcstack側で回避する手段はありません。
|
|
102
|
+
- **ベンダープレフィックス。** 一部の古いSafariバージョンは `webkitRequestFullscreen` / `webkitExitFullscreen` / `webkitFullscreenElement` / `webkitfullscreenchange` のみを実装しています。Coreは標準名を優先的にプローブし、**呼び出しの都度**(非キャッシュ)レガシー名にフォールバックするため、両方とも透過的にサポートされます。
|
|
103
|
+
- **複数インスタンス。** `document.fullscreenElement` はdocument全体で単一の値です。異なるtargetを指す複数の `<wcs-fullscreen>` インスタンスが存在する場合、`target` が `document.fullscreenElement` と一致するインスタンスのみが `active: true` を報告し、他は正しく `false` を報告します。各インスタンスは内部で**自分自身が解決したtarget**を追跡しており、単純に「何かがfullscreenかどうか」をミラーしているわけではありません。ただし非対称な点に注意: `exitFullscreen()` はインスタンス単位にスコープされて**いません** — document全体に作用する `document.exitFullscreen()` を呼ぶため、どのインスタンスから呼んでも、現在fullscreenの要素(別インスタンスの `target` がfullscreen化した要素であっても)を解除します(silent no-opの判定も同様にdocument全体の「何かがfullscreenか」であり、「自分のtargetがfullscreenか」ではありません)。これはプラットフォームAPI自体の挙動をそのまま反映したものです。
|
|
104
|
+
- **`exitFullscreen()` は安全なno-op。** 何もfullscreenでない状態(またはAPI非対応)で呼び出してもエラーなくresolveします — 失敗しうる事前条件チェックではなく、べき等な「fullscreenでないことを保証する」コマンドとして扱われます。
|
|
105
|
+
- **`error` に専用イベントは無く、`data-wcs` でバインドもできない。** 大半のwcstack IOノードと異なり、`error` は専用の `wcs-fullscreen:error` イベントを持たない単純なgetterで、`static wcBindable.properties` にも宣言されていません — バインディング側が購読できる対象が無いため、リアクティブに観測することはできません。コマンドのPromiseがsettleした後、`element.error` を命令的に読み取ってください(例: `await el.requestFullscreen(); if (el.error) { ... }`)。
|
|
106
|
+
- **`_gen` 世代ガード。** `dispose()` 後(または後続の呼び出しに追い越された後)にsettleした進行中の `requestFullscreen()`/`exitFullscreen()` 呼び出しは、破棄済みの状態を書き換えません。
|
|
107
|
+
- **SSR(`@wcstack/server`)。** `static hasConnectedCallbackPromise = true` を宣言し `connectedCallbackPromise` を公開しますが、`fullscreenchange` の購読が同期的なため、この promise は常に即座に settle します。
|
|
108
|
+
|
|
109
|
+
## ヘッドレス利用(`FullscreenCore`)
|
|
110
|
+
|
|
111
|
+
CoreはDOM依存が `document` と明示的に渡された対象 `Element` のみで、セレクタの解決自体は一切行いません:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { FullscreenCore } from "@wcstack/fullscreen";
|
|
115
|
+
|
|
116
|
+
const core = new FullscreenCore();
|
|
117
|
+
core.addEventListener("wcs-fullscreen:change", (e) => {
|
|
118
|
+
console.log((e as CustomEvent).detail); // { active: true | false }
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await core.observe(); // document の fullscreenchange を購読
|
|
122
|
+
await core.requestFullscreen(myElement); // user gesture 内から呼ぶ必要がある
|
|
123
|
+
console.log(core.active); // fullscreenchange で確認されると true
|
|
124
|
+
|
|
125
|
+
await core.exitFullscreen();
|
|
126
|
+
core.dispose(); // fullscreenchange リスナーを外す
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## ライセンス
|
|
130
|
+
|
|
131
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @wcstack/fullscreen
|
|
2
|
+
|
|
3
|
+
`@wcstack/fullscreen` is a headless Fullscreen API component for the wcstack ecosystem.
|
|
4
|
+
|
|
5
|
+
It is not a visual UI widget.
|
|
6
|
+
It is a **control node**: unlike most wcstack IO nodes (which act on themselves), `<wcs-fullscreen>` drives `requestFullscreen()` / `exitFullscreen()` on a *referenced* element — the same way `@wcstack/intersection` observes a referenced element rather than itself.
|
|
7
|
+
|
|
8
|
+
With `@wcstack/state`, `<wcs-fullscreen>` can be bound directly through path contracts:
|
|
9
|
+
|
|
10
|
+
- **input surface**: `target` (which element to operate on — see below)
|
|
11
|
+
- **output state surface**: `active`, `error`
|
|
12
|
+
- **commands**: `requestFullscreen()`, `exitFullscreen()`
|
|
13
|
+
|
|
14
|
+
`@wcstack/fullscreen` follows the [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md) (Core / Shell / Binding Contract) architecture:
|
|
15
|
+
|
|
16
|
+
- **Core** (`FullscreenCore`) drives the Fullscreen API and tracks `document`'s `fullscreenchange` event
|
|
17
|
+
- **Shell** (`<wcs-fullscreen target="...">`) resolves `target` to a DOM element and connects Core state to DOM lifecycle
|
|
18
|
+
- **Binding Contract** (`static wcBindable`) declares the observable `active` property and the `requestFullscreen`/`exitFullscreen` commands
|
|
19
|
+
|
|
20
|
+
## Why this exists — you operate on the *target*, not on the tag itself
|
|
21
|
+
|
|
22
|
+
`Element.requestFullscreen()` is a method on the element you want to fullscreen — an image, a video, a card UI — not on `<wcs-fullscreen>` itself. So this tag is a non-visual control element (its `display` is set per target-resolution mode — see the table below) that points at another element via its `target` attribute, exactly like `<wcs-intersect>`:
|
|
23
|
+
|
|
24
|
+
| `target` | operates on | display | typical use |
|
|
25
|
+
| ---------------- | ------------------------ | ------------ | -------------------------- |
|
|
26
|
+
| omitted | first element child | `contents` | wrap a gallery image/video |
|
|
27
|
+
| `"#hero"` / selector | the matched element | `none` | point at a distant node |
|
|
28
|
+
| `"self"` | the element itself | `block` | fullscreen the wrapper itself |
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @wcstack/fullscreen
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### 1. Fullscreen an image on button click
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
42
|
+
<script type="module" src="https://esm.run/@wcstack/fullscreen/auto"></script>
|
|
43
|
+
|
|
44
|
+
<wcs-state>
|
|
45
|
+
<script type="module">
|
|
46
|
+
export default {
|
|
47
|
+
$commandTokens: ["goFullscreen"],
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
50
|
+
</wcs-state>
|
|
51
|
+
|
|
52
|
+
<wcs-fullscreen target="#hero" data-wcs="command.requestFullscreen: $command.goFullscreen"></wcs-fullscreen>
|
|
53
|
+
<img id="hero" src="/photo.jpg">
|
|
54
|
+
<button data-wcs="onclick: $command.goFullscreen">Fullscreen</button>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The button never touches `<wcs-fullscreen>` directly: its click emits the `goFullscreen` command token, and `<wcs-fullscreen>` subscribes to that token via `command.requestFullscreen: $command.goFullscreen` (the [command-token protocol](../state/) — the element with the command method is the *subscriber*, not the emitter).
|
|
58
|
+
|
|
59
|
+
### 2. Wrap a video and show an exit button while active
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<wcs-state>
|
|
63
|
+
<script type="module">
|
|
64
|
+
export default {
|
|
65
|
+
$commandTokens: ["exitFs"],
|
|
66
|
+
isFullscreen: false,
|
|
67
|
+
};
|
|
68
|
+
</script>
|
|
69
|
+
</wcs-state>
|
|
70
|
+
|
|
71
|
+
<wcs-fullscreen data-wcs="active: isFullscreen; command.exitFullscreen: $command.exitFs">
|
|
72
|
+
<video src="/movie.mp4" controls></video>
|
|
73
|
+
</wcs-fullscreen>
|
|
74
|
+
<button data-wcs="hidden: isFullscreen|not; onclick: $command.exitFs">Exit fullscreen</button>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Every bound state path must be declared up front — `isFullscreen: false` here; binding an undeclared path throws at initialization. Negation in a `data-wcs` path is done with the `|not` filter (`isFullscreen|not`), not a leading `!` — paths do not support prefix operators.
|
|
78
|
+
|
|
79
|
+
## Observable Properties (outputs)
|
|
80
|
+
|
|
81
|
+
| Property | Event | Description |
|
|
82
|
+
| --------- | ---------------------- | ------------ |
|
|
83
|
+
| `active` | `wcs-fullscreen:change` | `true` while `document.fullscreenElement` is *this instance's resolved target*; `false` otherwise. |
|
|
84
|
+
| `error` | *(none — plain getter, not data-wcs bindable)* | The most recent failure: a rejected promise (e.g. a `TypeError` for a gesture-less call), `{ message: "Fullscreen API is not supported." }` when the platform API is missing, `{ message: "Fullscreen target could not be resolved." }` when `target` did not resolve to an element, or `null` if the last attempt succeeded / nothing has failed yet. |
|
|
85
|
+
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
| Command | Async | Description |
|
|
89
|
+
| --------------------- | ------ | ------------ |
|
|
90
|
+
| `requestFullscreen()` | yes | Resolve `target` and call `requestFullscreen()` on it. |
|
|
91
|
+
| `exitFullscreen()` | yes | Call `document.exitFullscreen()`. Silent no-op if nothing is currently fullscreen. |
|
|
92
|
+
|
|
93
|
+
## Attributes / Inputs
|
|
94
|
+
|
|
95
|
+
| Attribute | Description |
|
|
96
|
+
| ---------- | ------------ |
|
|
97
|
+
| `target` | Same 3-mode resolution as `@wcstack/intersection`'s `target`: `"self"`, a CSS selector, or omitted (first child). |
|
|
98
|
+
|
|
99
|
+
## Notes & limitations
|
|
100
|
+
|
|
101
|
+
- **User gesture requirement.** `requestFullscreen()` only succeeds when called synchronously from within a real user gesture (e.g. a click handler). This node cannot manufacture a gesture — if you invoke `requestFullscreen` via the command-token protocol (`command.requestFullscreen: $command.<token>` on `<wcs-fullscreen>`, emitted by a button's `onclick: $command.<token>`), make sure the *triggering* event itself is a genuine user gesture. Calling it from inside a `setTimeout` or deep in a promise chain will reject with a `TypeError` (per the WHATWG Fullscreen spec's transient-activation check — not `NotAllowedError`) regardless of how it was invoked — this is a browser-level constraint, not something wcstack can work around.
|
|
102
|
+
- **Vendor prefixes.** Some older Safari versions only implement `webkitRequestFullscreen` / `webkitExitFullscreen` / `webkitFullscreenElement` / `webkitfullscreenchange`. The Core probes the standard name first and falls back to the legacy name at *call time* (never cached), so both are supported transparently.
|
|
103
|
+
- **Multiple instances.** `document.fullscreenElement` is a single, document-wide value. If you have several `<wcs-fullscreen>` instances pointed at different targets, only the instance whose `target` matches `document.fullscreenElement` reports `active: true` — the others correctly report `false`. Each instance tracks *its own* resolved target internally; it does not simply mirror "is anything fullscreen". Note the asymmetry: `exitFullscreen()` is **not** scoped per instance — it calls the document-global `document.exitFullscreen()`, so invoking it on any instance exits whatever element is currently fullscreen, even one put there by another instance's `target` (its silent no-op check is likewise document-wide: "is anything fullscreen", not "is *my* target fullscreen"). This mirrors the platform API itself.
|
|
104
|
+
- **`exitFullscreen()` is a safe no-op.** Calling it when nothing is fullscreen (or when the API is unsupported) resolves without error — it is treated as an idempotent "make sure we're not fullscreen" command, not a failable precondition check.
|
|
105
|
+
- **`error` has no dedicated event, and is not `data-wcs` bindable.** Unlike most wcstack IO nodes, `error` is a plain getter with no `wcs-fullscreen:error` event of its own, and it is not declared in `static wcBindable.properties` — a binding system has nothing to subscribe to and cannot observe it reactively. Read `element.error` imperatively after a command's promise settles (e.g. `await el.requestFullscreen(); if (el.error) { ... }`).
|
|
106
|
+
- **`_gen` generation guard.** In-flight `requestFullscreen()`/`exitFullscreen()` calls that settle after `dispose()` (or after a superseding call) do not write to torn-down state.
|
|
107
|
+
- **SSR (`@wcstack/server`).** Declares `static hasConnectedCallbackPromise = true` and exposes `connectedCallbackPromise`, though since subscribing to `fullscreenchange` is synchronous this promise always settles immediately.
|
|
108
|
+
|
|
109
|
+
## Headless usage (`FullscreenCore`)
|
|
110
|
+
|
|
111
|
+
The Core has no DOM dependency beyond `document` and the target `Element` you pass it explicitly — it never resolves selectors itself:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { FullscreenCore } from "@wcstack/fullscreen";
|
|
115
|
+
|
|
116
|
+
const core = new FullscreenCore();
|
|
117
|
+
core.addEventListener("wcs-fullscreen:change", (e) => {
|
|
118
|
+
console.log((e as CustomEvent).detail); // { active: true | false }
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await core.observe(); // subscribe to document's fullscreenchange
|
|
122
|
+
await core.requestFullscreen(myElement); // must be called from within a user gesture
|
|
123
|
+
console.log(core.active); // true once fullscreenchange confirms it
|
|
124
|
+
|
|
125
|
+
await core.exitFullscreen();
|
|
126
|
+
core.dispose(); // detach the fullscreenchange listener
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bootstrapFullscreen}from"./index.esm.min.js";bootstrapFullscreen();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
interface IWcBindableProperty {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly event: string;
|
|
4
|
+
readonly getter?: (event: Event) => any;
|
|
5
|
+
}
|
|
6
|
+
interface IWcBindableInput {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly attribute?: string;
|
|
9
|
+
}
|
|
10
|
+
interface IWcBindableCommand {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly async?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface IWcBindable {
|
|
15
|
+
readonly protocol: "wc-bindable";
|
|
16
|
+
readonly version: 1;
|
|
17
|
+
readonly properties: readonly IWcBindableProperty[];
|
|
18
|
+
readonly inputs?: readonly IWcBindableInput[];
|
|
19
|
+
readonly commands?: readonly IWcBindableCommand[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ITagNames {
|
|
23
|
+
readonly fullscreen: string;
|
|
24
|
+
}
|
|
25
|
+
interface IWritableTagNames {
|
|
26
|
+
fullscreen?: string;
|
|
27
|
+
}
|
|
28
|
+
interface IConfig {
|
|
29
|
+
readonly tagNames: ITagNames;
|
|
30
|
+
}
|
|
31
|
+
interface IWritableConfig {
|
|
32
|
+
tagNames?: IWritableTagNames;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Value types for FullscreenCore (headless) — the Core's readable value
|
|
37
|
+
* surface. Note that only `active` is *observable* (declared in
|
|
38
|
+
* `wcBindable.properties` with a change event); `error` is an
|
|
39
|
+
* imperative-read-only getter with no event of its own — a wc-bindable
|
|
40
|
+
* binding core will never deliver it, so read it after a command settles
|
|
41
|
+
* (docs/fullscreen-tag-design.md §8, README "Notes & limitations").
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const core = new FullscreenCore();
|
|
46
|
+
* // bind() only ever delivers "active" — see the note above about "error".
|
|
47
|
+
* bind(core, (name: keyof WcsFullscreenCoreValues, value) => { ... });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
interface WcsFullscreenCoreValues {
|
|
51
|
+
active: boolean;
|
|
52
|
+
error: any;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Value types for the Shell (`<wcs-fullscreen target="...">`) — identical
|
|
56
|
+
* value surface to the Core (same caveat: only `active` is observable).
|
|
57
|
+
* The Shell adds the `target` input (attribute-mirrored) that resolves which
|
|
58
|
+
* element requestFullscreen()/exitFullscreen() operate on
|
|
59
|
+
* (docs/fullscreen-tag-design.md §1/§9).
|
|
60
|
+
*/
|
|
61
|
+
type WcsFullscreenValues = WcsFullscreenCoreValues;
|
|
62
|
+
|
|
63
|
+
declare function bootstrapFullscreen(userConfig?: IWritableConfig): void;
|
|
64
|
+
|
|
65
|
+
declare function getConfig(): IConfig;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Headless Fullscreen API primitive. Unlike most wcstack IO nodes, this Core
|
|
69
|
+
* does not operate on itself: it drives `requestFullscreen()` /
|
|
70
|
+
* `exitFullscreen()` on a *referenced* Element that the Shell resolves via its
|
|
71
|
+
* `target` attribute (docs/fullscreen-tag-design.md §0). The Core only ever
|
|
72
|
+
* receives already-resolved `Element`s from its callers — it has no opinion on
|
|
73
|
+
* how `target` selectors are parsed.
|
|
74
|
+
*
|
|
75
|
+
* `document.fullscreenElement` is a single document-wide value, so this Core
|
|
76
|
+
* always compares against the *last element it resolved* (via
|
|
77
|
+
* `requestFullscreen()`/`setTarget()`), never against "is the document
|
|
78
|
+
* fullscreen at all" — that comparison is what keeps multiple concurrent
|
|
79
|
+
* `<wcs-fullscreen>` instances from all reporting the same `active` value
|
|
80
|
+
* (docs/fullscreen-tag-design.md §2.1, MUST).
|
|
81
|
+
*/
|
|
82
|
+
declare class FullscreenCore extends EventTarget {
|
|
83
|
+
static wcBindable: IWcBindable;
|
|
84
|
+
private _target;
|
|
85
|
+
private _active;
|
|
86
|
+
private _error;
|
|
87
|
+
private _resolvedTarget;
|
|
88
|
+
private _gen;
|
|
89
|
+
private _subscribed;
|
|
90
|
+
private _ready;
|
|
91
|
+
constructor(target?: EventTarget);
|
|
92
|
+
get ready(): Promise<void>;
|
|
93
|
+
get active(): boolean;
|
|
94
|
+
get error(): any;
|
|
95
|
+
/**
|
|
96
|
+
* Update the resolved target without issuing a fullscreen request (e.g. the
|
|
97
|
+
* Shell re-resolves `target` on attribute change / connect). Re-evaluates
|
|
98
|
+
* `active` against the current `document.fullscreenElement` so the state
|
|
99
|
+
* stays correct even if the target changed while already fullscreen.
|
|
100
|
+
*/
|
|
101
|
+
setTarget(element: Element | null): void;
|
|
102
|
+
observe(): Promise<void>;
|
|
103
|
+
dispose(): void;
|
|
104
|
+
/**
|
|
105
|
+
* Request fullscreen on `element`. never-throw (§3/§6): a missing API or a
|
|
106
|
+
* rejected promise (e.g. a `TypeError` from a call outside a user
|
|
107
|
+
* gesture, per the WHATWG Fullscreen spec's transient-activation check) is
|
|
108
|
+
* caught and surfaced via `error`, never thrown. The caller
|
|
109
|
+
* (Shell) is responsible for resolving `target` and for ensuring this is
|
|
110
|
+
* invoked from within an actual user gesture — this Core cannot manufacture
|
|
111
|
+
* one (docs/fullscreen-tag-design.md §3).
|
|
112
|
+
*/
|
|
113
|
+
requestFullscreen(element: Element | null): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Exit fullscreen. Silent no-op (§7) when nothing is currently fullscreen or
|
|
116
|
+
* the API is unsupported — both are treated as "already achieved the exit
|
|
117
|
+
* intent", not as errors, keeping repeated calls safe and never-throw.
|
|
118
|
+
*/
|
|
119
|
+
exitFullscreen(): Promise<void>;
|
|
120
|
+
private _requestFullscreenFn;
|
|
121
|
+
private _elementFullscreenFn;
|
|
122
|
+
private _exitFullscreenFn;
|
|
123
|
+
private _fullscreenElement;
|
|
124
|
+
private _fullscreenChangeEventName;
|
|
125
|
+
private _onFullscreenChange;
|
|
126
|
+
private _applyActive;
|
|
127
|
+
private _setActive;
|
|
128
|
+
private _setError;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* `<wcs-fullscreen target="...">` — declarative Fullscreen API control.
|
|
133
|
+
*
|
|
134
|
+
* Like `intersection`/`resize`, this Shell operates on a *referenced* element,
|
|
135
|
+
* not itself (docs/fullscreen-tag-design.md §0): `target` resolves which
|
|
136
|
+
* element `requestFullscreen()`/`exitFullscreen()` are invoked on, using the
|
|
137
|
+
* exact same 3-mode resolution as `<wcs-intersect>`
|
|
138
|
+
* (docs/fullscreen-tag-design.md §1):
|
|
139
|
+
*
|
|
140
|
+
* | `target` | operates on | display | use case |
|
|
141
|
+
* |-----------------|-------------------------|-------------|--------------------------|
|
|
142
|
+
* | omitted | first element child | `contents` | wrap a gallery image/video |
|
|
143
|
+
* | `"#hero"` / sel | the matched element | `none` | point at a distant node |
|
|
144
|
+
* | `"self"` | the element itself | `block` | fullscreen the wrapper |
|
|
145
|
+
*
|
|
146
|
+
* `requestFullscreen()` requires an active user gesture — this element cannot
|
|
147
|
+
* manufacture one. Invoke the command from within a real click handler
|
|
148
|
+
* (typically via the command-token protocol: this element subscribes with
|
|
149
|
+
* `command.requestFullscreen: $command.<token>`, and a button emits the
|
|
150
|
+
* token from its own click handler, e.g. `onclick: $command.<token>`).
|
|
151
|
+
*/
|
|
152
|
+
declare class WcsFullscreen extends HTMLElement {
|
|
153
|
+
static hasConnectedCallbackPromise: boolean;
|
|
154
|
+
static observedAttributes: string[];
|
|
155
|
+
static wcBindable: IWcBindable;
|
|
156
|
+
private _core;
|
|
157
|
+
private _connectedCallbackPromise;
|
|
158
|
+
constructor();
|
|
159
|
+
get connectedCallbackPromise(): Promise<void>;
|
|
160
|
+
get target(): string;
|
|
161
|
+
set target(value: string);
|
|
162
|
+
get active(): boolean;
|
|
163
|
+
get error(): any;
|
|
164
|
+
/**
|
|
165
|
+
* Resolve `target` and request fullscreen on it. never-throw: an
|
|
166
|
+
* unresolvable target or an unsupported/rejected API call are both
|
|
167
|
+
* surfaced via `error`, never thrown (docs/fullscreen-tag-design.md §3/§6).
|
|
168
|
+
*/
|
|
169
|
+
requestFullscreen(): Promise<void>;
|
|
170
|
+
exitFullscreen(): Promise<void>;
|
|
171
|
+
private _resolveTarget;
|
|
172
|
+
private _safeQuery;
|
|
173
|
+
private _reresolve;
|
|
174
|
+
connectedCallback(): void;
|
|
175
|
+
disconnectedCallback(): void;
|
|
176
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { FullscreenCore, WcsFullscreen, bootstrapFullscreen, getConfig };
|
|
180
|
+
export type { IWritableConfig, IWritableTagNames, WcsFullscreenCoreValues, WcsFullscreenValues };
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
const _config = {
|
|
2
|
+
tagNames: {
|
|
3
|
+
fullscreen: "wcs-fullscreen",
|
|
4
|
+
},
|
|
5
|
+
};
|
|
6
|
+
function deepFreeze(obj) {
|
|
7
|
+
if (obj === null || typeof obj !== "object")
|
|
8
|
+
return obj;
|
|
9
|
+
Object.freeze(obj);
|
|
10
|
+
for (const key of Object.keys(obj)) {
|
|
11
|
+
deepFreeze(obj[key]);
|
|
12
|
+
}
|
|
13
|
+
return obj;
|
|
14
|
+
}
|
|
15
|
+
function deepClone(obj) {
|
|
16
|
+
if (obj === null || typeof obj !== "object")
|
|
17
|
+
return obj;
|
|
18
|
+
const clone = {};
|
|
19
|
+
for (const key of Object.keys(obj)) {
|
|
20
|
+
clone[key] = deepClone(obj[key]);
|
|
21
|
+
}
|
|
22
|
+
return clone;
|
|
23
|
+
}
|
|
24
|
+
let frozenConfig = null;
|
|
25
|
+
const config = _config;
|
|
26
|
+
function getConfig() {
|
|
27
|
+
if (!frozenConfig) {
|
|
28
|
+
frozenConfig = deepFreeze(deepClone(_config));
|
|
29
|
+
}
|
|
30
|
+
return frozenConfig;
|
|
31
|
+
}
|
|
32
|
+
function setConfig(partialConfig) {
|
|
33
|
+
if (partialConfig.tagNames) {
|
|
34
|
+
Object.assign(_config.tagNames, partialConfig.tagNames);
|
|
35
|
+
}
|
|
36
|
+
frozenConfig = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Headless Fullscreen API primitive. Unlike most wcstack IO nodes, this Core
|
|
41
|
+
* does not operate on itself: it drives `requestFullscreen()` /
|
|
42
|
+
* `exitFullscreen()` on a *referenced* Element that the Shell resolves via its
|
|
43
|
+
* `target` attribute (docs/fullscreen-tag-design.md §0). The Core only ever
|
|
44
|
+
* receives already-resolved `Element`s from its callers — it has no opinion on
|
|
45
|
+
* how `target` selectors are parsed.
|
|
46
|
+
*
|
|
47
|
+
* `document.fullscreenElement` is a single document-wide value, so this Core
|
|
48
|
+
* always compares against the *last element it resolved* (via
|
|
49
|
+
* `requestFullscreen()`/`setTarget()`), never against "is the document
|
|
50
|
+
* fullscreen at all" — that comparison is what keeps multiple concurrent
|
|
51
|
+
* `<wcs-fullscreen>` instances from all reporting the same `active` value
|
|
52
|
+
* (docs/fullscreen-tag-design.md §2.1, MUST).
|
|
53
|
+
*/
|
|
54
|
+
class FullscreenCore extends EventTarget {
|
|
55
|
+
static wcBindable = {
|
|
56
|
+
protocol: "wc-bindable",
|
|
57
|
+
version: 1,
|
|
58
|
+
properties: [
|
|
59
|
+
{ name: "active", event: "wcs-fullscreen:change", getter: (e) => e.detail.active },
|
|
60
|
+
],
|
|
61
|
+
commands: [
|
|
62
|
+
{ name: "requestFullscreen", async: true },
|
|
63
|
+
{ name: "exitFullscreen", async: true },
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
_target;
|
|
67
|
+
_active = false;
|
|
68
|
+
// Single error slot (§8): null means "no recent failure". Fullscreen's
|
|
69
|
+
// gesture-rejection failure is a one-shot event, not a persistent state
|
|
70
|
+
// machine like permission's 4-value surface — active/error are two
|
|
71
|
+
// orthogonal, independently-observable axes.
|
|
72
|
+
_error = null;
|
|
73
|
+
// The last Element this Core resolved via requestFullscreen()/setTarget().
|
|
74
|
+
// Compared against document.fullscreenElement on every fullscreenchange so
|
|
75
|
+
// each instance judges only its own target (§2.1). null means "no target
|
|
76
|
+
// resolved yet" — active must stay false in that case.
|
|
77
|
+
_resolvedTarget = null;
|
|
78
|
+
// Generation guard (§6): Core-scoped (one per Core, not per-target),
|
|
79
|
+
// mirroring fetch/upload. document.fullscreenElement is a single
|
|
80
|
+
// document-wide slot, so at most one in-flight request/exit is meaningful
|
|
81
|
+
// per Core at a time.
|
|
82
|
+
_gen = 0;
|
|
83
|
+
// True once observe() has attached the document-level fullscreenchange
|
|
84
|
+
// listener. Guards observe() so a redundant call does not double-subscribe;
|
|
85
|
+
// dispose() resets it so a later observe() resumes cleanly.
|
|
86
|
+
_subscribed = false;
|
|
87
|
+
// SSR (§10): no asynchronous probe to await — observe() completes
|
|
88
|
+
// synchronously, so readiness is immediate.
|
|
89
|
+
_ready = Promise.resolve();
|
|
90
|
+
constructor(target) {
|
|
91
|
+
super();
|
|
92
|
+
this._target = target ?? this;
|
|
93
|
+
}
|
|
94
|
+
get ready() {
|
|
95
|
+
return this._ready;
|
|
96
|
+
}
|
|
97
|
+
get active() {
|
|
98
|
+
return this._active;
|
|
99
|
+
}
|
|
100
|
+
get error() {
|
|
101
|
+
return this._error;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Update the resolved target without issuing a fullscreen request (e.g. the
|
|
105
|
+
* Shell re-resolves `target` on attribute change / connect). Re-evaluates
|
|
106
|
+
* `active` against the current `document.fullscreenElement` so the state
|
|
107
|
+
* stays correct even if the target changed while already fullscreen.
|
|
108
|
+
*/
|
|
109
|
+
setTarget(element) {
|
|
110
|
+
this._resolvedTarget = element;
|
|
111
|
+
this._applyActive();
|
|
112
|
+
}
|
|
113
|
+
// Lifecycle (§10/§3.5). Idempotent: a second observe() while already
|
|
114
|
+
// subscribed is a no-op (no double listener). Synchronous overall (no probe
|
|
115
|
+
// to await), so the returned promise is only for API uniformity with other
|
|
116
|
+
// IO nodes.
|
|
117
|
+
observe() {
|
|
118
|
+
if (!this._subscribed) {
|
|
119
|
+
this._subscribed = true;
|
|
120
|
+
document.addEventListener(this._fullscreenChangeEventName(), this._onFullscreenChange);
|
|
121
|
+
}
|
|
122
|
+
return this._ready;
|
|
123
|
+
}
|
|
124
|
+
dispose() {
|
|
125
|
+
this._gen++;
|
|
126
|
+
if (this._subscribed) {
|
|
127
|
+
this._subscribed = false;
|
|
128
|
+
document.removeEventListener(this._fullscreenChangeEventName(), this._onFullscreenChange);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Request fullscreen on `element`. never-throw (§3/§6): a missing API or a
|
|
133
|
+
* rejected promise (e.g. a `TypeError` from a call outside a user
|
|
134
|
+
* gesture, per the WHATWG Fullscreen spec's transient-activation check) is
|
|
135
|
+
* caught and surfaced via `error`, never thrown. The caller
|
|
136
|
+
* (Shell) is responsible for resolving `target` and for ensuring this is
|
|
137
|
+
* invoked from within an actual user gesture — this Core cannot manufacture
|
|
138
|
+
* one (docs/fullscreen-tag-design.md §3).
|
|
139
|
+
*/
|
|
140
|
+
async requestFullscreen(element) {
|
|
141
|
+
const gen = ++this._gen;
|
|
142
|
+
this._resolvedTarget = element;
|
|
143
|
+
if (!element) {
|
|
144
|
+
// Distinct from "API is not supported" (below): the Shell's `target`
|
|
145
|
+
// selector did not resolve to any element (missing/typo'd selector).
|
|
146
|
+
// Conflating the two previously misled users into thinking Fullscreen
|
|
147
|
+
// itself was unsupported when only their selector was wrong.
|
|
148
|
+
this._setError({ message: "Fullscreen target could not be resolved." });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const fn = this._requestFullscreenFn(element);
|
|
152
|
+
if (!fn) {
|
|
153
|
+
this._setError({ message: "Fullscreen API is not supported." });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
await fn.call(element);
|
|
158
|
+
if (gen !== this._gen)
|
|
159
|
+
return; // stale: dispose()/superseding call ran
|
|
160
|
+
this._setError(null);
|
|
161
|
+
this._applyActive();
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
if (gen !== this._gen)
|
|
165
|
+
return; // stale
|
|
166
|
+
this._setError(e);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Exit fullscreen. Silent no-op (§7) when nothing is currently fullscreen or
|
|
171
|
+
* the API is unsupported — both are treated as "already achieved the exit
|
|
172
|
+
* intent", not as errors, keeping repeated calls safe and never-throw.
|
|
173
|
+
*/
|
|
174
|
+
async exitFullscreen() {
|
|
175
|
+
// no-op checks come BEFORE the generation bump: a call that does nothing
|
|
176
|
+
// must not supersede an in-flight requestFullscreen() — bumping first
|
|
177
|
+
// would make the pending request's settle handling stale and silently
|
|
178
|
+
// swallow its error/active updates.
|
|
179
|
+
if (this._fullscreenElement() === null)
|
|
180
|
+
return; // already not fullscreen: silent no-op
|
|
181
|
+
const fn = this._exitFullscreenFn();
|
|
182
|
+
if (!fn)
|
|
183
|
+
return; // unsupported: silent no-op (semantically already "not fullscreen")
|
|
184
|
+
const gen = ++this._gen;
|
|
185
|
+
try {
|
|
186
|
+
await fn();
|
|
187
|
+
if (gen !== this._gen)
|
|
188
|
+
return; // stale
|
|
189
|
+
this._setError(null);
|
|
190
|
+
this._applyActive();
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
if (gen !== this._gen)
|
|
194
|
+
return; // stale
|
|
195
|
+
this._setError(e);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// --- API resolution layer (§4): call-time, never cached. Lets tests
|
|
199
|
+
// install/remove the standard/legacy APIs freely and lets an unsupported
|
|
200
|
+
// environment be detected correctly on every call. ---
|
|
201
|
+
_requestFullscreenFn(el) {
|
|
202
|
+
return this._elementFullscreenFn(el, "requestFullscreen")
|
|
203
|
+
?? this._elementFullscreenFn(el, "webkitRequestFullscreen");
|
|
204
|
+
}
|
|
205
|
+
// Resolve a fullscreen method for `el` WITHOUT a naive `el[name]` lookup.
|
|
206
|
+
// A plain lookup walks the whole prototype chain — and <wcs-fullscreen>
|
|
207
|
+
// itself declares a `requestFullscreen()` *command* method, so when the
|
|
208
|
+
// resolved target is the Shell element (target="self", or target omitted
|
|
209
|
+
// with no children), the naive lookup would find the Shell's own command
|
|
210
|
+
// instead of the platform API and recurse infinitely (stack overflow).
|
|
211
|
+
// Instead: check the element's own properties (how tests install stubs —
|
|
212
|
+
// happy-dom has no Fullscreen API — and how a deliberate per-element
|
|
213
|
+
// monkey-patch would appear), then jump straight to Element.prototype,
|
|
214
|
+
// where the platform defines the real methods. Both the standard and the
|
|
215
|
+
// legacy webkit name go through this same resolution for symmetry.
|
|
216
|
+
_elementFullscreenFn(el, name) {
|
|
217
|
+
if (Object.prototype.hasOwnProperty.call(el, name)) {
|
|
218
|
+
return el[name];
|
|
219
|
+
}
|
|
220
|
+
return Element.prototype[name];
|
|
221
|
+
}
|
|
222
|
+
_exitFullscreenFn() {
|
|
223
|
+
const d = document;
|
|
224
|
+
return d.exitFullscreen?.bind(document) ?? d.webkitExitFullscreen?.bind(document);
|
|
225
|
+
}
|
|
226
|
+
_fullscreenElement() {
|
|
227
|
+
const d = document;
|
|
228
|
+
return d.fullscreenElement ?? d.webkitFullscreenElement ?? null;
|
|
229
|
+
}
|
|
230
|
+
_fullscreenChangeEventName() {
|
|
231
|
+
return "onfullscreenchange" in document ? "fullscreenchange" : "webkitfullscreenchange";
|
|
232
|
+
}
|
|
233
|
+
// --- Internal ---
|
|
234
|
+
_onFullscreenChange = () => {
|
|
235
|
+
this._applyActive();
|
|
236
|
+
};
|
|
237
|
+
// Re-derive `active` by comparing document.fullscreenElement (incl. legacy
|
|
238
|
+
// fallback) against *this instance's* resolved target (§2/§2.1/§5). A null
|
|
239
|
+
// resolved target always yields active=false — there is nothing for this
|
|
240
|
+
// instance to claim as "mine".
|
|
241
|
+
_applyActive() {
|
|
242
|
+
const next = this._resolvedTarget !== null && this._fullscreenElement() === this._resolvedTarget;
|
|
243
|
+
this._setActive(next);
|
|
244
|
+
}
|
|
245
|
+
_setActive(v) {
|
|
246
|
+
if (this._active === v)
|
|
247
|
+
return; // same-value guard (§3.3 MUST)
|
|
248
|
+
this._active = v;
|
|
249
|
+
this._target.dispatchEvent(new CustomEvent("wcs-fullscreen:change", {
|
|
250
|
+
detail: { active: v },
|
|
251
|
+
bubbles: true,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
_setError(error) {
|
|
255
|
+
this._error = error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* `<wcs-fullscreen target="...">` — declarative Fullscreen API control.
|
|
261
|
+
*
|
|
262
|
+
* Like `intersection`/`resize`, this Shell operates on a *referenced* element,
|
|
263
|
+
* not itself (docs/fullscreen-tag-design.md §0): `target` resolves which
|
|
264
|
+
* element `requestFullscreen()`/`exitFullscreen()` are invoked on, using the
|
|
265
|
+
* exact same 3-mode resolution as `<wcs-intersect>`
|
|
266
|
+
* (docs/fullscreen-tag-design.md §1):
|
|
267
|
+
*
|
|
268
|
+
* | `target` | operates on | display | use case |
|
|
269
|
+
* |-----------------|-------------------------|-------------|--------------------------|
|
|
270
|
+
* | omitted | first element child | `contents` | wrap a gallery image/video |
|
|
271
|
+
* | `"#hero"` / sel | the matched element | `none` | point at a distant node |
|
|
272
|
+
* | `"self"` | the element itself | `block` | fullscreen the wrapper |
|
|
273
|
+
*
|
|
274
|
+
* `requestFullscreen()` requires an active user gesture — this element cannot
|
|
275
|
+
* manufacture one. Invoke the command from within a real click handler
|
|
276
|
+
* (typically via the command-token protocol: this element subscribes with
|
|
277
|
+
* `command.requestFullscreen: $command.<token>`, and a button emits the
|
|
278
|
+
* token from its own click handler, e.g. `onclick: $command.<token>`).
|
|
279
|
+
*/
|
|
280
|
+
class WcsFullscreen extends HTMLElement {
|
|
281
|
+
// SSR (§10): the fullscreenchange subscription is established synchronously
|
|
282
|
+
// on connect, but the Shell still exposes connectedCallbackPromise so the
|
|
283
|
+
// state binder can await it uniformly across all IO nodes before
|
|
284
|
+
// snapshotting.
|
|
285
|
+
static hasConnectedCallbackPromise = true;
|
|
286
|
+
static observedAttributes = ["target"];
|
|
287
|
+
static wcBindable = {
|
|
288
|
+
...FullscreenCore.wcBindable,
|
|
289
|
+
inputs: [{ name: "target", attribute: "target" }],
|
|
290
|
+
// Core の commands をそのまま継承(単一情報源)。network/intersection と同型。
|
|
291
|
+
commands: FullscreenCore.wcBindable.commands,
|
|
292
|
+
};
|
|
293
|
+
_core;
|
|
294
|
+
_connectedCallbackPromise = Promise.resolve();
|
|
295
|
+
constructor() {
|
|
296
|
+
super();
|
|
297
|
+
this._core = new FullscreenCore(this);
|
|
298
|
+
}
|
|
299
|
+
get connectedCallbackPromise() {
|
|
300
|
+
return this._connectedCallbackPromise;
|
|
301
|
+
}
|
|
302
|
+
// --- Attribute accessors ---
|
|
303
|
+
get target() {
|
|
304
|
+
return this.getAttribute("target") ?? "";
|
|
305
|
+
}
|
|
306
|
+
set target(value) {
|
|
307
|
+
this.setAttribute("target", value);
|
|
308
|
+
}
|
|
309
|
+
// --- Core delegated getters ---
|
|
310
|
+
get active() {
|
|
311
|
+
return this._core.active;
|
|
312
|
+
}
|
|
313
|
+
get error() {
|
|
314
|
+
return this._core.error;
|
|
315
|
+
}
|
|
316
|
+
// --- Commands ---
|
|
317
|
+
/**
|
|
318
|
+
* Resolve `target` and request fullscreen on it. never-throw: an
|
|
319
|
+
* unresolvable target or an unsupported/rejected API call are both
|
|
320
|
+
* surfaced via `error`, never thrown (docs/fullscreen-tag-design.md §3/§6).
|
|
321
|
+
*/
|
|
322
|
+
async requestFullscreen() {
|
|
323
|
+
const { element } = this._resolveTarget();
|
|
324
|
+
await this._core.requestFullscreen(element);
|
|
325
|
+
}
|
|
326
|
+
async exitFullscreen() {
|
|
327
|
+
await this._core.exitFullscreen();
|
|
328
|
+
}
|
|
329
|
+
// --- Internal ---
|
|
330
|
+
// Copied verbatim from <wcs-intersect> (Intersect.ts _resolveTarget/_safeQuery,
|
|
331
|
+
// docs/fullscreen-tag-design.md §1): identical 3-mode resolution, only the
|
|
332
|
+
// "what to do with the resolved element" step differs.
|
|
333
|
+
_resolveTarget() {
|
|
334
|
+
const target = this.target;
|
|
335
|
+
if (target === "self") {
|
|
336
|
+
return { element: this, display: "block" };
|
|
337
|
+
}
|
|
338
|
+
if (target !== "") {
|
|
339
|
+
const scope = this.getRootNode();
|
|
340
|
+
return { element: this._safeQuery(scope, target), display: "none" };
|
|
341
|
+
}
|
|
342
|
+
const child = this.firstElementChild;
|
|
343
|
+
if (child) {
|
|
344
|
+
return { element: child, display: "contents" };
|
|
345
|
+
}
|
|
346
|
+
return { element: this, display: "block" };
|
|
347
|
+
}
|
|
348
|
+
_safeQuery(scope, selector) {
|
|
349
|
+
try {
|
|
350
|
+
return scope.querySelector(selector);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
_reresolve() {
|
|
357
|
+
const { element, display } = this._resolveTarget();
|
|
358
|
+
this.style.display = display;
|
|
359
|
+
this._core.setTarget(element);
|
|
360
|
+
}
|
|
361
|
+
// --- Lifecycle ---
|
|
362
|
+
connectedCallback() {
|
|
363
|
+
this._reresolve();
|
|
364
|
+
this._connectedCallbackPromise = this._core.observe();
|
|
365
|
+
}
|
|
366
|
+
disconnectedCallback() {
|
|
367
|
+
this._core.dispose();
|
|
368
|
+
}
|
|
369
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
370
|
+
if (oldValue === newValue)
|
|
371
|
+
return;
|
|
372
|
+
if (!this.isConnected)
|
|
373
|
+
return;
|
|
374
|
+
this._reresolve();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function registerComponents() {
|
|
379
|
+
if (!customElements.get(config.tagNames.fullscreen)) {
|
|
380
|
+
customElements.define(config.tagNames.fullscreen, WcsFullscreen);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function bootstrapFullscreen(userConfig) {
|
|
385
|
+
if (userConfig) {
|
|
386
|
+
setConfig(userConfig);
|
|
387
|
+
}
|
|
388
|
+
registerComponents();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export { FullscreenCore, WcsFullscreen, bootstrapFullscreen, getConfig };
|
|
392
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/config.ts","../src/core/FullscreenCore.ts","../src/components/Fullscreen.ts","../src/registerComponents.ts","../src/bootstrapFullscreen.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n tagNames: {\n fullscreen: string;\n };\n}\n\nconst _config: IInternalConfig = {\n tagNames: {\n fullscreen: \"wcs-fullscreen\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import { IWcBindable } from \"../types.js\";\n\n/**\n * Headless Fullscreen API primitive. Unlike most wcstack IO nodes, this Core\n * does not operate on itself: it drives `requestFullscreen()` /\n * `exitFullscreen()` on a *referenced* Element that the Shell resolves via its\n * `target` attribute (docs/fullscreen-tag-design.md §0). The Core only ever\n * receives already-resolved `Element`s from its callers — it has no opinion on\n * how `target` selectors are parsed.\n *\n * `document.fullscreenElement` is a single document-wide value, so this Core\n * always compares against the *last element it resolved* (via\n * `requestFullscreen()`/`setTarget()`), never against \"is the document\n * fullscreen at all\" — that comparison is what keeps multiple concurrent\n * `<wcs-fullscreen>` instances from all reporting the same `active` value\n * (docs/fullscreen-tag-design.md §2.1, MUST).\n */\nexport class FullscreenCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"active\", event: \"wcs-fullscreen:change\", getter: (e: Event) => (e as CustomEvent).detail.active },\n ],\n commands: [\n { name: \"requestFullscreen\", async: true },\n { name: \"exitFullscreen\", async: true },\n ],\n };\n\n private _target: EventTarget;\n private _active: boolean = false;\n // Single error slot (§8): null means \"no recent failure\". Fullscreen's\n // gesture-rejection failure is a one-shot event, not a persistent state\n // machine like permission's 4-value surface — active/error are two\n // orthogonal, independently-observable axes.\n private _error: any = null;\n\n // The last Element this Core resolved via requestFullscreen()/setTarget().\n // Compared against document.fullscreenElement on every fullscreenchange so\n // each instance judges only its own target (§2.1). null means \"no target\n // resolved yet\" — active must stay false in that case.\n private _resolvedTarget: Element | null = null;\n\n // Generation guard (§6): Core-scoped (one per Core, not per-target),\n // mirroring fetch/upload. document.fullscreenElement is a single\n // document-wide slot, so at most one in-flight request/exit is meaningful\n // per Core at a time.\n private _gen = 0;\n\n // True once observe() has attached the document-level fullscreenchange\n // listener. Guards observe() so a redundant call does not double-subscribe;\n // dispose() resets it so a later observe() resumes cleanly.\n private _subscribed = false;\n\n // SSR (§10): no asynchronous probe to await — observe() completes\n // synchronously, so readiness is immediate.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get ready(): Promise<void> {\n return this._ready;\n }\n\n get active(): boolean {\n return this._active;\n }\n\n get error(): any {\n return this._error;\n }\n\n /**\n * Update the resolved target without issuing a fullscreen request (e.g. the\n * Shell re-resolves `target` on attribute change / connect). Re-evaluates\n * `active` against the current `document.fullscreenElement` so the state\n * stays correct even if the target changed while already fullscreen.\n */\n setTarget(element: Element | null): void {\n this._resolvedTarget = element;\n this._applyActive();\n }\n\n // Lifecycle (§10/§3.5). Idempotent: a second observe() while already\n // subscribed is a no-op (no double listener). Synchronous overall (no probe\n // to await), so the returned promise is only for API uniformity with other\n // IO nodes.\n observe(): Promise<void> {\n if (!this._subscribed) {\n this._subscribed = true;\n document.addEventListener(this._fullscreenChangeEventName(), this._onFullscreenChange);\n }\n return this._ready;\n }\n\n dispose(): void {\n this._gen++;\n if (this._subscribed) {\n this._subscribed = false;\n document.removeEventListener(this._fullscreenChangeEventName(), this._onFullscreenChange);\n }\n }\n\n /**\n * Request fullscreen on `element`. never-throw (§3/§6): a missing API or a\n * rejected promise (e.g. a `TypeError` from a call outside a user\n * gesture, per the WHATWG Fullscreen spec's transient-activation check) is\n * caught and surfaced via `error`, never thrown. The caller\n * (Shell) is responsible for resolving `target` and for ensuring this is\n * invoked from within an actual user gesture — this Core cannot manufacture\n * one (docs/fullscreen-tag-design.md §3).\n */\n async requestFullscreen(element: Element | null): Promise<void> {\n const gen = ++this._gen;\n this._resolvedTarget = element;\n if (!element) {\n // Distinct from \"API is not supported\" (below): the Shell's `target`\n // selector did not resolve to any element (missing/typo'd selector).\n // Conflating the two previously misled users into thinking Fullscreen\n // itself was unsupported when only their selector was wrong.\n this._setError({ message: \"Fullscreen target could not be resolved.\" });\n return;\n }\n const fn = this._requestFullscreenFn(element);\n if (!fn) {\n this._setError({ message: \"Fullscreen API is not supported.\" });\n return;\n }\n try {\n await fn.call(element);\n if (gen !== this._gen) return; // stale: dispose()/superseding call ran\n this._setError(null);\n this._applyActive();\n } catch (e: any) {\n if (gen !== this._gen) return; // stale\n this._setError(e);\n }\n }\n\n /**\n * Exit fullscreen. Silent no-op (§7) when nothing is currently fullscreen or\n * the API is unsupported — both are treated as \"already achieved the exit\n * intent\", not as errors, keeping repeated calls safe and never-throw.\n */\n async exitFullscreen(): Promise<void> {\n // no-op checks come BEFORE the generation bump: a call that does nothing\n // must not supersede an in-flight requestFullscreen() — bumping first\n // would make the pending request's settle handling stale and silently\n // swallow its error/active updates.\n if (this._fullscreenElement() === null) return; // already not fullscreen: silent no-op\n const fn = this._exitFullscreenFn();\n if (!fn) return; // unsupported: silent no-op (semantically already \"not fullscreen\")\n const gen = ++this._gen;\n try {\n await fn();\n if (gen !== this._gen) return; // stale\n this._setError(null);\n this._applyActive();\n } catch (e: any) {\n if (gen !== this._gen) return; // stale\n this._setError(e);\n }\n }\n\n // --- API resolution layer (§4): call-time, never cached. Lets tests\n // install/remove the standard/legacy APIs freely and lets an unsupported\n // environment be detected correctly on every call. ---\n\n private _requestFullscreenFn(el: Element): (() => Promise<void>) | undefined {\n return this._elementFullscreenFn(el, \"requestFullscreen\")\n ?? this._elementFullscreenFn(el, \"webkitRequestFullscreen\");\n }\n\n // Resolve a fullscreen method for `el` WITHOUT a naive `el[name]` lookup.\n // A plain lookup walks the whole prototype chain — and <wcs-fullscreen>\n // itself declares a `requestFullscreen()` *command* method, so when the\n // resolved target is the Shell element (target=\"self\", or target omitted\n // with no children), the naive lookup would find the Shell's own command\n // instead of the platform API and recurse infinitely (stack overflow).\n // Instead: check the element's own properties (how tests install stubs —\n // happy-dom has no Fullscreen API — and how a deliberate per-element\n // monkey-patch would appear), then jump straight to Element.prototype,\n // where the platform defines the real methods. Both the standard and the\n // legacy webkit name go through this same resolution for symmetry.\n private _elementFullscreenFn(el: Element, name: string): (() => Promise<void>) | undefined {\n if (Object.prototype.hasOwnProperty.call(el, name)) {\n return (el as any)[name];\n }\n return (Element.prototype as any)[name];\n }\n\n private _exitFullscreenFn(): (() => Promise<void>) | undefined {\n const d = document as any;\n return d.exitFullscreen?.bind(document) ?? d.webkitExitFullscreen?.bind(document);\n }\n\n private _fullscreenElement(): Element | null {\n const d = document as any;\n return d.fullscreenElement ?? d.webkitFullscreenElement ?? null;\n }\n\n private _fullscreenChangeEventName(): string {\n return \"onfullscreenchange\" in document ? \"fullscreenchange\" : \"webkitfullscreenchange\";\n }\n\n // --- Internal ---\n\n private _onFullscreenChange = (): void => {\n this._applyActive();\n };\n\n // Re-derive `active` by comparing document.fullscreenElement (incl. legacy\n // fallback) against *this instance's* resolved target (§2/§2.1/§5). A null\n // resolved target always yields active=false — there is nothing for this\n // instance to claim as \"mine\".\n private _applyActive(): void {\n const next = this._resolvedTarget !== null && this._fullscreenElement() === this._resolvedTarget;\n this._setActive(next);\n }\n\n private _setActive(v: boolean): void {\n if (this._active === v) return; // same-value guard (§3.3 MUST)\n this._active = v;\n this._target.dispatchEvent(new CustomEvent(\"wcs-fullscreen:change\", {\n detail: { active: v },\n bubbles: true,\n }));\n }\n\n private _setError(error: any): void {\n this._error = error;\n }\n}\n","import { IWcBindable } from \"../types.js\";\nimport { FullscreenCore } from \"../core/FullscreenCore.js\";\n\n/**\n * `<wcs-fullscreen target=\"...\">` — declarative Fullscreen API control.\n *\n * Like `intersection`/`resize`, this Shell operates on a *referenced* element,\n * not itself (docs/fullscreen-tag-design.md §0): `target` resolves which\n * element `requestFullscreen()`/`exitFullscreen()` are invoked on, using the\n * exact same 3-mode resolution as `<wcs-intersect>`\n * (docs/fullscreen-tag-design.md §1):\n *\n * | `target` | operates on | display | use case |\n * |-----------------|-------------------------|-------------|--------------------------|\n * | omitted | first element child | `contents` | wrap a gallery image/video |\n * | `\"#hero\"` / sel | the matched element | `none` | point at a distant node |\n * | `\"self\"` | the element itself | `block` | fullscreen the wrapper |\n *\n * `requestFullscreen()` requires an active user gesture — this element cannot\n * manufacture one. Invoke the command from within a real click handler\n * (typically via the command-token protocol: this element subscribes with\n * `command.requestFullscreen: $command.<token>`, and a button emits the\n * token from its own click handler, e.g. `onclick: $command.<token>`).\n */\nexport class WcsFullscreen extends HTMLElement {\n // SSR (§10): the fullscreenchange subscription is established synchronously\n // on connect, but the Shell still exposes connectedCallbackPromise so the\n // state binder can await it uniformly across all IO nodes before\n // snapshotting.\n static hasConnectedCallbackPromise = true;\n\n static observedAttributes = [\"target\"];\n\n static wcBindable: IWcBindable = {\n ...FullscreenCore.wcBindable,\n inputs: [{ name: \"target\", attribute: \"target\" }],\n // Core の commands をそのまま継承(単一情報源)。network/intersection と同型。\n commands: FullscreenCore.wcBindable.commands,\n };\n\n private _core: FullscreenCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new FullscreenCore(this);\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Attribute accessors ---\n\n get target(): string {\n return this.getAttribute(\"target\") ?? \"\";\n }\n\n set target(value: string) {\n this.setAttribute(\"target\", value);\n }\n\n // --- Core delegated getters ---\n\n get active(): boolean {\n return this._core.active;\n }\n\n get error(): any {\n return this._core.error;\n }\n\n // --- Commands ---\n\n /**\n * Resolve `target` and request fullscreen on it. never-throw: an\n * unresolvable target or an unsupported/rejected API call are both\n * surfaced via `error`, never thrown (docs/fullscreen-tag-design.md §3/§6).\n */\n async requestFullscreen(): Promise<void> {\n const { element } = this._resolveTarget();\n await this._core.requestFullscreen(element);\n }\n\n async exitFullscreen(): Promise<void> {\n await this._core.exitFullscreen();\n }\n\n // --- Internal ---\n\n // Copied verbatim from <wcs-intersect> (Intersect.ts _resolveTarget/_safeQuery,\n // docs/fullscreen-tag-design.md §1): identical 3-mode resolution, only the\n // \"what to do with the resolved element\" step differs.\n private _resolveTarget(): { element: Element | null; display: string } {\n const target = this.target;\n if (target === \"self\") {\n return { element: this, display: \"block\" };\n }\n if (target !== \"\") {\n const scope = this.getRootNode() as Document | ShadowRoot;\n return { element: this._safeQuery(scope, target), display: \"none\" };\n }\n const child = this.firstElementChild;\n if (child) {\n return { element: child, display: \"contents\" };\n }\n return { element: this, display: \"block\" };\n }\n\n private _safeQuery(scope: Document | ShadowRoot, selector: string): Element | null {\n try {\n return scope.querySelector(selector);\n } catch {\n return null;\n }\n }\n\n private _reresolve(): void {\n const { element, display } = this._resolveTarget();\n this.style.display = display;\n this._core.setTarget(element);\n }\n\n // --- Lifecycle ---\n\n connectedCallback(): void {\n this._reresolve();\n this._connectedCallbackPromise = this._core.observe();\n }\n\n disconnectedCallback(): void {\n this._core.dispose();\n }\n\n attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (!this.isConnected) return;\n this._reresolve();\n }\n}\n","import { WcsFullscreen } from \"./components/Fullscreen.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.fullscreen)) {\n customElements.define(config.tagNames.fullscreen, WcsFullscreen);\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapFullscreen(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e={tagNames:{fullscreen:"wcs-fullscreen"}};function t(e){if(null===e||"object"!=typeof e)return e;Object.freeze(e);for(const s of Object.keys(e))t(e[s]);return e}function s(e){if(null===e||"object"!=typeof e)return e;const t={};for(const r of Object.keys(e))t[r]=s(e[r]);return t}let r=null;const n=e;function l(){return r||(r=t(s(e))),r}class c extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"active",event:"wcs-fullscreen:change",getter:e=>e.detail.active}],commands:[{name:"requestFullscreen",async:!0},{name:"exitFullscreen",async:!0}]};_target;_active=!1;_error=null;_resolvedTarget=null;_gen=0;_subscribed=!1;_ready=Promise.resolve();constructor(e){super(),this._target=e??this}get ready(){return this._ready}get active(){return this._active}get error(){return this._error}setTarget(e){this._resolvedTarget=e,this._applyActive()}observe(){return this._subscribed||(this._subscribed=!0,document.addEventListener(this._fullscreenChangeEventName(),this._onFullscreenChange)),this._ready}dispose(){this._gen++,this._subscribed&&(this._subscribed=!1,document.removeEventListener(this._fullscreenChangeEventName(),this._onFullscreenChange))}async requestFullscreen(e){const t=++this._gen;if(this._resolvedTarget=e,!e)return void this._setError({message:"Fullscreen target could not be resolved."});const s=this._requestFullscreenFn(e);if(s)try{if(await s.call(e),t!==this._gen)return;this._setError(null),this._applyActive()}catch(e){if(t!==this._gen)return;this._setError(e)}else this._setError({message:"Fullscreen API is not supported."})}async exitFullscreen(){if(null===this._fullscreenElement())return;const e=this._exitFullscreenFn();if(!e)return;const t=++this._gen;try{if(await e(),t!==this._gen)return;this._setError(null),this._applyActive()}catch(e){if(t!==this._gen)return;this._setError(e)}}_requestFullscreenFn(e){return this._elementFullscreenFn(e,"requestFullscreen")??this._elementFullscreenFn(e,"webkitRequestFullscreen")}_elementFullscreenFn(e,t){return Object.prototype.hasOwnProperty.call(e,t)?e[t]:Element.prototype[t]}_exitFullscreenFn(){const e=document;return e.exitFullscreen?.bind(document)??e.webkitExitFullscreen?.bind(document)}_fullscreenElement(){const e=document;return e.fullscreenElement??e.webkitFullscreenElement??null}_fullscreenChangeEventName(){return"onfullscreenchange"in document?"fullscreenchange":"webkitfullscreenchange"}_onFullscreenChange=()=>{this._applyActive()};_applyActive(){const e=null!==this._resolvedTarget&&this._fullscreenElement()===this._resolvedTarget;this._setActive(e)}_setActive(e){this._active!==e&&(this._active=e,this._target.dispatchEvent(new CustomEvent("wcs-fullscreen:change",{detail:{active:e},bubbles:!0})))}_setError(e){this._error=e}}class i extends HTMLElement{static hasConnectedCallbackPromise=!0;static observedAttributes=["target"];static wcBindable={...c.wcBindable,inputs:[{name:"target",attribute:"target"}],commands:c.wcBindable.commands};_core;_connectedCallbackPromise=Promise.resolve();constructor(){super(),this._core=new c(this)}get connectedCallbackPromise(){return this._connectedCallbackPromise}get target(){return this.getAttribute("target")??""}set target(e){this.setAttribute("target",e)}get active(){return this._core.active}get error(){return this._core.error}async requestFullscreen(){const{element:e}=this._resolveTarget();await this._core.requestFullscreen(e)}async exitFullscreen(){await this._core.exitFullscreen()}_resolveTarget(){const e=this.target;if("self"===e)return{element:this,display:"block"};if(""!==e){const t=this.getRootNode();return{element:this._safeQuery(t,e),display:"none"}}const t=this.firstElementChild;return t?{element:t,display:"contents"}:{element:this,display:"block"}}_safeQuery(e,t){try{return e.querySelector(t)}catch{return null}}_reresolve(){const{element:e,display:t}=this._resolveTarget();this.style.display=t,this._core.setTarget(e)}connectedCallback(){this._reresolve(),this._connectedCallbackPromise=this._core.observe()}disconnectedCallback(){this._core.dispose()}attributeChangedCallback(e,t,s){t!==s&&this.isConnected&&this._reresolve()}}function a(t){var s;t&&((s=t).tagNames&&Object.assign(e.tagNames,s.tagNames),r=null),customElements.get(n.tagNames.fullscreen)||customElements.define(n.tagNames.fullscreen,i)}export{c as FullscreenCore,i as WcsFullscreen,a as bootstrapFullscreen,l as getConfig};
|
|
2
|
+
//# sourceMappingURL=index.esm.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.min.js","sources":["../src/config.ts","../src/core/FullscreenCore.ts","../src/components/Fullscreen.ts","../src/bootstrapFullscreen.ts","../src/registerComponents.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n tagNames: {\n fullscreen: string;\n };\n}\n\nconst _config: IInternalConfig = {\n tagNames: {\n fullscreen: \"wcs-fullscreen\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import { IWcBindable } from \"../types.js\";\n\n/**\n * Headless Fullscreen API primitive. Unlike most wcstack IO nodes, this Core\n * does not operate on itself: it drives `requestFullscreen()` /\n * `exitFullscreen()` on a *referenced* Element that the Shell resolves via its\n * `target` attribute (docs/fullscreen-tag-design.md §0). The Core only ever\n * receives already-resolved `Element`s from its callers — it has no opinion on\n * how `target` selectors are parsed.\n *\n * `document.fullscreenElement` is a single document-wide value, so this Core\n * always compares against the *last element it resolved* (via\n * `requestFullscreen()`/`setTarget()`), never against \"is the document\n * fullscreen at all\" — that comparison is what keeps multiple concurrent\n * `<wcs-fullscreen>` instances from all reporting the same `active` value\n * (docs/fullscreen-tag-design.md §2.1, MUST).\n */\nexport class FullscreenCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"active\", event: \"wcs-fullscreen:change\", getter: (e: Event) => (e as CustomEvent).detail.active },\n ],\n commands: [\n { name: \"requestFullscreen\", async: true },\n { name: \"exitFullscreen\", async: true },\n ],\n };\n\n private _target: EventTarget;\n private _active: boolean = false;\n // Single error slot (§8): null means \"no recent failure\". Fullscreen's\n // gesture-rejection failure is a one-shot event, not a persistent state\n // machine like permission's 4-value surface — active/error are two\n // orthogonal, independently-observable axes.\n private _error: any = null;\n\n // The last Element this Core resolved via requestFullscreen()/setTarget().\n // Compared against document.fullscreenElement on every fullscreenchange so\n // each instance judges only its own target (§2.1). null means \"no target\n // resolved yet\" — active must stay false in that case.\n private _resolvedTarget: Element | null = null;\n\n // Generation guard (§6): Core-scoped (one per Core, not per-target),\n // mirroring fetch/upload. document.fullscreenElement is a single\n // document-wide slot, so at most one in-flight request/exit is meaningful\n // per Core at a time.\n private _gen = 0;\n\n // True once observe() has attached the document-level fullscreenchange\n // listener. Guards observe() so a redundant call does not double-subscribe;\n // dispose() resets it so a later observe() resumes cleanly.\n private _subscribed = false;\n\n // SSR (§10): no asynchronous probe to await — observe() completes\n // synchronously, so readiness is immediate.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get ready(): Promise<void> {\n return this._ready;\n }\n\n get active(): boolean {\n return this._active;\n }\n\n get error(): any {\n return this._error;\n }\n\n /**\n * Update the resolved target without issuing a fullscreen request (e.g. the\n * Shell re-resolves `target` on attribute change / connect). Re-evaluates\n * `active` against the current `document.fullscreenElement` so the state\n * stays correct even if the target changed while already fullscreen.\n */\n setTarget(element: Element | null): void {\n this._resolvedTarget = element;\n this._applyActive();\n }\n\n // Lifecycle (§10/§3.5). Idempotent: a second observe() while already\n // subscribed is a no-op (no double listener). Synchronous overall (no probe\n // to await), so the returned promise is only for API uniformity with other\n // IO nodes.\n observe(): Promise<void> {\n if (!this._subscribed) {\n this._subscribed = true;\n document.addEventListener(this._fullscreenChangeEventName(), this._onFullscreenChange);\n }\n return this._ready;\n }\n\n dispose(): void {\n this._gen++;\n if (this._subscribed) {\n this._subscribed = false;\n document.removeEventListener(this._fullscreenChangeEventName(), this._onFullscreenChange);\n }\n }\n\n /**\n * Request fullscreen on `element`. never-throw (§3/§6): a missing API or a\n * rejected promise (e.g. a `TypeError` from a call outside a user\n * gesture, per the WHATWG Fullscreen spec's transient-activation check) is\n * caught and surfaced via `error`, never thrown. The caller\n * (Shell) is responsible for resolving `target` and for ensuring this is\n * invoked from within an actual user gesture — this Core cannot manufacture\n * one (docs/fullscreen-tag-design.md §3).\n */\n async requestFullscreen(element: Element | null): Promise<void> {\n const gen = ++this._gen;\n this._resolvedTarget = element;\n if (!element) {\n // Distinct from \"API is not supported\" (below): the Shell's `target`\n // selector did not resolve to any element (missing/typo'd selector).\n // Conflating the two previously misled users into thinking Fullscreen\n // itself was unsupported when only their selector was wrong.\n this._setError({ message: \"Fullscreen target could not be resolved.\" });\n return;\n }\n const fn = this._requestFullscreenFn(element);\n if (!fn) {\n this._setError({ message: \"Fullscreen API is not supported.\" });\n return;\n }\n try {\n await fn.call(element);\n if (gen !== this._gen) return; // stale: dispose()/superseding call ran\n this._setError(null);\n this._applyActive();\n } catch (e: any) {\n if (gen !== this._gen) return; // stale\n this._setError(e);\n }\n }\n\n /**\n * Exit fullscreen. Silent no-op (§7) when nothing is currently fullscreen or\n * the API is unsupported — both are treated as \"already achieved the exit\n * intent\", not as errors, keeping repeated calls safe and never-throw.\n */\n async exitFullscreen(): Promise<void> {\n // no-op checks come BEFORE the generation bump: a call that does nothing\n // must not supersede an in-flight requestFullscreen() — bumping first\n // would make the pending request's settle handling stale and silently\n // swallow its error/active updates.\n if (this._fullscreenElement() === null) return; // already not fullscreen: silent no-op\n const fn = this._exitFullscreenFn();\n if (!fn) return; // unsupported: silent no-op (semantically already \"not fullscreen\")\n const gen = ++this._gen;\n try {\n await fn();\n if (gen !== this._gen) return; // stale\n this._setError(null);\n this._applyActive();\n } catch (e: any) {\n if (gen !== this._gen) return; // stale\n this._setError(e);\n }\n }\n\n // --- API resolution layer (§4): call-time, never cached. Lets tests\n // install/remove the standard/legacy APIs freely and lets an unsupported\n // environment be detected correctly on every call. ---\n\n private _requestFullscreenFn(el: Element): (() => Promise<void>) | undefined {\n return this._elementFullscreenFn(el, \"requestFullscreen\")\n ?? this._elementFullscreenFn(el, \"webkitRequestFullscreen\");\n }\n\n // Resolve a fullscreen method for `el` WITHOUT a naive `el[name]` lookup.\n // A plain lookup walks the whole prototype chain — and <wcs-fullscreen>\n // itself declares a `requestFullscreen()` *command* method, so when the\n // resolved target is the Shell element (target=\"self\", or target omitted\n // with no children), the naive lookup would find the Shell's own command\n // instead of the platform API and recurse infinitely (stack overflow).\n // Instead: check the element's own properties (how tests install stubs —\n // happy-dom has no Fullscreen API — and how a deliberate per-element\n // monkey-patch would appear), then jump straight to Element.prototype,\n // where the platform defines the real methods. Both the standard and the\n // legacy webkit name go through this same resolution for symmetry.\n private _elementFullscreenFn(el: Element, name: string): (() => Promise<void>) | undefined {\n if (Object.prototype.hasOwnProperty.call(el, name)) {\n return (el as any)[name];\n }\n return (Element.prototype as any)[name];\n }\n\n private _exitFullscreenFn(): (() => Promise<void>) | undefined {\n const d = document as any;\n return d.exitFullscreen?.bind(document) ?? d.webkitExitFullscreen?.bind(document);\n }\n\n private _fullscreenElement(): Element | null {\n const d = document as any;\n return d.fullscreenElement ?? d.webkitFullscreenElement ?? null;\n }\n\n private _fullscreenChangeEventName(): string {\n return \"onfullscreenchange\" in document ? \"fullscreenchange\" : \"webkitfullscreenchange\";\n }\n\n // --- Internal ---\n\n private _onFullscreenChange = (): void => {\n this._applyActive();\n };\n\n // Re-derive `active` by comparing document.fullscreenElement (incl. legacy\n // fallback) against *this instance's* resolved target (§2/§2.1/§5). A null\n // resolved target always yields active=false — there is nothing for this\n // instance to claim as \"mine\".\n private _applyActive(): void {\n const next = this._resolvedTarget !== null && this._fullscreenElement() === this._resolvedTarget;\n this._setActive(next);\n }\n\n private _setActive(v: boolean): void {\n if (this._active === v) return; // same-value guard (§3.3 MUST)\n this._active = v;\n this._target.dispatchEvent(new CustomEvent(\"wcs-fullscreen:change\", {\n detail: { active: v },\n bubbles: true,\n }));\n }\n\n private _setError(error: any): void {\n this._error = error;\n }\n}\n","import { IWcBindable } from \"../types.js\";\nimport { FullscreenCore } from \"../core/FullscreenCore.js\";\n\n/**\n * `<wcs-fullscreen target=\"...\">` — declarative Fullscreen API control.\n *\n * Like `intersection`/`resize`, this Shell operates on a *referenced* element,\n * not itself (docs/fullscreen-tag-design.md §0): `target` resolves which\n * element `requestFullscreen()`/`exitFullscreen()` are invoked on, using the\n * exact same 3-mode resolution as `<wcs-intersect>`\n * (docs/fullscreen-tag-design.md §1):\n *\n * | `target` | operates on | display | use case |\n * |-----------------|-------------------------|-------------|--------------------------|\n * | omitted | first element child | `contents` | wrap a gallery image/video |\n * | `\"#hero\"` / sel | the matched element | `none` | point at a distant node |\n * | `\"self\"` | the element itself | `block` | fullscreen the wrapper |\n *\n * `requestFullscreen()` requires an active user gesture — this element cannot\n * manufacture one. Invoke the command from within a real click handler\n * (typically via the command-token protocol: this element subscribes with\n * `command.requestFullscreen: $command.<token>`, and a button emits the\n * token from its own click handler, e.g. `onclick: $command.<token>`).\n */\nexport class WcsFullscreen extends HTMLElement {\n // SSR (§10): the fullscreenchange subscription is established synchronously\n // on connect, but the Shell still exposes connectedCallbackPromise so the\n // state binder can await it uniformly across all IO nodes before\n // snapshotting.\n static hasConnectedCallbackPromise = true;\n\n static observedAttributes = [\"target\"];\n\n static wcBindable: IWcBindable = {\n ...FullscreenCore.wcBindable,\n inputs: [{ name: \"target\", attribute: \"target\" }],\n // Core の commands をそのまま継承(単一情報源)。network/intersection と同型。\n commands: FullscreenCore.wcBindable.commands,\n };\n\n private _core: FullscreenCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new FullscreenCore(this);\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Attribute accessors ---\n\n get target(): string {\n return this.getAttribute(\"target\") ?? \"\";\n }\n\n set target(value: string) {\n this.setAttribute(\"target\", value);\n }\n\n // --- Core delegated getters ---\n\n get active(): boolean {\n return this._core.active;\n }\n\n get error(): any {\n return this._core.error;\n }\n\n // --- Commands ---\n\n /**\n * Resolve `target` and request fullscreen on it. never-throw: an\n * unresolvable target or an unsupported/rejected API call are both\n * surfaced via `error`, never thrown (docs/fullscreen-tag-design.md §3/§6).\n */\n async requestFullscreen(): Promise<void> {\n const { element } = this._resolveTarget();\n await this._core.requestFullscreen(element);\n }\n\n async exitFullscreen(): Promise<void> {\n await this._core.exitFullscreen();\n }\n\n // --- Internal ---\n\n // Copied verbatim from <wcs-intersect> (Intersect.ts _resolveTarget/_safeQuery,\n // docs/fullscreen-tag-design.md §1): identical 3-mode resolution, only the\n // \"what to do with the resolved element\" step differs.\n private _resolveTarget(): { element: Element | null; display: string } {\n const target = this.target;\n if (target === \"self\") {\n return { element: this, display: \"block\" };\n }\n if (target !== \"\") {\n const scope = this.getRootNode() as Document | ShadowRoot;\n return { element: this._safeQuery(scope, target), display: \"none\" };\n }\n const child = this.firstElementChild;\n if (child) {\n return { element: child, display: \"contents\" };\n }\n return { element: this, display: \"block\" };\n }\n\n private _safeQuery(scope: Document | ShadowRoot, selector: string): Element | null {\n try {\n return scope.querySelector(selector);\n } catch {\n return null;\n }\n }\n\n private _reresolve(): void {\n const { element, display } = this._resolveTarget();\n this.style.display = display;\n this._core.setTarget(element);\n }\n\n // --- Lifecycle ---\n\n connectedCallback(): void {\n this._reresolve();\n this._connectedCallbackPromise = this._core.observe();\n }\n\n disconnectedCallback(): void {\n this._core.dispose();\n }\n\n attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (!this.isConnected) return;\n this._reresolve();\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapFullscreen(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n","import { WcsFullscreen } from \"./components/Fullscreen.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.fullscreen)) {\n customElements.define(config.tagNames.fullscreen, WcsFullscreen);\n }\n}\n"],"names":["_config","tagNames","fullscreen","deepFreeze","obj","Object","freeze","key","keys","deepClone","clone","frozenConfig","config","getConfig","FullscreenCore","EventTarget","static","protocol","version","properties","name","event","getter","e","detail","active","commands","async","_target","_active","_error","_resolvedTarget","_gen","_subscribed","_ready","Promise","resolve","constructor","target","super","this","ready","error","setTarget","element","_applyActive","observe","document","addEventListener","_fullscreenChangeEventName","_onFullscreenChange","dispose","removeEventListener","requestFullscreen","gen","_setError","message","fn","_requestFullscreenFn","call","exitFullscreen","_fullscreenElement","_exitFullscreenFn","el","_elementFullscreenFn","prototype","hasOwnProperty","Element","d","bind","webkitExitFullscreen","fullscreenElement","webkitFullscreenElement","next","_setActive","v","dispatchEvent","CustomEvent","bubbles","WcsFullscreen","HTMLElement","wcBindable","inputs","attribute","_core","_connectedCallbackPromise","connectedCallbackPromise","getAttribute","value","setAttribute","_resolveTarget","display","scope","getRootNode","_safeQuery","child","firstElementChild","selector","querySelector","_reresolve","style","connectedCallback","disconnectedCallback","attributeChangedCallback","_name","oldValue","newValue","isConnected","bootstrapFullscreen","userConfig","partialConfig","assign","customElements","get","define"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wcstack/fullscreen",
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "Declarative Fullscreen API component for Web Components. Framework-agnostic requestFullscreen()/exitFullscreen() control via wc-bindable-protocol.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.esm.js",
|
|
7
|
+
"module": "./dist/index.esm.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.esm.js"
|
|
13
|
+
},
|
|
14
|
+
"./auto": "./dist/auto.min.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rimraf dist .tsc-out",
|
|
21
|
+
"build": "rimraf dist .tsc-out && tsc && rollup -c",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"lint": "eslint src",
|
|
26
|
+
"version:patch": "npm version patch",
|
|
27
|
+
"version:minor": "npm version minor",
|
|
28
|
+
"version:major": "npm version major",
|
|
29
|
+
"prepublishOnly": "npm run build && npm run test:coverage"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"web-components",
|
|
33
|
+
"fullscreen",
|
|
34
|
+
"requestfullscreen",
|
|
35
|
+
"custom-elements",
|
|
36
|
+
"wc-bindable",
|
|
37
|
+
"declarative",
|
|
38
|
+
"zero-dependencies",
|
|
39
|
+
"framework-agnostic"
|
|
40
|
+
],
|
|
41
|
+
"author": "mogera551",
|
|
42
|
+
"homepage": "https://wcstack.github.io",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/wcstack/wcstack.git",
|
|
46
|
+
"directory": "packages/fullscreen"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/wcstack/wcstack/issues"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/js": "^9.39.1",
|
|
54
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
55
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
56
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
57
|
+
"@vitest/ui": "^4.0.15",
|
|
58
|
+
"eslint": "^9.39.1",
|
|
59
|
+
"globals": "^16.5.0",
|
|
60
|
+
"happy-dom": "^20.0.11",
|
|
61
|
+
"rimraf": "^6.0.1",
|
|
62
|
+
"rollup": "^4.22.4",
|
|
63
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
64
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
65
|
+
"tslib": "^2.8.1",
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"typescript-eslint": "^8.49.0",
|
|
68
|
+
"vitest": "^4.0.15"
|
|
69
|
+
}
|
|
70
|
+
}
|