haya-select 1.0.44 → 1.0.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haya-select",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -2,13 +2,15 @@ import {anythingDifferent} from "set-state-compare/src/diff-utils"
2
2
  import config from "../config.js"
3
3
  import {dig, digg, digs} from "diggerize"
4
4
  import debounce from "debounce"
5
+ import FontAwesomeIcon from "react-native-vector-icons/FontAwesome"
5
6
  import idForComponent from "@kaspernj/api-maker/src/inputs/id-for-component.mjs"
6
7
  import nameForComponent from "@kaspernj/api-maker/src/inputs/name-for-component.mjs"
7
8
  import Option from "./option"
8
9
  import OptionGroup from "./option-group"
9
10
  import PropTypes from "prop-types"
10
11
  import propTypesExact from "prop-types-exact"
11
- import {memo, useRef} from "react"
12
+ import RenderHtml from "react-native-render-html"
13
+ import {memo, useEffect, useRef} from "react"
12
14
  import {shapeComponent, ShapeComponent} from "set-state-compare/src/shape-component.js"
13
15
  import {Platform, Pressable, Text, TextInput, View} from "react-native"
14
16
  import useEventListener from "@kaspernj/api-maker/src/use-event-listener"
@@ -24,6 +26,7 @@ const nameForComponentWithMultiple = (component) => {
24
26
  export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
25
27
  static defaultProps = {
26
28
  multiple: false,
29
+ optionsWidth: null,
27
30
  search: false
28
31
  }
29
32
 
@@ -42,6 +45,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
42
45
  onChangeValue: PropTypes.func,
43
46
  onOptionsClosed: PropTypes.func,
44
47
  options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]).isRequired,
48
+ optionsWidth: PropTypes.number,
45
49
  placeholder: PropTypes.node,
46
50
  search: PropTypes.bool.isRequired,
47
51
  selectContainerStyle: PropTypes.object,
@@ -55,6 +59,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
55
59
  values: PropTypes.array
56
60
  })
57
61
 
62
+ callOptionsPositionAboveIfOutsideScreen = false
58
63
  p = fetchingObject(() => this.props)
59
64
  s = fetchingObject(() => this.state)
60
65
  tt = fetchingObject(this)
@@ -73,27 +78,38 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
73
78
  this.setInstance({
74
79
  endOfSelectRef: useRef(),
75
80
  optionsContainerRef: useRef(),
76
- currentSelectedRef: useRef(),
77
81
  searchTextInputRef: useRef(),
78
82
  t
79
83
  })
80
84
  this.useStates({
81
85
  currentOptions: () => this.defaultCurrentOptions(),
86
+ selectContainerLayout: null,
87
+ endOfSelectLayout: null,
82
88
  loadedOptions: () => this.defaultLoadedOptions(),
83
89
  opened: false,
84
- optionsLeft: undefined,
90
+ optionsContainerLayout: null,
85
91
  optionsPlacement: undefined,
86
92
  optionsTop: undefined,
87
93
  optionsVisibility: undefined,
88
94
  optionsWidth: undefined,
95
+ scrollLeft: document.documentElement.scrollLeft,
96
+ scrollTop: document.documentElement.scrollTop,
89
97
  searchText: "",
90
- scrollLeft: undefined,
91
- scrollTop: undefined,
92
98
  toggled: () => this.defaultToggled()
93
99
  })
94
- useEventListener(window, "click", this.onWindowClicked)
95
- useEventListener(window, "resize", this.tt.onAnythingResizedDebounced)
96
- useEventListener(window, "scroll", this.tt.onAnythingScrolledDebounced)
100
+
101
+ if (Platform.OS == "web") {
102
+ useEventListener(window, "click", this.tt.onWindowClicked)
103
+ useEventListener(window, "resize", this.tt.onAnythingResizedDebounced)
104
+ useEventListener(window, "scroll", this.tt.onAnythingScrolledDebounced)
105
+ }
106
+
107
+ useEffect(() => {
108
+ if (this.tt.callOptionsPositionAboveIfOutsideScreen && this.s.optionsContainerLayout) {
109
+ this.callOptionsPositionAboveIfOutsideScreen = false
110
+ this.setOptionsPositionAboveIfOutsideScreen()
111
+ }
112
+ }, [this.tt.callOptionsPositionAboveIfOutsideScreen, this.s.optionsContainerLayout])
97
113
  }
98
114
 
