@qite/tide-booking-component 1.4.25 → 1.4.26

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.
@@ -23564,11 +23564,11 @@ var SearchInput = function (_a) {
23564
23564
  _a.label;
23565
23565
  var isSecondInput = _a.isSecondInput,
23566
23566
  isDoubleInput = _a.isDoubleInput;
23567
- var highlightMatch = function (text, highlight) {
23567
+ var highlightMatch = function (option, highlight) {
23568
23568
  if (!highlight) {
23569
- return text;
23569
+ return option.value;
23570
23570
  }
23571
- var parts = text.split(new RegExp('('.concat(highlight, ')'), 'gi'));
23571
+ var parts = option.value.split(new RegExp('('.concat(highlight, ')'), 'gi'));
23572
23572
  return React__default['default'].createElement(
23573
23573
  'span',
23574
23574
  null,
@@ -23589,14 +23589,18 @@ var SearchInput = function (_a) {
23589
23589
  .concat(isSecondInput ? ' qsm__double-input-options--second-input' : '')
23590
23590
  .concat(isDoubleInput ? ' qsm__double-input-options--splittable' : '')
23591
23591
  },
23592
- searchResults.map(function (result, index) {
23592
+ searchResults.map(function (option, index) {
23593
23593
  return React__default['default'].createElement(
23594
23594
  'div',
23595
23595
  {
23596
23596
  key: index,
23597
23597
  className: 'qsm__double-input-option',
23598
- onClick: function () {
23599
- return onOptionSelect(result);
23598
+ onMouseDown: function (e) {
23599
+ return e.preventDefault();
23600
+ },
23601
+ onClick: function (e) {
23602
+ e.stopPropagation();
23603
+ onOptionSelect(option);
23600
23604
  },
23601
23605
  role: 'option',
23602
23606
  'aria-selected': false
@@ -23604,15 +23608,16 @@ var SearchInput = function (_a) {
23604
23608
  React__default['default'].createElement(
23605
23609
  'div',
23606
23610
  { className: 'qsm__double-input-option-content' },
23607
- React__default['default'].createElement(Icon$1, { name: 'ui-location', height: 16 }),
23611
+ React__default['default'].createElement(Icon$1, { name: option.type == 'hotel' ? 'ui-hotel' : 'ui-location', height: 16 }),
23608
23612
  React__default['default'].createElement(
23609
23613
  'div',
23610
23614
  { className: 'qsm__double-input-option-content-text' },
23611
- highlightMatch(result, highlightTarget),
23612
- React__default['default'].createElement('span', { className: 'qsm__double-input-option-content-country' }, 'Belgi\u00EB')
23615
+ highlightMatch(option, highlightTarget),
23616
+ option.country && React__default['default'].createElement('span', { className: 'qsm__double-input-option-content-country' }, option.country)
23613
23617
  )
23614
23618
  ),
23615
- React__default['default'].createElement('span', { className: 'qsm__double-input-option-content-airport-label' }, '[BRU]')
23619
+ option.iataCode &&
23620
+ React__default['default'].createElement('span', { className: 'qsm__double-input-option-content-airport-label' }, '[', option.iataCode, ']')
23616
23621
  );
23617
23622
  })
23618
23623
  );
@@ -24609,26 +24614,41 @@ var MobileFilterModal = function () {
24609
24614
  hasTypedRef.current = false;
24610
24615
  dispatch(closeMobileFilter());
24611
24616
  };
24617
+ // const handleLocationChange = (val: string) => {
24618
+ // setInputValue(val);
24619
+ // hasTypedRef.current = true;
24620
+ // if (val.trim() !== '' && activeSearchFieldProps) {
24621
+ // const filtered = activeSearchFieldProps.options.filter((loc) => loc.value.toLowerCase().includes(val.toLowerCase())).map((loc) => loc.value);
24622
+ // setSearchResultsLocal(filtered);
24623
+ // } else {
24624
+ // setSearchResultsLocal([]);
24625
+ // }
24626
+ // };
24612
24627
  var handleLocationChange = function (val) {
24613
24628
  setInputValue(val);
24614
24629
  hasTypedRef.current = true;
24615
24630
  if (val.trim() !== '' && activeSearchFieldProps) {
24616
- var filtered = activeSearchFieldProps.options
24617
- .filter(function (loc) {
24618
- return loc.value.toLowerCase().includes(val.toLowerCase());
24619
- })
24620
- .map(function (loc) {
24621
- return loc.value;
24622
- });
24631
+ var filtered = activeSearchFieldProps.options.filter(function (option) {
24632
+ return option.value.toLowerCase().includes(val.toLowerCase());
24633
+ });
24623
24634
  setSearchResultsLocal(filtered);
24624
24635
  } else {
24625
24636
  setSearchResultsLocal([]);
24626
24637
  }
24627
24638
  };
24628
- var handleLocationSelect = function (val) {
24639
+ // const handleLocationSelect = (val: string) => {
24640
+ // if (activeSearchFieldProps) {
24641
+ // const { fieldKey } = activeSearchFieldProps;
24642
+ // dispatch(setFieldValue({ fieldKey, value: val }));
24643
+ // dispatch(setSearchResultsAction([]));
24644
+ // dispatch(setActiveSearchField(null));
24645
+ // }
24646
+ // dispatch(closeMobileFilter());
24647
+ // };
24648
+ var handleLocationSelect = function (option) {
24629
24649
  if (activeSearchFieldProps) {
24630
24650
  var fieldKey = activeSearchFieldProps.fieldKey;
24631
- dispatch(setFieldValue({ fieldKey: fieldKey, value: val }));
24651
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: option.value }));
24632
24652
  dispatch(setSearchResults([]));
24633
24653
  dispatch(setActiveSearchField(null));
24634
24654
  }
@@ -24818,7 +24838,6 @@ var MobileFilterModal = function () {
24818
24838
  }
24819
24839
  };
24820
24840
 
24821
- // SearchInputGroup.tsx
24822
24841
  var findConfig = function (all, key) {
24823
24842
  for (var _i = 0, all_1 = all; _i < all_1.length; _i++) {
24824
24843
  var config = all_1[_i];
@@ -24859,17 +24878,6 @@ var SearchInputGroup = function (_a) {
24859
24878
  var label = config.label,
24860
24879
  placeholder = config.placeholder,
24861
24880
  options = config.options;
24862
- var lowerOptions = React.useMemo(
24863
- function () {
24864
- return options.map(function (x) {
24865
- return {
24866
- value: x.value,
24867
- lower: x.value.toLowerCase()
24868
- };
24869
- });
24870
- },
24871
- [options]
24872
- );
24873
24881
  var selector = React.useMemo(
24874
24882
  function () {
24875
24883
  return function (state) {
@@ -24886,30 +24894,44 @@ var SearchInputGroup = function (_a) {
24886
24894
  searchResults = _g.searchResults,
24887
24895
  activeSearchField = _g.activeSearchField;
24888
24896
  var match = React.useCallback(
24889
- function (option) {
24890
- if (!option) {
24897
+ function (input) {
24898
+ if (!input) {
24891
24899
  return [];
24892
24900
  }
24893
- var lowered = option.toLowerCase();
24894
- return lowerOptions
24895
- .filter(function (x) {
24896
- return x.lower.includes(lowered);
24897
- })
24898
- .map(function (x) {
24899
- return x.value;
24900
- });
24901
+ var lowered = input.toLowerCase();
24902
+ return options.filter(function (option) {
24903
+ return option.value.toLowerCase().includes(lowered);
24904
+ });
24901
24905
  },
24902
- [lowerOptions]
24906
+ [options]
24903
24907
  );
24904
- var change = React.useCallback(
24905
- function (val) {
24906
- dispatch(setFieldValue({ fieldKey: fieldKey, value: val }));
24908
+ var handleInputChange = React.useCallback(
24909
+ function (input) {
24910
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: input }));
24907
24911
  dispatch(setSearchResults([]));
24908
- if (!small) {
24909
- dispatch(setSearchResults(match(val)));
24912
+ if (small) return;
24913
+ if (input.length === 3) {
24914
+ var exactIataMatch = findExactIataMatch(options, input);
24915
+ if (exactIataMatch) {
24916
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: exactIataMatch.value }));
24917
+ dispatch(setSearchResults([]));
24918
+ dispatch(setActiveSearchField(null));
24919
+ return;
24920
+ }
24910
24921
  }
24922
+ // Normal typeahead behavior
24923
+ dispatch(setActiveSearchField(fieldKey));
24924
+ dispatch(setSearchResults(match(input)));
24911
24925
  },
24912
- [dispatch, fieldKey, small, match]
24926
+ [dispatch, fieldKey, small, match, options]
24927
+ );
24928
+ var handleOptionSelect = React.useCallback(
24929
+ function (option) {
24930
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: option.value }));
24931
+ dispatch(setSearchResults([]));
24932
+ dispatch(setActiveSearchField(null));
24933
+ },
24934
+ [dispatch, fieldKey]
24913
24935
  );
24914
24936
  var click = function () {
24915
24937
  dispatch(setActiveSearchField(fieldKey));
@@ -24929,6 +24951,11 @@ var SearchInputGroup = function (_a) {
24929
24951
  dispatch(setSearchResults(match(value)));
24930
24952
  }
24931
24953
  };
24954
+ var findExactIataMatch = function (options, input) {
24955
+ return options.find(function (option) {
24956
+ return option.iataCode && option.iataCode.toLowerCase() === input.toLowerCase();
24957
+ });
24958
+ };
24932
24959
  React.useEffect(
24933
24960
  function () {
24934
24961
  var outside = function (e) {
@@ -24965,9 +24992,12 @@ var SearchInputGroup = function (_a) {
24965
24992
  name: fieldKey,
24966
24993
  value: value,
24967
24994
  readOnly: small,
24968
- onClick: click,
24995
+ onFocus: click,
24996
+ onClick: function (e) {
24997
+ return e.stopPropagation();
24998
+ },
24969
24999
  onChange: function (e) {
24970
- return !small && !readOnlyForced && change(e.target.value);
25000
+ return !small && !readOnlyForced && handleInputChange(e.target.value);
24971
25001
  },
24972
25002
  className: 'qsm__input'.concat(isSecondInput ? ' qsm__input--splittable' : ' u-ps-2'),
24973
25003
  placeholder: placeholder
@@ -24975,12 +25005,9 @@ var SearchInputGroup = function (_a) {
24975
25005
  !small &&
24976
25006
  activeSearchField === fieldKey &&
24977
25007
  React__default['default'].createElement(SearchInput, {
24978
- onChange: change,
25008
+ onChange: handleInputChange,
24979
25009
  searchResults: searchResults,
24980
- onOptionSelect: function (val) {
24981
- change(val);
24982
- dispatch(setActiveSearchField(null));
24983
- },
25010
+ onOptionSelect: handleOptionSelect,
24984
25011
  highlightTarget: highlightTarget,
24985
25012
  label: label,
24986
25013
  isSecondInput: isSecondInput,
@@ -25148,7 +25175,7 @@ var DoubleSearchInputGroup = function (_a) {
25148
25175
  { className: 'qsm__reverse-wrapper' },
25149
25176
  React__default['default'].createElement(
25150
25177
  'button',
25151
- { type: 'button', onClick: reverse, className: 'qsm__input-line--reverse-button' },
25178
+ { type: 'button', onClick: reverse, className: 'qsm__input-line--reverse-button', tabIndex: -1 },
25152
25179
  React__default['default'].createElement(
25153
25180
  'svg',
25154
25181
  { id: 'qsm-planes-icon', viewBox: '0 0 18 18', width: 18, height: 18 },
@@ -25394,17 +25421,22 @@ var TravelInputGroup = function () {
25394
25421
  });
25395
25422
  }
25396
25423
  };
25397
- React.useEffect(function () {
25398
- var handleClickOutside = function (event) {
25399
- if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
25400
- setIsOpen(false);
25424
+ React.useEffect(
25425
+ function () {
25426
+ var handleClickOutside = function (event) {
25427
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
25428
+ setIsOpen(false);
25429
+ }
25430
+ };
25431
+ if (isOpen) {
25432
+ document.addEventListener('mousedown', handleClickOutside);
25401
25433
  }
25402
- };
25403
- document.addEventListener('mousedown', handleClickOutside);
25404
- return function () {
25405
- return document.removeEventListener('mousedown', handleClickOutside);
25406
- };
25407
- }, []);
25434
+ return function () {
25435
+ document.removeEventListener('mousedown', handleClickOutside);
25436
+ };
25437
+ },
25438
+ [isOpen]
25439
+ );
25408
25440
  React.useEffect(
25409
25441
  function () {
25410
25442
  if (initDone.current) {
@@ -25432,8 +25464,8 @@ var TravelInputGroup = function () {
25432
25464
  [defaultTravelers, maxTravelers]
25433
25465
  );
25434
25466
  return React__default['default'].createElement(
25435
- 'label',
25436
- { className: 'qsm__single-input-wrapper qsm__single-input-wrapper--travel' },
25467
+ 'div',
25468
+ { ref: wrapperRef, className: 'qsm__single-input-wrapper qsm__single-input-wrapper--travel' },
25437
25469
  React__default['default'].createElement(Icon$1, { name: 'ui-user', height: 16 }),
25438
25470
  React__default['default'].createElement('span', { className: 'qsm__label' }, 'Met wie ga je?'),
25439
25471
  React__default['default'].createElement('input', {
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import { TypeaheadOption } from '../../types';
2
3
  interface SearchInputProps {
3
4
  onChange: (input: string) => void;
4
- searchResults: string[];
5
- onOptionSelect: (val: string) => void;
5
+ searchResults: TypeaheadOption[];
6
+ onOptionSelect: (val: TypeaheadOption) => void;
6
7
  highlightTarget: string;
7
8
  label: string;
8
9
  isSecondInput?: boolean;
@@ -1,4 +1,4 @@
1
- import { MobileFilterType, Room, TravelerType, TravelType, TravelClass } from '../types';
1
+ import { MobileFilterType, Room, TravelerType, TravelType, TravelClass, TypeaheadOption } from '../types';
2
2
  import { ReactNode } from 'react';
3
3
  export interface QSMState {
4
4
  selectedOrigin?: string;
@@ -9,16 +9,14 @@ export interface QSMState {
9
9
  travelers: any[];
10
10
  travelClass?: string;
11
11
  mobileFilterType: MobileFilterType;
12
- searchResults: string[];
12
+ searchResults: TypeaheadOption[];
13
13
  activeSearchField: string | null;
14
14
  activeSearchFieldProps: {
15
15
  fieldKey: string;
16
16
  label: string;
17
17
  placeholder: string;
18
18
  value: string;
19
- options: {
20
- value: string;
21
- }[];
19
+ options: TypeaheadOption[];
22
20
  } | null;
23
21
  language: 'nl' | 'fr' | 'en';
24
22
  mobileDatePickerMode: 'range' | 'single';
@@ -63,13 +61,11 @@ export declare const setOrigin: import('@reduxjs/toolkit').ActionCreatorWithPayl
63
61
  label: string;
64
62
  placeholder: string;
65
63
  value: string;
66
- options: {
67
- value: string;
68
- }[];
64
+ options: TypeaheadOption[];
69
65
  },
70
66
  'qsm/setActiveSearchFieldProps'
71
67
  >,
72
- setSearchResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<string[], 'qsm/setSearchResults'>,
68
+ setSearchResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<TypeaheadOption[], 'qsm/setSearchResults'>,
73
69
  setLanguage: import('@reduxjs/toolkit').ActionCreatorWithPayload<'nl' | 'fr' | 'en', 'qsm/setLanguage'>,
74
70
  setMobileDatePickerMode: import('@reduxjs/toolkit').ActionCreatorWithPayload<'range' | 'single', 'qsm/setMobileDatePickerMode'>,
75
71
  setMinDate: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string | undefined, 'qsm/setMinDate'>,
@@ -47,6 +47,8 @@ export interface BaseFieldConfig {
47
47
  export interface TypeaheadOption {
48
48
  key: string;
49
49
  value: string;
50
+ iataCode?: string;
51
+ country?: string;
50
52
  type: OptionType;
51
53
  }
52
54
  export type OptionType = 'country' | 'region' | 'oord' | 'location' | 'airport' | 'hotel' | 'other';
@@ -89,6 +91,7 @@ export interface Room {
89
91
  babies: number;
90
92
  }
91
93
  export interface TravelType {
94
+ id: number;
92
95
  label: string;
93
96
  icon?: ReactNode;
94
97
  }
@@ -23422,11 +23422,11 @@ var SearchInput = function (_a) {
23422
23422
  _a.label;
23423
23423
  var isSecondInput = _a.isSecondInput,
23424
23424
  isDoubleInput = _a.isDoubleInput;
23425
- var highlightMatch = function (text, highlight) {
23425
+ var highlightMatch = function (option, highlight) {
23426
23426
  if (!highlight) {
23427
- return text;
23427
+ return option.value;
23428
23428
  }
23429
- var parts = text.split(new RegExp('('.concat(highlight, ')'), 'gi'));
23429
+ var parts = option.value.split(new RegExp('('.concat(highlight, ')'), 'gi'));
23430
23430
  return React.createElement(
23431
23431
  'span',
23432
23432
  null,
@@ -23447,14 +23447,18 @@ var SearchInput = function (_a) {
23447
23447
  .concat(isSecondInput ? ' qsm__double-input-options--second-input' : '')
23448
23448
  .concat(isDoubleInput ? ' qsm__double-input-options--splittable' : '')
23449
23449
  },
23450
- searchResults.map(function (result, index) {
23450
+ searchResults.map(function (option, index) {
23451
23451
  return React.createElement(
23452
23452
  'div',
23453
23453
  {
23454
23454
  key: index,
23455
23455
  className: 'qsm__double-input-option',
23456
- onClick: function () {
23457
- return onOptionSelect(result);
23456
+ onMouseDown: function (e) {
23457
+ return e.preventDefault();
23458
+ },
23459
+ onClick: function (e) {
23460
+ e.stopPropagation();
23461
+ onOptionSelect(option);
23458
23462
  },
23459
23463
  role: 'option',
23460
23464
  'aria-selected': false
@@ -23462,15 +23466,15 @@ var SearchInput = function (_a) {
23462
23466
  React.createElement(
23463
23467
  'div',
23464
23468
  { className: 'qsm__double-input-option-content' },
23465
- React.createElement(Icon$1, { name: 'ui-location', height: 16 }),
23469
+ React.createElement(Icon$1, { name: option.type == 'hotel' ? 'ui-hotel' : 'ui-location', height: 16 }),
23466
23470
  React.createElement(
23467
23471
  'div',
23468
23472
  { className: 'qsm__double-input-option-content-text' },
23469
- highlightMatch(result, highlightTarget),
23470
- React.createElement('span', { className: 'qsm__double-input-option-content-country' }, 'Belgi\u00EB')
23473
+ highlightMatch(option, highlightTarget),
23474
+ option.country && React.createElement('span', { className: 'qsm__double-input-option-content-country' }, option.country)
23471
23475
  )
23472
23476
  ),
23473
- React.createElement('span', { className: 'qsm__double-input-option-content-airport-label' }, '[BRU]')
23477
+ option.iataCode && React.createElement('span', { className: 'qsm__double-input-option-content-airport-label' }, '[', option.iataCode, ']')
23474
23478
  );
23475
23479
  })
23476
23480
  );
@@ -24452,26 +24456,41 @@ var MobileFilterModal = function () {
24452
24456
  hasTypedRef.current = false;
24453
24457
  dispatch(closeMobileFilter());
24454
24458
  };
24459
+ // const handleLocationChange = (val: string) => {
24460
+ // setInputValue(val);
24461
+ // hasTypedRef.current = true;
24462
+ // if (val.trim() !== '' && activeSearchFieldProps) {
24463
+ // const filtered = activeSearchFieldProps.options.filter((loc) => loc.value.toLowerCase().includes(val.toLowerCase())).map((loc) => loc.value);
24464
+ // setSearchResultsLocal(filtered);
24465
+ // } else {
24466
+ // setSearchResultsLocal([]);
24467
+ // }
24468
+ // };
24455
24469
  var handleLocationChange = function (val) {
24456
24470
  setInputValue(val);
24457
24471
  hasTypedRef.current = true;
24458
24472
  if (val.trim() !== '' && activeSearchFieldProps) {
24459
- var filtered = activeSearchFieldProps.options
24460
- .filter(function (loc) {
24461
- return loc.value.toLowerCase().includes(val.toLowerCase());
24462
- })
24463
- .map(function (loc) {
24464
- return loc.value;
24465
- });
24473
+ var filtered = activeSearchFieldProps.options.filter(function (option) {
24474
+ return option.value.toLowerCase().includes(val.toLowerCase());
24475
+ });
24466
24476
  setSearchResultsLocal(filtered);
24467
24477
  } else {
24468
24478
  setSearchResultsLocal([]);
24469
24479
  }
24470
24480
  };
24471
- var handleLocationSelect = function (val) {
24481
+ // const handleLocationSelect = (val: string) => {
24482
+ // if (activeSearchFieldProps) {
24483
+ // const { fieldKey } = activeSearchFieldProps;
24484
+ // dispatch(setFieldValue({ fieldKey, value: val }));
24485
+ // dispatch(setSearchResultsAction([]));
24486
+ // dispatch(setActiveSearchField(null));
24487
+ // }
24488
+ // dispatch(closeMobileFilter());
24489
+ // };
24490
+ var handleLocationSelect = function (option) {
24472
24491
  if (activeSearchFieldProps) {
24473
24492
  var fieldKey = activeSearchFieldProps.fieldKey;
24474
- dispatch(setFieldValue({ fieldKey: fieldKey, value: val }));
24493
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: option.value }));
24475
24494
  dispatch(setSearchResults([]));
24476
24495
  dispatch(setActiveSearchField(null));
24477
24496
  }
@@ -24657,7 +24676,6 @@ var MobileFilterModal = function () {
24657
24676
  }
24658
24677
  };
24659
24678
 
24660
- // SearchInputGroup.tsx
24661
24679
  var findConfig = function (all, key) {
24662
24680
  for (var _i = 0, all_1 = all; _i < all_1.length; _i++) {
24663
24681
  var config = all_1[_i];
@@ -24698,17 +24716,6 @@ var SearchInputGroup = function (_a) {
24698
24716
  var label = config.label,
24699
24717
  placeholder = config.placeholder,
24700
24718
  options = config.options;
24701
- var lowerOptions = useMemo(
24702
- function () {
24703
- return options.map(function (x) {
24704
- return {
24705
- value: x.value,
24706
- lower: x.value.toLowerCase()
24707
- };
24708
- });
24709
- },
24710
- [options]
24711
- );
24712
24719
  var selector = useMemo(
24713
24720
  function () {
24714
24721
  return function (state) {
@@ -24725,30 +24732,44 @@ var SearchInputGroup = function (_a) {
24725
24732
  searchResults = _g.searchResults,
24726
24733
  activeSearchField = _g.activeSearchField;
24727
24734
  var match = useCallback(
24728
- function (option) {
24729
- if (!option) {
24735
+ function (input) {
24736
+ if (!input) {
24730
24737
  return [];
24731
24738
  }
24732
- var lowered = option.toLowerCase();
24733
- return lowerOptions
24734
- .filter(function (x) {
24735
- return x.lower.includes(lowered);
24736
- })
24737
- .map(function (x) {
24738
- return x.value;
24739
- });
24739
+ var lowered = input.toLowerCase();
24740
+ return options.filter(function (option) {
24741
+ return option.value.toLowerCase().includes(lowered);
24742
+ });
24740
24743
  },
24741
- [lowerOptions]
24744
+ [options]
24742
24745
  );
24743
- var change = useCallback(
24744
- function (val) {
24745
- dispatch(setFieldValue({ fieldKey: fieldKey, value: val }));
24746
+ var handleInputChange = useCallback(
24747
+ function (input) {
24748
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: input }));
24746
24749
  dispatch(setSearchResults([]));
24747
- if (!small) {
24748
- dispatch(setSearchResults(match(val)));
24750
+ if (small) return;
24751
+ if (input.length === 3) {
24752
+ var exactIataMatch = findExactIataMatch(options, input);
24753
+ if (exactIataMatch) {
24754
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: exactIataMatch.value }));
24755
+ dispatch(setSearchResults([]));
24756
+ dispatch(setActiveSearchField(null));
24757
+ return;
24758
+ }
24749
24759
  }
24760
+ // Normal typeahead behavior
24761
+ dispatch(setActiveSearchField(fieldKey));
24762
+ dispatch(setSearchResults(match(input)));
24750
24763
  },
24751
- [dispatch, fieldKey, small, match]
24764
+ [dispatch, fieldKey, small, match, options]
24765
+ );
24766
+ var handleOptionSelect = useCallback(
24767
+ function (option) {
24768
+ dispatch(setFieldValue({ fieldKey: fieldKey, value: option.value }));
24769
+ dispatch(setSearchResults([]));
24770
+ dispatch(setActiveSearchField(null));
24771
+ },
24772
+ [dispatch, fieldKey]
24752
24773
  );
24753
24774
  var click = function () {
24754
24775
  dispatch(setActiveSearchField(fieldKey));
@@ -24768,6 +24789,11 @@ var SearchInputGroup = function (_a) {
24768
24789
  dispatch(setSearchResults(match(value)));
24769
24790
  }
24770
24791
  };
24792
+ var findExactIataMatch = function (options, input) {
24793
+ return options.find(function (option) {
24794
+ return option.iataCode && option.iataCode.toLowerCase() === input.toLowerCase();
24795
+ });
24796
+ };
24771
24797
  useEffect(
24772
24798
  function () {
24773
24799
  var outside = function (e) {
@@ -24800,9 +24826,12 @@ var SearchInputGroup = function (_a) {
24800
24826
  name: fieldKey,
24801
24827
  value: value,
24802
24828
  readOnly: small,
24803
- onClick: click,
24829
+ onFocus: click,
24830
+ onClick: function (e) {
24831
+ return e.stopPropagation();
24832
+ },
24804
24833
  onChange: function (e) {
24805
- return !small && !readOnlyForced && change(e.target.value);
24834
+ return !small && !readOnlyForced && handleInputChange(e.target.value);
24806
24835
  },
24807
24836
  className: 'qsm__input'.concat(isSecondInput ? ' qsm__input--splittable' : ' u-ps-2'),
24808
24837
  placeholder: placeholder
@@ -24810,12 +24839,9 @@ var SearchInputGroup = function (_a) {
24810
24839
  !small &&
24811
24840
  activeSearchField === fieldKey &&
24812
24841
  React.createElement(SearchInput, {
24813
- onChange: change,
24842
+ onChange: handleInputChange,
24814
24843
  searchResults: searchResults,
24815
- onOptionSelect: function (val) {
24816
- change(val);
24817
- dispatch(setActiveSearchField(null));
24818
- },
24844
+ onOptionSelect: handleOptionSelect,
24819
24845
  highlightTarget: highlightTarget,
24820
24846
  label: label,
24821
24847
  isSecondInput: isSecondInput,
@@ -24978,7 +25004,7 @@ var DoubleSearchInputGroup = function (_a) {
24978
25004
  { className: 'qsm__reverse-wrapper' },
24979
25005
  React.createElement(
24980
25006
  'button',
24981
- { type: 'button', onClick: reverse, className: 'qsm__input-line--reverse-button' },
25007
+ { type: 'button', onClick: reverse, className: 'qsm__input-line--reverse-button', tabIndex: -1 },
24982
25008
  React.createElement(
24983
25009
  'svg',
24984
25010
  { id: 'qsm-planes-icon', viewBox: '0 0 18 18', width: 18, height: 18 },
@@ -25224,17 +25250,22 @@ var TravelInputGroup = function () {
25224
25250
  });
25225
25251
  }
25226
25252
  };
25227
- useEffect(function () {
25228
- var handleClickOutside = function (event) {
25229
- if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
25230
- setIsOpen(false);
25253
+ useEffect(
25254
+ function () {
25255
+ var handleClickOutside = function (event) {
25256
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
25257
+ setIsOpen(false);
25258
+ }
25259
+ };
25260
+ if (isOpen) {
25261
+ document.addEventListener('mousedown', handleClickOutside);
25231
25262
  }
25232
- };
25233
- document.addEventListener('mousedown', handleClickOutside);
25234
- return function () {
25235
- return document.removeEventListener('mousedown', handleClickOutside);
25236
- };
25237
- }, []);
25263
+ return function () {
25264
+ document.removeEventListener('mousedown', handleClickOutside);
25265
+ };
25266
+ },
25267
+ [isOpen]
25268
+ );
25238
25269
  useEffect(
25239
25270
  function () {
25240
25271
  if (initDone.current) {
@@ -25262,8 +25293,8 @@ var TravelInputGroup = function () {
25262
25293
  [defaultTravelers, maxTravelers]
25263
25294
  );
25264
25295
  return React.createElement(
25265
- 'label',
25266
- { className: 'qsm__single-input-wrapper qsm__single-input-wrapper--travel' },
25296
+ 'div',
25297
+ { ref: wrapperRef, className: 'qsm__single-input-wrapper qsm__single-input-wrapper--travel' },
25267
25298
  React.createElement(Icon$1, { name: 'ui-user', height: 16 }),
25268
25299
  React.createElement('span', { className: 'qsm__label' }, 'Met wie ga je?'),
25269
25300
  React.createElement('input', {
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import { TypeaheadOption } from '../../types';
2
3
  interface SearchInputProps {
3
4
  onChange: (input: string) => void;
4
- searchResults: string[];
5
- onOptionSelect: (val: string) => void;
5
+ searchResults: TypeaheadOption[];
6
+ onOptionSelect: (val: TypeaheadOption) => void;
6
7
  highlightTarget: string;
7
8
  label: string;
8
9
  isSecondInput?: boolean;
@@ -1,4 +1,4 @@
1
- import { MobileFilterType, Room, TravelerType, TravelType, TravelClass } from '../types';
1
+ import { MobileFilterType, Room, TravelerType, TravelType, TravelClass, TypeaheadOption } from '../types';
2
2
  import { ReactNode } from 'react';
3
3
  export interface QSMState {
4
4
  selectedOrigin?: string;
@@ -9,16 +9,14 @@ export interface QSMState {
9
9
  travelers: any[];
10
10
  travelClass?: string;
11
11
  mobileFilterType: MobileFilterType;
12
- searchResults: string[];
12
+ searchResults: TypeaheadOption[];
13
13
  activeSearchField: string | null;
14
14
  activeSearchFieldProps: {
15
15
  fieldKey: string;
16
16
  label: string;
17
17
  placeholder: string;
18
18
  value: string;
19
- options: {
20
- value: string;
21
- }[];
19
+ options: TypeaheadOption[];
22
20
  } | null;
23
21
  language: 'nl' | 'fr' | 'en';
24
22
  mobileDatePickerMode: 'range' | 'single';
@@ -63,13 +61,11 @@ export declare const setOrigin: import('@reduxjs/toolkit').ActionCreatorWithPayl
63
61
  label: string;
64
62
  placeholder: string;
65
63
  value: string;
66
- options: {
67
- value: string;
68
- }[];
64
+ options: TypeaheadOption[];
69
65
  },
70
66
  'qsm/setActiveSearchFieldProps'
71
67
  >,
72
- setSearchResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<string[], 'qsm/setSearchResults'>,
68
+ setSearchResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<TypeaheadOption[], 'qsm/setSearchResults'>,
73
69
  setLanguage: import('@reduxjs/toolkit').ActionCreatorWithPayload<'nl' | 'fr' | 'en', 'qsm/setLanguage'>,
74
70
  setMobileDatePickerMode: import('@reduxjs/toolkit').ActionCreatorWithPayload<'range' | 'single', 'qsm/setMobileDatePickerMode'>,
75
71
  setMinDate: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string | undefined, 'qsm/setMinDate'>,
@@ -47,6 +47,8 @@ export interface BaseFieldConfig {
47
47
  export interface TypeaheadOption {
48
48
  key: string;
49
49
  value: string;
50
+ iataCode?: string;
51
+ country?: string;
50
52
  type: OptionType;
51
53
  }
52
54
  export type OptionType = 'country' | 'region' | 'oord' | 'location' | 'airport' | 'hotel' | 'other';
@@ -89,6 +91,7 @@ export interface Room {
89
91
  babies: number;
90
92
  }
91
93
  export interface TravelType {
94
+ id: number;
92
95
  label: string;
93
96
  icon?: ReactNode;
94
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qite/tide-booking-component",
3
- "version": "1.4.25",
3
+ "version": "1.4.26",
4
4
  "description": "React Booking wizard & Booking product component for Tide",
5
5
  "main": "build/build-cjs/index.js",
6
6
  "module": "build/build-esm/index.js",
@@ -4,7 +4,7 @@ import { TideLogo, language, languages, topLinks, navItems } from '../../../../s
4
4
  import Icon from '../../components/icon';
5
5
  import ContactForm from '../../components/contact';
6
6
  import Slider from '../../components/slider';
7
- import { QSMConfiguration, TypeaheadOption } from '../../../qsm/types';
7
+ import { Nationality, QSMConfiguration, TypeaheadOption } from '../../../qsm/types';
8
8
  import QSM from '../../../qsm';
9
9
 
10
10
  interface ContentPageSelfContainedProps {
@@ -38,6 +38,15 @@ const destinations: TypeaheadOption[] = [
38
38
  { key: 'SIN', value: 'Singapore Changi', type: 'location' }
39
39
  ];
40
40
 
41
+ const nationalities: Nationality[] = [
42
+ { id: 1, label: 'Nederland' },
43
+ { id: 2, label: 'België' },
44
+ { id: 3, label: 'Duitsland' },
45
+ { id: 4, label: 'Frankrijk' },
46
+ { id: 5, label: 'Verenigd Koninkrijk' },
47
+ { id: 6, label: 'Spanje' }
48
+ ];
49
+
41
50
  const configuration: QSMConfiguration = {
42
51
  type: 'hotel',
43
52
  askTravelers: true,
@@ -135,6 +144,8 @@ const configuration: QSMConfiguration = {
135
144
  travelClasses: [],
136
145
  travelClassIcon: '',
137
146
 
147
+ nationalities: nationalities,
148
+
138
149
  dateFlexibility: [
139
150
  {
140
151
  name: '1 dag voor en na',
@@ -11,6 +11,7 @@ import TravelInputGroup from '../travel-input-group';
11
11
  import TravelClassPicker from '../travel-class-picker';
12
12
  import TravelTypePicker from '../travel-type-picker';
13
13
  import Icon from '../icon';
14
+ import TravelNationalityPicker from '../travel-nationality-picker';
14
15
 
15
16
  const QSMContainer: React.FC = () => {
16
17
  const isMobile = useMediaQuery('(max-width: 768px)');
@@ -120,6 +121,7 @@ const QSMContainer: React.FC = () => {
120
121
  <div className="qsm__filter__classgroup">
121
122
  <TravelClassPicker />
122
123
  <TravelTypePicker />
124
+ <TravelNationalityPicker />
123
125
  </div>
124
126
  </div>
125
127
  <div className="qsm__input-group">
@@ -1,9 +1,9 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
2
  import { useDispatch } from 'react-redux';
3
- import { TravelClass, TravelType } from '../../types';
3
+ import { Nationality, TravelClass, TravelType } from '../../types';
4
4
 
5
5
  interface ItemPickerProps {
6
- items: TravelType[] | TravelClass[];
6
+ items: TravelType[] | TravelClass[] | Nationality[];
7
7
  selection: string | undefined;
8
8
  label: string;
9
9
  placeholder: string;
@@ -21,8 +21,8 @@ const ItemPicker: React.FC<ItemPickerProps> = ({ items, selection, label, placeh
21
21
  const toggleButtonRef = useRef<HTMLButtonElement | null>(null);
22
22
 
23
23
  const handlePick = (picked: string) => {
24
- dispatch(onPick(picked));
25
24
  setIsDropdownOpen(false);
25
+ dispatch(onPick(picked));
26
26
  };
27
27
 
28
28
  useEffect(() => {
@@ -49,7 +49,7 @@ const ItemPicker: React.FC<ItemPickerProps> = ({ items, selection, label, placeh
49
49
  }, [isDropdownOpen]);
50
50
 
51
51
  return (
52
- <label className={'dropdown__input ' + classModifier}>
52
+ <div className={'dropdown__input ' + classModifier}>
53
53
  <span className="dropdown__label">{label}</span>
54
54
  <div className="dropdown">
55
55
  <button
@@ -62,7 +62,13 @@ const ItemPicker: React.FC<ItemPickerProps> = ({ items, selection, label, placeh
62
62
  {isDropdownOpen && (
63
63
  <ul className={`dropdown-menu dropdown-menu--${openDirection}`} ref={dropdownMenuRef}>
64
64
  {items.map(({ label, icon }) => (
65
- <li key={label} onClick={() => handlePick(label)} className={`dropdown-menu__item${selection === label ? ' dropdown-menu__item--selected' : ''}`}>
65
+ <li
66
+ key={label}
67
+ onClick={(e) => {
68
+ handlePick(label);
69
+ e.stopPropagation();
70
+ }}
71
+ className={`dropdown-menu__item${selection === label ? ' dropdown-menu__item--selected' : ''}`}>
66
72
  {icon && <span className="travel-class-icon">{icon}</span>}
67
73
  {label}
68
74
  </li>
@@ -70,7 +76,7 @@ const ItemPicker: React.FC<ItemPickerProps> = ({ items, selection, label, placeh
70
76
  </ul>
71
77
  )}
72
78
  </div>
73
- </label>
79
+ </div>
74
80
  );
75
81
  };
76
82
 
@@ -0,0 +1,24 @@
1
+ import React, { useContext } from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { QSMRootState } from '../../store/qsm-store';
4
+ import { setSelectedNationality } from '../../store/qsm-slice';
5
+ import QSMConfigurationContext from '../../qsm-configuration-context';
6
+ import ItemPicker from '../item-picker';
7
+
8
+ const TravelNationalityPicker: React.FC = () => {
9
+ const { nationalities } = useContext(QSMConfigurationContext);
10
+ const { selectedNationality } = useSelector((state: QSMRootState) => state.qsm);
11
+
12
+ return (
13
+ <ItemPicker
14
+ items={nationalities}
15
+ selection={selectedNationality}
16
+ label="Nationaliteit"
17
+ placeholder="Selecteer nationaliteit"
18
+ classModifier="travel-class-picker__items"
19
+ onPick={setSelectedNationality}
20
+ />
21
+ );
22
+ };
23
+
24
+ export default TravelNationalityPicker;
@@ -28,6 +28,8 @@ const QSMConfigurationContext = React.createContext<QSMConfiguration>({
28
28
  travelClasses: [],
29
29
  travelClassIcon: '',
30
30
 
31
+ nationalities: [],
32
+
31
33
  showReturnDate: false,
32
34
  datesIcon: '',
33
35
 
@@ -36,6 +36,7 @@ export interface QSMState {
36
36
  selectedTravelType?: string;
37
37
  travelClasses: TravelClass[];
38
38
  selectedTravelClass?: string;
39
+ selectedNationality?: string;
39
40
  defaultTravelers: number;
40
41
  maxTravelers: number;
41
42
  maxChildAge: number;
@@ -75,6 +76,7 @@ const initialState: QSMState = {
75
76
  selectedTravelType: undefined,
76
77
  travelClasses: [],
77
78
  selectedTravelClass: undefined,
79
+ selectedNationality: undefined,
78
80
  defaultTravelers: 2,
79
81
  maxTravelers: 9,
80
82
  maxChildAge: 12,
@@ -174,6 +176,9 @@ const qsmSlice = createSlice({
174
176
  setSelectedTravelClass: (state, action) => {
175
177
  state.selectedTravelClass = action.payload;
176
178
  },
179
+ setSelectedNationality: (state, action) => {
180
+ state.selectedNationality = action.payload;
181
+ },
177
182
  setDefaultTravelers: (state, action: PayloadAction<number>) => {
178
183
  state.defaultTravelers = action.payload;
179
184
  },
@@ -245,6 +250,7 @@ export const {
245
250
  setSelectedTravelType,
246
251
  setTravelClasses,
247
252
  setSelectedTravelClass,
253
+ setSelectedNationality,
248
254
  setDefaultTravelers,
249
255
  setMaxTravelers,
250
256
  setMaxChildAge,
package/src/qsm/types.ts CHANGED
@@ -54,6 +54,8 @@ export interface QSMConfiguration {
54
54
  onSubmit: (data: any) => void;
55
55
  submitLabel: string;
56
56
  submitIcon: ReactNode;
57
+
58
+ nationalities: Nationality[];
57
59
  }
58
60
 
59
61
  export interface BaseFieldConfig {
@@ -132,3 +134,9 @@ export interface TravelClass {
132
134
  label: string;
133
135
  icon?: ReactNode;
134
136
  }
137
+
138
+ export interface Nationality {
139
+ id: number;
140
+ label: string;
141
+ icon?: ReactNode;
142
+ }