haya-select 1.0.45 → 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.45",
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,7 @@ 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 (Array.isArray(this.props.options)) {
145
+ if (Array.isArray(this.props.options) && this.props.values) {
130
146
  return this.p.values.map((value) => this.p.options.find((option) => option.value == value))
131
147
  } else if (this.s.loadedOptions || typeof this.props.options == "function" && this.props.values) {
132
148
  if (this.s.loadedOptions) {
@@ -151,7 +167,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
151
167
  } else {
152
168
  this.setCurrentFromGivenValues()
153
169
  }
154
- } else {
170
+ } else if (this.props.values) {
155
171
  return this.p.values.map((value) => ({value}))
156
172
  }
157
173
  }
@@ -192,7 +208,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
192
208
  }
193
209
 
194
210
  render() {
195
- const {currentSelectedRef, endOfSelectRef} = this.tt
211
+ const {endOfSelectRef} = this.tt
196
212
  const {className, placeholder, toggleOptions} = this.props
197
213
  const {opened, optionsPlacement} = this.s
198
214
  const currentOptions = this.getCurrentOptions()
@@ -257,12 +273,12 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
257
273
  }
258
274
  <Pressable
259
275
  dataSet={{class: "select-container"}}
276
+ onLayout={this.tt.onSelectContainerLayout}
260
277
  onPress={this.tt.onSelectClicked}
261
278
  style={selectContainerStyleActual}
262
279
  >
263
280
  <View
264
281
  dataSet={{class: "current-selected"}}
265
- ref={currentSelectedRef}
266
282
  style={this.stylingFor("currentSelected", {width: "calc(100% - 27px)", flexWrap: "wrap"})}
267
283
  >
268
284
  {opened &&
@@ -284,7 +300,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
284
300
  {!opened &&
285
301
  <>
286
302
  {currentOptions.length == 0 &&
287
- <Text style={this.stylingFor("nothingSelected", {color: "grey"})}>
303
+ <Text numberOfLines={1} style={this.stylingFor("nothingSelected", {color: "grey"})}>
288
304
  {placeholder || this.t(".nothing_selected")}
289
305
  </Text>
290
306
  }
@@ -348,7 +364,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
348
364
  </Text>
349
365
  </View>
350
366
  </Pressable>
351
- <View ref={endOfSelectRef} />
367
+ <View onLayout={this.tt.onEndOfSelectLayout} ref={endOfSelectRef} />
352
368
  </View>
353
369
  )
354
370
  }
@@ -425,6 +441,10 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
425
441
  this.setState({loadedOptions})
426
442
  }
