@skyscanner/backpack-web 42.27.0 → 42.27.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.
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
export { BpkProvider } from "./src/BpkProvider";
|
|
19
|
+
export { BpkProvider, BpkEmotionCacheContext } from "./src/BpkProvider";
|
|
20
20
|
export { BpkBox } from "./src/BpkBox";
|
|
21
21
|
export { BpkVessel } from "./src/BpkVessel";
|
|
22
22
|
export { BpkFlex } from "./src/BpkFlex";
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode, ReactElement } from 'react';
|
|
2
|
+
import type { EmotionCache } from '@emotion/cache';
|
|
2
3
|
export interface BpkProviderProps {
|
|
3
4
|
children: ReactNode;
|
|
4
5
|
}
|
|
6
|
+
export declare const BpkEmotionCacheContext: import("react").Context<EmotionCache | null>;
|
|
5
7
|
/**
|
|
6
8
|
* BpkProvider - Provides context for Backpack layout and Ark-based components.
|
|
7
9
|
*
|
|
8
10
|
* Wraps children with:
|
|
11
|
+
* - Emotion CacheProvider (own cache, or external cache injected via BpkEmotionCacheContext)
|
|
9
12
|
* - Chakra UI system context (for layout components: BpkFlex, BpkGrid, etc.)
|
|
10
13
|
* - Ark UI LocaleProvider (for Ark-based components: BpkCheckboxV2, BpkSegmentedControlV2, etc.)
|
|
11
14
|
*
|
|
@@ -13,6 +16,11 @@ export interface BpkProviderProps {
|
|
|
13
16
|
* the appropriate locale to Ark's LocaleProvider. All Ark-based components in the
|
|
14
17
|
* tree render correctly in RTL without requiring additional wrapping or prop changes.
|
|
15
18
|
*
|
|
19
|
+
* External cache injection: host apps can supply an Emotion cache via
|
|
20
|
+
* BpkEmotionCacheContext. When a non-null value is provided, BpkProvider uses it
|
|
21
|
+
* directly and skips creating its own cache. This allows the host to swap in a
|
|
22
|
+
* fresh cache after a hydration error so all Backpack styles are re-injected.
|
|
23
|
+
*
|
|
16
24
|
* @param {BpkProviderProps} props - The provider props.
|
|
17
25
|
* @returns {ReactElement} The provider wrapping its children with Chakra and Ark context.
|
|
18
26
|
*/
|
|
@@ -23,6 +23,12 @@ import createCache from '@emotion/cache';
|
|
|
23
23
|
import { CacheProvider } from '@emotion/react';
|
|
24
24
|
import { createBpkConfig } from "./theme";
|
|
25
25
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
26
|
+
// Exported so host apps can inject an externally-managed Emotion cache into
|
|
27
|
+
// BpkProvider (e.g. to force re-injection after a hydration error recovery).
|
|
28
|
+
// When a non-null value is provided via this context, BpkProvider operates in
|
|
29
|
+
// "external cache" mode: it skips creating its own cache and delegates to the external one.
|
|
30
|
+
export const BpkEmotionCacheContext = /*#__PURE__*/createContext(null);
|
|
31
|
+
|
|
26
32
|
/**
|
|
27
33
|
* Creates a Chakra UI system with Backpack token mappings.
|
|
28
34
|
*
|
|
@@ -37,6 +43,14 @@ const bpkSystem = createSystem(defaultBaseConfig, createBpkConfig());
|
|
|
37
43
|
// Force speedy:false + recreate after mount in Cypress. Remove once Emotion /
|
|
38
44
|
// React Router 7 provide a cleaner hydration story. Ported from hotels-website#12025.
|
|
39
45
|
|
|
46
|
+
// `'css'` is shared with Chakra v3's internal key on purpose — keeps this
|
|
47
|
+
// boundary in front of Chakra's auto-created cache.
|
|
48
|
+
const createBpkEmotionCache = speedy => createCache(speedy === undefined ? {
|
|
49
|
+
key: 'css'
|
|
50
|
+
} : {
|
|
51
|
+
key: 'css',
|
|
52
|
+
speedy
|
|
53
|
+
});
|
|
40
54
|
const isCypressEnv = () => {
|
|
41
55
|
if (typeof window === 'undefined') return false;
|
|
42
56
|
const win = window;
|
|
@@ -49,16 +63,6 @@ const isCypressEnv = () => {
|
|
|
49
63
|
return false; // cross-origin parent frame
|
|
50
64
|
}
|
|
51
65
|
};
|
|
52
|
-
|
|
53
|
-
// `'css'` is shared with Chakra v3's internal key on purpose — keeps this
|
|
54
|
-
// boundary in front of Chakra's auto-created cache.
|
|
55
|
-
const createBpkEmotionCache = speedy => createCache(speedy === undefined ? {
|
|
56
|
-
key: 'css'
|
|
57
|
-
} : {
|
|
58
|
-
key: 'css',
|
|
59
|
-
speedy
|
|
60
|
-
});
|
|
61
|
-
const BpkEmotionCacheContext = /*#__PURE__*/createContext(null);
|
|
62
66
|
// Fallback locale mapping used when no explicit locale is available on the document.
|
|
63
67
|
// Maps DOM direction to minimal BCP 47 locales understood by Ark's isRTL() utility.
|
|
64
68
|
// 'ar-SA' is the minimal RTL locale — Ark only uses it to derive dir='rtl'.
|
|
@@ -71,6 +75,19 @@ const FALLBACK_LOCALE_BY_DIRECTION = {
|
|
|
71
75
|
// Intl.Locale.textInfo is unavailable (Node < 22, older browsers).
|
|
72
76
|
const RTL_LANGUAGE_SUBTAGS = new Set(['ar', 'he', 'fa', 'ur', 'yi', 'iw', 'ps', 'sd', 'ug', 'ku']);
|
|
73
77
|
|
|
78
|
+
// Returns true when `locale` is a BCP 47 string that Intl.Locale accepts.
|
|
79
|
+
// Ark's LocaleProvider calls `new Intl.Locale(locale)` without a try/catch,
|
|
80
|
+
// so any value we hand it must be validated here first or it throws
|
|
81
|
+
// "Incorrect locale information provided".
|
|
82
|
+
const isValidLocale = locale => {
|
|
83
|
+
try {
|
|
84
|
+
// Reading a property on the result (rather than bare `new`) satisfies the no-new lint rule.
|
|
85
|
+
return Boolean(new Intl.Locale(locale).baseName);
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
74
91
|
// Returns the text direction implied by a BCP 47 locale string.
|
|
75
92
|
// Uses Intl.Locale.textInfo when available (Chrome 99+, Safari 15.4+, Firefox 126+, Node 22+);
|
|
76
93
|
// falls back to a known-RTL-subtag lookup.
|
|
@@ -88,11 +105,18 @@ const getLangDir = locale => {
|
|
|
88
105
|
//
|
|
89
106
|
// Priority rules:
|
|
90
107
|
// 1. If html[dir] is explicitly set:
|
|
91
|
-
// - Use html[lang] only when its direction is
|
|
108
|
+
// - Use html[lang] only when it is a valid locale AND its direction is
|
|
109
|
+
// consistent with html[dir].
|
|
92
110
|
// - Otherwise fall back to FALLBACK_LOCALE_BY_DIRECTION[dir].
|
|
93
111
|
// This prevents an LTR html[lang] (e.g. 'en' from a page template) from
|
|
94
112
|
// overriding an explicit html[dir]="rtl" signal (e.g. from a dev RTL toggle).
|
|
95
|
-
// 2. If html[dir] is not set: use html[lang] if
|
|
113
|
+
// 2. If html[dir] is not set: use html[lang] if it is a valid locale, else 'en-US'.
|
|
114
|
+
//
|
|
115
|
+
// Every value returned here is validated with isValidLocale() because Ark's
|
|
116
|
+
// LocaleProvider passes it straight to `new Intl.Locale()`, which throws on
|
|
117
|
+
// malformed input (e.g. '', '123', 'en_US'). An unvalidated value crashes the
|
|
118
|
+
// provider, and when the ErrorBoundary fallback also mounts BpkProvider the same
|
|
119
|
+
// bad value is re-read on every remount, producing an indefinite crash loop.
|
|
96
120
|
//
|
|
97
121
|
// SSR-safe: returns 'en-US' when document is unavailable.
|
|
98
122
|
const getArkLocale = () => {
|
|
@@ -100,17 +124,12 @@ const getArkLocale = () => {
|
|
|
100
124
|
const explicitDir = document.documentElement.getAttribute('dir');
|
|
101
125
|
const lang = document.documentElement.getAttribute('lang');
|
|
102
126
|
if (explicitDir === 'rtl' || explicitDir === 'ltr') {
|
|
103
|
-
if (lang && getLangDir(lang) === explicitDir)
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
if (lang) {
|
|
107
|
-
try {
|
|
108
|
-
const locale = new Intl.Locale(lang);
|
|
109
|
-
if (locale) return lang;
|
|
110
|
-
} catch {
|
|
111
|
-
// Invalid locale string — fall through to default
|
|
127
|
+
if (lang && isValidLocale(lang) && getLangDir(lang) === explicitDir) {
|
|
128
|
+
return lang;
|
|
112
129
|
}
|
|
130
|
+
return FALLBACK_LOCALE_BY_DIRECTION[explicitDir];
|
|
113
131
|
}
|
|
132
|
+
if (lang && isValidLocale(lang)) return lang;
|
|
114
133
|
return 'en-US';
|
|
115
134
|
};
|
|
116
135
|
|
|
@@ -138,6 +157,7 @@ const useArkLocale = () => {
|
|
|
138
157
|
* BpkProvider - Provides context for Backpack layout and Ark-based components.
|
|
139
158
|
*
|
|
140
159
|
* Wraps children with:
|
|
160
|
+
* - Emotion CacheProvider (own cache, or external cache injected via BpkEmotionCacheContext)
|
|
141
161
|
* - Chakra UI system context (for layout components: BpkFlex, BpkGrid, etc.)
|
|
142
162
|
* - Ark UI LocaleProvider (for Ark-based components: BpkCheckboxV2, BpkSegmentedControlV2, etc.)
|
|
143
163
|
*
|
|
@@ -145,16 +165,21 @@ const useArkLocale = () => {
|
|
|
145
165
|
* the appropriate locale to Ark's LocaleProvider. All Ark-based components in the
|
|
146
166
|
* tree render correctly in RTL without requiring additional wrapping or prop changes.
|
|
147
167
|
*
|
|
168
|
+
* External cache injection: host apps can supply an Emotion cache via
|
|
169
|
+
* BpkEmotionCacheContext. When a non-null value is provided, BpkProvider uses it
|
|
170
|
+
* directly and skips creating its own cache. This allows the host to swap in a
|
|
171
|
+
* fresh cache after a hydration error so all Backpack styles are re-injected.
|
|
172
|
+
*
|
|
148
173
|
* @param {BpkProviderProps} props - The provider props.
|
|
149
174
|
* @returns {ReactElement} The provider wrapping its children with Chakra and Ark context.
|
|
150
175
|
*/
|
|
151
176
|
export const BpkProvider = ({
|
|
152
177
|
children
|
|
153
178
|
}) => {
|
|
154
|
-
const
|
|
155
|
-
const
|
|
179
|
+
const externalCache = useContext(BpkEmotionCacheContext);
|
|
180
|
+
const hasExternalCache = externalCache !== null;
|
|
156
181
|
const [isCypress] = useState(isCypressEnv);
|
|
157
|
-
const [ownCache, setOwnCache] = useState(() =>
|
|
182
|
+
const [ownCache, setOwnCache] = useState(() => hasExternalCache ? externalCache : createBpkEmotionCache(isCypress ? false : undefined));
|
|
158
183
|
const hasRecreated = useRef(false);
|
|
159
184
|
const locale = useArkLocale();
|
|
160
185
|
|
|
@@ -162,12 +187,19 @@ export const BpkProvider = ({
|
|
|
162
187
|
// nodes the hydrator stripped. `hasRecreated` guards StrictMode double-invoke.
|
|
163
188
|
// Deps stable for provider lifetime → empty array is intentional.
|
|
164
189
|
useEffect(() => {
|
|
165
|
-
if (
|
|
190
|
+
if (hasExternalCache || !isCypress) return;
|
|
166
191
|
if (hasRecreated.current) return;
|
|
167
192
|
hasRecreated.current = true;
|
|
168
193
|
setOwnCache(createBpkEmotionCache(false));
|
|
169
194
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
170
195
|
}, []);
|
|
196
|
+
|
|
197
|
+
// NOTE: if externalCache changes from non-null to null at runtime, ownCache
|
|
198
|
+
// will still hold the value it was initialised with (the old external cache).
|
|
199
|
+
// This is intentional: BpkEmotionCacheContext.Provider is expected to be
|
|
200
|
+
// mounted for the lifetime of the app once set — toggling it off is not a
|
|
201
|
+
// supported use case. The state initialiser runs only once per mount.
|
|
202
|
+
const activeCache = hasExternalCache ? externalCache : ownCache;
|
|
171
203
|
const inner = /*#__PURE__*/_jsx(ChakraProvider, {
|
|
172
204
|
value: bpkSystem,
|
|
173
205
|
children: /*#__PURE__*/_jsx(LocaleProvider, {
|
|
@@ -175,13 +207,10 @@ export const BpkProvider = ({
|
|
|
175
207
|
children: children
|
|
176
208
|
})
|
|
177
209
|
});
|
|
178
|
-
if (isNested) {
|
|
179
|
-
return inner;
|
|
180
|
-
}
|
|
181
210
|
return /*#__PURE__*/_jsx(BpkEmotionCacheContext.Provider, {
|
|
182
|
-
value:
|
|
211
|
+
value: activeCache,
|
|
183
212
|
children: /*#__PURE__*/_jsx(CacheProvider, {
|
|
184
|
-
value:
|
|
213
|
+
value: activeCache,
|
|
185
214
|
children: inner
|
|
186
215
|
})
|
|
187
216
|
});
|