expo-tvos-search 1.3.1 → 1.4.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.3.2] - 2026-01-25
9
+
10
+ ### Added
11
+ - **Apple TV hardware keyboard support**: New `onSearchFieldFocused` and `onSearchFieldBlurred` event callbacks
12
+ - Enables proper Siri Remote keyboard input on physical Apple TV devices
13
+ - Works with `TVEventControl.disableGestureHandlersCancelTouches()` for JS-side handling
14
+ - Native Swift implementation automatically disables tap/press gesture recognizers when search field is focused
15
+ - Keeps swipe/pan recognizers enabled for keyboard navigation
16
+ - TypeScript type `SearchFieldFocusEvent` for the new focus events
17
+ - **Data URI support for images**: `imageUrl` now accepts `data:` URIs (e.g., `data:image/png;base64,...`) in addition to HTTP/HTTPS URLs
18
+ - Enables inline base64-encoded images without external requests
19
+ - Useful for cached thumbnails or placeholder images
20
+
21
+ ### Fixed
22
+ - Siri Remote click events not reaching native SwiftUI search field on real Apple TV hardware
23
+ - React Native gesture handlers intercepting keyboard input before it reached SwiftUI `.searchable` modifier
24
+
25
+ ## [1.3.1] - 2026-01-21
26
+
27
+ ### Fixed
28
+ - Minor stability improvements
29
+ - Documentation updates
30
+
31
+ ## [1.3.0] - 2026-01-20
32
+
33
+ ### Added
34
+ - `cardMargin` prop - Customize spacing between cards in the grid (default: 40)
35
+ - `cardPadding` prop - Customize padding inside cards for overlay content (default: 16)
36
+ - `overlayTitleSize` prop - Customize font size for title in blur overlay (default: 20)
37
+
38
+ ### Changed
39
+ - Improved grid layout flexibility with customizable spacing
40
+
41
+ ## [1.2.3] - 2026-01-20
42
+ - Patch release for npm publish workflow fix.
43
+
44
+ ### Added
45
+ - GitHub Actions workflow step for npm publish, trigger on PR with labels: `release:patch`, `release:minor`, `release:major`
46
+
47
+ ## [1.2.0] - 2026-01-17
48
+
49
+ ### Added
50
+ - `onError` callback - Receive notifications for fatal errors (image loading failures, validation errors)
51
+ - `onValidationWarning` callback - Receive non-fatal warnings (truncated fields, clamped values, invalid URLs)
52
+ - `SearchViewErrorEvent` and `ValidationWarningEvent` TypeScript types
53
+ - JSDoc documentation for all TypeScript exports
54
+ - `SearchEvent` and `SelectItemEvent` type interfaces for improved type safety
55
+ - Coverage thresholds (80% global) in Jest configuration
56
+ - Explicit tvOS modules section in expo-module.config.json
57
+ - Performance and Accessibility documentation sections in README
58
+ - Debug logging for skipped invalid results (id or title missing/empty)
59
+
60
+ ### Changed
61
+ - Input validation: `columns` prop now clamps between 1-10 (was: min 1 only)
62
+ - Input validation: `topInset` prop now clamps between 0-500 (was: min 0 only)
63
+ - Input validation: `marqueeDelay` prop now clamps between 0-60 seconds (was: min 0 only)
64
+ - Input validation: `placeholder` prop now limited to 500 characters
65
+ - Input validation: `results` array now limited to 500 items max
66
+ - MarqueeAnimationCalculator guards against division by zero
67
+ - MarqueeAnimationCalculator ensures non-negative values for spacing and distances
68
+
69
+ ### Security
70
+ - URL scheme validation: `imageUrl` now only accepts HTTP/HTTPS schemes
71
+ - String length limits: `id`, `title`, `subtitle` clamped to 500 characters each
72
+ - Empty string rejection: Results with empty `id` or `title` are now skipped
73
+ - Added `@types/react-native` and build dependencies for TypeScript compilation
74
+
75
+ ### Fixed
76
+ - TypeScript build now compiles successfully with `jsx: "react"` option
77
+ - Empty strings no longer pass validation for required fields
78
+
79
+ ## [1.1.0] - 2026-01-15
80
+
81
+ ### Added
82
+ - Marquee scrolling animation for long titles that overflow card width
83
+ - `enableMarquee` prop to toggle marquee behavior (default: true)
84
+ - `marqueeDelay` prop to control delay before scrolling starts (default: 1.5s)
85
+ - `showTitleOverlay` prop for gradient title overlay at bottom of cards
86
+ - MarqueeAnimationCalculator for testable animation logic
87
+
88
+ ### Changed
89
+ - Title display now uses overlay by default instead of below-card text
90
+ - Improved focus state visual feedback
91
+
92
+ ## [1.0.0] - 2026-01-10
93
+
94
+ ### Added
95
+ - Initial release
96
+ - Native SwiftUI search view with `.searchable` modifier
97
+ - Grid layout for search results with configurable columns
98
+ - `TvosSearchView` React Native component
99
+ - `isNativeSearchAvailable()` utility function
100
+ - Core props:
101
+ - `results`, `columns`, `placeholder`, `isLoading`
102
+ - `cardWidth`, `cardHeight` - Customizable card dimensions
103
+ - `imageContentMode` - Image scaling (`fill`, `fit`, `contain`)
104
+ - `textColor`, `accentColor` - Color customization
105
+ - `showTitle`, `showSubtitle`, `showFocusBorder` - Display options
106
+ - `topInset` - Tab bar clearance
107
+ - `emptyStateText`, `searchingText`, `noResultsText`, `noResultsHintText` - Text customization
108
+ - `onSearch` and `onSelectItem` event callbacks
109
+ - Automatic fallback when native module is unavailable
110
+ - TypeScript type definitions (`SearchResult`, `TvosSearchViewProps`, etc.)
111
+ - Comprehensive test suite
package/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  [![npm version](https://img.shields.io/npm/v/expo-tvos-search.svg)](https://www.npmjs.com/package/expo-tvos-search)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
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
6
 
8
7
  A native tvOS search component for Expo and React Native using SwiftUI's `.searchable` modifier. Provides the native tvOS search experience with automatic focus handling, remote control support, and flexible customization for media apps.
9
8
 
@@ -13,7 +12,7 @@ A native tvOS search component for Expo and React Native using SwiftUI's `.searc
13
12
  - React Native tvOS 0.71+
14
13
 
15
14
  <p align="center">
16
- <img src="screenshots/demo-mini.png" width="80%" alt="Demo Mini screen for expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/>
15
+ <img src="screenshots/default.png" alt="expo-tvos-search fullscreen native tvOS search component" style="border-radius: 16px;max-width: 100%;"/>
17
16
  </p>
18
17
 
19
18
  ## Installation
@@ -28,55 +27,18 @@ Or install from GitHub:
28
27
  npx expo install github:keiver/expo-tvos-search
29
28
  ```
30
29
 
31
- Then follow the **tvOS prerequisites** below and rebuild your native project.
30
+ ## Prerequisites for tvOS Builds
32
31
 
33
- ## Quick Start
32
+ Your project must be configured for React Native tvOS to use this module.
34
33
 
35
- ### Try the Demo App
34
+ ### 1. Install react-native-tvos
36
35
 
37
- **[expo-tvos-search-demo](https://github.com/keiver/expo-tvos-search-demo)** - Comprehensive showcase with 7 tabs demonstrating all library features:
38
-
39
- - **Default** - 4-column grid with custom colors
40
- - **Portrait** - Netflix-style tall cards (280×420)
41
- - **Landscape** - Wide 16:9 cards (500×280)
42
- - **Mini** - Compact 5-column layout (240×360)
43
- - **External Title** - Titles displayed below cards
44
- - **Minimal** - Bare minimum setup (5 props)
45
- - **Help** - Feature overview and usage guide
46
-
47
- Clone and run:
48
36
  ```bash
49
- git clone https://github.com/keiver/expo-tvos-search-demo.git
50
- cd expo-tvos-search-demo
51
- npm install
52
- npm run prebuild
53
- npm run tvos
54
- ```
55
-
56
- The demo uses a planet search theme with 8 planets (Mercury to Neptune) and demonstrates all library features with real working code.
57
-
58
- ## Prerequisites for tvOS Builds (Expo)
59
-
60
- Your project must be configured for React Native tvOS to build and run this module.
61
-
62
- **Quick Checklist:**
63
-
64
- - ✅ `react-native-tvos` in use
65
- - ✅ `@react-native-tvos/config-tv` installed + added to Expo plugins
66
- - ✅ Run prebuild with `EXPO_TV=1`
67
-
68
- ### 1. Swap to react-native-tvos
69
-
70
- Replace `react-native` with the [tvOS fork](https://github.com/react-native-tvos/react-native-tvos):
71
-
72
- ```bash
73
- npm remove react-native && npm install react-native-tvos@latest
37
+ npm install react-native-tvos@latest
74
38
  ```
75
39
 
76
40
  ### 2. Install the tvOS config plugin
77
41
 
78
- Install:
79
-
80
42
  ```bash
81
43
  npx expo install @react-native-tvos/config-tv
82
44
  ```
@@ -91,32 +53,9 @@ Then add the plugin in `app.json` / `app.config.js`:
91
53
  }
92
54
  ```
93
55
 
94
- ### 3. Generate native projects with tvOS enabled
95
-
96
- ```bash
97
- EXPO_TV=1 npx expo prebuild --clean
98
- ```
99
-
100
- Then run:
101
-
102
- ```bash
103
- npx expo run:ios
104
- ```
105
-
106
-
107
56
  ## Usage
108
57
 
109
- ### Minimal Example
110
-
111
- For the absolute minimum setup, see the [Minimal tab](https://github.com/keiver/expo-tvos-search-demo/blob/main/app/(tabs)/minimal.tsx) in the demo app.
112
-
113
- <p align="center">
114
- <img src="screenshots/demo-default.png" width="80%" alt="Minimal demo screen for expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/><br/>
115
- </p>
116
-
117
- ### Complete Example
118
-
119
- This example from the demo's [Portrait tab](https://github.com/keiver/expo-tvos-search-demo/blob/main/app/(tabs)/portrait.tsx) shows a complete implementation with best practices:
58
+ This example from the demo's [Portrait tab](https://github.com/keiver/expo-tvos-search-demo/blob/main/app/(tabs)/portrait.tsx) shows a complete implementation:
120
59
 
121
60
  ```tsx
122
61
  import { useState } from 'react';
@@ -136,7 +75,7 @@ const PLANETS: SearchResult[] = [
136
75
  subtitle: 'Our home planet, the only known world to harbor life',
137
76
  imageUrl: require('./assets/planets/earth.webp'),
138
77
  },
139
- // ... more planets
78
+ // ... all planets
140
79
  ];
141
80
 
142
81
  export default function SearchScreen() {
@@ -154,7 +93,7 @@ export default function SearchScreen() {
154
93
 
155
94
  setIsLoading(true);
156
95
 
157
- // Debounce search (300ms)
96
+ // Debounce dummy search (300ms)
158
97
  setTimeout(() => {
159
98
  const filtered = PLANETS.filter(
160
99
  planet =>
@@ -194,7 +133,7 @@ export default function SearchScreen() {
194
133
  accentColor="#E50914"
195
134
  cardWidth={280}
196
135
  cardHeight={420}
197
- overlayTitleSize={18} // v1.3.0 - control title font size
136
+ overlayTitleSize={18}
198
137
  style={{ flex: 1 }}
199
138
  />
200
139
  </LinearGradient>
@@ -202,9 +141,13 @@ export default function SearchScreen() {
202
141
  }
203
142
  ```
204
143
 
205
- ## Layout Styles
144
+ <p align="center">
145
+ <img src="screenshots/no-results.png" alt="No results for search screen using expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/><br/>
146
+ </p>
147
+
148
+ ## Demo App and Common Configurations
206
149
 
207
- Explore all 7 configurations in the [demo app](https://github.com/keiver/expo-tvos-search-demo).
150
+ Explore all configurations in the [demo app](https://github.com/keiver/expo-tvos-search-demo).
208
151
 
209
152
  ### Portrait Cards
210
153
 
@@ -268,6 +211,28 @@ Explore all 7 configurations in the [demo app](https://github.com/keiver/expo-tv
268
211
  />
269
212
  ```
270
213
 
214
+ ### Apple TV Hardware Keyboard Support (v1.3.2+)
215
+
216
+ On real Apple TV hardware, React Native's `RCTTVRemoteHandler` installs gesture recognizers that consume Siri Remote presses before they reach SwiftUI's `.searchable` text field, which prevents keyboard input. When the search field gains focus, this component temporarily disables touch cancellation using the official `react-native-tvos` notification API, and also disables tap/long-press recognizers from parent views (to cover cases like `react-native-gesture-handler`). Swipe and pan recognizers stay active for keyboard navigation. Everything is restored when focus leaves the field. This only applies to physical devices -- the Simulator doesn't need it.
217
+
218
+ If this interferes with gesture handling in your app, please [open an issue](https://github.com/keiver/expo-tvos-search/issues) so we can sort it out.
219
+
220
+ For additional control, you can use the focus callbacks with `TVEventControl`:
221
+
222
+ ```tsx
223
+ import { TVEventControl } from 'react-native';
224
+
225
+ <TvosSearchView
226
+ onSearchFieldFocused={() => {
227
+ TVEventControl.disableGestureHandlersCancelTouches();
228
+ }}
229
+ onSearchFieldBlurred={() => {
230
+ TVEventControl.enableGestureHandlersCancelTouches();
231
+ }}
232
+ // ... other props
233
+ />
234
+ ```
235
+
271
236
  ### Customizing Colors and Card Dimensions
272
237
 
273
238
  ```tsx
@@ -310,101 +275,8 @@ Explore all 7 configurations in the [demo app](https://github.com/keiver/expo-tv
310
275
  />
311
276
  ```
312
277
 
313
- ## TypeScript Support
314
-
315
- The library provides comprehensive type definitions for all events and props.
316
-
317
- ### Event Types
318
-
319
- ```typescript
320
- import type {
321
- SearchEvent,
322
- SelectItemEvent,
323
- SearchViewErrorEvent,
324
- ValidationWarningEvent,
325
- SearchResult,
326
- } from 'expo-tvos-search';
327
-
328
- // Search event - fired on text change
329
- interface SearchEvent {
330
- nativeEvent: {
331
- query: string;
332
- };
333
- }
334
-
335
- // Selection event - fired when result is selected
336
- interface SelectItemEvent {
337
- nativeEvent: {
338
- id: string;
339
- };
340
- }
341
-
342
- // Error event - fatal errors (v1.2.0+)
343
- interface SearchViewErrorEvent {
344
- nativeEvent: {
345
- category: 'module_unavailable' | 'validation_failed' | 'image_load_failed' | 'unknown';
346
- message: string;
347
- context?: string;
348
- };
349
- }
350
-
351
- // Validation warning - non-fatal issues (v1.2.0+)
352
- interface ValidationWarningEvent {
353
- nativeEvent: {
354
- type: 'field_truncated' | 'value_clamped' | 'url_invalid' | 'validation_failed';
355
- message: string;
356
- context?: string;
357
- };
358
- }
359
-
360
- // Search result shape
361
- interface SearchResult {
362
- id: string; // Required, max 500 chars
363
- title: string; // Required, max 500 chars
364
- subtitle?: string; // Optional, max 500 chars
365
- imageUrl?: string; // Optional, HTTPS recommended
366
- }
367
- ```
368
-
369
- ### Typed Usage
370
-
371
- ```typescript
372
- const handleSearch = (event: SearchEvent) => {
373
- const query = event.nativeEvent.query;
374
- // TypeScript knows query is a string
375
- };
376
-
377
- const handleSelect = (event: SelectItemEvent) => {
378
- const id = event.nativeEvent.id;
379
- // TypeScript knows id is a string
380
- };
381
-
382
- const handleError = (event: SearchViewErrorEvent) => {
383
- const { category, message, context } = event.nativeEvent;
384
- // Full autocomplete for category values
385
- if (category === 'image_load_failed') {
386
- logger.warn(`Image failed to load: ${message}`, { context });
387
- }
388
- };
389
- ```
390
-
391
- ## Demo Apps & Examples
392
-
393
- ### Official Demo App
394
-
395
- **[expo-tvos-search-demo](https://github.com/keiver/expo-tvos-search-demo)** - Complete working examples with 7 different layout styles. Browse the [source code](https://github.com/keiver/expo-tvos-search-demo/tree/main/app/(tabs)) for each configuration.
396
-
397
- ### Apps Using This Library
398
-
399
- **[Tomo TV](https://github.com/keiver/tomotv)** - Full-featured tvOS Jellyfin client
400
- - Real-world integration with media library API
401
- - Advanced search with live server calls
402
- - Complete authentication and navigation flow
403
-
404
- ## See it in action:
405
-
406
278
  <p align="center">
407
- <img src="screenshots/expo-tvos-search.gif" width="700" alt="expo-tvos-search screen in action" loading="lazy" />
279
+ <img src="screenshots/results.png" alt="Results for search screen using expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/><br/>
408
280
  </p>
409
281
 
410
282
  ## Props
@@ -415,7 +287,8 @@ const handleError = (event: SearchViewErrorEvent) => {
415
287
  |------|------|---------|-------------|
416
288
  | `results` | `SearchResult[]` | `[]` | Array of search results |
417
289
  | `columns` | `number` | `5` | Number of columns in the grid |
418
- | `placeholder` | `string` | `"Search movies and videos..."` | Search field placeholder |
290
+ | `placeholder` | `string` | `"Search..."` | Search field placeholder |
291
+ | `searchText` | `string` | — | Programmatically set search field text (restore state, deep links) |
419
292
  | `isLoading` | `boolean` | `false` | Shows loading indicator |
420
293
 
421
294
  ### Card Dimensions & Spacing
@@ -457,7 +330,7 @@ const handleError = (event: SearchViewErrorEvent) => {
457
330
 
458
331
  | Prop | Type | Default | Description |
459
332
  |------|------|---------|-------------|
460
- | `emptyStateText` | `string` | `"Search for movies and videos"` | Text shown when search field is empty |
333
+ | `emptyStateText` | `string` | `"Search your library"` | Text shown when search field is empty |
461
334
  | `searchingText` | `string` | `"Searching..."` | Text shown during search |
462
335
  | `noResultsText` | `string` | `"No results found"` | Text shown when no results found |
463
336
  | `noResultsHintText` | `string` | `"Try a different search term"` | Hint text below no results message |
@@ -470,6 +343,8 @@ const handleError = (event: SearchViewErrorEvent) => {
470
343
  | `onSelectItem` | `function` | required | Called when result is selected |
471
344
  | `onError` | `function` | optional | **(v1.2.0+)** Called when errors occur (image loading failures, validation errors) |
472
345
  | `onValidationWarning` | `function` | optional | **(v1.2.0+)** Called for non-fatal warnings (truncated fields, clamped values, invalid URLs) |
346
+ | `onSearchFieldFocused` | `function` | optional | **(v1.3.2+)** Called when native search field gains focus. Use with `TVEventControl` for Apple TV hardware keyboard support. |
347
+ | `onSearchFieldBlurred` | `function` | optional | **(v1.3.2+)** Called when native search field loses focus. Use to re-enable gesture handlers. |
473
348
 
474
349
  ### Other
475
350
 
@@ -477,127 +352,15 @@ const handleError = (event: SearchViewErrorEvent) => {
477
352
  |------|------|---------|-------------|
478
353
  | `style` | `ViewStyle` | optional | Style object for the view container |
479
354
 
480
- ## SearchResult Type
481
-
482
- ```typescript
483
- interface SearchResult {
484
- id: string;
485
- title: string;
486
- subtitle?: string;
487
- imageUrl?: string;
488
- }
489
- ```
490
-
491
355
  ## Result Handling
492
356
 
493
357
  The native implementation applies the following validation and constraints:
494
358
 
495
359
  - **Maximum results**: The results array is capped at 500 items. Any results beyond this limit are silently ignored.
496
360
  - **Required fields**: Results with empty `id` or `title` are automatically filtered out and not displayed.
497
- - **Image URL schemes**: Only HTTP and HTTPS URLs are accepted for `imageUrl`. Other URL schemes (e.g., `file://`, `data:`) are rejected.
361
+ - **Image URL schemes**: HTTP, HTTPS, and `data:` URIs are accepted for `imageUrl`. Other URL schemes (e.g., `file://`) are rejected.
498
362
  - **HTTPS recommended**: HTTP URLs may be blocked by App Transport Security on tvOS unless explicitly allowed in Info.plist.
499
363
 
500
- ## Focus Handling - Do's and Don'ts
501
-
502
- The native `.searchable` modifier manages focus automatically. Here's what to do and what to avoid:
503
-
504
- ### ✅ Do: Render directly in your screen
505
-
506
- ```tsx
507
- function SearchScreen() {
508
- return (
509
- <TvosSearchView
510
- results={results}
511
- onSearch={handleSearch}
512
- onSelectItem={handleSelect}
513
- style={{ flex: 1 }}
514
- />
515
- );
516
- }
517
- ```
518
-
519
- ### ❌ Don't: Wrap in focusable containers
520
-
521
- ```tsx
522
- // ❌ WRONG - breaks focus navigation
523
- function SearchScreen() {
524
- return (
525
- <Pressable> {/* Don't wrap in Pressable */}
526
- <TvosSearchView ... />
527
- </Pressable>
528
- );
529
- }
530
-
531
- // ❌ WRONG - interferes with native focus
532
- function SearchScreen() {
533
- return (
534
- <TouchableOpacity> {/* Don't wrap in TouchableOpacity */}
535
- <TvosSearchView ... />
536
- </TouchableOpacity>
537
- );
538
- }
539
- ```
540
-
541
- **Why this breaks**: Focusable wrappers steal focus from the native SwiftUI search container, which breaks directional navigation.
542
-
543
- ### ✅ Do: Use non-interactive containers
544
-
545
- ```tsx
546
- // ✅ CORRECT - View is not focusable
547
- function SearchScreen() {
548
- return (
549
- <View style={{ flex: 1, backgroundColor: '#000' }}>
550
- <TvosSearchView ... />
551
- </View>
552
- );
553
- }
554
-
555
- // ✅ CORRECT - SafeAreaView is not focusable
556
- function SearchScreen() {
557
- return (
558
- <SafeAreaView style={{ flex: 1 }}>
559
- <TvosSearchView ... />
560
- </SafeAreaView>
561
- );
562
- }
563
- ```
564
-
565
- ## Troubleshooting
566
-
567
- ### Native module not found
568
-
569
- If you see `requireNativeViewManager("ExpoTvosSearch") returned null`, the native module hasn't been built:
570
-
571
- ```bash
572
- # Clean and rebuild with tvOS support
573
- EXPO_TV=1 npx expo prebuild --clean
574
- npx expo run:ios
575
- ```
576
-
577
- **Note:** Expo Go doesn't support this. Build a dev client or native build instead.
578
-
579
- ### Images not loading
580
-
581
- 1. Verify your image URLs are HTTPS (HTTP may be blocked by App Transport Security)
582
- 2. Ensure required authentication parameters are included in image URLs
583
- 3. For local development, ensure your server is accessible from the Apple TV
584
-
585
- ### Focus issues
586
-
587
- If focus doesn't move correctly:
588
-
589
- 1. Ensure `columns` prop matches your layout (default: 5)
590
- 2. Check `topInset` if the first row is hidden under the tab bar
591
- 3. The native `.searchable` modifier handles focus automatically - avoid wrapping in focusable containers
592
-
593
- ### Marquee not scrolling
594
-
595
- If long titles don't scroll when focused:
596
-
597
- 1. Verify `enableMarquee={true}` (default)
598
- 2. Check `marqueeDelay` - scrolling starts after this delay (default: 1.5s)
599
- 3. Text only scrolls if it overflows the card width
600
-
601
364
  ## Testing
602
365
 
603
366
  Run TypeScript tests:
@@ -609,6 +372,7 @@ npm run test:coverage # Generate coverage report
609
372
  ```
610
373
 
611
374
  Tests cover:
375
+
612
376
  - `isNativeSearchAvailable()` behavior on different platforms
613
377
  - Component rendering when native module is unavailable
614
378
  - Event structure validation
@@ -616,6 +380,7 @@ Tests cover:
616
380
  ## Contributing
617
381
 
618
382
  We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:
383
+
619
384
  - Code of conduct
620
385
  - Development setup
621
386
  - Testing requirements
@@ -628,4 +393,4 @@ If you're adding new props to the library, follow the comprehensive checklist in
628
393
 
629
394
  ## License
630
395
 
631
- MIT
396
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package/build/index.d.ts CHANGED
@@ -45,13 +45,21 @@ export interface SearchViewErrorEvent {
45
45
  export interface ValidationWarningEvent {
46
46
  nativeEvent: {
47
47
  /** Type of validation warning */
48
- type: "field_truncated" | "value_clamped" | "url_invalid" | "validation_failed";
48
+ type: "field_truncated" | "value_clamped" | "value_truncated" | "results_truncated" | "url_invalid" | "url_insecure" | "validation_failed";
49
49
  /** Human-readable warning message */
50
50
  message: string;
51
51
  /** Optional additional context */
52
52
  context?: string;
53
53
  };
54
54
  }
55
+ /**
56
+ * Event payload for search field focus changes.
57
+ * Fired when the native search field gains or loses focus.
58
+ * Useful for managing RN gesture handlers via TVEventControl.
59
+ */
60
+ export interface SearchFieldFocusEvent {
61
+ nativeEvent: Record<string, never>;
62
+ }
55
63
  /**
56
64
  * Represents a single search result displayed in the grid.
57
65
  */
@@ -62,7 +70,7 @@ export interface SearchResult {
62
70
  title: string;
63
71
  /** Optional secondary text displayed below the title */
64
72
  subtitle?: string;
65
- /** Optional image URL for the result poster/thumbnail */
73
+ /** Optional image URL for the result poster/thumbnail. Supports HTTPS, HTTP, and data: URIs */
66
74
  imageUrl?: string;
67
75
  }
68
76
  /**
@@ -73,7 +81,7 @@ export interface SearchResult {
73
81
  * <TvosSearchView
74
82
  * results={searchResults}
75
83
  * columns={5}
76
- * placeholder="Search movies..."
84
+ * placeholder="Search..."
77
85
  * isLoading={loading}
78
86
  * topInset={140}
79
87
  * onSearch={(e) => handleSearch(e.nativeEvent.query)}
@@ -101,9 +109,20 @@ export interface TvosSearchViewProps {
101
109
  columns?: number;
102
110
  /**
103
111
  * Placeholder text shown in the search field when empty.
104
- * @default "Search movies and videos..."
112
+ * @default "Search..."
105
113
  */
106
114
  placeholder?: string;
115
+ /**
116
+ * Programmatically set the search field text.
117
+ * Works like React Native TextInput's `value` + `onChangeText` pattern.
118
+ * Useful for restoring search state, deep links, or "search for similar" flows.
119
+ *
120
+ * **Warning:** Avoid setting `searchText` inside your `onSearch` handler with
121
+ * transforms (e.g., trimming, lowercasing). The native guard only prevents
122
+ * same-value loops — transformed values will trigger a new `onSearch` event,
123
+ * creating an infinite update cycle.
124
+ */
125
+ searchText?: string;
107
126
  /**
108
127
  * Whether to show a loading indicator.
109
128
  * @default false
@@ -155,7 +174,7 @@ export interface TvosSearchViewProps {
155
174
  marqueeDelay?: number;
156
175
  /**
157
176
  * Text displayed when the search field is empty and no results are shown.
158
- * @default "Search for movies and videos"
177
+ * @default "Search your library"
159
178
  */
160
179
  emptyStateText?: string;
161
180
  /**
@@ -231,6 +250,9 @@ export interface TvosSearchViewProps {
231
250
  /**
232
251
  * Callback fired when the search text changes.
233
252
  * Debounce this handler to avoid excessive API calls.
253
+ *
254
+ * **Note:** If using the `searchText` prop, do not set it to a transformed
255
+ * value inside this handler — see `searchText` docs for loop prevention.
234
256
  */
235
257
  onSearch: (event: SearchEvent) => void;
236
258
  /**
@@ -262,6 +284,36 @@ export interface TvosSearchViewProps {
262
284
  * ```
263
285
  */
264
286
  onValidationWarning?: (event: ValidationWarningEvent) => void;
287
+ /**
288
+ * Optional callback fired when the native search field gains focus.
289
+ * Use this to disable RN gesture handlers via TVEventControl if the
290
+ * automatic gesture handling doesn't work on your device.
291
+ *
292
+ * @example
293
+ * ```tsx
294
+ * import { TVEventControl } from 'react-native';
295
+ *
296
+ * onSearchFieldFocused={() => {
297
+ * TVEventControl.disableGestureHandlersCancelTouches();
298
+ * }}
299
+ * ```
300
+ */
301
+ onSearchFieldFocused?: (event: SearchFieldFocusEvent) => void;
302
+ /**
303
+ * Optional callback fired when the native search field loses focus.
304
+ * Use this to re-enable RN gesture handlers via TVEventControl if you
305
+ * disabled them in onSearchFieldFocused.
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * import { TVEventControl } from 'react-native';
310
+ *
311
+ * onSearchFieldBlurred={() => {
312
+ * TVEventControl.enableGestureHandlersCancelTouches();
313
+ * }}
314
+ * ```
315
+ */
316
+ onSearchFieldBlurred?: (event: SearchFieldFocusEvent) => void;
265
317
  /**
266
318
  * Optional style for the view container.
267
319
  */