@flighthq/statusbar 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/statusbar`. The `@fl
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/statusbar@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/statusbar)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/statusbar@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/statusbar)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { attachStatusBar, clearStatusBarStyleStack, createStatusBar, detachStatusBar, disposeStatusBar, explainStatusBarBackend, getStatusBarHeight, getStatusBarInfo, hasStatusBarStyleEntry, popStatusBarStyleEntry, pushStatusBarStyleEntry, setStatusBarColor, setStatusBarOverlaysContent, setStatusBarStyle, setStatusBarVisible, } from './contract';
1
+ export { attachStatusBar, clearStatusBarStyleStack, createStatusBar, detachStatusBar, disposeStatusBar, getStatusBarHeight, getStatusBarInfo, hasStatusBarStyleEntry, popStatusBarStyleEntry, pushStatusBarStyleEntry, setStatusBarColor, setStatusBarOverlaysContent, setStatusBarStyle, setStatusBarVisible, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { attachStatusBar, clearStatusBarStyleStack, createStatusBar, detachStatusBar, disposeStatusBar, explainStatusBarBackend, getStatusBarHeight, getStatusBarInfo, hasStatusBarStyleEntry, popStatusBarStyleEntry, pushStatusBarStyleEntry, setStatusBarColor, setStatusBarOverlaysContent, setStatusBarStyle, setStatusBarVisible, } from './contract';
1
+ export { attachStatusBar, clearStatusBarStyleStack, createStatusBar, detachStatusBar, disposeStatusBar, getStatusBarHeight, getStatusBarInfo, hasStatusBarStyleEntry, popStatusBarStyleEntry, pushStatusBarStyleEntry, setStatusBarColor, setStatusBarOverlaysContent, setStatusBarStyle, setStatusBarVisible, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
@@ -1,24 +1,18 @@
1
- import type { BackendExplanation, StatusBar, StatusBarAnimation, StatusBarBackend, StatusBarInfo, StatusBarStyle, StatusBarStyleEntry, StatusBarStyleEntryHandle } from '@flighthq/types/contract';
2
- export declare function attachStatusBar(bar: StatusBar): void;
3
- export declare function clearStatusBarStyleStack(): void;
1
+ import type { HasUiStatusBarChange, HasUiStatusBarColor, HasUiStatusBarInfo, HasUiStatusBarOverlays, HasUiStatusBarStyle, HasUiStatusBarStyleStack, HasUiStatusBarVisibility, StatusBar, StatusBarAnimation, StatusBarInfo, StatusBarStyle, StatusBarStyleEntry, StatusBarStyleEntryHandle } from '@flighthq/types/contract';
2
+ export declare function attachStatusBar(host: HasUiStatusBarChange & HasUiStatusBarInfo, bar: StatusBar): void;
3
+ export declare function clearStatusBarStyleStack(host: HasUiStatusBarStyleStack): void;
4
4
  export declare function createStatusBar(): StatusBar;
5
5
  export declare function createStatusBarInfo(): StatusBarInfo;
6
6
  export declare function detachStatusBar(bar: StatusBar): void;
7
7
  export declare function disposeStatusBar(bar: StatusBar): void;
8
- export declare function explainStatusBarBackend(): BackendExplanation;
9
- export declare function getStatusBarBackend(): StatusBarBackend;
10
- export declare function getStatusBarHeight(): number;
11
- export declare function getStatusBarInfo(out: StatusBarInfo): StatusBarInfo;
12
- export declare function hasStatusBarStyleEntry(handle: StatusBarStyleEntryHandle): boolean;
13
- export declare function installStatusBarHostBackend(backend: StatusBarBackend): void;
14
- export declare function observeStatusBarHostResult(operation: string, succeeded: boolean): void;
8
+ export declare function getStatusBarHeight(host: HasUiStatusBarInfo): number;
9
+ export declare function getStatusBarInfo(host: HasUiStatusBarInfo, out: StatusBarInfo): StatusBarInfo;
10
+ export declare function hasStatusBarStyleEntry(host: HasUiStatusBarStyleStack, handle: StatusBarStyleEntryHandle): boolean;
15
11
  export declare function packedRgbaToHexColor(color: number): string;
16
- export declare function popStatusBarStyleEntry(handle: StatusBarStyleEntryHandle): void;
17
- export declare function pushStatusBarStyleEntry(entry: Readonly<StatusBarStyleEntry>): StatusBarStyleEntryHandle;
18
- export declare function resetStatusBarBackendForTest(): void;
19
- export declare function setStatusBarBackend(backend: StatusBarBackend | null): void;
20
- export declare function setStatusBarColor(color: number, animated?: boolean): void;
21
- export declare function setStatusBarOverlaysContent(overlay: boolean): void;
22
- export declare function setStatusBarStyle(style: StatusBarStyle): void;
23
- export declare function setStatusBarVisible(visible: boolean, animation?: StatusBarAnimation): void;
12
+ export declare function popStatusBarStyleEntry(host: HasUiStatusBarStyleStack, handle: StatusBarStyleEntryHandle): void;
13
+ export declare function pushStatusBarStyleEntry(host: HasUiStatusBarStyleStack, entry: Readonly<StatusBarStyleEntry>): StatusBarStyleEntryHandle;
14
+ export declare function setStatusBarColor(host: HasUiStatusBarColor, color: number, animated?: boolean): void;
15
+ export declare function setStatusBarOverlaysContent(host: HasUiStatusBarOverlays, overlay: boolean): void;
16
+ export declare function setStatusBarStyle(host: HasUiStatusBarStyle, style: StatusBarStyle): void;
17
+ export declare function setStatusBarVisible(host: HasUiStatusBarVisibility, visible: boolean, animation?: StatusBarAnimation): void;
24
18
  //# sourceMappingURL=statusbar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"statusbar.d.ts","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,0BAA0B,CAAC;AAalC,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAOpD;AAKD,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAGD,wBAAgB,eAAe,IAAI,SAAS,CAI3C;AAID,wBAAgB,mBAAmB,IAAI,aAAa,CAQnD;AAGD,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAMpD;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAErD;AAED,wBAAgB,uBAAuB,IAAI,kBAAkB,CAa5D;AAGD,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAOD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,aAAa,CAElE;AAKD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAGjF;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAM3E;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKtF;AAGD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG1D;AAMD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAM9E;AAUD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GAAG,yBAAyB,CAWvG;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAKnD;AAGD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAE1E;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzE;AAGD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAElE;AAGD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAE7D;AAGD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAE1F"}
1
+ {"version":3,"file":"statusbar.d.ts","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,SAAS,EACT,kBAAkB,EAElB,aAAa,EAEb,cAAc,EAEd,mBAAmB,EACnB,yBAAyB,EAE1B,MAAM,0BAA0B,CAAC;AAElC,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CAQrG;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,wBAAwB,GAAG,IAAI,CAK7E;AAED,wBAAgB,eAAe,IAAI,SAAS,CAE3C;AAED,wBAAgB,mBAAmB,IAAI,aAAa,CAQnD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAKpD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAErD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAEnE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,aAAa,GAAG,aAAa,CAE5F;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAGjH;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAQ9G;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,wBAAwB,EAC9B,KAAK,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GACnC,yBAAyB,CAoB3B;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAEpG;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAEhG;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAExF;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,kBAAkB,GAC7B,IAAI,CAEN"}
package/dist/statusbar.js CHANGED
@@ -1,40 +1,23 @@
1
1
  import { createSignal, emitSignal } from '@flighthq/signals/contract';
2
- // Begins delivering OS-driven status bar changes to `bar`'s signals by subscribing to the active
3
- // backend. Idempotent: a prior subscription is torn down first. Pair with detachStatusBar /
4
- // disposeStatusBar.
5
- //
6
- // Each event carries a freshly allocated StatusBarInfo the listener owns outright. This is the one
7
- // place in the package that deliberately allocates per event rather than filling a shared scratch:
8
- // an emitted payload is retained by whoever listens, and a shared scratch would let the next event —
9
- // or an unrelated getStatusBarHeight() call — rewrite a snapshot a listener had already stored, and
10
- // would hand every attached bar the same object. Status bar changes are rare OS events, so the
11
- // allocation is not on any hot path; a caller that wants zero-allocation reads uses getStatusBarInfo
12
- // with its own `out`.
13
- export function attachStatusBar(bar) {
2
+ export function attachStatusBar(host, bar) {
14
3
  detachStatusBar(bar);
15
- const backend = getStatusBarBackend();
16
- const unsubscribe = backend.subscribe(() => {
17
- emitSignal(bar.onChange, backend.getInfo(createStatusBarInfo()));
4
+ const changeProvider = host.ui.statusBarChange;
5
+ const infoProvider = host.ui.statusBarInfo;
6
+ const unsubscribe = changeProvider.subscribe(() => {
7
+ emitSignal(bar.onChange, infoProvider.getInfo(createStatusBarInfo()));
18
8
  });
19
9
  _subscriptions.set(bar, unsubscribe);
20
10
  }
21
- // Empties the style stack and restores the status bar to the state it held before the first entry
22
- // was pushed. The counterpart to pushStatusBarStyleEntry for whole-screen teardown, where popping
23
- // each handle individually would mean tracking them all. A no-op on an empty stack.
24
- export function clearStatusBarStyleStack() {
25
- if (_styleStack.length === 0)
11
+ export function clearStatusBarStyleStack(host) {
12
+ const state = _styleStacks.get(host);
13
+ if (state === undefined || state.entries.length === 0)
26
14
  return;
27
- _styleStack.length = 0;
28
- _applyTopStyleEntry();
15
+ state.entries.length = 0;
16
+ applyTopStyleEntry(host, state);
29
17
  }
30
- // Allocates a StatusBar event entity with inert signals; call attachStatusBar to start delivery.
31
18
  export function createStatusBar() {
32
- return {
33
- onChange: createSignal(),
34
- };
19
+ return { onChange: createSignal() };
35
20
  }
36
- // Allocates a zeroed StatusBarInfo, suitable as the `out` for getStatusBarInfo.
37
- // height defaults to -1 (unknown), color to 0 (transparent black), style to 'default'.
38
21
  export function createStatusBarInfo() {
39
22
  return {
40
23
  color: 0,
@@ -44,236 +27,118 @@ export function createStatusBarInfo() {
44
27
  visible: true,
45
28
  };
46
29
  }
47
- // Stops delivery to `bar` and forgets its subscription. Safe to call when not attached.
48
30
  export function detachStatusBar(bar) {
49
31
  const unsubscribe = _subscriptions.get(bar);
50
- if (unsubscribe !== undefined) {
51
- unsubscribe();
52
- _subscriptions.delete(bar);
53
- }
32
+ if (unsubscribe === undefined)
33
+ return;
34
+ unsubscribe();
35
+ _subscriptions.delete(bar);
54
36
  }
55
- // Releases `bar` for garbage collection by detaching its backend subscription. The signals remain
56
- // plain GC-managed memory afterward.
57
37
  export function disposeStatusBar(bar) {
58
38
  detachStatusBar(bar);
59
39
  }
60
- export function explainStatusBarBackend() {
61
- if (_custom !== null) {
62
- return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
63
- }
64
- if (_host !== null) {
65
- return {
66
- conflict: _hostConflict,
67
- layer: 'host',
68
- operation: _hostObservation !== null ? _hostObservation.operation : null,
69
- viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
70
- };
71
- }
72
- return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
73
- }
74
- // The active status bar backend. Precedence: custom > host > sentinel.
75
- export function getStatusBarBackend() {
76
- return _custom ?? _host ?? _sentinel;
40
+ export function getStatusBarHeight(host) {
41
+ return host.ui.statusBarInfo.getInfo(_scratchInfo).height;
77
42
  }
78
- // Returns the status bar height in CSS pixels, or -1 when the host does not report it (web,
79
- // desktops). Convenience over getStatusBarInfo.
80
- // NOTE: On notched/island devices, the safe-area top inset (owned by @flighthq/device) may differ
81
- // from the status bar height. Use device.getSafeAreaInsets().top for layout-safe top padding;
82
- // use getStatusBarHeight() when you specifically need the status bar element's intrinsic height.
83
- export function getStatusBarHeight() {
84
- return getStatusBarBackend().getInfo(_scratchInfo).height;
43
+ export function getStatusBarInfo(host, out) {
44
+ return host.ui.statusBarInfo.getInfo(out);
85
45
  }
86
- // Fills `out` with the current status bar state snapshot and returns it. Alias-safe: `out` may
87
- // be the same object as any internal scratch.
88
- export function getStatusBarInfo(out) {
89
- return getStatusBarBackend().getInfo(out);
90
- }
91
- // True when `handle` names an entry still on the style stack. False for a popped handle, an unknown
92
- // one, or the invalid handle — so a component can check before popping without tracking its own
93
- // mounted flag.
94
- export function hasStatusBarStyleEntry(handle) {
46
+ export function hasStatusBarStyleEntry(host, handle) {
95
47
  if (handle === INVALID_HANDLE)
96
48
  return false;
97
- return _styleStack.some((e) => e.handle === handle);
98
- }
99
- export function installStatusBarHostBackend(backend) {
100
- if (_host !== null) {
101
- if (_host !== backend)
102
- _hostConflict = true;
103
- return;
104
- }
105
- _host = backend;
49
+ return _styleStacks.get(host)?.entries.some((entry) => entry.handle === handle) ?? false;
106
50
  }
107
- export function observeStatusBarHostResult(operation, succeeded) {
108
- _hostObservation = {
109
- operation,
110
- viability: succeeded ? 'available' : 'runtime-api-unavailable',
111
- };
112
- }
113
- // Converts a packed RGBA integer (0xRRGGBBAA) to a #rrggbb hex string, dropping alpha.
114
51
  export function packedRgbaToHexColor(color) {
115
52
  const rgb = (color >>> 8) & 0xffffff;
116
53
  return '#' + rgb.toString(16).padStart(6, '0');
117
54
  }
118
- // Removes the style stack entry identified by `handle`. If the handle is unknown or invalid,
119
- // this is a no-op. The remaining stack — falling back to the baseline captured before the first
120
- // push — is re-applied after removal, so a field the popped entry was the last to set returns to
121
- // what it was rather than staying where that entry left it.
122
- export function popStatusBarStyleEntry(handle) {
55
+ export function popStatusBarStyleEntry(host, handle) {
123
56
  if (handle === INVALID_HANDLE)
124
57
  return;
125
- const idx = _styleStack.findIndex((e) => e.handle === handle);
126
- if (idx === -1)
58
+ const state = _styleStacks.get(host);
59
+ if (state === undefined)
127
60
  return;
128
- _styleStack.splice(idx, 1);
129
- _applyTopStyleEntry();
130
- }
131
- // Pushes a style stack entry, returns an opaque handle for later pop. Nested components can push
132
- // entries and restore the previous state on unmount without global last-write-wins clashes.
133
- // Unset fields fall through to the next entry down the stack (last pushed wins per field), and
134
- // through an empty stack to the baseline.
135
- //
136
- // The baseline is read from the backend when the stack goes from empty to non-empty — the last
137
- // moment it still describes the pre-stack status bar. Capturing it at module load would be wrong
138
- // (no backend is installed yet) and re-reading it per push would capture the stack's own effect.
139
- export function pushStatusBarStyleEntry(entry) {
140
- if (_styleStack.length === 0) {
141
- _baseline = getStatusBarBackend().getInfo(createStatusBarInfo());
142
- // `_applied` starts as a copy of the baseline, not null, so the first push only calls setters for
143
- // fields it actually changes rather than restating the baseline back to the backend.
144
- _applied = getStatusBarBackend().getInfo(createStatusBarInfo());
61
+ const index = state.entries.findIndex((entry) => entry.handle === handle);
62
+ if (index === -1)
63
+ return;
64
+ state.entries.splice(index, 1);
65
+ applyTopStyleEntry(host, state);
66
+ }
67
+ export function pushStatusBarStyleEntry(host, entry) {
68
+ let state = _styleStacks.get(host);
69
+ if (state === undefined) {
70
+ const infoProvider = host.ui.statusBarInfo;
71
+ const baseline = infoProvider.getInfo(createStatusBarInfo());
72
+ state = {
73
+ applied: { ...baseline },
74
+ baseline,
75
+ colorProvider: host.ui.statusBarColor,
76
+ entries: [],
77
+ overlaysProvider: host.ui.statusBarOverlays,
78
+ styleProvider: host.ui.statusBarStyle,
79
+ visibilityProvider: host.ui.statusBarVisibility,
80
+ };
81
+ _styleStacks.set(host, state);
145
82
  }
146
83
  const handle = _nextHandle++;
147
- _styleStack.push({ handle, entry });
148
- _applyTopStyleEntry();
84
+ state.entries.push({ entry, handle });
85
+ applyTopStyleEntry(host, state);
149
86
  return handle;
150
87
  }
151
- export function resetStatusBarBackendForTest() {
152
- _custom = null;
153
- _host = null;
154
- _hostConflict = false;
155
- _hostObservation = null;
156
- }
157
- // Installs a custom status bar backend; pass null to clear the custom override.
158
- export function setStatusBarBackend(backend) {
159
- _custom = backend;
88
+ export function setStatusBarColor(host, color, animated) {
89
+ host.ui.statusBarColor.setBackgroundColor(color, animated);
160
90
  }
161
- // Sets the status bar background color from a packed RGBA integer (0xRRGGBBAA). On web this
162
- // updates the theme-color hint; alpha is ignored. Set animated to true for a smooth transition
163
- // (native hosts only; no-op on web).
164
- export function setStatusBarColor(color, animated) {
165
- getStatusBarBackend().setBackgroundColor(color, animated);
91
+ export function setStatusBarOverlaysContent(host, overlay) {
92
+ host.ui.statusBarOverlays.setOverlaysContent(overlay);
166
93
  }
167
- // Controls whether content draws under the status bar. No-op on web.
168
- export function setStatusBarOverlaysContent(overlay) {
169
- getStatusBarBackend().setOverlaysContent(overlay);
94
+ export function setStatusBarStyle(host, style) {
95
+ host.ui.statusBarStyle.setStyle(style);
170
96
  }
171
- // Sets the status bar foreground style ('light' | 'dark' | 'default'). No-op on web.
172
- export function setStatusBarStyle(style) {
173
- getStatusBarBackend().setStyle(style);
97
+ export function setStatusBarVisible(host, visible, animation) {
98
+ host.ui.statusBarVisibility.setVisible(visible, animation);
174
99
  }
175
- // Shows or hides the status bar. animation controls the transition; defaults to 'none'. No-op on web.
176
- export function setStatusBarVisible(visible, animation) {
177
- getStatusBarBackend().setVisible(visible, animation);
178
- }
179
- const _sentinel = {
180
- getInfo(out) {
181
- out.color = 0;
182
- out.height = -1;
183
- out.overlaysContent = false;
184
- out.style = 'default';
185
- out.visible = true;
186
- return out;
187
- },
188
- setBackgroundColor() { },
189
- setOverlaysContent() { },
190
- setStyle() { },
191
- setVisible() { },
192
- subscribe() {
193
- return () => { };
194
- },
195
- };
196
- let _custom = null;
197
- let _host = null;
198
- let _hostConflict = false;
199
- let _hostObservation = null;
100
+ const INVALID_HANDLE = -1;
200
101
  let _nextHandle = 1;
201
102
  const _scratchInfo = createStatusBarInfo();
202
- const _styleStack = [];
103
+ const _styleStacks = new WeakMap();
203
104
  const _subscriptions = new WeakMap();
204
- // The status bar as it stood before the stack's first entry, and the state the stack has actually
205
- // pushed to the backend. Together they make pop a restore rather than a no-op: `_baseline` supplies
206
- // the value a released field returns to, and `_applied` keeps the backend from being told things it
207
- // already knows. Both are cleared when the stack empties, so the next push re-reads a baseline that
208
- // is once again the pre-stack truth.
209
- let _baseline = null;
210
- let _applied = null;
211
- const INVALID_HANDLE = -1;
212
- // Merges the style stack top-down (last pushed = highest priority per field), falls unset fields
213
- // through to the captured baseline, and pushes to the backend only what actually changed.
214
- //
215
- // The baseline fallback is what makes a pop restore: merging the stack alone leaves a released field
216
- // undefined, and an undefined field used to mean "call no setter", which left the OS holding the
217
- // value of an entry that is no longer on the stack. Diffing against `_applied` keeps that fix from
218
- // costing a burst of redundant setter calls on every push and pop.
219
- function _applyTopStyleEntry() {
220
- const backend = getStatusBarBackend();
221
- let style;
222
- let visible;
105
+ function applyTopStyleEntry(host, state) {
106
+ let animation;
223
107
  let color;
224
108
  let overlaysContent;
225
- let animation;
226
- // Stack is in push order; iterate from last pushed (top) to earliest (bottom).
227
- for (let i = _styleStack.length - 1; i >= 0; i--) {
228
- const e = _styleStack[i].entry;
229
- if (style === undefined && e.style !== undefined)
230
- style = e.style;
231
- if (visible === undefined && e.visible !== undefined)
232
- visible = e.visible;
233
- if (color === undefined && e.color !== undefined)
234
- color = e.color;
235
- if (overlaysContent === undefined && e.overlaysContent !== undefined)
236
- overlaysContent = e.overlaysContent;
237
- if (animation === undefined && e.animation !== undefined)
238
- animation = e.animation;
239
- }
240
- const baseline = _baseline;
241
- if (baseline !== null) {
242
- if (style === undefined)
243
- style = baseline.style;
244
- if (visible === undefined)
245
- visible = baseline.visible;
246
- if (color === undefined)
247
- color = baseline.color;
248
- if (overlaysContent === undefined)
249
- overlaysContent = baseline.overlaysContent;
250
- }
251
- const applied = _applied;
252
- if (style !== undefined && style !== applied?.style)
253
- backend.setStyle(style);
254
- if (visible !== undefined && visible !== applied?.visible)
255
- backend.setVisible(visible, animation ?? 'none');
256
- if (color !== undefined && color !== applied?.color)
257
- backend.setBackgroundColor(color, false);
258
- if (overlaysContent !== undefined && overlaysContent !== applied?.overlaysContent) {
259
- backend.setOverlaysContent(overlaysContent);
260
- }
261
- if (_styleStack.length === 0) {
262
- // The stack is spent: the backend is back at the baseline, so forget both and let the next push
263
- // capture a fresh one.
264
- _baseline = null;
265
- _applied = null;
266
- return;
109
+ let style;
110
+ let visible;
111
+ for (let index = state.entries.length - 1; index >= 0; index--) {
112
+ const entry = state.entries[index].entry;
113
+ if (animation === undefined && entry.animation !== undefined)
114
+ animation = entry.animation;
115
+ if (color === undefined && entry.color !== undefined)
116
+ color = entry.color;
117
+ if (overlaysContent === undefined && entry.overlaysContent !== undefined) {
118
+ overlaysContent = entry.overlaysContent;
119
+ }
120
+ if (style === undefined && entry.style !== undefined)
121
+ style = entry.style;
122
+ if (visible === undefined && entry.visible !== undefined)
123
+ visible = entry.visible;
267
124
  }
268
- const next = _applied ?? createStatusBarInfo();
269
- if (style !== undefined)
270
- next.style = style;
271
- if (visible !== undefined)
272
- next.visible = visible;
273
- if (color !== undefined)
274
- next.color = color;
275
- if (overlaysContent !== undefined)
276
- next.overlaysContent = overlaysContent;
277
- _applied = next;
125
+ color ??= state.baseline.color;
126
+ overlaysContent ??= state.baseline.overlaysContent;
127
+ style ??= state.baseline.style;
128
+ visible ??= state.baseline.visible;
129
+ if (color !== state.applied.color)
130
+ state.colorProvider.setBackgroundColor(color, false);
131
+ if (overlaysContent !== state.applied.overlaysContent)
132
+ state.overlaysProvider.setOverlaysContent(overlaysContent);
133
+ if (style !== state.applied.style)
134
+ state.styleProvider.setStyle(style);
135
+ if (visible !== state.applied.visible)
136
+ state.visibilityProvider.setVisible(visible, animation ?? 'none');
137
+ state.applied.color = color;
138
+ state.applied.overlaysContent = overlaysContent;
139
+ state.applied.style = style;
140
+ state.applied.visible = visible;
141
+ if (state.entries.length === 0)
142
+ _styleStacks.delete(host);
278
143
  }
279
144
  //# sourceMappingURL=statusbar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"statusbar.js","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"statusbar.js","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/statusbar",
3
- "version": "0.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.638.d4dbd4e",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,8 +38,8 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/signals": "0.5.0-next.1944.cd6b6e7",
42
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/signals": "0.5.1-edge.638.d4dbd4e",
42
+ "@flighthq/types": "0.5.1-edge.638.d4dbd4e"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.3.0"
@@ -1,622 +1,201 @@
1
1
  import { connectSignal } from '@flighthq/signals/contract';
2
- import type { StatusBar, StatusBarBackend, StatusBarInfo, StatusBarStyle } from '@flighthq/types/contract';
2
+ import type {
3
+ HasUiStatusBarChange,
4
+ HasUiStatusBarStyleStack,
5
+ StatusBarInfo,
6
+ StatusBarStyle,
7
+ } from '@flighthq/types/contract';
8
+ import { describe, expect, it, vi } from 'vitest';
3
9
 
4
- import {
5
- attachStatusBar,
6
- clearStatusBarStyleStack,
7
- createStatusBar,
8
- createStatusBarInfo,
9
- detachStatusBar,
10
- disposeStatusBar,
11
- explainStatusBarBackend,
12
- getStatusBarBackend,
13
- getStatusBarHeight,
14
- getStatusBarInfo,
15
- hasStatusBarStyleEntry,
16
- installStatusBarHostBackend,
17
- observeStatusBarHostResult,
18
- packedRgbaToHexColor,
19
- popStatusBarStyleEntry,
20
- pushStatusBarStyleEntry,
21
- resetStatusBarBackendForTest,
22
- setStatusBarBackend,
23
- setStatusBarColor,
24
- setStatusBarOverlaysContent,
25
- setStatusBarStyle,
26
- setStatusBarVisible,
27
- } from './statusbar';
28
-
29
- function fakeBackend(): StatusBarBackend & {
30
- _emit(): void;
31
- animatedColor: boolean | undefined;
32
- animation: string | undefined;
33
- color: number;
34
- infoHeight: number;
35
- overlay: boolean;
36
- style: StatusBarStyle;
37
- subscribeCallCount: number;
38
- visible: boolean;
39
- } {
40
- let listener: (() => void) | null = null;
41
- return {
42
- animatedColor: undefined,
43
- animation: undefined,
44
- color: 0,
45
- infoHeight: 42,
46
- overlay: false,
47
- style: 'default',
48
- subscribeCallCount: 0,
49
- visible: true,
50
- getInfo(out: StatusBarInfo): StatusBarInfo {
51
- out.color = this.color;
52
- out.height = this.infoHeight;
53
- out.overlaysContent = this.overlay;
54
- out.style = this.style;
55
- out.visible = this.visible;
56
- return out;
57
- },
58
- setBackgroundColor(color: number, animated?: boolean): void {
59
- this.color = color;
60
- this.animatedColor = animated;
61
- },
62
- setOverlaysContent(overlay: boolean): void {
63
- this.overlay = overlay;
64
- },
65
- setStyle(style: StatusBarStyle): void {
66
- this.style = style;
67
- },
68
- setVisible(visible: boolean, animation?: string): void {
69
- this.visible = visible;
70
- this.animation = animation;
71
- },
72
- subscribe(l: () => void): () => void {
73
- this.subscribeCallCount++;
74
- listener = l;
75
- return () => {
76
- listener = null;
77
- };
78
- },
79
- // Trigger for tests: fire the subscription listener externally.
80
- _emit(): void {
81
- if (listener !== null) listener();
82
- },
83
- } as ReturnType<typeof fakeBackend>;
84
- }
85
-
86
- afterEach(() => {
87
- clearStatusBarStyleStack();
88
- resetStatusBarBackendForTest();
89
- });
10
+ import * as statusbar from './statusbar';
90
11
 
91
12
  describe('attachStatusBar', () => {
92
- it('subscribes to the backend and emits onChange on change', () => {
93
- const backend = fakeBackend();
94
- setStatusBarBackend(backend);
95
- const bar = createStatusBar();
96
- const received: StatusBarInfo[] = [];
97
- bar.onChange.emit = (info) => {
98
- received.push({ ...info });
99
- };
100
- attachStatusBar(bar);
101
- (backend as ReturnType<typeof fakeBackend>)._emit();
102
- expect(received.length).toBe(1);
103
- expect(received[0].height).toBe(42);
104
- disposeStatusBar(bar);
105
- });
106
-
107
- it('is idempotent: re-attaching replaces the subscription', () => {
108
- const backend = fakeBackend();
109
- setStatusBarBackend(backend);
110
- const bar = createStatusBar();
111
- attachStatusBar(bar);
112
- attachStatusBar(bar);
113
- expect(backend.subscribeCallCount).toBe(2);
114
- disposeStatusBar(bar);
115
- });
116
-
117
- it('hands each event a payload the listener owns, not a shared scratch', () => {
118
- // The regression this pins: the emitted info used to be a single module-level scratch, so a
119
- // listener that retained a snapshot watched it change under them on the next event — or on an
120
- // unrelated getStatusBarHeight() call — and two attached bars received the same object.
121
- const backend = fakeBackend();
122
- setStatusBarBackend(backend);
123
- const bar = createStatusBar();
13
+ it('pins both event and snapshot providers at attachment time', () => {
14
+ const first = fakeHost('light');
15
+ const second = fakeHost('dark');
16
+ const bar = statusbar.createStatusBar();
124
17
  const seen: StatusBarInfo[] = [];
125
18
  connectSignal(bar.onChange, (info) => seen.push(info));
126
- attachStatusBar(bar);
127
19
 
128
- backend.infoHeight = 44;
129
- backend._emit();
130
- backend.infoHeight = 99;
131
- backend._emit();
132
- expect(seen.length).toBe(2);
133
- expect(seen[0]).not.toBe(seen[1]);
134
- expect(seen[0].height).toBe(44);
135
- expect(seen[1].height).toBe(99);
20
+ statusbar.attachStatusBar(first.host, bar);
21
+ first.emit();
22
+ statusbar.attachStatusBar(second.host, bar);
23
+ first.emit();
24
+ second.emit();
136
25
 
137
- // An unrelated read must not reach back into a payload already delivered.
138
- backend.infoHeight = 12;
139
- getStatusBarHeight();
140
- expect(seen[0].height).toBe(44);
141
- });
142
-
143
- it('gives two attached bars separate payload objects', () => {
144
- const backend = fakeBackend();
145
- setStatusBarBackend(backend);
146
- const first = createStatusBar();
147
- const second = createStatusBar();
148
- const seen: StatusBarInfo[] = [];
149
- connectSignal(first.onChange, (info) => seen.push(info));
150
- connectSignal(second.onChange, (info) => seen.push(info));
151
- attachStatusBar(first);
152
- attachStatusBar(second);
153
- // The fake keeps one listener slot, so drive each bar's subscription in turn.
154
- backend._emit();
155
- attachStatusBar(first);
156
- backend._emit();
157
- expect(seen.length).toBe(2);
158
- expect(seen[0]).not.toBe(seen[1]);
26
+ expect(first.unsubscribe).toHaveBeenCalledOnce();
27
+ expect(seen.map((info) => info.style)).toEqual(['light', 'dark']);
159
28
  });
160
29
  });
161
30
 
162
31
  describe('clearStatusBarStyleStack', () => {
163
- it('empties the stack and restores the baseline in one call', () => {
164
- const backend = fakeBackend();
165
- backend.style = 'default';
166
- backend.visible = true;
167
- setStatusBarBackend(backend);
168
- pushStatusBarStyleEntry({ style: 'dark' });
169
- pushStatusBarStyleEntry({ visible: false });
170
- clearStatusBarStyleStack();
171
- expect(backend.style).toBe('default');
172
- expect(backend.visible).toBe(true);
173
- });
174
-
175
- it('drops every handle', () => {
176
- const backend = fakeBackend();
177
- setStatusBarBackend(backend);
178
- const first = pushStatusBarStyleEntry({ style: 'dark' });
179
- const second = pushStatusBarStyleEntry({ style: 'light' });
180
- clearStatusBarStyleStack();
181
- expect(hasStatusBarStyleEntry(first)).toBe(false);
182
- expect(hasStatusBarStyleEntry(second)).toBe(false);
183
- });
184
-
185
- it('is a no-op on an empty stack', () => {
186
- const backend = fakeBackend();
187
- setStatusBarBackend(backend);
188
- clearStatusBarStyleStack();
189
- expect(() => clearStatusBarStyleStack()).not.toThrow();
190
- expect(backend.style).toBe('default');
32
+ it('restores only the passed host baseline', () => {
33
+ const first = fakeHost('light');
34
+ const second = fakeHost('dark');
35
+ statusbar.pushStatusBarStyleEntry(first.host, { style: 'dark' });
36
+ statusbar.pushStatusBarStyleEntry(second.host, { style: 'light' });
37
+ statusbar.clearStatusBarStyleStack(first.host);
38
+ expect(first.setStyle).toHaveBeenLastCalledWith('light');
39
+ expect(second.setStyle).toHaveBeenCalledTimes(1);
191
40
  });
192
41
  });
193
42
 
194
43
  describe('createStatusBar', () => {
195
- it('returns a StatusBar with an inert onChange signal', () => {
196
- const bar = createStatusBar();
197
- expect(bar.onChange).not.toBeNull();
198
- // Signals are inert (no listeners) until attachStatusBar is called.
199
- });
44
+ it('creates an inert signal entity', () => expect(statusbar.createStatusBar().onChange).toBeDefined());
200
45
  });
201
46
 
202
47
  describe('createStatusBarInfo', () => {
203
- it('returns defaults with height -1 and style default', () => {
204
- const info = createStatusBarInfo();
205
- expect(info.height).toBe(-1);
206
- expect(info.style).toBe('default');
207
- expect(info.visible).toBe(true);
208
- expect(info.overlaysContent).toBe(false);
209
- expect(info.color).toBe(0);
48
+ it('creates the documented unknown/default snapshot', () => {
49
+ expect(statusbar.createStatusBarInfo()).toEqual({
50
+ color: 0,
51
+ height: -1,
52
+ overlaysContent: false,
53
+ style: 'default',
54
+ visible: true,
55
+ });
210
56
  });
211
57
  });
212
58
 
213
59
  describe('detachStatusBar', () => {
214
- it('stops subscription and is safe to call when not attached', () => {
215
- const backend = fakeBackend();
216
- setStatusBarBackend(backend);
217
- const bar = createStatusBar();
218
- expect(() => detachStatusBar(bar)).not.toThrow();
219
- attachStatusBar(bar);
220
- detachStatusBar(bar);
221
- let emitCount = 0;
222
- bar.onChange.emit = () => {
223
- emitCount++;
224
- };
225
- (backend as ReturnType<typeof fakeBackend>)._emit();
226
- expect(emitCount).toBe(0);
60
+ it('releases the exact attached provider once', () => {
61
+ const provider = fakeHost('light');
62
+ const bar = statusbar.createStatusBar();
63
+ statusbar.attachStatusBar(provider.host, bar);
64
+ statusbar.detachStatusBar(bar);
65
+ statusbar.detachStatusBar(bar);
66
+ expect(provider.unsubscribe).toHaveBeenCalledOnce();
227
67
  });
228
68
  });
229
69
 
230
70
  describe('disposeStatusBar', () => {
231
- it('detaches subscription and releases the entity', () => {
232
- const backend = fakeBackend();
233
- setStatusBarBackend(backend);
234
- const bar = createStatusBar();
235
- attachStatusBar(bar);
236
- disposeStatusBar(bar);
237
- let emitCount = 0;
238
- bar.onChange.emit = () => {
239
- emitCount++;
240
- };
241
- (backend as ReturnType<typeof fakeBackend>)._emit();
242
- expect(emitCount).toBe(0);
243
- });
244
- });
245
-
246
- describe('explainStatusBarBackend', () => {
247
- afterEach(() => resetStatusBarBackendForTest());
248
-
249
- it('reports host-not-enabled when no backend is installed', () => {
250
- resetStatusBarBackendForTest();
251
- const explanation = explainStatusBarBackend();
252
- expect(explanation.layer).toBe('host-not-enabled');
253
- expect(explanation.conflict).toBe(false);
254
- expect(explanation.viability).toBe('unobserved');
255
- });
256
-
257
- it('reports custom layer when a custom backend is set', () => {
258
- setStatusBarBackend(fakeBackend());
259
- expect(explainStatusBarBackend().layer).toBe('custom');
260
- });
261
-
262
- it('reports host layer when a host backend is installed', () => {
263
- installStatusBarHostBackend(fakeBackend());
264
- expect(explainStatusBarBackend().layer).toBe('host');
265
- });
266
-
267
- it('reports conflict when two different host backends are installed', () => {
268
- installStatusBarHostBackend(fakeBackend());
269
- installStatusBarHostBackend(fakeBackend());
270
- expect(explainStatusBarBackend().conflict).toBe(true);
271
- });
272
- });
273
-
274
- describe('getStatusBarBackend', () => {
275
- it('falls back to a web backend', () => {
276
- expect(getStatusBarBackend()).not.toBeNull();
277
- });
278
-
279
- it('returns the registered backend', () => {
280
- const backend = fakeBackend();
281
- setStatusBarBackend(backend);
282
- expect(getStatusBarBackend()).toBe(backend);
283
- });
284
- });
285
-
286
- describe('getStatusBarBackend (sentinel)', () => {
287
- it('no-ops style/visible/overlay/subscribe without throwing', () => {
288
- const backend = getStatusBarBackend();
289
- expect(() => {
290
- backend.setStyle('light');
291
- backend.setVisible(false, 'slide');
292
- backend.setOverlaysContent(true);
293
- const unsub = backend.subscribe(() => {});
294
- unsub();
295
- }).not.toThrow();
296
- });
297
-
298
- it('getInfo returns -1 height and defaults', () => {
299
- const backend = getStatusBarBackend();
300
- const info = createStatusBarInfo();
301
- backend.getInfo(info);
302
- expect(info.height).toBe(-1);
303
- expect(info.visible).toBe(true);
304
- expect(info.color).toBe(0);
305
- });
306
-
307
- it('setBackgroundColor is a no-op', () => {
308
- const backend = getStatusBarBackend();
309
- expect(() => backend.setBackgroundColor(0xff0000ff)).not.toThrow();
310
- });
311
-
312
- it('subscribe returns a no-op unsubscribe function', () => {
313
- const backend = getStatusBarBackend();
314
- const unsub = backend.subscribe(() => {});
315
- expect(() => unsub()).not.toThrow();
71
+ it('detaches its subscription', () => {
72
+ const provider = fakeHost('light');
73
+ const bar = statusbar.createStatusBar();
74
+ statusbar.attachStatusBar(provider.host, bar);
75
+ statusbar.disposeStatusBar(bar);
76
+ expect(provider.unsubscribe).toHaveBeenCalledOnce();
316
77
  });
317
78
  });
318
79
 
319
80
  describe('getStatusBarHeight', () => {
320
- it('returns the height from the active backend', () => {
321
- const backend = fakeBackend();
322
- setStatusBarBackend(backend);
323
- expect(getStatusBarHeight()).toBe(42);
324
- });
325
-
326
- it('returns -1 when the web backend reports unknown height', () => {
327
- document.head.querySelectorAll('meta[name="theme-color"]').forEach((m) => m.remove());
328
- // Web backend always returns -1 for height.
329
- expect(getStatusBarHeight()).toBe(-1);
330
- });
81
+ it('reads from the passed host', () => expect(statusbar.getStatusBarHeight(fakeHost('light').host)).toBe(20));
331
82
  });
332
83
 
333
84
  describe('getStatusBarInfo', () => {
334
- it('fills the out parameter from the active backend', () => {
335
- const backend = fakeBackend();
336
- backend.color = 0x112233ff;
337
- backend.style = 'light';
338
- backend.visible = false;
339
- backend.overlay = true;
340
- setStatusBarBackend(backend);
341
- const out = createStatusBarInfo();
342
- const result = getStatusBarInfo(out);
343
- expect(result).toBe(out); // returns `out`
344
- expect(out.color).toBe(0x112233ff);
345
- expect(out.style).toBe('light');
346
- expect(out.visible).toBe(false);
347
- expect(out.overlaysContent).toBe(true);
348
- expect(out.height).toBe(42);
349
- });
350
-
351
- it('is alias-safe: out may be reused across calls', () => {
352
- const backend = fakeBackend();
353
- setStatusBarBackend(backend);
354
- const out = createStatusBarInfo();
355
- getStatusBarInfo(out);
356
- backend.style = 'dark';
357
- getStatusBarInfo(out);
85
+ it('fills and returns the caller out object', () => {
86
+ const out = statusbar.createStatusBarInfo();
87
+ expect(statusbar.getStatusBarInfo(fakeHost('dark').host, out)).toBe(out);
358
88
  expect(out.style).toBe('dark');
359
89
  });
360
90
  });
361
91
 
362
92
  describe('hasStatusBarStyleEntry', () => {
363
- it('is true while the entry is on the stack and false once popped', () => {
364
- const backend = fakeBackend();
365
- setStatusBarBackend(backend);
366
- const handle = pushStatusBarStyleEntry({ style: 'dark' });
367
- expect(hasStatusBarStyleEntry(handle)).toBe(true);
368
- popStatusBarStyleEntry(handle);
369
- expect(hasStatusBarStyleEntry(handle)).toBe(false);
370
- });
371
-
372
- it('is false for an unknown or invalid handle', () => {
373
- expect(hasStatusBarStyleEntry(-1)).toBe(false);
374
- expect(hasStatusBarStyleEntry(99999)).toBe(false);
375
- });
376
-
377
- it('stays true for an entry below one that was popped', () => {
378
- const backend = fakeBackend();
379
- setStatusBarBackend(backend);
380
- const bottom = pushStatusBarStyleEntry({ style: 'dark' });
381
- const top = pushStatusBarStyleEntry({ style: 'light' });
382
- popStatusBarStyleEntry(top);
383
- expect(hasStatusBarStyleEntry(bottom)).toBe(true);
384
- });
385
- });
386
-
387
- describe('installStatusBarHostBackend', () => {
388
- afterEach(() => resetStatusBarBackendForTest());
389
-
390
- it('installs a host backend that getStatusBarBackend returns', () => {
391
- const backend = fakeBackend();
392
- installStatusBarHostBackend(backend);
393
- expect(getStatusBarBackend()).toBe(backend);
394
- });
395
-
396
- it('is first-host-wins: a second different backend sets conflict', () => {
397
- const first = fakeBackend();
398
- const second = fakeBackend();
399
- installStatusBarHostBackend(first);
400
- installStatusBarHostBackend(second);
401
- expect(getStatusBarBackend()).toBe(first);
402
- expect(explainStatusBarBackend().conflict).toBe(true);
403
- });
404
- });
405
-
406
- describe('observeStatusBarHostResult', () => {
407
- afterEach(() => resetStatusBarBackendForTest());
408
-
409
- it('records a successful observation', () => {
410
- installStatusBarHostBackend(fakeBackend());
411
- observeStatusBarHostResult('setBackgroundColor', true);
412
- const explanation = explainStatusBarBackend();
413
- expect(explanation.operation).toBe('setBackgroundColor');
414
- expect(explanation.viability).toBe('available');
415
- });
416
-
417
- it('records a failed observation', () => {
418
- installStatusBarHostBackend(fakeBackend());
419
- observeStatusBarHostResult('setBackgroundColor', false);
420
- expect(explainStatusBarBackend().viability).toBe('runtime-api-unavailable');
93
+ it('tracks handles within one explicit host', () => {
94
+ const provider = fakeHost('light');
95
+ const handle = statusbar.pushStatusBarStyleEntry(provider.host, { style: 'dark' });
96
+ expect(statusbar.hasStatusBarStyleEntry(provider.host, handle)).toBe(true);
97
+ statusbar.popStatusBarStyleEntry(provider.host, handle);
98
+ expect(statusbar.hasStatusBarStyleEntry(provider.host, handle)).toBe(false);
421
99
  });
422
100
  });
423
101
 
424
102
  describe('packedRgbaToHexColor', () => {
425
- it('converts a packed RGBA to a hex color string', () => {
426
- expect(packedRgbaToHexColor(0xff0000ff)).toBe('#ff0000');
427
- });
428
-
429
- it('converts black', () => {
430
- expect(packedRgbaToHexColor(0x000000ff)).toBe('#000000');
431
- });
432
-
433
- it('converts white', () => {
434
- expect(packedRgbaToHexColor(0xffffffff)).toBe('#ffffff');
435
- });
103
+ it('drops alpha and pads hex digits', () => expect(statusbar.packedRgbaToHexColor(0x01020304)).toBe('#010203'));
436
104
  });
437
105
 
438
106
  describe('popStatusBarStyleEntry', () => {
439
- it('no-ops for unknown or invalid handles', () => {
440
- expect(() => popStatusBarStyleEntry(-1)).not.toThrow();
441
- expect(() => popStatusBarStyleEntry(99999)).not.toThrow();
442
- });
443
-
444
- it('restores the pre-push value rather than leaving the popped entry applied', () => {
445
- // The regression this replaces: the old assertion stopped at "the pop does not throw", and the
446
- // implementation matched it — popping re-merged the stack, found no entry setting `style`, and
447
- // called no setter, so the OS kept the popped entry's value forever. A style stack whose whole
448
- // purpose is "restore the previous state on unmount" did not restore.
449
- const backend = fakeBackend();
450
- backend.style = 'default';
451
- setStatusBarBackend(backend);
452
- const handle = pushStatusBarStyleEntry({ style: 'dark' });
453
- expect(backend.style).toBe('dark');
454
- popStatusBarStyleEntry(handle);
455
- expect(backend.style).toBe('default');
456
- });
457
-
458
- it('restores every field the popped entry had set', () => {
459
- const backend = fakeBackend();
460
- backend.style = 'light';
461
- backend.visible = true;
462
- backend.color = 0x112233ff;
463
- backend.overlay = false;
464
- setStatusBarBackend(backend);
465
- const handle = pushStatusBarStyleEntry({
466
- color: 0xaabbccff,
467
- overlaysContent: true,
468
- style: 'dark',
469
- visible: false,
470
- });
471
- popStatusBarStyleEntry(handle);
472
- expect(backend.style).toBe('light');
473
- expect(backend.visible).toBe(true);
474
- expect(backend.color).toBe(0x112233ff);
475
- expect(backend.overlay).toBe(false);
476
- });
477
-
478
- it('falls back to the entry below before the baseline', () => {
479
- const backend = fakeBackend();
480
- backend.style = 'default';
481
- setStatusBarBackend(backend);
482
- pushStatusBarStyleEntry({ style: 'dark' });
483
- const top = pushStatusBarStyleEntry({ style: 'light' });
484
- expect(backend.style).toBe('light');
485
- popStatusBarStyleEntry(top);
486
- expect(backend.style).toBe('dark');
487
- });
488
-
489
- it('restores after popping out of order', () => {
490
- const backend = fakeBackend();
491
- backend.style = 'default';
492
- setStatusBarBackend(backend);
493
- const bottom = pushStatusBarStyleEntry({ style: 'dark' });
494
- const top = pushStatusBarStyleEntry({ visible: false });
495
- popStatusBarStyleEntry(bottom);
496
- expect(backend.style).toBe('default');
497
- expect(backend.visible).toBe(false);
498
- popStatusBarStyleEntry(top);
499
- expect(backend.visible).toBe(true);
500
- });
501
-
502
- it('captures a fresh baseline after the stack empties and is pushed again', () => {
503
- const backend = fakeBackend();
504
- backend.style = 'default';
505
- setStatusBarBackend(backend);
506
- popStatusBarStyleEntry(pushStatusBarStyleEntry({ style: 'dark' }));
507
- // A change made outside the stack becomes the new baseline for the next push.
508
- setStatusBarStyle('light');
509
- popStatusBarStyleEntry(pushStatusBarStyleEntry({ style: 'dark' }));
510
- expect(backend.style).toBe('light');
107
+ it('restores the captured baseline', () => {
108
+ const provider = fakeHost('light');
109
+ const handle = statusbar.pushStatusBarStyleEntry(provider.host, { style: 'dark' });
110
+ statusbar.popStatusBarStyleEntry(provider.host, handle);
111
+ expect(provider.setStyle).toHaveBeenLastCalledWith('light');
511
112
  });
512
113
  });
513
114
 
514
115
  describe('pushStatusBarStyleEntry', () => {
515
- it('applies the entry to the active backend', () => {
516
- const backend = fakeBackend();
517
- setStatusBarBackend(backend);
518
- pushStatusBarStyleEntry({ style: 'light', visible: false });
519
- expect(backend.style).toBe('light');
520
- expect(backend.visible).toBe(false);
521
- });
522
-
523
- it('later entries win per-field over earlier entries', () => {
524
- const backend = fakeBackend();
525
- setStatusBarBackend(backend);
526
- pushStatusBarStyleEntry({ style: 'dark' });
527
- pushStatusBarStyleEntry({ style: 'light' });
528
- expect(backend.style).toBe('light');
529
- });
530
-
531
- it('returns unique handles', () => {
532
- const backend = fakeBackend();
533
- setStatusBarBackend(backend);
534
- const h1 = pushStatusBarStyleEntry({ style: 'dark' });
535
- const h2 = pushStatusBarStyleEntry({ style: 'light' });
536
- expect(h1).not.toBe(h2);
537
- popStatusBarStyleEntry(h1);
538
- popStatusBarStyleEntry(h2);
539
- });
540
-
541
- it('fields not set fall through to lower entries', () => {
542
- const backend = fakeBackend();
543
- setStatusBarBackend(backend);
544
- pushStatusBarStyleEntry({ style: 'dark' });
545
- pushStatusBarStyleEntry({ visible: false }); // no style set → falls through
546
- expect(backend.style).toBe('dark');
547
- expect(backend.visible).toBe(false);
548
- });
549
- });
550
-
551
- describe('resetStatusBarBackendForTest', () => {
552
- it('clears all backend slots', () => {
553
- const backend = fakeBackend();
554
- setStatusBarBackend(backend);
555
- installStatusBarHostBackend(fakeBackend());
556
- observeStatusBarHostResult('setBackgroundColor', true);
557
- resetStatusBarBackendForTest();
558
- expect(getStatusBarBackend()).not.toBe(backend);
559
- expect(explainStatusBarBackend().layer).toBe('host-not-enabled');
560
- expect(explainStatusBarBackend().conflict).toBe(false);
561
- expect(explainStatusBarBackend().viability).toBe('unobserved');
562
- });
563
- });
564
-
565
- describe('setStatusBarBackend', () => {
566
- it('clears back to the web fallback when passed null', () => {
567
- setStatusBarBackend(fakeBackend());
568
- setStatusBarBackend(null);
569
- expect(getStatusBarBackend()).not.toBeNull();
116
+ it('merges unset fields through the stack', () => {
117
+ const provider = fakeHost('light');
118
+ statusbar.pushStatusBarStyleEntry(provider.host, { style: 'dark' });
119
+ statusbar.pushStatusBarStyleEntry(provider.host, { visible: false });
120
+ expect(provider.setStyle).toHaveBeenCalledTimes(1);
121
+ expect(provider.setVisible).toHaveBeenLastCalledWith(false, 'none');
570
122
  });
571
123
  });
572
124
 
573
125
  describe('setStatusBarColor', () => {
574
- it('forwards the packed color to the active backend', () => {
575
- const backend = fakeBackend();
576
- setStatusBarBackend(backend);
577
- setStatusBarColor(0x123456ff);
578
- expect(backend.color).toBe(0x123456ff);
579
- expect(backend.animatedColor).toBeUndefined();
580
- });
581
-
582
- it('forwards the animated flag when provided', () => {
583
- const backend = fakeBackend();
584
- setStatusBarBackend(backend);
585
- setStatusBarColor(0x123456ff, true);
586
- expect(backend.animatedColor).toBe(true);
126
+ it('uses only the color slot', () => {
127
+ const provider = fakeHost('light');
128
+ statusbar.setStatusBarColor(provider.host, 0x112233ff, true);
129
+ expect(provider.setBackgroundColor).toHaveBeenCalledWith(0x112233ff, true);
587
130
  });
588
131
  });
589
132
 
590
133
  describe('setStatusBarOverlaysContent', () => {
591
- it('forwards overlay to the active backend', () => {
592
- const backend = fakeBackend();
593
- setStatusBarBackend(backend);
594
- setStatusBarOverlaysContent(true);
595
- expect(backend.overlay).toBe(true);
134
+ it('uses only the overlays slot', () => {
135
+ const provider = fakeHost('light');
136
+ statusbar.setStatusBarOverlaysContent(provider.host, true);
137
+ expect(provider.setOverlaysContent).toHaveBeenCalledWith(true);
596
138
  });
597
139
  });
598
140
 
599
141
  describe('setStatusBarStyle', () => {
600
- it('forwards style to the active backend', () => {
601
- const backend = fakeBackend();
602
- setStatusBarBackend(backend);
603
- setStatusBarStyle('dark');
604
- expect(backend.style).toBe('dark');
142
+ it('uses only the style slot', () => {
143
+ const provider = fakeHost('light');
144
+ statusbar.setStatusBarStyle(provider.host, 'dark');
145
+ expect(provider.setStyle).toHaveBeenCalledWith('dark');
605
146
  });
606
147
  });
607
148
 
608
149
  describe('setStatusBarVisible', () => {
609
- it('forwards visibility to the active backend', () => {
610
- const backend = fakeBackend();
611
- setStatusBarBackend(backend);
612
- setStatusBarVisible(false);
613
- expect(backend.visible).toBe(false);
614
- });
615
-
616
- it('forwards animation parameter to the active backend', () => {
617
- const backend = fakeBackend();
618
- setStatusBarBackend(backend);
619
- setStatusBarVisible(false, 'fade');
620
- expect(backend.animation).toBe('fade');
150
+ it('uses only the visibility slot', () => {
151
+ const provider = fakeHost('light');
152
+ statusbar.setStatusBarVisible(provider.host, false, 'fade');
153
+ expect(provider.setVisible).toHaveBeenCalledWith(false, 'fade');
621
154
  });
622
155
  });
156
+
157
+ function fakeHost(initialStyle: StatusBarStyle) {
158
+ let listener: (() => void) | null = null;
159
+ const unsubscribe = vi.fn(() => {
160
+ listener = null;
161
+ });
162
+ const setBackgroundColor = vi.fn();
163
+ const setOverlaysContent = vi.fn();
164
+ const setStyle = vi.fn();
165
+ const setVisible = vi.fn();
166
+ const host: HasUiStatusBarChange & HasUiStatusBarStyleStack = {
167
+ ui: {
168
+ statusBarChange: {
169
+ subscribe(next) {
170
+ listener = next;
171
+ return unsubscribe;
172
+ },
173
+ },
174
+ statusBarColor: { setBackgroundColor },
175
+ statusBarInfo: {
176
+ getInfo(out) {
177
+ Object.assign(out, {
178
+ color: 0,
179
+ height: 20,
180
+ overlaysContent: false,
181
+ style: initialStyle,
182
+ visible: true,
183
+ });
184
+ return out;
185
+ },
186
+ },
187
+ statusBarOverlays: { setOverlaysContent },
188
+ statusBarStyle: { setStyle },
189
+ statusBarVisibility: { setVisible },
190
+ },
191
+ };
192
+ return {
193
+ emit: () => listener?.(),
194
+ host,
195
+ setBackgroundColor,
196
+ setOverlaysContent,
197
+ setStyle,
198
+ setVisible,
199
+ unsubscribe,
200
+ };
201
+ }