@kbach/react 0.6.1 → 0.6.3

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,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  // src/core/theme.ts
2
4
  var defaultColors = {
3
5
  transparent: "transparent",
@@ -359,7 +361,10 @@ var defaultTheme = {
359
361
  "2/3": "66.666667%",
360
362
  "1/4": "25%",
361
363
  "3/4": "75%",
362
- screen: "100vh",
364
+ // Dynamic viewport unit — see the comment on the 'screen' standalone utilities
365
+ // in utilities.ts for why dvh beats vh on mobile. Used by h-screen (w-screen
366
+ // has its own standalone entry so it never reaches this spacing lookup).
367
+ screen: "100dvh",
363
368
  min: "min-content",
364
369
  max: "max-content",
365
370
  fit: "fit-content"
@@ -545,9 +550,6 @@ var _resolveTargetOverride = null;
545
550
  function setResolveTarget(target) {
546
551
  _resolveTargetOverride = target;
547
552
  }
548
- function setCSSGenMode(on) {
549
- _resolveTargetOverride = on ? "web" : null;
550
- }
551
553
  function getEffectiveIsWeb() {
552
554
  if (_resolveTargetOverride) return _resolveTargetOverride === "web";
553
555
  return isWeb || !isNative;
@@ -811,15 +813,19 @@ function buildStandalone() {
811
813
  // Display
812
814
  // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
813
815
  // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
814
- // `grid`, `inline-*`, `contents`, `flow-root` have no native equivalent.
816
+ // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
817
+ // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
818
+ // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
819
+ // time, but without it these utilities couldn't re-show a `hidden` element on
820
+ // native either (null → no style applied → still display:'none').
815
821
  flex: { display: "flex" },
816
822
  block: web ? { display: "block" } : null,
817
- "inline-block": web ? { display: "inline-block" } : null,
818
- inline: web ? { display: "inline" } : null,
823
+ "inline-block": { display: web ? "inline-block" : "flex" },
824
+ inline: { display: web ? "inline" : "flex" },
819
825
  grid: web ? { display: "grid" } : null,
820
826
  grd: web ? { display: "grid" } : null,
821
827
  "inline-flex": { display: web ? "inline-flex" : "flex" },
822
- "inline-grid": web ? { display: "inline-grid" } : null,
828
+ "inline-grid": { display: web ? "inline-grid" : "flex" },
823
829
  hidden: { display: "none" },
824
830
  contents: web ? { display: "contents" } : null,
825
831
  "flow-root": web ? { display: "flow-root" } : null,
@@ -1078,12 +1084,17 @@ function buildStandalone() {
1078
1084
  "text-nowrap": web ? { textWrap: "nowrap" } : null,
1079
1085
  "text-balance": web ? { textWrap: "balance" } : null,
1080
1086
  "text-pretty": web ? { textWrap: "pretty" } : null,
1081
- // Screen sizing (vw values are web-only; vh values work cross-platform via spacing scale for h-*)
1082
- "w-screen": web ? { width: "100vw" } : null,
1083
- "min-h-screen": { minHeight: "100vh" },
1084
- "max-h-screen": { maxHeight: "100vh" },
1085
- "min-w-screen": web ? { minWidth: "100vw" } : null,
1086
- "max-w-screen": web ? { maxWidth: "100vw" } : null,
1087
+ // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1088
+ // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1089
+ // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1090
+ // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1091
+ // is unchanged since there's no dynamic chrome to account for.
1092
+ // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1093
+ "w-screen": web ? { width: "100dvw" } : null,
1094
+ "min-h-screen": { minHeight: "100dvh" },
1095
+ "max-h-screen": { maxHeight: "100dvh" },
1096
+ "min-w-screen": web ? { minWidth: "100dvw" } : null,
1097
+ "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1087
1098
  // max-w named container sizes (mirrors Tailwind's container scale)
1088
1099
  "max-w-none": { maxWidth: "none" },
1089
1100
  "max-w-xs": { maxWidth: 320 },
@@ -2209,34 +2220,50 @@ var RESOLVERS = {
2209
2220
  const ms = isArbitrary ? value : `${value}ms`;
2210
2221
  return { transitionDelay: ms };
2211
2222
  },
2212
- // ── Ring (web-only: box-shadow outline ring) ──────────────────────────────
2223
+ // ── Ring ───────────────────────────────────────────────────────────────────
2224
+ // Web: box-shadow outline ring (doesn't affect layout).
2225
+ // Native: React Native has no box-shadow, so this falls back to borderWidth/
2226
+ // borderColor — the closest visual approximation (used by other RN Tailwind-
2227
+ // likes for the same reason). Unlike the web ring, this DOES affect layout,
2228
+ // and it shares its properties with the `border` utility — combining
2229
+ // `border-*` and `ring-*` on the same native element means whichever class
2230
+ // comes later wins, since both ultimately set borderWidth/borderColor.
2213
2231
  ring: ({ value, isArbitrary }, { colors }) => {
2214
- if (!getEffectiveIsWeb()) return null;
2232
+ const onWeb = getEffectiveIsWeb();
2215
2233
  const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
2216
2234
  const DEFAULT_WIDTH = 3;
2235
+ const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
2217
2236
  if (!value) {
2218
- return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
2237
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2219
2238
  }
2220
2239
  if (value === "inset") {
2221
- return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
2240
+ return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2222
2241
  }
2223
2242
  const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2224
2243
  if (!isArbitrary && value in widthTokens) {
2225
2244
  const w = widthTokens[value];
2226
- return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
2245
+ return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
2227
2246
  }
2228
2247
  if (isArbitrary) {
2229
2248
  const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
2230
2249
  if (numMatch) {
2231
2250
  const unit = numMatch[2] ?? "px";
2232
- return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
2251
+ if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
2252
+ return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
2233
2253
  }
2234
- return { boxShadow: value.replace(/_/g, " ") };
2254
+ return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
2235
2255
  }
2236
2256
  const color = resolveColor(value, colors, false);
2237
- if (color) return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` };
2257
+ if (color) {
2258
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
2259
+ }
2238
2260
  return null;
2239
2261
  },
2262
+ // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
2263
+ // create a gap between the element and the ring. There's no native
2264
+ // equivalent to approximate that with (a border can't create a gap outside
2265
+ // its own element without an extra wrapper view), so this stays a no-op
2266
+ // on native rather than rendering a misleading half-translation.
2240
2267
  "ring-offset": ({ value, isArbitrary }, _theme) => {
2241
2268
  if (!getEffectiveIsWeb()) return null;
2242
2269
  const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
@@ -2815,16 +2842,6 @@ function injectClassRule(cls, bucketKey, styles, darkMode, important) {
2815
2842
  const cssRules = buildClassCSSRules(cls, bucketKey, styles, darkMode, important, getGlobalScreens());
2816
2843
  for (const r of cssRules) injectRule(r);
2817
2844
  }
2818
- function generateClassCSS(classString, theme, darkMode = "attribute", screens = {}) {
2819
- const rules = [];
2820
- for (const parsed of parseClasses(classString)) {
2821
- const styles = resolveUtility(parsed, theme);
2822
- if (!styles) continue;
2823
- const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
2824
- rules.push(...buildClassCSSRules(parsed.original, bucketKey, styles, darkMode, parsed.important, screens));
2825
- }
2826
- return rules.join("\n");
2827
- }
2828
2845
  function resolve(classString, theme, darkMode = "attribute") {
2829
2846
  const cache = getThemeCache(theme);
2830
2847
  const cacheKey = `${classString}::${darkMode}`;
@@ -3101,13 +3118,511 @@ var customVariants = new Proxy(
3101
3118
  }
3102
3119
  );
3103
3120
 
3121
+ // src/core/darkModeStore.ts
3122
+ var KEY = "__kbach_dark_store__";
3123
+ function getStore2() {
3124
+ const g = globalThis;
3125
+ if (!g[KEY]) {
3126
+ g[KEY] = { isDark: false, subscribers: /* @__PURE__ */ new Set() };
3127
+ }
3128
+ return g[KEY];
3129
+ }
3130
+ function syncGlobalDarkMode(isDark) {
3131
+ getStore2().isDark = isDark;
3132
+ }
3133
+ function setGlobalDarkMode(isDark) {
3134
+ const store = getStore2();
3135
+ if (store.isDark === isDark) return;
3136
+ store.isDark = isDark;
3137
+ for (const sub of store.subscribers) sub();
3138
+ }
3139
+ function getGlobalDarkMode() {
3140
+ return getStore2().isDark;
3141
+ }
3142
+ function subscribeGlobalDarkMode(callback) {
3143
+ const store = getStore2();
3144
+ store.subscribers.add(callback);
3145
+ return () => store.subscribers.delete(callback);
3146
+ }
3147
+
3148
+ // src/useSyncExternalStoreShim.ts
3149
+ import React from "react";
3150
+ var useSyncExternalStore = React.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, _getServerSnapshot) {
3151
+ const [, forceUpdate] = React.useReducer((n) => n + 1, 0);
3152
+ React.useEffect(() => subscribe(forceUpdate), [subscribe]);
3153
+ return getSnapshot();
3154
+ };
3155
+
3156
+ // src/useGlobalDarkMode.ts
3157
+ function useGlobalDarkMode() {
3158
+ return useSyncExternalStore(
3159
+ subscribeGlobalDarkMode,
3160
+ getGlobalDarkMode,
3161
+ () => false
3162
+ // SSR: default light
3163
+ );
3164
+ }
3165
+
3166
+ // src/shared-utils.ts
3167
+ function hasResponsiveBuckets(resolved) {
3168
+ const responsiveMods = getResponsiveModifiers();
3169
+ for (const key of Object.keys(resolved)) {
3170
+ if (key === "base") continue;
3171
+ for (const mod of key.split(":")) {
3172
+ if (responsiveMods.has(mod)) return true;
3173
+ }
3174
+ }
3175
+ return false;
3176
+ }
3177
+ function hasInteractiveBuckets(resolved) {
3178
+ const interactiveMods = getInteractiveModifiers();
3179
+ for (const key of Object.keys(resolved)) {
3180
+ if (key === "base") continue;
3181
+ for (const mod of key.split(":")) {
3182
+ if (interactiveMods.has(mod)) return true;
3183
+ }
3184
+ }
3185
+ return false;
3186
+ }
3187
+ function chain(original, extra) {
3188
+ return (...args) => {
3189
+ original?.(...args);
3190
+ extra();
3191
+ };
3192
+ }
3193
+ function stripInternalMarkers(s) {
3194
+ delete s.__divideX;
3195
+ delete s.__divideY;
3196
+ delete s.__divideColor;
3197
+ delete s.__divideStyle;
3198
+ delete s.__keyframe;
3199
+ }
3200
+ function stripWebOnlyProps(s) {
3201
+ if (s.display === "grid" || s.display === "inline-grid") delete s.display;
3202
+ delete s.gridTemplateColumns;
3203
+ delete s.gridTemplateRows;
3204
+ delete s.gridColumn;
3205
+ delete s.gridRow;
3206
+ delete s.gridArea;
3207
+ delete s.gridColumnStart;
3208
+ delete s.gridColumnEnd;
3209
+ delete s.gridRowStart;
3210
+ delete s.gridRowEnd;
3211
+ delete s.gridAutoFlow;
3212
+ delete s.gridAutoColumns;
3213
+ delete s.gridAutoRows;
3214
+ delete s.placeItems;
3215
+ delete s.placeContent;
3216
+ delete s.justifyItems;
3217
+ delete s.placeSelf;
3218
+ delete s.justifySelf;
3219
+ if (s.position === "sticky" || s.position === "fixed" || s.position === "static") {
3220
+ delete s.position;
3221
+ }
3222
+ }
3223
+
3224
+ // src/web-substitute.ts
3225
+ var _rnToHtml = {
3226
+ View: "div",
3227
+ SafeAreaView: "div",
3228
+ KeyboardAvoidingView: "div",
3229
+ ScrollView: "div",
3230
+ VirtualizedList: "div",
3231
+ FlatList: "div",
3232
+ SectionList: "div",
3233
+ Text: "span",
3234
+ TextInput: "input",
3235
+ Image: "img",
3236
+ ImageBackground: "div",
3237
+ Pressable: "div",
3238
+ TouchableOpacity: "div",
3239
+ TouchableHighlight: "div",
3240
+ TouchableWithoutFeedback: "div",
3241
+ TouchableNativeFeedback: "div"
3242
+ };
3243
+ var _userMap = /* @__PURE__ */ new Map();
3244
+ var _cache = /* @__PURE__ */ new WeakMap();
3245
+ function registerWebElement(rnComponent, htmlTag) {
3246
+ if (typeof rnComponent !== "object" && typeof rnComponent !== "function") return;
3247
+ _userMap.set(rnComponent, htmlTag);
3248
+ _cache.delete(rnComponent);
3249
+ }
3250
+ function getWebTag(type, props) {
3251
+ if (typeof type === "string") return null;
3252
+ if (type === null || typeof type !== "function" && typeof type !== "object") return null;
3253
+ const obj = type;
3254
+ if (_userMap.has(obj)) return _userMap.get(obj);
3255
+ const name = obj.displayName ?? obj.name;
3256
+ if (!name) {
3257
+ _cache.set(obj, null);
3258
+ return null;
3259
+ }
3260
+ if (name === "TextInput") {
3261
+ const multiline = !!props && (props.multiline === true || Number(props.numberOfLines) > 1);
3262
+ return multiline ? "textarea" : "input";
3263
+ }
3264
+ if (_cache.has(obj)) return _cache.get(obj);
3265
+ const tag = _rnToHtml[name] ?? null;
3266
+ _cache.set(obj, tag);
3267
+ return tag;
3268
+ }
3269
+ var _rnOnlyProps = /* @__PURE__ */ new Set([
3270
+ // Interaction
3271
+ "onLongPress",
3272
+ "delayLongPress",
3273
+ "activeOpacity",
3274
+ "underlayColor",
3275
+ "hitSlop",
3276
+ "pressRetentionOffset",
3277
+ "android_ripple",
3278
+ "android_disableSound",
3279
+ "onHoverIn",
3280
+ "onHoverOut",
3281
+ "onHoverStart",
3282
+ "onHoverEnd",
3283
+ // Layout event
3284
+ "onLayout",
3285
+ // Accessibility
3286
+ "accessible",
3287
+ "accessibilityState",
3288
+ "accessibilityLiveRegion",
3289
+ "importantForAccessibility",
3290
+ // Platform
3291
+ "nativeID",
3292
+ "collapsable",
3293
+ "needsOffscreenAlphaCompositing",
3294
+ "renderToHardwareTextureAndroid",
3295
+ "shouldRasterizeIOS",
3296
+ "focusable",
3297
+ "hasTVPreferredFocus",
3298
+ // 'pointerEvents' is handled in transformToWebProps (mapped to CSS style)
3299
+ // Text
3300
+ "selectable",
3301
+ "allowFontScaling",
3302
+ "adjustsFontSizeToFit",
3303
+ "minimumFontScale",
3304
+ "ellipsizeMode",
3305
+ "numberOfLines",
3306
+ "onTextLayout",
3307
+ "textBreakStrategy",
3308
+ "lineBreakStrategyIOS",
3309
+ // TextInput
3310
+ "multiline",
3311
+ "resizeMode",
3312
+ // handled separately
3313
+ "blurOnSubmit",
3314
+ "clearButtonMode",
3315
+ "clearTextOnFocus",
3316
+ "enablesReturnKeyAutomatically",
3317
+ "returnKeyType",
3318
+ "spellCheck",
3319
+ // ScrollView
3320
+ "scrollEnabled",
3321
+ "showsVerticalScrollIndicator",
3322
+ "showsHorizontalScrollIndicator",
3323
+ "contentContainerStyle",
3324
+ // 'horizontal' is handled in transformToWebProps (converted to CSS overflow-x).
3325
+ "keyboardShouldPersistTaps",
3326
+ "keyboardDismissMode",
3327
+ "pagingEnabled",
3328
+ "scrollEventThrottle",
3329
+ "decelerationRate",
3330
+ "bounces",
3331
+ "alwaysBounceHorizontal",
3332
+ "alwaysBounceVertical",
3333
+ "snapToAlignment",
3334
+ "snapToInterval",
3335
+ "snapToOffsets",
3336
+ "removeClippedSubviews",
3337
+ "overScrollMode",
3338
+ "stickyHeaderIndices",
3339
+ "invertStickyHeaders",
3340
+ "onScrollBeginDrag",
3341
+ "onScrollEndDrag",
3342
+ "onMomentumScrollBegin",
3343
+ "onMomentumScrollEnd",
3344
+ "contentInset",
3345
+ "contentInsetAdjustmentBehavior",
3346
+ "automaticallyAdjustContentInsets",
3347
+ "automaticallyAdjustsScrollIndicatorInsets",
3348
+ // expo-image
3349
+ "contentPosition",
3350
+ "cachePolicy",
3351
+ "recyclingKey",
3352
+ "blurRadius",
3353
+ "fadeDuration",
3354
+ "responsivePolicy",
3355
+ "tintColor",
3356
+ "allowDownscaling",
3357
+ "placeholderContentFit",
3358
+ // FlatList / SectionList
3359
+ "data",
3360
+ "renderItem",
3361
+ "keyExtractor",
3362
+ "getItemLayout",
3363
+ "initialScrollIndex",
3364
+ "initialNumToRender",
3365
+ "maxToRenderPerBatch",
3366
+ "windowSize",
3367
+ "updateCellsBatchingPeriod",
3368
+ "onEndReached",
3369
+ "onEndReachedThreshold",
3370
+ "ListHeaderComponent",
3371
+ "ListFooterComponent",
3372
+ "ListEmptyComponent",
3373
+ "ListHeaderComponentStyle",
3374
+ "ListFooterComponentStyle",
3375
+ "ItemSeparatorComponent",
3376
+ "SectionSeparatorComponent",
3377
+ "inverted",
3378
+ "getItem",
3379
+ "getItemCount"
3380
+ ]);
3381
+ var _pressableNames = /* @__PURE__ */ new Set([
3382
+ "Pressable",
3383
+ "TouchableOpacity",
3384
+ "TouchableHighlight",
3385
+ "TouchableWithoutFeedback",
3386
+ "TouchableNativeFeedback"
3387
+ ]);
3388
+ var _keyboardTypeMap = {
3389
+ "numeric": "number",
3390
+ "number-pad": "number",
3391
+ "decimal-pad": "decimal",
3392
+ "email-address": "email",
3393
+ "phone-pad": "tel",
3394
+ "url": "url"
3395
+ };
3396
+ var _resizeModeMap = {
3397
+ "contain": "contain",
3398
+ "cover": "cover",
3399
+ "stretch": "fill",
3400
+ "center": "none",
3401
+ "repeat": "none"
3402
+ };
3403
+ function transformToWebProps(originalName, tag, props) {
3404
+ const out = {};
3405
+ const isPressable = _pressableNames.has(originalName);
3406
+ const isTextInput = originalName === "TextInput";
3407
+ const isImage = originalName === "Image" || originalName === "ImageBackground";
3408
+ const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
3409
+ let pendingStyle = null;
3410
+ for (const [k, v] of Object.entries(props)) {
3411
+ if (_rnOnlyProps.has(k)) continue;
3412
+ if (k === "onPress") {
3413
+ if (!("onClick" in props)) out.onClick = v;
3414
+ continue;
3415
+ }
3416
+ if (k === "accessibilityLabel") {
3417
+ if (out["aria-label"] == null) out["aria-label"] = v;
3418
+ continue;
3419
+ }
3420
+ if (k === "accessibilityRole") {
3421
+ if (out.role == null) out.role = v;
3422
+ continue;
3423
+ }
3424
+ if (k === "testID") {
3425
+ if (out["data-testid"] == null) out["data-testid"] = v;
3426
+ continue;
3427
+ }
3428
+ if (isTextInput) {
3429
+ if (k === "onChangeText") {
3430
+ if (!("onChange" in props)) out.onChange = (e) => v(e.target.value);
3431
+ continue;
3432
+ }
3433
+ if (k === "secureTextEntry") {
3434
+ if (v && !("type" in props)) out.type = "password";
3435
+ continue;
3436
+ }
3437
+ if (k === "keyboardType") {
3438
+ if (!("type" in props) && !("secureTextEntry" in props)) {
3439
+ const mapped = _keyboardTypeMap[v];
3440
+ if (mapped) out.type = mapped;
3441
+ }
3442
+ continue;
3443
+ }
3444
+ if (k === "editable") {
3445
+ if (v === false) out.readOnly = true;
3446
+ continue;
3447
+ }
3448
+ if (k === "maxLength") {
3449
+ out.maxLength = v;
3450
+ continue;
3451
+ }
3452
+ }
3453
+ if (isScrollable && k === "horizontal") {
3454
+ if (v) pendingStyle = { display: "flex", flexDirection: "row", overflowX: "auto" };
3455
+ continue;
3456
+ }
3457
+ if (k === "pointerEvents") {
3458
+ if (v === "none" || v === "auto") pendingStyle = { ...pendingStyle ?? {}, pointerEvents: v };
3459
+ continue;
3460
+ }
3461
+ if (isImage) {
3462
+ if (k === "source") {
3463
+ if (typeof v === "string") {
3464
+ out.src = v;
3465
+ } else if (v && typeof v === "object" && "uri" in v) {
3466
+ out.src = v.uri;
3467
+ if (v.headers) out["crossOrigin"] = "anonymous";
3468
+ }
3469
+ continue;
3470
+ }
3471
+ if (k === "resizeMode") {
3472
+ pendingStyle = { ...pendingStyle ?? {}, objectFit: _resizeModeMap[v] ?? "cover" };
3473
+ continue;
3474
+ }
3475
+ if (k === "contentFit") {
3476
+ pendingStyle = { ...pendingStyle ?? {}, objectFit: v };
3477
+ continue;
3478
+ }
3479
+ if (k === "defaultSource") continue;
3480
+ }
3481
+ if (k === "style") {
3482
+ if (Array.isArray(v)) {
3483
+ out.style = Object.assign({}, ...v.filter(Boolean));
3484
+ } else if (v != null) {
3485
+ out.style = v;
3486
+ }
3487
+ continue;
3488
+ }
3489
+ out[k] = v;
3490
+ }
3491
+ if (pendingStyle) {
3492
+ out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
3493
+ }
3494
+ if (isPressable && !out.role) out.role = "button";
3495
+ if (isImage && tag === "img" && out.alt == null) out.alt = "";
3496
+ return out;
3497
+ }
3498
+
3499
+ // src/InteractiveWrapper.tsx
3500
+ import React2, { forwardRef, useState, useCallback, useMemo } from "react";
3501
+
3502
+ // src/useGlobalWidth.ts
3503
+ var NOOP_SUB = (_) => () => {
3504
+ };
3505
+ var ZERO_SNAP = () => 0;
3506
+ var EMPTY_BREAKPOINTS = /* @__PURE__ */ new Set();
3507
+ var _webListeners = null;
3508
+ var _webRaf = 0;
3509
+ function _webResizeHandler() {
3510
+ cancelAnimationFrame(_webRaf);
3511
+ _webRaf = requestAnimationFrame(() => {
3512
+ if (_webListeners) for (const cb of _webListeners) cb();
3513
+ });
3514
+ }
3515
+ function subscribeWebWidth(cb) {
3516
+ if (typeof window === "undefined") return () => {
3517
+ };
3518
+ if (!_webListeners) {
3519
+ _webListeners = /* @__PURE__ */ new Set();
3520
+ window.addEventListener("resize", _webResizeHandler);
3521
+ }
3522
+ _webListeners.add(cb);
3523
+ return () => {
3524
+ _webListeners.delete(cb);
3525
+ if (_webListeners.size === 0) {
3526
+ window.removeEventListener("resize", _webResizeHandler);
3527
+ cancelAnimationFrame(_webRaf);
3528
+ _webListeners = null;
3529
+ }
3530
+ };
3531
+ }
3532
+ function getWebWidth() {
3533
+ return typeof window !== "undefined" ? window.innerWidth : 0;
3534
+ }
3535
+ function useGlobalWidth() {
3536
+ return useSyncExternalStore(
3537
+ isWeb ? subscribeWebWidth : subscribeGlobalWidth,
3538
+ isWeb ? getWebWidth : getGlobalWidth,
3539
+ ZERO_SNAP
3540
+ );
3541
+ }
3542
+ function useConditionalWidth(active) {
3543
+ return useSyncExternalStore(
3544
+ active ? isWeb ? subscribeWebWidth : subscribeGlobalWidth : NOOP_SUB,
3545
+ active ? isWeb ? getWebWidth : getGlobalWidth : ZERO_SNAP,
3546
+ ZERO_SNAP
3547
+ );
3548
+ }
3549
+
3550
+ // src/InteractiveWrapper.tsx
3551
+ var InteractiveWrapper = forwardRef(
3552
+ function InteractiveWrapper2({
3553
+ Component,
3554
+ resolvedStyle,
3555
+ className,
3556
+ style: styleProp,
3557
+ onPressIn,
3558
+ onPressOut,
3559
+ onPointerDown,
3560
+ onPointerUp,
3561
+ onPointerLeave,
3562
+ onPointerCancel,
3563
+ onMouseEnter,
3564
+ onMouseLeave,
3565
+ onFocus,
3566
+ onBlur,
3567
+ ...rest
3568
+ }, ref) {
3569
+ const isDark = useGlobalDarkMode();
3570
+ const needsWidth = hasResponsiveBuckets(resolvedStyle);
3571
+ const width = useConditionalWidth(needsWidth);
3572
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
3573
+ const [pressed, setPressed] = useState(false);
3574
+ const [hovered, setHovered] = useState(false);
3575
+ const [focused, setFocused] = useState(false);
3576
+ const handlePressIn = useCallback(chain(onPressIn, () => setPressed(true)), [onPressIn]);
3577
+ const handlePressOut = useCallback(chain(onPressOut, () => setPressed(false)), [onPressOut]);
3578
+ const handlePointerDown = useCallback(chain(onPointerDown, () => setPressed(true)), [onPointerDown]);
3579
+ const handlePointerUp = useCallback(chain(onPointerUp, () => setPressed(false)), [onPointerUp]);
3580
+ const handlePointerLeave = useCallback(chain(onPointerLeave, () => setPressed(false)), [onPointerLeave]);
3581
+ const handlePointerCancel = useCallback(chain(onPointerCancel, () => setPressed(false)), [onPointerCancel]);
3582
+ const handleMouseEnter = useCallback(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
3583
+ const handleMouseLeave = useCallback(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
3584
+ const handleFocus = useCallback(chain(onFocus, () => setFocused(true)), [onFocus]);
3585
+ const handleBlur = useCallback(chain(onBlur, () => setFocused(false)), [onBlur]);
3586
+ const { children, disabled, checked, ...restForComponent } = rest;
3587
+ const computedStyle = useMemo(
3588
+ () => {
3589
+ if (isWeb) return {};
3590
+ const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
3591
+ stripInternalMarkers(s);
3592
+ if (typeof Component !== "string") stripWebOnlyProps(s);
3593
+ return s;
3594
+ },
3595
+ [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked]
3596
+ );
3597
+ const skipComputedInline = isWeb || !isNative && typeof Component === "string";
3598
+ const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
3599
+ const componentProps = {
3600
+ ref,
3601
+ ...restForComponent,
3602
+ ...disabled !== void 0 ? { disabled } : {},
3603
+ ...checked !== void 0 ? { checked } : {},
3604
+ style: finalStyle,
3605
+ ...!isNative && className ? { className } : {},
3606
+ // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
3607
+ // but onPressIn/onPressOut are still forwarded unchanged — react-native-web components
3608
+ // (and any component that accepts both prop names) depend on receiving them directly.
3609
+ ...!isNative ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerCancel, onPressIn, onPressOut } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
3610
+ onMouseEnter: handleMouseEnter,
3611
+ onMouseLeave: handleMouseLeave,
3612
+ onFocus: handleFocus,
3613
+ onBlur: handleBlur
3614
+ };
3615
+ return Array.isArray(children) ? React2.createElement(Component, componentProps, ...children) : React2.createElement(Component, componentProps, children);
3616
+ }
3617
+ );
3618
+ InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
3619
+
3104
3620
  export {
3105
3621
  defaultColors,
3106
3622
  defaultTheme,
3107
3623
  isWeb,
3108
3624
  isNative,
3109
3625
  setResolveTarget,
3110
- setCSSGenMode,
3111
3626
  getInteractiveModifiers,
3112
3627
  getModeModifiers,
3113
3628
  getResponsiveModifiers,
@@ -3116,18 +3631,14 @@ export {
3116
3631
  splitClassTokens,
3117
3632
  normalizeClassString,
3118
3633
  parseClasses,
3119
- BASE_RESET,
3120
3634
  syncGlobalWidth,
3121
3635
  syncGlobalScreens,
3122
3636
  getGlobalScreens,
3123
3637
  setGlobalWidth,
3124
- getGlobalWidth,
3125
- subscribeGlobalWidth,
3126
3638
  getActiveBreakpoints,
3127
3639
  getDefaultFontFamily,
3128
3640
  disableRuntimeCSS,
3129
3641
  isRuntimeCSSDisabled,
3130
- generateClassCSS,
3131
3642
  resolve,
3132
3643
  flatten,
3133
3644
  clearCache,
@@ -3135,5 +3646,21 @@ export {
3135
3646
  buildConfig,
3136
3647
  onConfigChange,
3137
3648
  updateConfig,
3138
- initConfig
3649
+ initConfig,
3650
+ syncGlobalDarkMode,
3651
+ setGlobalDarkMode,
3652
+ useSyncExternalStore,
3653
+ useGlobalDarkMode,
3654
+ EMPTY_BREAKPOINTS,
3655
+ useGlobalWidth,
3656
+ useConditionalWidth,
3657
+ hasResponsiveBuckets,
3658
+ hasInteractiveBuckets,
3659
+ chain,
3660
+ stripInternalMarkers,
3661
+ stripWebOnlyProps,
3662
+ registerWebElement,
3663
+ getWebTag,
3664
+ transformToWebProps,
3665
+ InteractiveWrapper
3139
3666
  };