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 +1 -1
- package/src/select/index.jsx +98 -70
package/package.json
CHANGED
package/src/select/index.jsx
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 {
|
|
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
|
-
{
|
|
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 {
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
const
|
|
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 <
|
|
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 {
|
|
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 = (
|
|
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 = (
|
|
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 {
|
|
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
|
-
<
|
|
746
|
+
<View dataSet={{class: "toggle-icon-placeholder"}} style={{width: 25}} />
|
|
722
747
|
}
|
|
723
748
|
{toggleOptions && (currentValue.value in toggled) &&
|
|
724
|
-
<
|
|
749
|
+
<FontAwesomeIcon dataSet={{class: "toggle-icon"}} name={icon} style={{width: 25}} />
|
|
725
750
|
}
|
|
726
751
|
{currentValue.content}
|
|
727
752
|
{!currentValue.content &&
|
|
728
|
-
<Text
|
|
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>
|