@kbach/ui 0.1.0-beta.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/KBACH.md +1044 -0
- package/README.md +473 -0
- package/dist/chunk-BPCFICND.mjs +4187 -0
- package/dist/chunk-UE54W6ZG.mjs +208 -0
- package/dist/core/index.d.ts +1346 -0
- package/dist/core/index.js +3712 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.js +1191 -0
- package/dist/index.mjs +589 -0
- package/dist/jsx-dev-runtime.d.ts +21 -0
- package/dist/jsx-dev-runtime.js +780 -0
- package/dist/jsx-dev-runtime.mjs +21 -0
- package/dist/jsx-runtime.d.ts +17 -0
- package/dist/jsx-runtime.js +779 -0
- package/dist/jsx-runtime.mjs +17 -0
- package/dist/native.d.ts +314 -0
- package/dist/native.js +99 -0
- package/dist/vite-plugin.d.mts +155 -0
- package/dist/vite-plugin.d.ts +155 -0
- package/dist/vite-plugin.js +3939 -0
- package/dist/vite-plugin.mjs +3903 -0
- package/dist/web-substitute-6xH1WxpZ.d.ts +22 -0
- package/jsx-dev-runtime.js +3 -0
- package/jsx-runtime.js +3 -0
- package/kbach-ui.md +889 -0
- package/package.json +104 -0
- package/scripts/postinstall.js +28 -0
- package/src/native/babel/index.js +30 -0
- package/src/native/babel-plugin/index.js +605 -0
- package/src/native/babel-plugin/index.test.ts +86 -0
- package/types.d.ts +1 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
BASE_RESET,
|
|
4
|
+
EMPTY_BREAKPOINTS,
|
|
5
|
+
InteractiveWrapper,
|
|
6
|
+
RESET_STYLE_ID,
|
|
7
|
+
buildConfig,
|
|
8
|
+
chain,
|
|
9
|
+
clearCache,
|
|
10
|
+
composeNativeStyle,
|
|
11
|
+
defaultColors,
|
|
12
|
+
defaultTheme,
|
|
13
|
+
disableRuntimeCSS,
|
|
14
|
+
flatten,
|
|
15
|
+
generateKbachTypesDts,
|
|
16
|
+
getActiveBreakpoints,
|
|
17
|
+
getConfig,
|
|
18
|
+
getEffectiveIsWeb,
|
|
19
|
+
getGlobalScreens,
|
|
20
|
+
getImpliedRNClasses,
|
|
21
|
+
getWebTag,
|
|
22
|
+
hasInteractiveBuckets,
|
|
23
|
+
hasResponsiveBuckets,
|
|
24
|
+
initConfig,
|
|
25
|
+
isModeAwareColor,
|
|
26
|
+
isNative,
|
|
27
|
+
isWeb,
|
|
28
|
+
kbachWarn,
|
|
29
|
+
normalizeClassString,
|
|
30
|
+
onConfigChange,
|
|
31
|
+
parseClass,
|
|
32
|
+
parseClasses,
|
|
33
|
+
parseHexRgb,
|
|
34
|
+
registerWebElement,
|
|
35
|
+
resetConfig,
|
|
36
|
+
resolve,
|
|
37
|
+
setGlobalDarkMode,
|
|
38
|
+
setGlobalWidth,
|
|
39
|
+
setResolveTarget,
|
|
40
|
+
splitClassTokens,
|
|
41
|
+
syncGlobalDarkMode,
|
|
42
|
+
syncGlobalScreens,
|
|
43
|
+
syncGlobalWidth,
|
|
44
|
+
transformToWebProps,
|
|
45
|
+
updateConfig,
|
|
46
|
+
useConditionalGlobalDarkMode,
|
|
47
|
+
useConditionalWidth,
|
|
48
|
+
useGlobalDarkMode,
|
|
49
|
+
useGlobalWidth,
|
|
50
|
+
useSyncExternalStore
|
|
51
|
+
} from "./chunk-BPCFICND.mjs";
|
|
52
|
+
|
|
53
|
+
// src/context.tsx
|
|
54
|
+
import { createContext, useContext } from "react";
|
|
55
|
+
var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = createContext(null));
|
|
56
|
+
function useTheme() {
|
|
57
|
+
const ctx = useContext(ThemeContext);
|
|
58
|
+
if (!ctx) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"[Kbach] useTheme() must be called inside a <ThemeProvider>. Wrap your app root with <ThemeProvider>."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return ctx;
|
|
64
|
+
}
|
|
65
|
+
function useIsDark() {
|
|
66
|
+
return useTheme().isDark;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/useColors.ts
|
|
70
|
+
import { useMemo } from "react";
|
|
71
|
+
function applyOpacity(color, opacity) {
|
|
72
|
+
const a = Math.max(0, Math.min(1, opacity / 100));
|
|
73
|
+
if (color.startsWith("#")) {
|
|
74
|
+
const rgb = parseHexRgb(color);
|
|
75
|
+
if (!rgb) return color;
|
|
76
|
+
const [r, g, b] = rgb;
|
|
77
|
+
return `rgba(${r},${g},${b},${a})`;
|
|
78
|
+
}
|
|
79
|
+
if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
|
|
80
|
+
if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
|
|
81
|
+
return color;
|
|
82
|
+
}
|
|
83
|
+
function pickSide(value, isDark) {
|
|
84
|
+
return isModeAwareColor(value) ? isDark ? value.dark : value.light : value;
|
|
85
|
+
}
|
|
86
|
+
function makeShadeProxy(shades, isDark) {
|
|
87
|
+
return new Proxy(shades, {
|
|
88
|
+
get(target, prop) {
|
|
89
|
+
const key = String(prop);
|
|
90
|
+
if (key === "then") return void 0;
|
|
91
|
+
if (key.includes("/")) {
|
|
92
|
+
const slash = key.indexOf("/");
|
|
93
|
+
const shade = key.slice(0, slash);
|
|
94
|
+
const op = Number(key.slice(slash + 1));
|
|
95
|
+
const color2 = target[shade];
|
|
96
|
+
return color2 !== void 0 ? applyOpacity(pickSide(color2, isDark), op) : void 0;
|
|
97
|
+
}
|
|
98
|
+
const color = target[key];
|
|
99
|
+
return color !== void 0 ? pickSide(color, isDark) : void 0;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function wrapColors(rawColors, isDark = false) {
|
|
104
|
+
const cache = /* @__PURE__ */ new Map();
|
|
105
|
+
const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
|
|
106
|
+
return new Proxy({ alpha }, {
|
|
107
|
+
get(_, prop) {
|
|
108
|
+
const key = String(prop);
|
|
109
|
+
if (key === "then") return void 0;
|
|
110
|
+
if (key === "alpha") return alpha;
|
|
111
|
+
if (key.includes("/")) {
|
|
112
|
+
const slash = key.indexOf("/");
|
|
113
|
+
const name = key.slice(0, slash);
|
|
114
|
+
const op = Number(key.slice(slash + 1));
|
|
115
|
+
const entry2 = rawColors[name];
|
|
116
|
+
return entry2 !== void 0 && (typeof entry2 === "string" || isModeAwareColor(entry2)) ? applyOpacity(pickSide(entry2, isDark), op) : void 0;
|
|
117
|
+
}
|
|
118
|
+
const entry = rawColors[key];
|
|
119
|
+
if (entry === void 0) return void 0;
|
|
120
|
+
if (typeof entry === "string" || isModeAwareColor(entry)) return pickSide(entry, isDark);
|
|
121
|
+
const cacheKey = `${key}:${isDark}`;
|
|
122
|
+
if (!cache.has(cacheKey)) cache.set(cacheKey, makeShadeProxy(entry, isDark));
|
|
123
|
+
return cache.get(cacheKey);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function useColors() {
|
|
128
|
+
const { config, isDark } = useTheme();
|
|
129
|
+
return useMemo(() => wrapColors(config.theme.colors, isDark), [config.theme.colors, isDark]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/useSpacing.ts
|
|
133
|
+
import { useMemo as useMemo2 } from "react";
|
|
134
|
+
function wrapSpacing(rawSpacing) {
|
|
135
|
+
return rawSpacing;
|
|
136
|
+
}
|
|
137
|
+
function useSpacing() {
|
|
138
|
+
const { config } = useTheme();
|
|
139
|
+
return useMemo2(() => wrapSpacing(config.theme.spacing), [config.theme.spacing]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/ThemeProvider.tsx
|
|
143
|
+
import {
|
|
144
|
+
useCallback,
|
|
145
|
+
useEffect,
|
|
146
|
+
useLayoutEffect,
|
|
147
|
+
useMemo as useMemo3,
|
|
148
|
+
useRef,
|
|
149
|
+
useState
|
|
150
|
+
} from "react";
|
|
151
|
+
import { jsx } from "react/jsx-runtime";
|
|
152
|
+
var useIsomorphicLayoutEffect = isWeb || isNative ? useLayoutEffect : useEffect;
|
|
153
|
+
var STORAGE_KEY = "kbach-theme";
|
|
154
|
+
var _mountedProviderCount = 0;
|
|
155
|
+
var _warnedMultipleProviders = false;
|
|
156
|
+
var _mountedConfigOverrideCount = 0;
|
|
157
|
+
var _warnedConfigOverride = false;
|
|
158
|
+
var _warnedMediaModeToggle = false;
|
|
159
|
+
function loadPersistedMode() {
|
|
160
|
+
try {
|
|
161
|
+
if (isWeb) return localStorage.getItem(STORAGE_KEY);
|
|
162
|
+
return null;
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function persistMode(mode) {
|
|
168
|
+
try {
|
|
169
|
+
if (isWeb) localStorage.setItem(STORAGE_KEY, mode);
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function subscribeSystemScheme(callback) {
|
|
174
|
+
if (!isWeb || typeof window === "undefined") return () => {
|
|
175
|
+
};
|
|
176
|
+
const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
177
|
+
if (!mq) return () => {
|
|
178
|
+
};
|
|
179
|
+
mq.addEventListener("change", callback);
|
|
180
|
+
return () => mq.removeEventListener("change", callback);
|
|
181
|
+
}
|
|
182
|
+
function getSystemScheme() {
|
|
183
|
+
if (!isWeb || typeof window === "undefined") return "light";
|
|
184
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
|
|
185
|
+
}
|
|
186
|
+
function getSystemSchemeServerSnapshot() {
|
|
187
|
+
return "light";
|
|
188
|
+
}
|
|
189
|
+
function applyWebTheme(resolvedMode, strategy) {
|
|
190
|
+
if (!isWeb) return;
|
|
191
|
+
const root = document.documentElement;
|
|
192
|
+
if (strategy === "attribute") {
|
|
193
|
+
root.setAttribute("data-theme", resolvedMode);
|
|
194
|
+
} else if (strategy === "class") {
|
|
195
|
+
root.classList.toggle("dark", resolvedMode === "dark");
|
|
196
|
+
root.classList.toggle("light", resolvedMode === "light");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function ThemeProvider({
|
|
200
|
+
children,
|
|
201
|
+
defaultMode = "system",
|
|
202
|
+
colorScheme,
|
|
203
|
+
windowWidth: windowWidthProp,
|
|
204
|
+
config: configOverride,
|
|
205
|
+
disablePersistence = false
|
|
206
|
+
}) {
|
|
207
|
+
const [resolvedConfig, setResolvedConfig] = useState(
|
|
208
|
+
() => configOverride ? buildConfig(configOverride) : getConfig()
|
|
209
|
+
);
|
|
210
|
+
const _prevConfigOverrideRef = useRef(void 0);
|
|
211
|
+
if (configOverride && _prevConfigOverrideRef.current !== configOverride) {
|
|
212
|
+
_prevConfigOverrideRef.current = configOverride;
|
|
213
|
+
updateConfig(configOverride);
|
|
214
|
+
const fresh = getConfig();
|
|
215
|
+
if (fresh !== resolvedConfig) setResolvedConfig(fresh);
|
|
216
|
+
}
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
_mountedProviderCount++;
|
|
219
|
+
if (configOverride) _mountedConfigOverrideCount++;
|
|
220
|
+
if (process.env.NODE_ENV !== "production") {
|
|
221
|
+
if (_mountedProviderCount > 1 && !_warnedMultipleProviders) {
|
|
222
|
+
_warnedMultipleProviders = true;
|
|
223
|
+
kbachWarn(
|
|
224
|
+
"Multiple <ThemeProvider> instances are mounted at once. Dark mode and responsive width are shared through one global store, so whichever provider rendered most recently wins for every consumer \u2014 nested or per-section theming is not isolated between providers."
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (_mountedConfigOverrideCount > 1 && !_warnedConfigOverride) {
|
|
228
|
+
_warnedConfigOverride = true;
|
|
229
|
+
kbachWarn(
|
|
230
|
+
"Multiple <ThemeProvider config={...}> overrides are mounted at once. The resolved config is shared through the same global store as dark mode/width (see the multiple-providers warning), so per-tree config overrides are not actually isolated between providers \u2014 plain className/kb elements with no hover:/dark:/sm: modifier resolve against whichever override last committed, not necessarily their nearest ancestor <ThemeProvider>."
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return () => {
|
|
235
|
+
_mountedProviderCount--;
|
|
236
|
+
if (configOverride) _mountedConfigOverrideCount--;
|
|
237
|
+
if (_prevConfigOverrideRef.current !== void 0 && _mountedProviderCount === 0) {
|
|
238
|
+
resetConfig();
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}, []);
|
|
242
|
+
const [webWidth, setWebWidth] = useState(0);
|
|
243
|
+
const effectiveWidth = getEffectiveIsWeb() ? webWidth : windowWidthProp ?? 0;
|
|
244
|
+
const numericScreens = useMemo3(() => {
|
|
245
|
+
const out = {};
|
|
246
|
+
for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
|
|
247
|
+
out[k] = typeof v === "number" ? v : parseInt(String(v), 10);
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
}, [resolvedConfig.theme.screens]);
|
|
251
|
+
const _prevWidthRef = useRef(-1);
|
|
252
|
+
if (_prevWidthRef.current !== effectiveWidth) {
|
|
253
|
+
_prevWidthRef.current = effectiveWidth;
|
|
254
|
+
syncGlobalWidth(effectiveWidth);
|
|
255
|
+
}
|
|
256
|
+
const _prevScreensRef = useRef(null);
|
|
257
|
+
if (_prevScreensRef.current !== numericScreens) {
|
|
258
|
+
_prevScreensRef.current = numericScreens;
|
|
259
|
+
syncGlobalScreens(numericScreens);
|
|
260
|
+
}
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
setGlobalWidth(effectiveWidth);
|
|
263
|
+
}, [effectiveWidth]);
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (!isWeb || typeof window === "undefined") return;
|
|
266
|
+
setWebWidth(window.innerWidth);
|
|
267
|
+
let raf = 0;
|
|
268
|
+
const update = () => {
|
|
269
|
+
cancelAnimationFrame(raf);
|
|
270
|
+
raf = requestAnimationFrame(() => {
|
|
271
|
+
const w = window.innerWidth;
|
|
272
|
+
setWebWidth(w);
|
|
273
|
+
setGlobalWidth(w);
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
window.addEventListener("resize", update);
|
|
277
|
+
return () => {
|
|
278
|
+
window.removeEventListener("resize", update);
|
|
279
|
+
cancelAnimationFrame(raf);
|
|
280
|
+
};
|
|
281
|
+
}, []);
|
|
282
|
+
const webScheme = useSyncExternalStore(
|
|
283
|
+
subscribeSystemScheme,
|
|
284
|
+
getSystemScheme,
|
|
285
|
+
getSystemSchemeServerSnapshot
|
|
286
|
+
);
|
|
287
|
+
const systemScheme = getEffectiveIsWeb() ? webScheme : colorScheme === "dark" ? "dark" : "light";
|
|
288
|
+
const [mode, _setMode] = useState(defaultMode);
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (disablePersistence) return;
|
|
291
|
+
const persisted = loadPersistedMode();
|
|
292
|
+
if (persisted) _setMode(persisted);
|
|
293
|
+
}, []);
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (disablePersistence || !isWeb || typeof window === "undefined") return;
|
|
296
|
+
const onStorage = (e) => {
|
|
297
|
+
if (e.key !== STORAGE_KEY || e.newValue == null) return;
|
|
298
|
+
if (e.newValue === "light" || e.newValue === "dark" || e.newValue === "system") {
|
|
299
|
+
_setMode(e.newValue);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
window.addEventListener("storage", onStorage);
|
|
303
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
304
|
+
}, [disablePersistence]);
|
|
305
|
+
const setMode = useCallback((next) => {
|
|
306
|
+
if (process.env.NODE_ENV !== "production" && isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
|
|
307
|
+
_warnedMediaModeToggle = true;
|
|
308
|
+
kbachWarn(
|
|
309
|
+
'setMode()/toggle() was called while darkMode is "media" \u2014 the resolved mode/isDark from useTheme()/useIsDark() will update, but the actual CSS stays driven by prefers-color-scheme and will not change to match. Use darkMode: "attribute" or "class" if the app needs manual toggling.'
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
_setMode(next);
|
|
313
|
+
if (!disablePersistence) persistMode(next);
|
|
314
|
+
}, [disablePersistence, resolvedConfig.darkMode]);
|
|
315
|
+
const toggle = useCallback(() => {
|
|
316
|
+
setMode(mode === "dark" || mode === "system" && systemScheme === "dark" ? "light" : "dark");
|
|
317
|
+
}, [mode, systemScheme, setMode]);
|
|
318
|
+
const resolvedMode = mode === "system" ? systemScheme : mode;
|
|
319
|
+
const isDark = resolvedMode === "dark";
|
|
320
|
+
syncGlobalDarkMode(isDark);
|
|
321
|
+
useIsomorphicLayoutEffect(() => {
|
|
322
|
+
applyWebTheme(resolvedMode, resolvedConfig.darkMode);
|
|
323
|
+
setGlobalDarkMode(isDark);
|
|
324
|
+
}, [isDark, resolvedMode, resolvedConfig.darkMode]);
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
if (configOverride) return;
|
|
327
|
+
let mounted = true;
|
|
328
|
+
const unsub = onConfigChange((config) => {
|
|
329
|
+
if (mounted) setResolvedConfig(config);
|
|
330
|
+
});
|
|
331
|
+
return () => {
|
|
332
|
+
mounted = false;
|
|
333
|
+
unsub();
|
|
334
|
+
};
|
|
335
|
+
}, [configOverride]);
|
|
336
|
+
const contextValue = useMemo3(
|
|
337
|
+
() => ({ mode, resolvedMode, isDark, setMode, toggle, config: resolvedConfig }),
|
|
338
|
+
[mode, resolvedMode, isDark, setMode, toggle, resolvedConfig]
|
|
339
|
+
);
|
|
340
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/KbachReset.tsx
|
|
344
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
345
|
+
function KbachReset() {
|
|
346
|
+
if (isNative) return null;
|
|
347
|
+
return /* @__PURE__ */ jsx2("style", { id: RESET_STYLE_ID, children: BASE_RESET });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/styled.tsx
|
|
351
|
+
import React2, {
|
|
352
|
+
forwardRef,
|
|
353
|
+
useState as useState2,
|
|
354
|
+
useCallback as useCallback2,
|
|
355
|
+
useMemo as useMemo4
|
|
356
|
+
} from "react";
|
|
357
|
+
function styled(Component, baseClasses = "") {
|
|
358
|
+
const Styled = forwardRef(
|
|
359
|
+
(props, ref) => {
|
|
360
|
+
const {
|
|
361
|
+
kb: extraClasses,
|
|
362
|
+
style: styleProp,
|
|
363
|
+
onPressIn,
|
|
364
|
+
onPressOut,
|
|
365
|
+
onPointerDown,
|
|
366
|
+
onPointerUp,
|
|
367
|
+
onPointerLeave,
|
|
368
|
+
onPointerCancel,
|
|
369
|
+
onMouseEnter,
|
|
370
|
+
onMouseLeave,
|
|
371
|
+
onFocus,
|
|
372
|
+
onBlur,
|
|
373
|
+
...rest
|
|
374
|
+
} = props;
|
|
375
|
+
const disabled = !!props.disabled;
|
|
376
|
+
const checked = !!props.checked;
|
|
377
|
+
const { config } = useTheme();
|
|
378
|
+
const isWebPlatform = getEffectiveIsWeb();
|
|
379
|
+
const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
|
|
380
|
+
const [pressed, setPressed] = useState2(false);
|
|
381
|
+
const [hovered, setHovered] = useState2(false);
|
|
382
|
+
const [focused, setFocused] = useState2(false);
|
|
383
|
+
const handlePressIn = useCallback2(chain(onPressIn, () => {
|
|
384
|
+
if (!isWebPlatform) setPressed(true);
|
|
385
|
+
}), [onPressIn, isWebPlatform]);
|
|
386
|
+
const handlePressOut = useCallback2(chain(onPressOut, () => {
|
|
387
|
+
if (!isWebPlatform) setPressed(false);
|
|
388
|
+
}), [onPressOut, isWebPlatform]);
|
|
389
|
+
const handlePointerDown = useCallback2(chain(onPointerDown, () => {
|
|
390
|
+
if (!isWebPlatform) setPressed(true);
|
|
391
|
+
}), [onPointerDown, isWebPlatform]);
|
|
392
|
+
const handlePointerUp = useCallback2(chain(onPointerUp, () => {
|
|
393
|
+
if (!isWebPlatform) setPressed(false);
|
|
394
|
+
}), [onPointerUp, isWebPlatform]);
|
|
395
|
+
const handlePointerLeave = useCallback2(chain(onPointerLeave, () => {
|
|
396
|
+
if (!isWebPlatform) setPressed(false);
|
|
397
|
+
}), [onPointerLeave, isWebPlatform]);
|
|
398
|
+
const handlePointerCancel = useCallback2(chain(onPointerCancel, () => {
|
|
399
|
+
if (!isWebPlatform) setPressed(false);
|
|
400
|
+
}), [onPointerCancel, isWebPlatform]);
|
|
401
|
+
const handleMouseEnter = useCallback2(chain(onMouseEnter, () => {
|
|
402
|
+
if (!isWebPlatform) setHovered(true);
|
|
403
|
+
}), [onMouseEnter, isWebPlatform]);
|
|
404
|
+
const handleMouseLeave = useCallback2(chain(onMouseLeave, () => {
|
|
405
|
+
if (!isWebPlatform) setHovered(false);
|
|
406
|
+
}), [onMouseLeave, isWebPlatform]);
|
|
407
|
+
const handleFocus = useCallback2(chain(onFocus, () => {
|
|
408
|
+
if (!isWebPlatform) setFocused(true);
|
|
409
|
+
}), [onFocus, isWebPlatform]);
|
|
410
|
+
const handleBlur = useCallback2(chain(onBlur, () => {
|
|
411
|
+
if (!isWebPlatform) setFocused(false);
|
|
412
|
+
}), [onBlur, isWebPlatform]);
|
|
413
|
+
const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
|
|
414
|
+
const firstPassResolved = useMemo4(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
|
|
415
|
+
const webTag = isWebPlatform ? getWebTag(Component, rest) : null;
|
|
416
|
+
const impliedClasses = getImpliedRNClasses(webTag, firstPassResolved.base);
|
|
417
|
+
const finalClassStr = impliedClasses ? `${combined} ${impliedClasses}` : combined;
|
|
418
|
+
const resolved = useMemo4(() => resolve(finalClassStr, config.theme, config.darkMode), [finalClassStr, config.theme, config.darkMode]);
|
|
419
|
+
const hasInteractive = useMemo4(() => hasInteractiveBuckets(resolved), [resolved]);
|
|
420
|
+
const needsWidth = hasResponsiveBuckets(resolved);
|
|
421
|
+
const width = useConditionalWidth(needsWidth && !isWebPlatform);
|
|
422
|
+
const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
|
|
423
|
+
const screens = getGlobalScreens();
|
|
424
|
+
const computedStyle = useMemo4(
|
|
425
|
+
() => isWebPlatform ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
|
|
426
|
+
[resolved, isDark, pressed, hovered, focused, disabled, checked, width, screens]
|
|
427
|
+
// eslint-disable-line react-hooks/exhaustive-deps
|
|
428
|
+
);
|
|
429
|
+
const finalStyle = isWebPlatform ? (Array.isArray(styleProp) ? Object.assign({}, ...styleProp) : styleProp) ?? void 0 : composeNativeStyle(computedStyle, styleProp);
|
|
430
|
+
const effectiveComponent = webTag ?? Component;
|
|
431
|
+
const componentName2 = Component.displayName ?? Component.name ?? "";
|
|
432
|
+
const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
|
|
433
|
+
return React2.createElement(effectiveComponent, {
|
|
434
|
+
ref,
|
|
435
|
+
...effectiveRest,
|
|
436
|
+
style: finalStyle,
|
|
437
|
+
// className lets injected CSS rules (group-hover:, before:, print:, etc.) match the element.
|
|
438
|
+
...isWebPlatform && finalClassStr ? { className: normalizeClassString(finalClassStr) } : {},
|
|
439
|
+
// Only attach state-tracking handlers when interactive modifiers are present.
|
|
440
|
+
// Always forward user-provided handlers to avoid silently swallowing them.
|
|
441
|
+
// Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
|
|
442
|
+
// and onPressIn/onPressOut must stay excluded there as well — SSR is exactly where
|
|
443
|
+
// getWebTag() above is skipped (it's isWeb-gated), so effectiveComponent may still be
|
|
444
|
+
// a non-string RN-style reference server-side, and this used to forward onPressIn/
|
|
445
|
+
// onPressOut to it unconditionally in that case (isWeb ? ... : ...'s false branch
|
|
446
|
+
// covered both native AND SSR). onPressIn/onPressOut are RN-only prop names — a
|
|
447
|
+
// non-string effectiveComponent on the web/SSR side is just as likely to be an
|
|
448
|
+
// ordinary web component (React Router's <Link>, Next.js's <Link>, any custom
|
|
449
|
+
// wrapper) as an actual react-native-web primitive, and "not a string" alone isn't a
|
|
450
|
+
// reliable signal either way. Forwarding them made React DOM warn "Unknown event
|
|
451
|
+
// handler property" the moment any such component got an interactive modifier
|
|
452
|
+
// (hover:, active:, …), which is the common case, not the exception.
|
|
453
|
+
...!isNative ? hasInteractive ? {
|
|
454
|
+
onPointerDown: handlePointerDown,
|
|
455
|
+
onPointerUp: handlePointerUp,
|
|
456
|
+
onPointerLeave: handlePointerLeave,
|
|
457
|
+
onPointerCancel: handlePointerCancel
|
|
458
|
+
} : {
|
|
459
|
+
onPointerDown,
|
|
460
|
+
onPointerUp,
|
|
461
|
+
onPointerLeave,
|
|
462
|
+
onPointerCancel
|
|
463
|
+
} : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
|
|
464
|
+
onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
|
|
465
|
+
onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
|
|
466
|
+
onFocus: hasInteractive ? handleFocus : onFocus,
|
|
467
|
+
onBlur: hasInteractive ? handleBlur : onBlur
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
const componentName = Component.displayName ?? Component.name ?? "Component";
|
|
472
|
+
Styled.displayName = `Styled(${componentName})`;
|
|
473
|
+
return Styled;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/useStyles.ts
|
|
477
|
+
import { useMemo as useMemo5 } from "react";
|
|
478
|
+
function useStyles(classString, state = {}) {
|
|
479
|
+
const { isDark, config } = useTheme();
|
|
480
|
+
const width = useGlobalWidth();
|
|
481
|
+
const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
|
|
482
|
+
return useMemo5(() => {
|
|
483
|
+
const resolved = resolve(normalised, config.theme, config.darkMode);
|
|
484
|
+
const breakpoints = getActiveBreakpoints(width);
|
|
485
|
+
return flatten(resolved, isDark, state, breakpoints);
|
|
486
|
+
}, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
|
|
487
|
+
}
|
|
488
|
+
function useResolvedStyle(classString) {
|
|
489
|
+
const { config } = useTheme();
|
|
490
|
+
const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
|
|
491
|
+
return useMemo5(
|
|
492
|
+
() => resolve(normalised, config.theme, config.darkMode),
|
|
493
|
+
[normalised, config]
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/useBreakpoint.ts
|
|
498
|
+
import { useMemo as useMemo6 } from "react";
|
|
499
|
+
function toNumericScreens(screens) {
|
|
500
|
+
const out = {};
|
|
501
|
+
for (const [name, v] of Object.entries(screens)) {
|
|
502
|
+
const n = typeof v === "number" ? v : parseInt(String(v), 10);
|
|
503
|
+
if (!Number.isNaN(n)) out[name] = n;
|
|
504
|
+
}
|
|
505
|
+
return out;
|
|
506
|
+
}
|
|
507
|
+
function useBreakpoint() {
|
|
508
|
+
const { config } = useTheme();
|
|
509
|
+
const width = useGlobalWidth();
|
|
510
|
+
const screens = config.theme.screens;
|
|
511
|
+
const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
|
|
512
|
+
return useMemo6(() => {
|
|
513
|
+
const active = getActiveBreakpoints(width, numericScreens);
|
|
514
|
+
let best = null;
|
|
515
|
+
let bestMinW = -Infinity;
|
|
516
|
+
for (const name of active) {
|
|
517
|
+
const minW = numericScreens[name];
|
|
518
|
+
if (minW > bestMinW) {
|
|
519
|
+
bestMinW = minW;
|
|
520
|
+
best = name;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return best ?? "xs";
|
|
524
|
+
}, [numericScreens, width]);
|
|
525
|
+
}
|
|
526
|
+
function useResponsive() {
|
|
527
|
+
const { config } = useTheme();
|
|
528
|
+
const width = useGlobalWidth();
|
|
529
|
+
const screens = config.theme.screens;
|
|
530
|
+
const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
|
|
531
|
+
return useMemo6(() => {
|
|
532
|
+
const active = getActiveBreakpoints(width, numericScreens);
|
|
533
|
+
const result = {};
|
|
534
|
+
for (const name of Object.keys(numericScreens)) {
|
|
535
|
+
result[name] = active.has(name);
|
|
536
|
+
}
|
|
537
|
+
return result;
|
|
538
|
+
}, [numericScreens, width]);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/kb.ts
|
|
542
|
+
function kb(classString, isDark = false) {
|
|
543
|
+
const config = getConfig();
|
|
544
|
+
const resolved = resolve(classString, config.theme, config.darkMode);
|
|
545
|
+
if (getEffectiveIsWeb()) {
|
|
546
|
+
return classString;
|
|
547
|
+
}
|
|
548
|
+
return flatten(resolved, isDark);
|
|
549
|
+
}
|
|
550
|
+
function cx(...classes) {
|
|
551
|
+
return classes.filter(Boolean).join(" ");
|
|
552
|
+
}
|
|
553
|
+
export {
|
|
554
|
+
InteractiveWrapper,
|
|
555
|
+
KbachReset,
|
|
556
|
+
ThemeContext,
|
|
557
|
+
ThemeProvider,
|
|
558
|
+
buildConfig,
|
|
559
|
+
clearCache,
|
|
560
|
+
cx,
|
|
561
|
+
defaultColors,
|
|
562
|
+
defaultTheme,
|
|
563
|
+
disableRuntimeCSS,
|
|
564
|
+
flatten,
|
|
565
|
+
generateKbachTypesDts,
|
|
566
|
+
getConfig,
|
|
567
|
+
initConfig,
|
|
568
|
+
kb,
|
|
569
|
+
normalizeClassString,
|
|
570
|
+
parseClass,
|
|
571
|
+
parseClasses,
|
|
572
|
+
registerWebElement,
|
|
573
|
+
resolve,
|
|
574
|
+
setResolveTarget,
|
|
575
|
+
splitClassTokens,
|
|
576
|
+
styled,
|
|
577
|
+
updateConfig,
|
|
578
|
+
useBreakpoint,
|
|
579
|
+
useColors,
|
|
580
|
+
useGlobalDarkMode,
|
|
581
|
+
useIsDark,
|
|
582
|
+
useResolvedStyle,
|
|
583
|
+
useResponsive,
|
|
584
|
+
useSpacing,
|
|
585
|
+
useStyles,
|
|
586
|
+
useTheme,
|
|
587
|
+
wrapColors,
|
|
588
|
+
wrapSpacing
|
|
589
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
export { Fragment } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @kbach/ui/jsx-dev-runtime
|
|
6
|
+
*
|
|
7
|
+
* Development variant of the custom JSX runtime.
|
|
8
|
+
* Babel uses jsxDEV (instead of jsx/jsxs) in dev builds.
|
|
9
|
+
*
|
|
10
|
+
* We intercept `className`/`kb` props exactly like the production runtime, but
|
|
11
|
+
* forward `_source` to React's own jsxDEV so React DevTools can display the
|
|
12
|
+
* correct file + line number for every element.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare function jsxDEV(type: unknown, props: Record<string, unknown> | null, key?: string, isStaticChildren?: boolean, source?: {
|
|
16
|
+
fileName: string;
|
|
17
|
+
lineNumber: number;
|
|
18
|
+
columnNumber: number;
|
|
19
|
+
}, self?: unknown): ReactElement;
|
|
20
|
+
|
|
21
|
+
export { jsxDEV };
|