@wcstack/intersection 1.12.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 ADDED
@@ -0,0 +1,193 @@
1
+ # @wcstack/intersection
2
+
3
+ `@wcstack/intersection` は wcstack エコシステム向けのヘッドレスな IntersectionObserver コンポーネントです。
4
+
5
+ これは視覚的な UI ウィジェットではありません。
6
+ `@wcstack/fetch` がネットワークリクエストをリアクティブな状態に変え、`@wcstack/geolocation` がデバイスの位置情報をリアクティブな状態に変えるのと同じように、**要素の*可視性*をリアクティブな状態に変える非同期プリミティブノード**です。
7
+
8
+ `@wcstack/state` と組み合わせると、`<wcs-intersect>` はパス契約を通じて直接バインドできます。
9
+
10
+ - **入力 / コマンド面**: `target`, `root`, `root-margin`, `threshold`, `once`, `manual`, `trigger`
11
+ - **出力状態面**: `entry`, `intersecting`, `ratio`, `visible`, `observing`
12
+
13
+ つまり、可視性を意識した処理 — 遅延読み込み、無限スクロール、スクロールスパイ — を HTML 上で宣言的に表現でき、UI 層に `new IntersectionObserver()` / `observe()` / `disconnect()`、後始末のグルーコードを書く必要がありません。
14
+
15
+ `@wcstack/intersection` は [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md)(Core / Shell / Binding Contract)アーキテクチャに従います。
16
+
17
+ - **Core**(`IntersectionCore`)が observer、entry の正規化、`visible` ラッチ、観測のライフサイクルを所有
18
+ - **Shell**(`<wcs-intersect>`)が*何を*観測するかを DOM から解決し、display・ライフサイクル・宣言的コマンドを管理
19
+ - **Binding Contract**(`static wcBindable`)が観測可能な `properties`・書き込み可能な `inputs`・呼び出し可能な `commands` を宣言
20
+
21
+ ## なぜ存在するのか
22
+
23
+ IntersectionObserver は他のすべての @wcstack センサと異なります。観測対象がヘッドレスなリソースではなく、**DOM 要素**だからです。命令的に配線するには、observer を作成し、ターゲットノードを解決し、entry コールバックを処理し、切断時にすべてを解体する必要があります。
24
+
25
+ `@wcstack/intersection` はそのロジックを再利用可能なコンポーネントに押し込み、結果をバインド可能な状態として公開します。要素がビューにスクロールインすることが命令的なコールバック配線ではなく、**状態遷移**になります。これは読み取り専用のプロデューサです。element/layout は state のために値を生み出すだけで(`element/layout → state`)、逆向きの経路はありません。
26
+
27
+ ## `target` 属性がすべてを決める
28
+
29
+ `target` は*何を*観測するかを選ぶ唯一のツマミであり、それに伴って `<wcs-intersect>` がどうレンダリングされるかも決めます。明示的に要求しない限り、layout box を決して注入しません。
30
+
31
+ | `target` | 観測対象 | `display` | ユースケース |
32
+ |-------------------|-----------------------|-------------|----------------------|
33
+ | *省略* | 最初の要素の子 | `contents` | 遅延読み込みラッパー |
34
+ | `"#hero"` / sel. | マッチした要素 | `none` | スクロールスパイ(単一) |
35
+ | `"self"` | 要素自身 | `block` | 無限スクロールの端 |
36
+
37
+ `display:contents` は、子をラップしても自身の box を注入しないことを意味します — そのため `<wcs-intersect><img></wcs-intersect>` は flex/grid の親を乱しません。明示的な `target="self"` センチネルだけが box を取ります。
38
+
39
+ > **最初の要素の子。** `target` が省略されると、*最初の要素の子*が観測されます。ターゲットは毎回の `observe()`(接続時と各 observed-attribute 変更時に実行)で再解決されるため、接続後に最初の子を追加・削除すると、次回の再観測で観測要素が切り替わります。解決時点で要素の子が無い場合は、自身を観測することにフォールバックします(`display:block`)。複数のターゲットを同時に観測することは意図的に対象外です — 各ターゲットをそれぞれ独自の `<wcs-intersect>` でラップしてください。
40
+
41
+ ## インストール
42
+
43
+ ```bash
44
+ npm install @wcstack/intersection
45
+ ```
46
+
47
+ ## クイックスタート
48
+
49
+ ### 1. 画像を遅延読み込み(`visible` ラッチ)
50
+
51
+ `visible` はターゲットが初めて交差したときに `true` に切り替わり、その後 `true` の**ままになります**。画像の `src` をこれにバインドすると、画像はビューにスクロールインしたときに一度だけ読み込まれます。
52
+
53
+ ```html
54
+ <script type="module" src="https://esm.run/@wcstack/state/auto"></script>
55
+ <script type="module" src="https://esm.run/@wcstack/intersection/auto"></script>
56
+
57
+ <wcs-state>
58
+ <script type="module">
59
+ export default {
60
+ shown: false,
61
+ get src() {
62
+ return this.shown ? "/photo.jpg" : "";
63
+ }
64
+ };
65
+ </script>
66
+ </wcs-state>
67
+
68
+ <wcs-intersect once data-wcs="visible: shown">
69
+ <img data-wcs="src: src" alt="lazy">
70
+ </wcs-intersect>
71
+ ```
72
+
73
+ `once` は最初の交差後に observer を切断します — 一度きりの遅延読み込みに最適です。
74
+
75
+ ### 2. 無限スクロール(センチネル)
76
+
77
+ リストの末尾に空の `target="self"` マーカーを置き、`intersecting` を「さらに読み込む」をトリガする state フラグにバインドします。
78
+
79
+ ```html
80
+ <ul data-wcs="for: items">
81
+ <li data-wcs="textContent: items.*.name"></li>
82
+ </ul>
83
+
84
+ <wcs-intersect target="self" data-wcs="intersecting: atEnd"></wcs-intersect>
85
+ ```
86
+
87
+ ```js
88
+ export default {
89
+ items: [],
90
+ atEnd: false,
91
+ get _loadMore() {
92
+ // atEnd が true になることに反応する computed/effect
93
+ return this.atEnd ? fetchNextPage() : null;
94
+ }
95
+ };
96
+ ```
97
+
98
+ ### 3. スクロールスパイ(単一セクション)
99
+
100
+ ドキュメント内の別の場所にあるセクションに `target` を向け、`intersecting` をバインドして対応するナビ項目をハイライトします。
101
+
102
+ ```html
103
+ <nav>
104
+ <a href="#features" data-wcs="class.active: featuresVisible">Features</a>
105
+ </nav>
106
+
107
+ <section id="features">…</section>
108
+
109
+ <wcs-intersect target="#features" threshold="0.5"
110
+ data-wcs="intersecting: featuresVisible"></wcs-intersect>
111
+ ```
112
+
113
+ ## 属性
114
+
115
+ | 属性 | 型 | 既定値 | 説明 |
116
+ |----------------|---------|------------|-------------|
117
+ | `target` | string | *(省略)* | 何を観測するか: 省略 → 最初の子、セレクタ → その要素、`self` → この要素。 |
118
+ | `root` | string | *(viewport)* | スクロールルートのセレクタ。 |
119
+ | `root-margin` | string | `0px` | ルート周りのマージン(CSS の margin 構文)。 |
120
+ | `threshold` | string | `0` | 単一の比率(`0.5`)またはカンマ区切りリスト(`0,0.5,1`)の `0..1` の閾値。不正 / 範囲外の値は捨てられる。 |
121
+ | `once` | boolean | `false` | 最初の交差した観測の後に切断する。 |
122
+ | `manual` | boolean | `false` | 接続時に自動観測しない。代わりにコマンドで駆動する。 |
123
+
124
+ > **`trigger`** には*属性がありません* — `@wcstack/state` の配線専用の瞬間的なコマンドプロパティです。`false → true` への書き込みは `observe()` を再実行し、プロパティは自動的に `false` にリセットされます(一度きりの確認応答。実際の結果は `observing` を読んでください)。状態駆動の観測には、この boolean より command-token プロトコル(`command.observe: …`)を優先してください。
125
+
126
+ ## 出力状態
127
+
128
+ | プロパティ | 型 | 説明 |
129
+ |----------------|----------------------------|-------------|
130
+ | `entry` | `WcsIntersectEntry \| null`| 直近の `IntersectionObserverEntry` のプレーンなスナップショット(rect はプレーンな数値に正規化)に、ライブの `target` ノードを加えたもの。 |
131
+ | `intersecting` | `boolean` | ターゲットが現在ルートと交差しているか。 |
132
+ | `ratio` | `number` | 直近の `intersectionRatio`。 |
133
+ | `visible` | `boolean` | ラッチ: ターゲットが一度交差すると `true`。`reset()` でのみクリアされる。 |
134
+ | `observing` | `boolean` | 現在観測がアクティブか。 |
135
+
136
+ ## コマンド
137
+
138
+ | コマンド | 説明 |
139
+ |---------------|-------------|
140
+ | `observe()` | DOM から `target` / `root` を再解決し、観測を(再)開始する。 |
141
+ | `unobserve()` | 現在のターゲットの観測を停止する。 |
142
+ | `disconnect()`| すべての観測を停止する。 |
143
+ | `reset()` | `visible` ラッチをクリアし、後の交差が再びそれを設定できるようにする。 |
144
+
145
+ ## Binding Contract(`wcBindable`)
146
+
147
+ Core と Shell の両方が [wc-bindable](https://github.com/csbc-dev) プロトコルを宣言します。
148
+
149
+ ```js
150
+ // IntersectionCore (headless)
151
+ IntersectionCore.wcBindable = {
152
+ protocol: "wc-bindable",
153
+ version: 1,
154
+ properties: [
155
+ { name: "entry", event: "wcs-intersect:change" },
156
+ { name: "intersecting", event: "wcs-intersect:change", getter: (e) => e.detail.isIntersecting },
157
+ { name: "ratio", event: "wcs-intersect:change", getter: (e) => e.detail.intersectionRatio },
158
+ { name: "visible", event: "wcs-intersect:visible-changed" },
159
+ { name: "observing", event: "wcs-intersect:observing-changed" },
160
+ ],
161
+ commands: [
162
+ { name: "observe" }, { name: "unobserve" }, { name: "disconnect" }, { name: "reset" },
163
+ ],
164
+ };
165
+ ```
166
+
167
+ Shell(`<wcs-intersect>`)は Core の `properties` / `commands` を継承し、瞬間的な `trigger` プロパティを追加し、DOM 駆動の `inputs`(`target`, `root`, `rootMargin`, `threshold`, `once`, `manual`, `trigger`)を宣言します。
168
+
169
+ ## Core を単体で使う
170
+
171
+ `IntersectionCore` はフレームワーク非依存で、カスタム要素なしで使えます。観測する要素を渡します(Shell はこの解決をあなたの代わりに行います)。
172
+
173
+ ```js
174
+ import { IntersectionCore } from "@wcstack/intersection";
175
+
176
+ const core = new IntersectionCore();
177
+ core.addEventListener("wcs-intersect:change", (e) => {
178
+ console.log(e.detail.isIntersecting, e.detail.intersectionRatio);
179
+ });
180
+ core.observe(document.querySelector("#hero"), { threshold: [0, 0.5, 1] });
181
+ // 後で
182
+ core.disconnect();
183
+ ```
184
+
185
+ ## 注意点と制約
186
+
187
+ - **単一ターゲット。** 各 `<wcs-intersect>` はちょうど 1 つの要素を観測するので、状態は単一の値面にマップします。多数のターゲットには多数の要素を使ってください。
188
+ - **決して throw しない。** 非対応環境(`IntersectionObserver` が無い)や不正なオプション(例: 不正な形式の `root-margin`)は無言の no-op です。throw せず `observing` が `false` のままになります。
189
+ - **パーミッションゲート / セキュアコンテキスト要件は無い**(`@wcstack/geolocation` と異なります)。
190
+
191
+ ## ライセンス
192
+
193
+ MIT
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # @wcstack/intersection
2
+
3
+ `@wcstack/intersection` is a headless IntersectionObserver component for the wcstack ecosystem.
4
+
5
+ It is not a visual UI widget.
6
+ It is an **async primitive node** that turns an element's *visibility* into reactive state — the same way `@wcstack/fetch` turns a network request into reactive state and `@wcstack/geolocation` turns the device's location into reactive state.
7
+
8
+ With `@wcstack/state`, `<wcs-intersect>` can be bound directly through path contracts:
9
+
10
+ - **input / command surface**: `target`, `root`, `root-margin`, `threshold`, `once`, `manual`, `trigger`
11
+ - **output state surface**: `entry`, `intersecting`, `ratio`, `visible`, `observing`
12
+
13
+ This means visibility-aware work — lazy-loading, infinite scroll, scroll-spying — can be expressed declaratively in HTML, without writing `new IntersectionObserver()`, `observe()`, `disconnect()`, or teardown glue in your UI layer.
14
+
15
+ `@wcstack/intersection` follows the [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md) (Core / Shell / Binding Contract) architecture:
16
+
17
+ - **Core** (`IntersectionCore`) owns the observer, entry normalization, the `visible` latch, and observation lifecycle
18
+ - **Shell** (`<wcs-intersect>`) resolves *what* to observe from the DOM, manages display, lifecycle, and declarative commands
19
+ - **Binding Contract** (`static wcBindable`) declares observable `properties`, writable `inputs`, and callable `commands`
20
+
21
+ ## Why this exists
22
+
23
+ IntersectionObserver is different from every other @wcstack sensor: the thing it observes is **a DOM element**, not a headless resource. Imperatively, wiring it up means creating an observer, resolving a target node, handling the entry callback, and tearing it all down on disconnect.
24
+
25
+ `@wcstack/intersection` moves that logic into a reusable component and exposes the result as bindable state. An element scrolling into view becomes a **state transition**, not imperative callback wiring. It is a read-only producer: the element/layout only produces values for the state (`element/layout → state`), with no path back.
26
+
27
+ ## The `target` attribute decides everything
28
+
29
+ `target` is the single knob that selects *what* is observed — and, with it, how `<wcs-intersect>` renders. It never injects a layout box unless you explicitly ask for one:
30
+
31
+ | `target` | observes | `display` | use case |
32
+ |-------------------|-----------------------|-------------|----------------------|
33
+ | *omitted* | first element child | `contents` | lazy-load wrapper |
34
+ | `"#hero"` / sel. | the matched element | `none` | scrollspy (single) |
35
+ | `"self"` | the element itself | `block` | infinite-scroll edge |
36
+
37
+ `display:contents` means wrapping a child injects no box of its own — so `<wcs-intersect><img></wcs-intersect>` does not disturb a flex/grid parent. Only the explicit `target="self"` sentinel takes a box.
38
+
39
+ > **First element child.** When `target` is omitted, the *first element child* is observed. The target is re-resolved on every `observe()` (which runs on connect and on each observed-attribute change), so adding or removing the first child after connect switches the observed element on the next re-observe. If there is no element child at resolution time, it falls back to observing itself (`display:block`). Observing multiple targets at once is intentionally out of scope — wrap each target in its own `<wcs-intersect>`.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install @wcstack/intersection
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ### 1. Lazy-load an image (`visible` latch)
50
+
51
+ `visible` flips to `true` the first time the target intersects and **stays** `true`. Bind the image `src` to it and the image only loads once it scrolls into view.
52
+
53
+ ```html
54
+ <script type="module" src="https://esm.run/@wcstack/state/auto"></script>
55
+ <script type="module" src="https://esm.run/@wcstack/intersection/auto"></script>
56
+
57
+ <wcs-state>
58
+ <script type="module">
59
+ export default {
60
+ shown: false,
61
+ get src() {
62
+ return this.shown ? "/photo.jpg" : "";
63
+ }
64
+ };
65
+ </script>
66
+ </wcs-state>
67
+
68
+ <wcs-intersect once data-wcs="visible: shown">
69
+ <img data-wcs="src: src" alt="lazy">
70
+ </wcs-intersect>
71
+ ```
72
+
73
+ `once` disconnects the observer after the first intersection — ideal for one-shot lazy loads.
74
+
75
+ ### 2. Infinite scroll (sentinel)
76
+
77
+ Place an empty `target="self"` marker at the bottom of a list; bind `intersecting` to a state flag that triggers loading more.
78
+
79
+ ```html
80
+ <ul data-wcs="for: items">
81
+ <li data-wcs="textContent: items.*.name"></li>
82
+ </ul>
83
+
84
+ <wcs-intersect target="self" data-wcs="intersecting: atEnd"></wcs-intersect>
85
+ ```
86
+
87
+ ```js
88
+ export default {
89
+ items: [],
90
+ atEnd: false,
91
+ get _loadMore() {
92
+ // a computed/effect that reacts to atEnd becoming true
93
+ return this.atEnd ? fetchNextPage() : null;
94
+ }
95
+ };
96
+ ```
97
+
98
+ ### 3. Scrollspy (single section)
99
+
100
+ Point `target` at a section elsewhere in the document; bind `intersecting` to highlight the matching nav item.
101
+
102
+ ```html
103
+ <nav>
104
+ <a href="#features" data-wcs="class.active: featuresVisible">Features</a>
105
+ </nav>
106
+
107
+ <section id="features">…</section>
108
+
109
+ <wcs-intersect target="#features" threshold="0.5"
110
+ data-wcs="intersecting: featuresVisible"></wcs-intersect>
111
+ ```
112
+
113
+ ## Attributes
114
+
115
+ | Attribute | Type | Default | Description |
116
+ |----------------|---------|------------|-------------|
117
+ | `target` | string | *(omitted)*| What to observe: omitted → first child, a selector → that element, `self` → this element. |
118
+ | `root` | string | *(viewport)* | Selector for the scroll root. |
119
+ | `root-margin` | string | `0px` | Margin around the root (CSS-margin syntax). |
120
+ | `threshold` | string | `0` | A single ratio (`0.5`) or comma list (`0,0.5,1`) of `0..1` thresholds. Invalid / out-of-range values are dropped. |
121
+ | `once` | boolean | `false` | Disconnect after the first intersecting observation. |
122
+ | `manual` | boolean | `false` | Do not auto-observe on connect; drive it via commands instead. |
123
+
124
+ > **`trigger`** has *no attribute* — it is a momentary command-property meant for `@wcstack/state` wiring only. A `false → true` write re-runs `observe()` and the property auto-resets to `false` (a one-shot acknowledgement; read `observing` for the actual outcome). Prefer the command-token protocol (`command.observe: …`) over this boolean for state-driven observation.
125
+
126
+ ## Output state
127
+
128
+ | Property | Type | Description |
129
+ |----------------|----------------------------|-------------|
130
+ | `entry` | `WcsIntersectEntry \| null`| Plain snapshot of the latest `IntersectionObserverEntry` (rects normalized to plain numbers), plus the live `target` node. |
131
+ | `intersecting` | `boolean` | Whether the target currently intersects the root. |
132
+ | `ratio` | `number` | The latest `intersectionRatio`. |
133
+ | `visible` | `boolean` | Latch: `true` once the target has intersected; cleared only by `reset()`. |
134
+ | `observing` | `boolean` | Whether an observation is currently active. |
135
+
136
+ ## Commands
137
+
138
+ | Command | Description |
139
+ |---------------|-------------|
140
+ | `observe()` | Re-resolve `target` / `root` from the DOM and (re)start observing. |
141
+ | `unobserve()` | Stop observing the current target. |
142
+ | `disconnect()`| Stop all observation. |
143
+ | `reset()` | Clear the `visible` latch so a later intersection can set it again. |
144
+
145
+ ## Binding Contract (`wcBindable`)
146
+
147
+ Both the Core and the Shell declare the [wc-bindable](https://github.com/csbc-dev) protocol.
148
+
149
+ ```js
150
+ // IntersectionCore (headless)
151
+ IntersectionCore.wcBindable = {
152
+ protocol: "wc-bindable",
153
+ version: 1,
154
+ properties: [
155
+ { name: "entry", event: "wcs-intersect:change" },
156
+ { name: "intersecting", event: "wcs-intersect:change", getter: (e) => e.detail.isIntersecting },
157
+ { name: "ratio", event: "wcs-intersect:change", getter: (e) => e.detail.intersectionRatio },
158
+ { name: "visible", event: "wcs-intersect:visible-changed" },
159
+ { name: "observing", event: "wcs-intersect:observing-changed" },
160
+ ],
161
+ commands: [
162
+ { name: "observe" }, { name: "unobserve" }, { name: "disconnect" }, { name: "reset" },
163
+ ],
164
+ };
165
+ ```
166
+
167
+ The Shell (`<wcs-intersect>`) inherits the Core's `properties` / `commands`, adds the momentary `trigger` property, and declares the DOM-driven `inputs` (`target`, `root`, `rootMargin`, `threshold`, `once`, `manual`, `trigger`).
168
+
169
+ ## Using the Core standalone
170
+
171
+ `IntersectionCore` is framework-agnostic and can be used without the custom element. You hand it the element to observe (the Shell does this resolution for you):
172
+
173
+ ```js
174
+ import { IntersectionCore } from "@wcstack/intersection";
175
+
176
+ const core = new IntersectionCore();
177
+ core.addEventListener("wcs-intersect:change", (e) => {
178
+ console.log(e.detail.isIntersecting, e.detail.intersectionRatio);
179
+ });
180
+ core.observe(document.querySelector("#hero"), { threshold: [0, 0.5, 1] });
181
+ // later
182
+ core.disconnect();
183
+ ```
184
+
185
+ ## Notes & limitations
186
+
187
+ - **Single target.** Each `<wcs-intersect>` observes exactly one element so the state maps to a single value surface. For many targets, use many elements.
188
+ - **Never throws.** Unsupported environments (no `IntersectionObserver`) and invalid options (e.g. a malformed `root-margin`) are silent no-ops: `observing` stays `false` rather than throwing.
189
+ - **No permission gate / secure context requirement** (unlike `@wcstack/geolocation`).
190
+
191
+ ## License
192
+
193
+ MIT
package/dist/auto.js ADDED
@@ -0,0 +1,3 @@
1
+ import { bootstrapIntersection } from "./index.esm.js";
2
+
3
+ bootstrapIntersection();
@@ -0,0 +1 @@
1
+ import{bootstrapIntersection as o}from"./index.esm.min.js";o();
@@ -0,0 +1,275 @@
1
+ interface ITagNames {
2
+ readonly intersect: string;
3
+ }
4
+ interface IWritableTagNames {
5
+ intersect?: string;
6
+ }
7
+ interface IConfig {
8
+ readonly tagNames: ITagNames;
9
+ }
10
+ interface IWritableConfig {
11
+ tagNames?: IWritableTagNames;
12
+ }
13
+ interface IWcBindableProperty {
14
+ readonly name: string;
15
+ readonly event: string;
16
+ readonly getter?: (event: Event) => any;
17
+ }
18
+ interface IWcBindableInput {
19
+ readonly name: string;
20
+ readonly attribute?: string;
21
+ }
22
+ interface IWcBindableCommand {
23
+ readonly name: string;
24
+ readonly async?: boolean;
25
+ }
26
+ interface IWcBindable {
27
+ readonly protocol: "wc-bindable";
28
+ readonly version: number;
29
+ readonly properties: IWcBindableProperty[];
30
+ readonly inputs?: IWcBindableInput[];
31
+ readonly commands?: IWcBindableCommand[];
32
+ }
33
+ /**
34
+ * Plain snapshot of a `DOMRectReadOnly` (e.g. `boundingClientRect`,
35
+ * `intersectionRect`, `rootBounds`). Unlike the live DOM rect, every field is a
36
+ * plain number so it can flow through data binding and be serialized.
37
+ */
38
+ interface WcsIntersectRect {
39
+ x: number;
40
+ y: number;
41
+ width: number;
42
+ height: number;
43
+ top: number;
44
+ right: number;
45
+ bottom: number;
46
+ left: number;
47
+ }
48
+ /**
49
+ * Payload carried by the `wcs-intersect:change` event — a structured-clone-friendly
50
+ * snapshot of `IntersectionObserverEntry`, plus the live `target` Element for
51
+ * consumers that need the actual node.
52
+ *
53
+ * `boundingClientRect` / `intersectionRect` are always present; `rootBounds` is
54
+ * `null` when the root is a cross-origin document (mirroring the native API).
55
+ */
56
+ interface WcsIntersectEntry {
57
+ isIntersecting: boolean;
58
+ intersectionRatio: number;
59
+ time: number;
60
+ boundingClientRect: WcsIntersectRect;
61
+ intersectionRect: WcsIntersectRect;
62
+ rootBounds: WcsIntersectRect | null;
63
+ /** The observed element. Not serializable — kept for consumers needing the node. */
64
+ target: Element;
65
+ }
66
+ /**
67
+ * Options accepted by `IntersectionCore.observe`, mirroring
68
+ * `IntersectionObserverInit`. `root` is an already-resolved Element (the Shell
69
+ * resolves a selector to a node before calling), or `null` for the viewport.
70
+ */
71
+ interface IntersectOptions {
72
+ root?: Element | null;
73
+ rootMargin?: string;
74
+ threshold?: number | number[];
75
+ }
76
+ /**
77
+ * Value types for IntersectionCore (headless) — the observable state properties.
78
+ * Use with `bind()` from `@wc-bindable/core` for compile-time type checking.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const core = new IntersectionCore();
83
+ * bind(core, (name: keyof WcsIntersectCoreValues, value) => { ... });
84
+ * core.observe(document.querySelector("#hero")!);
85
+ * ```
86
+ */
87
+ interface WcsIntersectCoreValues {
88
+ entry: WcsIntersectEntry | null;
89
+ intersecting: boolean;
90
+ ratio: number;
91
+ visible: boolean;
92
+ observing: boolean;
93
+ }
94
+ /**
95
+ * Value types for the Shell (`<wcs-intersect>`) — identical observable surface to
96
+ * the Core, plus the DOM-driven `trigger` command-property.
97
+ */
98
+ interface WcsIntersectValues extends WcsIntersectCoreValues {
99
+ trigger: boolean;
100
+ }
101
+ interface WcsIntersectInputs {
102
+ /**
103
+ * What to observe. Omitted → the first element child (the element itself
104
+ * renders as `display:contents`). A selector (`"#hero"`, `".section"`) → the
105
+ * matched element (`display:none`). The literal `"self"` → the element itself
106
+ * as a zero-height marker (`display:block`).
107
+ */
108
+ target: string;
109
+ /** Selector for the scroll root. Omitted → the viewport. */
110
+ root: string;
111
+ rootMargin: string;
112
+ /** A single ratio or a comma list (`"0,0.5,1"`) of 0..1 thresholds. */
113
+ threshold: string;
114
+ /** Disconnect after the first time the target becomes intersecting. */
115
+ once: boolean;
116
+ /** Do not auto-observe on connect; observe is driven manually instead. */
117
+ manual: boolean;
118
+ /**
119
+ * Momentary command-property (no mirrored attribute): a `false`→`true` write
120
+ * re-runs `observe()`, then the flag immediately resets to `false`. Unlike the
121
+ * other inputs it does not reflect to an HTML attribute.
122
+ */
123
+ trigger: boolean;
124
+ }
125
+ interface WcsIntersectCoreCommands {
126
+ observe(element: Element, options?: IntersectOptions): void;
127
+ unobserve(element: Element): void;
128
+ disconnect(): void;
129
+ /** Clear the `visible` latch so it can be set again by a later intersection. */
130
+ reset(): void;
131
+ }
132
+ interface WcsIntersectCommands {
133
+ /** Re-resolve the target/root from the DOM and (re)start observing. */
134
+ observe(): void;
135
+ unobserve(): void;
136
+ disconnect(): void;
137
+ reset(): void;
138
+ }
139
+
140
+ declare function bootstrapIntersection(userConfig?: IWritableConfig): void;
141
+
142
+ declare function getConfig(): IConfig;
143
+
144
+ /**
145
+ * Headless visibility primitive. A thin, framework-agnostic wrapper around the
146
+ * IntersectionObserver API exposed through the wc-bindable protocol.
147
+ *
148
+ * Unlike the other @wcstack sensors (geolocation / timer / websocket), the thing
149
+ * being observed is a *DOM element* — so `observe()` takes the target node. The
150
+ * Core stays DOM-resolution-agnostic: it observes whatever element it is handed
151
+ * (the Shell resolves `target` / `root` selectors before calling). It is a
152
+ * read-only producer — element/layout → state only, with no element-bound path.
153
+ *
154
+ * Every observer callback is published via the single `wcs-intersect:change`
155
+ * event; `intersecting` / `ratio` are read from it through getters (mirroring how
156
+ * GeolocationCore exposes latitude/longitude from one `wcs-geo:position` event),
157
+ * so an observer that binds any of them is notified on every change.
158
+ *
159
+ * `visible` is a latch: it flips to `true` the first time the target intersects
160
+ * and stays `true` until `reset()` — ideal for one-way lazy-load bindings
161
+ * (`src@visible`). `observing` reflects whether an observation is currently
162
+ * active (like TimerCore's `running`).
163
+ *
164
+ * Single-target by design: the Shell observes exactly one element, so the state
165
+ * reflects that element. Multi-target observation is intentionally out of scope.
166
+ */
167
+ declare class IntersectionCore extends EventTarget {
168
+ static wcBindable: IWcBindable;
169
+ private _target;
170
+ private _observer;
171
+ private _observed;
172
+ private _options;
173
+ private _entry;
174
+ private _visible;
175
+ private _observing;
176
+ constructor(target?: EventTarget);
177
+ get entry(): WcsIntersectEntry | null;
178
+ get intersecting(): boolean;
179
+ get ratio(): number;
180
+ get visible(): boolean;
181
+ get observing(): boolean;
182
+ private _setEntry;
183
+ private _setVisible;
184
+ private _setObserving;
185
+ /**
186
+ * Start observing `element`. Idempotent while already observing the same
187
+ * element with the same options. Changing the element or options tears down the
188
+ * current observer and builds a new one (IntersectionObserver options are fixed
189
+ * at construction, so reconfiguring requires a fresh observer).
190
+ *
191
+ * If IntersectionObserver is unavailable (SSR) or the options are invalid (e.g.
192
+ * a malformed `rootMargin`, which the constructor rejects), this is a silent
193
+ * no-op — `observing` stays false, consistent with the never-throw design of
194
+ * the other @wcstack sensors.
195
+ */
196
+ observe(element: Element, options?: IntersectOptions): void;
197
+ /**
198
+ * Stop observing `element`. A no-op if it is not the currently observed
199
+ * element. The observer instance is torn down (single-target Core), so a later
200
+ * observe() rebuilds it.
201
+ */
202
+ unobserve(element: Element): void;
203
+ /** Stop all observation and release the observer. */
204
+ disconnect(): void;
205
+ /** Clear the `visible` latch so a later intersection can set it again. */
206
+ reset(): void;
207
+ private _teardownObserver;
208
+ private _createObserver;
209
+ private _onIntersect;
210
+ private _normalizeEntry;
211
+ private _normalizeRect;
212
+ private _optionsEqual;
213
+ private _thresholdKey;
214
+ }
215
+
216
+ /**
217
+ * `<wcs-intersect>` — declarative IntersectionObserver.
218
+ *
219
+ * The `target` attribute is the single knob that decides both *what* is observed
220
+ * and how the element renders (it never injects a layout box unless asked):
221
+ *
222
+ * | `target` | observes | display | use case |
223
+ * |-----------------|-----------------------|-------------|-------------------|
224
+ * | omitted | first element child | `contents` | lazy-load wrapper |
225
+ * | `"#hero"` / sel | the matched element | `none` | scrollspy (single)|
226
+ * | `"self"` | the element itself | `block` | infinite-scroll |
227
+ *
228
+ * `display:contents` means wrapping a child injects no box of its own (so a
229
+ * `<wcs-intersect><img></wcs-intersect>` does not disturb a flex/grid parent);
230
+ * only the explicit `target="self"` sentinel takes a box.
231
+ */
232
+ declare class WcsIntersect extends HTMLElement {
233
+ static hasConnectedCallbackPromise: boolean;
234
+ static observedAttributes: string[];
235
+ static wcBindable: IWcBindable;
236
+ private _core;
237
+ private _trigger;
238
+ constructor();
239
+ get target(): string;
240
+ set target(value: string);
241
+ get root(): string;
242
+ set root(value: string);
243
+ get rootMargin(): string;
244
+ set rootMargin(value: string);
245
+ get threshold(): string;
246
+ set threshold(value: string);
247
+ get once(): boolean;
248
+ set once(value: boolean);
249
+ get manual(): boolean;
250
+ set manual(value: boolean);
251
+ get entry(): WcsIntersectEntry | null;
252
+ get intersecting(): boolean;
253
+ get ratio(): number;
254
+ get visible(): boolean;
255
+ get observing(): boolean;
256
+ get trigger(): boolean;
257
+ set trigger(value: boolean);
258
+ /** Re-resolve the target/root from the DOM and (re)start observing. */
259
+ observe(): void;
260
+ unobserve(): void;
261
+ disconnect(): void;
262
+ reset(): void;
263
+ private _resolveTarget;
264
+ private _resolveRoot;
265
+ private _safeQuery;
266
+ private _parseThreshold;
267
+ private _options;
268
+ private _onChange;
269
+ connectedCallback(): void;
270
+ disconnectedCallback(): void;
271
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
272
+ }
273
+
274
+ export { IntersectionCore, WcsIntersect, bootstrapIntersection, getConfig };
275
+ export type { IWritableConfig, IWritableTagNames, IntersectOptions, WcsIntersectCommands, WcsIntersectCoreCommands, WcsIntersectCoreValues, WcsIntersectEntry, WcsIntersectInputs, WcsIntersectRect, WcsIntersectValues };