@excom/network-status 0.1.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.
Files changed (34) hide show
  1. package/.rush/temp/chunked-rush-logs/network-status.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/network-status.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/network-status.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/index.ts +17 -0
  16. package/network-status.ts +215 -0
  17. package/package.json +42 -0
  18. package/rush-logs/network-status.apply-exports.cache.log +1 -0
  19. package/rush-logs/network-status.apply-exports.log +1 -0
  20. package/rush-logs/network-status.build_docs.cache.log +1 -0
  21. package/rush-logs/network-status.build_docs.log +1 -0
  22. package/rush-logs/network-status.build_package-metas.cache.log +1 -0
  23. package/rush-logs/network-status.build_package-metas.log +1 -0
  24. package/support/custom-elements.json +156 -0
  25. package/support/demos/offline-banner.html +6 -0
  26. package/support/demos/simple.html +5 -0
  27. package/support/dist-docs/network-status.md +125 -0
  28. package/support/docs/INTERNAL.md +12 -0
  29. package/support/docs/README.md +65 -0
  30. package/support/package-meta.json +103 -0
  31. package/support/tests/network-status.test.ts +401 -0
  32. package/support/tests/offline-banner.view.test.ts +20 -0
  33. package/support/tests/simple.view.test.ts +22 -0
  34. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 38.15602999999987
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 50.17207899999994
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 109.18563200000017
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "../../packages/network-status": "../../packages/network-status:YAC8G8l1nkKknuTXH370JI0/DynCPkP81MNQcvbO6fA=:"
3
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
package/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { NetworkStatus } from "./network-status";
2
+
3
+ NetworkStatus.define();
4
+
5
+ export { NetworkStatus };
6
+
7
+ type T_HTMLNetworkStatusElement = typeof NetworkStatus.CustomElement;
8
+ declare global {
9
+ interface HTMLNetworkStatusElement extends T_HTMLNetworkStatusElement {}
10
+ interface Window {
11
+ HTMLNetworkStatusElement: HTMLNetworkStatusElement;
12
+ }
13
+ interface HTMLElementTagNameMap {
14
+ "network-status": HTMLNetworkStatusElement;
15
+ }
16
+ }
17
+ export type { HTMLNetworkStatusElement };
@@ -0,0 +1,215 @@
1
+ import { ConstructorType, Neutron, TEvent } from "@excom/neutron";
2
+
3
+ interface NetworkConnection {
4
+ readonly type?: string;
5
+ readonly effectiveType?: string;
6
+ readonly downlink?: number;
7
+ readonly rtt?: number;
8
+ addEventListener(type: string, listener: EventListener): void;
9
+ removeEventListener(type: string, listener: EventListener): void;
10
+ }
11
+
12
+ declare global {
13
+ interface Navigator {
14
+ readonly connection?: NetworkConnection;
15
+ readonly mozConnection?: NetworkConnection;
16
+ readonly webkitConnection?: NetworkConnection;
17
+ }
18
+ }
19
+
20
+ export interface NetworkStatusProvision {
21
+ isOnline: boolean;
22
+ connectionType?: string | null;
23
+ effectiveType?: string | null;
24
+ downlink?: number | null;
25
+ rtt?: number | null;
26
+ lastOnline?: number | null;
27
+ lastOffline?: number | null;
28
+ }
29
+
30
+ export type NetworkStatusOnlineEvent = TEvent & {
31
+ type: "network-status-online";
32
+ detail: NetworkStatusProvision;
33
+ };
34
+
35
+ export type NetworkStatusOfflineEvent = TEvent & {
36
+ type: "network-status-offline";
37
+ detail: NetworkStatusProvision;
38
+ };
39
+
40
+ export type NetworkStatusChangeEvent = TEvent & {
41
+ type: "network-status-change";
42
+ detail: NetworkStatusProvision;
43
+ };
44
+
45
+ /**
46
+ * Reports live network connectivity — `navigator.onLine` plus, where the
47
+ * Network Information API is available, connection type and effective
48
+ * speed. Reflects `is-mounted` so CSS can distinguish "not yet upgraded"
49
+ * from a genuine offline state.
50
+ *
51
+ * The Network Information API (`connection-type` / `effective-type` /
52
+ * `downlink` / `rtt`) is Chromium-only — unsupported in Safari and
53
+ * Firefox, where those fields stay `null` and only `is-online` /
54
+ * `network-status-online` / `network-status-offline` are meaningful. See
55
+ * `INTERNAL.md` for known `navigator.onLine` reliability caveats.
56
+ *
57
+ * Nothing fires at mount — the first read only sets `is-online` and
58
+ * `.provision`. Select on the attribute / read the provision for the
59
+ * initial state; the events report transitions.
60
+ *
61
+ * @fires network-status-online - Dispatched on a transition from offline
62
+ * to online, never at mount. `event.detail` is the full `.provision`
63
+ * payload.
64
+ * @type NetworkStatusOnlineEvent
65
+ * @fires network-status-offline - Dispatched on a transition from online
66
+ * to offline, never at mount. `event.detail` is the full `.provision`
67
+ * payload.
68
+ * @type NetworkStatusOfflineEvent
69
+ * @fires network-status-change - Dispatched when connection details
70
+ * change without an online/offline transition (Network Information API
71
+ * only), never at mount. `event.detail` is the full `.provision`
72
+ * payload.
73
+ * @type NetworkStatusChangeEvent
74
+ */
75
+ export const NetworkStatus = Neutron({
76
+ tag: "network-status",
77
+ reflectDefaultProps: ["isMounted"],
78
+ props: {
79
+ // state
80
+ /**
81
+ * @provision
82
+ * Full connectivity snapshot: `{ isOnline, connectionType,
83
+ * effectiveType, downlink, rtt, lastOnline, lastOffline }`. Set at
84
+ * mount and on every change; the same object dispatched as
85
+ * `event.detail` on every fired event. `lastOnline` / `lastOffline`
86
+ * are stamped on transitions only. Not reflected as an attribute.
87
+ * @type NetworkStatusProvision
88
+ */
89
+ provision: Object as unknown as ConstructorType<NetworkStatusProvision>,
90
+ /**
91
+ * @state
92
+ * Current `navigator.onLine` value.
93
+ */
94
+ isOnline: Boolean,
95
+ /**
96
+ * @state
97
+ * Connection type (`wifi`, `cellular`, `ethernet`, …) from the
98
+ * Network Information API. `null` where unsupported (Safari,
99
+ * Firefox).
100
+ */
101
+ connectionType: String,
102
+ /**
103
+ * @state
104
+ * Effective connection type (`4g`, `3g`, `2g`, `slow-2g`) from the
105
+ * Network Information API. `null` where unsupported (Safari,
106
+ * Firefox).
107
+ */
108
+ effectiveType: String,
109
+ // private
110
+ networkConnection: Object as unknown as ConstructorType<NetworkConnection>,
111
+ },
112
+ })
113
+ .defineMethods({
114
+ setNetworkData: (
115
+ { isOnline, networkConnection, provision },
116
+ argData: { isOnline?: boolean } | undefined = {}
117
+ ) => {
118
+ const _isOnline = argData.isOnline ?? isOnline;
119
+ /* `provision` is only set here, so its absence is the first read.
120
+ A Boolean prop reads `false` before it's set, so `isOnline` alone
121
+ can't tell "unset" from "offline". */
122
+ const isFirstRead = !provision;
123
+ const isOnlineChanged = !isFirstRead && isOnline !== _isOnline;
124
+ const _connectionType = networkConnection?.type;
125
+ const _effectiveType = networkConnection?.effectiveType;
126
+ const newData = {
127
+ isOnline: _isOnline,
128
+ connectionType: _connectionType,
129
+ effectiveType: _effectiveType,
130
+ downlink: networkConnection?.downlink,
131
+ rtt: networkConnection?.rtt,
132
+ lastOnline:
133
+ isOnlineChanged && _isOnline ? Date.now() : provision?.lastOnline,
134
+ lastOffline:
135
+ isOnlineChanged && !_isOnline ? Date.now() : provision?.lastOffline,
136
+ };
137
+ return {
138
+ isOnline: _isOnline,
139
+ connectionType: _connectionType,
140
+ effectiveType: _effectiveType,
141
+ provision: newData as NetworkStatusProvision,
142
+ /* First read is state, not a transition: nothing fires at mount.
143
+ After that: `online` / `offline` on a flip, `change` otherwise. */
144
+ ...(isFirstRead
145
+ ? {}
146
+ : {
147
+ emit: [
148
+ `network-status-${
149
+ isOnlineChanged
150
+ ? _isOnline
151
+ ? "online"
152
+ : "offline"
153
+ : "change"
154
+ }`,
155
+ { detail: newData },
156
+ ],
157
+ }),
158
+ };
159
+ },
160
+ handleOnline: () => ({
161
+ setNetworkData: [{ isOnline: true }],
162
+ }),
163
+ handleOffline: () => ({
164
+ setNetworkData: [{ isOnline: false }],
165
+ }),
166
+ handleChange: () => ({
167
+ setNetworkData: [],
168
+ }),
169
+ })
170
+ .onConnected(({ isMoving, handleOnline, handleOffline, handleChange }) => {
171
+ // A move: keep the existing listeners
172
+ if (isMoving) return;
173
+
174
+ // Window + connection listeners
175
+ window.addEventListener("online", handleOnline);
176
+ window.addEventListener("offline", handleOffline);
177
+
178
+ // Initial `networkConnection`
179
+ const networkConnection =
180
+ navigator.connection ||
181
+ navigator.mozConnection ||
182
+ navigator.webkitConnection;
183
+
184
+ networkConnection?.addEventListener("change", handleChange);
185
+
186
+ return [
187
+ { networkConnection },
188
+ {
189
+ setNetworkData: [
190
+ {
191
+ isOnline: navigator.onLine,
192
+ },
193
+ ],
194
+ },
195
+ ];
196
+ })
197
+ .onDisconnected(
198
+ ({
199
+ isMoving,
200
+ handleOnline,
201
+ handleOffline,
202
+ handleChange,
203
+ networkConnection,
204
+ }) => {
205
+ // A move: keep the existing listeners
206
+ if (isMoving) return;
207
+ // Drop window + connection listeners
208
+ window.removeEventListener("online", handleOnline);
209
+ window.removeEventListener("offline", handleOffline);
210
+ networkConnection?.removeEventListener("change", handleChange);
211
+ return {
212
+ networkConnection: null,
213
+ };
214
+ }
215
+ );
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@excom/network-status",
3
+ "version": "0.1.0",
4
+ "description": "<network-status> custom element",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@excom/neutron": "^0.1.0"
12
+ },
13
+ "peerDependencies": {},
14
+ "devDependencies": {
15
+ "@excom/heft-rig": "^0.1.0"
16
+ },
17
+ "repository": {
18
+ "url": "excom-dev/nucleus",
19
+ "directory": "packages/network-status"
20
+ },
21
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/network-status/support/docs/README.md",
22
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
23
+ "keywords": [
24
+ "network-status",
25
+ "neutron",
26
+ "custom-elements"
27
+ ],
28
+ "excom": {
29
+ "packageType": "kit-element"
30
+ },
31
+ "scripts": {
32
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
33
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
34
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
35
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
36
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
37
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
38
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
39
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
40
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
41
+ }
42
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1,156 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "modules": [
4
+ {
5
+ "kind": "javascript-module",
6
+ "path": "network-status.ts",
7
+ "declarations": [
8
+ {
9
+ "kind": "class",
10
+ "name": "NetworkStatus",
11
+ "customElement": true,
12
+ "tagName": "network-status",
13
+ "description": "Reports live network connectivity — `navigator.onLine` plus, where the Network Information API is available, connection type and effective speed. Reflects `is-mounted` so CSS can distinguish \"not yet upgraded\" from a genuine offline state. The Network Information API (`connection-type` / `effective-type` / `downlink` / `rtt`) is Chromium-only — unsupported in Safari and Firefox, where those fields stay `null` and only `is-online` / `network-status-online` / `network-status-offline` are meaningful. See `INTERNAL.md` for known `navigator.onLine` reliability caveats. Nothing fires at mount — the first read only sets `is-online` and `.provision`. Select on the attribute / read the provision for the initial state; the events report transitions.",
14
+ "attributes": [
15
+ {
16
+ "name": "is-online",
17
+ "type": {
18
+ "text": "boolean"
19
+ },
20
+ "description": "Current `navigator.onLine` value.",
21
+ "fieldName": "isOnline"
22
+ },
23
+ {
24
+ "name": "connection-type",
25
+ "type": {
26
+ "text": "string"
27
+ },
28
+ "description": "Connection type (`wifi`, `cellular`, `ethernet`, …) from the Network Information API. `null` where unsupported (Safari, Firefox).",
29
+ "fieldName": "connectionType"
30
+ },
31
+ {
32
+ "name": "effective-type",
33
+ "type": {
34
+ "text": "string"
35
+ },
36
+ "description": "Effective connection type (`4g`, `3g`, `2g`, `slow-2g`) from the Network Information API. `null` where unsupported (Safari, Firefox).",
37
+ "fieldName": "effectiveType"
38
+ }
39
+ ],
40
+ "members": [
41
+ {
42
+ "kind": "field",
43
+ "name": "provision",
44
+ "type": {
45
+ "text": "NetworkStatusProvision",
46
+ "expanded": "{ isOnline: boolean; connectionType?: string | null; effectiveType?: string | null; downlink?: number | null; rtt?: number | null; lastOnline?: number | null; lastOffline?: number | null; }"
47
+ },
48
+ "privacy": "public",
49
+ "readonly": false,
50
+ "description": "Full connectivity snapshot: `{ isOnline, connectionType, effectiveType, downlink, rtt, lastOnline, lastOffline }`. Set at mount and on every change; the same object dispatched as `event.detail` on every fired event. `lastOnline` / `lastOffline` are stamped on transitions only. Not reflected as an attribute.",
51
+ "_neutron": {
52
+ "surface": "option"
53
+ }
54
+ },
55
+ {
56
+ "kind": "field",
57
+ "name": "isOnline",
58
+ "type": {
59
+ "text": "boolean"
60
+ },
61
+ "privacy": "public",
62
+ "readonly": true,
63
+ "description": "Current `navigator.onLine` value.",
64
+ "_neutron": {
65
+ "surface": "state"
66
+ }
67
+ },
68
+ {
69
+ "kind": "field",
70
+ "name": "connectionType",
71
+ "type": {
72
+ "text": "string"
73
+ },
74
+ "privacy": "public",
75
+ "readonly": true,
76
+ "description": "Connection type (`wifi`, `cellular`, `ethernet`, …) from the Network Information API. `null` where unsupported (Safari, Firefox).",
77
+ "_neutron": {
78
+ "surface": "state"
79
+ }
80
+ },
81
+ {
82
+ "kind": "field",
83
+ "name": "effectiveType",
84
+ "type": {
85
+ "text": "string"
86
+ },
87
+ "privacy": "public",
88
+ "readonly": true,
89
+ "description": "Effective connection type (`4g`, `3g`, `2g`, `slow-2g`) from the Network Information API. `null` where unsupported (Safari, Firefox).",
90
+ "_neutron": {
91
+ "surface": "state"
92
+ }
93
+ }
94
+ ],
95
+ "events": [
96
+ {
97
+ "name": "network-status-online",
98
+ "description": "Dispatched on a transition from offline to online, never at mount. `event.detail` is the full `.provision` payload.",
99
+ "type": {
100
+ "text": "NetworkStatusOnlineEvent",
101
+ "expanded": "CustomEvent & { type: \"network-status-online\"; detail: { isOnline: boolean; connectionType?: string | null; effectiveType?: string | null; downlink?: number | null; rtt?: number | null; lastOnline?: number | null; lastOffline?: number | null; }; bubbles: true; cancelable: true; composed: true }"
102
+ }
103
+ },
104
+ {
105
+ "name": "network-status-offline",
106
+ "description": "Dispatched on a transition from online to offline, never at mount. `event.detail` is the full `.provision` payload.",
107
+ "type": {
108
+ "text": "NetworkStatusOfflineEvent",
109
+ "expanded": "CustomEvent & { type: \"network-status-offline\"; detail: { isOnline: boolean; connectionType?: string | null; effectiveType?: string | null; downlink?: number | null; rtt?: number | null; lastOnline?: number | null; lastOffline?: number | null; }; bubbles: true; cancelable: true; composed: true }"
110
+ }
111
+ },
112
+ {
113
+ "name": "network-status-change",
114
+ "description": "Dispatched when connection details change without an online/offline transition (Network Information API only), never at mount. `event.detail` is the full `.provision` payload.",
115
+ "type": {
116
+ "text": "NetworkStatusChangeEvent",
117
+ "expanded": "CustomEvent & { type: \"network-status-change\"; detail: { isOnline: boolean; connectionType?: string | null; effectiveType?: string | null; downlink?: number | null; rtt?: number | null; lastOnline?: number | null; lastOffline?: number | null; }; bubbles: true; cancelable: true; composed: true }"
118
+ }
119
+ }
120
+ ],
121
+ "_neutron": {
122
+ "provisions": [
123
+ {
124
+ "name": "provision",
125
+ "type": {
126
+ "text": "NetworkStatusProvision",
127
+ "expanded": "{ isOnline: boolean; connectionType?: string | null; effectiveType?: string | null; downlink?: number | null; rtt?: number | null; lastOnline?: number | null; lastOffline?: number | null; }"
128
+ },
129
+ "description": "Full connectivity snapshot: `{ isOnline, connectionType, effectiveType, downlink, rtt, lastOnline, lastOffline }`. Set at mount and on every change; the same object dispatched as `event.detail` on every fired event. `lastOnline` / `lastOffline` are stamped on transitions only. Not reflected as an attribute.",
130
+ "fieldName": "provision"
131
+ }
132
+ ]
133
+ }
134
+ }
135
+ ],
136
+ "exports": [
137
+ {
138
+ "kind": "js",
139
+ "name": "NetworkStatus",
140
+ "declaration": {
141
+ "name": "NetworkStatus",
142
+ "module": "network-status.ts"
143
+ }
144
+ },
145
+ {
146
+ "kind": "custom-element-definition",
147
+ "name": "network-status",
148
+ "declaration": {
149
+ "name": "NetworkStatus",
150
+ "module": "network-status.ts"
151
+ }
152
+ }
153
+ ]
154
+ }
155
+ ]
156
+ }
@@ -0,0 +1,6 @@
1
+ <div>
2
+ <network-status></network-status>
3
+ <p role="alert">You're offline — some features may be unavailable.</p>
4
+ <small role="note">Banner is CSS-only, driven by
5
+ <code>[is-online]</code>. Go offline to see it appear.</small>
6
+ </div>
@@ -0,0 +1,5 @@
1
+ <div>
2
+ <network-status></network-status>
3
+ <small role="note">Live — reflects real connectivity. Toggle your
4
+ devtools network throttling (or your device's wifi/data) to see it change.</small>
5
+ </div>
@@ -0,0 +1,125 @@
1
+ # network-status
2
+
3
+ Live connectivity readout — `navigator.onLine` plus connection type and
4
+ speed where the browser exposes them.
5
+
6
+
7
+ ```html
8
+ <div>
9
+ <network-status></network-status>
10
+ <small role="note">Live — reflects real connectivity. Toggle your
11
+ devtools network throttling (or your device's wifi/data) to see it change.</small>
12
+ </div>
13
+ ```
14
+
15
+
16
+ ## Features
17
+
18
+ - **CSS-driven UI** Style from `[is-online]` / `[connection-type]`
19
+ - **Live events** `online` / `offline` / `change` fire as connectivity
20
+ shifts — on transitions, never at mount
21
+ - **Bindable state** `.provision` is the full snapshot; read it from Quark
22
+ with `prop("provision")`
23
+ - **Network Information API** Connection type + effective speed where
24
+ supported
25
+ - **Graceful degradation** Unsupported fields stay `null`; core
26
+ online/offline still works everywhere
27
+
28
+ ## Installation
29
+
30
+
31
+ `@excom/network-status` v0.1.0
32
+
33
+ ```bash
34
+ pnpm add @excom/network-status
35
+ ```
36
+
37
+ ```bash
38
+ npm install @excom/network-status
39
+ ```
40
+
41
+ ```bash
42
+ yarn add @excom/network-status
43
+ ```
44
+
45
+ ### Import
46
+
47
+ ```ts
48
+ import "@excom/network-status";
49
+ ```
50
+
51
+
52
+
53
+ ## Usage
54
+
55
+ Drop it anywhere and hook `[is-online]` / `[is-mounted]` with CSS, or
56
+ listen for its events from a parent.
57
+
58
+ ```html
59
+ <network-status></network-status>
60
+ ```
61
+
62
+ ```css
63
+ network-status:not([is-mounted])::before {
64
+ content: "Loading…";
65
+ }
66
+ network-status[is-online]::before {
67
+ content: "📶 Online";
68
+ }
69
+ network-status[is-mounted]:not([is-online])::before {
70
+ content: "📵 Offline";
71
+ }
72
+ ```
73
+
74
+ Nothing fires at mount: the first read sets `is-online` and `.provision`
75
+ only. React to the attribute (or `prop("provision")`) for the initial
76
+ state; `network-status-online` / `-offline` report transitions.
77
+
78
+ The Network Information API (`connection-type`, `effective-type`,
79
+ `downlink`, `rtt`) is Chromium-only — Safari and Firefox leave those
80
+ fields `null`. Build critical UX on `is-online` alone; treat the rest as
81
+ a progressive enhancement. Safari/iOS also have known `navigator.onLine`
82
+ reliability quirks — see `INTERNAL.md`.
83
+
84
+ ### API Reference
85
+
86
+
87
+ #### Attributes
88
+
89
+ | Name | Surface | Type | Default | Values | Description |
90
+ | --- | --- | --- | --- | --- | --- |
91
+ | `is-online` | state | `boolean` | | | Current `navigator.onLine` value. |
92
+ | `connection-type` | state | `string` | | | Connection type (`wifi`, `cellular`, `ethernet`, …) from the Network Information API. `null` where unsupported (Safari, Firefox). |
93
+ | `effective-type` | state | `string` | | | Effective connection type (`4g`, `3g`, `2g`, `slow-2g`) from the Network Information API. `null` where unsupported (Safari, Firefox). |
94
+
95
+ #### Provision
96
+
97
+ | Name | Type | Description |
98
+ | --- | --- | --- |
99
+ | `provision` | `NetworkStatusProvision` (`{ isOnline: boolean; connectionType?: string \| null; effectiveType?: string \| null; downlink?: number \| null; rtt?: number \| null; lastOnline?: number \| null; lastOffline?: number \| null; }`) | Full connectivity snapshot: `{ isOnline, connectionType, effectiveType, downlink, rtt, lastOnline, lastOffline }`. Set at mount and on every change; the same object dispatched as `event.detail` on every fired event. `lastOnline` / `lastOffline` are stamped on transitions only. Not reflected as an attribute. |
100
+
101
+ #### Fires
102
+
103
+ | Name | Type | Description |
104
+ | --- | --- | --- |
105
+ | `network-status-online` | `NetworkStatusOnlineEvent` (`CustomEvent & { type: "network-status-online"; detail: { isOnline: boolean; connectionType?: string \| null; effectiveType?: string \| null; downlink?: number \| null; rtt?: number \| null; lastOnline?: number \| null; lastOffline?: number \| null; }; bubbles: true; cancelable: true; composed: true }`) | Dispatched on a transition from offline to online, never at mount. `event.detail` is the full `.provision` payload. |
106
+ | `network-status-offline` | `NetworkStatusOfflineEvent` (`CustomEvent & { type: "network-status-offline"; detail: { isOnline: boolean; connectionType?: string \| null; effectiveType?: string \| null; downlink?: number \| null; rtt?: number \| null; lastOnline?: number \| null; lastOffline?: number \| null; }; bubbles: true; cancelable: true; composed: true }`) | Dispatched on a transition from online to offline, never at mount. `event.detail` is the full `.provision` payload. |
107
+ | `network-status-change` | `NetworkStatusChangeEvent` (`CustomEvent & { type: "network-status-change"; detail: { isOnline: boolean; connectionType?: string \| null; effectiveType?: string \| null; downlink?: number \| null; rtt?: number \| null; lastOnline?: number \| null; lastOffline?: number \| null; }; bubbles: true; cancelable: true; composed: true }`) | Dispatched when connection details change without an online/offline transition (Network Information API only), never at mount. `event.detail` is the full `.provision` payload. |
108
+
109
+
110
+
111
+ ### Examples
112
+
113
+ #### Offline banner
114
+
115
+ A banner shown purely by CSS attribute selector — no listeners needed.
116
+
117
+
118
+ ```html
119
+ <div>
120
+ <network-status></network-status>
121
+ <p role="alert">You're offline — some features may be unavailable.</p>
122
+ <small role="note">Banner is CSS-only, driven by
123
+ <code>[is-online]</code>. Go offline to see it appear.</small>
124
+ </div>
125
+ ```
@@ -0,0 +1,12 @@
1
+ - `navigator.onLine` is not a reliable "can reach the internet" signal.
2
+ Findings from the ws investigation: iOS Safari (and other WebKit
3
+ embeds) can report `true` while on a captive portal or a cellular
4
+ connection with no actual data path, and some Android WebViews lag
5
+ behind the real link state by several seconds after a transition.
6
+ Treat `is-online` as "the OS thinks it has a link", not as an
7
+ end-to-end connectivity check — pair with an actual request/heartbeat
8
+ if you need the stronger guarantee.
9
+ - Network Information API (`connection-type` / `effective-type` /
10
+ `downlink` / `rtt`) is Chromium-only. Safari and Firefox never expose
11
+ `navigator.connection`, so those fields stay `null` indefinitely on
12
+ those browsers — this is expected, not a bug.