@oxyhq/bloom 0.6.0 → 0.6.2
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/lib/commonjs/context-menu/index.web.js +6 -2
- package/lib/commonjs/context-menu/index.web.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +7 -2
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/menu/index.web.js +3 -1
- package/lib/commonjs/menu/index.web.js.map +1 -1
- package/lib/commonjs/portal/index.web.js +23 -3
- package/lib/commonjs/portal/index.web.js.map +1 -1
- package/lib/commonjs/prompt-input/PromptInput.js +4 -1
- package/lib/commonjs/prompt-input/PromptInput.js.map +1 -1
- package/lib/commonjs/select/index.web.js +6 -2
- package/lib/commonjs/select/index.web.js.map +1 -1
- package/lib/commonjs/theme/BloomThemeProvider.js +6 -0
- package/lib/commonjs/theme/BloomThemeProvider.js.map +1 -1
- package/lib/commonjs/theme/index.js +7 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/init-css-interop.js +112 -0
- package/lib/commonjs/theme/init-css-interop.js.map +1 -0
- package/lib/module/context-menu/index.web.js +6 -2
- package/lib/module/context-menu/index.web.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +7 -2
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/menu/index.web.js +3 -1
- package/lib/module/menu/index.web.js.map +1 -1
- package/lib/module/portal/index.web.js +23 -3
- package/lib/module/portal/index.web.js.map +1 -1
- package/lib/module/prompt-input/PromptInput.js +4 -1
- package/lib/module/prompt-input/PromptInput.js.map +1 -1
- package/lib/module/select/index.web.js +6 -2
- package/lib/module/select/index.web.js.map +1 -1
- package/lib/module/theme/BloomThemeProvider.js +5 -0
- package/lib/module/theme/BloomThemeProvider.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/init-css-interop.js +109 -0
- package/lib/module/theme/init-css-interop.js.map +1 -0
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/portal/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/prompt-input/PromptInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts +1 -0
- package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/index.d.ts +1 -0
- package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/init-css-interop.d.ts +6 -0
- package/lib/typescript/commonjs/theme/init-css-interop.d.ts.map +1 -0
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/portal/index.web.d.ts.map +1 -1
- package/lib/typescript/module/prompt-input/PromptInput.d.ts.map +1 -1
- package/lib/typescript/module/theme/BloomThemeProvider.d.ts +1 -0
- package/lib/typescript/module/theme/BloomThemeProvider.d.ts.map +1 -1
- package/lib/typescript/module/theme/index.d.ts +1 -0
- package/lib/typescript/module/theme/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/init-css-interop.d.ts +6 -0
- package/lib/typescript/module/theme/init-css-interop.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/init-css-interop.test.ts +130 -0
- package/src/context-menu/index.web.tsx +4 -0
- package/src/dialog/Dialog.web.tsx +4 -0
- package/src/menu/index.web.tsx +2 -0
- package/src/portal/index.web.tsx +23 -3
- package/src/prompt-input/PromptInput.tsx +3 -0
- package/src/select/index.web.tsx +4 -0
- package/src/theme/BloomThemeProvider.tsx +5 -0
- package/src/theme/index.ts +1 -0
- package/src/theme/init-css-interop.ts +126 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize react-native-css-interop's `darkMode` flag to `'class'` once at
|
|
7
|
+
* module load time so that downstream calls to its color-scheme machinery do
|
|
8
|
+
* not throw.
|
|
9
|
+
*
|
|
10
|
+
* Why this exists
|
|
11
|
+
* ---------------
|
|
12
|
+
* Bloom toggles a `dark` class on `<html>` (see `applyDarkClass`). When the
|
|
13
|
+
* consuming app ships `react-native-css-interop` (directly or via NativeWind),
|
|
14
|
+
* its web runtime installs a MutationObserver on `<head>` that watches for
|
|
15
|
+
* the NativeWind-generated CSS to be injected. Once that CSS lands, the
|
|
16
|
+
* observer reads the `--css-interop-darkMode` CSS variable and calls
|
|
17
|
+
* `colorScheme.set(...)`. If the active value is `'media'` (the Tailwind
|
|
18
|
+
* default when `darkMode` is unset), `colorScheme.set` throws:
|
|
19
|
+
*
|
|
20
|
+
* "Cannot manually set color scheme, as dark mode is type 'media'.
|
|
21
|
+
* Please use StyleSheet.setFlag('darkMode', 'class')"
|
|
22
|
+
*
|
|
23
|
+
* Bloom is class-driven by design — `applyDarkClass` toggles `<html>.dark` —
|
|
24
|
+
* so we need to force the flag to `'class'` before that observer fires.
|
|
25
|
+
*
|
|
26
|
+
* How it works
|
|
27
|
+
* ------------
|
|
28
|
+
* - On web we set the documented `--css-interop-darkMode` CSS custom property
|
|
29
|
+
* on `documentElement`. This is exactly what NativeWind's Tailwind plugin
|
|
30
|
+
* writes when its config is `darkMode: 'class'`, so we are replicating the
|
|
31
|
+
* public contract — not poking internals.
|
|
32
|
+
* - We additionally try to call `StyleSheet.setFlag('darkMode', 'class')`
|
|
33
|
+
* off of `react-native-css-interop`'s exported `StyleSheet`. The function
|
|
34
|
+
* does not exist in current `react-native-css-interop` (≤0.2.4), but the
|
|
35
|
+
* error message advertises it and future versions are likely to add it.
|
|
36
|
+
* Calling it is forward-compatible; missing-function is a no-op.
|
|
37
|
+
*
|
|
38
|
+
* The whole routine is idempotent and runs exactly once per process via
|
|
39
|
+
* a module-scoped guard.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
let initialized = false;
|
|
43
|
+
let warned = false;
|
|
44
|
+
const CSS_VAR_NAME = '--css-interop-darkMode';
|
|
45
|
+
const CSS_VAR_VALUE = 'class dark';
|
|
46
|
+
function warnOnce(message, error) {
|
|
47
|
+
if (warned) return;
|
|
48
|
+
warned = true;
|
|
49
|
+
// Internal Bloom diagnostic. Consumers cannot react to this — it signals
|
|
50
|
+
// a possible compatibility issue between Bloom and the host's css-interop.
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.warn(`[Bloom] ${message}`, error ?? '');
|
|
53
|
+
}
|
|
54
|
+
function setWebCssVariable() {
|
|
55
|
+
if (typeof document === 'undefined') return;
|
|
56
|
+
const root = document.documentElement;
|
|
57
|
+
if (!root) return;
|
|
58
|
+
// Only set if not already explicitly configured by the host (e.g. the host's
|
|
59
|
+
// Tailwind config already emitted this variable). Avoid clobbering an
|
|
60
|
+
// explicit `media` choice from the consumer.
|
|
61
|
+
const current = root.style.getPropertyValue(CSS_VAR_NAME);
|
|
62
|
+
if (current) return;
|
|
63
|
+
root.style.setProperty(CSS_VAR_NAME, CSS_VAR_VALUE);
|
|
64
|
+
}
|
|
65
|
+
function tryCallSetFlag() {
|
|
66
|
+
let mod;
|
|
67
|
+
try {
|
|
68
|
+
// Dynamic require: css-interop is not a Bloom dependency. If the host
|
|
69
|
+
// app ships it (via NativeWind or directly), this resolves. Otherwise
|
|
70
|
+
// the require throws and we silently move on — there is nothing to
|
|
71
|
+
// initialize because css-interop is not in the bundle.
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
73
|
+
mod = require('react-native-css-interop');
|
|
74
|
+
} catch {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const setFlag = mod?.StyleSheet?.setFlag;
|
|
78
|
+
if (typeof setFlag !== 'function') {
|
|
79
|
+
// Current react-native-css-interop (≤0.2.4) does not expose `setFlag`.
|
|
80
|
+
// The CSS variable path above is the actual fix on web; on native the
|
|
81
|
+
// flag is baked in by the Tailwind build. Surface a warning so we
|
|
82
|
+
// notice if a future version adds the method but we miss it.
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
setFlag.call(mod?.StyleSheet, 'darkMode', 'class');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
warnOnce('react-native-css-interop StyleSheet.setFlag("darkMode", "class") threw. ' + 'Falling back to CSS variable. This is non-fatal.', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Idempotent initializer. Safe to call from multiple modules; only the first
|
|
94
|
+
* invocation performs work.
|
|
95
|
+
*/
|
|
96
|
+
export function initCssInteropDarkMode() {
|
|
97
|
+
if (initialized) return;
|
|
98
|
+
initialized = true;
|
|
99
|
+
if (Platform.OS === 'web') {
|
|
100
|
+
setWebCssVariable();
|
|
101
|
+
}
|
|
102
|
+
tryCallSetFlag();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Run on module import so that the flag is in place before any component
|
|
106
|
+
// code (and, critically, before css-interop's MutationObserver fires on web).
|
|
107
|
+
// useEffect would be too late — observers can fire during initial render.
|
|
108
|
+
initCssInteropDarkMode();
|
|
109
|
+
//# sourceMappingURL=init-css-interop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Platform","initialized","warned","CSS_VAR_NAME","CSS_VAR_VALUE","warnOnce","message","error","console","warn","setWebCssVariable","document","root","documentElement","current","style","getPropertyValue","setProperty","tryCallSetFlag","mod","require","setFlag","StyleSheet","call","initCssInteropDarkMode","OS"],"sourceRoot":"../../../src","sources":["theme/init-css-interop.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;;AAEvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,IAAIC,WAAW,GAAG,KAAK;AACvB,IAAIC,MAAM,GAAG,KAAK;AAElB,MAAMC,YAAY,GAAG,wBAAwB;AAC7C,MAAMC,aAAa,GAAG,YAAY;AAUlC,SAASC,QAAQA,CAACC,OAAe,EAAEC,KAAe,EAAQ;EACxD,IAAIL,MAAM,EAAE;EACZA,MAAM,GAAG,IAAI;EACb;EACA;EACA;EACAM,OAAO,CAACC,IAAI,CAAC,WAAWH,OAAO,EAAE,EAAEC,KAAK,IAAI,EAAE,CAAC;AACjD;AAEA,SAASG,iBAAiBA,CAAA,EAAS;EACjC,IAAI,OAAOC,QAAQ,KAAK,WAAW,EAAE;EACrC,MAAMC,IAAI,GAAGD,QAAQ,CAACE,eAAe;EACrC,IAAI,CAACD,IAAI,EAAE;EACX;EACA;EACA;EACA,MAAME,OAAO,GAAGF,IAAI,CAACG,KAAK,CAACC,gBAAgB,CAACb,YAAY,CAAC;EACzD,IAAIW,OAAO,EAAE;EACbF,IAAI,CAACG,KAAK,CAACE,WAAW,CAACd,YAAY,EAAEC,aAAa,CAAC;AACrD;AAEA,SAASc,cAAcA,CAAA,EAAS;EAC9B,IAAIC,GAAiC;EACrC,IAAI;IACF;IACA;IACA;IACA;IACA;IACAA,GAAG,GAAGC,OAAO,CAAC,0BAA0B,CAAqB;EAC/D,CAAC,CAAC,MAAM;IACN;EACF;EAEA,MAAMC,OAAO,GAAGF,GAAG,EAAEG,UAAU,EAAED,OAAO;EACxC,IAAI,OAAOA,OAAO,KAAK,UAAU,EAAE;IACjC;IACA;IACA;IACA;IACA;EACF;EAEA,IAAI;IACFA,OAAO,CAACE,IAAI,CAACJ,GAAG,EAAEG,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC;EACpD,CAAC,CAAC,OAAOf,KAAK,EAAE;IACdF,QAAQ,CACN,0EAA0E,GACxE,kDAAkD,EACpDE,KACF,CAAC;EACH;AACF;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASiB,sBAAsBA,CAAA,EAAS;EAC7C,IAAIvB,WAAW,EAAE;EACjBA,WAAW,GAAG,IAAI;EAElB,IAAID,QAAQ,CAACyB,EAAE,KAAK,KAAK,EAAE;IACzBf,iBAAiB,CAAC,CAAC;EACrB;EAEAQ,cAAc,CAAC,CAAC;AAClB;;AAEA;AACA;AACA;AACAM,sBAAsB,CAAC,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.web.d.ts","sourceRoot":"","sources":["../../../../src/dialog/Dialog.web.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,YAAY,EAGZ,WAAW,EACZ,MAAM,SAAS,CAAC;AAQjB;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,GACT,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"Dialog.web.d.ts","sourceRoot":"","sources":["../../../../src/dialog/Dialog.web.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,YAAY,EAGZ,WAAW,EACZ,MAAM,SAAS,CAAC;AAQjB;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,GACT,EAAE,WAAW,kDA4Gb;AA8LD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,WAAW,EACX,OAAO,EACP,SAAS,GACV,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,2CAgBA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,mZAK5B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/portal/index.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAoD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/portal/index.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAoD,MAAM,OAAO,CAAC;AA6CzE,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,4BAOnE;AAKD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,2CAE9D;AAED,eAAO,MAAM,MAAM,oCAEjB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/PromptInput.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,EAC1B,SAAiB,EACjB,SAAe,EACf,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,MAAM,EACN,WAAW,EACX,WAAW,EAAE,qBAAqB,EAClC,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,wBAAgC,EAChC,UAAU,EACV,YAAY,EACZ,MAAM,GACP,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/PromptInput.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,EAC1B,SAAiB,EACjB,SAAe,EACf,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,MAAM,EACN,WAAW,EACX,WAAW,EAAE,qBAAqB,EAClC,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,wBAAgC,EAChC,UAAU,EACV,YAAY,EACZ,MAAM,GACP,EAAE,gBAAgB,2CA+NlB;yBArPe,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BloomThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/theme/BloomThemeProvider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BloomThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/theme/BloomThemeProvider.tsx"],"names":[],"mappings":"AAIA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAA4E,MAAM,OAAO,CAAC;AAEjG,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKvE,OAAO,KAAK,EAAE,KAAK,EAAe,SAAS,EAAE,MAAM,SAAS,CAAC;AAmB7D,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,UAAU,GAAE,OAAe,GAAG,KAAK,CAuEjH;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,YAAY,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACnC,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAChD;AAED,eAAO,MAAM,iBAAiB,8CAAqD,CAAC;AAEpF,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IACrD;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EAAE,cAAc,EACpB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,EACZ,mBAAmB,EACnB,KAAY,EACZ,cAAc,EACd,QAAQ,GACT,EAAE,uBAAuB,2CA2DzB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,YAAY,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,oBAAoB,2CAoB9E"}
|
|
@@ -6,4 +6,5 @@ export type { AppColorName, AppColorPreset } from './color-presets';
|
|
|
6
6
|
export { APP_COLOR_NAMES, APP_COLOR_PRESETS, HEX_TO_APP_COLOR, hexToAppColorName } from './color-presets';
|
|
7
7
|
export { applyDarkClass } from './apply-dark-class';
|
|
8
8
|
export { setColorSchemeSafe } from './set-color-scheme-safe';
|
|
9
|
+
export { initCssInteropDarkMode } from './init-css-interop';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-css-interop.d.ts","sourceRoot":"","sources":["../../../../src/theme/init-css-interop.ts"],"names":[],"mappings":"AA2GA;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAS7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.web.d.ts","sourceRoot":"","sources":["../../../../src/dialog/Dialog.web.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,YAAY,EAGZ,WAAW,EACZ,MAAM,SAAS,CAAC;AAQjB;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,GACT,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"Dialog.web.d.ts","sourceRoot":"","sources":["../../../../src/dialog/Dialog.web.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,YAAY,EAGZ,WAAW,EACZ,MAAM,SAAS,CAAC;AAQjB;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,GACT,EAAE,WAAW,kDA4Gb;AA8LD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,WAAW,EACX,OAAO,EACP,SAAS,GACV,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,2CAgBA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,mZAK5B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/portal/index.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAoD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/portal/index.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAoD,MAAM,OAAO,CAAC;AA6CzE,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,4BAOnE;AAKD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,2CAE9D;AAED,eAAO,MAAM,MAAM,oCAEjB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/PromptInput.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,EAC1B,SAAiB,EACjB,SAAe,EACf,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,MAAM,EACN,WAAW,EACX,WAAW,EAAE,qBAAqB,EAClC,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,wBAAgC,EAChC,UAAU,EACV,YAAY,EACZ,MAAM,GACP,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/PromptInput.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,EAC1B,SAAiB,EACjB,SAAe,EACf,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,MAAM,EACN,WAAW,EACX,WAAW,EAAE,qBAAqB,EAClC,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,wBAAgC,EAChC,UAAU,EACV,YAAY,EACZ,MAAM,GACP,EAAE,gBAAgB,2CA+NlB;yBArPe,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BloomThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/theme/BloomThemeProvider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BloomThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/theme/BloomThemeProvider.tsx"],"names":[],"mappings":"AAIA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAA4E,MAAM,OAAO,CAAC;AAEjG,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKvE,OAAO,KAAK,EAAE,KAAK,EAAe,SAAS,EAAE,MAAM,SAAS,CAAC;AAmB7D,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,UAAU,GAAE,OAAe,GAAG,KAAK,CAuEjH;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,YAAY,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACnC,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAChD;AAED,eAAO,MAAM,iBAAiB,8CAAqD,CAAC;AAEpF,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IACrD;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EAAE,cAAc,EACpB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,EACZ,mBAAmB,EACnB,KAAY,EACZ,cAAc,EACd,QAAQ,GACT,EAAE,uBAAuB,2CA2DzB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,YAAY,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,oBAAoB,2CAoB9E"}
|
|
@@ -6,4 +6,5 @@ export type { AppColorName, AppColorPreset } from './color-presets';
|
|
|
6
6
|
export { APP_COLOR_NAMES, APP_COLOR_PRESETS, HEX_TO_APP_COLOR, hexToAppColorName } from './color-presets';
|
|
7
7
|
export { applyDarkClass } from './apply-dark-class';
|
|
8
8
|
export { setColorSchemeSafe } from './set-color-scheme-safe';
|
|
9
|
+
export { initCssInteropDarkMode } from './init-css-interop';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-css-interop.d.ts","sourceRoot":"","sources":["../../../../src/theme/init-css-interop.ts"],"names":[],"mappings":"AA2GA;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAS7C"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const CSS_VAR_NAME = '--css-interop-darkMode';
|
|
6
|
+
|
|
7
|
+
interface RNMock {
|
|
8
|
+
Platform: { OS: 'ios' | 'android' | 'web' | 'windows' | 'macos' };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('initCssInteropDarkMode', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.resetModules();
|
|
14
|
+
document.documentElement.style.removeProperty(CSS_VAR_NAME);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('sets the --css-interop-darkMode CSS variable on the document root when on web', () => {
|
|
18
|
+
expect(document.documentElement.style.getPropertyValue(CSS_VAR_NAME)).toBe('');
|
|
19
|
+
|
|
20
|
+
jest.isolateModules(() => {
|
|
21
|
+
const rn = require('react-native') as RNMock;
|
|
22
|
+
rn.Platform.OS = 'web';
|
|
23
|
+
// Importing the module triggers the side-effect initializer.
|
|
24
|
+
require('../theme/init-css-interop');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(document.documentElement.style.getPropertyValue(CSS_VAR_NAME)).toBe('class dark');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('does not clobber an explicit value already set by the host app', () => {
|
|
31
|
+
document.documentElement.style.setProperty(CSS_VAR_NAME, 'media');
|
|
32
|
+
|
|
33
|
+
jest.isolateModules(() => {
|
|
34
|
+
const rn = require('react-native') as RNMock;
|
|
35
|
+
rn.Platform.OS = 'web';
|
|
36
|
+
require('../theme/init-css-interop');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(document.documentElement.style.getPropertyValue(CSS_VAR_NAME)).toBe('media');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('is idempotent: subsequent calls to initCssInteropDarkMode() do not re-write', () => {
|
|
43
|
+
jest.isolateModules(() => {
|
|
44
|
+
const rn = require('react-native') as RNMock;
|
|
45
|
+
rn.Platform.OS = 'web';
|
|
46
|
+
const { initCssInteropDarkMode } = require('../theme/init-css-interop') as {
|
|
47
|
+
initCssInteropDarkMode: () => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// First call ran on import. Clear the value to prove the second call
|
|
51
|
+
// is a no-op (does not re-write).
|
|
52
|
+
document.documentElement.style.removeProperty(CSS_VAR_NAME);
|
|
53
|
+
initCssInteropDarkMode();
|
|
54
|
+
|
|
55
|
+
expect(document.documentElement.style.getPropertyValue(CSS_VAR_NAME)).toBe('');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('skips the CSS variable on non-web platforms', () => {
|
|
60
|
+
jest.isolateModules(() => {
|
|
61
|
+
const rn = require('react-native') as RNMock;
|
|
62
|
+
rn.Platform.OS = 'ios';
|
|
63
|
+
require('../theme/init-css-interop');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(document.documentElement.style.getPropertyValue(CSS_VAR_NAME)).toBe('');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('attempts to call StyleSheet.setFlag when react-native-css-interop exposes it', () => {
|
|
70
|
+
const setFlag = jest.fn();
|
|
71
|
+
|
|
72
|
+
jest.isolateModules(() => {
|
|
73
|
+
const rn = require('react-native') as RNMock;
|
|
74
|
+
rn.Platform.OS = 'web';
|
|
75
|
+
jest.doMock(
|
|
76
|
+
'react-native-css-interop',
|
|
77
|
+
() => ({
|
|
78
|
+
StyleSheet: { setFlag },
|
|
79
|
+
}),
|
|
80
|
+
{ virtual: true },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
require('../theme/init-css-interop');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(setFlag).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(setFlag).toHaveBeenCalledWith('darkMode', 'class');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('tolerates react-native-css-interop being absent (not a Bloom dep)', () => {
|
|
91
|
+
jest.isolateModules(() => {
|
|
92
|
+
const rn = require('react-native') as RNMock;
|
|
93
|
+
rn.Platform.OS = 'web';
|
|
94
|
+
jest.doMock(
|
|
95
|
+
'react-native-css-interop',
|
|
96
|
+
() => {
|
|
97
|
+
throw new Error('Cannot find module');
|
|
98
|
+
},
|
|
99
|
+
{ virtual: true },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(() => require('../theme/init-css-interop')).not.toThrow();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('does not throw if setFlag itself throws — warns instead', () => {
|
|
107
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
108
|
+
const setFlag = jest.fn(() => {
|
|
109
|
+
throw new Error('setFlag boom');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
jest.isolateModules(() => {
|
|
113
|
+
const rn = require('react-native') as RNMock;
|
|
114
|
+
rn.Platform.OS = 'web';
|
|
115
|
+
jest.doMock(
|
|
116
|
+
'react-native-css-interop',
|
|
117
|
+
() => ({
|
|
118
|
+
StyleSheet: { setFlag },
|
|
119
|
+
}),
|
|
120
|
+
{ virtual: true },
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(() => require('../theme/init-css-interop')).not.toThrow();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(setFlag).toHaveBeenCalled();
|
|
127
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
128
|
+
warnSpy.mockRestore();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -328,6 +328,8 @@ const styles = StyleSheet.create({
|
|
|
328
328
|
right: 0,
|
|
329
329
|
bottom: 0,
|
|
330
330
|
zIndex: 50,
|
|
331
|
+
// Opt back in from the Portal root's `pointer-events: none`.
|
|
332
|
+
pointerEvents: 'auto',
|
|
331
333
|
},
|
|
332
334
|
dropdown: {
|
|
333
335
|
position: 'fixed' as 'absolute',
|
|
@@ -341,6 +343,8 @@ const styles = StyleSheet.create({
|
|
|
341
343
|
shadowOffset: { width: 0, height: 4 },
|
|
342
344
|
minWidth: 180,
|
|
343
345
|
maxWidth: 280,
|
|
346
|
+
// Opt back in from the Portal root's `pointer-events: none`.
|
|
347
|
+
pointerEvents: 'auto',
|
|
344
348
|
},
|
|
345
349
|
item: {
|
|
346
350
|
flexDirection: 'row',
|
|
@@ -134,6 +134,9 @@ export function Dialog({
|
|
|
134
134
|
<RemoveScrollBar />
|
|
135
135
|
<Pressable
|
|
136
136
|
onPress={() => close()}
|
|
137
|
+
// `pointerEvents: 'auto'` opts back in from the Portal root's
|
|
138
|
+
// `pointer-events: none`, which is set so the idle portal
|
|
139
|
+
// doesn't intercept clicks on the underlying app.
|
|
137
140
|
style={{
|
|
138
141
|
position: 'fixed' as 'absolute',
|
|
139
142
|
top: 0,
|
|
@@ -144,6 +147,7 @@ export function Dialog({
|
|
|
144
147
|
alignItems: 'center',
|
|
145
148
|
justifyContent: 'center',
|
|
146
149
|
paddingHorizontal: 20,
|
|
150
|
+
pointerEvents: 'auto',
|
|
147
151
|
}}
|
|
148
152
|
>
|
|
149
153
|
<DialogBackdrop isClosing={isClosing} />
|
package/src/menu/index.web.tsx
CHANGED
package/src/portal/index.web.tsx
CHANGED
|
@@ -17,7 +17,20 @@ import React, { Fragment, memo, useLayoutEffect, useState } from 'react';
|
|
|
17
17
|
import { createPortal } from 'react-dom';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Render children into a stable container
|
|
20
|
+
* Render children into a stable container appended to `document.body`.
|
|
21
|
+
*
|
|
22
|
+
* The container is positioned `fixed` and stretched edge-to-edge so it
|
|
23
|
+
* fills the viewport. This makes it a valid containing block for any
|
|
24
|
+
* portaled descendant that uses `position: absolute` (e.g. RN's
|
|
25
|
+
* `StyleSheet.absoluteFillObject`) — without it those children anchor to a
|
|
26
|
+
* zero-area block at the end of `<body>` and render off-screen.
|
|
27
|
+
*
|
|
28
|
+
* `pointer-events: none` lets clicks fall through to the underlying app
|
|
29
|
+
* while the portal is idle. Interactive descendants (backdrops, modal
|
|
30
|
+
* panels, dropdowns, etc.) must opt back in with `pointer-events: auto`
|
|
31
|
+
* (or RN's `pointerEvents="auto"` / `"box-only"`). Bloom's own overlay
|
|
32
|
+
* components do this on the elements they actually want to receive
|
|
33
|
+
* events.
|
|
21
34
|
*
|
|
22
35
|
* Lazy-creates the container on first mount so SSR (where `document` does
|
|
23
36
|
* not exist) doesn't crash — the portal simply renders nothing on the
|
|
@@ -29,9 +42,16 @@ function getPortalRoot(): HTMLElement | null {
|
|
|
29
42
|
if (!root) {
|
|
30
43
|
root = document.createElement('div');
|
|
31
44
|
root.id = 'bloom-portal-root';
|
|
32
|
-
|
|
45
|
+
root.style.position = 'fixed';
|
|
46
|
+
root.style.top = '0';
|
|
47
|
+
root.style.left = '0';
|
|
48
|
+
root.style.right = '0';
|
|
49
|
+
root.style.bottom = '0';
|
|
50
|
+
// Idle portal must not intercept clicks on the underlying app.
|
|
51
|
+
// Children opt back in to receive events via their own pointer-events.
|
|
52
|
+
root.style.pointerEvents = 'none';
|
|
53
|
+
// Above the document flow; individual portaled components can use
|
|
33
54
|
// their own z-index for stacking among themselves.
|
|
34
|
-
root.style.position = 'relative';
|
|
35
55
|
root.style.zIndex = '999999';
|
|
36
56
|
document.body.appendChild(root);
|
|
37
57
|
}
|
|
@@ -235,6 +235,9 @@ export function PromptInput({
|
|
|
235
235
|
{
|
|
236
236
|
zIndex: 9998,
|
|
237
237
|
backgroundColor: theme.colors.background,
|
|
238
|
+
// Opt back in from the Portal root's `pointer-events: none`
|
|
239
|
+
// (web only — harmless on native).
|
|
240
|
+
pointerEvents: 'auto',
|
|
238
241
|
},
|
|
239
242
|
]}
|
|
240
243
|
>
|
package/src/select/index.web.tsx
CHANGED
|
@@ -338,6 +338,8 @@ const styles = StyleSheet.create({
|
|
|
338
338
|
right: 0,
|
|
339
339
|
bottom: 0,
|
|
340
340
|
zIndex: 50,
|
|
341
|
+
// Opt back in from the Portal root's `pointer-events: none`.
|
|
342
|
+
pointerEvents: 'auto',
|
|
341
343
|
},
|
|
342
344
|
dropdown: {
|
|
343
345
|
position: 'absolute',
|
|
@@ -350,6 +352,8 @@ const styles = StyleSheet.create({
|
|
|
350
352
|
shadowOpacity: 0.15,
|
|
351
353
|
shadowRadius: 12,
|
|
352
354
|
shadowOffset: { width: 0, height: 4 },
|
|
355
|
+
// Opt back in from the Portal root's `pointer-events: none`.
|
|
356
|
+
pointerEvents: 'auto',
|
|
353
357
|
},
|
|
354
358
|
item: {
|
|
355
359
|
position: 'relative',
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// Side-effect import — must come first. Initializes react-native-css-interop's
|
|
2
|
+
// `darkMode` flag to `'class'` at module load so its MutationObserver doesn't
|
|
3
|
+
// throw "Cannot manually set color scheme, as dark mode is type 'media'" the
|
|
4
|
+
// first time Bloom toggles the dark class on <html>. See ./init-css-interop.
|
|
5
|
+
import './init-css-interop';
|
|
1
6
|
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
2
7
|
import { useColorScheme as useRNColorScheme, Platform } from 'react-native';
|
|
3
8
|
import { APP_COLOR_PRESETS, type AppColorName } from './color-presets';
|
package/src/theme/index.ts
CHANGED
|
@@ -6,3 +6,4 @@ export type { AppColorName, AppColorPreset } from './color-presets';
|
|
|
6
6
|
export { APP_COLOR_NAMES, APP_COLOR_PRESETS, HEX_TO_APP_COLOR, hexToAppColorName } from './color-presets';
|
|
7
7
|
export { applyDarkClass } from './apply-dark-class';
|
|
8
8
|
export { setColorSchemeSafe } from './set-color-scheme-safe';
|
|
9
|
+
export { initCssInteropDarkMode } from './init-css-interop';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize react-native-css-interop's `darkMode` flag to `'class'` once at
|
|
5
|
+
* module load time so that downstream calls to its color-scheme machinery do
|
|
6
|
+
* not throw.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists
|
|
9
|
+
* ---------------
|
|
10
|
+
* Bloom toggles a `dark` class on `<html>` (see `applyDarkClass`). When the
|
|
11
|
+
* consuming app ships `react-native-css-interop` (directly or via NativeWind),
|
|
12
|
+
* its web runtime installs a MutationObserver on `<head>` that watches for
|
|
13
|
+
* the NativeWind-generated CSS to be injected. Once that CSS lands, the
|
|
14
|
+
* observer reads the `--css-interop-darkMode` CSS variable and calls
|
|
15
|
+
* `colorScheme.set(...)`. If the active value is `'media'` (the Tailwind
|
|
16
|
+
* default when `darkMode` is unset), `colorScheme.set` throws:
|
|
17
|
+
*
|
|
18
|
+
* "Cannot manually set color scheme, as dark mode is type 'media'.
|
|
19
|
+
* Please use StyleSheet.setFlag('darkMode', 'class')"
|
|
20
|
+
*
|
|
21
|
+
* Bloom is class-driven by design — `applyDarkClass` toggles `<html>.dark` —
|
|
22
|
+
* so we need to force the flag to `'class'` before that observer fires.
|
|
23
|
+
*
|
|
24
|
+
* How it works
|
|
25
|
+
* ------------
|
|
26
|
+
* - On web we set the documented `--css-interop-darkMode` CSS custom property
|
|
27
|
+
* on `documentElement`. This is exactly what NativeWind's Tailwind plugin
|
|
28
|
+
* writes when its config is `darkMode: 'class'`, so we are replicating the
|
|
29
|
+
* public contract — not poking internals.
|
|
30
|
+
* - We additionally try to call `StyleSheet.setFlag('darkMode', 'class')`
|
|
31
|
+
* off of `react-native-css-interop`'s exported `StyleSheet`. The function
|
|
32
|
+
* does not exist in current `react-native-css-interop` (≤0.2.4), but the
|
|
33
|
+
* error message advertises it and future versions are likely to add it.
|
|
34
|
+
* Calling it is forward-compatible; missing-function is a no-op.
|
|
35
|
+
*
|
|
36
|
+
* The whole routine is idempotent and runs exactly once per process via
|
|
37
|
+
* a module-scoped guard.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
let initialized = false;
|
|
41
|
+
let warned = false;
|
|
42
|
+
|
|
43
|
+
const CSS_VAR_NAME = '--css-interop-darkMode';
|
|
44
|
+
const CSS_VAR_VALUE = 'class dark';
|
|
45
|
+
|
|
46
|
+
interface CssInteropStyleSheet {
|
|
47
|
+
setFlag?: (name: string, value: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface CssInteropModule {
|
|
51
|
+
StyleSheet?: CssInteropStyleSheet;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function warnOnce(message: string, error?: unknown): void {
|
|
55
|
+
if (warned) return;
|
|
56
|
+
warned = true;
|
|
57
|
+
// Internal Bloom diagnostic. Consumers cannot react to this — it signals
|
|
58
|
+
// a possible compatibility issue between Bloom and the host's css-interop.
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.warn(`[Bloom] ${message}`, error ?? '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function setWebCssVariable(): void {
|
|
64
|
+
if (typeof document === 'undefined') return;
|
|
65
|
+
const root = document.documentElement;
|
|
66
|
+
if (!root) return;
|
|
67
|
+
// Only set if not already explicitly configured by the host (e.g. the host's
|
|
68
|
+
// Tailwind config already emitted this variable). Avoid clobbering an
|
|
69
|
+
// explicit `media` choice from the consumer.
|
|
70
|
+
const current = root.style.getPropertyValue(CSS_VAR_NAME);
|
|
71
|
+
if (current) return;
|
|
72
|
+
root.style.setProperty(CSS_VAR_NAME, CSS_VAR_VALUE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function tryCallSetFlag(): void {
|
|
76
|
+
let mod: CssInteropModule | undefined;
|
|
77
|
+
try {
|
|
78
|
+
// Dynamic require: css-interop is not a Bloom dependency. If the host
|
|
79
|
+
// app ships it (via NativeWind or directly), this resolves. Otherwise
|
|
80
|
+
// the require throws and we silently move on — there is nothing to
|
|
81
|
+
// initialize because css-interop is not in the bundle.
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
83
|
+
mod = require('react-native-css-interop') as CssInteropModule;
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const setFlag = mod?.StyleSheet?.setFlag;
|
|
89
|
+
if (typeof setFlag !== 'function') {
|
|
90
|
+
// Current react-native-css-interop (≤0.2.4) does not expose `setFlag`.
|
|
91
|
+
// The CSS variable path above is the actual fix on web; on native the
|
|
92
|
+
// flag is baked in by the Tailwind build. Surface a warning so we
|
|
93
|
+
// notice if a future version adds the method but we miss it.
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
setFlag.call(mod?.StyleSheet, 'darkMode', 'class');
|
|
99
|
+
} catch (error) {
|
|
100
|
+
warnOnce(
|
|
101
|
+
'react-native-css-interop StyleSheet.setFlag("darkMode", "class") threw. ' +
|
|
102
|
+
'Falling back to CSS variable. This is non-fatal.',
|
|
103
|
+
error,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Idempotent initializer. Safe to call from multiple modules; only the first
|
|
110
|
+
* invocation performs work.
|
|
111
|
+
*/
|
|
112
|
+
export function initCssInteropDarkMode(): void {
|
|
113
|
+
if (initialized) return;
|
|
114
|
+
initialized = true;
|
|
115
|
+
|
|
116
|
+
if (Platform.OS === 'web') {
|
|
117
|
+
setWebCssVariable();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
tryCallSetFlag();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Run on module import so that the flag is in place before any component
|
|
124
|
+
// code (and, critically, before css-interop's MutationObserver fires on web).
|
|
125
|
+
// useEffect would be too late — observers can fire during initial render.
|
|
126
|
+
initCssInteropDarkMode();
|