@labpics/colors 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,65 +1,65 @@
1
- {
2
- "name": "@labpics/colors",
3
- "version": "0.1.0",
4
- "description": "Framework-agnostic contrast engine: resolve accessible, perceptually-anchored colour roles for any background. WASM core, zero runtime dependencies.",
5
- "type": "module",
6
- "license": "MIT",
7
- "publishConfig": {
8
- "access": "public",
9
- "provenance": true
10
- },
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/Labpics-Team/lab-colors",
14
- "directory": "packages/colors"
15
- },
16
- "exports": {
17
- ".": {
18
- "types": "./index.d.ts",
19
- "default": "./index.js"
20
- },
21
- "./apply-theme": {
22
- "types": "./apply-theme.d.ts",
23
- "default": "./apply-theme.js"
24
- },
25
- "./watch-theme": {
26
- "types": "./watch-theme.d.ts",
27
- "default": "./watch-theme.js"
28
- },
29
- "./adapt-theme": {
30
- "types": "./adapt-theme.d.ts",
31
- "default": "./adapt-theme.js"
32
- },
33
- "./effective-bg": {
34
- "types": "./effective-bg.d.ts",
35
- "default": "./effective-bg.js"
36
- }
37
- },
38
- "files": [
39
- "index.js",
40
- "index.d.ts",
41
- "apply-theme.js",
42
- "apply-theme.d.ts",
43
- "watch-theme.js",
44
- "watch-theme.d.ts",
45
- "adapt-theme.js",
46
- "adapt-theme.d.ts",
47
- "effective-bg.js",
48
- "effective-bg.d.ts",
49
- "pkg/labcolors.js",
50
- "pkg/labcolors.d.ts",
51
- "pkg/labcolors_bg.wasm",
52
- "pkg/labcolors_bg.wasm.d.ts"
53
- ],
54
- "scripts": {
55
- "//build": "wasm-pack resolves --out-dir relative to the CRATE dir (crates/labcolors-wasm), not the cwd, so the ../../packages path is correct and unambiguous regardless of where npm runs it.",
56
- "build": "wasm-pack build ../../crates/labcolors-wasm --release --target web --out-dir ../../packages/colors/pkg --out-name labcolors",
57
- "typecheck": "tsc --noEmit -p tsconfig.json",
58
- "test": "node --test"
59
- },
60
- "devDependencies": {
61
- "typescript": "^5.5.0"
62
- },
63
- "types": "./index.d.ts",
64
- "sideEffects": false
65
- }
1
+ {
2
+ "name": "@labpics/colors",
3
+ "version": "0.2.0",
4
+ "description": "Framework-agnostic contrast engine: resolve accessible, perceptually-anchored colour roles for any background. WASM core, zero runtime dependencies.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "provenance": false
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/Labpics-Team/lab-colors",
14
+ "directory": "packages/colors"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./index.d.ts",
19
+ "default": "./index.js"
20
+ },
21
+ "./apply-theme": {
22
+ "types": "./apply-theme.d.ts",
23
+ "default": "./apply-theme.js"
24
+ },
25
+ "./watch-theme": {
26
+ "types": "./watch-theme.d.ts",
27
+ "default": "./watch-theme.js"
28
+ },
29
+ "./adapt-theme": {
30
+ "types": "./adapt-theme.d.ts",
31
+ "default": "./adapt-theme.js"
32
+ },
33
+ "./effective-bg": {
34
+ "types": "./effective-bg.d.ts",
35
+ "default": "./effective-bg.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "index.js",
40
+ "index.d.ts",
41
+ "apply-theme.js",
42
+ "apply-theme.d.ts",
43
+ "watch-theme.js",
44
+ "watch-theme.d.ts",
45
+ "adapt-theme.js",
46
+ "adapt-theme.d.ts",
47
+ "effective-bg.js",
48
+ "effective-bg.d.ts",
49
+ "pkg/labcolors.js",
50
+ "pkg/labcolors.d.ts",
51
+ "pkg/labcolors_bg.wasm",
52
+ "pkg/labcolors_bg.wasm.d.ts"
53
+ ],
54
+ "scripts": {
55
+ "//build": "wasm-pack resolves --out-dir relative to the CRATE dir (crates/labcolors-wasm), not the cwd, so the ../../packages path is correct and unambiguous regardless of where npm runs it.",
56
+ "build": "wasm-pack build ../../crates/labcolors-wasm --release --target web --out-dir ../../packages/colors/pkg --out-name labcolors",
57
+ "typecheck": "tsc --noEmit -p tsconfig.json",
58
+ "test": "node --test"
59
+ },
60
+ "devDependencies": {
61
+ "typescript": "^5.5.0"
62
+ },
63
+ "types": "./index.d.ts",
64
+ "sideEffects": false
65
+ }
package/pkg/labcolors.js CHANGED
@@ -191,11 +191,11 @@ if (Symbol.dispose) LabColors.prototype[Symbol.dispose] = LabColors.prototype.fr
191
191
  function __wbg_get_imports() {
192
192
  const import0 = {
193
193
  __proto__: null,
194
- __wbg_Error_9dc85fe1bc224456: function(arg0, arg1) {
194
+ __wbg_Error_92b29b0548f8b746: function(arg0, arg1) {
195
195
  const ret = Error(getStringFromWasm0(arg0, arg1));
196
196
  return addHeapObject(ret);
197
197
  },
198
- __wbg___wbindgen_string_get_fa2687d531ed17a5: function(arg0, arg1) {
198
+ __wbg___wbindgen_string_get_b0ca35b86a603356: function(arg0, arg1) {
199
199
  const obj = getObject(arg1);
200
200
  const ret = typeof(obj) === 'string' ? obj : undefined;
201
201
  var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2);
@@ -203,14 +203,14 @@ function __wbg_get_imports() {
203
203
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
204
204
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
205
205
  },
206
- __wbg___wbindgen_throw_bbadd78c1bac3a77: function(arg0, arg1) {
206
+ __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) {
207
207
  throw new Error(getStringFromWasm0(arg0, arg1));
208
208
  },
209
- __wbg_new_20b778a4c5c691c3: function() {
209
+ __wbg_new_da52cf8fe3429cb2: function() {
210
210
  const ret = new Object();
211
211
  return addHeapObject(ret);
212
212
  },
213
- __wbg_set_a6ba3ac0e634b822: function() { return handleError(function (arg0, arg1, arg2) {
213
+ __wbg_set_8535240470bf2500: function() { return handleError(function (arg0, arg1, arg2) {
214
214
  const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
215
215
  return ret;
216
216
  }, arguments); },
Binary file
package/watch-theme.d.ts CHANGED
@@ -1,57 +1,57 @@
1
- // Public types for the reactive theme runtime.
2
-
3
- import type { LabColors, ResolvedTheme, ThemeName } from "./pkg/labcolors.js";
4
-
5
- export interface WatchThemeOptions {
6
- /** An initialised `LabColors` engine (after `await init()`). */
7
- colors: Pick<LabColors, "resolveTheme">;
8
- /** Theme name. */
9
- theme: ThemeName;
10
- /**
11
- * Explicit effective background, overriding the ancestor-composite. Use a hex
12
- * string or a `() => hex` you sample yourself for image/gradient/blur surfaces
13
- * that have no single readable `background-color`.
14
- */
15
- background?: string | (() => string);
16
- /** Element to write the `--lab-*` variables onto. Defaults to the watched element. */
17
- target?: HTMLElement;
18
- /** Base colour when the ancestor chain is fully translucent. Default `"#FFFFFF"`. */
19
- fallback?: string;
20
- /** Auto-refresh on DOM attribute mutations. Default `true`. */
21
- observe?: boolean;
22
- /** Mutation-observer root. Defaults to the document element. */
23
- root?: Node;
24
- /** Window-like host (for `MutationObserver`). Defaults to `globalThis`. */
25
- win?: Window;
26
- /** Injection seam for the computed style of an element (testing). */
27
- getStyle?: (element: unknown) => { getPropertyValue(property: string): string };
28
- /** Injection seam for an element's parent (testing). */
29
- parentOf?: (element: unknown) => unknown;
30
- }
31
-
32
- export interface WatchController {
33
- /**
34
- * Re-resolve and re-apply if the effective background (or theme) changed;
35
- * `force` re-applies unconditionally. Returns the now-applied result, or the
36
- * cached one when nothing changed.
37
- */
38
- refresh(force?: boolean): ResolvedTheme | null;
39
- /** Switch theme and re-apply. */
40
- setTheme(theme: ThemeName): void;
41
- /** The effective background hex last resolved. */
42
- background(): string;
43
- /** Disconnect observers and stop watching. */
44
- stop(): void;
45
- }
46
-
47
- /**
48
- * Keep an element's `--lab-*` variables in sync with its (effective) background.
49
- *
50
- * Discrete background/theme changes are caught by a `MutationObserver`;
51
- * continuous (animated) backgrounds are driven by calling `refresh()` from a
52
- * `requestAnimationFrame` loop (cheap — re-resolves only on real change).
53
- */
54
- export declare function watchTheme(
55
- element: HTMLElement,
56
- options: WatchThemeOptions,
57
- ): WatchController;
1
+ // Public types for the reactive theme runtime.
2
+
3
+ import type { LabColors, ResolvedTheme, ThemeName } from "./pkg/labcolors.js";
4
+
5
+ export interface WatchThemeOptions {
6
+ /** An initialised `LabColors` engine (after `await init()`). */
7
+ colors: Pick<LabColors, "resolveTheme">;
8
+ /** Theme name. */
9
+ theme: ThemeName;
10
+ /**
11
+ * Explicit effective background, overriding the ancestor-composite. Use a hex
12
+ * string or a `() => hex` you sample yourself for image/gradient/blur surfaces
13
+ * that have no single readable `background-color`.
14
+ */
15
+ background?: string | (() => string);
16
+ /** Element to write the `--lab-*` variables onto. Defaults to the watched element. */
17
+ target?: HTMLElement;
18
+ /** Base colour when the ancestor chain is fully translucent. Default `"#FFFFFF"`. */
19
+ fallback?: string;
20
+ /** Auto-refresh on DOM attribute mutations. Default `true`. */
21
+ observe?: boolean;
22
+ /** Mutation-observer root. Defaults to the document element. */
23
+ root?: Node;
24
+ /** Window-like host (for `MutationObserver`). Defaults to `globalThis`. */
25
+ win?: Window;
26
+ /** Injection seam for the computed style of an element (testing). */
27
+ getStyle?: (element: unknown) => { getPropertyValue(property: string): string };
28
+ /** Injection seam for an element's parent (testing). */
29
+ parentOf?: (element: unknown) => unknown;
30
+ }
31
+
32
+ export interface WatchController {
33
+ /**
34
+ * Re-resolve and re-apply if the effective background (or theme) changed;
35
+ * `force` re-applies unconditionally. Returns the now-applied result, or the
36
+ * cached one when nothing changed.
37
+ */
38
+ refresh(force?: boolean): ResolvedTheme | null;
39
+ /** Switch theme and re-apply. */
40
+ setTheme(theme: ThemeName): void;
41
+ /** The effective background hex last resolved. */
42
+ background(): string;
43
+ /** Disconnect observers and stop watching. */
44
+ stop(): void;
45
+ }
46
+
47
+ /**
48
+ * Keep an element's `--lab-*` variables in sync with its (effective) background.
49
+ *
50
+ * Discrete background/theme changes are caught by a `MutationObserver`;
51
+ * continuous (animated) backgrounds are driven by calling `refresh()` from a
52
+ * `requestAnimationFrame` loop (cheap — re-resolves only on real change).
53
+ */
54
+ export declare function watchTheme(
55
+ element: HTMLElement,
56
+ options: WatchThemeOptions,
57
+ ): WatchController;
package/watch-theme.js CHANGED
@@ -1,144 +1,144 @@
1
- // Reactive theme runtime — zero dependencies.
2
- //
3
- // `applyTheme` writes a resolved theme's `--lab-*` variables onto an element
4
- // once. `watchTheme` closes the loop the css-injection-runtime chapter left
5
- // open: it keeps an element's variables *in sync* with its background. Point it
6
- // at a surface and theming follows the background — the seamless drop-in for a
7
- // design system that re-resolves against a changing background.
8
- //
9
- // It serves both regimes:
10
- // * DISCRETE changes (theme switch, a class/style toggle, DOM reflow) are
11
- // caught automatically by a `MutationObserver` — call once, forget.
12
- // * CONTINUOUS changes (a CSS-animated or per-frame-scripted background that
13
- // never mutates inline style) are driven by the caller calling `refresh()`
14
- // inside its own `requestAnimationFrame` loop. `refresh()` is cheap: it
15
- // re-resolves only when the effective background actually changed, so a
16
- // steady background costs one string compare per frame, not a WASM solve.
17
- //
18
- // The effective background is computed by alpha-compositing ancestors
19
- // (`effective-bg.js`); for surfaces over images/gradients/blur — which have no
20
- // single readable colour — pass an explicit `background` (a hex string or a
21
- // `() => hex` you sample yourself).
22
-
23
- import { applyTheme } from "./apply-theme.js";
24
- import { effectiveBackground } from "./effective-bg.js";
25
-
26
- /**
27
- * @typedef {object} WatchController
28
- * @property {(force?: boolean) => (object | null)} refresh Re-resolve+apply if the
29
- * background (or theme) changed; `force` re-applies unconditionally. Returns the
30
- * `resolveTheme` result that is now applied, or `null` if nothing was applied.
31
- * @property {(theme: string) => void} setTheme Switch theme and re-apply.
32
- * @property {() => string} background The effective background hex last resolved.
33
- * @property {() => void} stop Disconnect observers and stop watching.
34
- */
35
-
36
- /**
37
- * Keep `element`'s `--lab-*` variables in sync with its (effective) background.
38
- *
39
- * @param {*} element The surface to read the background from and (by default)
40
- * write the variables onto.
41
- * @param {object} options
42
- * @param {{ resolveTheme: (bgHex: string, theme: string) => object }} options.colors
43
- * An initialised `LabColors` engine (already `await init()`-ed).
44
- * @param {string} options.theme Theme name (`"light" | "dark" | …`).
45
- * @param {string | (() => string)} [options.background] Explicit effective
46
- * background, overriding the ancestor-composite (use for image/gradient/blur
47
- * surfaces you sample yourself).
48
- * @param {*} [options.target=element] Element to write the variables onto.
49
- * @param {string} [options.fallback="#FFFFFF"] Base for a fully-translucent chain.
50
- * @param {boolean} [options.observe=true] Auto-refresh on DOM attribute mutations.
51
- * @param {*} [options.root] Mutation-observer root (default: the document element).
52
- * @param {*} [options.win=globalThis] Window-like host (for MutationObserver).
53
- * @param {(el:*)=>*} [options.getStyle] Injection seam for `effectiveBackground`.
54
- * @param {(el:*)=>*} [options.parentOf] Injection seam for `effectiveBackground`.
55
- * @returns {WatchController}
56
- */
57
- export function watchTheme(element, options) {
58
- if (!options || typeof options.colors?.resolveTheme !== "function") {
59
- throw new TypeError("watchTheme: options.colors must be an initialised LabColors engine");
60
- }
61
- if (typeof options.theme !== "string") {
62
- throw new TypeError("watchTheme: options.theme must be a theme name string");
63
- }
64
-
65
- const target = options.target ?? element;
66
- const fallback = options.fallback ?? "#FFFFFF";
67
- const win = options.win ?? (typeof globalThis !== "undefined" ? globalThis : undefined);
68
- let theme = options.theme;
69
- let lastBg = null;
70
- let lastTheme = null;
71
- let lastResult = null;
72
-
73
- const readBackground = () => {
74
- const b = options.background;
75
- if (typeof b === "function") return b();
76
- if (typeof b === "string") return b;
77
- return effectiveBackground(element, {
78
- fallback,
79
- getStyle: options.getStyle,
80
- parentOf: options.parentOf,
81
- });
82
- };
83
-
84
- const refresh = (force = false) => {
85
- const bg = readBackground();
86
- if (!force && bg === lastBg && theme === lastTheme) return lastResult;
87
- const result = options.colors.resolveTheme(bg, theme);
88
- applyTheme(target, result);
89
- lastBg = bg;
90
- lastTheme = theme;
91
- lastResult = result;
92
- return result;
93
- };
94
-
95
- // Coalesce a burst of mutations into a single refresh on the next microtask.
96
- let scheduled = false;
97
- let stopped = false;
98
- const schedule = () => {
99
- if (scheduled || stopped) return;
100
- scheduled = true;
101
- Promise.resolve().then(() => {
102
- scheduled = false;
103
- // A `stop()` between scheduling and this microtask must cancel the refresh
104
- // — the watcher is done, no late writes.
105
- if (!stopped) refresh();
106
- });
107
- };
108
-
109
- let observer = null;
110
- if (options.observe !== false && win && typeof win.MutationObserver === "function") {
111
- const root =
112
- options.root ??
113
- (typeof win.document !== "undefined" ? win.document.documentElement : null);
114
- if (root) {
115
- observer = new win.MutationObserver(schedule);
116
- // A background can change on the element OR any ancestor, via inline style
117
- // or a class swap — so watch attribute changes across the subtree.
118
- observer.observe(root, {
119
- subtree: true,
120
- attributes: true,
121
- attributeFilter: ["style", "class"],
122
- });
123
- }
124
- }
125
-
126
- // Apply immediately so the surface is correct on creation.
127
- refresh(true);
128
-
129
- return {
130
- refresh,
131
- setTheme(next) {
132
- theme = next;
133
- refresh();
134
- },
135
- background() {
136
- return lastBg;
137
- },
138
- stop() {
139
- stopped = true;
140
- if (observer) observer.disconnect();
141
- observer = null;
142
- },
143
- };
144
- }
1
+ // Reactive theme runtime — zero dependencies.
2
+ //
3
+ // `applyTheme` writes a resolved theme's `--lab-*` variables onto an element
4
+ // once. `watchTheme` closes the loop the css-injection-runtime chapter left
5
+ // open: it keeps an element's variables *in sync* with its background. Point it
6
+ // at a surface and theming follows the background — the seamless drop-in for a
7
+ // design system that re-resolves against a changing background.
8
+ //
9
+ // It serves both regimes:
10
+ // * DISCRETE changes (theme switch, a class/style toggle, DOM reflow) are
11
+ // caught automatically by a `MutationObserver` — call once, forget.
12
+ // * CONTINUOUS changes (a CSS-animated or per-frame-scripted background that
13
+ // never mutates inline style) are driven by the caller calling `refresh()`
14
+ // inside its own `requestAnimationFrame` loop. `refresh()` is cheap: it
15
+ // re-resolves only when the effective background actually changed, so a
16
+ // steady background costs one string compare per frame, not a WASM solve.
17
+ //
18
+ // The effective background is computed by alpha-compositing ancestors
19
+ // (`effective-bg.js`); for surfaces over images/gradients/blur — which have no
20
+ // single readable colour — pass an explicit `background` (a hex string or a
21
+ // `() => hex` you sample yourself).
22
+
23
+ import { applyTheme } from "./apply-theme.js";
24
+ import { effectiveBackground } from "./effective-bg.js";
25
+
26
+ /**
27
+ * @typedef {object} WatchController
28
+ * @property {(force?: boolean) => (object | null)} refresh Re-resolve+apply if the
29
+ * background (or theme) changed; `force` re-applies unconditionally. Returns the
30
+ * `resolveTheme` result that is now applied, or `null` if nothing was applied.
31
+ * @property {(theme: string) => void} setTheme Switch theme and re-apply.
32
+ * @property {() => string} background The effective background hex last resolved.
33
+ * @property {() => void} stop Disconnect observers and stop watching.
34
+ */
35
+
36
+ /**
37
+ * Keep `element`'s `--lab-*` variables in sync with its (effective) background.
38
+ *
39
+ * @param {*} element The surface to read the background from and (by default)
40
+ * write the variables onto.
41
+ * @param {object} options
42
+ * @param {{ resolveTheme: (bgHex: string, theme: string) => object }} options.colors
43
+ * An initialised `LabColors` engine (already `await init()`-ed).
44
+ * @param {string} options.theme Theme name (`"light" | "dark" | …`).
45
+ * @param {string | (() => string)} [options.background] Explicit effective
46
+ * background, overriding the ancestor-composite (use for image/gradient/blur
47
+ * surfaces you sample yourself).
48
+ * @param {*} [options.target=element] Element to write the variables onto.
49
+ * @param {string} [options.fallback="#FFFFFF"] Base for a fully-translucent chain.
50
+ * @param {boolean} [options.observe=true] Auto-refresh on DOM attribute mutations.
51
+ * @param {*} [options.root] Mutation-observer root (default: the document element).
52
+ * @param {*} [options.win=globalThis] Window-like host (for MutationObserver).
53
+ * @param {(el:*)=>*} [options.getStyle] Injection seam for `effectiveBackground`.
54
+ * @param {(el:*)=>*} [options.parentOf] Injection seam for `effectiveBackground`.
55
+ * @returns {WatchController}
56
+ */
57
+ export function watchTheme(element, options) {
58
+ if (!options || typeof options.colors?.resolveTheme !== "function") {
59
+ throw new TypeError("watchTheme: options.colors must be an initialised LabColors engine");
60
+ }
61
+ if (typeof options.theme !== "string") {
62
+ throw new TypeError("watchTheme: options.theme must be a theme name string");
63
+ }
64
+
65
+ const target = options.target ?? element;
66
+ const fallback = options.fallback ?? "#FFFFFF";
67
+ const win = options.win ?? (typeof globalThis !== "undefined" ? globalThis : undefined);
68
+ let theme = options.theme;
69
+ let lastBg = null;
70
+ let lastTheme = null;
71
+ let lastResult = null;
72
+
73
+ const readBackground = () => {
74
+ const b = options.background;
75
+ if (typeof b === "function") return b();
76
+ if (typeof b === "string") return b;
77
+ return effectiveBackground(element, {
78
+ fallback,
79
+ getStyle: options.getStyle,
80
+ parentOf: options.parentOf,
81
+ });
82
+ };
83
+
84
+ const refresh = (force = false) => {
85
+ const bg = readBackground();
86
+ if (!force && bg === lastBg && theme === lastTheme) return lastResult;
87
+ const result = options.colors.resolveTheme(bg, theme);
88
+ applyTheme(target, result);
89
+ lastBg = bg;
90
+ lastTheme = theme;
91
+ lastResult = result;
92
+ return result;
93
+ };
94
+
95
+ // Coalesce a burst of mutations into a single refresh on the next microtask.
96
+ let scheduled = false;
97
+ let stopped = false;
98
+ const schedule = () => {
99
+ if (scheduled || stopped) return;
100
+ scheduled = true;
101
+ Promise.resolve().then(() => {
102
+ scheduled = false;
103
+ // A `stop()` between scheduling and this microtask must cancel the refresh
104
+ // — the watcher is done, no late writes.
105
+ if (!stopped) refresh();
106
+ });
107
+ };
108
+
109
+ let observer = null;
110
+ if (options.observe !== false && win && typeof win.MutationObserver === "function") {
111
+ const root =
112
+ options.root ??
113
+ (typeof win.document !== "undefined" ? win.document.documentElement : null);
114
+ if (root) {
115
+ observer = new win.MutationObserver(schedule);
116
+ // A background can change on the element OR any ancestor, via inline style
117
+ // or a class swap — so watch attribute changes across the subtree.
118
+ observer.observe(root, {
119
+ subtree: true,
120
+ attributes: true,
121
+ attributeFilter: ["style", "class"],
122
+ });
123
+ }
124
+ }
125
+
126
+ // Apply immediately so the surface is correct on creation.
127
+ refresh(true);
128
+
129
+ return {
130
+ refresh,
131
+ setTheme(next) {
132
+ theme = next;
133
+ refresh();
134
+ },
135
+ background() {
136
+ return lastBg;
137
+ },
138
+ stop() {
139
+ stopped = true;
140
+ if (observer) observer.disconnect();
141
+ observer = null;
142
+ },
143
+ };
144
+ }