ferns-ui 2.0.0-beta.2 → 2.0.0-beta.5
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/dist/Accordion.js +7 -2
- package/dist/Accordion.js.map +1 -1
- package/dist/ActionSheet.js +14 -11
- package/dist/ActionSheet.js.map +1 -1
- package/dist/AddressField.js +1 -1
- package/dist/AddressField.js.map +1 -1
- package/dist/Badge.js +1 -1
- package/dist/Badge.js.map +1 -1
- package/dist/Banner.js +1 -1
- package/dist/Banner.js.map +1 -1
- package/dist/Box.js +3 -3
- package/dist/Box.js.map +1 -1
- package/dist/Button.js +0 -1
- package/dist/Button.js.map +1 -1
- package/dist/CheckBox.js.map +1 -1
- package/dist/Common.d.ts +14 -9
- package/dist/Common.js.map +1 -1
- package/dist/DataTable.js +1 -2
- package/dist/DataTable.js.map +1 -1
- package/dist/DateTimeField.js +22 -22
- package/dist/DateTimeField.js.map +1 -1
- package/dist/EmailField.js +17 -37
- package/dist/EmailField.js.map +1 -1
- package/dist/EmojiSelector.d.ts +114 -0
- package/dist/EmojiSelector.js +332 -0
- package/dist/EmojiSelector.js.map +1 -0
- package/dist/FernsProvider.js +1 -1
- package/dist/FernsProvider.js.map +1 -1
- package/dist/Heading.js +3 -1
- package/dist/Heading.js.map +1 -1
- package/dist/Hyperlink.js +1 -1
- package/dist/Hyperlink.js.map +1 -1
- package/dist/IconButton.js +1 -1
- package/dist/IconButton.js.map +1 -1
- package/dist/Image.js.map +1 -1
- package/dist/MarkdownView.d.ts +5 -0
- package/dist/MarkdownView.js +44 -0
- package/dist/MarkdownView.js.map +1 -0
- package/dist/MobileAddressAutoComplete.js +1 -1
- package/dist/MobileAddressAutoComplete.js.map +1 -1
- package/dist/Modal.d.ts +1 -1
- package/dist/Modal.js +35 -15
- package/dist/Modal.js.map +1 -1
- package/dist/NumberField.js +10 -4
- package/dist/NumberField.js.map +1 -1
- package/dist/NumberPickerActionSheet.d.ts +1 -3
- package/dist/NumberPickerActionSheet.js +0 -3
- package/dist/NumberPickerActionSheet.js.map +1 -1
- package/dist/Page.js +1 -1
- package/dist/Page.js.map +1 -1
- package/dist/Pagination.js +2 -2
- package/dist/Pagination.js.map +1 -1
- package/dist/Permissions.d.ts +1 -1
- package/dist/Permissions.js +2 -2
- package/dist/Permissions.js.map +1 -1
- package/dist/PickerSelect.js +1 -1
- package/dist/PickerSelect.js.map +1 -1
- package/dist/SectionDivider.js +1 -1
- package/dist/SectionDivider.js.map +1 -1
- package/dist/SegmentedControl.js.map +1 -1
- package/dist/Signature.native.js +2 -2
- package/dist/Signature.native.js.map +1 -1
- package/dist/SignatureField.js +2 -2
- package/dist/SignatureField.js.map +1 -1
- package/dist/Slider.js +3 -3
- package/dist/Slider.js.map +1 -1
- package/dist/SplitPage.js +7 -7
- package/dist/SplitPage.js.map +1 -1
- package/dist/SplitPage.native.js +4 -6
- package/dist/SplitPage.native.js.map +1 -1
- package/dist/TapToEdit.js +3 -3
- package/dist/TapToEdit.js.map +1 -1
- package/dist/Text.js +1 -1
- package/dist/Text.js.map +1 -1
- package/dist/TextField.js +9 -2
- package/dist/TextField.js.map +1 -1
- package/dist/TextFieldNumberActionSheet.d.ts +2 -4
- package/dist/TextFieldNumberActionSheet.js +1 -4
- package/dist/TextFieldNumberActionSheet.js.map +1 -1
- package/dist/Tooltip.js +37 -19
- package/dist/Tooltip.js.map +1 -1
- package/dist/Unifier.d.ts +0 -1
- package/dist/Unifier.js.map +1 -1
- package/dist/Utilities.d.ts +1 -1
- package/dist/Utilities.js +2 -3
- package/dist/Utilities.js.map +1 -1
- package/dist/WebAddressAutocomplete.js +2 -1
- package/dist/WebAddressAutocomplete.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/dist/table/Table.js +14 -15
- package/dist/table/Table.js.map +1 -1
- package/dist/table/TableHeaderCell.js +2 -2
- package/dist/table/TableHeaderCell.js.map +1 -1
- package/dist/useStoredState.js +4 -2
- package/dist/useStoredState.js.map +1 -1
- package/package.json +7 -65
- package/src/Accordion.tsx +7 -1
- package/src/ActionSheet.tsx +26 -22
- package/src/AddressField.tsx +1 -1
- package/src/Badge.tsx +1 -1
- package/src/Banner.tsx +1 -1
- package/src/Box.test.tsx +71 -70
- package/src/Box.tsx +21 -9
- package/src/Button.tsx +0 -1
- package/src/CheckBox.tsx +7 -1
- package/src/Common.ts +15 -21
- package/src/DataTable.tsx +1 -2
- package/src/DateTimeField.tsx +22 -22
- package/src/EmailField.tsx +22 -42
- package/src/EmojiSelector.test.tsx +61 -0
- package/src/EmojiSelector.tsx +510 -0
- package/src/FernsProvider.tsx +1 -4
- package/src/Heading.tsx +3 -1
- package/src/Hyperlink.tsx +1 -1
- package/src/IconButton.tsx +2 -2
- package/src/Image.tsx +1 -0
- package/src/MarkdownView.tsx +67 -0
- package/src/MobileAddressAutoComplete.tsx +1 -1
- package/src/Modal.tsx +58 -21
- package/src/NumberField.tsx +10 -4
- package/src/NumberPickerActionSheet.tsx +1 -5
- package/src/Page.tsx +1 -1
- package/src/Pagination.tsx +2 -11
- package/src/Permissions.ts +2 -2
- package/src/PickerSelect.tsx +1 -1
- package/src/SectionDivider.tsx +1 -1
- package/src/SegmentedControl.tsx +3 -1
- package/src/Signature.native.tsx +2 -2
- package/src/SignatureField.tsx +2 -2
- package/src/Slider.tsx +10 -17
- package/src/SplitPage.native.tsx +2 -4
- package/src/SplitPage.tsx +4 -4
- package/src/TapToEdit.tsx +3 -7
- package/src/Text.tsx +1 -1
- package/src/TextArea.test.tsx +27 -43
- package/src/TextField.test.tsx +64 -5
- package/src/TextField.tsx +10 -1
- package/src/TextFieldNumberActionSheet.tsx +3 -7
- package/src/Tooltip.tsx +41 -19
- package/src/Unifier.ts +1 -3
- package/src/Utilities.tsx +3 -4
- package/src/WebAddressAutocomplete.tsx +1 -1
- package/src/__snapshots__/EmojiSelector.test.tsx.snap +604 -0
- package/src/index.tsx +12 -10
- package/src/table/Table.tsx +34 -36
- package/src/table/TableHeaderCell.tsx +2 -2
- package/src/useStoredState.ts +13 -11
package/src/TextArea.test.tsx
CHANGED
|
@@ -20,7 +20,7 @@ describe("TextArea", () => {
|
|
|
20
20
|
const {getByDisplayValue} = renderWithTheme(
|
|
21
21
|
<TextArea value="test content" onChange={mockOnChange} />
|
|
22
22
|
);
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
const input = getByDisplayValue("test content");
|
|
25
25
|
expect(input.props.multiline).toBe(true);
|
|
26
26
|
expect(input.props.value).toBe("test content");
|
|
@@ -30,7 +30,7 @@ describe("TextArea", () => {
|
|
|
30
30
|
const {getByText} = renderWithTheme(
|
|
31
31
|
<TextArea title="Description" value="" onChange={mockOnChange} />
|
|
32
32
|
);
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
expect(getByText("Description")).toBeTruthy();
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ describe("TextArea", () => {
|
|
|
38
38
|
const {getByPlaceholderText} = renderWithTheme(
|
|
39
39
|
<TextArea placeholder="Enter description" value="" onChange={mockOnChange} />
|
|
40
40
|
);
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
expect(getByPlaceholderText("Enter description")).toBeTruthy();
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@ describe("TextArea", () => {
|
|
|
46
46
|
const {getByText} = renderWithTheme(
|
|
47
47
|
<TextArea helperText="Maximum 500 characters" value="" onChange={mockOnChange} />
|
|
48
48
|
);
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
expect(getByText("Maximum 500 characters")).toBeTruthy();
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ describe("TextArea", () => {
|
|
|
54
54
|
const {getByText} = renderWithTheme(
|
|
55
55
|
<TextArea errorText="This field is required" value="" onChange={mockOnChange} />
|
|
56
56
|
);
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
expect(getByText("This field is required")).toBeTruthy();
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ describe("TextArea", () => {
|
|
|
62
62
|
const {getByDisplayValue} = renderWithTheme(
|
|
63
63
|
<TextArea grow value="" onChange={mockOnChange} />
|
|
64
64
|
);
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
const input = getByDisplayValue("");
|
|
67
67
|
expect(input.props.multiline).toBe(true);
|
|
68
68
|
});
|
|
@@ -71,16 +71,14 @@ describe("TextArea", () => {
|
|
|
71
71
|
const {getByDisplayValue} = renderWithTheme(
|
|
72
72
|
<TextArea disabled value="test" onChange={mockOnChange} />
|
|
73
73
|
);
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
const input = getByDisplayValue("test");
|
|
76
76
|
expect(input.props.readOnly).toBe(true);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it("should always have text type", () => {
|
|
80
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
81
|
-
|
|
82
|
-
);
|
|
83
|
-
|
|
80
|
+
const {getByDisplayValue} = renderWithTheme(<TextArea value="" onChange={mockOnChange} />);
|
|
81
|
+
|
|
84
82
|
const input = getByDisplayValue("");
|
|
85
83
|
expect(input.props.keyboardType).toBe("default");
|
|
86
84
|
});
|
|
@@ -89,9 +87,7 @@ describe("TextArea", () => {
|
|
|
89
87
|
describe("user interactions", () => {
|
|
90
88
|
it("should call onChange when text is entered", async () => {
|
|
91
89
|
const user = userEvent.setup();
|
|
92
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
93
|
-
<TextArea value="" onChange={mockOnChange} />
|
|
94
|
-
);
|
|
90
|
+
const {getByDisplayValue} = renderWithTheme(<TextArea value="" onChange={mockOnChange} />);
|
|
95
91
|
|
|
96
92
|
const input = getByDisplayValue("");
|
|
97
93
|
await user.type(input, "hello world");
|
|
@@ -103,9 +99,7 @@ describe("TextArea", () => {
|
|
|
103
99
|
it("should handle multiline text input", async () => {
|
|
104
100
|
const user = userEvent.setup();
|
|
105
101
|
const multilineText = "Line 1\nLine 2\nLine 3";
|
|
106
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
107
|
-
<TextArea value="" onChange={mockOnChange} />
|
|
108
|
-
);
|
|
102
|
+
const {getByDisplayValue} = renderWithTheme(<TextArea value="" onChange={mockOnChange} />);
|
|
109
103
|
|
|
110
104
|
const input = getByDisplayValue("");
|
|
111
105
|
await user.type(input, multilineText);
|
|
@@ -117,10 +111,8 @@ describe("TextArea", () => {
|
|
|
117
111
|
|
|
118
112
|
describe("accessibility", () => {
|
|
119
113
|
it("should have correct accessibility properties", () => {
|
|
120
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
121
|
-
|
|
122
|
-
);
|
|
123
|
-
|
|
114
|
+
const {getByDisplayValue} = renderWithTheme(<TextArea value="" onChange={mockOnChange} />);
|
|
115
|
+
|
|
124
116
|
const input = getByDisplayValue("");
|
|
125
117
|
expect(input.props.accessibilityHint).toBe("Enter text here");
|
|
126
118
|
expect(input.props["aria-label"]).toBe("Text input field");
|
|
@@ -130,7 +122,7 @@ describe("TextArea", () => {
|
|
|
130
122
|
const {getByDisplayValue} = renderWithTheme(
|
|
131
123
|
<TextArea disabled value="" onChange={mockOnChange} />
|
|
132
124
|
);
|
|
133
|
-
|
|
125
|
+
|
|
134
126
|
const input = getByDisplayValue("");
|
|
135
127
|
expect(input.props.accessibilityState.disabled).toBe(true);
|
|
136
128
|
});
|
|
@@ -138,19 +130,15 @@ describe("TextArea", () => {
|
|
|
138
130
|
|
|
139
131
|
describe("edge cases", () => {
|
|
140
132
|
it("should handle empty value", () => {
|
|
141
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
|
|
133
|
+
const {getByDisplayValue} = renderWithTheme(<TextArea value="" onChange={mockOnChange} />);
|
|
134
|
+
|
|
145
135
|
const input = getByDisplayValue("");
|
|
146
136
|
expect(input.props.value).toBe("");
|
|
147
137
|
});
|
|
148
138
|
|
|
149
139
|
it("should handle undefined value", () => {
|
|
150
|
-
const {root} = renderWithTheme(
|
|
151
|
-
|
|
152
|
-
);
|
|
153
|
-
|
|
140
|
+
const {root} = renderWithTheme(<TextArea value={undefined} onChange={mockOnChange} />);
|
|
141
|
+
|
|
154
142
|
expect(root).toBeTruthy();
|
|
155
143
|
});
|
|
156
144
|
|
|
@@ -159,7 +147,7 @@ describe("TextArea", () => {
|
|
|
159
147
|
const {getByDisplayValue} = renderWithTheme(
|
|
160
148
|
<TextArea value={longText} onChange={mockOnChange} />
|
|
161
149
|
);
|
|
162
|
-
|
|
150
|
+
|
|
163
151
|
const input = getByDisplayValue(longText);
|
|
164
152
|
expect(input.props.value).toBe(longText);
|
|
165
153
|
});
|
|
@@ -169,7 +157,7 @@ describe("TextArea", () => {
|
|
|
169
157
|
const {getByDisplayValue} = renderWithTheme(
|
|
170
158
|
<TextArea value={textWithBreaks} onChange={mockOnChange} />
|
|
171
159
|
);
|
|
172
|
-
|
|
160
|
+
|
|
173
161
|
const input = getByDisplayValue(textWithBreaks);
|
|
174
162
|
expect(input.props.value).toBe(textWithBreaks);
|
|
175
163
|
});
|
|
@@ -179,10 +167,10 @@ describe("TextArea", () => {
|
|
|
179
167
|
it("should inherit all TextField props except multiline and type", () => {
|
|
180
168
|
const mockOnFocus = jest.fn();
|
|
181
169
|
const mockOnBlur = jest.fn();
|
|
182
|
-
|
|
170
|
+
|
|
183
171
|
const {getByDisplayValue} = renderWithTheme(
|
|
184
|
-
<TextArea
|
|
185
|
-
value="test"
|
|
172
|
+
<TextArea
|
|
173
|
+
value="test"
|
|
186
174
|
onChange={mockOnChange}
|
|
187
175
|
onFocus={mockOnFocus}
|
|
188
176
|
onBlur={mockOnBlur}
|
|
@@ -191,7 +179,7 @@ describe("TextArea", () => {
|
|
|
191
179
|
rows={5}
|
|
192
180
|
/>
|
|
193
181
|
);
|
|
194
|
-
|
|
182
|
+
|
|
195
183
|
const input = getByDisplayValue("test");
|
|
196
184
|
expect(input.props.numberOfLines).toBe(5);
|
|
197
185
|
expect(input.props.onFocus).toBeTruthy();
|
|
@@ -200,19 +188,15 @@ describe("TextArea", () => {
|
|
|
200
188
|
|
|
201
189
|
it("should support inputRef", () => {
|
|
202
190
|
const mockInputRef = jest.fn();
|
|
203
|
-
renderWithTheme(
|
|
204
|
-
|
|
205
|
-
);
|
|
206
|
-
|
|
191
|
+
renderWithTheme(<TextArea inputRef={mockInputRef} value="" onChange={mockOnChange} />);
|
|
192
|
+
|
|
207
193
|
expect(mockInputRef).toHaveBeenCalled();
|
|
208
194
|
});
|
|
209
195
|
});
|
|
210
196
|
|
|
211
197
|
describe("snapshots", () => {
|
|
212
198
|
it("should match snapshot with default props", () => {
|
|
213
|
-
const component = renderWithTheme(
|
|
214
|
-
<TextArea value="test content" onChange={mockOnChange} />
|
|
215
|
-
);
|
|
199
|
+
const component = renderWithTheme(<TextArea value="test content" onChange={mockOnChange} />);
|
|
216
200
|
expect(component.toJSON()).toMatchSnapshot();
|
|
217
201
|
});
|
|
218
202
|
|
package/src/TextField.test.tsx
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {act, userEvent} from "@testing-library/react-native";
|
|
1
|
+
import {act, fireEvent, userEvent} from "@testing-library/react-native";
|
|
2
2
|
import React from "react";
|
|
3
|
-
|
|
4
|
-
import {renderWithTheme} from "./test-utils";
|
|
5
3
|
import {TextField} from "./TextField";
|
|
4
|
+
import {renderWithTheme} from "./test-utils";
|
|
6
5
|
|
|
7
6
|
describe("TextField", () => {
|
|
8
7
|
let mockOnChange: jest.Mock;
|
|
@@ -103,7 +102,7 @@ describe("TextField", () => {
|
|
|
103
102
|
});
|
|
104
103
|
|
|
105
104
|
it("should call onEnter when enter key is pressed", async () => {
|
|
106
|
-
const
|
|
105
|
+
const _user = userEvent.setup();
|
|
107
106
|
const {getByDisplayValue} = renderWithTheme(
|
|
108
107
|
<TextField value="" onChange={mockOnChange} onEnter={mockOnEnter} />
|
|
109
108
|
);
|
|
@@ -115,6 +114,66 @@ describe("TextField", () => {
|
|
|
115
114
|
|
|
116
115
|
expect(mockOnEnter).toHaveBeenCalledTimes(1);
|
|
117
116
|
});
|
|
117
|
+
|
|
118
|
+
it("should trim value on blur if trimOnBlur is true, even if onBlur prop is not provided", () => {
|
|
119
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
120
|
+
<TextField value="test " trimOnBlur onChange={mockOnChange} />
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const input = getByDisplayValue("test ");
|
|
124
|
+
|
|
125
|
+
fireEvent(input, "blur");
|
|
126
|
+
|
|
127
|
+
// on change should be called with trimmed value
|
|
128
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
129
|
+
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
130
|
+
expect(lastCall?.[0]).toBe("test");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should trim value on blur if trimOnBlur is true, with onBlur prop provided", async () => {
|
|
134
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
135
|
+
<TextField value="test " trimOnBlur={true} onChange={mockOnChange} onBlur={mockOnBlur} />
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const input = getByDisplayValue("test ");
|
|
139
|
+
|
|
140
|
+
fireEvent(input, "blur");
|
|
141
|
+
|
|
142
|
+
// onChange should be called with trimmed value
|
|
143
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
144
|
+
const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
|
|
145
|
+
expect(lastCall[0]).toBe("test");
|
|
146
|
+
|
|
147
|
+
// onBlur should also be called with trimmed value
|
|
148
|
+
expect(mockOnBlur).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(mockOnBlur.mock.calls[0][0]).toBe("test");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should NOT trim value on blur if trimOnBlur is false", async () => {
|
|
153
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
154
|
+
<TextField value="test " trimOnBlur={false} onChange={mockOnChange} />
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const input = getByDisplayValue("test ");
|
|
158
|
+
fireEvent(input, "blur");
|
|
159
|
+
|
|
160
|
+
// onChange should not be called because the value hasn't changed (no trimming)
|
|
161
|
+
expect(mockOnChange).not.toHaveBeenCalled();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("trims on blur by default when no prop is provided", async () => {
|
|
165
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
166
|
+
<TextField value="test " onChange={mockOnChange} />
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const input = getByDisplayValue("test ");
|
|
170
|
+
fireEvent(input, "blur");
|
|
171
|
+
|
|
172
|
+
// onChange should be called with trimmed value
|
|
173
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
174
|
+
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
175
|
+
expect(lastCall?.[0]).toBe("test");
|
|
176
|
+
});
|
|
118
177
|
});
|
|
119
178
|
|
|
120
179
|
describe("field types", () => {
|
|
@@ -206,7 +265,7 @@ describe("TextField", () => {
|
|
|
206
265
|
});
|
|
207
266
|
|
|
208
267
|
it("should not call onFocus when disabled", async () => {
|
|
209
|
-
const
|
|
268
|
+
const _user = userEvent.setup();
|
|
210
269
|
const {getByDisplayValue} = renderWithTheme(
|
|
211
270
|
<TextField disabled value="" onChange={mockOnChange} onFocus={mockOnFocus} />
|
|
212
271
|
);
|
package/src/TextField.tsx
CHANGED
|
@@ -65,6 +65,7 @@ export const TextField: FC<TextFieldProps> = ({
|
|
|
65
65
|
blurOnSubmit = true,
|
|
66
66
|
iconName,
|
|
67
67
|
onIconClick,
|
|
68
|
+
trimOnBlur = true,
|
|
68
69
|
type = "text",
|
|
69
70
|
autoComplete,
|
|
70
71
|
inputRef,
|
|
@@ -183,8 +184,16 @@ export const TextField: FC<TextFieldProps> = ({
|
|
|
183
184
|
value={value}
|
|
184
185
|
onBlur={() => {
|
|
185
186
|
if (disabled) return;
|
|
187
|
+
let finalValue = value ?? "";
|
|
188
|
+
|
|
189
|
+
if (trimOnBlur && value) {
|
|
190
|
+
finalValue = finalValue.trim();
|
|
191
|
+
if (finalValue !== value) {
|
|
192
|
+
onChange(finalValue);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
186
195
|
if (onBlur) {
|
|
187
|
-
onBlur(
|
|
196
|
+
onBlur(finalValue);
|
|
188
197
|
}
|
|
189
198
|
setFocused(false);
|
|
190
199
|
}}
|
|
@@ -4,18 +4,14 @@ import React from "react";
|
|
|
4
4
|
import {ActionSheet} from "./ActionSheet";
|
|
5
5
|
import {Box} from "./Box";
|
|
6
6
|
import {Button} from "./Button";
|
|
7
|
-
import {
|
|
7
|
+
import {TextFieldPickerActionSheetProps} from "./Common";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type NumberPickerActionSheetState = {};
|
|
10
10
|
|
|
11
11
|
export class NumberPickerActionSheet extends React.Component<
|
|
12
12
|
TextFieldPickerActionSheetProps,
|
|
13
13
|
NumberPickerActionSheetState
|
|
14
14
|
> {
|
|
15
|
-
constructor(props: NumberPickerActionSheetProps) {
|
|
16
|
-
super(props);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
render() {
|
|
20
16
|
return (
|
|
21
17
|
<ActionSheet ref={this.props.actionSheetRef} bounceOnOpen gestureEnabled>
|
|
@@ -36,7 +32,7 @@ export class NumberPickerActionSheet extends React.Component<
|
|
|
36
32
|
mode={this.props.mode}
|
|
37
33
|
testID="dateTimePicker"
|
|
38
34
|
value={this.props.value ? new Date(this.props.value) : new Date()}
|
|
39
|
-
onChange={(
|
|
35
|
+
onChange={(_event: any, date?: Date) => {
|
|
40
36
|
if (!date) {
|
|
41
37
|
return;
|
|
42
38
|
}
|
package/src/Tooltip.tsx
CHANGED
|
@@ -35,7 +35,7 @@ interface Measurement {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
interface ChildrenProps {
|
|
38
|
-
onClick?: () => void
|
|
38
|
+
onClick?: () => void;
|
|
39
39
|
onHoverIn?: () => void;
|
|
40
40
|
onHoverOut?: () => void;
|
|
41
41
|
}
|
|
@@ -188,20 +188,33 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
188
188
|
const childrenWrapperRef = useRef<View>(null);
|
|
189
189
|
const touched = useRef(false);
|
|
190
190
|
const isWeb = Platform.OS === "web";
|
|
191
|
+
const resetMeasurement = useCallback(() => {
|
|
192
|
+
setMeasurement({
|
|
193
|
+
children: {},
|
|
194
|
+
tooltip: {},
|
|
195
|
+
measured: false,
|
|
196
|
+
});
|
|
197
|
+
}, []);
|
|
198
|
+
const hideTooltip = useCallback(() => {
|
|
199
|
+
if (showTooltipTimer.current) {
|
|
200
|
+
clearTimeout(showTooltipTimer.current);
|
|
201
|
+
}
|
|
202
|
+
if (hideTooltipTimer.current) {
|
|
203
|
+
clearTimeout(hideTooltipTimer.current);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
touched.current = false;
|
|
207
|
+
setVisible(false);
|
|
208
|
+
resetMeasurement();
|
|
209
|
+
}, [resetMeasurement, setVisible]);
|
|
191
210
|
|
|
192
211
|
// If the tooltip is visible, and the user clicks outside of the tooltip, hide it.
|
|
193
212
|
useEffect(() => {
|
|
194
213
|
return () => {
|
|
195
|
-
if (showTooltipTimer.current) {
|
|
196
|
-
clearTimeout(showTooltipTimer.current);
|
|
197
|
-
}
|
|
198
|
-
if (hideTooltipTimer.current) {
|
|
199
|
-
clearTimeout(hideTooltipTimer.current);
|
|
200
|
-
}
|
|
201
214
|
// Hide tooltip on unmount to prevent it from staying stuck on screen
|
|
202
|
-
|
|
215
|
+
hideTooltip();
|
|
203
216
|
};
|
|
204
|
-
}, []);
|
|
217
|
+
}, [hideTooltip]);
|
|
205
218
|
|
|
206
219
|
const getArrowContainerStyle = (): ViewStyle => {
|
|
207
220
|
if (!includeArrow) {
|
|
@@ -264,6 +277,11 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
264
277
|
);
|
|
265
278
|
|
|
266
279
|
const handleTouchStart = useCallback(() => {
|
|
280
|
+
if (visible) {
|
|
281
|
+
hideTooltip();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
267
285
|
if (hideTooltipTimer.current) {
|
|
268
286
|
clearTimeout(hideTooltipTimer.current);
|
|
269
287
|
}
|
|
@@ -272,7 +290,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
272
290
|
touched.current = true;
|
|
273
291
|
setVisible(true);
|
|
274
292
|
}, 100);
|
|
275
|
-
}, []);
|
|
293
|
+
}, [hideTooltip, visible]);
|
|
276
294
|
|
|
277
295
|
const handleHoverIn = useCallback(() => {
|
|
278
296
|
if (hideTooltipTimer.current) {
|
|
@@ -286,20 +304,23 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
286
304
|
}, [hoverDelay]);
|
|
287
305
|
|
|
288
306
|
const handleHoverOut = useCallback(() => {
|
|
289
|
-
touched.current = false;
|
|
290
307
|
if (showTooltipTimer.current) {
|
|
291
308
|
clearTimeout(showTooltipTimer.current);
|
|
292
309
|
}
|
|
310
|
+
if (hideTooltipTimer.current) {
|
|
311
|
+
clearTimeout(hideTooltipTimer.current);
|
|
312
|
+
}
|
|
293
313
|
|
|
294
314
|
hideTooltipTimer.current = setTimeout(() => {
|
|
295
|
-
|
|
296
|
-
setMeasurement({
|
|
297
|
-
children: {},
|
|
298
|
-
tooltip: {},
|
|
299
|
-
measured: false,
|
|
300
|
-
});
|
|
315
|
+
hideTooltip();
|
|
301
316
|
}, hoverEndDelay);
|
|
302
|
-
}, [hoverEndDelay]);
|
|
317
|
+
}, [hideTooltip, hoverEndDelay]);
|
|
318
|
+
|
|
319
|
+
const handleClick = useCallback(() => {
|
|
320
|
+
if (visible) {
|
|
321
|
+
hideTooltip();
|
|
322
|
+
}
|
|
323
|
+
}, [hideTooltip, visible]);
|
|
303
324
|
|
|
304
325
|
const mobilePressProps = {
|
|
305
326
|
onPress: useCallback(() => {
|
|
@@ -354,7 +375,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
354
375
|
borderRadius: theme.radius.default,
|
|
355
376
|
}}
|
|
356
377
|
testID="tooltip-container"
|
|
357
|
-
onPress={
|
|
378
|
+
onPress={hideTooltip}
|
|
358
379
|
>
|
|
359
380
|
<Text color="inverted" size="sm">
|
|
360
381
|
{text}
|
|
@@ -375,6 +396,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
375
396
|
handleHoverOut();
|
|
376
397
|
(children.props as ChildrenProps).onHoverOut?.();
|
|
377
398
|
}}
|
|
399
|
+
onPress={isWeb ? handleClick : undefined}
|
|
378
400
|
onTouchStart={handleTouchStart}
|
|
379
401
|
{...(!isWeb && mobilePressProps)}
|
|
380
402
|
>
|
package/src/Unifier.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as Clipboard from "expo-clipboard";
|
|
|
6
6
|
import * as Haptics from "expo-haptics";
|
|
7
7
|
import {Dimensions, Keyboard, Linking, Platform, Vibration} from "react-native";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {PermissionKind} from "./Common";
|
|
10
10
|
import {requestPermissions} from "./Permissions";
|
|
11
11
|
|
|
12
12
|
declare global {
|
|
@@ -61,8 +61,6 @@ export function changeColorLuminance(hex: string, luminanceChange: Luminance) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
class UnifierClass {
|
|
64
|
-
private _theme?: Partial<FernsTheme>;
|
|
65
|
-
|
|
66
64
|
private _web = false;
|
|
67
65
|
|
|
68
66
|
private _dev = false;
|
package/src/Utilities.tsx
CHANGED
|
@@ -18,8 +18,7 @@ export function mergeInlineStyles(inlineStyle?: any, newStyle?: any) {
|
|
|
18
18
|
|
|
19
19
|
export function isTestUser(profile?: BaseProfile) {
|
|
20
20
|
return (
|
|
21
|
-
profile &&
|
|
22
|
-
profile.email &&
|
|
21
|
+
profile?.email &&
|
|
23
22
|
(profile.email.indexOf("nang.io") > -1 || profile.email.indexOf("example.com") > -1)
|
|
24
23
|
);
|
|
25
24
|
}
|
|
@@ -57,7 +56,7 @@ order in which you do so doesn't really matter.
|
|
|
57
56
|
*/
|
|
58
57
|
|
|
59
58
|
interface InlineStyle {
|
|
60
|
-
[key: string]: string | number |
|
|
59
|
+
[key: string]: string | number | undefined;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
// TODO: This type should be opaque, however the Babel parser doesn't support
|
|
@@ -318,7 +317,7 @@ export function formattedCountyCode(state: string, countyName: string): string {
|
|
|
318
317
|
}
|
|
319
318
|
|
|
320
319
|
export function isAPIError(error: any): error is APIError {
|
|
321
|
-
return error
|
|
320
|
+
return error?.data?.title;
|
|
322
321
|
}
|
|
323
322
|
|
|
324
323
|
export function printAPIError(error: APIError, details = true): string {
|
|
@@ -7,7 +7,7 @@ import {processAddressComponents} from "./Utilities";
|
|
|
7
7
|
|
|
8
8
|
const loadGooglePlacesScript = (googleMapsApiKey: string, callbackName: any): Promise<void> => {
|
|
9
9
|
return new Promise<void>((resolve, reject): undefined => {
|
|
10
|
-
if (window.google
|
|
10
|
+
if (window.google?.maps?.places) {
|
|
11
11
|
resolve();
|
|
12
12
|
return;
|
|
13
13
|
}
|