haya-select 1.0.45 → 1.0.47
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 +120 -75
package/package.json
CHANGED
package/src/select/index.jsx
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {anythingDifferent} from "set-state-compare/src/diff-utils"
|
|
2
|
-
import
|
|
3
|
-
import {dig, digg, digs} from "diggerize"
|
|
2
|
+
import {dig, digg} from "diggerize"
|
|
4
3
|
import debounce from "debounce"
|
|
4
|
+
import FontAwesomeIcon from "react-native-vector-icons/FontAwesome"
|
|
5
5
|
import idForComponent from "@kaspernj/api-maker/src/inputs/id-for-component.mjs"
|
|
6
6
|
import nameForComponent from "@kaspernj/api-maker/src/inputs/name-for-component.mjs"
|
|
7
7
|
import Option from "./option"
|
|
8
8
|
import OptionGroup from "./option-group"
|
|
9
9
|
import PropTypes from "prop-types"
|
|
10
10
|
import propTypesExact from "prop-types-exact"
|
|
11
|
-
import
|
|
11
|
+
import RenderHtml from "react-native-render-html"
|
|
12
|
+
import {Portal} from "react-native-portalize"
|
|
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,8 @@ const nameForComponentWithMultiple = (component) => {
|
|
|
24
26
|
export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
25
27
|
static defaultProps = {
|
|
26
28
|
multiple: false,
|
|
29
|
+
optionsAbsolute: true,
|
|
30
|
+
optionsWidth: null,
|
|
27
31
|
search: false
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -42,6 +46,8 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
42
46
|
onChangeValue: PropTypes.func,
|
|
43
47
|
onOptionsClosed: PropTypes.func,
|
|
44
48
|
options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]).isRequired,
|
|
49
|
+
optionsAbsolute: PropTypes.bool.isRequired,
|
|
50
|
+
optionsWidth: PropTypes.number,
|
|
45
51
|
placeholder: PropTypes.node,
|
|
46
52
|
search: PropTypes.bool.isRequired,
|
|
47
53
|
selectContainerStyle: PropTypes.object,
|
|
@@ -55,6 +61,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
55
61
|
values: PropTypes.array
|
|
56
62
|
})
|
|
57
63
|
|
|
64
|
+
callOptionsPositionAboveIfOutsideScreen = false
|
|
58
65
|
p = fetchingObject(() => this.props)
|
|
59
66
|
s = fetchingObject(() => this.state)
|
|
60
67
|
tt = fetchingObject(this)
|
|
@@ -73,27 +80,39 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
73
80
|
this.setInstance({
|
|
74
81
|
endOfSelectRef: useRef(),
|
|
75
82
|
optionsContainerRef: useRef(),
|
|
76
|
-
currentSelectedRef: useRef(),
|
|
77
83
|
searchTextInputRef: useRef(),
|
|
84
|
+
selectContainerRef: useRef(),
|
|
78
85
|
t
|
|
79
86
|
})
|
|
80
87
|
this.useStates({
|
|
81
88
|
currentOptions: () => this.defaultCurrentOptions(),
|
|
89
|
+
selectContainerLayout: null,
|
|
90
|
+
endOfSelectLayout: null,
|
|
82
91
|
loadedOptions: () => this.defaultLoadedOptions(),
|
|
83
92
|
opened: false,
|
|
84
|
-
|
|
93
|
+
optionsContainerLayout: null,
|
|
85
94
|
optionsPlacement: undefined,
|
|
86
95
|
optionsTop: undefined,
|
|
87
96
|
optionsVisibility: undefined,
|
|
88
97
|
optionsWidth: undefined,
|
|
98
|
+
scrollLeft: document.documentElement.scrollLeft,
|
|
99
|
+
scrollTop: document.documentElement.scrollTop,
|
|
89
100
|
searchText: "",
|
|
90
|
-
scrollLeft: undefined,
|
|
91
|
-
scrollTop: undefined,
|
|
92
101
|
toggled: () => this.defaultToggled()
|
|
93
102
|
})
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
|
|
104
|
+
if (Platform.OS == "web") {
|
|
105
|
+
useEventListener(window, "click", this.tt.onWindowClicked)
|
|
106
|
+
useEventListener(window, "resize", this.tt.onAnythingResizedDebounced)
|
|
107
|
+
useEventListener(window, "scroll", this.tt.onAnythingScrolledDebounced)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (this.tt.callOptionsPositionAboveIfOutsideScreen && this.s.optionsContainerLayout) {
|
|
112
|
+
this.callOptionsPositionAboveIfOutsideScreen = false
|
|
113
|
+
this.setOptionsPositionAboveIfOutsideScreen()
|
|
114
|
+
}
|
|
115
|
+
}, [this.tt.callOptionsPositionAboveIfOutsideScreen, this.s.optionsContainerLayout])
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
defaultCurrentOptions() {
|
|
@@ -126,7 +145,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
126
145
|
getValues = () => ("values" in this.props) ? this.p.values : this.s.currentOptions.map((currentOption) => currentOption.value)
|
|
127
146
|
getCurrentOptions = () => {
|
|
128
147
|
if ("values" in this.props && typeof this.props.values != "undefined") {
|
|
129
|
-
if (Array.isArray(this.props.options)) {
|
|
148
|
+
if (Array.isArray(this.props.options) && this.props.values) {
|
|
130
149
|
return this.p.values.map((value) => this.p.options.find((option) => option.value == value))
|
|
131
150
|
} else if (this.s.loadedOptions || typeof this.props.options == "function" && this.props.values) {
|
|
132
151
|
if (this.s.loadedOptions) {
|
|
@@ -151,7 +170,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
151
170
|
} else {
|
|
152
171
|
this.setCurrentFromGivenValues()
|
|
153
172
|
}
|
|
154
|
-
} else {
|
|
173
|
+
} else if (this.props.values) {
|
|
155
174
|
return this.p.values.map((value) => ({value}))
|
|
156
175
|
}
|
|
157
176
|
}
|
|
@@ -192,12 +211,10 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
192
211
|
}
|
|
193
212
|
|
|
194
213
|
render() {
|
|
195
|
-
const {
|
|
214
|
+
const {endOfSelectRef} = this.tt
|
|
196
215
|
const {className, placeholder, toggleOptions} = this.props
|
|
197
216
|
const {opened, optionsPlacement} = this.s
|
|
198
217
|
const currentOptions = this.getCurrentOptions()
|
|
199
|
-
const BodyPortal = config.getBodyPortal()
|
|
200
|
-
|
|
201
218
|
const selectContainerStyleActual = this.stylingFor("selectContainer", {
|
|
202
219
|
display: "flex",
|
|
203
220
|
flexDirection: "row",
|
|
@@ -250,19 +267,15 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
250
267
|
}}
|
|
251
268
|
style={this.stylingFor("main")}
|
|
252
269
|
>
|
|
253
|
-
{opened &&
|
|
254
|
-
<BodyPortal>
|
|
255
|
-
{this.optionsContainer()}
|
|
256
|
-
</BodyPortal>
|
|
257
|
-
}
|
|
258
270
|
<Pressable
|
|
259
271
|
dataSet={{class: "select-container"}}
|
|
272
|
+
onLayout={this.tt.onSelectContainerLayout}
|
|
260
273
|
onPress={this.tt.onSelectClicked}
|
|
274
|
+
ref={this.tt.selectContainerRef}
|
|
261
275
|
style={selectContainerStyleActual}
|
|
262
276
|
>
|
|
263
277
|
<View
|
|
264
278
|
dataSet={{class: "current-selected"}}
|
|
265
|
-
ref={currentSelectedRef}
|
|
266
279
|
style={this.stylingFor("currentSelected", {width: "calc(100% - 27px)", flexWrap: "wrap"})}
|
|
267
280
|
>
|
|
268
281
|
{opened &&
|
|
@@ -284,7 +297,7 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
284
297
|
{!opened &&
|
|
285
298
|
<>
|
|
286
299
|
{currentOptions.length == 0 &&
|
|
287
|
-
<Text style={this.stylingFor("nothingSelected", {color: "grey"})}>
|
|
300
|
+
<Text numberOfLines={1} style={this.stylingFor("nothingSelected", {color: "grey"})}>
|
|
288
301
|
{placeholder || this.t(".nothing_selected")}
|
|
289
302
|
</Text>
|
|
290
303
|
}
|
|
@@ -348,7 +361,12 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
348
361
|
</Text>
|
|
349
362
|
</View>
|
|
350
363
|
</Pressable>
|
|
351
|
-
<View ref={endOfSelectRef} />
|
|
364
|
+
<View dataSet={{class: "end-of-select"}} onLayout={this.tt.onEndOfSelectLayout} ref={endOfSelectRef} />
|
|
365
|
+
{opened &&
|
|
366
|
+
<Portal dataSet={{class: "haya-select-portal"}}>
|
|
367
|
+
{this.optionsContainer()}
|
|
368
|
+
</Portal>
|
|
369
|
+
}
|
|
352
370
|
</View>
|
|
353
371
|
)
|
|
354
372
|
}
|
|
@@ -425,6 +443,10 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
425
443
|
this.setState({loadedOptions})
|
|
426
444
|
}
|
|
427
445
|
|
|
446
|
+
onSelectContainerLayout = (e) => this.setState({selectContainerLayout: Object.assign({}, digg(e, "nativeEvent", "layout"))})
|
|
447
|
+
onEndOfSelectLayout = (e) => this.setState({endOfSelectLayout: Object.assign({}, digg(e, "nativeEvent", "layout"))})
|
|
448
|
+
onOptionsContainerLayout = (e) => this.setState({optionsContainerLayout: Object.assign({}, digg(e, "nativeEvent", "layout"))})
|
|
449
|
+
|
|
428
450
|
onSelectClicked = (e) => {
|
|
429
451
|
e.preventDefault()
|
|
430
452
|
e.stopPropagation()
|
|
@@ -443,7 +465,8 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
443
465
|
closeOptions() {
|
|
444
466
|
this.setState({
|
|
445
467
|
loadedOptions: undefined,
|
|
446
|
-
opened: false
|
|
468
|
+
opened: false,
|
|
469
|
+
optionsContainerLayout: null
|
|
447
470
|
})
|
|
448
471
|
|
|
449
472
|
if (this.props.onOptionsClosed) {
|
|
@@ -456,11 +479,16 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
456
479
|
openOptions() {
|
|
457
480
|
this.setOptionsPositionBelow()
|
|
458
481
|
this.loadOptions()
|
|
482
|
+
this.callOptionsPositionAboveIfOutsideScreen = true
|
|
459
483
|
this.setState(
|
|
460
|
-
{
|
|
484
|
+
{
|
|
485
|
+
opened: true,
|
|
486
|
+
scrollLeft: Platform.OS == "web" ? document.documentElement.scrollLeft : null,
|
|
487
|
+
scrollTop: Platform.OS == "web" ? document.documentElement.scrollTop : null,
|
|
488
|
+
searchText: ""
|
|
489
|
+
},
|
|
461
490
|
() => {
|
|
462
491
|
this.focusTextInput()
|
|
463
|
-
this.setOptionsPositionAboveIfOutsideScreen()
|
|
464
492
|
}
|
|
465
493
|
)
|
|
466
494
|
}
|
|
@@ -472,19 +500,17 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
472
500
|
return // Debounce after un-mount handeling.
|
|
473
501
|
}
|
|
474
502
|
|
|
503
|
+
this.callOptionsPositionAboveIfOutsideScreen = true
|
|
475
504
|
this.setOptionsPositionBelow()
|
|
476
|
-
this.setOptionsPositionAboveIfOutsideScreen()
|
|
477
505
|
}
|
|
478
506
|
|
|
479
507
|
setOptionsPositionAboveIfOutsideScreen() {
|
|
480
|
-
const {
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
const optionsTotalHeight = optionsHeight + optionsTop + scrollTop
|
|
485
|
-
const windowHeightWithScroll = window.innerHeight + scrollTop
|
|
508
|
+
const {optionsContainerLayout} = this.s
|
|
509
|
+
const optionsTop = this.s.endOfSelectLayout.top
|
|
510
|
+
const optionsTotalBottomPosition = optionsContainerLayout.height + optionsTop
|
|
511
|
+
const windowHeightWithScroll = document.body.clientHeight + this.s.scrollTop
|
|
486
512
|
|
|
487
|
-
if (windowHeightWithScroll <
|
|
513
|
+
if (windowHeightWithScroll < optionsTotalBottomPosition) {
|
|
488
514
|
this.setOptionsPositionAbove()
|
|
489
515
|
} else {
|
|
490
516
|
this.setState({optionsVisibility: "visible"})
|
|
@@ -492,49 +518,32 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
492
518
|
}
|
|
493
519
|
|
|
494
520
|
setOptionsPositionAbove() {
|
|
495
|
-
const {
|
|
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")
|
|
521
|
+
const {endOfSelectLayout} = this.s
|
|
501
522
|
|
|
502
523
|
this.setState(
|
|
503
524
|
{
|
|
504
525
|
opened: true,
|
|
505
|
-
optionsLeft: left,
|
|
506
526
|
optionsPlacement: "above",
|
|
507
|
-
optionsTop,
|
|
508
527
|
optionsVisibility: "visible",
|
|
509
|
-
optionsWidth: width
|
|
510
|
-
scrollLeft: document.documentElement.scrollLeft,
|
|
511
|
-
scrollTop: document.documentElement.scrollTop
|
|
528
|
+
optionsWidth: endOfSelectLayout.width
|
|
512
529
|
},
|
|
513
530
|
() => this.focusTextInput()
|
|
514
531
|
)
|
|
515
532
|
}
|
|
516
533
|
|
|
517
534
|
setOptionsPositionBelow() {
|
|
518
|
-
const {endOfSelectRef} = this.tt
|
|
519
|
-
const position = endOfSelectRef.current.getBoundingClientRect()
|
|
520
|
-
const {left, top, width} = digs(position, "left", "top", "width")
|
|
521
|
-
|
|
522
535
|
this.setState(
|
|
523
536
|
{
|
|
524
537
|
opened: true,
|
|
525
|
-
optionsLeft: left,
|
|
526
538
|
optionsPlacement: "below",
|
|
527
|
-
optionsTop: top,
|
|
528
539
|
optionsVisibility: "hidden",
|
|
529
|
-
optionsWidth: width
|
|
530
|
-
scrollLeft: document.documentElement.scrollLeft,
|
|
531
|
-
scrollTop: document.documentElement.scrollTop
|
|
540
|
+
optionsWidth: this.s.endOfSelectLayout.width
|
|
532
541
|
},
|
|
533
542
|
() => this.focusTextInput()
|
|
534
543
|
)
|
|
535
544
|
}
|
|
536
545
|
|
|
537
|
-
onAnythingResized = (
|
|
546
|
+
onAnythingResized = () => {
|
|
538
547
|
if (this.s.opened) {
|
|
539
548
|
this.setOptionsPosition()
|
|
540
549
|
}
|
|
@@ -542,8 +551,12 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
542
551
|
|
|
543
552
|
onAnythingResizedDebounced = debounce(this.tt.onAnythingResized, 25)
|
|
544
553
|
|
|
545
|
-
onAnythingScrolled = (
|
|
554
|
+
onAnythingScrolled = () => {
|
|
546
555
|
if (this.s.opened) {
|
|
556
|
+
this.setState({
|
|
557
|
+
scrollLeft: document.documentElement.scrollLeft,
|
|
558
|
+
scrollTop: document.documentElement.scrollTop
|
|
559
|
+
})
|
|
547
560
|
this.setOptionsPosition()
|
|
548
561
|
}
|
|
549
562
|
}
|
|
@@ -562,26 +575,55 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
562
575
|
|
|
563
576
|
optionsContainer() {
|
|
564
577
|
const {optionsContainerRef} = this.tt
|
|
565
|
-
const {
|
|
578
|
+
const {optionsAbsolute} = this.p
|
|
579
|
+
const {selectContainerLayout, loadedOptions, endOfSelectLayout, optionsContainerLayout, optionsPlacement, optionsVisibility} = this.s
|
|
580
|
+
let top
|
|
581
|
+
|
|
582
|
+
const style = this.stylingFor("optionsContainer", {
|
|
583
|
+
position: "absolute",
|
|
584
|
+
zIndex: 99999,
|
|
585
|
+
elevation: 99999,
|
|
586
|
+
visibility: optionsVisibility,
|
|
587
|
+
width: this.p.optionsWidth || this.s.optionsWidth,
|
|
588
|
+
backgroundColor: "#fff",
|
|
589
|
+
border: "1px solid #999",
|
|
590
|
+
maxHeight: 300,
|
|
591
|
+
overflowY: "auto"
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
if (!optionsAbsolute) {
|
|
595
|
+
style.left = 0
|
|
596
|
+
style.bottom = 0
|
|
597
|
+
} else if (optionsPlacement == "below") {
|
|
598
|
+
if (Platform.OS == "web") {
|
|
599
|
+
// onLayout top value is sometimes negative so use browser JS to get it instead
|
|
600
|
+
top = digg(this.tt.endOfSelectRef.current.getBoundingClientRect(), "top")
|
|
601
|
+
} else {
|
|
602
|
+
top = selectContainerLayout.top
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
style.left = this.s.endOfSelectLayout.left
|
|
606
|
+
style.top = top - 2
|
|
607
|
+
} else if (optionsPlacement == "above") {
|
|
608
|
+
if (Platform.OS == "web") {
|
|
609
|
+
// onLayout top value is sometimes negative so use browser JS to get it instead
|
|
610
|
+
top = digg(this.tt.selectContainerRef.current.getBoundingClientRect(), "top")
|
|
611
|
+
} else {
|
|
612
|
+
top = selectContainerLayout.top
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
style.left = endOfSelectLayout.left
|
|
616
|
+
style.top = top - optionsContainerLayout.height + 1
|
|
617
|
+
} else {
|
|
618
|
+
throw new Error(`Unkonwn options placement: ${optionsPlacement}`)
|
|
619
|
+
}
|
|
566
620
|
|
|
567
621
|
return (
|
|
568
622
|
<View
|
|
569
623
|
dataSet={{class: "options-container", id: idForComponent(this)}}
|
|
624
|
+
onLayout={this.tt.onOptionsContainerLayout}
|
|
570
625
|
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
|
-
}
|
|
626
|
+
style={style}
|
|
585
627
|
>
|
|
586
628
|
{loadedOptions?.map((loadedOption) =>
|
|
587
629
|
this.hayaSelectOption({
|
|
@@ -720,18 +762,21 @@ export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
|
720
762
|
}}
|
|
721
763
|
>
|
|
722
764
|
{toggleOptions && !(currentValue.value in toggled) &&
|
|
723
|
-
<
|
|
765
|
+
<View dataSet={{class: "toggle-icon-placeholder"}} style={{width: 25}} />
|
|
724
766
|
}
|
|
725
767
|
{toggleOptions && (currentValue.value in toggled) &&
|
|
726
|
-
<
|
|
768
|
+
<FontAwesomeIcon dataSet={{class: "toggle-icon"}} name={icon} style={{width: 25}} />
|
|
727
769
|
}
|
|
728
770
|
{currentValue.content}
|
|
729
771
|
{!currentValue.content &&
|
|
730
|
-
<Text
|
|
772
|
+
<Text dataSet={{class: "option-presentation-text"}} style={this.stylingFor("optionPresentationText", {whiteSpace: "nowrap"})}>
|
|
731
773
|
{currentValue.text}
|
|
732
774
|
</Text>
|
|
733
775
|
}
|
|
734
|
-
{("html" in currentValue) &&
|
|
776
|
+
{("html" in currentValue) && Platform.OS != "web" &&
|
|
777
|
+
<RenderHtml source={{html: digg(currentValue, "html")}} />
|
|
778
|
+
}
|
|
779
|
+
{("html" in currentValue) && Platform.OS == "web" &&
|
|
735
780
|
<div dangerouslySetInnerHTML={{__html: digg(currentValue, "html")}} />
|
|
736
781
|
}
|
|
737
782
|
</View>
|