427
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
+
428
448
  onSelectClicked = (e) => {
429
449
  e.preventDefault()
430
450
  e.stopPropagation()
@@ -443,7 +463,8 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
443
463
  closeOptions() {
444
464
  this.setState({
445
465
  loadedOptions: undefined,
446
- opened: false
466
+ opened: false,
467
+ optionsContainerLayout: null
447
468
  })
448
469
 
449
470
  if (this.props.onOptionsClosed) {
@@ -456,11 +477,16 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
456
477
  openOptions() {
457
478
  this.setOptionsPositionBelow()
458
479
  this.loadOptions()
480
+ this.callOptionsPositionAboveIfOutsideScreen = true
459
481
  this.setState(
460
- {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
+ },
461
488
  () => {
462
489
  this.focusTextInput()
463
- this.setOptionsPositionAboveIfOutsideScreen()
464
490
  }
465
491
  )
466
492
  }
@@ -472,19 +498,17 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
472
498
  return // Debounce after un-mount handeling.
473
499
  }
474
500
 
501
+ this.callOptionsPositionAboveIfOutsideScreen = true
475
502
  this.setOptionsPositionBelow()
476
- this.setOptionsPositionAboveIfOutsideScreen()
477
503
  }
478
504
 
479
505
  setOptionsPositionAboveIfOutsideScreen() {
480
- const {optionsContainerRef} = this.tt
481
- const {optionsTop} = this.s
482
- const optionsHeight = digg(optionsContainerRef, "current", "offsetHeight")
483
- const scrollTop = document.documentElement.scrollTop
484
- const optionsTotalHeight = optionsHeight + optionsTop + scrollTop
485
- 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
486
510
 
487
- if (windowHeightWithScroll < optionsTotalHeight) {
511
+ if (windowHeightWithScroll < optionsTotalBottomPosition) {
488
512
  this.setOptionsPositionAbove()
489
513
  } else {
490
514
  this.setState({optionsVisibility: "visible"})
@@ -492,49 +516,32 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
492
516
  }
493
517
 
494
518
  setOptionsPositionAbove() {
495
- const {optionsContainerRef, currentSelectedRef, endOfSelectRef} = this.tt
496
- const optionsHeight = digg(optionsContainerRef, "current", "offsetHeight")
497
- const position = currentSelectedRef.current.getBoundingClientRect()
498
- const {top} = digs(position, "top")
499
- const optionsTop = top - optionsHeight + 2
500
- const {left, width} = digs(endOfSelectRef.current.getBoundingClientRect(), "left", "width")
519
+ const {endOfSelectLayout} = this.s
501
520
 
502
521
  this.setState(
503
522
  {
504
523
  opened: true,
505
- optionsLeft: left,
506
524
  optionsPlacement: "above",
507
- optionsTop,
508
525
  optionsVisibility: "visible",
509
- optionsWidth: width,
510
- scrollLeft: document.documentElement.scrollLeft,
511
- scrollTop: document.documentElement.scrollTop
526
+ optionsWidth: endOfSelectLayout.width
512
527
  },
513
528
  () => this.focusTextInput()
514
529
  )
515
530
  }
516
531
 
517
532
  setOptionsPositionBelow() {
518
- const {endOfSelectRef} = this.tt
519
- const position = endOfSelectRef.current.getBoundingClientRect()
520
- const {left, top, width} = digs(position, "left", "top", "width")
521
-
522
533
  this.setState(
523
534
  {
524
535
  opened: true,
525
- optionsLeft: left,
526
536
  optionsPlacement: "below",
527
- optionsTop: top,
528
537
  optionsVisibility: "hidden",
529
- optionsWidth: width,
530
- scrollLeft: document.documentElement.scrollLeft,
531
- scrollTop: document.documentElement.scrollTop
538
+ optionsWidth: this.s.endOfSelectLayout.width
532
539
  },
533
540
  () => this.focusTextInput()
534
541
  )
535
542
  }
536
543
 
537
- onAnythingResized = (e) => {
544
+ onAnythingResized = () => {
538
545
  if (this.s.opened) {
539
546
  this.setOptionsPosition()
540
547
  }
@@ -542,8 +549,12 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
542
549
 
543
550
  onAnythingResizedDebounced = debounce(this.tt.onAnythingResized, 25)
544
551
 
545
- onAnythingScrolled = (e) => {
552
+ onAnythingScrolled = () => {
546
553
  if (this.s.opened) {
554
+ this.setState({
555
+ scrollLeft: document.documentElement.scrollLeft,
556
+ scrollTop: document.documentElement.scrollTop
557
+ })
547
558
  this.setOptionsPosition()
548
559
  }
549
560
  }
@@ -562,26 +573,38 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
562
573
 
563
574
  optionsContainer() {
564
575
  const {optionsContainerRef} = this.tt
565
- 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
+ })
566
601
 
567
602
  return (
568
603
  <View
569
604
  dataSet={{class: "options-container", id: idForComponent(this)}}
605
+ onLayout={this.onOptionsContainerLayout}
570
606
  ref={optionsContainerRef}
571
- style={
572
- this.stylingFor("optionsContainer", {
573
- position: "absolute",
574
- left: optionsLeft + scrollLeft,
575
- top: optionsTop + scrollTop - 1,
576
- zIndex: 99999,
577
- visibility: optionsVisibility,
578
- width: optionsWidth,
579
- backgroundColor: "#fff",
580
- border: "1px solid #999",
581
- maxHeight: 300,
582
- overflowY: "auto"
583
- })
584
- }
607
+ style={style}
585
608
  >
586
609
  {loadedOptions?.map((loadedOption) =>
587
610
  this.hayaSelectOption({
@@ -720,18 +743,21 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
720
743
  }}
721
744
  >
722
745
  {toggleOptions && !(currentValue.value in toggled) &&
723
- <i className="fa fa-fw" />
746
+ <View dataSet={{class: "toggle-icon-placeholder"}} style={{width: 25}} />
724
747
  }
725
748
  {toggleOptions && (currentValue.value in toggled) &&
726
- <i className={`fa fa-fw fa-${icon}`} />
749
+ <FontAwesomeIcon dataSet={{class: "toggle-icon"}} name={icon} style={{width: 25}} />
727
750
  }
728
751
  {currentValue.content}
729
752
  {!currentValue.content &&
730
- <Text className="option-presentation-text" style={this.stylingFor("optionPresentationText", {whiteSpace: "nowrap"})}>
753
+ <Text dataSet={{class: "option-presentation-text"}} style={this.stylingFor("optionPresentationText", {whiteSpace: "nowrap"})}>
731
754
  {currentValue.text}
732
755
  </Text>
733
756
  }
734
- {("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" &&
735
761
  <div dangerouslySetInnerHTML={{__html: digg(currentValue, "html")}} />
736
762
  }
737
763
  </View>