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/README.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # expo-tvos-search
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/expo-tvos-search.svg)](https://www.npmjs.com/package/expo-tvos-search)
4
- [![npm downloads](https://img.shields.io/npm/dm/expo-tvos-search.svg)](https://www.npmjs.com/package/expo-tvos-search)
5
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
5
  [![Test Status](https://github.com/keiver/expo-tvos-search/workflows/Test%20PR/badge.svg)](https://github.com/keiver/expo-tvos-search/actions)
6
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/expo-tvos-search)](https://bundlephobia.com/package/expo-tvos-search)
7
7
 
8
- A native tvOS search component for Expo and React Native.
9
-
10
- This library provides a native tvOS search view using SwiftUI's `.searchable` modifier. It handles focus, keyboard navigation, and accessibility out of the box, providing a seamless search experience on Apple TV with a native fullscreen search interface.
8
+ A native tvOS search component for Expo and React Native using SwiftUI's `.searchable` modifier. Handles focus, keyboard navigation, and accessibility out of the box.
11
9
 
12
10
  <p align="center">
13
11
  <img src="screenshots/results.png" width="700" alt="TomoTV Search Results"/>
@@ -30,43 +28,126 @@ This library provides a native tvOS search view using SwiftUI's `.searchable` mo
30
28
  </tr>
31
29
  </table>
32
30
 
33
-
34
31
  ## Installation
35
32
 
36
33
  ```bash
37
- npm install expo-tvos-search
34
+ npx expo install expo-tvos-search
38
35
  ```
39
36
 
40
37
  Or install from GitHub:
41
38
 
42
39
  ```bash
43
- npm install github:keiver/expo-tvos-search
40
+ npx expo install github:keiver/expo-tvos-search
41
+ ```
42
+
43
+ Then follow the **tvOS prerequisites** below and rebuild your native project.
44
+
45
+ ## Prerequisites for tvOS Builds (Expo)
46
+
47
+ Your project must be configured for React Native tvOS to build and run this module.
48
+
49
+ **Quick Checklist:**
50
+
51
+ - ✅ `react-native-tvos` in use
52
+ - ✅ `@react-native-tvos/config-tv` installed + added to Expo plugins
53
+ - ✅ Run prebuild with `EXPO_TV=1`
54
+
55
+ ### 1. Swap to react-native-tvos
56
+
57
+ Replace `react-native` with the [tvOS fork](https://github.com/react-native-tvos/react-native-tvos):
58
+
59
+ ```bash
60
+ npm remove react-native && npm install react-native-tvos@latest
61
+ ```
62
+
63
+ ### 2. Install the tvOS config plugin
64
+
65
+ Install:
66
+
67
+ ```bash
68
+ npx expo install @react-native-tvos/config-tv
69
+ ```
70
+
71
+ Then add the plugin in `app.json` / `app.config.js`:
72
+
73
+ ```json
74
+ {
75
+ "expo": {
76
+ "plugins": ["@react-native-tvos/config-tv"]
77
+ }
78
+ }
44
79
  ```
45
80
 
46
- Then rebuild your native project:
81
+ ### 3. Generate native projects with tvOS enabled
47
82
 
48
83
  ```bash
49
84
  EXPO_TV=1 npx expo prebuild --clean
85
+ ```
86
+
87
+ Then run:
88
+
89
+ ```bash
50
90
  npx expo run:ios
51
91
  ```
52
92
 
93
+ ### 4. Common gotchas
94
+
95
+ - **Prebuild must be re-run** if you add/remove tvOS dependencies or change the tvOS plugin configuration.
96
+ - **If you see App Transport Security errors** for images, ensure your `imageUrl` uses `https://` (recommended) or add the appropriate ATS exceptions.
97
+ - **If the tvOS keyboard/search UI doesn't appear**, confirm you're actually running a tvOS target/simulator, not an iOS target.
98
+
53
99
  ## Usage
54
100
 
55
101
  ```tsx
56
- import { TvosSearchView, isNativeSearchAvailable } from 'expo-tvos-search';
57
-
58
- function SearchScreen() {
59
- const [results, setResults] = useState([]);
102
+ import React, { useState } from "react";
103
+ import { Alert } from "react-native";
104
+ import {
105
+ TvosSearchView,
106
+ isNativeSearchAvailable,
107
+ type SearchResult,
108
+ } from "expo-tvos-search";
109
+
110
+ const PLANETS: SearchResult[] = [
111
+ { id: "mercury", title: "Mercury", subtitle: "Smallest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Mercury_in_true_color.jpg/400px-Mercury_in_true_color.jpg" },
112
+ { id: "venus", title: "Venus", subtitle: "Hottest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Venus-real_color.jpg/400px-Venus-real_color.jpg" },
113
+ { id: "earth", title: "Earth", subtitle: "Our home", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/The_Earth_seen_from_Apollo_17.jpg/400px-The_Earth_seen_from_Apollo_17.jpg" },
114
+ { id: "mars", title: "Mars", subtitle: "The red planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/OSIRIS_Mars_true_color.jpg/400px-OSIRIS_Mars_true_color.jpg" },
115
+ { id: "jupiter", title: "Jupiter", subtitle: "Largest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Jupiter_New_Horizons.jpg/400px-Jupiter_New_Horizons.jpg" },
116
+ { id: "saturn", title: "Saturn", subtitle: "Ringed giant", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Saturn_during_Equinox.jpg/400px-Saturn_during_Equinox.jpg" },
117
+ { id: "uranus", title: "Uranus", subtitle: "Ice giant", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/Uranus2.jpg/400px-Uranus2.jpg" },
118
+ { id: "neptune", title: "Neptune", subtitle: "Windiest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Neptune_-_Voyager_2_%2829347980845%29_flatten_crop.jpg/400px-Neptune_-_Voyager_2_%2829347980845%29_flatten_crop.jpg" },
119
+ ];
120
+
121
+ export function SearchScreen() {
122
+ const [results, setResults] = useState<SearchResult[]>([]);
60
123
  const [isLoading, setIsLoading] = useState(false);
61
124
 
62
- const handleSearch = (event) => {
63
- const query = event.nativeEvent.query;
64
- // Fetch your results...
125
+ const handleSearch = (event: { nativeEvent: { query: string } }) => {
126
+ const { query } = event.nativeEvent;
127
+
128
+ if (!query.trim()) {
129
+ setResults([]);
130
+ return;
131
+ }
132
+
133
+ setIsLoading(true);
134
+
135
+ // Simulate async search
136
+ setTimeout(() => {
137
+ const filtered = PLANETS.filter((planet) =>
138
+ planet.title.toLowerCase().includes(query.toLowerCase()) ||
139
+ planet.subtitle?.toLowerCase().includes(query.toLowerCase())
140
+ );
141
+ setResults(filtered);
142
+ setIsLoading(false);
143
+ }, 300);
65
144
  };
66
145
 
67
- const handleSelect = (event) => {
68
- const id = event.nativeEvent.id;
69
- // Navigate to detail...
146
+ const handleSelect = (event: { nativeEvent: { id: string } }) => {
147
+ const planet = PLANETS.find((p) => p.id === event.nativeEvent.id);
148
+ if (planet) {
149
+ Alert.alert(planet.title, planet.subtitle);
150
+ }
70
151
  };
71
152
 
72
153
  if (!isNativeSearchAvailable()) {
@@ -77,7 +158,7 @@ function SearchScreen() {
77
158
  <TvosSearchView
78
159
  results={results}
79
160
  columns={5}
80
- placeholder="Search..."
161
+ placeholder="Search planets..."
81
162
  isLoading={isLoading}
82
163
  topInset={140}
83
164
  onSearch={handleSearch}
@@ -88,13 +169,146 @@ function SearchScreen() {
88
169
  }
89
170
  ```
90
171
 
172
+ > **⚠️ IMPORTANT**: Always debounce the `onSearch` callback to prevent excessive API calls or expensive operations on every keystroke. In production apps, use a debounce library like lodash or implement your own debounce function:
173
+ >
174
+ > ```tsx
175
+ > import { useMemo } from 'react';
176
+ > import { debounce } from 'lodash'; // or your preferred debounce implementation
177
+ >
178
+ > function SearchScreen() {
179
+ > const [results, setResults] = useState([]);
180
+ >
181
+ > const debouncedSearch = useMemo(
182
+ > () => debounce((query: string) => {
183
+ > // Your actual search logic (API call, etc.)
184
+ > fetchSearchResults(query).then(setResults);
185
+ > }, 300), // 300ms delay
186
+ > []
187
+ > );
188
+ >
189
+ > return (
190
+ > <TvosSearchView
191
+ > results={results}
192
+ > onSearch={(e) => debouncedSearch(e.nativeEvent.query)}
193
+ > onSelectItem={handleSelect}
194
+ > />
195
+ > );
196
+ > }
197
+ > ```
198
+
199
+ ### Error Handling and Monitoring
200
+
201
+ The library provides error and validation warning callbacks for production monitoring:
202
+
203
+ ```tsx
204
+ <TvosSearchView
205
+ results={results}
206
+ onSearch={handleSearch}
207
+ onSelectItem={handleSelect}
208
+ onError={(e) => {
209
+ const { category, message, context } = e.nativeEvent;
210
+ // Log to your error monitoring service
211
+ console.error(`[Search Error] ${category}: ${message}`, context);
212
+ // Examples: 'module_unavailable', 'validation_failed', 'image_load_failed', 'unknown'
213
+ }}
214
+ onValidationWarning={(e) => {
215
+ const { type, message, context } = e.nativeEvent;
216
+ // Log non-fatal issues for monitoring
217
+ console.warn(`[Validation] ${type}: ${message}`, context);
218
+ // Examples: 'field_truncated', 'value_clamped', 'url_invalid', 'validation_failed'
219
+ }}
220
+ />
221
+ ```
222
+
223
+ These callbacks help you:
224
+ - Monitor data quality issues (truncated fields, invalid URLs)
225
+ - Track when props are clamped to safe ranges
226
+ - Detect when results exceed the 500-item limit
227
+ - Log errors for debugging in production
228
+
229
+ ### Customizing Colors and Card Dimensions
230
+
231
+ You can customize the appearance of the search interface with color and dimension props:
232
+
233
+ ```tsx
234
+ <TvosSearchView
235
+ results={results}
236
+ onSearch={handleSearch}
237
+ onSelectItem={handleSelect}
238
+ // Custom colors
239
+ textColor="#E5E5E5" // Light gray text on dark background
240
+ accentColor="#E50914" // Red focused borders (House Flix style 😂)
241
+ // Custom card dimensions
242
+ cardWidth={420} // Landscape cards
243
+ cardHeight={240} // 16:9 aspect ratio
244
+ style={{ flex: 1 }}
245
+ />
246
+ ```
247
+
248
+ **Color props:**
249
+ - `textColor` - Affects subtitle text, empty state text, and placeholder icons
250
+ - `accentColor` - Affects focused card borders and highlights
251
+
252
+ **Dimension props:**
253
+ - Default: 280x420 (portrait, 2:3 aspect ratio)
254
+ - Landscape example: 420x240 (16:9)
255
+ - Square example: 300x300 (1:1)
256
+
257
+ ### Layout and Spacing Customization
258
+
259
+ Control image display, card spacing, and overlay padding for different layouts:
260
+
261
+ ```tsx
262
+ <TvosSearchView
263
+ results={results}
264
+ onSearch={handleSearch}
265
+ onSelectItem={handleSelect}
266
+ // Image display mode
267
+ imageContentMode="fit" // 'fill' (default, crop), 'fit'/'contain' (letterbox)
268
+ // Card spacing
269
+ cardMargin={60} // Space between cards (default: 40)
270
+ cardPadding={20} // Padding inside overlay (default: 16)
271
+ style={{ flex: 1 }}
272
+ />
273
+ ```
274
+
275
+ **Layout props:**
276
+ - `imageContentMode` - How images fill cards:
277
+ - `'fill'` (default) - Crops image to fill entire card
278
+ - `'fit'` or `'contain'` - Shows entire image, may add letterboxing
279
+ - `cardMargin` - Controls spacing between cards in the grid (both horizontal and vertical)
280
+ - `cardPadding` - Controls padding inside the card's title overlay for better text spacing
281
+
282
+ **Common use cases:**
283
+ - **Portrait posters** (movie/TV): `imageContentMode="fill"` + `cardMargin={40}`
284
+ - **Landscape thumbnails** (episodes): `imageContentMode="fit"` + `cardMargin={60}`
285
+ - **Compact grid** (music): `cardMargin={20}` + `cardPadding={12}`
286
+ - **Spacious layout** (photos): `cardMargin={60}` + `cardPadding={20}`
287
+
288
+ ## Example App
289
+
290
+ **[Tomo TV](https://github.com/keiver/tomotv)** is a tvOS application that uses `expo-tvos-search` in a real-world Jellyfin client. It demonstrates the search component integrated with a complete media browsing experience, including:
291
+
292
+ - Search interaction with tvOS remote
293
+ - Focus navigation through results
294
+ - Integration with a live media library
295
+ - Complete setup instructions and screenshots
296
+
297
+ Check out Tomo TV to see `expo-tvos-search` in action and reference its implementation for your own projects.
298
+
299
+ ## See it in action:
300
+
301
+ <p align="center">
302
+ <img src="screenshots/expo-tvos-search.gif" width="700" alt="expo-tvos-search screen in action" loading="lazy" />
303
+ </p>
304
+
91
305
  ## Props
92
306
 
93
307
  | Prop | Type | Default | Description |
94
308
  |------|------|---------|-------------|
95
309
  | `results` | `SearchResult[]` | `[]` | Array of search results |
96
310
  | `columns` | `number` | `5` | Number of columns in the grid |
97
- | `placeholder` | `string` | `"Search..."` | Search field placeholder |
311
+ | `placeholder` | `string` | `"Search movies and videos..."` | Search field placeholder |
98
312
  | `isLoading` | `boolean` | `false` | Shows loading indicator |
99
313
  | `showTitle` | `boolean` | `false` | Show title below each result |
100
314
  | `showSubtitle` | `boolean` | `false` | Show subtitle below title |
@@ -103,8 +317,23 @@ function SearchScreen() {
103
317
  | `showTitleOverlay` | `boolean` | `true` | Show title overlay with gradient at bottom of card |
104
318
  | `enableMarquee` | `boolean` | `true` | Enable marquee scrolling for long titles |
105
319
  | `marqueeDelay` | `number` | `1.5` | Delay in seconds before marquee starts |
320
+ | `emptyStateText` | `string` | `"Search for movies and videos"` | Text shown when search field is empty |
321
+ | `searchingText` | `string` | `"Searching..."` | Text shown during search |
322
+ | `noResultsText` | `string` | `"No results found"` | Text shown when no results found |
323
+ | `noResultsHintText` | `string` | `"Try a different search term"` | Hint text below no results message |
324
+ | `textColor` | `string` | system default | Color for text and UI elements (hex format, e.g., "#FFFFFF") |
325
+ | `accentColor` | `string` | `"#FFC312"` | Accent color for focused elements (hex format, e.g., "#FFC312") |
326
+ | `cardWidth` | `number` | `280` | Width of each result card in points |
327
+ | `cardHeight` | `number` | `420` | Height of each result card in points |
328
+ | `imageContentMode` | `'fill' \| 'fit' \| 'contain'` | `'fill'` | How images fill the card: `fill` (crop to fill), `fit`/`contain` (letterbox) |
329
+ | `cardMargin` | `number` | `40` | Spacing between cards in the grid (horizontal and vertical) |
330
+ | `cardPadding` | `number` | `16` | Padding inside the card for overlay content (title/subtitle) |
331
+ | `overlayTitleSize` | `number` | `20` | Font size for title text in the blur overlay (when showTitleOverlay is true) |
106
332
  | `onSearch` | `function` | required | Called when search text changes |
107
333
  | `onSelectItem` | `function` | required | Called when result is selected |
334
+ | `onError` | `function` | optional | Called when errors occur (image loading failures, validation errors) |
335
+ | `onValidationWarning` | `function` | optional | Called for non-fatal warnings (truncated fields, clamped values, invalid URLs) |
336
+ | `style` | `ViewStyle` | optional | Style object for the view container |
108
337
 
109
338
  ## SearchResult Type
110
339
 
@@ -117,11 +346,86 @@ interface SearchResult {
117
346
  }
118
347
  ```
119
348
 
349
+ ## Result Handling
350
+
351
+ The native implementation applies the following validation and constraints:
352
+
353
+ - **Maximum results**: The results array is capped at 500 items. Any results beyond this limit are silently ignored.
354
+ - **Required fields**: Results with empty `id` or `title` are automatically filtered out and not displayed.
355
+ - **Image URL schemes**: Only HTTP and HTTPS URLs are accepted for `imageUrl`. Other URL schemes (e.g., `file://`, `data:`) are rejected.
356
+ - **HTTPS recommended**: HTTP URLs may be blocked by App Transport Security on tvOS unless explicitly allowed in Info.plist.
357
+
358
+ ## Focus Handling - Do's and Don'ts
359
+
360
+ The native `.searchable` modifier manages focus automatically. Here's what to do and what to avoid:
361
+
362
+ ### ✅ Do: Render directly in your screen
363
+
364
+ ```tsx
365
+ function SearchScreen() {
366
+ return (
367
+ <TvosSearchView
368
+ results={results}
369
+ onSearch={handleSearch}
370
+ onSelectItem={handleSelect}
371
+ style={{ flex: 1 }}
372
+ />
373
+ );
374
+ }
375
+ ```
376
+
377
+ ### ❌ Don't: Wrap in focusable containers
378
+
379
+ ```tsx
380
+ // ❌ WRONG - breaks focus navigation
381
+ function SearchScreen() {
382
+ return (
383
+ <Pressable> {/* Don't wrap in Pressable */}
384
+ <TvosSearchView ... />
385
+ </Pressable>
386
+ );
387
+ }
388
+
389
+ // ❌ WRONG - interferes with native focus
390
+ function SearchScreen() {
391
+ return (
392
+ <TouchableOpacity> {/* Don't wrap in TouchableOpacity */}
393
+ <TvosSearchView ... />
394
+ </TouchableOpacity>
395
+ );
396
+ }
397
+ ```
398
+
399
+ **Why this breaks**: Focusable wrappers steal focus from the native SwiftUI search container, which breaks directional navigation.
400
+
401
+ ### ✅ Do: Use non-interactive containers
402
+
403
+ ```tsx
404
+ // ✅ CORRECT - View is not focusable
405
+ function SearchScreen() {
406
+ return (
407
+ <View style={{ flex: 1, backgroundColor: '#000' }}>
408
+ <TvosSearchView ... />
409
+ </View>
410
+ );
411
+ }
412
+
413
+ // ✅ CORRECT - SafeAreaView is not focusable
414
+ function SearchScreen() {
415
+ return (
416
+ <SafeAreaView style={{ flex: 1 }}>
417
+ <TvosSearchView ... />
418
+ </SafeAreaView>
419
+ );
420
+ }
421
+ ```
422
+
120
423
  ## Requirements
121
424
 
425
+ - Node.js 18+
122
426
  - Expo SDK 51+
123
- - tvOS 15.0+
124
- - React Native TVOS
427
+ - tvOS 15+
428
+ - Project configured for tvOS (`react-native-tvos` + `@react-native-tvos/config-tv`)
125
429
 
126
430
  ## Troubleshooting
127
431
 
@@ -135,10 +439,12 @@ EXPO_TV=1 npx expo prebuild --clean
135
439
  npx expo run:ios
136
440
  ```
137
441
 
442
+ **Note:** Expo Go doesn't support this. Build a dev client or native build instead.
443
+
138
444
  ### Images not loading
139
445
 
140
446
  1. Verify your image URLs are HTTPS (HTTP may be blocked by App Transport Security)
141
- 2. Check that the Jellyfin API key is included in the URL query parameters
447
+ 2. Ensure required authentication parameters are included in image URLs
142
448
  3. For local development, ensure your server is accessible from the Apple TV
143
449
 
144
450
  ### Focus issues
@@ -172,36 +478,18 @@ Tests cover:
172
478
  - Component rendering when native module is unavailable
173
479
  - Event structure validation
174
480
 
175
- ### Best Practices
176
-
177
- For optimal performance:
178
- - **Debounce search input**: Wait 300-500ms after typing stops before calling your API
179
- - **Batch result updates**: Update the entire `results` array at once rather than incrementally
180
- - **Limit image sizes**: Use appropriately sized poster images (280x420 is the card size)
181
- - **Cap result count**: Consider limiting to 50-100 results for smooth scrolling
182
-
183
- ## Accessibility
184
-
185
- ### Built-in Support
186
-
187
- The native SwiftUI implementation provides accessibility features automatically:
188
- - **Focus management**: tvOS focus system handles navigation
189
- - **VoiceOver**: Cards announce title and subtitle
190
- - **Button semantics**: Cards are properly identified as interactive elements
191
- - **Focus indicators**: Visual feedback for focused state
192
-
193
- ### Remote Navigation
481
+ ## Contributing
194
482
 
195
- The native `.searchable` modifier provides standard tvOS navigation:
196
- - **Swipe up/down**: Move between search field and results
197
- - **Swipe left/right**: Navigate between grid items
198
- - **Click (select)**: Open the focused result
199
- - **Menu button**: Exit search or navigate back
483
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:
484
+ - Code of conduct
485
+ - Development setup
486
+ - Testing requirements
487
+ - Commit message conventions
488
+ - Pull request process
200
489
 
201
- Built for [TomoTV](https://github.com/keiver/tomotv), a Jellyfin client for Apple TV.
490
+ ### Adding New Props
202
491
 
203
- Swift documentation references:
204
- - [.searchable modifier](https://developer.apple.com/documentation/SwiftUI/Creating-a-tvOS-media-catalog-app-in-SwiftUI)
492
+ If you're adding new props to the library, follow the comprehensive checklist in [.claude/CLAUDE-adding-new-props.md](.claude/CLAUDE-adding-new-props.md). This memory bank provides a 9-step guide ensuring props are properly wired from TypeScript through to Swift rendering.
205
493
 
206
494
  ## License
207
495
 
package/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { ViewStyle } from "react-native";
1
+ /// <reference types="react" />
2
+ import type { ViewStyle } from "react-native";
3
3
  /**
4
4
  * Event payload for search text changes.
5
5
  * Fired when the user types in the native search field.
@@ -20,6 +20,38 @@ export interface SelectItemEvent {
20
20
  id: string;
21
21
  };
22
22
  }
23
+ /**
24
+ * Categories of errors that can occur in the search view.
25
+ */
26
+ export type SearchViewErrorCategory = "module_unavailable" | "validation_failed" | "image_load_failed" | "unknown";
27
+ /**
28
+ * Event payload for error callbacks.
29
+ * Provides details about errors that occur during search view operations.
30
+ */
31
+ export interface SearchViewErrorEvent {
32
+ nativeEvent: {
33
+ /** Category of the error for programmatic handling */
34
+ category: SearchViewErrorCategory;
35
+ /** Human-readable error message */
36
+ message: string;
37
+ /** Optional additional context (e.g., result ID, URL) */
38
+ context?: string;
39
+ };
40
+ }
41
+ /**
42
+ * Event payload for validation warnings.
43
+ * Non-fatal issues like truncated fields or clamped values.
44
+ */
45
+ export interface ValidationWarningEvent {
46
+ nativeEvent: {
47
+ /** Type of validation warning */
48
+ type: "field_truncated" | "value_clamped" | "url_invalid" | "validation_failed";
49
+ /** Human-readable warning message */
50
+ message: string;
51
+ /** Optional additional context */
52
+ context?: string;
53
+ };
54
+ }
23
55
  /**
24
56
  * Represents a single search result displayed in the grid.
25
57
  */
@@ -69,7 +101,7 @@ export interface TvosSearchViewProps {
69
101
  columns?: number;
70
102
  /**
71
103
  * Placeholder text shown in the search field when empty.
72
- * @default "Search..."
104
+ * @default "Search movies and videos..."
73
105
  */
74
106
  placeholder?: string;
75
107
  /**
@@ -141,6 +173,61 @@ export interface TvosSearchViewProps {
141
173
  * @default "Try a different search term"
142
174
  */
143
175
  noResultsHintText?: string;
176
+ /**
177
+ * Color for text and UI elements in the search interface.
178
+ * Hex color string (e.g., "#FFFFFF", "#E5E5E5").
179
+ * @default Uses system default based on userInterfaceStyle
180
+ * @example "#E5E5E5" for light gray text on dark background
181
+ */
182
+ textColor?: string;
183
+ /**
184
+ * Accent color for focused elements and highlights.
185
+ * Hex color string (e.g., "#FFC312").
186
+ * @default "#FFC312" (gold)
187
+ * @example "#E50914" for Netflix red
188
+ */
189
+ accentColor?: string;
190
+ /**
191
+ * Width of each result card in points.
192
+ * Allows customization for portrait, landscape, or square layouts.
193
+ * @default 280
194
+ * @example 420 for landscape cards
195
+ */
196
+ cardWidth?: number;
197
+ /**
198
+ * Height of each result card in points.
199
+ * Allows customization for portrait, landscape, or square layouts.
200
+ * @default 420
201
+ * @example 240 for landscape cards (16:9 ratio with width=420)
202
+ */
203
+ cardHeight?: number;
204
+ /**
205
+ * How the image fills the card area.
206
+ * - 'fill': Image fills entire card, may crop (default)
207
+ * - 'fit': Image fits within card, may show letterboxing
208
+ * - 'contain': Same as fit (alias for consistency)
209
+ * @default "fill"
210
+ */
211
+ imageContentMode?: 'fill' | 'fit' | 'contain';
212
+ /**
213
+ * Spacing between cards in the grid layout (both horizontal and vertical).
214
+ * @default 40
215
+ * @example 60 for spacious layouts, 20 for compact grids
216
+ */
217
+ cardMargin?: number;
218
+ /**
219
+ * Padding inside the card for overlay content (title, subtitle).
220
+ * @default 16
221
+ * @example 20 for more breathing room, 12 for compact cards
222
+ */
223
+ cardPadding?: number;
224
+ /**
225
+ * Font size for title in the blur overlay (when showTitleOverlay is true).
226
+ * Allows customization of overlay text size for different card layouts.
227
+ * @default 20
228
+ * @example 18 for smaller cards, 24 for larger cards
229
+ */
230
+ overlayTitleSize?: number;
144
231
  /**
145
232
  * Callback fired when the search text changes.
146
233
  * Debounce this handler to avoid excessive API calls.
@@ -151,6 +238,30 @@ export interface TvosSearchViewProps {
151
238
  * Use the `id` from the event to identify which result was selected.
152
239
  */
153
240
  onSelectItem: (event: SelectItemEvent) => void;
241
+ /**
242
+ * Optional callback fired when errors occur.
243
+ * Use this to monitor and log issues in production.
244
+ * @example
245
+ * ```tsx
246
+ * onError={(e) => {
247
+ * const { category, message, context } = e.nativeEvent;
248
+ * logger.error(`Search error [${category}]: ${message}`, { context });
249
+ * }}
250
+ * ```
251
+ */
252
+ onError?: (event: SearchViewErrorEvent) => void;
253
+ /**
254
+ * Optional callback fired for non-fatal validation warnings.
255
+ * Examples: truncated fields, clamped values, invalid URLs.
256
+ * @example
257
+ * ```tsx
258
+ * onValidationWarning={(e) => {
259
+ * const { type, message } = e.nativeEvent;
260
+ * console.warn(`Validation warning [${type}]: ${message}`);
261
+ * }}
262
+ * ```
263
+ */
264
+ onValidationWarning?: (event: ValidationWarningEvent) => void;
154
265
  /**
155
266
  * Optional style for the view container.
156
267
  */
@@ -189,7 +300,7 @@ export interface TvosSearchViewProps {
189
300
  * @param props - Component props
190
301
  * @returns The native search view on tvOS, or `null` if unavailable
191
302
  */
192
- export declare function TvosSearchView(props: TvosSearchViewProps): React.JSX.Element | null;
303
+ export declare function TvosSearchView(props: TvosSearchViewProps): JSX.Element | null;
193
304
  /**
194
305
  * Checks if the native tvOS search component is available.
195
306
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAY,MAAM,cAAc,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE;QACX,0DAA0D;QAC1D,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE;QACX,0DAA0D;QAC1D,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,OAAO,EAAE,YAAY,EAAE,CAAC;IAExB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAEvC;;;OAGG;IACH,YAAY,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAE/C;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAmBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,4BAKxD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}