99
115
  defaultCurrentOptions() {
@@ -126,7 +142,9 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
126
142
  getValues = () => ("values" in this.props) ? this.p.values : this.s.currentOptions.map((currentOption) => currentOption.value)
127
143
  getCurrentOptions = () => {
128
144
  if ("values" in this.props && typeof this.props.values != "undefined") {
129
- if (this.s.loadedOptions || typeof this.props.options == "function" && this.props.values) {
145
+ if (Array.isArray(this.props.options) && this.props.values) {
146
+ return this.p.values.map((value) => this.p.options.find((option) => option.value == value))
147
+ } else if (this.s.loadedOptions || typeof this.props.options == "function" && this.props.values) {
130
148
  if (this.s.loadedOptions) {
131
149
  const result = this.p.values.map((value) => {
132
150
  let foundOption = this.s.loadedOptions.find((option) => option.value == value)
@@ -135,7 +153,10 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
135
153
  foundOption = this.s.currentOptions.find((option) => option.value == value)
136
154
 
137
155
  if (!foundOption) {
138
- throw new Error(`Couldn't find option: ${value} in loadedOptions or state currentOptions`)
156
+ console.error(
157
+ `Couldn't find option: ${value} in loadedOptions or state currentOptions`,
158
+ {stateLoadedOptions: this.s.loadedOptions, stateCurrentOptions: this.s.currentOptions, propsOptions: this.props.options}
159
+ )
139
160
  }
140
161
  }
141
162
 
@@ -146,9 +167,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
146
167
  } else {
147
168
  this.setCurrentFromGivenValues()
148
169
  }
149
- } else if (Array.isArray(this.props.options)) {
150
- return this.p.values.map((value) => this.p.options.find((option) => option.value == value))
151
- } else {
170
+ } else if (this.props.values) {
152
171
  return this.p.values.map((value) => ({value}))
153
172
  }
154
173
  }
@@ -189,7 +208,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
189
208
  }
190
209
 
191
210
  render() {
192
- const {currentSelectedRef, endOfSelectRef} = this.tt
211
+ const {endOfSelectRef} = this.tt
193
212
  const {className, placeholder, toggleOptions} = this.props
194
213
  const {opened, optionsPlacement} = this.s
195
214
  const currentOptions = this.getCurrentOptions()
@@ -254,12 +273,12 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
254
273
  }
255
274
  <Pressable
256
275
  dataSet={{class: "select-container"}}
276
+ onLayout={this.tt.onSelectContainerLayout}
257
277
  onPress={this.tt.onSelectClicked}
258
278
  style={selectContainerStyleActual}
259
279
  >
260
280
  <View
261
281
  dataSet={{class: "current-selected"}}
262
- ref={currentSelectedRef}
263
282
  style={this.stylingFor("currentSelected", {width: "calc(100% - 27px)", flexWrap: "wrap"})}
264
283
  >
265
284
  {opened &&
@@ -281,7 +300,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
281
300
  {!opened &&
282
301
  <>
283
302
  {currentOptions.length == 0 &&
284
- <Text style={this.stylingFor("nothingSelected", {color: "grey"})}>
303
+ <Text numberOfLines={1} style={this.stylingFor("nothingSelected", {color: "grey"})}>
285
304
  {placeholder || this.t(".nothing_selected")}
286
305
  </Text>
287
306
  }
@@ -345,7 +364,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
345
364
  </Text>
346
365
  </View>
347
366
  </Pressable>
348
- <View ref={endOfSelectRef} />
367
+ <View onLayout={this.tt.onEndOfSelectLayout} ref={endOfSelectRef} />
349
368
  </View>
350
369
  )
351
370
  }
@@ -422,6 +441,10 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
422
441
  this.setState({loadedOptions})
423
442
  }
424
443
 
444
+ onSelectContainerLayout = (e) => this.setState({selectContainerLayout: Object.assign({}, digg(e, "nativeEvent", "layout"))})
445
+ onEndOfSelectLayout = (e) => this.setState({endOfSelectLayout: Object.assign({}, digg(e, "nativeEvent", "layout"))})
446
+ onOptionsContainerLayout = (e) => this.setState({optionsContainerLayout: Object.assign({}, digg(e, "nativeEvent", "layout"))})
447
+
425
448
  onSelectClicked = (e) => {
426
449
  e.preventDefault()
427
450
  e.stopPropagation()
@@ -440,7 +463,8 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
440
463
  closeOptions() {
441
464
  this.setState({
442
465
  loadedOptions: undefined,
443
- opened: false
466
+ opened: false,
467
+ optionsContainerLayout: null
444
468
  })
445
469
 
446
470
  if (this.props.onOptionsClosed) {
@@ -453,11 +477,16 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
453
477
  openOptions() {
454
478
  this.setOptionsPositionBelow()
455
479
  this.loadOptions()
480
+ this.callOptionsPositionAboveIfOutsideScreen = true
456
481
  this.setState(
457
- {opened: true, searchText: ""},
482
+ {
483
+ opened: true,
484
+ scrollLeft: Platform.OS == "web" ? document.documentElement.scrollLeft : null,
485
+ scrollTop: Platform.OS == "web" ? document.documentElement.scrollTop : null,
486
+ searchText: ""
487
+ },
458
488
  () => {
459
489
  this.focusTextInput()
460
- this.setOptionsPositionAboveIfOutsideScreen()
461
490
  }
462
491
  )
463
492
  }
@@ -469,19 +498,17 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
469
498
  return // Debounce after un-mount handeling.
470
499
  }
471
500
 
501
+ this.callOptionsPositionAboveIfOutsideScreen = true
472
502
  this.setOptionsPositionBelow()
473
- this.setOptionsPositionAboveIfOutsideScreen()
474
503
  }
475
504
 
476
505
  setOptionsPositionAboveIfOutsideScreen() {
477
- const {optionsContainerRef} = this.tt
478
- const {optionsTop} = this.s
479
- const optionsHeight = digg(optionsContainerRef, "current", "offsetHeight")
480
- const scrollTop = document.documentElement.scrollTop
481
- const optionsTotalHeight = optionsHeight + optionsTop + scrollTop
482
- const windowHeightWithScroll = window.innerHeight + scrollTop
506
+ const {optionsContainerLayout} = this.s
507
+ const optionsTop = this.s.endOfSelectLayout.top
508
+ const optionsTotalBottomPosition = optionsContainerLayout.height + optionsTop
509
+ const windowHeightWithScroll = document.body.clientHeight + this.s.scrollTop
483
510
 
484
- if (windowHeightWithScroll < optionsTotalHeight) {
511
+ if (windowHeightWithScroll < optionsTotalBottomPosition) {
485
512
  this.setOptionsPositionAbove()
486
513
  } else {
487
514
  this.setState({optionsVisibility: "visible"})
@@ -489,49 +516,32 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
489
516
  }
490
517
 
491
518
  setOptionsPositionAbove() {
492
- const {optionsContainerRef, currentSelectedRef, endOfSelectRef} = this.tt
493
- const optionsHeight = digg(optionsContainerRef, "current", "offsetHeight")
494
- const position = currentSelectedRef.current.getBoundingClientRect()
495
- const {top} = digs(position, "top")
496
- const optionsTop = top - optionsHeight + 2
497
- const {left, width} = digs(endOfSelectRef.current.getBoundingClientRect(), "left", "width")
519
+ const {endOfSelectLayout} = this.s
498
520
 
499
521
  this.setState(
500
522
  {
501
523
  opened: true,
502
- optionsLeft: left,
503
524
  optionsPlacement: "above",
504
- optionsTop,
505
525
  optionsVisibility: "visible",
506
- optionsWidth: width,
507
- scrollLeft: document.documentElement.scrollLeft,
508
- scrollTop: document.documentElement.scrollTop
526
+ optionsWidth: endOfSelectLayout.width
509
527
  },
510
528
  () => this.focusTextInput()
511
529
  )
512
530
  }
513
531
 
514
532
  setOptionsPositionBelow() {
515
- const {endOfSelectRef} = this.tt
516
- const position = endOfSelectRef.current.getBoundingClientRect()
517
- const {left, top, width} = digs(position, "left", "top", "width")
518
-
519
533
  this.setState(
520
534
  {
521
535
  opened: true,
522
- optionsLeft: left,
523
536
  optionsPlacement: "below",
524
- optionsTop: top,
525
537
  optionsVisibility: "hidden",
526
- optionsWidth: width,
527
- scrollLeft: document.documentElement.scrollLeft,
528
- scrollTop: document.documentElement.scrollTop
538
+ optionsWidth: this.s.endOfSelectLayout.width
529
539
  },
530
540
  () => this.focusTextInput()
531
541
  )
532
542
  }
533
543
 
534
- onAnythingResized = (e) => {
544
+ onAnythingResized = () => {
535
545
  if (this.s.opened) {
536
546
  this.setOptionsPosition()
537
547
  }
@@ -539,8 +549,12 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
539
549
 
540
550
  onAnythingResizedDebounced = debounce(this.tt.onAnythingResized, 25)
541
551
 
542
- onAnythingScrolled = (e) => {
552
+ onAnythingScrolled = () => {
543
553
  if (this.s.opened) {
554
+ this.setState({
555
+ scrollLeft: document.documentElement.scrollLeft,
556
+ scrollTop: document.documentElement.scrollTop
557
+ })
544
558
  this.setOptionsPosition()
545
559
  }
546
560
  }
@@ -559,26 +573,38 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
559
573
 
560
574
  optionsContainer() {
561
575
  const {optionsContainerRef} = this.tt
562
- const {loadedOptions, optionsLeft, optionsTop, optionsVisibility, optionsWidth, scrollLeft, scrollTop} = this.s
576
+ const {selectContainerLayout, loadedOptions, endOfSelectLayout, optionsContainerLayout, optionsPlacement, optionsVisibility} = this.s
577
+ let left, top
578
+
579
+ if (optionsPlacement == "below") {
580
+ left = this.s.endOfSelectLayout.left
581
+ top = this.s.endOfSelectLayout.top - 2
582
+ } else if (optionsPlacement == "above") {
583
+ left = endOfSelectLayout.left
584
+ top = selectContainerLayout.top - optionsContainerLayout.height + 1
585
+ } else {
586
+ throw new Error(`Unkonwn options placement: ${optionsPlacement}`)
587
+ }
588
+
589
+ const style = this.stylingFor("optionsContainer", {
590
+ position: "absolute",
591
+ left,
592
+ top,
593
+ zIndex: 99999,
594
+ visibility: optionsVisibility,
595
+ width: this.p.optionsWidth || this.s.optionsWidth,
596
+ backgroundColor: "#fff",
597
+ border: "1px solid #999",
598
+ maxHeight: 300,
599
+ overflowY: "auto"
600
+ })
563
601
 
564
602
  return (
565
603
  <View
566
604
  dataSet={{class: "options-container", id: idForComponent(this)}}
605
+ onLayout={this.onOptionsContainerLayout}
567
606
  ref={optionsContainerRef}
568
- style={
569
- this.stylingFor("optionsContainer", {
570
- position: "absolute",
571
- left: optionsLeft + scrollLeft,
572
- top: optionsTop + scrollTop - 1,
573
- zIndex: 99999,
574
- visibility: optionsVisibility,
575
- width: optionsWidth,
576
- backgroundColor: "#fff",
577
- border: "1px solid #999",
578
- maxHeight: 300,
579
- overflowY: "auto"
580
- })
581
- }
607
+ style={style}
582
608
  >
583
609
  {loadedOptions?.map((loadedOption) =>
584
610
  this.hayaSelectOption({
@@ -635,7 +661,6 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
635
661
 
636
662
  if (toggleOptions) {
637
663
  // Set fresh toggle
638
-
639
664
  newToggled[loadedOption.value] = toggleOptions[0].value
640
665
  newState.toggled = newToggled
641
666
  }
@@ -718,18 +743,21 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
718
743
  }}
719
744
  >
720
745
  {toggleOptions && !(currentValue.value in toggled) &&
721
- <i className="fa fa-fw" />
746
+ <View dataSet={{class: "toggle-icon-placeholder"}} style={{width: 25}} />
722
747
  }
723
748
  {toggleOptions && (currentValue.value in toggled) &&
724
- <i className={`fa fa-fw fa-${icon}`} />
749
+ <FontAwesomeIcon dataSet={{class: "toggle-icon"}} name={icon} style={{width: 25}} />
725
750
  }
726
751
  {currentValue.content}
727
752
  {!currentValue.content &&
728
- <Text className="option-presentation-text" style={this.stylingFor("optionPresentationText", {whiteSpace: "nowrap"})}>
753
+ <Text dataSet={{class: "option-presentation-text"}} style={this.stylingFor("optionPresentationText", {whiteSpace: "nowrap"})}>
729
754
  {currentValue.text}
730
755
  </Text>
731
756
  }
732
- {("html" in currentValue) &&
757
+ {("html" in currentValue) && Platform.OS != "web" &&
758
+ <RenderHtml source={{html: digg(currentValue, "html")}} />
759
+ }
760
+ {("html" in currentValue) && Platform.OS == "web" &&
733
761
  <div dangerouslySetInnerHTML={{__html: digg(currentValue, "html")}} />
734
762
  }
735
763
  </View>