haya-select 1.0.63 → 1.0.64
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/.eslintrc.js +5 -0
- package/README.md +35 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/hayaselect/HayaSelectModule.kt +50 -0
- package/android/src/main/java/expo/modules/hayaselect/HayaSelectView.kt +30 -0
- package/build/config.d.ts +8 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +13 -0
- package/build/config.js.map +1 -0
- package/build/select/index.d.ts +3 -0
- package/build/select/index.d.ts.map +1 -0
- package/build/select/index.js +625 -0
- package/build/select/index.js.map +1 -0
- package/build/select/option-group.d.ts +3 -0
- package/build/select/option-group.d.ts.map +1 -0
- package/build/select/option-group.js +21 -0
- package/build/select/option-group.js.map +1 -0
- package/build/select/option.d.ts +3 -0
- package/build/select/option.d.ts.map +1 -0
- package/build/select/option.js +57 -0
- package/build/select/option.js.map +1 -0
- package/expo-module.config.json +17 -0
- package/ios/HayaSelect.podspec +29 -0
- package/ios/HayaSelectModule.swift +48 -0
- package/ios/HayaSelectView.swift +38 -0
- package/package.json +35 -8
- package/src/select/index.jsx +3 -3
- package/tsconfig.json +10 -0
- package/.github/dependabot.yml +0 -9
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import { anythingDifferent } from "set-state-compare/src/diff-utils";
|
|
2
|
+
import { dig, digg } from "diggerize";
|
|
3
|
+
import debounce from "debounce";
|
|
4
|
+
import FontAwesomeIcon from "react-native-vector-icons/FontAwesome";
|
|
5
|
+
import idForComponent from "@kaspernj/api-maker/build/inputs/id-for-component";
|
|
6
|
+
import nameForComponent from "@kaspernj/api-maker/build/inputs/name-for-component";
|
|
7
|
+
import Option from "./option";
|
|
8
|
+
import OptionGroup from "./option-group";
|
|
9
|
+
import PropTypes from "prop-types";
|
|
10
|
+
import propTypesExact from "prop-types-exact";
|
|
11
|
+
import RenderHtml from "react-native-render-html";
|
|
12
|
+
import { Portal } from "conjointment";
|
|
13
|
+
import { memo, useEffect, useRef } from "react";
|
|
14
|
+
import { shapeComponent, ShapeComponent } from "set-state-compare/src/shape-component.js";
|
|
15
|
+
import { Platform, Pressable, Text, TextInput, View } from "react-native";
|
|
16
|
+
import useEventListener from "@kaspernj/api-maker/build/use-event-listener";
|
|
17
|
+
const nameForComponentWithMultiple = (component) => {
|
|
18
|
+
let name = nameForComponent(component);
|
|
19
|
+
const currentOptions = component.getCurrentOptions();
|
|
20
|
+
if (component.props.multiple && name && currentOptions.length > 0) {
|
|
21
|
+
name += "[]";
|
|
22
|
+
}
|
|
23
|
+
return name;
|
|
24
|
+
};
|
|
25
|
+
export default memo(shapeComponent(class HayaSelect extends ShapeComponent {
|
|
26
|
+
static defaultProps = {
|
|
27
|
+
multiple: false,
|
|
28
|
+
optionsAbsolute: true,
|
|
29
|
+
optionsWidth: null,
|
|
30
|
+
search: false
|
|
31
|
+
};
|
|
32
|
+
static propTypes = propTypesExact({
|
|
33
|
+
attribute: PropTypes.string,
|
|
34
|
+
className: PropTypes.string,
|
|
35
|
+
defaultToggled: PropTypes.object,
|
|
36
|
+
defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
37
|
+
defaultValues: PropTypes.array,
|
|
38
|
+
defaultValuesFromOptions: PropTypes.array,
|
|
39
|
+
id: PropTypes.node,
|
|
40
|
+
model: PropTypes.object,
|
|
41
|
+
multiple: PropTypes.bool.isRequired,
|
|
42
|
+
name: PropTypes.string,
|
|
43
|
+
onChange: PropTypes.func,
|
|
44
|
+
onChangeValue: PropTypes.func,
|
|
45
|
+
onOptionsClosed: PropTypes.func,
|
|
46
|
+
options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]).isRequired,
|
|
47
|
+
optionsAbsolute: PropTypes.bool.isRequired,
|
|
48
|
+
optionsWidth: PropTypes.number,
|
|
49
|
+
placeholder: PropTypes.node,
|
|
50
|
+
search: PropTypes.bool.isRequired,
|
|
51
|
+
selectContainerStyle: PropTypes.object,
|
|
52
|
+
styles: PropTypes.object,
|
|
53
|
+
toggled: PropTypes.object,
|
|
54
|
+
toggleOptions: PropTypes.arrayOf(PropTypes.shape({
|
|
55
|
+
icon: PropTypes.string.isRequired,
|
|
56
|
+
label: PropTypes.string.isRequired,
|
|
57
|
+
value: PropTypes.string.isRequired
|
|
58
|
+
})),
|
|
59
|
+
values: PropTypes.array
|
|
60
|
+
});
|
|
61
|
+
callOptionsPositionAboveIfOutsideScreen = false;
|
|
62
|
+
p = fetchingObject(() => this.props);
|
|
63
|
+
s = fetchingObject(() => this.state);
|
|
64
|
+
tt = fetchingObject(this);
|
|
65
|
+
setup() {
|
|
66
|
+
const { t } = useI18n({ namespace: "haya_select" });
|
|
67
|
+
if (this.props.values) {
|
|
68
|
+
for (const value of this.props.values) {
|
|
69
|
+
if (typeof value == "undefined") {
|
|
70
|
+
throw new Error("HayaSelect: Undefined given as value");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.setInstance({
|
|
75
|
+
endOfSelectRef: useRef(),
|
|
76
|
+
optionsContainerRef: useRef(),
|
|
77
|
+
searchTextInputRef: useRef(),
|
|
78
|
+
selectContainerRef: useRef(),
|
|
79
|
+
t
|
|
80
|
+
});
|
|
81
|
+
this.useStates({
|
|
82
|
+
currentOptions: () => this.defaultCurrentOptions(),
|
|
83
|
+
selectContainerLayout: null,
|
|
84
|
+
endOfSelectLayout: null,
|
|
85
|
+
height: null,
|
|
86
|
+
loadedOptions: () => this.defaultLoadedOptions(),
|
|
87
|
+
opened: false,
|
|
88
|
+
optionsContainerLayout: null,
|
|
89
|
+
optionsPlacement: undefined,
|
|
90
|
+
optionsTop: undefined,
|
|
91
|
+
optionsVisibility: undefined,
|
|
92
|
+
optionsWidth: undefined,
|
|
93
|
+
scrollLeft: document.documentElement.scrollLeft,
|
|
94
|
+
scrollTop: document.documentElement.scrollTop,
|
|
95
|
+
searchText: "",
|
|
96
|
+
toggled: () => this.defaultToggled()
|
|
97
|
+
});
|
|
98
|
+
if (Platform.OS == "web") {
|
|
99
|
+
useEventListener(window, "click", this.tt.onWindowClicked);
|
|
100
|
+
useEventListener(window, "resize", this.tt.onAnythingResizedDebounced);
|
|
101
|
+
useEventListener(window, "scroll", this.tt.onAnythingScrolledDebounced);
|
|
102
|
+
}
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (this.tt.callOptionsPositionAboveIfOutsideScreen && this.s.optionsContainerLayout) {
|
|
105
|
+
this.callOptionsPositionAboveIfOutsideScreen = false;
|
|
106
|
+
this.setOptionsPositionAboveIfOutsideScreen();
|
|
107
|
+
}
|
|
108
|
+
}, [this.tt.callOptionsPositionAboveIfOutsideScreen, this.s.optionsContainerLayout]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const currentOptionIds = this.s.currentOptions?.map((currentOption) => currentOption.value);
|
|
111
|
+
if (this.props.values && anythingDifferent(currentOptionIds, this.props.values) && typeof this.props.options == "function") {
|
|
112
|
+
this.setCurrentFromGivenValues();
|
|
113
|
+
}
|
|
114
|
+
}, [this.props.values]);
|
|
115
|
+
}
|
|
116
|
+
defaultCurrentOptions() {
|
|
117
|
+
const { defaultValue, defaultValues, values } = this.props;
|
|
118
|
+
const { options } = this.p;
|
|
119
|
+
if (!Array.isArray(options))
|
|
120
|
+
return [];
|
|
121
|
+
return options.filter(({ value }) => (defaultValue && value == defaultValue) ||
|
|
122
|
+
(defaultValues && defaultValues.includes(value)) ||
|
|
123
|
+
(values && values.includes(value)));
|
|
124
|
+
}
|
|
125
|
+
defaultLoadedOptions() {
|
|
126
|
+
const { options } = this.p;
|
|
127
|
+
if (typeof options == "function") {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
else if (Array.isArray(options)) {
|
|
131
|
+
return options;
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Unknown type of options: ${typeof options}`);
|
|
134
|
+
}
|
|
135
|
+
defaultToggled = () => ("toggled" in this.props) ? this.p.toggled : this.props.defaultToggled || {};
|
|
136
|
+
getToggled = () => ("toggled" in this.props) ? this.p.toggled : this.s.toggled;
|
|
137
|
+
getValues = () => ("values" in this.props) ? this.p.values : this.s.currentOptions.map((currentOption) => currentOption.value);
|
|
138
|
+
getCurrentOptions = () => {
|
|
139
|
+
if ("values" in this.props && typeof this.props.values != "undefined") {
|
|
140
|
+
if (Array.isArray(this.props.options) && this.props.values) {
|
|
141
|
+
return this.p.values.map((value) => this.p.options.find((option) => option.value == value));
|
|
142
|
+
}
|
|
143
|
+
else if (this.s.loadedOptions && this.props.values) {
|
|
144
|
+
return this.p.values.map((value) => {
|
|
145
|
+
let foundOption = this.s.loadedOptions.find((option) => option.value == value);
|
|
146
|
+
if (!foundOption) {
|
|
147
|
+
foundOption = this.s.currentOptions.find((option) => option.value == value);
|
|
148
|
+
if (!foundOption) {
|
|
149
|
+
console.error(`Couldn't find option: ${value} in loadedOptions or state currentOptions`, { stateLoadedOptions: this.s.loadedOptions, stateCurrentOptions: this.s.currentOptions, propsOptions: this.props.options });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return foundOption;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
else if (typeof this.props.options == "function" && this.props.values) {
|
|
156
|
+
// Options haven't been loaded yet.
|
|
157
|
+
}
|
|
158
|
+
else if (this.props.values) {
|
|
159
|
+
return this.p.values.map((value) => ({ value }));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return this.s.currentOptions;
|
|
163
|
+
};
|
|
164
|
+
getCurrentOptionValues() {
|
|
165
|
+
if ("values" in this.props) {
|
|
166
|
+
return this.p.values;
|
|
167
|
+
}
|
|
168
|
+
return this.getCurrentOptions().map((option) => option.value);
|
|
169
|
+
}
|
|
170
|
+
componentDidMount() {
|
|
171
|
+
const { attribute, defaultValue, defaultValues, defaultValuesFromOptions, model, options } = this.props;
|
|
172
|
+
if (((defaultValue || defaultValues || defaultValuesFromOptions) || (attribute && model)) && typeof options == "function") {
|
|
173
|
+
this.loadDefaultValuesFromOptionsCallback();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
componentDidUpdate() {
|
|
177
|
+
const newState = {};
|
|
178
|
+
if ("toggled" in this.props) {
|
|
179
|
+
const newToggled = this.p.toggled;
|
|
180
|
+
if (anythingDifferent(this.state.toggled, newToggled)) {
|
|
181
|
+
newState.toggled = newToggled;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(newState).length > 0) {
|
|
185
|
+
this.setState(newState);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
render() {
|
|
189
|
+
const { endOfSelectRef } = this.tt;
|
|
190
|
+
const { className, placeholder, toggleOptions } = this.props;
|
|
191
|
+
const { opened, optionsPlacement } = this.s;
|
|
192
|
+
const currentOptions = this.getCurrentOptions();
|
|
193
|
+
const selectContainerStyleActual = this.stylingFor("selectContainer", {
|
|
194
|
+
flexDirection: "row",
|
|
195
|
+
alignItems: "center",
|
|
196
|
+
backgroundColor: "#fff",
|
|
197
|
+
border: "1px solid #999",
|
|
198
|
+
borderRadius: 4,
|
|
199
|
+
color: "#000",
|
|
200
|
+
cursor: "pointer",
|
|
201
|
+
paddingTop: 5,
|
|
202
|
+
paddingBottom: 5,
|
|
203
|
+
paddingLeft: 5
|
|
204
|
+
});
|
|
205
|
+
if (opened) {
|
|
206
|
+
// Prevent select from changing size once the content is replaced with search text once opened
|
|
207
|
+
selectContainerStyleActual.height = this.s.height;
|
|
208
|
+
if (optionsPlacement == "above") {
|
|
209
|
+
selectContainerStyleActual.borderTopLeftRadius = 0;
|
|
210
|
+
selectContainerStyleActual.borderTopRightRadius = 0;
|
|
211
|
+
selectContainerStyleActual.borderBottomRightRadius = selectContainerStyleActual.borderRadius;
|
|
212
|
+
selectContainerStyleActual.borderBottomLeftRadius = selectContainerStyleActual.borderRadius;
|
|
213
|
+
delete selectContainerStyleActual.borderRadius;
|
|
214
|
+
}
|
|
215
|
+
else if (optionsPlacement == "below") {
|
|
216
|
+
selectContainerStyleActual.borderTopLeftRadius = selectContainerStyleActual.borderRadius;
|
|
217
|
+
selectContainerStyleActual.borderTopRightRadius = selectContainerStyleActual.borderRadius;
|
|
218
|
+
selectContainerStyleActual.borderBottomRightRadius = 0;
|
|
219
|
+
selectContainerStyleActual.borderBottomLeftRadius = 0;
|
|
220
|
+
delete selectContainerStyleActual.borderRadius;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const chevronStyle = this.stylingFor("chevron", { fontSize: 24 });
|
|
224
|
+
if (opened) {
|
|
225
|
+
chevronStyle.marginBottom = -15;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
chevronStyle.marginTop = -9;
|
|
229
|
+
}
|
|
230
|
+
return (React.createElement(View, { dataSet: {
|
|
231
|
+
class: className,
|
|
232
|
+
component: "haya-select",
|
|
233
|
+
id: idForComponent(this),
|
|
234
|
+
opened,
|
|
235
|
+
optionsPlacement,
|
|
236
|
+
toggles: Boolean(toggleOptions)
|
|
237
|
+
}, style: this.stylingFor("main") },
|
|
238
|
+
React.createElement(Pressable, { dataSet: { class: "select-container" }, onLayout: this.tt.onSelectContainerLayout, onPress: this.tt.onSelectClicked, ref: this.tt.selectContainerRef, style: selectContainerStyleActual },
|
|
239
|
+
React.createElement(View, { dataSet: { class: "current-selected" }, style: this.stylingFor("currentSelected", { width: "calc(100% - 25px)", flexWrap: "wrap" }) },
|
|
240
|
+
opened &&
|
|
241
|
+
React.createElement(TextInput, { dataSet: { class: "search-text-input" }, onChange: this.tt.onSearchTextInputChangedDebounced, onChangeText: this.tt.onChangeSearchText, placeholder: this.t(".search_dot_dot_dot"), ref: this.tt.searchTextInputRef, style: this.stylingFor("searchTextInput", {
|
|
242
|
+
width: "100%",
|
|
243
|
+
border: 0,
|
|
244
|
+
outline: "none",
|
|
245
|
+
padding: 0
|
|
246
|
+
}), value: this.state.searchText }),
|
|
247
|
+
!opened &&
|
|
248
|
+
React.createElement(React.Fragment, null,
|
|
249
|
+
currentOptions.length == 0 &&
|
|
250
|
+
React.createElement(Text, { numberOfLines: 1, style: this.stylingFor("nothingSelected", { color: "grey" }) }, placeholder || this.t(".nothing_selected")),
|
|
251
|
+
currentOptions.length == 0 && Platform.OS == "web" &&
|
|
252
|
+
React.createElement("input", { id: idForComponent(this), name: nameForComponentWithMultiple(this), type: "hidden", value: "" }),
|
|
253
|
+
currentOptions.map((currentOption) => React.createElement(View, { dataSet: { class: "current-option" }, key: currentOption.key || `current-value-${currentOption.value}`, style: this.stylingFor("currentOption", { marginRight: 6 }) },
|
|
254
|
+
currentOption.type == "group" &&
|
|
255
|
+
React.createElement(View, { style: this.stylingFor("currentOptionGroup", { fontWeight: "bold" }) },
|
|
256
|
+
React.createElement(Text, { style: this.stylingFor("currentOptionGroupText") }, currentOption.text)),
|
|
257
|
+
currentOption.type != "group" &&
|
|
258
|
+
React.createElement(React.Fragment, null,
|
|
259
|
+
Platform.OS == "web" && nameForComponentWithMultiple(this) &&
|
|
260
|
+
React.createElement("input", { id: idForComponent(this), name: nameForComponentWithMultiple(this), type: "hidden", value: digg(currentOption, "value") }),
|
|
261
|
+
this.presentOption(currentOption, "current")))))),
|
|
262
|
+
React.createElement(View, { className: "chevron-container", style: this.stylingFor("chevronContainer", {
|
|
263
|
+
alignItems: "center",
|
|
264
|
+
justifyContent: "center",
|
|
265
|
+
height: "100%",
|
|
266
|
+
marginLeft: "auto"
|
|
267
|
+
}) },
|
|
268
|
+
React.createElement(Text, { style: chevronStyle },
|
|
269
|
+
opened &&
|
|
270
|
+
React.createElement(React.Fragment, null, "\u2303"),
|
|
271
|
+
!opened &&
|
|
272
|
+
React.createElement(React.Fragment, null, "\u2304")))),
|
|
273
|
+
React.createElement(View, { dataSet: { class: "end-of-select" }, onLayout: this.tt.onEndOfSelectLayout, ref: endOfSelectRef }),
|
|
274
|
+
opened &&
|
|
275
|
+
React.createElement(Portal, null, this.optionsContainer())));
|
|
276
|
+
}
|
|
277
|
+
defaultValues() {
|
|
278
|
+
const { attribute, defaultValue, defaultValues, defaultValuesFromOptions, model } = this.props;
|
|
279
|
+
if (defaultValuesFromOptions)
|
|
280
|
+
return defaultValuesFromOptions;
|
|
281
|
+
if (defaultValue)
|
|
282
|
+
return defaultValue;
|
|
283
|
+
if (defaultValues)
|
|
284
|
+
return defaultValues;
|
|
285
|
+
if (attribute && model) {
|
|
286
|
+
if (!(attribute in model))
|
|
287
|
+
throw new Error(`No such attribute on ${model.modelClassData().name}: ${attribute}`);
|
|
288
|
+
return model[attribute]();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
isActive() {
|
|
292
|
+
if (this.tt.endOfSelectRef.current) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
async loadDefaultValuesFromOptionsCallback() {
|
|
298
|
+
const defaultValues = this.defaultValues();
|
|
299
|
+
if (!defaultValues)
|
|
300
|
+
return;
|
|
301
|
+
const result = await this.props.options({
|
|
302
|
+
searchValue: this.s.searchText,
|
|
303
|
+
values: defaultValues
|
|
304
|
+
});
|
|
305
|
+
this.setState({ currentOptions: this.state.currentOptions.concat(result) });
|
|
306
|
+
}
|
|
307
|
+
loadOptions = async () => {
|
|
308
|
+
const { options } = this.p;
|
|
309
|
+
const searchValue = this.s.searchText;
|
|
310
|
+
if (Array.isArray(options)) {
|
|
311
|
+
return this.loadOptionsFromArray(options, searchValue);
|
|
312
|
+
}
|
|
313
|
+
const loadedOptions = await options({ searchValue });
|
|
314
|
+
this.setState({ loadedOptions });
|
|
315
|
+
};
|
|
316
|
+
hayaSelectOption({ key, loadedOption }) {
|
|
317
|
+
if (loadedOption.type == "group") {
|
|
318
|
+
return React.createElement(OptionGroup, { key: key, option: loadedOption });
|
|
319
|
+
}
|
|
320
|
+
return (React.createElement(Option, { currentOptionValues: this.getCurrentOptionValues(), icon: this.iconForOption(loadedOption), key: key, option: loadedOption, onOptionClicked: this.tt.onOptionClicked, presentOption: this.tt.presentOption }));
|
|
321
|
+
}
|
|
322
|
+
loadOptionsFromArray(options, searchValue) {
|
|
323
|
+
const lowerSearchValue = searchValue?.toLowerCase();
|
|
324
|
+
const loadedOptions = options.filter(({ text }) => !lowerSearchValue || text?.toLowerCase()?.includes(lowerSearchValue));
|
|
325
|
+
this.setState({ loadedOptions });
|
|
326
|
+
}
|
|
327
|
+
onSelectContainerLayout = (e) => this.setState({ selectContainerLayout: Object.assign({}, digg(e, "nativeEvent", "layout")) });
|
|
328
|
+
onEndOfSelectLayout = (e) => this.setState({ endOfSelectLayout: Object.assign({}, digg(e, "nativeEvent", "layout")) });
|
|
329
|
+
onOptionsContainerLayout = (e) => this.setState({ optionsContainerLayout: Object.assign({}, digg(e, "nativeEvent", "layout")) });
|
|
330
|
+
onSelectClicked = (e) => {
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
const { opened } = this.s;
|
|
334
|
+
if (opened) {
|
|
335
|
+
this.closeOptions();
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this.openOptions();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
onSearchTextInputChangedDebounced = debounce(this.tt.loadOptions, 200);
|
|
342
|
+
closeOptions() {
|
|
343
|
+
this.setState({
|
|
344
|
+
height: null,
|
|
345
|
+
loadedOptions: undefined,
|
|
346
|
+
opened: false,
|
|
347
|
+
optionsContainerLayout: null
|
|
348
|
+
});
|
|
349
|
+
if (this.props.onOptionsClosed) {
|
|
350
|
+
this.props.onOptionsClosed({ options: this.getCurrentOptions() });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
onChangeSearchText = (searchText) => this.setState({ searchText });
|
|
354
|
+
openOptions() {
|
|
355
|
+
this.setOptionsPositionBelow();
|
|
356
|
+
this.loadOptions();
|
|
357
|
+
this.callOptionsPositionAboveIfOutsideScreen = true;
|
|
358
|
+
this.setState({
|
|
359
|
+
height: this.s.selectContainerLayout.height,
|
|
360
|
+
opened: true,
|
|
361
|
+
scrollLeft: Platform.OS == "web" ? document.documentElement.scrollLeft : null,
|
|
362
|
+
scrollTop: Platform.OS == "web" ? document.documentElement.scrollTop : null,
|
|
363
|
+
searchText: ""
|
|
364
|
+
}, () => {
|
|
365
|
+
this.focusTextInput();
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
focusTextInput = () => digg(this.tt.searchTextInputRef, "current")?.focus();
|
|
369
|
+
setOptionsPosition() {
|
|
370
|
+
if (!this.isActive()) {
|
|
371
|
+
return; // Debounce after un-mount handeling.
|
|
372
|
+
}
|
|
373
|
+
this.callOptionsPositionAboveIfOutsideScreen = true;
|
|
374
|
+
this.setOptionsPositionBelow();
|
|
375
|
+
}
|
|
376
|
+
setOptionsPositionAboveIfOutsideScreen() {
|
|
377
|
+
const { optionsContainerLayout } = this.s;
|
|
378
|
+
const optionsTop = this.s.endOfSelectLayout.top;
|
|
379
|
+
const optionsTotalBottomPosition = optionsContainerLayout.height + optionsTop;
|
|
380
|
+
const windowHeightWithScroll = document.body.clientHeight + this.s.scrollTop;
|
|
381
|
+
if (windowHeightWithScroll < optionsTotalBottomPosition) {
|
|
382
|
+
this.setOptionsPositionAbove();
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
this.setState({ optionsVisibility: "visible" });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
setOptionsPositionAbove() {
|
|
389
|
+
const { endOfSelectLayout } = this.s;
|
|
390
|
+
this.setState({
|
|
391
|
+
opened: true,
|
|
392
|
+
optionsPlacement: "above",
|
|
393
|
+
optionsVisibility: "visible",
|
|
394
|
+
optionsWidth: endOfSelectLayout.width
|
|
395
|
+
}, () => this.focusTextInput());
|
|
396
|
+
}
|
|
397
|
+
setOptionsPositionBelow() {
|
|
398
|
+
this.setState({
|
|
399
|
+
opened: true,
|
|
400
|
+
optionsPlacement: "below",
|
|
401
|
+
optionsVisibility: "hidden",
|
|
402
|
+
optionsWidth: this.s.endOfSelectLayout.width
|
|
403
|
+
}, () => this.focusTextInput());
|
|
404
|
+
}
|
|
405
|
+
onAnythingResized = () => {
|
|
406
|
+
if (this.s.opened) {
|
|
407
|
+
this.setOptionsPosition();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
onAnythingResizedDebounced = debounce(this.tt.onAnythingResized, 25);
|
|
411
|
+
onAnythingScrolled = () => {
|
|
412
|
+
if (this.s.opened) {
|
|
413
|
+
this.setState({
|
|
414
|
+
scrollLeft: document.documentElement.scrollLeft,
|
|
415
|
+
scrollTop: document.documentElement.scrollTop
|
|
416
|
+
});
|
|
417
|
+
this.setOptionsPosition();
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
onAnythingScrolledDebounced = debounce(this.tt.onAnythingScrolled, 25);
|
|
421
|
+
onWindowClicked = (e) => {
|
|
422
|
+
const { optionsContainerRef } = this.tt;
|
|
423
|
+
const { opened } = this.s;
|
|
424
|
+
// If options are open and a click is made outside of the options container
|
|
425
|
+
if (opened && optionsContainerRef.current && !optionsContainerRef.current?.contains(e.target)) {
|
|
426
|
+
this.closeOptions();
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
optionsContainer() {
|
|
430
|
+
const { optionsContainerRef } = this.tt;
|
|
431
|
+
const { optionsAbsolute } = this.p;
|
|
432
|
+
const { selectContainerLayout, loadedOptions, endOfSelectLayout, optionsContainerLayout, optionsPlacement, optionsVisibility } = this.s;
|
|
433
|
+
let left, top;
|
|
434
|
+
let style = {
|
|
435
|
+
position: "absolute",
|
|
436
|
+
zIndex: 99999,
|
|
437
|
+
elevation: 99999,
|
|
438
|
+
visibility: optionsVisibility,
|
|
439
|
+
width: this.p.optionsWidth || this.s.optionsWidth,
|
|
440
|
+
backgroundColor: "#fff",
|
|
441
|
+
border: "1px solid #999",
|
|
442
|
+
maxHeight: 300,
|
|
443
|
+
overflowY: "auto"
|
|
444
|
+
};
|
|
445
|
+
if (!optionsAbsolute) {
|
|
446
|
+
style.left = 0;
|
|
447
|
+
style.bottom = 0;
|
|
448
|
+
}
|
|
449
|
+
else if (optionsPlacement == "below") {
|
|
450
|
+
if (Platform.OS == "web") {
|
|
451
|
+
// onLayout top value is sometimes negative so use browser JS to get it instead
|
|
452
|
+
top = digg(this.tt.endOfSelectRef.current.getBoundingClientRect(), "top") + document.documentElement.scrollTop + 1;
|
|
453
|
+
// onLayout left values doesn't always update when changed
|
|
454
|
+
left = digg(this.tt.endOfSelectRef.current.getBoundingClientRect(), "left") + document.documentElement.scrollLeft;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
left = this.s.endOfSelectLayout.left;
|
|
458
|
+
top = selectContainerLayout.top;
|
|
459
|
+
}
|
|
460
|
+
style.left = left;
|
|
461
|
+
style.top = top - 2;
|
|
462
|
+
}
|
|
463
|
+
else if (optionsPlacement == "above") {
|
|
464
|
+
if (Platform.OS == "web") {
|
|
465
|
+
// onLayout top value is sometimes negative so use browser JS to get it instead
|
|
466
|
+
top = digg(this.tt.selectContainerRef.current.getBoundingClientRect(), "top") + document.documentElement.scrollTop;
|
|
467
|
+
// onLayout left values doesn't always update when changed
|
|
468
|
+
left = digg(this.tt.selectContainerRef.current.getBoundingClientRect(), "left") + document.documentElement.scrollLeft;
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
left = endOfSelectLayout.left;
|
|
472
|
+
top = selectContainerLayout.top;
|
|
473
|
+
}
|
|
474
|
+
style.left = left;
|
|
475
|
+
style.top = top - optionsContainerLayout.height + 1;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
throw new Error(`Unkonwn options placement: ${optionsPlacement}`);
|
|
479
|
+
}
|
|
480
|
+
style = this.stylingFor("optionsContainer", style);
|
|
481
|
+
return (React.createElement(View, { dataSet: { class: "options-container", id: idForComponent(this) }, onLayout: this.tt.onOptionsContainerLayout, ref: optionsContainerRef, style: style },
|
|
482
|
+
loadedOptions?.map((loadedOption) => this.hayaSelectOption({
|
|
483
|
+
key: loadedOption.key || `loaded-option-${loadedOption.value}`,
|
|
484
|
+
loadedOption
|
|
485
|
+
})),
|
|
486
|
+
loadedOptions?.length === 0 &&
|
|
487
|
+
React.createElement(View, { dataSet: { class: "no-options-container" } },
|
|
488
|
+
React.createElement(Text, null, this.t(".no_options_found")))));
|
|
489
|
+
}
|
|
490
|
+
onOptionClicked = (event, loadedOption) => {
|
|
491
|
+
event.preventDefault();
|
|
492
|
+
event.stopPropagation();
|
|
493
|
+
const { onChange, toggleOptions } = this.props;
|
|
494
|
+
const { multiple } = this.p;
|
|
495
|
+
const currentOptions = this.getCurrentOptions();
|
|
496
|
+
const toggled = this.getToggled();
|
|
497
|
+
const newState = {};
|
|
498
|
+
const existingOption = currentOptions.find((currentOption) => currentOption.value == loadedOption.value);
|
|
499
|
+
const newToggled = { ...toggled };
|
|
500
|
+
if (existingOption) {
|
|
501
|
+
if (toggleOptions) {
|
|
502
|
+
const currentToggle = toggled[loadedOption.value];
|
|
503
|
+
const currentIndex = toggleOptions.findIndex((element) => element.value == currentToggle);
|
|
504
|
+
if (currentIndex >= (toggleOptions.length - 1)) {
|
|
505
|
+
// No next toggled - remove toggled and option
|
|
506
|
+
delete newToggled[loadedOption.value];
|
|
507
|
+
newState.currentOptions = currentOptions.filter((currentOption) => currentOption.value != loadedOption.value);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Already toggled - set to next toggle
|
|
511
|
+
newToggled[loadedOption.value] = digg(toggleOptions, currentIndex + 1, "value");
|
|
512
|
+
}
|
|
513
|
+
newState.toggled = newToggled;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
// Remove from current options
|
|
517
|
+
newState.currentOptions = currentOptions.filter((currentOption) => currentOption.value != loadedOption.value);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
// Don't do anything if the clicked option is disabled
|
|
522
|
+
if (loadedOption.disabled)
|
|
523
|
+
return;
|
|
524
|
+
if (toggleOptions) {
|
|
525
|
+
// Set fresh toggle
|
|
526
|
+
newToggled[loadedOption.value] = toggleOptions[0].value;
|
|
527
|
+
newState.toggled = newToggled;
|
|
528
|
+
}
|
|
529
|
+
if (multiple || toggleOptions) {
|
|
530
|
+
newState.currentOptions = currentOptions.concat([loadedOption]);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
newState.currentOptions = [loadedOption];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const options = newState.currentOptions || currentOptions;
|
|
537
|
+
if (onChange) {
|
|
538
|
+
onChange({
|
|
539
|
+
event,
|
|
540
|
+
options,
|
|
541
|
+
toggles: newToggled
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (this.props.onChangeValue) {
|
|
545
|
+
let optionValue;
|
|
546
|
+
if (multiple) {
|
|
547
|
+
optionValue = options.map((option) => option.value);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
optionValue = dig(options, 0, "value");
|
|
551
|
+
}
|
|
552
|
+
this.p.onChangeValue(optionValue);
|
|
553
|
+
}
|
|
554
|
+
if (!multiple)
|
|
555
|
+
this.closeOptions();
|
|
556
|
+
this.setState(newState);
|
|
557
|
+
};
|
|
558
|
+
stylingFor(stylingName, style = {}) {
|
|
559
|
+
let customStyling = dig(this, "props", "styles", stylingName);
|
|
560
|
+
if (typeof customStyling == "function")
|
|
561
|
+
customStyling = customStyling({ state: this.state, style });
|
|
562
|
+
if (customStyling) {
|
|
563
|
+
return Object.assign(style, customStyling);
|
|
564
|
+
}
|
|
565
|
+
return style;
|
|
566
|
+
}
|
|
567
|
+
iconForOption(option) {
|
|
568
|
+
const { toggleOptions } = this.props || {};
|
|
569
|
+
const toggled = this.getToggled();
|
|
570
|
+
if (toggleOptions && (option.value in toggled)) {
|
|
571
|
+
const toggledValue = toggled[option.value];
|
|
572
|
+
const toggledOption = toggleOptions.find((element) => element.value == toggledValue);
|
|
573
|
+
if (!toggledOption) {
|
|
574
|
+
throw new Error(`Couldn't find a toggle option for value: ${toggledValue}`);
|
|
575
|
+
}
|
|
576
|
+
return toggledOption.icon;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
presentOption = (option, mode) => {
|
|
580
|
+
const { toggleOptions } = this.props || {};
|
|
581
|
+
const toggled = this.getToggled();
|
|
582
|
+
const icon = this.iconForOption(option);
|
|
583
|
+
const toggleValue = toggled[option.value];
|
|
584
|
+
const toggleOption = toggleOptions?.find((toggleOptionI) => toggleOptionI.value == toggleValue);
|
|
585
|
+
return (React.createElement(View, { dataSet: {
|
|
586
|
+
class: "option-presentation",
|
|
587
|
+
text: option.text,
|
|
588
|
+
toggleIcon: toggleOption?.icon,
|
|
589
|
+
toggleValue: toggleOption?.value,
|
|
590
|
+
value: option.value
|
|
591
|
+
}, style: { flexDirection: "row" } },
|
|
592
|
+
toggleOptions && !(option.value in toggled) &&
|
|
593
|
+
React.createElement(View, { dataSet: { class: "toggle-icon-placeholder" }, style: { width: 20 } }),
|
|
594
|
+
toggleOptions && (option.value in toggled) &&
|
|
595
|
+
React.createElement(View, { style: { alignItems: "center", justifyContent: "center", width: 20 } },
|
|
596
|
+
React.createElement(FontAwesomeIcon, { dataSet: { class: "toggle-icon" }, name: icon })),
|
|
597
|
+
(() => {
|
|
598
|
+
if (mode == "current" && option.currentContent) {
|
|
599
|
+
return option.currentContent();
|
|
600
|
+
}
|
|
601
|
+
else if (option.content) {
|
|
602
|
+
return option.content();
|
|
603
|
+
}
|
|
604
|
+
else if ("html" in option && Platform.OS != "web") {
|
|
605
|
+
return React.createElement(RenderHtml, { source: { html: digg(option, "html") } });
|
|
606
|
+
}
|
|
607
|
+
else if ("html" in option && Platform.OS == "web") {
|
|
608
|
+
return React.createElement("div", { dangerouslySetInnerHTML: { __html: digg(option, "html") } });
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
return (React.createElement(Text, { dataSet: { class: "option-presentation-text" }, style: this.stylingFor("optionPresentationText", { flex: 1, whiteSpace: "nowrap" }) }, option.text));
|
|
612
|
+
}
|
|
613
|
+
})()));
|
|
614
|
+
};
|
|
615
|
+
async setCurrentFromGivenValues() {
|
|
616
|
+
const { options, values } = this.p;
|
|
617
|
+
const currentOptions = await options({ values });
|
|
618
|
+
const currentValues = currentOptions?.map((currentOption) => currentOption.value);
|
|
619
|
+
const stateValues = this.s.currentOptions?.map((currentOption) => currentOption.value);
|
|
620
|
+
if (anythingDifferent(currentValues, stateValues)) {
|
|
621
|
+
this.setState({ currentOptions });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}));
|
|
625
|
+
//# sourceMappingURL=index.js.map
|