expo-tvos-search 1.2.2 → 1.3.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/build/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
2
  import { Platform } from "react-native";
3
- // Safely try to load the native view - it may not be available if:
4
- // 1. Running on a non-tvOS platform
5
- // 2. Native module hasn't been built yet (needs expo prebuild)
6
- // 3. expo-modules-core isn't properly installed
3
+ /**
4
+ * Native view component loaded at module initialization.
5
+ * Returns null on non-tvOS platforms or when the native module is unavailable.
6
+ */
7
7
  let NativeView = null;
8
8
  if (Platform.OS === "ios" && Platform.isTV) {
9
9
  try {
@@ -11,9 +11,38 @@ if (Platform.OS === "ios" && Platform.isTV) {
11
11
  if (typeof requireNativeViewManager === "function") {
12
12
  NativeView = requireNativeViewManager("ExpoTvosSearch");
13
13
  }
14
+ else {
15
+ console.warn("[expo-tvos-search] requireNativeViewManager is not a function. " +
16
+ "This usually indicates an incompatible expo-modules-core version. " +
17
+ "Try reinstalling expo-modules-core or updating to a compatible version.");
18
+ }
14
19
  }
15
- catch {
16
- // Native module not available - will fall back to React Native implementation
20
+ catch (error) {
21
+ // Categorize the error to help with debugging
22
+ const errorMessage = error instanceof Error ? error.message : String(error);
23
+ if (errorMessage.includes("expo-modules-core")) {
24
+ console.warn("[expo-tvos-search] Failed to load expo-modules-core. " +
25
+ "Make sure expo-modules-core is installed: npm install expo-modules-core\n" +
26
+ `Error: ${errorMessage}`);
27
+ }
28
+ else if (errorMessage.includes("ExpoTvosSearch")) {
29
+ console.warn("[expo-tvos-search] Native module ExpoTvosSearch not found. " +
30
+ "This usually means:\n" +
31
+ "1. You haven't run 'expo prebuild' yet, or\n" +
32
+ "2. The native project needs to be rebuilt (try 'expo prebuild --clean')\n" +
33
+ "3. You're not running on a tvOS simulator/device\n" +
34
+ `Error: ${errorMessage}`);
35
+ }
36
+ else {
37
+ // Unexpected error - log full details for debugging
38
+ console.warn("[expo-tvos-search] Unexpected error loading native module.\n" +
39
+ `Error: ${errorMessage}\n` +
40
+ "Please report this issue at: https://github.com/keiver/expo-tvos-search/issues");
41
+ // In development, log the full error for debugging
42
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
43
+ console.error("[expo-tvos-search] Full error details:", error);
44
+ }
45
+ }
17
46
  }
18
47
  }
19
48
  /**
@@ -51,6 +80,25 @@ if (Platform.OS === "ios" && Platform.isTV) {
51
80
  */
52
81
  export function TvosSearchView(props) {
53
82
  if (!NativeView) {
83
+ // Warn in development when native module is unavailable
84
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
85
+ const isRunningOnTvOS = Platform.OS === "ios" && Platform.isTV;
86
+ if (isRunningOnTvOS) {
87
+ // On tvOS but module failed to load - this is unexpected
88
+ console.warn("[expo-tvos-search] TvosSearchView is rendering null on tvOS. " +
89
+ "This usually means:\n" +
90
+ "1. The native module wasn't built properly (try 'expo prebuild --clean')\n" +
91
+ "2. expo-modules-core is missing or incompatible\n" +
92
+ "3. The app needs to be restarted after installing the module\n\n" +
93
+ "Check the earlier console logs for specific error details.");
94
+ }
95
+ else {
96
+ // Not on tvOS - expected behavior, but developer might want to know
97
+ console.info("[expo-tvos-search] TvosSearchView is not available on " +
98
+ `${Platform.OS}${Platform.isTV ? " (TV)" : ""}. ` +
99
+ "Use isNativeSearchAvailable() to check before rendering this component.");
100
+ }
101
+ }
54
102
  return null;
55
103
  }
56
104
  return React.createElement(NativeView, { ...props });
@@ -12,17 +12,32 @@ public class ExpoTvosSearchModule: Module {
12
12
  Name("ExpoTvosSearch")
13
13
 
14
14
  View(ExpoTvosSearchView.self) {
15
- Events("onSearch", "onSelectItem")
15
+ Events("onSearch", "onSelectItem", "onError", "onValidationWarning")
16
16
 
17
17
  Prop("results") { (view: ExpoTvosSearchView, results: [[String: Any]]) in
18
18
  // Limit results array size to prevent memory issues
19
19
  let limitedResults = Array(results.prefix(Self.maxResults))
20
+ if results.count > Self.maxResults {
21
+ view.onValidationWarning([
22
+ "type": "value_clamped",
23
+ "message": "Results array truncated from \(results.count) to \(Self.maxResults) items",
24
+ "context": "maxResults=\(Self.maxResults)"
25
+ ])
26
+ }
20
27
  view.updateResults(limitedResults)
21
28
  }
22
29
 
23
30
  Prop("columns") { (view: ExpoTvosSearchView, columns: Int) in
24
31
  // Clamp columns between min and max for safe grid layout
25
- view.columns = min(max(Self.minColumns, columns), Self.maxColumns)
32
+ let clampedValue = min(max(Self.minColumns, columns), Self.maxColumns)
33
+ if clampedValue != columns {
34
+ view.onValidationWarning([
35
+ "type": "value_clamped",
36
+ "message": "columns value \(columns) was clamped to range [\(Self.minColumns), \(Self.maxColumns)]",
37
+ "context": "columns=\(clampedValue)"
38
+ ])
39
+ }
40
+ view.columns = clampedValue
26
41
  }
27
42
 
28
43
  Prop("placeholder") { (view: ExpoTvosSearchView, placeholder: String) in
@@ -48,7 +63,15 @@ public class ExpoTvosSearchModule: Module {
48
63
 
49
64
  Prop("topInset") { (view: ExpoTvosSearchView, topInset: Double) in
50
65
  // Clamp to non-negative values (max 500 points reasonable for any screen)
51
- view.topInset = CGFloat(min(max(0, topInset), 500))
66
+ let clampedValue = min(max(0, topInset), 500)
67
+ if clampedValue != topInset {
68
+ view.onValidationWarning([
69
+ "type": "value_clamped",
70
+ "message": "topInset value \(topInset) was clamped to range [0, 500]",
71
+ "context": "topInset=\(clampedValue)"
72
+ ])
73
+ }
74
+ view.topInset = CGFloat(clampedValue)
52
75
  }
53
76
 
54
77
  Prop("showTitleOverlay") { (view: ExpoTvosSearchView, show: Bool) in
@@ -61,7 +84,15 @@ public class ExpoTvosSearchModule: Module {
61
84
 
62
85
  Prop("marqueeDelay") { (view: ExpoTvosSearchView, delay: Double) in
63
86
  // Clamp between 0 and maxMarqueeDelay seconds
64
- view.marqueeDelay = min(max(0, delay), Self.maxMarqueeDelay)
87
+ let clampedValue = min(max(0, delay), Self.maxMarqueeDelay)
88
+ if clampedValue != delay {
89
+ view.onValidationWarning([
90
+ "type": "value_clamped",
91
+ "message": "marqueeDelay value \(delay) was clamped to range [0, \(Self.maxMarqueeDelay)]",
92
+ "context": "marqueeDelay=\(clampedValue)"
93
+ ])
94
+ }
95
+ view.marqueeDelay = clampedValue
65
96
  }
66
97
 
67
98
  Prop("emptyStateText") { (view: ExpoTvosSearchView, text: String) in
@@ -79,6 +110,38 @@ public class ExpoTvosSearchModule: Module {
79
110
  Prop("noResultsHintText") { (view: ExpoTvosSearchView, text: String) in
80
111
  view.noResultsHintText = String(text.prefix(Self.maxStringLength))
81
112
  }
113
+
114
+ Prop("textColor") { (view: ExpoTvosSearchView, colorHex: String?) in
115
+ view.textColor = colorHex
116
+ }
117
+
118
+ Prop("accentColor") { (view: ExpoTvosSearchView, colorHex: String) in
119
+ view.accentColor = colorHex
120
+ }
121
+
122
+ Prop("cardWidth") { (view: ExpoTvosSearchView, width: Double) in
123
+ view.cardWidth = CGFloat(max(50, min(1000, width))) // Clamp to reasonable range
124
+ }
125
+
126
+ Prop("cardHeight") { (view: ExpoTvosSearchView, height: Double) in
127
+ view.cardHeight = CGFloat(max(50, min(1000, height))) // Clamp to reasonable range
128
+ }
129
+
130
+ Prop("imageContentMode") { (view: ExpoTvosSearchView, mode: String) in
131
+ view.imageContentMode = mode
132
+ }
133
+
134
+ Prop("cardMargin") { (view: ExpoTvosSearchView, margin: Double) in
135
+ view.cardMargin = CGFloat(max(0, min(200, margin))) // Clamp to reasonable range
136
+ }
137
+
138
+ Prop("cardPadding") { (view: ExpoTvosSearchView, padding: Double) in
139
+ view.cardPadding = CGFloat(max(0, min(100, padding))) // Clamp to reasonable range
140
+ }
141
+
142
+ Prop("overlayTitleSize") { (view: ExpoTvosSearchView, size: Double) in
143
+ view.overlayTitleSize = CGFloat(max(8, min(72, size))) // Clamp to reasonable font size range
144
+ }
82
145
  }
83
146
  }
84
147
  }