@wcstack/accelerometer 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 +137 -0
- package/README.md +140 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +1 -0
- package/dist/index.d.ts +218 -0
- package/dist/index.esm.js +366 -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 +71 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @wcstack/accelerometer
|
|
2
|
+
|
|
3
|
+
`@wcstack/accelerometer` は wcstack エコシステム向けのヘッドレスな Generic Sensor API(Accelerometer)コンポーネントです。
|
|
4
|
+
|
|
5
|
+
視覚的な UI ウィジェットではありません。デバイスの加速度読み取りをリアクティブな state に変える**非同期プリミティブノード**です。
|
|
6
|
+
|
|
7
|
+
`@wcstack/state` と組み合わせると、`<wcs-accelerometer>` はパス契約で直接バインドできます:
|
|
8
|
+
|
|
9
|
+
- **入力サーフェス**: `frequency`(サンプリングレート、Hz)
|
|
10
|
+
- **出力 state サーフェス**: `x`、`y`、`z`、`error`
|
|
11
|
+
|
|
12
|
+
`@wcstack/accelerometer` は [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md)(Core / Shell / Binding Contract)アーキテクチャに従います:
|
|
13
|
+
|
|
14
|
+
- **Core**(`AccelerometerCore`)がプラットフォームの`Accelerometer`を構築し、live な`reading`/`error`イベントを追従
|
|
15
|
+
- **Shell**(`<wcs-accelerometer>`)がその state を DOM ライフサイクルに接続
|
|
16
|
+
- **Binding Contract**(`static wcBindable`)が観測可能な`properties`と`start`/`stop`の`commands`を宣言
|
|
17
|
+
|
|
18
|
+
## なぜ存在するか — プラットフォームAPI自体がnever-throwと最初から一致する稀な例
|
|
19
|
+
|
|
20
|
+
Generic Sensor API の`Accelerometer`/`Gyroscope`/`Magnetometer`/`AmbientLightSensor`族は全て共通の形状を持ちます: `.start()`/`.stop()`、サンプルごとの`'reading'`イベント、そして——注目すべき点として——失敗は例外ではなく**`'error'`イベント**で通知されます。これは wcstack の never-throw 方針と最初から噛み合っています。唯一防御的な`try/catch`が要るのは、権限拒否やPermissions-Policyブロックで**同期的に例外を投げうる**`Accelerometer`のコンストラクタ自体です。
|
|
21
|
+
|
|
22
|
+
> **`@wcstack/permission`との合成を推奨。** `navigator.permissions.query({name:"accelerometer"})`が既に存在するため、`<wcs-accelerometer>`は`<wcs-permission name="accelerometer">`と併置して`granted`/`denied`/`prompt`状態を得てください(権限状態はこのノード自身では重複実装しません、`docs/sensor-tag-design.md`参照)。
|
|
23
|
+
|
|
24
|
+
> **Chromium/Android中心の対応。** デスクトップでは`Accelerometer`クラスが存在しても`SecurityError`になりがちです。unsupported/deniedを既定状態として設計してください。
|
|
25
|
+
|
|
26
|
+
## インストール
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @wcstack/accelerometer
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## クイックスタート
|
|
33
|
+
|
|
34
|
+
### 1. 加速度をライブ表示
|
|
35
|
+
|
|
36
|
+
`<wcs-accelerometer>`は接続時に**自動開始しません** — バインドしただけでは
|
|
37
|
+
`x`/`y`/`z`は初期値`null`のままです。読み取りを流すには(例えばボタンから)
|
|
38
|
+
`start`コマンドを発火する必要があります:
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
42
|
+
<script type="module" src="https://esm.run/@wcstack/accelerometer/auto"></script>
|
|
43
|
+
|
|
44
|
+
<wcs-state>
|
|
45
|
+
<script type="module">
|
|
46
|
+
export default {
|
|
47
|
+
$commandTokens: ["startAccel"],
|
|
48
|
+
x: null, y: null, z: null,
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
51
|
+
</wcs-state>
|
|
52
|
+
|
|
53
|
+
<wcs-accelerometer
|
|
54
|
+
data-wcs="x: x; y: y; z: z; command.start: $command.startAccel"
|
|
55
|
+
></wcs-accelerometer>
|
|
56
|
+
|
|
57
|
+
<button data-wcs="onclick: $command.startAccel">開始</button>
|
|
58
|
+
<p data-wcs="textContent: x"></p>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
ボタンは`<wcs-accelerometer>`に直接触れません: クリックは`startAccel`コマンドトークンを発火し(`$commandTokens: ["startAccel"]`で名前を宣言)、`<wcs-accelerometer>`は`command.start: $command.startAccel`でそれを購読します([command-token プロトコル](../state/) — コマンドメソッドを持つ要素が*subscriber*であり、emitter ではありません)。
|
|
62
|
+
|
|
63
|
+
### 2. 権限を確認してから start する
|
|
64
|
+
|
|
65
|
+
この例では`@wcstack/permission`の登録も必要です(例1の`@wcstack/state` /
|
|
66
|
+
`@wcstack/accelerometer`の script に加えて)。`accelGranted`を宣言する
|
|
67
|
+
独立した`<wcs-state>`を持ちます:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<script type="module" src="https://esm.run/@wcstack/permission/auto"></script>
|
|
71
|
+
|
|
72
|
+
<wcs-state>
|
|
73
|
+
<script type="module">
|
|
74
|
+
export default {
|
|
75
|
+
$commandTokens: ["startAccel"],
|
|
76
|
+
accelGranted: false,
|
|
77
|
+
};
|
|
78
|
+
</script>
|
|
79
|
+
</wcs-state>
|
|
80
|
+
|
|
81
|
+
<wcs-permission name="accelerometer" data-wcs="granted: accelGranted"></wcs-permission>
|
|
82
|
+
<wcs-accelerometer data-wcs="command.start: $command.startAccel"></wcs-accelerometer>
|
|
83
|
+
|
|
84
|
+
<button data-wcs="onclick: $command.startAccel; disabled: accelGranted|not">開始</button>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
バインドする state パスは事前にすべて宣言する必要があります — 未宣言のパスへのバインドは初期化時に例外を投げます。`data-wcs`パス内の否定は先頭`!`ではなく`|not`フィルタ(`accelGranted|not`)で行います。
|
|
88
|
+
|
|
89
|
+
## 属性 / 入力
|
|
90
|
+
|
|
91
|
+
| 属性 | 型 | 既定値 | 説明 |
|
|
92
|
+
| ----------- | ------ | ------ | ---- |
|
|
93
|
+
| `frequency` | number | — | サンプリングレート(Hz)。`Accelerometer`コンストラクタへそのまま渡る。 |
|
|
94
|
+
|
|
95
|
+
## 観測可能プロパティ(出力)
|
|
96
|
+
|
|
97
|
+
| プロパティ | イベント | 説明 |
|
|
98
|
+
| ---------- | --------------------------- | ---- |
|
|
99
|
+
| `x` | `wcs-accelerometer:reading` | x軸方向の加速度。初回読み取り前は`null`。 |
|
|
100
|
+
| `y` | `wcs-accelerometer:reading` | y軸方向の加速度。 |
|
|
101
|
+
| `z` | `wcs-accelerometer:reading` | z軸方向の加速度。 |
|
|
102
|
+
| `error` | `wcs-accelerometer:error` | 正規化された`{ error, message }`、無ければ`null`。 |
|
|
103
|
+
|
|
104
|
+
`x`/`y`/`z`は単一の`wcs-accelerometer:reading`イベントから派生します(ネイティブの1回の`reading`イベントで3軸が同時に更新される)。
|
|
105
|
+
|
|
106
|
+
## コマンド
|
|
107
|
+
|
|
108
|
+
| コマンド | 非同期 | 説明 |
|
|
109
|
+
| -------- | ------ | ---- |
|
|
110
|
+
| `start` | いいえ | センサーを構築(never-throw: コンストラクタの同期例外はキャッチし`error`へ)し読み取りを開始する。 |
|
|
111
|
+
| `stop` | いいえ | センサーを停止しリスナーを解除する。未開始でも安全に呼べる。 |
|
|
112
|
+
|
|
113
|
+
## 注意・制限
|
|
114
|
+
|
|
115
|
+
- **`_gen`世代ガードは無し。** `start()`/`stop()`は同期的な購読/購読解除のトグルであり、`dispose()`とレースしうる非同期probeが存在しません(`docs/sensor-tag-design.md` §1.5)。
|
|
116
|
+
- **`error`は sticky(据え置き)です。** 最後に観測した失敗(`unsupported`、`SecurityError`等)を保持し、その後の`start()`成功や`reading`受信では自動クリアされません。`stop()`+`start()`でリトライが成功しても直前の`error`は残り続けます。必要なら利用側の state でクリア/再解釈してください。
|
|
117
|
+
- **生の`new Accelerometer(...)`は唯一のガード付き構築ヘルパー以外では呼ばない。** 権限拒否・Permissions-Policyブロックは同期的に例外を投げます。
|
|
118
|
+
- 権限状態(`granted`/`denied`/`prompt`)は意図的にこのノードでは重複実装していません — `<wcs-permission name="accelerometer">`と合成してください。
|
|
119
|
+
|
|
120
|
+
## ヘッドレス利用(`AccelerometerCore`)
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { AccelerometerCore } from "@wcstack/accelerometer";
|
|
124
|
+
|
|
125
|
+
const core = new AccelerometerCore();
|
|
126
|
+
core.addEventListener("wcs-accelerometer:reading", (e) => {
|
|
127
|
+
console.log((e as CustomEvent).detail); // { x, y, z }
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
core.start();
|
|
131
|
+
// 後始末:
|
|
132
|
+
core.dispose();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## ライセンス
|
|
136
|
+
|
|
137
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @wcstack/accelerometer
|
|
2
|
+
|
|
3
|
+
`@wcstack/accelerometer` is a headless Generic Sensor API (Accelerometer) component for the wcstack ecosystem.
|
|
4
|
+
|
|
5
|
+
It is not a visual UI widget.
|
|
6
|
+
It is an **async primitive node** that turns device acceleration readings into reactive state.
|
|
7
|
+
|
|
8
|
+
With `@wcstack/state`, `<wcs-accelerometer>` can be bound directly through path contracts:
|
|
9
|
+
|
|
10
|
+
- **input surface**: `frequency` (sampling rate in Hz)
|
|
11
|
+
- **output state surface**: `x`, `y`, `z`, `error`
|
|
12
|
+
|
|
13
|
+
This means tilt/shake-gesture UI can be expressed declaratively in HTML, without writing `Accelerometer`/`reading`/`error`-listener glue in your UI layer.
|
|
14
|
+
|
|
15
|
+
`@wcstack/accelerometer` follows the [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md) (Core / Shell / Binding Contract) architecture:
|
|
16
|
+
|
|
17
|
+
- **Core** (`AccelerometerCore`) constructs the platform `Accelerometer`, tracks its live `reading`/`error` events
|
|
18
|
+
- **Shell** (`<wcs-accelerometer>`) connects that state to DOM lifecycle
|
|
19
|
+
- **Binding Contract** (`static wcBindable`) declares observable `properties` and `start`/`stop` `commands`
|
|
20
|
+
|
|
21
|
+
## Why this exists — a rare case where the platform API already matches never-throw
|
|
22
|
+
|
|
23
|
+
The Generic Sensor API's `Accelerometer`/`Gyroscope`/`Magnetometer`/`AmbientLightSensor` family all share one base shape: `.start()`/`.stop()`, a `'reading'` event per sample, and — notably — an `'error'` **event** for failures instead of a thrown exception. This already lines up with wcstack's never-throw convention; the one place this Core still needs a defensive `try/catch` is the synchronous `Accelerometer` **constructor** itself, which can throw (`SecurityError`) on permission denial or a Permissions-Policy block.
|
|
24
|
+
|
|
25
|
+
> **Compose with `@wcstack/permission`.** `navigator.permissions.query({name:"accelerometer"})` already exists — pair `<wcs-accelerometer>` with `<wcs-permission name="accelerometer">` for `granted`/`denied`/`prompt` status rather than duplicating that state here (see `docs/sensor-tag-design.md`).
|
|
26
|
+
|
|
27
|
+
> **Chromium/Android-centric support.** Desktop browsers commonly reject with `SecurityError` even when the `Accelerometer` class exists. Design any UI around `unsupported`/denied being the common case, not the exception.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @wcstack/accelerometer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### 1. Read live acceleration
|
|
38
|
+
|
|
39
|
+
`<wcs-accelerometer>` does **not** auto-start on connect — binding alone leaves
|
|
40
|
+
`x`/`y`/`z` at their initial `null`. You must fire the `start` command
|
|
41
|
+
(e.g. from a button) before readings flow:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
45
|
+
<script type="module" src="https://esm.run/@wcstack/accelerometer/auto"></script>
|
|
46
|
+
|
|
47
|
+
<wcs-state>
|
|
48
|
+
<script type="module">
|
|
49
|
+
export default {
|
|
50
|
+
$commandTokens: ["startAccel"],
|
|
51
|
+
x: null, y: null, z: null,
|
|
52
|
+
};
|
|
53
|
+
</script>
|
|
54
|
+
</wcs-state>
|
|
55
|
+
|
|
56
|
+
<wcs-accelerometer
|
|
57
|
+
data-wcs="x: x; y: y; z: z; command.start: $command.startAccel"
|
|
58
|
+
></wcs-accelerometer>
|
|
59
|
+
|
|
60
|
+
<button data-wcs="onclick: $command.startAccel">Start</button>
|
|
61
|
+
<p data-wcs="textContent: x"></p>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The button never touches `<wcs-accelerometer>` directly: its click emits the `startAccel` command token (`$commandTokens: ["startAccel"]` declares the name), and `<wcs-accelerometer>` subscribes to it via `command.start: $command.startAccel` (the [command-token protocol](../state/) — the element with the command method is the *subscriber*, not the emitter).
|
|
65
|
+
|
|
66
|
+
### 2. Gate on permission, then start
|
|
67
|
+
|
|
68
|
+
This example also needs `@wcstack/permission` registered (alongside the
|
|
69
|
+
`@wcstack/state` / `@wcstack/accelerometer` scripts from example 1), with its
|
|
70
|
+
own self-contained `<wcs-state>` declaring `accelGranted`:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<script type="module" src="https://esm.run/@wcstack/permission/auto"></script>
|
|
74
|
+
|
|
75
|
+
<wcs-state>
|
|
76
|
+
<script type="module">
|
|
77
|
+
export default {
|
|
78
|
+
$commandTokens: ["startAccel"],
|
|
79
|
+
accelGranted: false,
|
|
80
|
+
};
|
|
81
|
+
</script>
|
|
82
|
+
</wcs-state>
|
|
83
|
+
|
|
84
|
+
<wcs-permission name="accelerometer" data-wcs="granted: accelGranted"></wcs-permission>
|
|
85
|
+
<wcs-accelerometer data-wcs="command.start: $command.startAccel"></wcs-accelerometer>
|
|
86
|
+
|
|
87
|
+
<button data-wcs="onclick: $command.startAccel; disabled: accelGranted|not">Start</button>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Every bound state path must be declared up front — binding an undeclared path throws at initialization. Negation in a `data-wcs` path is done with the `|not` filter (`accelGranted|not`), not a leading `!`.
|
|
91
|
+
|
|
92
|
+
## Attributes / Inputs
|
|
93
|
+
|
|
94
|
+
| Attribute | Type | Default | Description |
|
|
95
|
+
| ----------- | ------ | ------- | ------------ |
|
|
96
|
+
| `frequency` | number | — | Sampling rate in Hz, forwarded to the `Accelerometer` constructor. |
|
|
97
|
+
|
|
98
|
+
## Observable Properties (outputs)
|
|
99
|
+
|
|
100
|
+
| Property | Event | Description |
|
|
101
|
+
| -------- | ------------------------- | ------------ |
|
|
102
|
+
| `x` | `wcs-accelerometer:reading` | Acceleration along the x-axis, or `null` before the first reading. |
|
|
103
|
+
| `y` | `wcs-accelerometer:reading` | Acceleration along the y-axis. |
|
|
104
|
+
| `z` | `wcs-accelerometer:reading` | Acceleration along the z-axis. |
|
|
105
|
+
| `error` | `wcs-accelerometer:error` | Normalized `{ error, message }`, or `null`. |
|
|
106
|
+
|
|
107
|
+
`x`/`y`/`z` all derive from the single `wcs-accelerometer:reading` event (one native `reading` event updates all three axes together).
|
|
108
|
+
|
|
109
|
+
## Commands
|
|
110
|
+
|
|
111
|
+
| Command | Async | Description |
|
|
112
|
+
| ------- | ----- | ------------ |
|
|
113
|
+
| `start` | no | Construct the sensor (never-throw: a synchronous constructor exception is caught and surfaced via `error`) and begin reading. |
|
|
114
|
+
| `stop` | no | Stop the sensor and detach its listeners. Safe to call when not started. |
|
|
115
|
+
|
|
116
|
+
## Notes & limitations
|
|
117
|
+
|
|
118
|
+
- **No `_gen` generation guard.** `start()`/`stop()` are a synchronous subscribe/unsubscribe toggle with no asynchronous probe to race against a `dispose()` — see `docs/sensor-tag-design.md` §1.5.
|
|
119
|
+
- **`error` is sticky.** It holds the last observed failure (e.g. `unsupported`, `SecurityError`) and is **not** auto-cleared by a later successful `start()` or by incoming `reading`s. A `stop()` + `start()` retry that succeeds still leaves the previous `error` in place — clear or reinterpret it in your own state if needed.
|
|
120
|
+
- **Never call the raw `new Accelerometer(...)` anywhere but the one guarded construction helper** — permission denial and Permissions-Policy blocks throw synchronously.
|
|
121
|
+
- Permission status (`granted`/`denied`/`prompt`) is intentionally not duplicated here — compose with `<wcs-permission name="accelerometer">`.
|
|
122
|
+
|
|
123
|
+
## Headless usage (`AccelerometerCore`)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { AccelerometerCore } from "@wcstack/accelerometer";
|
|
127
|
+
|
|
128
|
+
const core = new AccelerometerCore();
|
|
129
|
+
core.addEventListener("wcs-accelerometer:reading", (e) => {
|
|
130
|
+
console.log((e as CustomEvent).detail); // { x, y, z }
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
core.start();
|
|
134
|
+
// later:
|
|
135
|
+
core.dispose();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bootstrapAccelerometer}from"./index.esm.min.js";bootstrapAccelerometer();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
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 accelerometer: string;
|
|
24
|
+
}
|
|
25
|
+
interface IWritableTagNames {
|
|
26
|
+
accelerometer?: string;
|
|
27
|
+
}
|
|
28
|
+
interface IConfig {
|
|
29
|
+
readonly tagNames: ITagNames;
|
|
30
|
+
}
|
|
31
|
+
interface IWritableConfig {
|
|
32
|
+
tagNames?: IWritableTagNames;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A single `reading` sample from the Accelerometer sensor: linear acceleration
|
|
37
|
+
* along the x/y/z axes, in m/s^2 (including gravity — this is the plain
|
|
38
|
+
* `Accelerometer`, not `LinearAccelerationSensor`).
|
|
39
|
+
*/
|
|
40
|
+
interface WcsAccelerometerReading {
|
|
41
|
+
x: number | null;
|
|
42
|
+
y: number | null;
|
|
43
|
+
z: number | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error detail published on the `wcs-accelerometer:error` event. Mirrors the
|
|
47
|
+
* Generic Sensor API's `SensorErrorEvent.error` (a `DOMException`-like value)
|
|
48
|
+
* flattened to a plain object, plus the synthetic `"unsupported"` name used
|
|
49
|
+
* when the global `Accelerometer` constructor is absent.
|
|
50
|
+
*/
|
|
51
|
+
interface WcsAccelerometerErrorDetail {
|
|
52
|
+
error: string;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Value types for AccelerometerCore (headless) — the observable state
|
|
57
|
+
* properties. Use with `bind()` from a wc-bindable binding core for
|
|
58
|
+
* compile-time type checking.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const core = new AccelerometerCore();
|
|
63
|
+
* bind(core, (name: keyof WcsAccelerometerCoreValues, value) => { ... });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
interface WcsAccelerometerCoreValues extends WcsAccelerometerReading {
|
|
67
|
+
error: WcsAccelerometerErrorDetail | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Value types for the Shell (`<wcs-accelerometer>`) — identical observable
|
|
71
|
+
* surface to the Core, plus the `frequency` attribute-backed input.
|
|
72
|
+
*/
|
|
73
|
+
type WcsAccelerometerValues = WcsAccelerometerCoreValues;
|
|
74
|
+
|
|
75
|
+
declare function bootstrapAccelerometer(userConfig?: IWritableConfig): void;
|
|
76
|
+
|
|
77
|
+
declare function getConfig(): IConfig;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Headless Accelerometer primitive. A thin, framework-agnostic wrapper around
|
|
81
|
+
* the Generic Sensor API's `Accelerometer` class exposed through the
|
|
82
|
+
* wc-bindable protocol.
|
|
83
|
+
*
|
|
84
|
+
* The platform `Sensor` base class (shared by `Accelerometer` / `Gyroscope` /
|
|
85
|
+
* `Magnetometer` / `AmbientLightSensor`) reports failure through an `'error'`
|
|
86
|
+
* event rather than a rejected promise, so this Core can satisfy never-throw
|
|
87
|
+
* (docs/async-io-node-guidelines.md §3.6) by simply forwarding that event —
|
|
88
|
+
* see docs/sensor-tag-design.md §0. The one place a synchronous
|
|
89
|
+
* exception *can* still escape the platform API is the `Accelerometer`
|
|
90
|
+
* constructor itself (e.g. `SecurityError` on permission denial or a
|
|
91
|
+
* feature-policy block); `_createSensor()` wraps that single call in
|
|
92
|
+
* try/catch, mirroring FetchCore's `_doFetch` try/catch around
|
|
93
|
+
* `globalThis.fetch` (packages/fetch/src/core/FetchCore.ts).
|
|
94
|
+
*
|
|
95
|
+
* `x`/`y`/`z` are three getters derived from the single `wcs-accelerometer:reading`
|
|
96
|
+
* event (mirroring how NetworkCore exposes effectiveType/downlink/… from one
|
|
97
|
+
* `wcs-network:change` event): the native `reading` event already reports all
|
|
98
|
+
* three axes together, so they are not split into independent events. `reading`
|
|
99
|
+
* is an event-like signal (a fresh sample every time, not a settled state) and
|
|
100
|
+
* is therefore deliberately NOT same-value guarded — every sample dispatches.
|
|
101
|
+
* `error` is state-like (denial / unsupported does not change from tick to
|
|
102
|
+
* tick) and IS same-value guarded, and is published on its own
|
|
103
|
+
* `wcs-accelerometer:error` event, independent of `reading`.
|
|
104
|
+
*
|
|
105
|
+
* No `_gen` generation guard: start()/stop() are a synchronous
|
|
106
|
+
* subscribe/unsubscribe toggle with no asynchronous probe whose stale
|
|
107
|
+
* resolution could race a dispose() — see docs/sensor-tag-design.md §1.5
|
|
108
|
+
* (the same reasoning as NetworkCore, docs/network-tag-design.md §5).
|
|
109
|
+
*
|
|
110
|
+
* Permissions: this Core does not query `navigator.permissions` itself.
|
|
111
|
+
* Compose with `<wcs-permission name="accelerometer">` instead — see
|
|
112
|
+
* docs/sensor-tag-design.md §"2番目の決定: Permissions APIとの合成".
|
|
113
|
+
*/
|
|
114
|
+
declare class AccelerometerCore extends EventTarget {
|
|
115
|
+
static wcBindable: IWcBindable;
|
|
116
|
+
private _target;
|
|
117
|
+
private _reading;
|
|
118
|
+
private _error;
|
|
119
|
+
private _sensor;
|
|
120
|
+
constructor(target?: EventTarget);
|
|
121
|
+
get x(): number | null;
|
|
122
|
+
get y(): number | null;
|
|
123
|
+
get z(): number | null;
|
|
124
|
+
get error(): WcsAccelerometerErrorDetail | null;
|
|
125
|
+
/** No asynchronous probe to await: start()/stop() are synchronous
|
|
126
|
+
* (docs/async-io-node-guidelines.md §3.8 is satisfied trivially, mirroring
|
|
127
|
+
* NetworkCore). */
|
|
128
|
+
get ready(): Promise<void>;
|
|
129
|
+
private _setReading;
|
|
130
|
+
private _setError;
|
|
131
|
+
/**
|
|
132
|
+
* Start the sensor at the given `frequency` (Hz), or the platform default
|
|
133
|
+
* when omitted. Idempotent while already started: a redundant start() does
|
|
134
|
+
* not construct a second sensor instance (which would leak the first).
|
|
135
|
+
* Restart with a different frequency via stop() + start().
|
|
136
|
+
*
|
|
137
|
+
* Synchronous, mirroring the native `Sensor.start()` — never throws
|
|
138
|
+
* (docs/async-io-node-guidelines.md §3.6): both "unsupported" and a
|
|
139
|
+
* synchronous constructor exception (permission denial, feature-policy
|
|
140
|
+
* block) are converted to the `error` property instead of propagating.
|
|
141
|
+
*/
|
|
142
|
+
start(frequency?: number): void;
|
|
143
|
+
/** Stop the sensor and detach its listeners. Safe to call when not started. */
|
|
144
|
+
stop(): void;
|
|
145
|
+
/** Lifecycle alias for start(), so the Shell's connectedCallback can drive
|
|
146
|
+
* this Core the same way as other IO nodes' observe()/dispose() pair. No
|
|
147
|
+
* asynchronous probe, so the returned promise always resolves immediately. */
|
|
148
|
+
observe(frequency?: number): Promise<void>;
|
|
149
|
+
/** Lifecycle alias for stop(), invoked from the Shell's disconnectedCallback. */
|
|
150
|
+
dispose(): void;
|
|
151
|
+
private _teardownSensor;
|
|
152
|
+
/**
|
|
153
|
+
* Construct the platform `Accelerometer`, guarding both non-support and a
|
|
154
|
+
* synchronous constructor exception. Never calls the raw `new Accelerometer(...)`
|
|
155
|
+
* anywhere else in this class — see docs/sensor-tag-design.md §1.5.
|
|
156
|
+
*
|
|
157
|
+
* API resolution is call-time (docs/async-io-node-guidelines.md §3.7):
|
|
158
|
+
* re-checked on every start(), never cached, so tests can install/remove the
|
|
159
|
+
* global freely and an unsupported environment is always reported correctly.
|
|
160
|
+
*/
|
|
161
|
+
private _createSensor;
|
|
162
|
+
private _onReading;
|
|
163
|
+
private _onError;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* `<wcs-accelerometer>` — declarative Generic Sensor API (`Accelerometer`)
|
|
168
|
+
* monitor + start/stop control.
|
|
169
|
+
*
|
|
170
|
+
* Unlike `<wcs-network>` / `<wcs-permission>` (pure monitors), this Shell is a
|
|
171
|
+
* bidirectional node: `start`/`stop` commands (command-token: state → element)
|
|
172
|
+
* alongside the `x`/`y`/`z`/`error` observable surface (event-token: element →
|
|
173
|
+
* state). The `frequency` attribute is the sole configuration input, forwarded
|
|
174
|
+
* to the platform `Accelerometer` constructor's `{ frequency }` option
|
|
175
|
+
* (docs/sensor-tag-design.md §1.2). The getter normalizes it: a non-finite or
|
|
176
|
+
* non-positive value (NaN, 0, negative) reads back as `null` — meaning "no
|
|
177
|
+
* frequency specified" — so start() falls back to the platform default rather
|
|
178
|
+
* than forwarding a value the sensor would reject. Any positive finite value is
|
|
179
|
+
* passed through verbatim (no upper-bound clamping — an out-of-range-but-positive
|
|
180
|
+
* rate is still left to the browser/sensor to reject via `error`).
|
|
181
|
+
*
|
|
182
|
+
* Permission handling is intentionally NOT implemented here. Compose with
|
|
183
|
+
* `<wcs-permission name="accelerometer">` instead (see the README's permission
|
|
184
|
+
* example, "Gate on permission, then start", and docs/sensor-tag-design.md).
|
|
185
|
+
*/
|
|
186
|
+
declare class WcsAccelerometer extends HTMLElement {
|
|
187
|
+
static hasConnectedCallbackPromise: boolean;
|
|
188
|
+
static wcBindable: IWcBindable;
|
|
189
|
+
private _core;
|
|
190
|
+
private _connectedCallbackPromise;
|
|
191
|
+
constructor();
|
|
192
|
+
/**
|
|
193
|
+
* Sampling frequency in Hz. Reads back `null` when unset, blank, or when the
|
|
194
|
+
* attribute does not parse to a positive finite number (NaN, `"0"`, negative)
|
|
195
|
+
* — in every such "no usable value" case the platform default applies.
|
|
196
|
+
*
|
|
197
|
+
* Note the deliberate set/get asymmetry: `set frequency(0)` (or any
|
|
198
|
+
* non-positive/non-finite value) still writes the attribute verbatim for
|
|
199
|
+
* transparency/inspectability, but the getter normalizes it back to `null`.
|
|
200
|
+
* A round-trip through a non-positive value therefore does NOT preserve it —
|
|
201
|
+
* that value carries no valid sampling meaning, so it is treated as "unset"
|
|
202
|
+
* on read. Only positive finite frequencies survive a set→get round-trip.
|
|
203
|
+
*/
|
|
204
|
+
get frequency(): number | null;
|
|
205
|
+
set frequency(value: number | null | undefined);
|
|
206
|
+
get x(): number | null;
|
|
207
|
+
get y(): number | null;
|
|
208
|
+
get z(): number | null;
|
|
209
|
+
get error(): WcsAccelerometerErrorDetail | null;
|
|
210
|
+
get connectedCallbackPromise(): Promise<void>;
|
|
211
|
+
start(): void;
|
|
212
|
+
stop(): void;
|
|
213
|
+
connectedCallback(): void;
|
|
214
|
+
disconnectedCallback(): void;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { AccelerometerCore, WcsAccelerometer, bootstrapAccelerometer, getConfig };
|
|
218
|
+
export type { IWritableConfig, IWritableTagNames, WcsAccelerometerCoreValues, WcsAccelerometerErrorDetail, WcsAccelerometerReading, WcsAccelerometerValues };
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
const _config = {
|
|
2
|
+
tagNames: {
|
|
3
|
+
accelerometer: "wcs-accelerometer",
|
|
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
|
+
const NULL_READING = Object.freeze({ x: null, y: null, z: null });
|
|
40
|
+
/**
|
|
41
|
+
* Headless Accelerometer primitive. A thin, framework-agnostic wrapper around
|
|
42
|
+
* the Generic Sensor API's `Accelerometer` class exposed through the
|
|
43
|
+
* wc-bindable protocol.
|
|
44
|
+
*
|
|
45
|
+
* The platform `Sensor` base class (shared by `Accelerometer` / `Gyroscope` /
|
|
46
|
+
* `Magnetometer` / `AmbientLightSensor`) reports failure through an `'error'`
|
|
47
|
+
* event rather than a rejected promise, so this Core can satisfy never-throw
|
|
48
|
+
* (docs/async-io-node-guidelines.md §3.6) by simply forwarding that event —
|
|
49
|
+
* see docs/sensor-tag-design.md §0. The one place a synchronous
|
|
50
|
+
* exception *can* still escape the platform API is the `Accelerometer`
|
|
51
|
+
* constructor itself (e.g. `SecurityError` on permission denial or a
|
|
52
|
+
* feature-policy block); `_createSensor()` wraps that single call in
|
|
53
|
+
* try/catch, mirroring FetchCore's `_doFetch` try/catch around
|
|
54
|
+
* `globalThis.fetch` (packages/fetch/src/core/FetchCore.ts).
|
|
55
|
+
*
|
|
56
|
+
* `x`/`y`/`z` are three getters derived from the single `wcs-accelerometer:reading`
|
|
57
|
+
* event (mirroring how NetworkCore exposes effectiveType/downlink/… from one
|
|
58
|
+
* `wcs-network:change` event): the native `reading` event already reports all
|
|
59
|
+
* three axes together, so they are not split into independent events. `reading`
|
|
60
|
+
* is an event-like signal (a fresh sample every time, not a settled state) and
|
|
61
|
+
* is therefore deliberately NOT same-value guarded — every sample dispatches.
|
|
62
|
+
* `error` is state-like (denial / unsupported does not change from tick to
|
|
63
|
+
* tick) and IS same-value guarded, and is published on its own
|
|
64
|
+
* `wcs-accelerometer:error` event, independent of `reading`.
|
|
65
|
+
*
|
|
66
|
+
* No `_gen` generation guard: start()/stop() are a synchronous
|
|
67
|
+
* subscribe/unsubscribe toggle with no asynchronous probe whose stale
|
|
68
|
+
* resolution could race a dispose() — see docs/sensor-tag-design.md §1.5
|
|
69
|
+
* (the same reasoning as NetworkCore, docs/network-tag-design.md §5).
|
|
70
|
+
*
|
|
71
|
+
* Permissions: this Core does not query `navigator.permissions` itself.
|
|
72
|
+
* Compose with `<wcs-permission name="accelerometer">` instead — see
|
|
73
|
+
* docs/sensor-tag-design.md §"2番目の決定: Permissions APIとの合成".
|
|
74
|
+
*/
|
|
75
|
+
class AccelerometerCore extends EventTarget {
|
|
76
|
+
static wcBindable = {
|
|
77
|
+
protocol: "wc-bindable",
|
|
78
|
+
version: 1,
|
|
79
|
+
properties: [
|
|
80
|
+
{ name: "x", event: "wcs-accelerometer:reading", getter: (e) => e.detail.x },
|
|
81
|
+
{ name: "y", event: "wcs-accelerometer:reading", getter: (e) => e.detail.y },
|
|
82
|
+
{ name: "z", event: "wcs-accelerometer:reading", getter: (e) => e.detail.z },
|
|
83
|
+
{ name: "error", event: "wcs-accelerometer:error" },
|
|
84
|
+
],
|
|
85
|
+
commands: [{ name: "start" }, { name: "stop" }],
|
|
86
|
+
};
|
|
87
|
+
_target;
|
|
88
|
+
_reading = NULL_READING;
|
|
89
|
+
_error = null;
|
|
90
|
+
// The live sensor instance while started (null otherwise), kept so stop()
|
|
91
|
+
// can remove its listeners precisely and so start() can detect "already
|
|
92
|
+
// started" without a separate boolean (docs/async-io-node-guidelines.md §3.5
|
|
93
|
+
// idempotency).
|
|
94
|
+
_sensor = null;
|
|
95
|
+
constructor(target) {
|
|
96
|
+
super();
|
|
97
|
+
this._target = target ?? this;
|
|
98
|
+
}
|
|
99
|
+
get x() {
|
|
100
|
+
return this._reading.x;
|
|
101
|
+
}
|
|
102
|
+
get y() {
|
|
103
|
+
return this._reading.y;
|
|
104
|
+
}
|
|
105
|
+
get z() {
|
|
106
|
+
return this._reading.z;
|
|
107
|
+
}
|
|
108
|
+
get error() {
|
|
109
|
+
return this._error;
|
|
110
|
+
}
|
|
111
|
+
/** No asynchronous probe to await: start()/stop() are synchronous
|
|
112
|
+
* (docs/async-io-node-guidelines.md §3.8 is satisfied trivially, mirroring
|
|
113
|
+
* NetworkCore). */
|
|
114
|
+
get ready() {
|
|
115
|
+
return Promise.resolve();
|
|
116
|
+
}
|
|
117
|
+
// --- State setters ---
|
|
118
|
+
// Deliberately NOT same-value guarded: a `reading` is a fresh sample, not a
|
|
119
|
+
// settled state, so it must dispatch every time even when the values happen
|
|
120
|
+
// to repeat (docs/sensor-tag-design.md §1.1).
|
|
121
|
+
_setReading(reading) {
|
|
122
|
+
this._reading = reading;
|
|
123
|
+
this._target.dispatchEvent(new CustomEvent("wcs-accelerometer:reading", {
|
|
124
|
+
detail: reading,
|
|
125
|
+
bubbles: true,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
_setError(error) {
|
|
129
|
+
// Same-value guard (by error name + message): error is state-like, unlike
|
|
130
|
+
// reading — a repeated identical error (same name and message) must not
|
|
131
|
+
// redispatch.
|
|
132
|
+
if (this._error?.error === error?.error && this._error?.message === error?.message)
|
|
133
|
+
return;
|
|
134
|
+
this._error = error;
|
|
135
|
+
this._target.dispatchEvent(new CustomEvent("wcs-accelerometer:error", {
|
|
136
|
+
detail: error,
|
|
137
|
+
bubbles: true,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
// --- Public API ---
|
|
141
|
+
/**
|
|
142
|
+
* Start the sensor at the given `frequency` (Hz), or the platform default
|
|
143
|
+
* when omitted. Idempotent while already started: a redundant start() does
|
|
144
|
+
* not construct a second sensor instance (which would leak the first).
|
|
145
|
+
* Restart with a different frequency via stop() + start().
|
|
146
|
+
*
|
|
147
|
+
* Synchronous, mirroring the native `Sensor.start()` — never throws
|
|
148
|
+
* (docs/async-io-node-guidelines.md §3.6): both "unsupported" and a
|
|
149
|
+
* synchronous constructor exception (permission denial, feature-policy
|
|
150
|
+
* block) are converted to the `error` property instead of propagating.
|
|
151
|
+
*/
|
|
152
|
+
start(frequency) {
|
|
153
|
+
if (this._sensor)
|
|
154
|
+
return;
|
|
155
|
+
const sensor = this._createSensor(frequency);
|
|
156
|
+
if (!sensor)
|
|
157
|
+
return;
|
|
158
|
+
sensor.addEventListener("reading", this._onReading);
|
|
159
|
+
sensor.addEventListener("error", this._onError);
|
|
160
|
+
this._sensor = sensor;
|
|
161
|
+
try {
|
|
162
|
+
sensor.start();
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
// Defensive: the platform contract says start()/stop() do not throw
|
|
166
|
+
// (failures surface via the 'error' event), but never-throw is a hard
|
|
167
|
+
// requirement here, so guard against a non-conformant implementation
|
|
168
|
+
// too.
|
|
169
|
+
this._teardownSensor();
|
|
170
|
+
this._setError({ error: e?.name ?? "error", message: e?.message ?? String(e) });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Stop the sensor and detach its listeners. Safe to call when not started. */
|
|
174
|
+
stop() {
|
|
175
|
+
if (!this._sensor)
|
|
176
|
+
return;
|
|
177
|
+
try {
|
|
178
|
+
this._sensor.stop();
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Never-throw defensive guard, symmetric with start(). Teardown below
|
|
182
|
+
// still runs so listeners are detached regardless.
|
|
183
|
+
}
|
|
184
|
+
this._teardownSensor();
|
|
185
|
+
}
|
|
186
|
+
/** Lifecycle alias for start(), so the Shell's connectedCallback can drive
|
|
187
|
+
* this Core the same way as other IO nodes' observe()/dispose() pair. No
|
|
188
|
+
* asynchronous probe, so the returned promise always resolves immediately. */
|
|
189
|
+
observe(frequency) {
|
|
190
|
+
this.start(frequency);
|
|
191
|
+
return this.ready;
|
|
192
|
+
}
|
|
193
|
+
/** Lifecycle alias for stop(), invoked from the Shell's disconnectedCallback. */
|
|
194
|
+
dispose() {
|
|
195
|
+
this.stop();
|
|
196
|
+
}
|
|
197
|
+
// --- Internal ---
|
|
198
|
+
// Both call sites (start()'s catch, stop()) only ever invoke this once
|
|
199
|
+
// `this._sensor` is already known non-null, so there is no null-guard here
|
|
200
|
+
// (nothing to defend against).
|
|
201
|
+
_teardownSensor() {
|
|
202
|
+
this._sensor.removeEventListener("reading", this._onReading);
|
|
203
|
+
this._sensor.removeEventListener("error", this._onError);
|
|
204
|
+
this._sensor = null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Construct the platform `Accelerometer`, guarding both non-support and a
|
|
208
|
+
* synchronous constructor exception. Never calls the raw `new Accelerometer(...)`
|
|
209
|
+
* anywhere else in this class — see docs/sensor-tag-design.md §1.5.
|
|
210
|
+
*
|
|
211
|
+
* API resolution is call-time (docs/async-io-node-guidelines.md §3.7):
|
|
212
|
+
* re-checked on every start(), never cached, so tests can install/remove the
|
|
213
|
+
* global freely and an unsupported environment is always reported correctly.
|
|
214
|
+
*/
|
|
215
|
+
_createSensor(frequency) {
|
|
216
|
+
const Ctor = globalThis.Accelerometer;
|
|
217
|
+
if (typeof Ctor !== "function") {
|
|
218
|
+
this._setError({ error: "unsupported", message: "Accelerometer is not supported" });
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
return new Ctor(frequency !== undefined ? { frequency } : undefined);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
// SecurityError (permission denial, feature-policy block) or any other
|
|
226
|
+
// synchronous construction failure. Mirrors the FetchCore._doFetch
|
|
227
|
+
// try/catch structure (packages/fetch/src/core/FetchCore.ts) — a
|
|
228
|
+
// synchronous constructor call here instead of an awaited fetch().
|
|
229
|
+
this._setError({ error: e?.name ?? "error", message: e?.message ?? String(e) });
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
_onReading = (event) => {
|
|
234
|
+
const sensor = event.target;
|
|
235
|
+
this._setReading({ x: sensor.x, y: sensor.y, z: sensor.z });
|
|
236
|
+
};
|
|
237
|
+
_onError = (event) => {
|
|
238
|
+
const err = event.error;
|
|
239
|
+
// Fallback is a meaningful constant, NOT String(err): a SensorErrorEvent
|
|
240
|
+
// without an `error` field would otherwise stringify `undefined` into the
|
|
241
|
+
// literal message "undefined" (aligned across the sensor family).
|
|
242
|
+
this._setError({ error: err?.name ?? "error", message: err?.message ?? "Sensor error" });
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* `<wcs-accelerometer>` — declarative Generic Sensor API (`Accelerometer`)
|
|
248
|
+
* monitor + start/stop control.
|
|
249
|
+
*
|
|
250
|
+
* Unlike `<wcs-network>` / `<wcs-permission>` (pure monitors), this Shell is a
|
|
251
|
+
* bidirectional node: `start`/`stop` commands (command-token: state → element)
|
|
252
|
+
* alongside the `x`/`y`/`z`/`error` observable surface (event-token: element →
|
|
253
|
+
* state). The `frequency` attribute is the sole configuration input, forwarded
|
|
254
|
+
* to the platform `Accelerometer` constructor's `{ frequency }` option
|
|
255
|
+
* (docs/sensor-tag-design.md §1.2). The getter normalizes it: a non-finite or
|
|
256
|
+
* non-positive value (NaN, 0, negative) reads back as `null` — meaning "no
|
|
257
|
+
* frequency specified" — so start() falls back to the platform default rather
|
|
258
|
+
* than forwarding a value the sensor would reject. Any positive finite value is
|
|
259
|
+
* passed through verbatim (no upper-bound clamping — an out-of-range-but-positive
|
|
260
|
+
* rate is still left to the browser/sensor to reject via `error`).
|
|
261
|
+
*
|
|
262
|
+
* Permission handling is intentionally NOT implemented here. Compose with
|
|
263
|
+
* `<wcs-permission name="accelerometer">` instead (see the README's permission
|
|
264
|
+
* example, "Gate on permission, then start", and docs/sensor-tag-design.md).
|
|
265
|
+
*/
|
|
266
|
+
class WcsAccelerometer extends HTMLElement {
|
|
267
|
+
static hasConnectedCallbackPromise = true;
|
|
268
|
+
static wcBindable = {
|
|
269
|
+
...AccelerometerCore.wcBindable,
|
|
270
|
+
inputs: [{ name: "frequency" }],
|
|
271
|
+
// Core の commands をそのまま継承(単一情報源)。
|
|
272
|
+
commands: AccelerometerCore.wcBindable.commands,
|
|
273
|
+
};
|
|
274
|
+
_core;
|
|
275
|
+
_connectedCallbackPromise = Promise.resolve();
|
|
276
|
+
constructor() {
|
|
277
|
+
super();
|
|
278
|
+
this._core = new AccelerometerCore(this);
|
|
279
|
+
}
|
|
280
|
+
// --- Attribute accessors ---
|
|
281
|
+
/**
|
|
282
|
+
* Sampling frequency in Hz. Reads back `null` when unset, blank, or when the
|
|
283
|
+
* attribute does not parse to a positive finite number (NaN, `"0"`, negative)
|
|
284
|
+
* — in every such "no usable value" case the platform default applies.
|
|
285
|
+
*
|
|
286
|
+
* Note the deliberate set/get asymmetry: `set frequency(0)` (or any
|
|
287
|
+
* non-positive/non-finite value) still writes the attribute verbatim for
|
|
288
|
+
* transparency/inspectability, but the getter normalizes it back to `null`.
|
|
289
|
+
* A round-trip through a non-positive value therefore does NOT preserve it —
|
|
290
|
+
* that value carries no valid sampling meaning, so it is treated as "unset"
|
|
291
|
+
* on read. Only positive finite frequencies survive a set→get round-trip.
|
|
292
|
+
*/
|
|
293
|
+
get frequency() {
|
|
294
|
+
const attr = this.getAttribute("frequency");
|
|
295
|
+
if (attr === null || attr.trim() === "")
|
|
296
|
+
return null;
|
|
297
|
+
const parsed = Number(attr);
|
|
298
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
299
|
+
}
|
|
300
|
+
set frequency(value) {
|
|
301
|
+
if (value === null || value === undefined) {
|
|
302
|
+
this.removeAttribute("frequency");
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
this.setAttribute("frequency", String(value));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// --- Core delegated getters ---
|
|
309
|
+
get x() {
|
|
310
|
+
return this._core.x;
|
|
311
|
+
}
|
|
312
|
+
get y() {
|
|
313
|
+
return this._core.y;
|
|
314
|
+
}
|
|
315
|
+
get z() {
|
|
316
|
+
return this._core.z;
|
|
317
|
+
}
|
|
318
|
+
get error() {
|
|
319
|
+
return this._core.error;
|
|
320
|
+
}
|
|
321
|
+
get connectedCallbackPromise() {
|
|
322
|
+
return this._connectedCallbackPromise;
|
|
323
|
+
}
|
|
324
|
+
// --- Commands ---
|
|
325
|
+
start() {
|
|
326
|
+
this._core.start(this.frequency ?? undefined);
|
|
327
|
+
}
|
|
328
|
+
stop() {
|
|
329
|
+
this._core.stop();
|
|
330
|
+
}
|
|
331
|
+
// --- Lifecycle ---
|
|
332
|
+
// Deliberately does NOT auto-start the sensor on connect. Unlike
|
|
333
|
+
// Geolocation (whose default phase acquires a fix immediately unless
|
|
334
|
+
// `manual` is set), Accelerometer has no such "connect implies observing"
|
|
335
|
+
// precedent in the design doc (docs/sensor-tag-design.md §1.3):
|
|
336
|
+
// start/stop are the only commands, so connecting the element merely makes
|
|
337
|
+
// it inert until a command-token `start` (or the `start()` method) is
|
|
338
|
+
// invoked. This also keeps behavior predictable when composed with
|
|
339
|
+
// `<wcs-permission name="accelerometer">`: the caller decides when to start,
|
|
340
|
+
// typically gated on `granted`.
|
|
341
|
+
connectedCallback() {
|
|
342
|
+
this.style.display = "none";
|
|
343
|
+
// No asynchronous probe to await (docs/async-io-node-guidelines.md §3.8);
|
|
344
|
+
// kept for SSR uniformity with other IO nodes.
|
|
345
|
+
this._connectedCallbackPromise = this._core.ready;
|
|
346
|
+
}
|
|
347
|
+
disconnectedCallback() {
|
|
348
|
+
this._core.dispose();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function registerComponents() {
|
|
353
|
+
if (!customElements.get(config.tagNames.accelerometer)) {
|
|
354
|
+
customElements.define(config.tagNames.accelerometer, WcsAccelerometer);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function bootstrapAccelerometer(userConfig) {
|
|
359
|
+
if (userConfig) {
|
|
360
|
+
setConfig(userConfig);
|
|
361
|
+
}
|
|
362
|
+
registerComponents();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export { AccelerometerCore, WcsAccelerometer, bootstrapAccelerometer, getConfig };
|
|
366
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/config.ts","../src/core/AccelerometerCore.ts","../src/components/Accelerometer.ts","../src/registerComponents.ts","../src/bootstrapAccelerometer.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n tagNames: {\n accelerometer: string;\n };\n}\n\nconst _config: IInternalConfig = {\n tagNames: {\n accelerometer: \"wcs-accelerometer\",\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, WcsAccelerometerReading, WcsAccelerometerErrorDetail } from \"../types.js\";\n\nconst NULL_READING: WcsAccelerometerReading = Object.freeze({ x: null, y: null, z: null });\n\n/**\n * Headless Accelerometer primitive. A thin, framework-agnostic wrapper around\n * the Generic Sensor API's `Accelerometer` class exposed through the\n * wc-bindable protocol.\n *\n * The platform `Sensor` base class (shared by `Accelerometer` / `Gyroscope` /\n * `Magnetometer` / `AmbientLightSensor`) reports failure through an `'error'`\n * event rather than a rejected promise, so this Core can satisfy never-throw\n * (docs/async-io-node-guidelines.md §3.6) by simply forwarding that event —\n * see docs/sensor-tag-design.md §0. The one place a synchronous\n * exception *can* still escape the platform API is the `Accelerometer`\n * constructor itself (e.g. `SecurityError` on permission denial or a\n * feature-policy block); `_createSensor()` wraps that single call in\n * try/catch, mirroring FetchCore's `_doFetch` try/catch around\n * `globalThis.fetch` (packages/fetch/src/core/FetchCore.ts).\n *\n * `x`/`y`/`z` are three getters derived from the single `wcs-accelerometer:reading`\n * event (mirroring how NetworkCore exposes effectiveType/downlink/… from one\n * `wcs-network:change` event): the native `reading` event already reports all\n * three axes together, so they are not split into independent events. `reading`\n * is an event-like signal (a fresh sample every time, not a settled state) and\n * is therefore deliberately NOT same-value guarded — every sample dispatches.\n * `error` is state-like (denial / unsupported does not change from tick to\n * tick) and IS same-value guarded, and is published on its own\n * `wcs-accelerometer:error` event, independent of `reading`.\n *\n * No `_gen` generation guard: start()/stop() are a synchronous\n * subscribe/unsubscribe toggle with no asynchronous probe whose stale\n * resolution could race a dispose() — see docs/sensor-tag-design.md §1.5\n * (the same reasoning as NetworkCore, docs/network-tag-design.md §5).\n *\n * Permissions: this Core does not query `navigator.permissions` itself.\n * Compose with `<wcs-permission name=\"accelerometer\">` instead — see\n * docs/sensor-tag-design.md §\"2番目の決定: Permissions APIとの合成\".\n */\nexport class AccelerometerCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"x\", event: \"wcs-accelerometer:reading\", getter: (e: Event) => (e as CustomEvent).detail.x },\n { name: \"y\", event: \"wcs-accelerometer:reading\", getter: (e: Event) => (e as CustomEvent).detail.y },\n { name: \"z\", event: \"wcs-accelerometer:reading\", getter: (e: Event) => (e as CustomEvent).detail.z },\n { name: \"error\", event: \"wcs-accelerometer:error\" },\n ],\n commands: [{ name: \"start\" }, { name: \"stop\" }],\n };\n\n private _target: EventTarget;\n private _reading: WcsAccelerometerReading = NULL_READING;\n private _error: WcsAccelerometerErrorDetail | null = null;\n\n // The live sensor instance while started (null otherwise), kept so stop()\n // can remove its listeners precisely and so start() can detect \"already\n // started\" without a separate boolean (docs/async-io-node-guidelines.md §3.5\n // idempotency).\n private _sensor: (EventTarget & { start(): void; stop(): void }) | null = null;\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get x(): number | null {\n return this._reading.x;\n }\n\n get y(): number | null {\n return this._reading.y;\n }\n\n get z(): number | null {\n return this._reading.z;\n }\n\n get error(): WcsAccelerometerErrorDetail | null {\n return this._error;\n }\n\n /** No asynchronous probe to await: start()/stop() are synchronous\n * (docs/async-io-node-guidelines.md §3.8 is satisfied trivially, mirroring\n * NetworkCore). */\n get ready(): Promise<void> {\n return Promise.resolve();\n }\n\n // --- State setters ---\n\n // Deliberately NOT same-value guarded: a `reading` is a fresh sample, not a\n // settled state, so it must dispatch every time even when the values happen\n // to repeat (docs/sensor-tag-design.md §1.1).\n private _setReading(reading: WcsAccelerometerReading): void {\n this._reading = reading;\n this._target.dispatchEvent(new CustomEvent(\"wcs-accelerometer:reading\", {\n detail: reading,\n bubbles: true,\n }));\n }\n\n private _setError(error: WcsAccelerometerErrorDetail | null): void {\n // Same-value guard (by error name + message): error is state-like, unlike\n // reading — a repeated identical error (same name and message) must not\n // redispatch.\n if (this._error?.error === error?.error && this._error?.message === error?.message) return;\n this._error = error;\n this._target.dispatchEvent(new CustomEvent(\"wcs-accelerometer:error\", {\n detail: error,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Start the sensor at the given `frequency` (Hz), or the platform default\n * when omitted. Idempotent while already started: a redundant start() does\n * not construct a second sensor instance (which would leak the first).\n * Restart with a different frequency via stop() + start().\n *\n * Synchronous, mirroring the native `Sensor.start()` — never throws\n * (docs/async-io-node-guidelines.md §3.6): both \"unsupported\" and a\n * synchronous constructor exception (permission denial, feature-policy\n * block) are converted to the `error` property instead of propagating.\n */\n start(frequency?: number): void {\n if (this._sensor) return;\n const sensor = this._createSensor(frequency);\n if (!sensor) return;\n sensor.addEventListener(\"reading\", this._onReading as EventListener);\n sensor.addEventListener(\"error\", this._onError as EventListener);\n this._sensor = sensor;\n try {\n sensor.start();\n } catch (e: any) {\n // Defensive: the platform contract says start()/stop() do not throw\n // (failures surface via the 'error' event), but never-throw is a hard\n // requirement here, so guard against a non-conformant implementation\n // too.\n this._teardownSensor();\n this._setError({ error: e?.name ?? \"error\", message: e?.message ?? String(e) });\n }\n }\n\n /** Stop the sensor and detach its listeners. Safe to call when not started. */\n stop(): void {\n if (!this._sensor) return;\n try {\n this._sensor.stop();\n } catch {\n // Never-throw defensive guard, symmetric with start(). Teardown below\n // still runs so listeners are detached regardless.\n }\n this._teardownSensor();\n }\n\n /** Lifecycle alias for start(), so the Shell's connectedCallback can drive\n * this Core the same way as other IO nodes' observe()/dispose() pair. No\n * asynchronous probe, so the returned promise always resolves immediately. */\n observe(frequency?: number): Promise<void> {\n this.start(frequency);\n return this.ready;\n }\n\n /** Lifecycle alias for stop(), invoked from the Shell's disconnectedCallback. */\n dispose(): void {\n this.stop();\n }\n\n // --- Internal ---\n\n // Both call sites (start()'s catch, stop()) only ever invoke this once\n // `this._sensor` is already known non-null, so there is no null-guard here\n // (nothing to defend against).\n private _teardownSensor(): void {\n this._sensor!.removeEventListener(\"reading\", this._onReading as EventListener);\n this._sensor!.removeEventListener(\"error\", this._onError as EventListener);\n this._sensor = null;\n }\n\n /**\n * Construct the platform `Accelerometer`, guarding both non-support and a\n * synchronous constructor exception. Never calls the raw `new Accelerometer(...)`\n * anywhere else in this class — see docs/sensor-tag-design.md §1.5.\n *\n * API resolution is call-time (docs/async-io-node-guidelines.md §3.7):\n * re-checked on every start(), never cached, so tests can install/remove the\n * global freely and an unsupported environment is always reported correctly.\n */\n private _createSensor(frequency?: number): (EventTarget & { start(): void; stop(): void }) | null {\n const Ctor = (globalThis as any).Accelerometer;\n if (typeof Ctor !== \"function\") {\n this._setError({ error: \"unsupported\", message: \"Accelerometer is not supported\" });\n return null;\n }\n try {\n return new Ctor(frequency !== undefined ? { frequency } : undefined);\n } catch (e: any) {\n // SecurityError (permission denial, feature-policy block) or any other\n // synchronous construction failure. Mirrors the FetchCore._doFetch\n // try/catch structure (packages/fetch/src/core/FetchCore.ts) — a\n // synchronous constructor call here instead of an awaited fetch().\n this._setError({ error: e?.name ?? \"error\", message: e?.message ?? String(e) });\n return null;\n }\n }\n\n private _onReading = (event: Event): void => {\n const sensor = event.target as unknown as { x: number | null; y: number | null; z: number | null };\n this._setReading({ x: sensor.x, y: sensor.y, z: sensor.z });\n };\n\n private _onError = (event: Event): void => {\n const err = (event as any).error as { name?: string; message?: string } | undefined;\n // Fallback is a meaningful constant, NOT String(err): a SensorErrorEvent\n // without an `error` field would otherwise stringify `undefined` into the\n // literal message \"undefined\" (aligned across the sensor family).\n this._setError({ error: err?.name ?? \"error\", message: err?.message ?? \"Sensor error\" });\n };\n}\n","import { IWcBindable, WcsAccelerometerErrorDetail } from \"../types.js\";\nimport { AccelerometerCore } from \"../core/AccelerometerCore.js\";\n\n/**\n * `<wcs-accelerometer>` — declarative Generic Sensor API (`Accelerometer`)\n * monitor + start/stop control.\n *\n * Unlike `<wcs-network>` / `<wcs-permission>` (pure monitors), this Shell is a\n * bidirectional node: `start`/`stop` commands (command-token: state → element)\n * alongside the `x`/`y`/`z`/`error` observable surface (event-token: element →\n * state). The `frequency` attribute is the sole configuration input, forwarded\n * to the platform `Accelerometer` constructor's `{ frequency }` option\n * (docs/sensor-tag-design.md §1.2). The getter normalizes it: a non-finite or\n * non-positive value (NaN, 0, negative) reads back as `null` — meaning \"no\n * frequency specified\" — so start() falls back to the platform default rather\n * than forwarding a value the sensor would reject. Any positive finite value is\n * passed through verbatim (no upper-bound clamping — an out-of-range-but-positive\n * rate is still left to the browser/sensor to reject via `error`).\n *\n * Permission handling is intentionally NOT implemented here. Compose with\n * `<wcs-permission name=\"accelerometer\">` instead (see the README's permission\n * example, \"Gate on permission, then start\", and docs/sensor-tag-design.md).\n */\nexport class WcsAccelerometer extends HTMLElement {\n static hasConnectedCallbackPromise = true;\n\n static wcBindable: IWcBindable = {\n ...AccelerometerCore.wcBindable,\n inputs: [{ name: \"frequency\" }],\n // Core の commands をそのまま継承(単一情報源)。\n commands: AccelerometerCore.wcBindable.commands,\n };\n\n private _core: AccelerometerCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new AccelerometerCore(this);\n }\n\n // --- Attribute accessors ---\n\n /**\n * Sampling frequency in Hz. Reads back `null` when unset, blank, or when the\n * attribute does not parse to a positive finite number (NaN, `\"0\"`, negative)\n * — in every such \"no usable value\" case the platform default applies.\n *\n * Note the deliberate set/get asymmetry: `set frequency(0)` (or any\n * non-positive/non-finite value) still writes the attribute verbatim for\n * transparency/inspectability, but the getter normalizes it back to `null`.\n * A round-trip through a non-positive value therefore does NOT preserve it —\n * that value carries no valid sampling meaning, so it is treated as \"unset\"\n * on read. Only positive finite frequencies survive a set→get round-trip.\n */\n get frequency(): number | null {\n const attr = this.getAttribute(\"frequency\");\n if (attr === null || attr.trim() === \"\") return null;\n const parsed = Number(attr);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n }\n\n set frequency(value: number | null | undefined) {\n if (value === null || value === undefined) {\n this.removeAttribute(\"frequency\");\n } else {\n this.setAttribute(\"frequency\", String(value));\n }\n }\n\n // --- Core delegated getters ---\n\n get x(): number | null {\n return this._core.x;\n }\n\n get y(): number | null {\n return this._core.y;\n }\n\n get z(): number | null {\n return this._core.z;\n }\n\n get error(): WcsAccelerometerErrorDetail | null {\n return this._core.error;\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Commands ---\n\n start(): void {\n this._core.start(this.frequency ?? undefined);\n }\n\n stop(): void {\n this._core.stop();\n }\n\n // --- Lifecycle ---\n\n // Deliberately does NOT auto-start the sensor on connect. Unlike\n // Geolocation (whose default phase acquires a fix immediately unless\n // `manual` is set), Accelerometer has no such \"connect implies observing\"\n // precedent in the design doc (docs/sensor-tag-design.md §1.3):\n // start/stop are the only commands, so connecting the element merely makes\n // it inert until a command-token `start` (or the `start()` method) is\n // invoked. This also keeps behavior predictable when composed with\n // `<wcs-permission name=\"accelerometer\">`: the caller decides when to start,\n // typically gated on `granted`.\n connectedCallback(): void {\n this.style.display = \"none\";\n // No asynchronous probe to await (docs/async-io-node-guidelines.md §3.8);\n // kept for SSR uniformity with other IO nodes.\n this._connectedCallbackPromise = this._core.ready;\n }\n\n disconnectedCallback(): void {\n this._core.dispose();\n }\n}\n","import { WcsAccelerometer } from \"./components/Accelerometer.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.accelerometer)) {\n customElements.define(config.tagNames.accelerometer, WcsAccelerometer);\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapAccelerometer(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:{accelerometer:"wcs-accelerometer"}};function r(e){if(null===e||"object"!=typeof e)return e;Object.freeze(e);for(const t of Object.keys(e))r(e[t]);return e}function t(e){if(null===e||"object"!=typeof e)return e;const r={};for(const s of Object.keys(e))r[s]=t(e[s]);return r}let s=null;const n=e;function o(){return s||(s=r(t(e))),s}const i=Object.freeze({x:null,y:null,z:null});class c extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"x",event:"wcs-accelerometer:reading",getter:e=>e.detail.x},{name:"y",event:"wcs-accelerometer:reading",getter:e=>e.detail.y},{name:"z",event:"wcs-accelerometer:reading",getter:e=>e.detail.z},{name:"error",event:"wcs-accelerometer:error"}],commands:[{name:"start"},{name:"stop"}]};_target;_reading=i;_error=null;_sensor=null;constructor(e){super(),this._target=e??this}get x(){return this._reading.x}get y(){return this._reading.y}get z(){return this._reading.z}get error(){return this._error}get ready(){return Promise.resolve()}_setReading(e){this._reading=e,this._target.dispatchEvent(new CustomEvent("wcs-accelerometer:reading",{detail:e,bubbles:!0}))}_setError(e){this._error?.error===e?.error&&this._error?.message===e?.message||(this._error=e,this._target.dispatchEvent(new CustomEvent("wcs-accelerometer:error",{detail:e,bubbles:!0})))}start(e){if(this._sensor)return;const r=this._createSensor(e);if(r){r.addEventListener("reading",this._onReading),r.addEventListener("error",this._onError),this._sensor=r;try{r.start()}catch(e){this._teardownSensor(),this._setError({error:e?.name??"error",message:e?.message??String(e)})}}}stop(){if(this._sensor){try{this._sensor.stop()}catch{}this._teardownSensor()}}observe(e){return this.start(e),this.ready}dispose(){this.stop()}_teardownSensor(){this._sensor.removeEventListener("reading",this._onReading),this._sensor.removeEventListener("error",this._onError),this._sensor=null}_createSensor(e){const r=globalThis.Accelerometer;if("function"!=typeof r)return this._setError({error:"unsupported",message:"Accelerometer is not supported"}),null;try{return new r(void 0!==e?{frequency:e}:void 0)}catch(e){return this._setError({error:e?.name??"error",message:e?.message??String(e)}),null}}_onReading=e=>{const r=e.target;this._setReading({x:r.x,y:r.y,z:r.z})};_onError=e=>{const r=e.error;this._setError({error:r?.name??"error",message:r?.message??"Sensor error"})}}class a extends HTMLElement{static hasConnectedCallbackPromise=!0;static wcBindable={...c.wcBindable,inputs:[{name:"frequency"}],commands:c.wcBindable.commands};_core;_connectedCallbackPromise=Promise.resolve();constructor(){super(),this._core=new c(this)}get frequency(){const e=this.getAttribute("frequency");if(null===e||""===e.trim())return null;const r=Number(e);return Number.isFinite(r)&&r>0?r:null}set frequency(e){null==e?this.removeAttribute("frequency"):this.setAttribute("frequency",String(e))}get x(){return this._core.x}get y(){return this._core.y}get z(){return this._core.z}get error(){return this._core.error}get connectedCallbackPromise(){return this._connectedCallbackPromise}start(){this._core.start(this.frequency??void 0)}stop(){this._core.stop()}connectedCallback(){this.style.display="none",this._connectedCallbackPromise=this._core.ready}disconnectedCallback(){this._core.dispose()}}function l(r){var t;r&&((t=r).tagNames&&Object.assign(e.tagNames,t.tagNames),s=null),customElements.get(n.tagNames.accelerometer)||customElements.define(n.tagNames.accelerometer,a)}export{c as AccelerometerCore,a as WcsAccelerometer,l as bootstrapAccelerometer,o 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/AccelerometerCore.ts","../src/components/Accelerometer.ts","../src/bootstrapAccelerometer.ts","../src/registerComponents.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n tagNames: {\n accelerometer: string;\n };\n}\n\nconst _config: IInternalConfig = {\n tagNames: {\n accelerometer: \"wcs-accelerometer\",\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, WcsAccelerometerReading, WcsAccelerometerErrorDetail } from \"../types.js\";\n\nconst NULL_READING: WcsAccelerometerReading = Object.freeze({ x: null, y: null, z: null });\n\n/**\n * Headless Accelerometer primitive. A thin, framework-agnostic wrapper around\n * the Generic Sensor API's `Accelerometer` class exposed through the\n * wc-bindable protocol.\n *\n * The platform `Sensor` base class (shared by `Accelerometer` / `Gyroscope` /\n * `Magnetometer` / `AmbientLightSensor`) reports failure through an `'error'`\n * event rather than a rejected promise, so this Core can satisfy never-throw\n * (docs/async-io-node-guidelines.md §3.6) by simply forwarding that event —\n * see docs/sensor-tag-design.md §0. The one place a synchronous\n * exception *can* still escape the platform API is the `Accelerometer`\n * constructor itself (e.g. `SecurityError` on permission denial or a\n * feature-policy block); `_createSensor()` wraps that single call in\n * try/catch, mirroring FetchCore's `_doFetch` try/catch around\n * `globalThis.fetch` (packages/fetch/src/core/FetchCore.ts).\n *\n * `x`/`y`/`z` are three getters derived from the single `wcs-accelerometer:reading`\n * event (mirroring how NetworkCore exposes effectiveType/downlink/… from one\n * `wcs-network:change` event): the native `reading` event already reports all\n * three axes together, so they are not split into independent events. `reading`\n * is an event-like signal (a fresh sample every time, not a settled state) and\n * is therefore deliberately NOT same-value guarded — every sample dispatches.\n * `error` is state-like (denial / unsupported does not change from tick to\n * tick) and IS same-value guarded, and is published on its own\n * `wcs-accelerometer:error` event, independent of `reading`.\n *\n * No `_gen` generation guard: start()/stop() are a synchronous\n * subscribe/unsubscribe toggle with no asynchronous probe whose stale\n * resolution could race a dispose() — see docs/sensor-tag-design.md §1.5\n * (the same reasoning as NetworkCore, docs/network-tag-design.md §5).\n *\n * Permissions: this Core does not query `navigator.permissions` itself.\n * Compose with `<wcs-permission name=\"accelerometer\">` instead — see\n * docs/sensor-tag-design.md §\"2番目の決定: Permissions APIとの合成\".\n */\nexport class AccelerometerCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"x\", event: \"wcs-accelerometer:reading\", getter: (e: Event) => (e as CustomEvent).detail.x },\n { name: \"y\", event: \"wcs-accelerometer:reading\", getter: (e: Event) => (e as CustomEvent).detail.y },\n { name: \"z\", event: \"wcs-accelerometer:reading\", getter: (e: Event) => (e as CustomEvent).detail.z },\n { name: \"error\", event: \"wcs-accelerometer:error\" },\n ],\n commands: [{ name: \"start\" }, { name: \"stop\" }],\n };\n\n private _target: EventTarget;\n private _reading: WcsAccelerometerReading = NULL_READING;\n private _error: WcsAccelerometerErrorDetail | null = null;\n\n // The live sensor instance while started (null otherwise), kept so stop()\n // can remove its listeners precisely and so start() can detect \"already\n // started\" without a separate boolean (docs/async-io-node-guidelines.md §3.5\n // idempotency).\n private _sensor: (EventTarget & { start(): void; stop(): void }) | null = null;\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get x(): number | null {\n return this._reading.x;\n }\n\n get y(): number | null {\n return this._reading.y;\n }\n\n get z(): number | null {\n return this._reading.z;\n }\n\n get error(): WcsAccelerometerErrorDetail | null {\n return this._error;\n }\n\n /** No asynchronous probe to await: start()/stop() are synchronous\n * (docs/async-io-node-guidelines.md §3.8 is satisfied trivially, mirroring\n * NetworkCore). */\n get ready(): Promise<void> {\n return Promise.resolve();\n }\n\n // --- State setters ---\n\n // Deliberately NOT same-value guarded: a `reading` is a fresh sample, not a\n // settled state, so it must dispatch every time even when the values happen\n // to repeat (docs/sensor-tag-design.md §1.1).\n private _setReading(reading: WcsAccelerometerReading): void {\n this._reading = reading;\n this._target.dispatchEvent(new CustomEvent(\"wcs-accelerometer:reading\", {\n detail: reading,\n bubbles: true,\n }));\n }\n\n private _setError(error: WcsAccelerometerErrorDetail | null): void {\n // Same-value guard (by error name + message): error is state-like, unlike\n // reading — a repeated identical error (same name and message) must not\n // redispatch.\n if (this._error?.error === error?.error && this._error?.message === error?.message) return;\n this._error = error;\n this._target.dispatchEvent(new CustomEvent(\"wcs-accelerometer:error\", {\n detail: error,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Start the sensor at the given `frequency` (Hz), or the platform default\n * when omitted. Idempotent while already started: a redundant start() does\n * not construct a second sensor instance (which would leak the first).\n * Restart with a different frequency via stop() + start().\n *\n * Synchronous, mirroring the native `Sensor.start()` — never throws\n * (docs/async-io-node-guidelines.md §3.6): both \"unsupported\" and a\n * synchronous constructor exception (permission denial, feature-policy\n * block) are converted to the `error` property instead of propagating.\n */\n start(frequency?: number): void {\n if (this._sensor) return;\n const sensor = this._createSensor(frequency);\n if (!sensor) return;\n sensor.addEventListener(\"reading\", this._onReading as EventListener);\n sensor.addEventListener(\"error\", this._onError as EventListener);\n this._sensor = sensor;\n try {\n sensor.start();\n } catch (e: any) {\n // Defensive: the platform contract says start()/stop() do not throw\n // (failures surface via the 'error' event), but never-throw is a hard\n // requirement here, so guard against a non-conformant implementation\n // too.\n this._teardownSensor();\n this._setError({ error: e?.name ?? \"error\", message: e?.message ?? String(e) });\n }\n }\n\n /** Stop the sensor and detach its listeners. Safe to call when not started. */\n stop(): void {\n if (!this._sensor) return;\n try {\n this._sensor.stop();\n } catch {\n // Never-throw defensive guard, symmetric with start(). Teardown below\n // still runs so listeners are detached regardless.\n }\n this._teardownSensor();\n }\n\n /** Lifecycle alias for start(), so the Shell's connectedCallback can drive\n * this Core the same way as other IO nodes' observe()/dispose() pair. No\n * asynchronous probe, so the returned promise always resolves immediately. */\n observe(frequency?: number): Promise<void> {\n this.start(frequency);\n return this.ready;\n }\n\n /** Lifecycle alias for stop(), invoked from the Shell's disconnectedCallback. */\n dispose(): void {\n this.stop();\n }\n\n // --- Internal ---\n\n // Both call sites (start()'s catch, stop()) only ever invoke this once\n // `this._sensor` is already known non-null, so there is no null-guard here\n // (nothing to defend against).\n private _teardownSensor(): void {\n this._sensor!.removeEventListener(\"reading\", this._onReading as EventListener);\n this._sensor!.removeEventListener(\"error\", this._onError as EventListener);\n this._sensor = null;\n }\n\n /**\n * Construct the platform `Accelerometer`, guarding both non-support and a\n * synchronous constructor exception. Never calls the raw `new Accelerometer(...)`\n * anywhere else in this class — see docs/sensor-tag-design.md §1.5.\n *\n * API resolution is call-time (docs/async-io-node-guidelines.md §3.7):\n * re-checked on every start(), never cached, so tests can install/remove the\n * global freely and an unsupported environment is always reported correctly.\n */\n private _createSensor(frequency?: number): (EventTarget & { start(): void; stop(): void }) | null {\n const Ctor = (globalThis as any).Accelerometer;\n if (typeof Ctor !== \"function\") {\n this._setError({ error: \"unsupported\", message: \"Accelerometer is not supported\" });\n return null;\n }\n try {\n return new Ctor(frequency !== undefined ? { frequency } : undefined);\n } catch (e: any) {\n // SecurityError (permission denial, feature-policy block) or any other\n // synchronous construction failure. Mirrors the FetchCore._doFetch\n // try/catch structure (packages/fetch/src/core/FetchCore.ts) — a\n // synchronous constructor call here instead of an awaited fetch().\n this._setError({ error: e?.name ?? \"error\", message: e?.message ?? String(e) });\n return null;\n }\n }\n\n private _onReading = (event: Event): void => {\n const sensor = event.target as unknown as { x: number | null; y: number | null; z: number | null };\n this._setReading({ x: sensor.x, y: sensor.y, z: sensor.z });\n };\n\n private _onError = (event: Event): void => {\n const err = (event as any).error as { name?: string; message?: string } | undefined;\n // Fallback is a meaningful constant, NOT String(err): a SensorErrorEvent\n // without an `error` field would otherwise stringify `undefined` into the\n // literal message \"undefined\" (aligned across the sensor family).\n this._setError({ error: err?.name ?? \"error\", message: err?.message ?? \"Sensor error\" });\n };\n}\n","import { IWcBindable, WcsAccelerometerErrorDetail } from \"../types.js\";\nimport { AccelerometerCore } from \"../core/AccelerometerCore.js\";\n\n/**\n * `<wcs-accelerometer>` — declarative Generic Sensor API (`Accelerometer`)\n * monitor + start/stop control.\n *\n * Unlike `<wcs-network>` / `<wcs-permission>` (pure monitors), this Shell is a\n * bidirectional node: `start`/`stop` commands (command-token: state → element)\n * alongside the `x`/`y`/`z`/`error` observable surface (event-token: element →\n * state). The `frequency` attribute is the sole configuration input, forwarded\n * to the platform `Accelerometer` constructor's `{ frequency }` option\n * (docs/sensor-tag-design.md §1.2). The getter normalizes it: a non-finite or\n * non-positive value (NaN, 0, negative) reads back as `null` — meaning \"no\n * frequency specified\" — so start() falls back to the platform default rather\n * than forwarding a value the sensor would reject. Any positive finite value is\n * passed through verbatim (no upper-bound clamping — an out-of-range-but-positive\n * rate is still left to the browser/sensor to reject via `error`).\n *\n * Permission handling is intentionally NOT implemented here. Compose with\n * `<wcs-permission name=\"accelerometer\">` instead (see the README's permission\n * example, \"Gate on permission, then start\", and docs/sensor-tag-design.md).\n */\nexport class WcsAccelerometer extends HTMLElement {\n static hasConnectedCallbackPromise = true;\n\n static wcBindable: IWcBindable = {\n ...AccelerometerCore.wcBindable,\n inputs: [{ name: \"frequency\" }],\n // Core の commands をそのまま継承(単一情報源)。\n commands: AccelerometerCore.wcBindable.commands,\n };\n\n private _core: AccelerometerCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new AccelerometerCore(this);\n }\n\n // --- Attribute accessors ---\n\n /**\n * Sampling frequency in Hz. Reads back `null` when unset, blank, or when the\n * attribute does not parse to a positive finite number (NaN, `\"0\"`, negative)\n * — in every such \"no usable value\" case the platform default applies.\n *\n * Note the deliberate set/get asymmetry: `set frequency(0)` (or any\n * non-positive/non-finite value) still writes the attribute verbatim for\n * transparency/inspectability, but the getter normalizes it back to `null`.\n * A round-trip through a non-positive value therefore does NOT preserve it —\n * that value carries no valid sampling meaning, so it is treated as \"unset\"\n * on read. Only positive finite frequencies survive a set→get round-trip.\n */\n get frequency(): number | null {\n const attr = this.getAttribute(\"frequency\");\n if (attr === null || attr.trim() === \"\") return null;\n const parsed = Number(attr);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n }\n\n set frequency(value: number | null | undefined) {\n if (value === null || value === undefined) {\n this.removeAttribute(\"frequency\");\n } else {\n this.setAttribute(\"frequency\", String(value));\n }\n }\n\n // --- Core delegated getters ---\n\n get x(): number | null {\n return this._core.x;\n }\n\n get y(): number | null {\n return this._core.y;\n }\n\n get z(): number | null {\n return this._core.z;\n }\n\n get error(): WcsAccelerometerErrorDetail | null {\n return this._core.error;\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Commands ---\n\n start(): void {\n this._core.start(this.frequency ?? undefined);\n }\n\n stop(): void {\n this._core.stop();\n }\n\n // --- Lifecycle ---\n\n // Deliberately does NOT auto-start the sensor on connect. Unlike\n // Geolocation (whose default phase acquires a fix immediately unless\n // `manual` is set), Accelerometer has no such \"connect implies observing\"\n // precedent in the design doc (docs/sensor-tag-design.md §1.3):\n // start/stop are the only commands, so connecting the element merely makes\n // it inert until a command-token `start` (or the `start()` method) is\n // invoked. This also keeps behavior predictable when composed with\n // `<wcs-permission name=\"accelerometer\">`: the caller decides when to start,\n // typically gated on `granted`.\n connectedCallback(): void {\n this.style.display = \"none\";\n // No asynchronous probe to await (docs/async-io-node-guidelines.md §3.8);\n // kept for SSR uniformity with other IO nodes.\n this._connectedCallbackPromise = this._core.ready;\n }\n\n disconnectedCallback(): void {\n this._core.dispose();\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapAccelerometer(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n","import { WcsAccelerometer } from \"./components/Accelerometer.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.accelerometer)) {\n customElements.define(config.tagNames.accelerometer, WcsAccelerometer);\n }\n}\n"],"names":["_config","tagNames","accelerometer","deepFreeze","obj","Object","freeze","key","keys","deepClone","clone","frozenConfig","config","getConfig","NULL_READING","x","y","z","AccelerometerCore","EventTarget","static","protocol","version","properties","name","event","getter","e","detail","commands","_target","_reading","_error","_sensor","constructor","target","super","this","error","ready","Promise","resolve","_setReading","reading","dispatchEvent","CustomEvent","bubbles","_setError","message","start","frequency","sensor","_createSensor","addEventListener","_onReading","_onError","_teardownSensor","String","stop","observe","dispose","removeEventListener","Ctor","globalThis","Accelerometer","undefined","err","WcsAccelerometer","HTMLElement","wcBindable","inputs","_core","_connectedCallbackPromise","attr","getAttribute","trim","parsed","Number","isFinite","value","removeAttribute","setAttribute","connectedCallbackPromise","connectedCallback","style","display","disconnectedCallback","bootstrapAccelerometer","userConfig","partialConfig","assign","customElements","get","define"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wcstack/accelerometer",
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "Declarative Accelerometer component for Web Components. Framework-agnostic Generic Sensor API (Accelerometer) monitor 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
|
+
"accelerometer",
|
|
34
|
+
"generic-sensor",
|
|
35
|
+
"motion",
|
|
36
|
+
"custom-elements",
|
|
37
|
+
"wc-bindable",
|
|
38
|
+
"declarative",
|
|
39
|
+
"zero-dependencies",
|
|
40
|
+
"framework-agnostic"
|
|
41
|
+
],
|
|
42
|
+
"author": "mogera551",
|
|
43
|
+
"homepage": "https://wcstack.github.io",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/wcstack/wcstack.git",
|
|
47
|
+
"directory": "packages/accelerometer"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/wcstack/wcstack/issues"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^9.39.1",
|
|
55
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
56
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
57
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
58
|
+
"@vitest/ui": "^4.0.15",
|
|
59
|
+
"eslint": "^9.39.1",
|
|
60
|
+
"globals": "^16.5.0",
|
|
61
|
+
"happy-dom": "^20.0.11",
|
|
62
|
+
"rimraf": "^6.0.1",
|
|
63
|
+
"rollup": "^4.22.4",
|
|
64
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
65
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
66
|
+
"tslib": "^2.8.1",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.49.0",
|
|
69
|
+
"vitest": "^4.0.15"
|
|
70
|
+
}
|
|
71
|
+
}
|