@flighthq/statusbar 0.3.0-next.906.07cea63 → 0.3.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/statusbar.d.ts +2 -0
- package/dist/statusbar.d.ts.map +1 -1
- package/dist/statusbar.js +92 -15
- package/dist/statusbar.js.map +1 -1
- package/package.json +5 -3
- package/src/statusbar.test.ts +186 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { attachStatusBar, createStatusBar, detachStatusBar, disposeStatusBar, getStatusBarHeight, getStatusBarInfo, 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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,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, createStatusBar, detachStatusBar, disposeStatusBar, getStatusBarHeight, getStatusBarInfo, 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,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,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"}
|
package/dist/statusbar.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StatusBar, StatusBarAnimation, StatusBarBackend, StatusBarInfo, StatusBarStyle, StatusBarStyleEntry, StatusBarStyleEntryHandle } from '@flighthq/types/contract';
|
|
2
2
|
export declare function attachStatusBar(bar: StatusBar): void;
|
|
3
|
+
export declare function clearStatusBarStyleStack(): void;
|
|
3
4
|
export declare function createStatusBar(): StatusBar;
|
|
4
5
|
export declare function createStatusBarInfo(): StatusBarInfo;
|
|
5
6
|
export declare function createWebStatusBarBackend(): StatusBarBackend;
|
|
@@ -8,6 +9,7 @@ export declare function disposeStatusBar(bar: StatusBar): void;
|
|
|
8
9
|
export declare function getStatusBarBackend(): StatusBarBackend;
|
|
9
10
|
export declare function getStatusBarHeight(): number;
|
|
10
11
|
export declare function getStatusBarInfo(out: StatusBarInfo): StatusBarInfo;
|
|
12
|
+
export declare function hasStatusBarStyleEntry(handle: StatusBarStyleEntryHandle): boolean;
|
|
11
13
|
export declare function popStatusBarStyleEntry(handle: StatusBarStyleEntryHandle): void;
|
|
12
14
|
export declare function pushStatusBarStyleEntry(entry: Readonly<StatusBarStyleEntry>): StatusBarStyleEntryHandle;
|
|
13
15
|
export declare function setStatusBarBackend(backend: StatusBarBackend | null): void;
|
package/dist/statusbar.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusbar.d.ts","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"statusbar.d.ts","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,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;AAMD,wBAAgB,yBAAyB,IAAI,gBAAgB,CAsC5D;AAGD,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAMpD;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAErD;AAGD,wBAAgB,mBAAmB,IAAI,gBAAgB,CAGtD;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;AAMD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAM9E;AAUD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GAAG,yBAAyB,CAWvG;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"}
|
package/dist/statusbar.js
CHANGED
|
@@ -2,15 +2,31 @@ import { createSignal, emitSignal } from '@flighthq/signals/contract';
|
|
|
2
2
|
// Begins delivering OS-driven status bar changes to `bar`'s signals by subscribing to the active
|
|
3
3
|
// backend. Idempotent: a prior subscription is torn down first. Pair with detachStatusBar /
|
|
4
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`.
|
|
5
13
|
export function attachStatusBar(bar) {
|
|
6
14
|
detachStatusBar(bar);
|
|
7
15
|
const backend = getStatusBarBackend();
|
|
8
16
|
const unsubscribe = backend.subscribe(() => {
|
|
9
|
-
|
|
10
|
-
emitSignal(bar.onChange, info);
|
|
17
|
+
emitSignal(bar.onChange, backend.getInfo(createStatusBarInfo()));
|
|
11
18
|
});
|
|
12
19
|
_subscriptions.set(bar, unsubscribe);
|
|
13
20
|
}
|
|
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)
|
|
26
|
+
return;
|
|
27
|
+
_styleStack.length = 0;
|
|
28
|
+
_applyTopStyleEntry();
|
|
29
|
+
}
|
|
14
30
|
// Allocates a StatusBar event entity with inert signals; call attachStatusBar to start delivery.
|
|
15
31
|
export function createStatusBar() {
|
|
16
32
|
return {
|
|
@@ -105,8 +121,18 @@ export function getStatusBarHeight() {
|
|
|
105
121
|
export function getStatusBarInfo(out) {
|
|
106
122
|
return getStatusBarBackend().getInfo(out);
|
|
107
123
|
}
|
|
124
|
+
// True when `handle` names an entry still on the style stack. False for a popped handle, an unknown
|
|
125
|
+
// one, or the invalid handle — so a component can check before popping without tracking its own
|
|
126
|
+
// mounted flag.
|
|
127
|
+
export function hasStatusBarStyleEntry(handle) {
|
|
128
|
+
if (handle === INVALID_HANDLE)
|
|
129
|
+
return false;
|
|
130
|
+
return _styleStack.some((e) => e.handle === handle);
|
|
131
|
+
}
|
|
108
132
|
// Removes the style stack entry identified by `handle`. If the handle is unknown or invalid,
|
|
109
|
-
// this is a no-op. The
|
|
133
|
+
// this is a no-op. The remaining stack — falling back to the baseline captured before the first
|
|
134
|
+
// push — is re-applied after removal, so a field the popped entry was the last to set returns to
|
|
135
|
+
// what it was rather than staying where that entry left it.
|
|
110
136
|
export function popStatusBarStyleEntry(handle) {
|
|
111
137
|
if (handle === INVALID_HANDLE)
|
|
112
138
|
return;
|
|
@@ -118,8 +144,19 @@ export function popStatusBarStyleEntry(handle) {
|
|
|
118
144
|
}
|
|
119
145
|
// Pushes a style stack entry, returns an opaque handle for later pop. Nested components can push
|
|
120
146
|
// entries and restore the previous state on unmount without global last-write-wins clashes.
|
|
121
|
-
// Unset fields fall through to the next entry down the stack (last pushed wins per field)
|
|
147
|
+
// Unset fields fall through to the next entry down the stack (last pushed wins per field), and
|
|
148
|
+
// through an empty stack to the baseline.
|
|
149
|
+
//
|
|
150
|
+
// The baseline is read from the backend when the stack goes from empty to non-empty — the last
|
|
151
|
+
// moment it still describes the pre-stack status bar. Capturing it at module load would be wrong
|
|
152
|
+
// (no backend is installed yet) and re-reading it per push would capture the stack's own effect.
|
|
122
153
|
export function pushStatusBarStyleEntry(entry) {
|
|
154
|
+
if (_styleStack.length === 0) {
|
|
155
|
+
_baseline = getStatusBarBackend().getInfo(createStatusBarInfo());
|
|
156
|
+
// `_applied` starts as a copy of the baseline, not null, so the first push only calls setters for
|
|
157
|
+
// fields it actually changes rather than restating the baseline back to the backend.
|
|
158
|
+
_applied = getStatusBarBackend().getInfo(createStatusBarInfo());
|
|
159
|
+
}
|
|
123
160
|
const handle = _nextHandle++;
|
|
124
161
|
_styleStack.push({ handle, entry });
|
|
125
162
|
_applyTopStyleEntry();
|
|
@@ -147,15 +184,26 @@ export function setStatusBarStyle(style) {
|
|
|
147
184
|
export function setStatusBarVisible(visible, animation) {
|
|
148
185
|
getStatusBarBackend().setVisible(visible, animation);
|
|
149
186
|
}
|
|
150
|
-
// ---- module-level state ----
|
|
151
187
|
let _backend = null;
|
|
152
188
|
let _nextHandle = 1;
|
|
153
189
|
const _scratchInfo = createStatusBarInfo();
|
|
154
190
|
const _styleStack = [];
|
|
155
191
|
const _subscriptions = new WeakMap();
|
|
192
|
+
// The status bar as it stood before the stack's first entry, and the state the stack has actually
|
|
193
|
+
// pushed to the backend. Together they make pop a restore rather than a no-op: `_baseline` supplies
|
|
194
|
+
// the value a released field returns to, and `_applied` keeps the backend from being told things it
|
|
195
|
+
// already knows. Both are cleared when the stack empties, so the next push re-reads a baseline that
|
|
196
|
+
// is once again the pre-stack truth.
|
|
197
|
+
let _baseline = null;
|
|
198
|
+
let _applied = null;
|
|
156
199
|
const INVALID_HANDLE = -1;
|
|
157
|
-
// Merges the style stack top-down (last pushed = highest priority per field)
|
|
158
|
-
//
|
|
200
|
+
// Merges the style stack top-down (last pushed = highest priority per field), falls unset fields
|
|
201
|
+
// through to the captured baseline, and pushes to the backend only what actually changed.
|
|
202
|
+
//
|
|
203
|
+
// The baseline fallback is what makes a pop restore: merging the stack alone leaves a released field
|
|
204
|
+
// undefined, and an undefined field used to mean "call no setter", which left the OS holding the
|
|
205
|
+
// value of an entry that is no longer on the stack. Diffing against `_applied` keeps that fix from
|
|
206
|
+
// costing a burst of redundant setter calls on every push and pop.
|
|
159
207
|
function _applyTopStyleEntry() {
|
|
160
208
|
const backend = getStatusBarBackend();
|
|
161
209
|
let style;
|
|
@@ -177,14 +225,44 @@ function _applyTopStyleEntry() {
|
|
|
177
225
|
if (animation === undefined && e.animation !== undefined)
|
|
178
226
|
animation = e.animation;
|
|
179
227
|
}
|
|
180
|
-
|
|
228
|
+
const baseline = _baseline;
|
|
229
|
+
if (baseline !== null) {
|
|
230
|
+
if (style === undefined)
|
|
231
|
+
style = baseline.style;
|
|
232
|
+
if (visible === undefined)
|
|
233
|
+
visible = baseline.visible;
|
|
234
|
+
if (color === undefined)
|
|
235
|
+
color = baseline.color;
|
|
236
|
+
if (overlaysContent === undefined)
|
|
237
|
+
overlaysContent = baseline.overlaysContent;
|
|
238
|
+
}
|
|
239
|
+
const applied = _applied;
|
|
240
|
+
if (style !== undefined && style !== applied?.style)
|
|
181
241
|
backend.setStyle(style);
|
|
182
|
-
if (visible !== undefined)
|
|
242
|
+
if (visible !== undefined && visible !== applied?.visible)
|
|
183
243
|
backend.setVisible(visible, animation ?? 'none');
|
|
184
|
-
if (color !== undefined)
|
|
244
|
+
if (color !== undefined && color !== applied?.color)
|
|
185
245
|
backend.setBackgroundColor(color, false);
|
|
186
|
-
if (overlaysContent !== undefined)
|
|
246
|
+
if (overlaysContent !== undefined && overlaysContent !== applied?.overlaysContent) {
|
|
187
247
|
backend.setOverlaysContent(overlaysContent);
|
|
248
|
+
}
|
|
249
|
+
if (_styleStack.length === 0) {
|
|
250
|
+
// The stack is spent: the backend is back at the baseline, so forget both and let the next push
|
|
251
|
+
// capture a fresh one.
|
|
252
|
+
_baseline = null;
|
|
253
|
+
_applied = null;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const next = _applied ?? createStatusBarInfo();
|
|
257
|
+
if (style !== undefined)
|
|
258
|
+
next.style = style;
|
|
259
|
+
if (visible !== undefined)
|
|
260
|
+
next.visible = visible;
|
|
261
|
+
if (color !== undefined)
|
|
262
|
+
next.color = color;
|
|
263
|
+
if (overlaysContent !== undefined)
|
|
264
|
+
next.overlaysContent = overlaysContent;
|
|
265
|
+
_applied = next;
|
|
188
266
|
}
|
|
189
267
|
function packedRgbaToHexColor(color) {
|
|
190
268
|
const rgb = (color >>> 8) & 0xffffff;
|
|
@@ -202,11 +280,10 @@ function _webReadThemeColor() {
|
|
|
202
280
|
if (content === null || !content.startsWith('#'))
|
|
203
281
|
return 0;
|
|
204
282
|
const hex = content.slice(1);
|
|
205
|
-
if (
|
|
206
|
-
return 0;
|
|
207
|
-
const rgb = parseInt(hex, 16);
|
|
208
|
-
if (isNaN(rgb))
|
|
283
|
+
if (!/^(?:[\da-f]{3}|[\da-f]{6})$/i.test(hex))
|
|
209
284
|
return 0;
|
|
285
|
+
const expandedHex = hex.length === 3 ? [...hex].map((digit) => digit + digit).join('') : hex;
|
|
286
|
+
const rgb = parseInt(expandedHex, 16);
|
|
210
287
|
// Reconstruct as 0xRRGGBBFF (alpha fully opaque since web drops alpha).
|
|
211
288
|
return ((rgb << 8) | 0xff) >>> 0;
|
|
212
289
|
}
|
package/dist/statusbar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusbar.js","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAWtE,iGAAiG;AACjG,4FAA4F;AAC5F,oBAAoB;AACpB,MAAM,UAAU,eAAe,CAAC,GAAc;IAC5C,eAAe,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE;QACzC,
|
|
1
|
+
{"version":3,"file":"statusbar.js","sourceRoot":"","sources":["../src/statusbar.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/statusbar",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": "Joshua Granick and other contributors",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,8 +38,8 @@
|
|
|
36
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@flighthq/signals": "0.3.0
|
|
40
|
-
"@flighthq/types": "0.3.0
|
|
41
|
+
"@flighthq/signals": "0.3.0",
|
|
42
|
+
"@flighthq/types": "0.3.0"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"typescript": "^5.3.0"
|
package/src/statusbar.test.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
1
2
|
import type { StatusBar, StatusBarBackend, StatusBarInfo, StatusBarStyle } from '@flighthq/types/contract';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
attachStatusBar,
|
|
6
|
+
clearStatusBarStyleStack,
|
|
5
7
|
createStatusBar,
|
|
6
8
|
createStatusBarInfo,
|
|
7
9
|
createWebStatusBarBackend,
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
getStatusBarBackend,
|
|
11
13
|
getStatusBarHeight,
|
|
12
14
|
getStatusBarInfo,
|
|
15
|
+
hasStatusBarStyleEntry,
|
|
13
16
|
popStatusBarStyleEntry,
|
|
14
17
|
pushStatusBarStyleEntry,
|
|
15
18
|
setStatusBarBackend,
|
|
@@ -77,9 +80,10 @@ function fakeBackend(): StatusBarBackend & {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
afterEach(() => {
|
|
83
|
+
// Empty the stack before dropping the backend, so the restore lands on the fake rather than on the
|
|
84
|
+
// web default that setStatusBarBackend(null) falls back to.
|
|
85
|
+
clearStatusBarStyleStack();
|
|
80
86
|
setStatusBarBackend(null);
|
|
81
|
-
// Reset style stack state between tests by popping all entries (pop returns no-op on invalid)
|
|
82
|
-
for (let i = 0; i < 100; i++) popStatusBarStyleEntry(i);
|
|
83
87
|
});
|
|
84
88
|
|
|
85
89
|
describe('attachStatusBar', () => {
|
|
@@ -107,6 +111,82 @@ describe('attachStatusBar', () => {
|
|
|
107
111
|
expect(backend.subscribeCallCount).toBe(2);
|
|
108
112
|
disposeStatusBar(bar);
|
|
109
113
|
});
|
|
114
|
+
|
|
115
|
+
it('hands each event a payload the listener owns, not a shared scratch', () => {
|
|
116
|
+
// The regression this pins: the emitted info used to be a single module-level scratch, so a
|
|
117
|
+
// listener that retained a snapshot watched it change under them on the next event — or on an
|
|
118
|
+
// unrelated getStatusBarHeight() call — and two attached bars received the same object.
|
|
119
|
+
const backend = fakeBackend();
|
|
120
|
+
setStatusBarBackend(backend);
|
|
121
|
+
const bar = createStatusBar();
|
|
122
|
+
const seen: StatusBarInfo[] = [];
|
|
123
|
+
connectSignal(bar.onChange, (info) => seen.push(info));
|
|
124
|
+
attachStatusBar(bar);
|
|
125
|
+
|
|
126
|
+
backend.infoHeight = 44;
|
|
127
|
+
backend._emit();
|
|
128
|
+
backend.infoHeight = 99;
|
|
129
|
+
backend._emit();
|
|
130
|
+
expect(seen.length).toBe(2);
|
|
131
|
+
expect(seen[0]).not.toBe(seen[1]);
|
|
132
|
+
expect(seen[0].height).toBe(44);
|
|
133
|
+
expect(seen[1].height).toBe(99);
|
|
134
|
+
|
|
135
|
+
// An unrelated read must not reach back into a payload already delivered.
|
|
136
|
+
backend.infoHeight = 12;
|
|
137
|
+
getStatusBarHeight();
|
|
138
|
+
expect(seen[0].height).toBe(44);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('gives two attached bars separate payload objects', () => {
|
|
142
|
+
const backend = fakeBackend();
|
|
143
|
+
setStatusBarBackend(backend);
|
|
144
|
+
const first = createStatusBar();
|
|
145
|
+
const second = createStatusBar();
|
|
146
|
+
const seen: StatusBarInfo[] = [];
|
|
147
|
+
connectSignal(first.onChange, (info) => seen.push(info));
|
|
148
|
+
connectSignal(second.onChange, (info) => seen.push(info));
|
|
149
|
+
attachStatusBar(first);
|
|
150
|
+
attachStatusBar(second);
|
|
151
|
+
// The fake keeps one listener slot, so drive each bar's subscription in turn.
|
|
152
|
+
backend._emit();
|
|
153
|
+
attachStatusBar(first);
|
|
154
|
+
backend._emit();
|
|
155
|
+
expect(seen.length).toBe(2);
|
|
156
|
+
expect(seen[0]).not.toBe(seen[1]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('clearStatusBarStyleStack', () => {
|
|
161
|
+
it('empties the stack and restores the baseline in one call', () => {
|
|
162
|
+
const backend = fakeBackend();
|
|
163
|
+
backend.style = 'default';
|
|
164
|
+
backend.visible = true;
|
|
165
|
+
setStatusBarBackend(backend);
|
|
166
|
+
pushStatusBarStyleEntry({ style: 'dark' });
|
|
167
|
+
pushStatusBarStyleEntry({ visible: false });
|
|
168
|
+
clearStatusBarStyleStack();
|
|
169
|
+
expect(backend.style).toBe('default');
|
|
170
|
+
expect(backend.visible).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('drops every handle', () => {
|
|
174
|
+
const backend = fakeBackend();
|
|
175
|
+
setStatusBarBackend(backend);
|
|
176
|
+
const first = pushStatusBarStyleEntry({ style: 'dark' });
|
|
177
|
+
const second = pushStatusBarStyleEntry({ style: 'light' });
|
|
178
|
+
clearStatusBarStyleStack();
|
|
179
|
+
expect(hasStatusBarStyleEntry(first)).toBe(false);
|
|
180
|
+
expect(hasStatusBarStyleEntry(second)).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('is a no-op on an empty stack', () => {
|
|
184
|
+
const backend = fakeBackend();
|
|
185
|
+
setStatusBarBackend(backend);
|
|
186
|
+
clearStatusBarStyleStack();
|
|
187
|
+
expect(() => clearStatusBarStyleStack()).not.toThrow();
|
|
188
|
+
expect(backend.style).toBe('default');
|
|
189
|
+
});
|
|
110
190
|
});
|
|
111
191
|
|
|
112
192
|
describe('createStatusBar', () => {
|
|
@@ -170,6 +250,23 @@ describe('createWebStatusBarBackend', () => {
|
|
|
170
250
|
expect(info.color).toBe(0xff0000ff);
|
|
171
251
|
});
|
|
172
252
|
|
|
253
|
+
it('getInfo expands a short #rgb theme-color and rejects malformed hex', () => {
|
|
254
|
+
document.head.querySelectorAll('meta[name="theme-color"]').forEach((m) => m.remove());
|
|
255
|
+
const meta = document.createElement('meta');
|
|
256
|
+
meta.setAttribute('name', 'theme-color');
|
|
257
|
+
document.head.appendChild(meta);
|
|
258
|
+
const backend = createWebStatusBarBackend();
|
|
259
|
+
const info = createStatusBarInfo();
|
|
260
|
+
|
|
261
|
+
meta.setAttribute('content', '#aBc');
|
|
262
|
+
backend.getInfo(info);
|
|
263
|
+
expect(info.color).toBe(0xaabbccff);
|
|
264
|
+
|
|
265
|
+
meta.setAttribute('content', '#12xz56');
|
|
266
|
+
backend.getInfo(info);
|
|
267
|
+
expect(info.color).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
173
270
|
it('subscribe returns a no-op unsubscribe function', () => {
|
|
174
271
|
const backend = createWebStatusBarBackend();
|
|
175
272
|
const unsub = backend.subscribe(() => {});
|
|
@@ -265,20 +362,104 @@ describe('getStatusBarInfo', () => {
|
|
|
265
362
|
});
|
|
266
363
|
});
|
|
267
364
|
|
|
365
|
+
describe('hasStatusBarStyleEntry', () => {
|
|
366
|
+
it('is true while the entry is on the stack and false once popped', () => {
|
|
367
|
+
const backend = fakeBackend();
|
|
368
|
+
setStatusBarBackend(backend);
|
|
369
|
+
const handle = pushStatusBarStyleEntry({ style: 'dark' });
|
|
370
|
+
expect(hasStatusBarStyleEntry(handle)).toBe(true);
|
|
371
|
+
popStatusBarStyleEntry(handle);
|
|
372
|
+
expect(hasStatusBarStyleEntry(handle)).toBe(false);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('is false for an unknown or invalid handle', () => {
|
|
376
|
+
expect(hasStatusBarStyleEntry(-1)).toBe(false);
|
|
377
|
+
expect(hasStatusBarStyleEntry(99999)).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('stays true for an entry below one that was popped', () => {
|
|
381
|
+
const backend = fakeBackend();
|
|
382
|
+
setStatusBarBackend(backend);
|
|
383
|
+
const bottom = pushStatusBarStyleEntry({ style: 'dark' });
|
|
384
|
+
const top = pushStatusBarStyleEntry({ style: 'light' });
|
|
385
|
+
popStatusBarStyleEntry(top);
|
|
386
|
+
expect(hasStatusBarStyleEntry(bottom)).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
268
390
|
describe('popStatusBarStyleEntry', () => {
|
|
269
391
|
it('no-ops for unknown or invalid handles', () => {
|
|
270
392
|
expect(() => popStatusBarStyleEntry(-1)).not.toThrow();
|
|
271
393
|
expect(() => popStatusBarStyleEntry(99999)).not.toThrow();
|
|
272
394
|
});
|
|
273
395
|
|
|
274
|
-
it('
|
|
396
|
+
it('restores the pre-push value rather than leaving the popped entry applied', () => {
|
|
397
|
+
// The regression this replaces: the old assertion stopped at "the pop does not throw", and the
|
|
398
|
+
// implementation matched it — popping re-merged the stack, found no entry setting `style`, and
|
|
399
|
+
// called no setter, so the OS kept the popped entry's value forever. A style stack whose whole
|
|
400
|
+
// purpose is "restore the previous state on unmount" did not restore.
|
|
275
401
|
const backend = fakeBackend();
|
|
402
|
+
backend.style = 'default';
|
|
276
403
|
setStatusBarBackend(backend);
|
|
277
404
|
const handle = pushStatusBarStyleEntry({ style: 'dark' });
|
|
278
405
|
expect(backend.style).toBe('dark');
|
|
279
406
|
popStatusBarStyleEntry(handle);
|
|
280
|
-
|
|
281
|
-
|
|
407
|
+
expect(backend.style).toBe('default');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('restores every field the popped entry had set', () => {
|
|
411
|
+
const backend = fakeBackend();
|
|
412
|
+
backend.style = 'light';
|
|
413
|
+
backend.visible = true;
|
|
414
|
+
backend.color = 0x112233ff;
|
|
415
|
+
backend.overlay = false;
|
|
416
|
+
setStatusBarBackend(backend);
|
|
417
|
+
const handle = pushStatusBarStyleEntry({
|
|
418
|
+
color: 0xaabbccff,
|
|
419
|
+
overlaysContent: true,
|
|
420
|
+
style: 'dark',
|
|
421
|
+
visible: false,
|
|
422
|
+
});
|
|
423
|
+
popStatusBarStyleEntry(handle);
|
|
424
|
+
expect(backend.style).toBe('light');
|
|
425
|
+
expect(backend.visible).toBe(true);
|
|
426
|
+
expect(backend.color).toBe(0x112233ff);
|
|
427
|
+
expect(backend.overlay).toBe(false);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('falls back to the entry below before the baseline', () => {
|
|
431
|
+
const backend = fakeBackend();
|
|
432
|
+
backend.style = 'default';
|
|
433
|
+
setStatusBarBackend(backend);
|
|
434
|
+
pushStatusBarStyleEntry({ style: 'dark' });
|
|
435
|
+
const top = pushStatusBarStyleEntry({ style: 'light' });
|
|
436
|
+
expect(backend.style).toBe('light');
|
|
437
|
+
popStatusBarStyleEntry(top);
|
|
438
|
+
expect(backend.style).toBe('dark');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('restores after popping out of order', () => {
|
|
442
|
+
const backend = fakeBackend();
|
|
443
|
+
backend.style = 'default';
|
|
444
|
+
setStatusBarBackend(backend);
|
|
445
|
+
const bottom = pushStatusBarStyleEntry({ style: 'dark' });
|
|
446
|
+
const top = pushStatusBarStyleEntry({ visible: false });
|
|
447
|
+
popStatusBarStyleEntry(bottom);
|
|
448
|
+
expect(backend.style).toBe('default');
|
|
449
|
+
expect(backend.visible).toBe(false);
|
|
450
|
+
popStatusBarStyleEntry(top);
|
|
451
|
+
expect(backend.visible).toBe(true);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('captures a fresh baseline after the stack empties and is pushed again', () => {
|
|
455
|
+
const backend = fakeBackend();
|
|
456
|
+
backend.style = 'default';
|
|
457
|
+
setStatusBarBackend(backend);
|
|
458
|
+
popStatusBarStyleEntry(pushStatusBarStyleEntry({ style: 'dark' }));
|
|
459
|
+
// A change made outside the stack becomes the new baseline for the next push.
|
|
460
|
+
setStatusBarStyle('light');
|
|
461
|
+
popStatusBarStyleEntry(pushStatusBarStyleEntry({ style: 'dark' }));
|
|
462
|
+
expect(backend.style).toBe('light');
|
|
282
463
|
});
|
|
283
464
|
});
|
|
284
465
|
|