@kbach/react 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.
@@ -1,2 +1,3 @@
1
+ 'use client';
1
2
  'use strict';
2
3
  module.exports = require('./dist/jsx-dev-runtime.js');
package/jsx-runtime.js CHANGED
@@ -1,2 +1,3 @@
1
+ 'use client';
1
2
  'use strict';
2
3
  module.exports = require('./dist/jsx-runtime.js');
package/kbach-react.md CHANGED
@@ -14,6 +14,7 @@ Package: `@kbach/react`
14
14
  ```
15
15
 
16
16
  ### vite.config.ts
17
+ Requires `vite` and `@vitejs/plugin-react` as dev dependencies — not installed by `@kbach/react` itself: `npm install -D vite @vitejs/plugin-react`.
17
18
  ```ts
18
19
  import { defineConfig } from 'vite';
19
20
  import react from '@vitejs/plugin-react';
@@ -45,6 +46,12 @@ import { ThemeProvider } from '@kbach/react';
45
46
  <ThemeProvider defaultMode="system"><App /></ThemeProvider>
46
47
  ```
47
48
 
49
+ ### Next.js
50
+ tsconfig `jsxImportSource` setup above applies as-is. No Vite plugin for Next.js (webpack/Turbopack) — falls back to runtime CSS injection, which only runs client-side, so expect a brief flash of unstyled content on first paint before hydration. `@kbach/react`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, and `<ThemeProvider>` directly, with no manual client wrapper needed.
51
+
52
+ ### React Router
53
+ Framework mode (v7, SSR) builds with Vite, so the setup above works unchanged — default scan dirs include `app/`. Library mode (client-only) needs no special handling beyond the standard Vite setup.
54
+
48
55
  ---
49
56
 
50
57
  ## Vite Plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",
@@ -39,9 +39,9 @@
39
39
  }
40
40
  },
41
41
  "scripts": {
42
- "build": "tsup src/index.ts src/jsx-runtime.tsx src/jsx-dev-runtime.tsx src/vite-plugin.ts --format esm,cjs --dts --clean --external vite",
42
+ "build": "tsup",
43
43
  "postbuild": "node -e \"const f=require('fs');f.copyFileSync('src/vite-plugin-client.d.ts','dist/vite-plugin-client.d.ts');f.copyFileSync('src/vite-plugin-client.d.ts','dist/vite-plugin-client.d.mts')\"",
44
- "dev": "tsup src/index.ts src/jsx-runtime.tsx src/jsx-dev-runtime.tsx src/vite-plugin.ts --format esm,cjs --dts --watch --external vite",
44
+ "dev": "tsup --watch",
45
45
  "test": "vitest run --passWithNoTests",
46
46
  "lint": "tsc --noEmit",
47
47
  "postinstall": "node scripts/postinstall.js"
@@ -1,524 +0,0 @@
1
- import {
2
- flatten,
3
- getActiveBreakpoints,
4
- getGlobalWidth,
5
- getInteractiveModifiers,
6
- getResponsiveModifiers,
7
- isNative,
8
- isWeb,
9
- subscribeGlobalWidth
10
- } from "./chunk-6NED62DB.mjs";
11
-
12
- // src/core/darkModeStore.ts
13
- var KEY = "__kbach_dark_store__";
14
- function getStore() {
15
- const g = globalThis;
16
- if (!g[KEY]) {
17
- g[KEY] = { isDark: false, subscribers: /* @__PURE__ */ new Set() };
18
- }
19
- return g[KEY];
20
- }
21
- function syncGlobalDarkMode(isDark) {
22
- getStore().isDark = isDark;
23
- }
24
- function setGlobalDarkMode(isDark) {
25
- const store = getStore();
26
- if (store.isDark === isDark) return;
27
- store.isDark = isDark;
28
- for (const sub of store.subscribers) sub();
29
- }
30
- function getGlobalDarkMode() {
31
- return getStore().isDark;
32
- }
33
- function subscribeGlobalDarkMode(callback) {
34
- const store = getStore();
35
- store.subscribers.add(callback);
36
- return () => store.subscribers.delete(callback);
37
- }
38
-
39
- // src/useGlobalDarkMode.ts
40
- import React from "react";
41
- var useSyncExternalStore = React.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, _getServerSnapshot) {
42
- const [, forceUpdate] = React.useReducer((n) => n + 1, 0);
43
- React.useEffect(() => subscribe(forceUpdate), [subscribe]);
44
- return getSnapshot();
45
- };
46
- function useGlobalDarkMode() {
47
- return useSyncExternalStore(
48
- subscribeGlobalDarkMode,
49
- getGlobalDarkMode,
50
- () => false
51
- // SSR: default light
52
- );
53
- }
54
-
55
- // src/web-substitute.ts
56
- var _rnToHtml = {
57
- View: "div",
58
- SafeAreaView: "div",
59
- KeyboardAvoidingView: "div",
60
- ScrollView: "div",
61
- VirtualizedList: "div",
62
- FlatList: "div",
63
- SectionList: "div",
64
- Text: "span",
65
- TextInput: "input",
66
- Image: "img",
67
- ImageBackground: "div",
68
- Pressable: "div",
69
- TouchableOpacity: "div",
70
- TouchableHighlight: "div",
71
- TouchableWithoutFeedback: "div",
72
- TouchableNativeFeedback: "div"
73
- };
74
- var _userMap = /* @__PURE__ */ new Map();
75
- var _cache = /* @__PURE__ */ new WeakMap();
76
- function registerWebElement(rnComponent, htmlTag) {
77
- if (typeof rnComponent !== "object" && typeof rnComponent !== "function") return;
78
- _userMap.set(rnComponent, htmlTag);
79
- _cache.delete(rnComponent);
80
- }
81
- function getWebTag(type, props) {
82
- if (typeof type === "string") return null;
83
- if (type === null || typeof type !== "function" && typeof type !== "object") return null;
84
- const obj = type;
85
- if (_userMap.has(obj)) return _userMap.get(obj);
86
- const name = obj.displayName ?? obj.name;
87
- if (!name) {
88
- _cache.set(obj, null);
89
- return null;
90
- }
91
- if (name === "TextInput") {
92
- const multiline = !!props && (props.multiline === true || Number(props.numberOfLines) > 1);
93
- return multiline ? "textarea" : "input";
94
- }
95
- if (_cache.has(obj)) return _cache.get(obj);
96
- const tag = _rnToHtml[name] ?? null;
97
- _cache.set(obj, tag);
98
- return tag;
99
- }
100
- var _rnOnlyProps = /* @__PURE__ */ new Set([
101
- // Interaction
102
- "onLongPress",
103
- "delayLongPress",
104
- "activeOpacity",
105
- "underlayColor",
106
- "hitSlop",
107
- "pressRetentionOffset",
108
- "android_ripple",
109
- "android_disableSound",
110
- "onHoverIn",
111
- "onHoverOut",
112
- "onHoverStart",
113
- "onHoverEnd",
114
- // Layout event
115
- "onLayout",
116
- // Accessibility
117
- "accessible",
118
- "accessibilityState",
119
- "accessibilityLiveRegion",
120
- "importantForAccessibility",
121
- // Platform
122
- "nativeID",
123
- "collapsable",
124
- "needsOffscreenAlphaCompositing",
125
- "renderToHardwareTextureAndroid",
126
- "shouldRasterizeIOS",
127
- "focusable",
128
- "hasTVPreferredFocus",
129
- // 'pointerEvents' is handled in transformToWebProps (mapped to CSS style)
130
- // Text
131
- "selectable",
132
- "allowFontScaling",
133
- "adjustsFontSizeToFit",
134
- "minimumFontScale",
135
- "ellipsizeMode",
136
- "numberOfLines",
137
- "onTextLayout",
138
- "textBreakStrategy",
139
- "lineBreakStrategyIOS",
140
- // TextInput
141
- "multiline",
142
- "resizeMode",
143
- // handled separately
144
- "blurOnSubmit",
145
- "clearButtonMode",
146
- "clearTextOnFocus",
147
- "enablesReturnKeyAutomatically",
148
- "returnKeyType",
149
- "spellCheck",
150
- // ScrollView
151
- "scrollEnabled",
152
- "showsVerticalScrollIndicator",
153
- "showsHorizontalScrollIndicator",
154
- "contentContainerStyle",
155
- // 'horizontal' is handled in transformToWebProps (converted to CSS overflow-x).
156
- "keyboardShouldPersistTaps",
157
- "keyboardDismissMode",
158
- "pagingEnabled",
159
- "scrollEventThrottle",
160
- "decelerationRate",
161
- "bounces",
162
- "alwaysBounceHorizontal",
163
- "alwaysBounceVertical",
164
- "snapToAlignment",
165
- "snapToInterval",
166
- "snapToOffsets",
167
- "removeClippedSubviews",
168
- "overScrollMode",
169
- "stickyHeaderIndices",
170
- "invertStickyHeaders",
171
- "onScrollBeginDrag",
172
- "onScrollEndDrag",
173
- "onMomentumScrollBegin",
174
- "onMomentumScrollEnd",
175
- "contentInset",
176
- "contentInsetAdjustmentBehavior",
177
- "automaticallyAdjustContentInsets",
178
- "automaticallyAdjustsScrollIndicatorInsets",
179
- // expo-image
180
- "contentPosition",
181
- "cachePolicy",
182
- "recyclingKey",
183
- "blurRadius",
184
- "fadeDuration",
185
- "responsivePolicy",
186
- "tintColor",
187
- "allowDownscaling",
188
- "placeholderContentFit",
189
- // FlatList / SectionList
190
- "data",
191
- "renderItem",
192
- "keyExtractor",
193
- "getItemLayout",
194
- "initialScrollIndex",
195
- "initialNumToRender",
196
- "maxToRenderPerBatch",
197
- "windowSize",
198
- "updateCellsBatchingPeriod",
199
- "onEndReached",
200
- "onEndReachedThreshold",
201
- "ListHeaderComponent",
202
- "ListFooterComponent",
203
- "ListEmptyComponent",
204
- "ListHeaderComponentStyle",
205
- "ListFooterComponentStyle",
206
- "ItemSeparatorComponent",
207
- "SectionSeparatorComponent",
208
- "inverted",
209
- "getItem",
210
- "getItemCount"
211
- ]);
212
- var _pressableNames = /* @__PURE__ */ new Set([
213
- "Pressable",
214
- "TouchableOpacity",
215
- "TouchableHighlight",
216
- "TouchableWithoutFeedback",
217
- "TouchableNativeFeedback"
218
- ]);
219
- var _keyboardTypeMap = {
220
- "numeric": "number",
221
- "number-pad": "number",
222
- "decimal-pad": "decimal",
223
- "email-address": "email",
224
- "phone-pad": "tel",
225
- "url": "url"
226
- };
227
- var _resizeModeMap = {
228
- "contain": "contain",
229
- "cover": "cover",
230
- "stretch": "fill",
231
- "center": "none",
232
- "repeat": "none"
233
- };
234
- function transformToWebProps(originalName, tag, props) {
235
- const out = {};
236
- const isPressable = _pressableNames.has(originalName);
237
- const isTextInput = originalName === "TextInput";
238
- const isImage = originalName === "Image" || originalName === "ImageBackground";
239
- const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
240
- let pendingStyle = null;
241
- for (const [k, v] of Object.entries(props)) {
242
- if (_rnOnlyProps.has(k)) continue;
243
- if (k === "onPress") {
244
- if (!("onClick" in props)) out.onClick = v;
245
- continue;
246
- }
247
- if (k === "accessibilityLabel") {
248
- if (out["aria-label"] == null) out["aria-label"] = v;
249
- continue;
250
- }
251
- if (k === "accessibilityRole") {
252
- if (out.role == null) out.role = v;
253
- continue;
254
- }
255
- if (k === "testID") {
256
- if (out["data-testid"] == null) out["data-testid"] = v;
257
- continue;
258
- }
259
- if (isTextInput) {
260
- if (k === "onChangeText") {
261
- if (!("onChange" in props)) out.onChange = (e) => v(e.target.value);
262
- continue;
263
- }
264
- if (k === "secureTextEntry") {
265
- if (v && !("type" in props)) out.type = "password";
266
- continue;
267
- }
268
- if (k === "keyboardType") {
269
- if (!("type" in props) && !("secureTextEntry" in props)) {
270
- const mapped = _keyboardTypeMap[v];
271
- if (mapped) out.type = mapped;
272
- }
273
- continue;
274
- }
275
- if (k === "editable") {
276
- if (v === false) out.readOnly = true;
277
- continue;
278
- }
279
- if (k === "maxLength") {
280
- out.maxLength = v;
281
- continue;
282
- }
283
- }
284
- if (isScrollable && k === "horizontal") {
285
- if (v) pendingStyle = { display: "flex", flexDirection: "row", overflowX: "auto" };
286
- continue;
287
- }
288
- if (k === "pointerEvents") {
289
- if (v === "none" || v === "auto") pendingStyle = { ...pendingStyle ?? {}, pointerEvents: v };
290
- continue;
291
- }
292
- if (isImage) {
293
- if (k === "source") {
294
- if (typeof v === "string") {
295
- out.src = v;
296
- } else if (v && typeof v === "object" && "uri" in v) {
297
- out.src = v.uri;
298
- if (v.headers) out["crossOrigin"] = "anonymous";
299
- }
300
- continue;
301
- }
302
- if (k === "resizeMode") {
303
- const existing = out.style ?? {};
304
- out.style = { ...existing, objectFit: _resizeModeMap[v] ?? "cover" };
305
- continue;
306
- }
307
- if (k === "contentFit") {
308
- const existing = out.style ?? {};
309
- out.style = { ...existing, objectFit: v };
310
- continue;
311
- }
312
- if (k === "defaultSource") continue;
313
- }
314
- if (k === "style") {
315
- if (Array.isArray(v)) {
316
- out.style = Object.assign({}, ...v.filter(Boolean));
317
- } else if (v != null) {
318
- out.style = v;
319
- }
320
- continue;
321
- }
322
- out[k] = v;
323
- }
324
- if (pendingStyle) {
325
- out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
326
- }
327
- if (isPressable && !out.role) out.role = "button";
328
- if (isImage && tag === "img" && out.alt == null) out.alt = "";
329
- return out;
330
- }
331
-
332
- // src/InteractiveWrapper.tsx
333
- import React3, { forwardRef, useState, useCallback, useMemo } from "react";
334
-
335
- // src/useGlobalWidth.ts
336
- import React2 from "react";
337
- var useSyncExternalStore2 = React2.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
338
- const [, forceUpdate] = React2.useReducer((n) => n + 1, 0);
339
- React2.useEffect(() => subscribe(forceUpdate), [subscribe]);
340
- return getSnapshot();
341
- };
342
- var NOOP_SUB = (_) => () => {
343
- };
344
- var ZERO_SNAP = () => 0;
345
- var EMPTY_BREAKPOINTS = /* @__PURE__ */ new Set();
346
- var _webListeners = null;
347
- var _webRaf = 0;
348
- function _webResizeHandler() {
349
- cancelAnimationFrame(_webRaf);
350
- _webRaf = requestAnimationFrame(() => {
351
- if (_webListeners) for (const cb of _webListeners) cb();
352
- });
353
- }
354
- function subscribeWebWidth(cb) {
355
- if (typeof window === "undefined") return () => {
356
- };
357
- if (!_webListeners) {
358
- _webListeners = /* @__PURE__ */ new Set();
359
- window.addEventListener("resize", _webResizeHandler);
360
- }
361
- _webListeners.add(cb);
362
- return () => {
363
- _webListeners.delete(cb);
364
- if (_webListeners.size === 0) {
365
- window.removeEventListener("resize", _webResizeHandler);
366
- cancelAnimationFrame(_webRaf);
367
- _webListeners = null;
368
- }
369
- };
370
- }
371
- function getWebWidth() {
372
- return typeof window !== "undefined" ? window.innerWidth : 0;
373
- }
374
- function useGlobalWidth() {
375
- return useSyncExternalStore2(
376
- isWeb ? subscribeWebWidth : subscribeGlobalWidth,
377
- isWeb ? getWebWidth : getGlobalWidth,
378
- ZERO_SNAP
379
- );
380
- }
381
- function useConditionalWidth(active) {
382
- return useSyncExternalStore2(
383
- active ? isWeb ? subscribeWebWidth : subscribeGlobalWidth : NOOP_SUB,
384
- active ? isWeb ? getWebWidth : getGlobalWidth : ZERO_SNAP,
385
- ZERO_SNAP
386
- );
387
- }
388
-
389
- // src/shared-utils.ts
390
- function hasResponsiveBuckets(resolved) {
391
- const responsiveMods = getResponsiveModifiers();
392
- for (const key of Object.keys(resolved)) {
393
- if (key === "base") continue;
394
- for (const mod of key.split(":")) {
395
- if (responsiveMods.has(mod)) return true;
396
- }
397
- }
398
- return false;
399
- }
400
- function hasInteractiveBuckets(resolved) {
401
- const interactiveMods = getInteractiveModifiers();
402
- for (const key of Object.keys(resolved)) {
403
- if (key === "base") continue;
404
- for (const mod of key.split(":")) {
405
- if (interactiveMods.has(mod)) return true;
406
- }
407
- }
408
- return false;
409
- }
410
-
411
- // src/InteractiveWrapper.tsx
412
- function chain(original, extra) {
413
- return (...args) => {
414
- original?.(...args);
415
- extra();
416
- };
417
- }
418
- var InteractiveWrapper = forwardRef(
419
- function InteractiveWrapper2({
420
- Component,
421
- resolvedStyle,
422
- className,
423
- style: styleProp,
424
- onPressIn,
425
- onPressOut,
426
- onPointerDown,
427
- onPointerUp,
428
- onPointerLeave,
429
- onMouseEnter,
430
- onMouseLeave,
431
- onFocus,
432
- onBlur,
433
- ...rest
434
- }, ref) {
435
- const isDark = useGlobalDarkMode();
436
- const needsWidth = hasResponsiveBuckets(resolvedStyle);
437
- const width = useConditionalWidth(needsWidth);
438
- const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
439
- const [pressed, setPressed] = useState(false);
440
- const [hovered, setHovered] = useState(false);
441
- const [focused, setFocused] = useState(false);
442
- const handlePressIn = useCallback(chain(onPressIn, () => setPressed(true)), [onPressIn]);
443
- const handlePressOut = useCallback(chain(onPressOut, () => setPressed(false)), [onPressOut]);
444
- const handlePointerDown = useCallback(chain(onPointerDown, () => setPressed(true)), [onPointerDown]);
445
- const handlePointerUp = useCallback(chain(onPointerUp, () => setPressed(false)), [onPointerUp]);
446
- const handlePointerLeave = useCallback(chain(onPointerLeave, () => setPressed(false)), [onPointerLeave]);
447
- const handleMouseEnter = useCallback(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
448
- const handleMouseLeave = useCallback(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
449
- const handleFocus = useCallback(chain(onFocus, () => setFocused(true)), [onFocus]);
450
- const handleBlur = useCallback(chain(onBlur, () => setFocused(false)), [onBlur]);
451
- const { children, disabled, checked, ...restForComponent } = rest;
452
- const computedStyle = useMemo(
453
- () => {
454
- if (isWeb) return {};
455
- const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
456
- delete s.__divideX;
457
- delete s.__divideY;
458
- delete s.__divideColor;
459
- delete s.__divideStyle;
460
- delete s.__keyframe;
461
- if (typeof Component !== "string") {
462
- if (s.display === "grid" || s.display === "inline-grid") delete s.display;
463
- delete s.gridTemplateColumns;
464
- delete s.gridTemplateRows;
465
- delete s.gridColumn;
466
- delete s.gridRow;
467
- delete s.gridArea;
468
- delete s.gridColumnStart;
469
- delete s.gridColumnEnd;
470
- delete s.gridRowStart;
471
- delete s.gridRowEnd;
472
- delete s.gridAutoFlow;
473
- delete s.gridAutoColumns;
474
- delete s.gridAutoRows;
475
- delete s.placeItems;
476
- delete s.placeContent;
477
- delete s.justifyItems;
478
- delete s.placeSelf;
479
- delete s.justifySelf;
480
- if (s.position === "sticky" || s.position === "fixed" || s.position === "static") {
481
- delete s.position;
482
- }
483
- }
484
- return s;
485
- },
486
- [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked]
487
- );
488
- const skipComputedInline = isWeb || !isNative && typeof Component === "string";
489
- const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
490
- const componentProps = {
491
- ref,
492
- ...restForComponent,
493
- ...disabled !== void 0 ? { disabled } : {},
494
- ...checked !== void 0 ? { checked } : {},
495
- style: finalStyle,
496
- ...!isNative && className ? { className } : {},
497
- // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
498
- // but onPressIn/onPressOut are still forwarded unchanged — react-native-web components
499
- // (and any component that accepts both prop names) depend on receiving them directly.
500
- ...!isNative ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave, onPressIn, onPressOut } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
501
- onMouseEnter: handleMouseEnter,
502
- onMouseLeave: handleMouseLeave,
503
- onFocus: handleFocus,
504
- onBlur: handleBlur
505
- };
506
- return Array.isArray(children) ? React3.createElement(Component, componentProps, ...children) : React3.createElement(Component, componentProps, children);
507
- }
508
- );
509
- InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
510
-
511
- export {
512
- syncGlobalDarkMode,
513
- setGlobalDarkMode,
514
- useGlobalDarkMode,
515
- EMPTY_BREAKPOINTS,
516
- useGlobalWidth,
517
- useConditionalWidth,
518
- hasResponsiveBuckets,
519
- hasInteractiveBuckets,
520
- registerWebElement,
521
- getWebTag,
522
- transformToWebProps,
523
- InteractiveWrapper
524
- };