@devtravelcode/widget-native 1.0.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.
Files changed (208) hide show
  1. package/LICENSE +18 -0
  2. package/dist/App.d.ts +10 -0
  3. package/dist/App.js +192 -0
  4. package/dist/TravelHubWidget.d.ts +12 -0
  5. package/dist/TravelHubWidget.js +58 -0
  6. package/dist/components/ErrorBoundary.d.ts +18 -0
  7. package/dist/components/ErrorBoundary.js +63 -0
  8. package/dist/components/ui/Modal.d.ts +11 -0
  9. package/dist/components/ui/Modal.js +66 -0
  10. package/dist/components/ui/Skeleton.d.ts +8 -0
  11. package/dist/components/ui/Skeleton.js +33 -0
  12. package/dist/components/ui/badge.d.ts +11 -0
  13. package/dist/components/ui/badge.js +54 -0
  14. package/dist/components/ui/button.d.ts +14 -0
  15. package/dist/components/ui/button.js +101 -0
  16. package/dist/components/ui/card.d.ts +16 -0
  17. package/dist/components/ui/card.js +51 -0
  18. package/dist/components/ui/input.d.ts +8 -0
  19. package/dist/components/ui/input.js +61 -0
  20. package/dist/components/ui/label.d.ts +8 -0
  21. package/dist/components/ui/label.js +17 -0
  22. package/dist/context/WidgetContext.d.ts +29 -0
  23. package/dist/context/WidgetContext.js +64 -0
  24. package/dist/icons/CarIcon.d.ts +4 -0
  25. package/dist/icons/CarIcon.js +3 -0
  26. package/dist/icons/CloseIcon.d.ts +7 -0
  27. package/dist/icons/CloseIcon.js +3 -0
  28. package/dist/icons/FlightIcon.d.ts +4 -0
  29. package/dist/icons/FlightIcon.js +3 -0
  30. package/dist/icons/HotelIcon.d.ts +4 -0
  31. package/dist/icons/HotelIcon.js +3 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +1 -0
  34. package/dist/lib/utils.d.ts +2 -0
  35. package/dist/lib/utils.js +3 -0
  36. package/dist/locales/widgetTranslations.d.ts +1 -0
  37. package/dist/locales/widgetTranslations.js +732 -0
  38. package/dist/modules/hooks/useAirportSearch.d.ts +21 -0
  39. package/dist/modules/hooks/useAirportSearch.js +48 -0
  40. package/dist/modules/hooks/useCarOffers.d.ts +7 -0
  41. package/dist/modules/hooks/useCarOffers.js +47 -0
  42. package/dist/modules/hooks/useCountriesByCodes.d.ts +4 -0
  43. package/dist/modules/hooks/useCountriesByCodes.js +60 -0
  44. package/dist/modules/hooks/useCountriesSearch.d.ts +11 -0
  45. package/dist/modules/hooks/useCountriesSearch.js +55 -0
  46. package/dist/modules/hooks/useDefaultHotelLocation.d.ts +7 -0
  47. package/dist/modules/hooks/useDefaultHotelLocation.js +44 -0
  48. package/dist/modules/hooks/useDetectNationality.d.ts +1 -0
  49. package/dist/modules/hooks/useDetectNationality.js +20 -0
  50. package/dist/modules/hooks/useFlightSearch.d.ts +20 -0
  51. package/dist/modules/hooks/useFlightSearch.js +243 -0
  52. package/dist/modules/hooks/useHotelDetails.d.ts +7 -0
  53. package/dist/modules/hooks/useHotelDetails.js +80 -0
  54. package/dist/modules/hooks/useHotelLocations.d.ts +5 -0
  55. package/dist/modules/hooks/useHotelLocations.js +42 -0
  56. package/dist/modules/hooks/useHotelMapData.d.ts +12 -0
  57. package/dist/modules/hooks/useHotelMapData.js +78 -0
  58. package/dist/modules/hooks/useHotelOffers.d.ts +11 -0
  59. package/dist/modules/hooks/useHotelOffers.js +122 -0
  60. package/dist/modules/hooks/useHotelSearchSSE.d.ts +18 -0
  61. package/dist/modules/hooks/useHotelSearchSSE.js +411 -0
  62. package/dist/modules/hooks/useSearchExpiration.d.ts +6 -0
  63. package/dist/modules/hooks/useSearchExpiration.js +35 -0
  64. package/dist/modules/hooks/useTranslationsHub.d.ts +5 -0
  65. package/dist/modules/hooks/useTranslationsHub.js +48 -0
  66. package/dist/modules/navbar/Navbar.d.ts +8 -0
  67. package/dist/modules/navbar/Navbar.js +69 -0
  68. package/dist/modules/search-form/AgeSelector.d.ts +9 -0
  69. package/dist/modules/search-form/AgeSelector.js +109 -0
  70. package/dist/modules/search-form/CustomSelect.d.ts +14 -0
  71. package/dist/modules/search-form/CustomSelect.js +94 -0
  72. package/dist/modules/search-form/FiltersSkeleton.d.ts +6 -0
  73. package/dist/modules/search-form/FiltersSkeleton.js +35 -0
  74. package/dist/modules/search-form/NationalityInput.d.ts +18 -0
  75. package/dist/modules/search-form/NationalityInput.js +172 -0
  76. package/dist/modules/search-form/Pagination.d.ts +8 -0
  77. package/dist/modules/search-form/Pagination.js +84 -0
  78. package/dist/modules/search-form/SearchExpiredPopup.d.ts +10 -0
  79. package/dist/modules/search-form/SearchExpiredPopup.js +91 -0
  80. package/dist/modules/search-form/SkeletonWidget.d.ts +2 -0
  81. package/dist/modules/search-form/SkeletonWidget.js +33 -0
  82. package/dist/modules/search-form/TimePicker.d.ts +9 -0
  83. package/dist/modules/search-form/TimePicker.js +182 -0
  84. package/dist/modules/search-form/cars/CarEmptyState.d.ts +6 -0
  85. package/dist/modules/search-form/cars/CarEmptyState.js +42 -0
  86. package/dist/modules/search-form/cars/CarFiltersSkeleton.d.ts +6 -0
  87. package/dist/modules/search-form/cars/CarFiltersSkeleton.js +35 -0
  88. package/dist/modules/search-form/cars/CarOffersList.d.ts +43 -0
  89. package/dist/modules/search-form/cars/CarOffersList.js +214 -0
  90. package/dist/modules/search-form/cars/CarResults.d.ts +25 -0
  91. package/dist/modules/search-form/cars/CarResults.js +116 -0
  92. package/dist/modules/search-form/cars/CarSearchForm.d.ts +19 -0
  93. package/dist/modules/search-form/cars/CarSearchForm.js +173 -0
  94. package/dist/modules/search-form/cars/CarSkeleton.d.ts +6 -0
  95. package/dist/modules/search-form/cars/CarSkeleton.js +46 -0
  96. package/dist/modules/search-form/cars/CarTopOffersSlider.d.ts +20 -0
  97. package/dist/modules/search-form/cars/CarTopOffersSlider.js +100 -0
  98. package/dist/modules/search-form/cars/car-filters/CarFilters.d.ts +8 -0
  99. package/dist/modules/search-form/cars/car-filters/CarFilters.js +318 -0
  100. package/dist/modules/search-form/cars/car-filters/FilterSection.d.ts +9 -0
  101. package/dist/modules/search-form/cars/car-filters/FilterSection.js +30 -0
  102. package/dist/modules/search-form/cars/car-filters/index.d.ts +6 -0
  103. package/dist/modules/search-form/cars/car-filters/index.js +3 -0
  104. package/dist/modules/search-form/cars/car-filters/useCarFilters.d.ts +24 -0
  105. package/dist/modules/search-form/cars/car-filters/useCarFilters.js +121 -0
  106. package/dist/modules/search-form/flight/AirlinesSelector.d.ts +8 -0
  107. package/dist/modules/search-form/flight/AirlinesSelector.js +235 -0
  108. package/dist/modules/search-form/flight/CabinClassSelector.d.ts +9 -0
  109. package/dist/modules/search-form/flight/CabinClassSelector.js +63 -0
  110. package/dist/modules/search-form/flight/DatePicker.d.ts +12 -0
  111. package/dist/modules/search-form/flight/DatePicker.js +536 -0
  112. package/dist/modules/search-form/flight/FlightSearchForm.d.ts +18 -0
  113. package/dist/modules/search-form/flight/FlightSearchForm.js +147 -0
  114. package/dist/modules/search-form/flight/LocationInput.d.ts +11 -0
  115. package/dist/modules/search-form/flight/LocationInput.js +294 -0
  116. package/dist/modules/search-form/flight/PassengersPicker.d.ts +16 -0
  117. package/dist/modules/search-form/flight/PassengersPicker.js +203 -0
  118. package/dist/modules/search-form/flight/SwitchLocationBtn.d.ts +9 -0
  119. package/dist/modules/search-form/flight/SwitchLocationBtn.js +48 -0
  120. package/dist/modules/search-form/flight/flight-filters/FiltersContent.d.ts +21 -0
  121. package/dist/modules/search-form/flight/flight-filters/FiltersContent.js +145 -0
  122. package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.d.ts +11 -0
  123. package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.js +213 -0
  124. package/dist/modules/search-form/flight/flight-filters/RangeBlock.d.ts +22 -0
  125. package/dist/modules/search-form/flight/flight-filters/RangeBlock.js +164 -0
  126. package/dist/modules/search-form/flight/flight-filters/index.d.ts +1 -0
  127. package/dist/modules/search-form/flight/flight-filters/index.js +1 -0
  128. package/dist/modules/search-form/flight/flight-results/FlightAdditionalInfo.d.ts +16 -0
  129. package/dist/modules/search-form/flight/flight-results/FlightAdditionalInfo.js +229 -0
  130. package/dist/modules/search-form/flight/flight-results/FlightBaggageToggle.d.ts +15 -0
  131. package/dist/modules/search-form/flight/flight-results/FlightBaggageToggle.js +110 -0
  132. package/dist/modules/search-form/flight/flight-results/FlightCard.d.ts +14 -0
  133. package/dist/modules/search-form/flight/flight-results/FlightCard.js +436 -0
  134. package/dist/modules/search-form/flight/flight-results/FlightInfo.d.ts +12 -0
  135. package/dist/modules/search-form/flight/flight-results/FlightInfo.js +48 -0
  136. package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.d.ts +11 -0
  137. package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.js +36 -0
  138. package/dist/modules/search-form/flight/flight-results/FlightResults.d.ts +22 -0
  139. package/dist/modules/search-form/flight/flight-results/FlightResults.js +109 -0
  140. package/dist/modules/search-form/flight/flight-results/FlightSegments.d.ts +40 -0
  141. package/dist/modules/search-form/flight/flight-results/FlightSegments.js +162 -0
  142. package/dist/modules/search-form/flight/flight-results/FlightsSkeleton.d.ts +6 -0
  143. package/dist/modules/search-form/flight/flight-results/FlightsSkeleton.js +52 -0
  144. package/dist/modules/search-form/flight/flight-results/ProvidersLoader.d.ts +9 -0
  145. package/dist/modules/search-form/flight/flight-results/ProvidersLoader.js +242 -0
  146. package/dist/modules/search-form/hotel/DatePicker.d.ts +1 -0
  147. package/dist/modules/search-form/hotel/DatePicker.js +1 -0
  148. package/dist/modules/search-form/hotel/GuestSelector.d.ts +13 -0
  149. package/dist/modules/search-form/hotel/GuestSelector.js +272 -0
  150. package/dist/modules/search-form/hotel/GuestSelectorMobilePopup.d.ts +2 -0
  151. package/dist/modules/search-form/hotel/GuestSelectorMobilePopup.js +1 -0
  152. package/dist/modules/search-form/hotel/HotelLocation.d.ts +17 -0
  153. package/dist/modules/search-form/hotel/HotelLocation.js +178 -0
  154. package/dist/modules/search-form/hotel/HotelLocationMobilePopup.d.ts +2 -0
  155. package/dist/modules/search-form/hotel/HotelLocationMobilePopup.js +1 -0
  156. package/dist/modules/search-form/hotel/HotelResults.d.ts +21 -0
  157. package/dist/modules/search-form/hotel/HotelResults.js +333 -0
  158. package/dist/modules/search-form/hotel/HotelSearchForm.d.ts +13 -0
  159. package/dist/modules/search-form/hotel/HotelSearchForm.js +130 -0
  160. package/dist/modules/search-form/hotel/HotelsSkeleton.d.ts +6 -0
  161. package/dist/modules/search-form/hotel/HotelsSkeleton.js +40 -0
  162. package/dist/modules/search-form/hotel/PassengersPicker.d.ts +2 -0
  163. package/dist/modules/search-form/hotel/PassengersPicker.js +1 -0
  164. package/dist/modules/search-form/hotel/components/HotelCard.d.ts +11 -0
  165. package/dist/modules/search-form/hotel/components/HotelCard.js +180 -0
  166. package/dist/modules/search-form/hotel/components/HotelEmptyState.d.ts +6 -0
  167. package/dist/modules/search-form/hotel/components/HotelEmptyState.js +34 -0
  168. package/dist/modules/search-form/hotel/components/HotelGallery.d.ts +6 -0
  169. package/dist/modules/search-form/hotel/components/HotelGallery.js +77 -0
  170. package/dist/modules/search-form/hotel/components/HotelList.d.ts +9 -0
  171. package/dist/modules/search-form/hotel/components/HotelList.js +12 -0
  172. package/dist/modules/search-form/hotel/components/HotelMap.d.ts +26 -0
  173. package/dist/modules/search-form/hotel/components/HotelMap.js +707 -0
  174. package/dist/modules/search-form/hotel/components/HotelPin.d.ts +7 -0
  175. package/dist/modules/search-form/hotel/components/HotelPin.js +1 -0
  176. package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.d.ts +29 -0
  177. package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.js +339 -0
  178. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersBar.d.ts +3 -0
  179. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersBar.js +2 -0
  180. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersContent.d.ts +2 -0
  181. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersContent.js +1 -0
  182. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersDynamic.d.ts +2 -0
  183. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersDynamic.js +1 -0
  184. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersMobilePopup.d.ts +2 -0
  185. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersMobilePopup.js +1 -0
  186. package/dist/modules/search-form/hotel/hotel-filters/RangeBlock.d.ts +1 -0
  187. package/dist/modules/search-form/hotel/hotel-filters/RangeBlock.js +1 -0
  188. package/dist/modules/search-form/hotel/hotel-filters/index.d.ts +1 -0
  189. package/dist/modules/search-form/hotel/hotel-filters/index.js +1 -0
  190. package/dist/modules/tabs/FlightsTab.d.ts +1 -0
  191. package/dist/modules/tabs/FlightsTab.js +1 -0
  192. package/dist/modules/tabs/HotelsTab.d.ts +1 -0
  193. package/dist/modules/tabs/HotelsTab.js +1 -0
  194. package/dist/store/index.d.ts +22 -0
  195. package/dist/store/index.js +19 -0
  196. package/dist/theme/colors.d.ts +56 -0
  197. package/dist/theme/colors.js +56 -0
  198. package/dist/theme/formStyles.d.ts +76 -0
  199. package/dist/theme/formStyles.js +78 -0
  200. package/dist/utils/applyWidgetColors.d.ts +1 -0
  201. package/dist/utils/applyWidgetColors.js +2 -0
  202. package/dist/utils/dateTime.d.ts +2 -0
  203. package/dist/utils/dateTime.js +23 -0
  204. package/dist/utils/fetchSSE.d.ts +23 -0
  205. package/dist/utils/fetchSSE.js +104 -0
  206. package/dist/utils/getToken.d.ts +3 -0
  207. package/dist/utils/getToken.js +12 -0
  208. package/package.json +64 -0
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ type SwitchLocationBtnProps = {
3
+ from: string;
4
+ to: string;
5
+ setFrom: (val: string) => void;
6
+ setTo: (val: string) => void;
7
+ };
8
+ declare const SwitchLocationBtn: React.FC<SwitchLocationBtnProps>;
9
+ export default SwitchLocationBtn;
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from "react";
3
+ import { TouchableOpacity, StyleSheet, Animated, Easing, } from "react-native";
4
+ import Svg, { Path } from "react-native-svg";
5
+ import { Colors, BorderRadius } from "../../../theme/colors";
6
+ const SwitchLocationBtn = ({ from, to, setFrom, setTo, }) => {
7
+ const rotation = useRef(new Animated.Value(0)).current;
8
+ const handleSwitch = () => {
9
+ const prevFrom = from;
10
+ setFrom(to);
11
+ setTo(prevFrom);
12
+ Animated.timing(rotation, {
13
+ toValue: 1,
14
+ duration: 300,
15
+ easing: Easing.ease,
16
+ useNativeDriver: true,
17
+ }).start(() => {
18
+ rotation.setValue(0);
19
+ });
20
+ };
21
+ const rotateInterpolate = rotation.interpolate({
22
+ inputRange: [0, 1],
23
+ outputRange: ["0deg", "180deg"],
24
+ });
25
+ return (_jsx(Animated.View, { style: [
26
+ styles.container,
27
+ { transform: [{ rotate: rotateInterpolate }] },
28
+ ], children: _jsx(TouchableOpacity, { onPress: handleSwitch, style: styles.button, activeOpacity: 0.7, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsxs(Svg, { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", children: [_jsx(Path, { opacity: 0.3, d: "M13.3334 14.1667C13.7937 14.1667 14.1667 13.7936 14.1667 13.3333C14.1667 12.8731 13.7937 12.5 13.3334 12.5L5.00008 12.5C4.53984 12.5 4.16675 12.8731 4.16675 13.3333C4.16675 13.7936 4.53984 14.1667 5.00008 14.1667L13.3334 14.1667Z", fill: Colors.primary }), _jsx(Path, { d: "M11.9108 11.4224C11.5854 11.097 11.5854 10.5694 11.9108 10.2439C12.2363 9.91848 12.7639 9.91848 13.0893 10.2439L15.5893 12.7439C15.9148 13.0694 15.9148 13.597 15.5893 13.9224L13.0893 16.4224C12.7639 16.7479 12.2363 16.7479 11.9108 16.4224C11.5854 16.097 11.5854 15.5694 11.9108 15.2439L13.8216 13.3332L11.9108 11.4224Z", fill: Colors.primary }), _jsx(Path, { opacity: 0.3, d: "M6.66659 7.50016C6.20635 7.50016 5.83325 7.12707 5.83325 6.66683C5.83325 6.20659 6.20635 5.8335 6.66659 5.8335L14.9999 5.8335C15.4602 5.8335 15.8333 6.20659 15.8333 6.66683C15.8333 7.12707 15.4602 7.50016 14.9999 7.50016L6.66659 7.50016Z", fill: Colors.primary }), _jsx(Path, { d: "M8.08917 4.75592C8.41461 4.43049 8.41461 3.90285 8.08917 3.57741C7.76374 3.25197 7.2361 3.25197 6.91066 3.57741L4.41066 6.07741C4.08523 6.40285 4.08523 6.93049 4.41066 7.25592L6.91066 9.75592C7.2361 10.0814 7.76374 10.0814 8.08917 9.75592C8.41461 9.43049 8.41461 8.90285 8.08917 8.57741L6.17843 6.66667L8.08917 4.75592Z", fill: Colors.primary })] }) }) }));
29
+ };
30
+ const styles = StyleSheet.create({
31
+ container: {
32
+ alignItems: "center",
33
+ justifyContent: "center",
34
+ zIndex: 2,
35
+ marginTop: -16,
36
+ },
37
+ button: {
38
+ width: 50,
39
+ height: 50,
40
+ borderRadius: BorderRadius.full,
41
+ backgroundColor: Colors.surface,
42
+ borderWidth: 1,
43
+ borderColor: Colors.border,
44
+ alignItems: "center",
45
+ justifyContent: "center",
46
+ },
47
+ });
48
+ export default SwitchLocationBtn;
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ type FiltersContentProps = {
3
+ has: (key: string) => boolean;
4
+ openSections: string[];
5
+ toggleSection: (key: string) => void;
6
+ getValue: (name: string, fallback: any) => any;
7
+ handleRangeChange: (name: string, value: number) => void;
8
+ handleInputChange: (name: string, value: any) => void;
9
+ getRangeStyle: (minName: string, maxName: string, maxValue: number) => any;
10
+ formatTime: (v: number) => string;
11
+ formatDuration: (v: number) => string;
12
+ airlines: Record<string, string>;
13
+ translations: Record<string, string>;
14
+ currencySign: string;
15
+ dateTimeFormat: string;
16
+ maxDayMinutes: number;
17
+ onRangeDragStart?: () => void;
18
+ onRangeDragEnd?: () => void;
19
+ };
20
+ declare const FiltersContent: React.FC<FiltersContentProps>;
21
+ export default FiltersContent;
@@ -0,0 +1,145 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, TouchableOpacity, Switch, StyleSheet, } from "react-native";
3
+ import RangeBlock from "./RangeBlock";
4
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../../theme/colors";
5
+ const SectionTitle = ({ title }) => (_jsx(View, { style: styles.sectionHeader, children: _jsx(Text, { style: styles.sectionTitle, children: title }) }));
6
+ const RadioOption = ({ label, selected, onPress }) => (_jsxs(TouchableOpacity, { style: styles.radioRow, onPress: onPress, activeOpacity: 0.7, children: [_jsx(View, { style: [styles.radioOuter, selected && styles.radioOuterSelected], children: selected && _jsx(View, { style: styles.radioInner }) }), _jsx(Text, { style: styles.radioLabel, children: label })] }));
7
+ const CheckboxOption = ({ label, checked, onPress }) => (_jsxs(TouchableOpacity, { style: styles.checkboxRow, onPress: onPress, activeOpacity: 0.7, children: [_jsx(View, { style: [styles.checkboxOuter, checked && styles.checkboxChecked], children: checked && _jsx(Text, { style: styles.checkboxMark, children: "\u2713" }) }), _jsx(Text, { style: styles.checkboxLabel, children: label })] }));
8
+ const FiltersContent = ({ has, openSections, toggleSection, getValue, handleRangeChange, handleInputChange, getRangeStyle, formatTime, formatDuration, airlines, translations, currencySign, dateTimeFormat, maxDayMinutes, onRangeDragStart, onRangeDragEnd, }) => {
9
+ const transferLabels = [
10
+ translations["filter_any"] || "Any",
11
+ translations["filter_nonstop"] || "Nonstop",
12
+ "1 " + (translations["filter_stop"] || "stop"),
13
+ "2 " + (translations["filter_stops"] || "stops"),
14
+ "3 " + (translations["filter_stops"] || "stops"),
15
+ ];
16
+ const baggageLabels = [
17
+ translations["filter_baggage_any"] || "Any",
18
+ translations["filter_baggage_with"] || "With baggage",
19
+ translations["filter_baggage_without"] || "Without baggage",
20
+ ];
21
+ const baggageValues = ["all", "1", "0"];
22
+ const transferValues = ["all", "0", "1", "2", "3"];
23
+ return (_jsxs(View, { style: styles.container, children: [_jsx(Text, { style: styles.title, children: translations["filters_title"] || "Filters" }), (has("minprice") || has("maxprice")) && (_jsx(RangeBlock, { title: translations["filter_price"] || "Price", minName: "minprice", maxName: "maxprice", maxValue: 1000, step: 10, open: openSections.includes("price"), toggle: () => toggleSection("price"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: (v) => `${v} ${currencySign}`, isShow: true, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), has("baggage") && (_jsxs(View, { style: styles.section, children: [_jsx(SectionTitle, { title: translations["filter_baggage"] || "Baggage" }), _jsx(View, { style: styles.sectionContent, children: baggageLabels.map((label, i) => (_jsx(RadioOption, { label: label, selected: getValue("baggage", "all") === baggageValues[i], onPress: () => handleInputChange("baggage", baggageValues[i]) }, i))) })] })), has("transfer") && (_jsxs(View, { style: styles.section, children: [_jsx(SectionTitle, { title: translations["filter_transfer_outbound"] || "Stops (outbound)" }), _jsx(View, { style: styles.sectionContent, children: transferLabels.map((label, i) => (_jsx(RadioOption, { label: label, selected: getValue("transfer", "all") === transferValues[i], onPress: () => handleInputChange("transfer", transferValues[i]) }, i))) })] })), has("transferReturn") && (_jsxs(View, { style: styles.section, children: [_jsx(SectionTitle, { title: translations["filter_transfer_return"] || "Stops (return)" }), _jsx(View, { style: styles.sectionContent, children: transferLabels.map((label, i) => (_jsx(RadioOption, { label: label, selected: getValue("transferReturn", "all") === transferValues[i], onPress: () => handleInputChange("transferReturn", transferValues[i]) }, i))) })] })), has("charter") && (_jsxs(View, { style: styles.section, children: [_jsx(SectionTitle, { title: translations["filter_charter"] || "Charter" }), _jsxs(View, { style: styles.toggleRow, children: [_jsx(Text, { style: styles.toggleLabel, children: translations["filter_charter_toggle"] || "Charter" }), _jsx(Switch, { value: !!getValue("charter", 0), onValueChange: (val) => handleInputChange("charter", val ? 1 : 0), trackColor: { false: Colors.border, true: Colors.primary }, thumbColor: Colors.white, ios_backgroundColor: Colors.border })] })] })), has("changeAirport") && (_jsxs(View, { style: styles.section, children: [_jsx(SectionTitle, { title: translations["filter_no_airport_change"] || "No airport change" }), _jsxs(View, { style: styles.toggleRow, children: [_jsx(Text, { style: styles.toggleLabel, children: translations["filter_no_airport_change"] ||
24
+ "No airport change" }), _jsx(Switch, { value: !!getValue("changeAirport", 0), onValueChange: (val) => handleInputChange("changeAirport", val ? 1 : 0), trackColor: { false: Colors.border, true: Colors.primary }, thumbColor: Colors.white, ios_backgroundColor: Colors.border })] })] })), (has("departureMin") || has("departureMax")) && (_jsx(RangeBlock, { title: translations["filter_departure_outbound"] ||
25
+ "Departure time (outbound)", minName: "departureMin", maxName: "departureMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("departure"), toggle: () => toggleSection("departure"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatTime, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), (has("arrivalMin") || has("arrivalMax")) && (_jsx(RangeBlock, { title: translations["filter_arrival_outbound"] ||
26
+ "Arrival time (outbound)", minName: "arrivalMin", maxName: "arrivalMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("arrival"), toggle: () => toggleSection("arrival"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatTime, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), (has("returnMin") || has("returnMax")) && (_jsx(RangeBlock, { title: translations["filter_departure_return"] ||
27
+ "Departure time (return)", minName: "returnMin", maxName: "returnMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("return"), toggle: () => toggleSection("return"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatTime, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), (has("returnArrivalMin") || has("returnArrivalMax")) && (_jsx(RangeBlock, { title: translations["filter_arrival_return"] ||
28
+ "Arrival time (return)", minName: "returnArrivalMin", maxName: "returnArrivalMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("returnArrival"), toggle: () => toggleSection("returnArrival"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatTime, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), (has("outboundStopTimeMin") || has("outboundStopTimeMax")) && (_jsx(RangeBlock, { title: translations["filter_stop_duration_outbound"] ||
29
+ "Stop duration (outbound)", minName: "outboundStopTimeMin", maxName: "outboundStopTimeMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("outbound"), toggle: () => toggleSection("outbound"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatDuration, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), (has("inboundStopTimeMin") || has("inboundStopTimeMax")) && (_jsx(RangeBlock, { title: translations["filter_stop_duration_inbound"] ||
30
+ "Stop duration (return)", minName: "inboundStopTimeMin", maxName: "inboundStopTimeMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("inbound"), toggle: () => toggleSection("inbound"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatDuration, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), (has("durationMin") || has("durationMax")) && (_jsx(RangeBlock, { title: translations["filter_total_stop_duration"] ||
31
+ "Total stop duration", minName: "durationMin", maxName: "durationMax", maxValue: maxDayMinutes, step: 15, open: openSections.includes("duration"), toggle: () => toggleSection("duration"), getValue: getValue, handleRangeChange: handleRangeChange, getRangeStyle: getRangeStyle, formatter: formatDuration, isShow: false, onDragStart: onRangeDragStart, onDragEnd: onRangeDragEnd })), has("airline") && (_jsxs(View, { style: styles.section, children: [_jsx(SectionTitle, { title: translations["filter_airlines"] || "Airlines" }), _jsxs(View, { style: styles.sectionContent, children: [Object.entries(airlines).map(([code, name]) => (_jsx(CheckboxOption, { label: name, checked: Array.isArray(getValue("airline", [])) &&
32
+ getValue("airline", []).includes(code), onPress: () => handleInputChange("airline", {
33
+ code,
34
+ checked: !(Array.isArray(getValue("airline", [])) &&
35
+ getValue("airline", []).includes(code)),
36
+ }) }, code))), _jsx(TouchableOpacity, { style: styles.clearButton, onPress: () => handleInputChange("airline", []), activeOpacity: 0.7, children: _jsx(Text, { style: styles.clearButtonText, children: translations["clear_all"] || "Clear all" }) })] })] }))] }));
37
+ };
38
+ const styles = StyleSheet.create({
39
+ container: {
40
+ paddingBottom: Spacing.lg,
41
+ },
42
+ title: {
43
+ fontSize: FontSize.xl,
44
+ fontWeight: "700",
45
+ color: Colors.text,
46
+ marginBottom: Spacing.lg,
47
+ },
48
+ section: {
49
+ marginBottom: Spacing.sm,
50
+ borderBottomWidth: 1,
51
+ borderBottomColor: Colors.borderLight,
52
+ },
53
+ sectionHeader: {
54
+ flexDirection: "row",
55
+ justifyContent: "space-between",
56
+ alignItems: "center",
57
+ paddingVertical: Spacing.md,
58
+ },
59
+ sectionTitle: {
60
+ fontSize: FontSize.md,
61
+ fontWeight: "600",
62
+ color: Colors.text,
63
+ },
64
+ sectionContent: {
65
+ paddingBottom: Spacing.md,
66
+ },
67
+ radioRow: {
68
+ flexDirection: "row",
69
+ alignItems: "center",
70
+ paddingVertical: Spacing.sm,
71
+ },
72
+ radioOuter: {
73
+ width: 20,
74
+ height: 20,
75
+ borderRadius: 10,
76
+ borderWidth: 2,
77
+ borderColor: Colors.border,
78
+ justifyContent: "center",
79
+ alignItems: "center",
80
+ marginRight: Spacing.sm,
81
+ },
82
+ radioOuterSelected: {
83
+ borderColor: Colors.primary,
84
+ },
85
+ radioInner: {
86
+ width: 10,
87
+ height: 10,
88
+ borderRadius: 5,
89
+ backgroundColor: Colors.primary,
90
+ },
91
+ radioLabel: {
92
+ fontSize: FontSize.md,
93
+ color: Colors.text,
94
+ },
95
+ checkboxRow: {
96
+ flexDirection: "row",
97
+ alignItems: "center",
98
+ paddingVertical: Spacing.sm,
99
+ },
100
+ checkboxOuter: {
101
+ width: 20,
102
+ height: 20,
103
+ borderRadius: BorderRadius.sm,
104
+ borderWidth: 2,
105
+ borderColor: Colors.border,
106
+ justifyContent: "center",
107
+ alignItems: "center",
108
+ marginRight: Spacing.sm,
109
+ },
110
+ checkboxChecked: {
111
+ backgroundColor: Colors.primary,
112
+ borderColor: Colors.primary,
113
+ },
114
+ checkboxMark: {
115
+ color: Colors.white,
116
+ fontSize: FontSize.xs,
117
+ fontWeight: "700",
118
+ },
119
+ checkboxLabel: {
120
+ fontSize: FontSize.md,
121
+ color: Colors.text,
122
+ },
123
+ toggleRow: {
124
+ flexDirection: "row",
125
+ justifyContent: "space-between",
126
+ alignItems: "center",
127
+ paddingVertical: Spacing.sm,
128
+ paddingBottom: Spacing.md,
129
+ },
130
+ toggleLabel: {
131
+ fontSize: FontSize.md,
132
+ color: Colors.text,
133
+ flex: 1,
134
+ },
135
+ clearButton: {
136
+ marginTop: Spacing.sm,
137
+ alignSelf: "flex-start",
138
+ },
139
+ clearButtonText: {
140
+ fontSize: FontSize.sm,
141
+ color: Colors.primary,
142
+ fontWeight: "500",
143
+ },
144
+ });
145
+ export default FiltersContent;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export type FlightFiltersDynamicProps = {
3
+ filters?: string[];
4
+ airlines?: Record<string, string>;
5
+ loading?: boolean;
6
+ sessionId?: string | null;
7
+ onApplyFilters?: (sid: string | null, filters?: Record<string, any>) => Promise<void>;
8
+ translations: Record<string, string>;
9
+ };
10
+ declare const FlightFiltersDynamic: React.FC<FlightFiltersDynamicProps>;
11
+ export default FlightFiltersDynamic;
@@ -0,0 +1,213 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { View, Text, TouchableOpacity, Modal, ScrollView, StyleSheet, } from "react-native";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import Svg, { Path } from "react-native-svg";
6
+ import FiltersContent from "./FiltersContent";
7
+ import { useWidget } from "../../../../context/WidgetContext";
8
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../../theme/colors";
9
+ import { CloseIcon } from "../../../../store";
10
+ const FlightFiltersDynamic = ({ filters = [], airlines = {}, loading = false, sessionId = null, onApplyFilters, translations, }) => {
11
+ const insets = useSafeAreaInsets();
12
+ const [values, setValues] = useState({});
13
+ const [openSections, setOpenSections] = useState([]);
14
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
15
+ const [scrollEnabled, setScrollEnabled] = useState(true);
16
+ const { currency, dateTimeFormat } = useWidget();
17
+ const currencySigns = {
18
+ USD: "$",
19
+ EUR: "\u20AC",
20
+ GBP: "\u00A3",
21
+ CHF: "\u20A3",
22
+ };
23
+ const currencySign = currencySigns[currency] || currency;
24
+ const MAX_DAY_MINUTES = 1440;
25
+ const normalizeMinutes = (name, value) => {
26
+ const timeKeys = [
27
+ "departureMin",
28
+ "departureMax",
29
+ "arrivalMin",
30
+ "arrivalMax",
31
+ "returnMin",
32
+ "returnMax",
33
+ "returnArrivalMin",
34
+ "returnArrivalMax",
35
+ ];
36
+ if (timeKeys.includes(name) && value >= 1440) {
37
+ return 1439;
38
+ }
39
+ return value;
40
+ };
41
+ const formatTime = (minutes) => {
42
+ const safeMinutes = minutes >= 1440 ? 1439 : minutes;
43
+ const h24 = Math.floor(safeMinutes / 60);
44
+ const m = safeMinutes % 60;
45
+ if (dateTimeFormat !== "usa") {
46
+ return `${String(h24).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
47
+ }
48
+ const period = h24 >= 12 ? "PM" : "AM";
49
+ let h12 = h24 % 12;
50
+ if (h12 === 0)
51
+ h12 = 12;
52
+ return `${h12}:${String(m).padStart(2, "0")} ${period}`;
53
+ };
54
+ const formatDuration = (minutes) => {
55
+ const h = Math.floor(minutes / 60);
56
+ const m = minutes % 60;
57
+ const hLabel = translations["hours_label"] || "h";
58
+ const mLabel = translations["minutes_label"] || "m";
59
+ return `${h}${hLabel} ${m}${mLabel}`;
60
+ };
61
+ const has = (key) => filters.includes(key);
62
+ const handleRangeChange = (name, value) => {
63
+ updateFilterValue(name, value);
64
+ };
65
+ const handleInputChange = (name, value) => {
66
+ if (name === "airline") {
67
+ setValues((prev) => {
68
+ const current = Array.isArray(prev.airline) ? prev.airline : [];
69
+ let updated;
70
+ if (Array.isArray(value)) {
71
+ updated = [];
72
+ }
73
+ else if (value.checked) {
74
+ updated = [...current, value.code];
75
+ }
76
+ else {
77
+ updated = current.filter((code) => code !== value.code);
78
+ }
79
+ return { ...prev, airline: updated };
80
+ });
81
+ }
82
+ else {
83
+ const normalizedValue = typeof value === "boolean" ? (value ? 1 : 0) : value;
84
+ updateFilterValue(name, normalizedValue);
85
+ }
86
+ };
87
+ const updateFilterValue = (name, value) => {
88
+ setValues((prev) => {
89
+ let normalizedValue = value;
90
+ if (typeof value === "number") {
91
+ normalizedValue = normalizeMinutes(name, value);
92
+ }
93
+ return { ...prev, [name]: normalizedValue };
94
+ });
95
+ };
96
+ const getValue = (name, fallback) => values[name] ?? fallback;
97
+ const getRangeStyle = (minName, maxName, maxValue) => {
98
+ const min = getValue(minName, 0);
99
+ const max = getValue(maxName, maxValue);
100
+ const left = (min / maxValue) * 100;
101
+ const right = 100 - (max / maxValue) * 100;
102
+ return { left: `${left}%`, right: `${right}%` };
103
+ };
104
+ const toggleSection = (key) => {
105
+ setOpenSections((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]);
106
+ };
107
+ const handleApply = () => {
108
+ if (sessionId && onApplyFilters) {
109
+ onApplyFilters(sessionId, values);
110
+ }
111
+ setIsPopupOpen(false);
112
+ };
113
+ const handleReset = () => {
114
+ setValues({});
115
+ setOpenSections([]);
116
+ if (sessionId && onApplyFilters) {
117
+ onApplyFilters(sessionId, {});
118
+ }
119
+ };
120
+ if (loading) {
121
+ return (_jsx(View, { style: styles.loadingContainer, children: _jsx(Text, { style: styles.loadingText, children: translations["loading_filters"] || "Loading filters..." }) }));
122
+ }
123
+ if (!filters?.length || !sessionId)
124
+ return null;
125
+ return (_jsxs(View, { children: [_jsxs(TouchableOpacity, { style: styles.filterButton, onPress: () => setIsPopupOpen(true), activeOpacity: 0.7, children: [_jsxs(Svg, { width: 24, height: 20, viewBox: "0 0 24 20", fill: "none", children: [_jsx(Path, { d: "M18.5 4.1665H5.5C4.67157 4.1665 4 4.72615 4 5.4165C4 6.10686 4.67157 6.6665 5.5 6.6665H18.5C19.3284 6.6665 20 6.10686 20 5.4165C20 4.72615 19.3284 4.1665 18.5 4.1665Z", fill: Colors.primary }), _jsx(Path, { opacity: 0.3, fillRule: "evenodd", clipRule: "evenodd", d: "M7.5 9.1665H16.5C17.3284 9.1665 18 9.72615 18 10.4165C18 11.1069 17.3284 11.6665 16.5 11.6665H7.5C6.67157 11.6665 6 11.1069 6 10.4165C6 9.72615 6.67157 9.1665 7.5 9.1665ZM10.5 14.1665H13.5C14.3284 14.1665 15 14.7261 15 15.4165C15 16.1069 14.3284 16.6665 13.5 16.6665H10.5C9.67157 16.6665 9 16.1069 9 15.4165C9 14.7261 9.67157 14.1665 10.5 14.1665Z", fill: Colors.primary })] }), _jsx(Text, { style: styles.filterButtonText, children: translations["filters_title"] || "Filters" })] }), _jsx(Modal, { visible: isPopupOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsPopupOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: () => setIsPopupOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsx(View, { style: styles.modalHeader, children: _jsx(Text, { style: styles.modalTitle, children: translations["filters_title"] || "Filters" }) }), _jsx(ScrollView, { style: styles.modalContent, contentContainerStyle: styles.modalContentInner, showsVerticalScrollIndicator: false, scrollEnabled: scrollEnabled, children: _jsx(FiltersContent, { has: has, openSections: openSections, toggleSection: toggleSection, getValue: getValue, handleRangeChange: handleRangeChange, handleInputChange: handleInputChange, getRangeStyle: getRangeStyle, formatTime: formatTime, formatDuration: formatDuration, airlines: airlines, translations: translations, currencySign: currencySign, dateTimeFormat: dateTimeFormat, maxDayMinutes: MAX_DAY_MINUTES, onRangeDragStart: () => setScrollEnabled(false), onRangeDragEnd: () => setScrollEnabled(true) }) }), _jsxs(View, { style: styles.modalFooter, children: [_jsx(TouchableOpacity, { style: styles.resetButton, onPress: handleReset, activeOpacity: 0.7, children: _jsx(Text, { style: styles.resetButtonText, children: translations["filter_reset_btn"] || "Reset" }) }), _jsx(TouchableOpacity, { style: styles.applyButton, onPress: handleApply, activeOpacity: 0.8, children: _jsx(Text, { style: styles.applyButtonText, children: translations["filter_apply_btn"] || "Apply" }) })] })] }) })] }));
126
+ };
127
+ const styles = StyleSheet.create({
128
+ loadingContainer: {
129
+ padding: Spacing.lg,
130
+ alignItems: "center",
131
+ },
132
+ loadingText: {
133
+ fontSize: FontSize.md,
134
+ color: Colors.textSecondary,
135
+ },
136
+ filterButton: {
137
+ flexDirection: "row",
138
+ alignItems: "center",
139
+ backgroundColor: Colors.surface,
140
+ borderRadius: BorderRadius.md,
141
+ paddingVertical: Spacing.md,
142
+ paddingHorizontal: Spacing.lg,
143
+ marginHorizontal: Spacing.lg,
144
+ marginBottom: Spacing.md,
145
+ borderWidth: 1,
146
+ borderColor: Colors.border,
147
+ },
148
+ filterButtonText: {
149
+ marginLeft: Spacing.sm,
150
+ fontSize: FontSize.md,
151
+ fontWeight: "500",
152
+ color: Colors.text,
153
+ },
154
+ modalContainer: {
155
+ flex: 1,
156
+ backgroundColor: Colors.surface,
157
+ },
158
+ closeButton: {
159
+ alignSelf: "flex-end",
160
+ padding: Spacing.lg,
161
+ },
162
+ modalHeader: {
163
+ paddingHorizontal: Spacing.lg,
164
+ paddingBottom: Spacing.md,
165
+ borderBottomWidth: 1,
166
+ borderBottomColor: Colors.border,
167
+ },
168
+ modalTitle: {
169
+ fontSize: FontSize.lg,
170
+ fontWeight: "700",
171
+ color: Colors.text,
172
+ },
173
+ modalContent: {
174
+ flex: 1,
175
+ },
176
+ modalContentInner: {
177
+ padding: Spacing.lg,
178
+ },
179
+ modalFooter: {
180
+ flexDirection: "row",
181
+ paddingHorizontal: Spacing.lg,
182
+ paddingVertical: Spacing.md,
183
+ borderTopWidth: 1,
184
+ borderTopColor: Colors.borderLight,
185
+ gap: Spacing.md,
186
+ },
187
+ resetButton: {
188
+ flex: 1,
189
+ paddingVertical: Spacing.md,
190
+ borderRadius: BorderRadius.md,
191
+ borderWidth: 1,
192
+ borderColor: Colors.border,
193
+ alignItems: "center",
194
+ },
195
+ resetButtonText: {
196
+ fontSize: FontSize.md,
197
+ fontWeight: "600",
198
+ color: Colors.textSecondary,
199
+ },
200
+ applyButton: {
201
+ flex: 1,
202
+ paddingVertical: Spacing.md,
203
+ borderRadius: BorderRadius.md,
204
+ backgroundColor: Colors.primary,
205
+ alignItems: "center",
206
+ },
207
+ applyButtonText: {
208
+ fontSize: FontSize.md,
209
+ fontWeight: "600",
210
+ color: Colors.textOnPrimary,
211
+ },
212
+ });
213
+ export default FlightFiltersDynamic;
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ type RangeBlockProps = {
3
+ title: string;
4
+ minName: string;
5
+ maxName: string;
6
+ maxValue: number;
7
+ step: number;
8
+ open: boolean;
9
+ toggle: () => void;
10
+ getValue: (name: string, fallback: number) => number | string;
11
+ handleRangeChange: (name: string, value: number) => void;
12
+ getRangeStyle: (minName: string, maxName: string, maxValue: number) => {
13
+ left: string;
14
+ right: string;
15
+ };
16
+ formatter: (value: number) => string;
17
+ isShow: boolean;
18
+ onDragStart?: () => void;
19
+ onDragEnd?: () => void;
20
+ };
21
+ declare const RangeBlock: React.FC<RangeBlockProps>;
22
+ export default RangeBlock;
@@ -0,0 +1,164 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useCallback } from "react";
3
+ import { View, Text, PanResponder, StyleSheet, } from "react-native";
4
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../../theme/colors";
5
+ const THUMB_SIZE = 28;
6
+ const TRACK_HEIGHT = 4;
7
+ const RangeBlock = ({ title, minName, maxName, maxValue, step, getValue, handleRangeChange, formatter, onDragStart, onDragEnd, }) => {
8
+ const rawMin = getValue(minName, 0);
9
+ const rawMax = getValue(maxName, maxValue);
10
+ const currentMin = typeof rawMin === "string" ? parseFloat(rawMin) : rawMin;
11
+ const currentMax = typeof rawMax === "string" ? parseFloat(rawMax) : rawMax;
12
+ const isPriceFilter = title.toLowerCase().includes("price") ||
13
+ title.toLowerCase().includes("\u0446\u0435\u043D");
14
+ const isMaxPlus = isPriceFilter && currentMax >= maxValue;
15
+ const showMaxLabel = isMaxPlus
16
+ ? `${formatter(maxValue)}+`
17
+ : formatter(currentMax);
18
+ const minRef = useRef(currentMin);
19
+ const maxRef = useRef(currentMax);
20
+ minRef.current = currentMin;
21
+ maxRef.current = currentMax;
22
+ const onDragStartRef = useRef(onDragStart);
23
+ const onDragEndRef = useRef(onDragEnd);
24
+ onDragStartRef.current = onDragStart;
25
+ onDragEndRef.current = onDragEnd;
26
+ const trackLayoutRef = useRef({ x: 0, width: 0 });
27
+ const startValueRef = useRef(0);
28
+ const onTrackLayout = useCallback((e) => {
29
+ e.target.measureInWindow((x, _y, width) => {
30
+ trackLayoutRef.current = { x, width };
31
+ });
32
+ }, []);
33
+ const pageXToValue = (pageX) => {
34
+ const { x, width } = trackLayoutRef.current;
35
+ if (width === 0)
36
+ return 0;
37
+ const ratio = Math.max(0, Math.min(1, (pageX - x) / width));
38
+ return Math.round((ratio * maxValue) / step) * step;
39
+ };
40
+ const minPanResponder = useRef(PanResponder.create({
41
+ onStartShouldSetPanResponder: () => true,
42
+ onMoveShouldSetPanResponder: () => true,
43
+ onMoveShouldSetPanResponderCapture: () => true,
44
+ onPanResponderTerminationRequest: () => false,
45
+ onPanResponderGrant: () => {
46
+ startValueRef.current = minRef.current;
47
+ onDragStartRef.current?.();
48
+ },
49
+ onPanResponderMove: (evt) => {
50
+ const val = pageXToValue(evt.nativeEvent.pageX);
51
+ const clamped = Math.min(val, maxRef.current - step);
52
+ if (clamped >= 0 && clamped !== minRef.current) {
53
+ handleRangeChange(minName, clamped);
54
+ }
55
+ },
56
+ onPanResponderRelease: () => {
57
+ onDragEndRef.current?.();
58
+ },
59
+ onPanResponderTerminate: () => {
60
+ onDragEndRef.current?.();
61
+ },
62
+ })).current;
63
+ const maxPanResponder = useRef(PanResponder.create({
64
+ onStartShouldSetPanResponder: () => true,
65
+ onMoveShouldSetPanResponder: () => true,
66
+ onMoveShouldSetPanResponderCapture: () => true,
67
+ onPanResponderTerminationRequest: () => false,
68
+ onPanResponderGrant: () => {
69
+ startValueRef.current = maxRef.current;
70
+ onDragStartRef.current?.();
71
+ },
72
+ onPanResponderMove: (evt) => {
73
+ const val = pageXToValue(evt.nativeEvent.pageX);
74
+ const clamped = Math.max(val, minRef.current + step);
75
+ const final = Math.min(clamped, maxValue);
76
+ if (final !== maxRef.current) {
77
+ handleRangeChange(maxName, final);
78
+ }
79
+ },
80
+ onPanResponderRelease: () => {
81
+ onDragEndRef.current?.();
82
+ },
83
+ onPanResponderTerminate: () => {
84
+ onDragEndRef.current?.();
85
+ },
86
+ })).current;
87
+ const minPercent = (currentMin / maxValue) * 100;
88
+ const maxPercent = (currentMax / maxValue) * 100;
89
+ return (_jsxs(View, { style: styles.section, children: [_jsx(View, { style: styles.sectionHeader, children: _jsx(Text, { style: styles.sectionTitle, children: title }) }), _jsxs(View, { style: styles.rangeContainer, children: [_jsxs(View, { style: styles.rangeLabels, children: [_jsx(Text, { style: styles.rangeLabel, children: formatter(currentMin) }), _jsx(Text, { style: styles.rangeLabel, children: showMaxLabel })] }), _jsxs(View, { style: styles.trackContainer, onLayout: onTrackLayout, children: [_jsx(View, { style: styles.track }), _jsx(View, { style: [
90
+ styles.selectedRange,
91
+ {
92
+ left: `${minPercent}%`,
93
+ right: `${100 - maxPercent}%`,
94
+ },
95
+ ] }), _jsx(View, { style: [
96
+ styles.thumb,
97
+ { left: `${minPercent}%`, marginLeft: -THUMB_SIZE / 2 },
98
+ ], hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }, ...minPanResponder.panHandlers }), _jsx(View, { style: [
99
+ styles.thumb,
100
+ { left: `${maxPercent}%`, marginLeft: -THUMB_SIZE / 2 },
101
+ ], hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }, ...maxPanResponder.panHandlers })] })] })] }));
102
+ };
103
+ const styles = StyleSheet.create({
104
+ section: {
105
+ marginBottom: Spacing.md,
106
+ },
107
+ sectionHeader: {
108
+ flexDirection: "row",
109
+ justifyContent: "space-between",
110
+ alignItems: "center",
111
+ paddingVertical: Spacing.md,
112
+ },
113
+ sectionTitle: {
114
+ fontSize: FontSize.md,
115
+ fontWeight: "600",
116
+ color: Colors.text,
117
+ },
118
+ rangeContainer: {
119
+ paddingHorizontal: Spacing.xs,
120
+ paddingBottom: Spacing.md,
121
+ },
122
+ rangeLabels: {
123
+ flexDirection: "row",
124
+ justifyContent: "space-between",
125
+ marginBottom: Spacing.sm,
126
+ },
127
+ rangeLabel: {
128
+ fontSize: FontSize.sm,
129
+ color: Colors.textSecondary,
130
+ },
131
+ trackContainer: {
132
+ height: THUMB_SIZE + 8,
133
+ justifyContent: "center",
134
+ position: "relative",
135
+ },
136
+ track: {
137
+ height: TRACK_HEIGHT,
138
+ backgroundColor: Colors.borderLight,
139
+ borderRadius: BorderRadius.full,
140
+ },
141
+ selectedRange: {
142
+ position: "absolute",
143
+ height: TRACK_HEIGHT,
144
+ backgroundColor: Colors.primary,
145
+ borderRadius: BorderRadius.full,
146
+ top: (THUMB_SIZE + 8 - TRACK_HEIGHT) / 2,
147
+ },
148
+ thumb: {
149
+ position: "absolute",
150
+ width: THUMB_SIZE,
151
+ height: THUMB_SIZE,
152
+ borderRadius: THUMB_SIZE / 2,
153
+ backgroundColor: Colors.surface,
154
+ borderWidth: 2,
155
+ borderColor: Colors.primary,
156
+ top: 4,
157
+ shadowColor: Colors.black,
158
+ shadowOffset: { width: 0, height: 1 },
159
+ shadowOpacity: 0.15,
160
+ shadowRadius: 3,
161
+ elevation: 3,
162
+ },
163
+ });
164
+ export default RangeBlock;
@@ -0,0 +1 @@
1
+ export { default as FlightFilters } from "./FlightFiltersDynamic";
@@ -0,0 +1 @@
1
+ export { default as FlightFilters } from "./FlightFiltersDynamic";