@hubspot/ui-extensions 0.9.1 → 0.9.3
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/__tests__/experimental/crm/fetchCrmProperties.spec.js +179 -14
- package/dist/__tests__/experimental/hooks/useCrmProperties.spec.js +151 -4
- package/dist/experimental/crm/fetchCrmProperties.d.ts +22 -2
- package/dist/experimental/crm/fetchCrmProperties.js +8 -7
- package/dist/experimental/hooks/useCrmProperties.d.ts +3 -2
- package/dist/experimental/hooks/useCrmProperties.js +63 -21
- package/dist/experimental/index.d.ts +15 -1
- package/dist/experimental/index.js +8 -4
- package/dist/experimental/types.d.ts +191 -6
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
|
@@ -8,15 +8,19 @@ Object.defineProperty(global, 'self', {
|
|
|
8
8
|
value: mockSelf,
|
|
9
9
|
writable: true,
|
|
10
10
|
});
|
|
11
|
-
import { fetchCrmProperties
|
|
11
|
+
import { fetchCrmProperties } from '../../../experimental/crm/fetchCrmProperties';
|
|
12
|
+
const DEFAULT_OPTIONS = {};
|
|
12
13
|
describe('fetchCrmProperties', () => {
|
|
13
14
|
beforeEach(() => {
|
|
14
15
|
jest.clearAllMocks();
|
|
15
16
|
});
|
|
16
17
|
it('successfully fetches CRM properties', async () => {
|
|
17
18
|
const mockApiResponse = {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
data: {
|
|
20
|
+
firstname: 'Test value for firstname',
|
|
21
|
+
lastname: 'Test value for lastname',
|
|
22
|
+
},
|
|
23
|
+
cleanup: jest.fn(),
|
|
20
24
|
};
|
|
21
25
|
const mockResponse = {
|
|
22
26
|
ok: true,
|
|
@@ -24,11 +28,42 @@ describe('fetchCrmProperties', () => {
|
|
|
24
28
|
};
|
|
25
29
|
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
26
30
|
const propertyNames = ['firstname', 'lastname'];
|
|
27
|
-
const result = await fetchCrmProperties(propertyNames, jest.fn());
|
|
28
|
-
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function));
|
|
31
|
+
const result = await fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS);
|
|
32
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), DEFAULT_OPTIONS);
|
|
29
33
|
expect(result).toEqual({
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
data: {
|
|
35
|
+
firstname: 'Test value for firstname',
|
|
36
|
+
lastname: 'Test value for lastname',
|
|
37
|
+
},
|
|
38
|
+
cleanup: expect.any(Function),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('successfully fetches CRM properties with null values', async () => {
|
|
42
|
+
const mockApiResponse = {
|
|
43
|
+
data: {
|
|
44
|
+
firstname: 'John',
|
|
45
|
+
lastname: null,
|
|
46
|
+
email: 'john@example.com',
|
|
47
|
+
phone: null,
|
|
48
|
+
},
|
|
49
|
+
cleanup: jest.fn(),
|
|
50
|
+
};
|
|
51
|
+
const mockResponse = {
|
|
52
|
+
ok: true,
|
|
53
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
54
|
+
};
|
|
55
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
56
|
+
const propertyNames = ['firstname', 'lastname', 'email', 'phone'];
|
|
57
|
+
const result = await fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS);
|
|
58
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), DEFAULT_OPTIONS);
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
data: {
|
|
61
|
+
firstname: 'John',
|
|
62
|
+
lastname: null,
|
|
63
|
+
email: 'john@example.com',
|
|
64
|
+
phone: null,
|
|
65
|
+
},
|
|
66
|
+
cleanup: expect.any(Function),
|
|
32
67
|
});
|
|
33
68
|
});
|
|
34
69
|
it('throws an error when response is not OK', async () => {
|
|
@@ -38,28 +73,51 @@ describe('fetchCrmProperties', () => {
|
|
|
38
73
|
};
|
|
39
74
|
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
40
75
|
const propertyNames = ['firstname'];
|
|
41
|
-
await expect(fetchCrmProperties(propertyNames, jest.fn())).rejects.toThrow('Failed to fetch CRM properties: Not Found');
|
|
76
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Failed to fetch CRM properties: Not Found');
|
|
42
77
|
});
|
|
43
78
|
it('throws an error when fetch fails', async () => {
|
|
44
79
|
mockFetchCrmProperties.mockRejectedValue(new Error('Network error'));
|
|
45
80
|
const propertyNames = ['firstname'];
|
|
46
|
-
await expect(fetchCrmProperties(propertyNames, jest.fn())).rejects.toThrow('Network error');
|
|
81
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Network error');
|
|
47
82
|
});
|
|
48
83
|
it('throws an error if the response is not an object', async () => {
|
|
49
|
-
const mockApiResponse =
|
|
84
|
+
const mockApiResponse = {
|
|
85
|
+
data: 'Invalid response',
|
|
86
|
+
cleanup: jest.fn(),
|
|
87
|
+
};
|
|
50
88
|
const mockResponse = {
|
|
51
89
|
ok: true,
|
|
52
90
|
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
53
91
|
};
|
|
54
92
|
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
55
93
|
const propertyNames = ['firstname'];
|
|
56
|
-
await expect(fetchCrmProperties(propertyNames, jest.fn())).rejects.toThrow('Invalid response format');
|
|
94
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Invalid response format');
|
|
95
|
+
});
|
|
96
|
+
it('throws an error if response contains invalid property values', async () => {
|
|
97
|
+
const mockApiResponse = {
|
|
98
|
+
data: {
|
|
99
|
+
firstname: 'John',
|
|
100
|
+
lastname: 123,
|
|
101
|
+
email: 'john@example.com',
|
|
102
|
+
},
|
|
103
|
+
cleanup: jest.fn(),
|
|
104
|
+
};
|
|
105
|
+
const mockResponse = {
|
|
106
|
+
ok: true,
|
|
107
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
108
|
+
};
|
|
109
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
110
|
+
const propertyNames = ['firstname', 'lastname', 'email'];
|
|
111
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Invalid response format');
|
|
57
112
|
});
|
|
58
113
|
it('passes the propertiesUpdatedCallback and allows it to be called', async () => {
|
|
59
114
|
let capturedCallback;
|
|
60
115
|
const mockApiResponse = {
|
|
61
|
-
|
|
62
|
-
|
|
116
|
+
data: {
|
|
117
|
+
firstname: 'Initial',
|
|
118
|
+
lastname: 'Initial',
|
|
119
|
+
},
|
|
120
|
+
cleanup: jest.fn(),
|
|
63
121
|
};
|
|
64
122
|
const mockResponse = {
|
|
65
123
|
ok: true,
|
|
@@ -71,7 +129,7 @@ describe('fetchCrmProperties', () => {
|
|
|
71
129
|
});
|
|
72
130
|
const propertyNames = ['firstname', 'lastname'];
|
|
73
131
|
const mockCallback = jest.fn();
|
|
74
|
-
await fetchCrmProperties(propertyNames, mockCallback);
|
|
132
|
+
await fetchCrmProperties(propertyNames, mockCallback, DEFAULT_OPTIONS);
|
|
75
133
|
expect(typeof capturedCallback).toBe('function');
|
|
76
134
|
// Simulate the callback being called with new properties
|
|
77
135
|
const updatedProps = { firstname: 'Updated', lastname: 'Updated' };
|
|
@@ -80,4 +138,111 @@ describe('fetchCrmProperties', () => {
|
|
|
80
138
|
}
|
|
81
139
|
expect(mockCallback).toHaveBeenCalledWith(updatedProps);
|
|
82
140
|
});
|
|
141
|
+
it('passes the propertiesUpdatedCallback and allows it to be called with null values', async () => {
|
|
142
|
+
let capturedCallback;
|
|
143
|
+
const mockApiResponse = {
|
|
144
|
+
data: {
|
|
145
|
+
firstname: 'Initial',
|
|
146
|
+
lastname: null,
|
|
147
|
+
},
|
|
148
|
+
cleanup: jest.fn(),
|
|
149
|
+
};
|
|
150
|
+
const mockResponse = {
|
|
151
|
+
ok: true,
|
|
152
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
153
|
+
};
|
|
154
|
+
mockFetchCrmProperties.mockImplementation((propertyNames, callback) => {
|
|
155
|
+
capturedCallback = callback;
|
|
156
|
+
return Promise.resolve(mockResponse);
|
|
157
|
+
});
|
|
158
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
159
|
+
const mockCallback = jest.fn();
|
|
160
|
+
await fetchCrmProperties(propertyNames, mockCallback, DEFAULT_OPTIONS);
|
|
161
|
+
expect(typeof capturedCallback).toBe('function');
|
|
162
|
+
// Simulate the callback being called with new properties including null values
|
|
163
|
+
const updatedProps = { firstname: null, lastname: 'Updated Value' };
|
|
164
|
+
if (capturedCallback) {
|
|
165
|
+
capturedCallback(updatedProps);
|
|
166
|
+
}
|
|
167
|
+
expect(mockCallback).toHaveBeenCalledWith(updatedProps);
|
|
168
|
+
});
|
|
169
|
+
it('passes formatting options to the underlying fetch function', async () => {
|
|
170
|
+
const mockApiResponse = {
|
|
171
|
+
data: {
|
|
172
|
+
firstname: 'John',
|
|
173
|
+
lastname: 'Doe',
|
|
174
|
+
},
|
|
175
|
+
cleanup: jest.fn(),
|
|
176
|
+
};
|
|
177
|
+
const mockResponse = {
|
|
178
|
+
ok: true,
|
|
179
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
180
|
+
};
|
|
181
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
182
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
183
|
+
const options = {
|
|
184
|
+
propertiesToFormat: ['firstname'],
|
|
185
|
+
formattingOptions: {
|
|
186
|
+
date: {
|
|
187
|
+
format: 'MM/dd/yyyy',
|
|
188
|
+
},
|
|
189
|
+
currency: {
|
|
190
|
+
addSymbol: true,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
await fetchCrmProperties(propertyNames, jest.fn(), options);
|
|
195
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), options);
|
|
196
|
+
});
|
|
197
|
+
it('preserves error handling with formatting options', async () => {
|
|
198
|
+
mockFetchCrmProperties.mockRejectedValue(new Error('Network error'));
|
|
199
|
+
const propertyNames = ['firstname'];
|
|
200
|
+
const options = {
|
|
201
|
+
propertiesToFormat: ['firstname'],
|
|
202
|
+
formattingOptions: {
|
|
203
|
+
date: {
|
|
204
|
+
format: 'MM/dd/yyyy',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), options)).rejects.toThrow('Network error');
|
|
209
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), options);
|
|
210
|
+
});
|
|
211
|
+
it('preserves response validation with formatting options', async () => {
|
|
212
|
+
const mockApiResponse = {
|
|
213
|
+
data: 'Invalid response',
|
|
214
|
+
cleanup: jest.fn(),
|
|
215
|
+
};
|
|
216
|
+
const mockResponse = {
|
|
217
|
+
ok: true,
|
|
218
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
219
|
+
};
|
|
220
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
221
|
+
const propertyNames = ['firstname'];
|
|
222
|
+
const options = {
|
|
223
|
+
propertiesToFormat: 'all',
|
|
224
|
+
};
|
|
225
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), options)).rejects.toThrow('Invalid response format');
|
|
226
|
+
});
|
|
227
|
+
it('returns cleanup function that can be called', async () => {
|
|
228
|
+
const mockCleanup = jest.fn();
|
|
229
|
+
const mockApiResponse = {
|
|
230
|
+
data: {
|
|
231
|
+
firstname: 'John',
|
|
232
|
+
},
|
|
233
|
+
cleanup: mockCleanup,
|
|
234
|
+
};
|
|
235
|
+
const mockResponse = {
|
|
236
|
+
ok: true,
|
|
237
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
238
|
+
};
|
|
239
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
240
|
+
const propertyNames = ['firstname'];
|
|
241
|
+
const result = await fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS);
|
|
242
|
+
expect(result.cleanup).toBe(mockCleanup);
|
|
243
|
+
expect(typeof result.cleanup).toBe('function');
|
|
244
|
+
// Verify cleanup can be called without errors
|
|
245
|
+
result.cleanup();
|
|
246
|
+
expect(mockCleanup).toHaveBeenCalledTimes(1);
|
|
247
|
+
});
|
|
83
248
|
});
|
|
@@ -42,8 +42,11 @@ describe('useCrmProperties', () => {
|
|
|
42
42
|
});
|
|
43
43
|
it('should successfully fetch and return CRM properties', async () => {
|
|
44
44
|
mockFetchCrmProperties.mockResolvedValue({
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
data: {
|
|
46
|
+
firstname: 'Test value for firstname',
|
|
47
|
+
lastname: 'Test value for lastname',
|
|
48
|
+
},
|
|
49
|
+
cleanup: jest.fn(),
|
|
47
50
|
});
|
|
48
51
|
const propertyNames = ['firstname', 'lastname'];
|
|
49
52
|
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
@@ -73,7 +76,10 @@ describe('useCrmProperties', () => {
|
|
|
73
76
|
let capturedCallback;
|
|
74
77
|
mockFetchCrmProperties.mockImplementation((_propertyNames, propertiesUpdatedCallback) => {
|
|
75
78
|
capturedCallback = propertiesUpdatedCallback;
|
|
76
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
data: { firstname: 'Initial', lastname: 'Initial' },
|
|
81
|
+
cleanup: jest.fn(),
|
|
82
|
+
};
|
|
77
83
|
});
|
|
78
84
|
const propertyNames = ['firstname', 'lastname'];
|
|
79
85
|
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
@@ -87,9 +93,150 @@ describe('useCrmProperties', () => {
|
|
|
87
93
|
const updatedProperties = { firstname: 'Updated', lastname: 'Updated' };
|
|
88
94
|
await waitFor(() => {
|
|
89
95
|
if (capturedCallback) {
|
|
90
|
-
capturedCallback(updatedProperties);
|
|
96
|
+
capturedCallback?.(updatedProperties);
|
|
97
|
+
expect(result.current.properties).toEqual(updatedProperties);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it('should update properties when propertiesUpdatedCallback is called with null values', async () => {
|
|
102
|
+
// Capture the callback so we can simulate an external update to CRM properties
|
|
103
|
+
let capturedCallback;
|
|
104
|
+
mockFetchCrmProperties.mockImplementation((_propertyNames, propertiesUpdatedCallback) => {
|
|
105
|
+
capturedCallback = propertiesUpdatedCallback;
|
|
106
|
+
return {
|
|
107
|
+
data: { firstname: 'Initial', lastname: null },
|
|
108
|
+
cleanup: jest.fn(),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
112
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
expect(result.current.properties).toEqual({
|
|
115
|
+
firstname: 'Initial',
|
|
116
|
+
lastname: null,
|
|
117
|
+
});
|
|
118
|
+
expect(result.current.isLoading).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
const updatedProperties = { firstname: null, lastname: 'Updated Value' };
|
|
121
|
+
await waitFor(() => {
|
|
122
|
+
if (capturedCallback) {
|
|
123
|
+
capturedCallback?.(updatedProperties);
|
|
91
124
|
expect(result.current.properties).toEqual(updatedProperties);
|
|
92
125
|
}
|
|
93
126
|
});
|
|
94
127
|
});
|
|
128
|
+
// This will become "should pass formatting options to fetchCrmProperties" in the next PR
|
|
129
|
+
it('should pass formatting options to fetchCrmProperties', async () => {
|
|
130
|
+
mockFetchCrmProperties.mockResolvedValue({
|
|
131
|
+
data: {
|
|
132
|
+
firstname: 'John',
|
|
133
|
+
lastname: 'Doe',
|
|
134
|
+
},
|
|
135
|
+
cleanup: jest.fn(),
|
|
136
|
+
});
|
|
137
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
138
|
+
const options = {
|
|
139
|
+
propertiesToFormat: ['firstname'],
|
|
140
|
+
};
|
|
141
|
+
renderHook(() => useCrmProperties(propertyNames, options));
|
|
142
|
+
await waitFor(() => {
|
|
143
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), options);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
it('should use default empty options when no options provided', async () => {
|
|
147
|
+
mockFetchCrmProperties.mockResolvedValue({
|
|
148
|
+
data: {
|
|
149
|
+
firstname: 'John',
|
|
150
|
+
},
|
|
151
|
+
cleanup: jest.fn(),
|
|
152
|
+
});
|
|
153
|
+
const propertyNames = ['firstname'];
|
|
154
|
+
const defaultOptions = {};
|
|
155
|
+
renderHook(() => useCrmProperties(propertyNames));
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(['firstname'], expect.any(Function), defaultOptions);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
it('should not re-fetch when options object reference changes but content is the same', async () => {
|
|
161
|
+
mockFetchCrmProperties.mockResolvedValue({
|
|
162
|
+
data: {
|
|
163
|
+
firstname: 'John',
|
|
164
|
+
},
|
|
165
|
+
cleanup: jest.fn(),
|
|
166
|
+
});
|
|
167
|
+
const propertyNames = ['firstname'];
|
|
168
|
+
const initialOptions = { propertiesToFormat: ['firstname'] };
|
|
169
|
+
const { rerender } = renderHook(({ options }) => useCrmProperties(propertyNames, options), { initialProps: { options: initialOptions } });
|
|
170
|
+
await waitFor(() => {
|
|
171
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(1);
|
|
172
|
+
});
|
|
173
|
+
const newOptionsWithSameContent = { propertiesToFormat: ['firstname'] };
|
|
174
|
+
rerender({ options: newOptionsWithSameContent });
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(1);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it('should re-fetch when options content actually changes', async () => {
|
|
180
|
+
mockFetchCrmProperties.mockResolvedValue({
|
|
181
|
+
data: {
|
|
182
|
+
firstname: 'John',
|
|
183
|
+
},
|
|
184
|
+
cleanup: jest.fn(),
|
|
185
|
+
});
|
|
186
|
+
const propertyNames = ['firstname'];
|
|
187
|
+
const initialOptions = { propertiesToFormat: ['firstname'] };
|
|
188
|
+
const { rerender } = renderHook(({ options }) => useCrmProperties(propertyNames, options), { initialProps: { options: initialOptions } });
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(mockFetchCrmProperties).toHaveBeenLastCalledWith(['firstname'], expect.any(Function), initialOptions);
|
|
192
|
+
});
|
|
193
|
+
const newOptions = {
|
|
194
|
+
propertiesToFormat: ['firstname'],
|
|
195
|
+
formattingOptions: { dateFormat: 'yyyy-MM-dd' },
|
|
196
|
+
};
|
|
197
|
+
rerender({ options: newOptions });
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(2);
|
|
200
|
+
expect(mockFetchCrmProperties).toHaveBeenLastCalledWith(['firstname'], expect.any(Function), newOptions);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
it('should call cleanup function when component unmounts', async () => {
|
|
204
|
+
const mockCleanup = jest.fn();
|
|
205
|
+
mockFetchCrmProperties.mockResolvedValue({
|
|
206
|
+
data: {
|
|
207
|
+
firstname: 'John',
|
|
208
|
+
},
|
|
209
|
+
cleanup: mockCleanup,
|
|
210
|
+
});
|
|
211
|
+
const propertyNames = ['firstname'];
|
|
212
|
+
const { unmount } = renderHook(() => useCrmProperties(propertyNames));
|
|
213
|
+
await waitFor(() => {
|
|
214
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(1);
|
|
215
|
+
});
|
|
216
|
+
unmount();
|
|
217
|
+
expect(mockCleanup).toHaveBeenCalledTimes(1);
|
|
218
|
+
});
|
|
219
|
+
it('should call cleanup function when dependencies change', async () => {
|
|
220
|
+
const mockCleanup1 = jest.fn();
|
|
221
|
+
const mockCleanup2 = jest.fn();
|
|
222
|
+
mockFetchCrmProperties
|
|
223
|
+
.mockResolvedValueOnce({
|
|
224
|
+
data: { firstname: 'John' },
|
|
225
|
+
cleanup: mockCleanup1,
|
|
226
|
+
})
|
|
227
|
+
.mockResolvedValueOnce({
|
|
228
|
+
data: { firstname: 'Jane' },
|
|
229
|
+
cleanup: mockCleanup2,
|
|
230
|
+
});
|
|
231
|
+
const { rerender } = renderHook(({ propertyNames }) => useCrmProperties(propertyNames), { initialProps: { propertyNames: ['firstname'] } });
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(1);
|
|
234
|
+
});
|
|
235
|
+
// Change dependencies to trigger cleanup
|
|
236
|
+
rerender({ propertyNames: ['lastname'] });
|
|
237
|
+
await waitFor(() => {
|
|
238
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(2);
|
|
239
|
+
expect(mockCleanup1).toHaveBeenCalledTimes(1);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
95
242
|
});
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
export type CrmPropertiesResponse = {
|
|
2
|
-
[key: string]: string;
|
|
2
|
+
[key: string]: string | null;
|
|
3
3
|
};
|
|
4
|
-
export
|
|
4
|
+
export type FetchCrmPropertiesOptions = {
|
|
5
|
+
propertiesToFormat?: string[] | 'all';
|
|
6
|
+
formattingOptions?: {
|
|
7
|
+
date?: {
|
|
8
|
+
format?: string;
|
|
9
|
+
relative?: boolean;
|
|
10
|
+
};
|
|
11
|
+
dateTime?: {
|
|
12
|
+
format?: string;
|
|
13
|
+
relative?: boolean;
|
|
14
|
+
};
|
|
15
|
+
currency?: {
|
|
16
|
+
addSymbol?: boolean;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export interface FetchCrmPropertiesResult {
|
|
21
|
+
data: Record<string, string | null>;
|
|
22
|
+
cleanup: () => void;
|
|
23
|
+
}
|
|
24
|
+
export declare const fetchCrmProperties: (propertyNames: string[], propertiesUpdatedCallback: (properties: Record<string, string | null>) => void, options?: FetchCrmPropertiesOptions) => Promise<FetchCrmPropertiesResult>;
|
|
@@ -4,24 +4,25 @@ function isCrmPropertiesResponse(data) {
|
|
|
4
4
|
// Confirm the data is a defined object
|
|
5
5
|
data === null ||
|
|
6
6
|
typeof data !== 'object' ||
|
|
7
|
-
// Confirm all keys and values are strings
|
|
8
|
-
!Object.keys(data).every((key) => typeof key === 'string' &&
|
|
7
|
+
// Confirm all keys and values are strings, or null
|
|
8
|
+
!Object.keys(data).every((key) => typeof key === 'string' &&
|
|
9
|
+
(typeof data[key] === 'string' || data[key] === null))) {
|
|
9
10
|
return false;
|
|
10
11
|
}
|
|
11
12
|
return true;
|
|
12
13
|
}
|
|
13
|
-
export const fetchCrmProperties = async (propertyNames, propertiesUpdatedCallback) => {
|
|
14
|
+
export const fetchCrmProperties = async (propertyNames, propertiesUpdatedCallback, options) => {
|
|
14
15
|
try {
|
|
15
16
|
// eslint-disable-next-line hubspot-dev/no-confusing-browser-globals
|
|
16
|
-
const response = await self.fetchCrmProperties(propertyNames, propertiesUpdatedCallback);
|
|
17
|
+
const response = await self.fetchCrmProperties(propertyNames, propertiesUpdatedCallback, options);
|
|
17
18
|
if (!response.ok) {
|
|
18
19
|
throw new Error(`Failed to fetch CRM properties: ${response.statusText}`);
|
|
19
20
|
}
|
|
20
|
-
const
|
|
21
|
-
if (!isCrmPropertiesResponse(data)) {
|
|
21
|
+
const result = await response.json();
|
|
22
|
+
if (!isCrmPropertiesResponse(result.data)) {
|
|
22
23
|
throw new Error('Invalid response format');
|
|
23
24
|
}
|
|
24
|
-
return
|
|
25
|
+
return result;
|
|
25
26
|
}
|
|
26
27
|
catch (error) {
|
|
27
28
|
if (error instanceof Error) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type FetchCrmPropertiesOptions } from '../crm/fetchCrmProperties';
|
|
1
2
|
export interface CrmPropertiesState {
|
|
2
|
-
properties: Record<string, string>;
|
|
3
|
+
properties: Record<string, string | null>;
|
|
3
4
|
error: Error | null;
|
|
4
5
|
isLoading: boolean;
|
|
5
6
|
}
|
|
@@ -8,4 +9,4 @@ export interface CrmPropertiesState {
|
|
|
8
9
|
*
|
|
9
10
|
* @experimental This hook is experimental and might change or be removed in future versions.
|
|
10
11
|
*/
|
|
11
|
-
export declare function useCrmProperties(propertyNames: string[]): CrmPropertiesState;
|
|
12
|
+
export declare function useCrmProperties(propertyNames: string[], options?: FetchCrmPropertiesOptions): CrmPropertiesState;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useState, useMemo, useRef } from 'react';
|
|
2
2
|
import { logger } from '../../logger';
|
|
3
|
-
import { fetchCrmProperties } from '../crm/fetchCrmProperties';
|
|
3
|
+
import { fetchCrmProperties, } from '../crm/fetchCrmProperties';
|
|
4
|
+
const DEFAULT_OPTIONS = {};
|
|
4
5
|
/**
|
|
5
6
|
* A hook for using and managing CRM properties.
|
|
6
7
|
*
|
|
7
8
|
* @experimental This hook is experimental and might change or be removed in future versions.
|
|
8
9
|
*/
|
|
9
|
-
export function useCrmProperties(propertyNames) {
|
|
10
|
+
export function useCrmProperties(propertyNames, options = DEFAULT_OPTIONS) {
|
|
10
11
|
const [properties, setProperties] = useState({});
|
|
11
12
|
const [isLoading, setIsLoading] = useState(true);
|
|
12
13
|
const [error, setError] = useState(null);
|
|
@@ -14,33 +15,74 @@ export function useCrmProperties(propertyNames) {
|
|
|
14
15
|
useEffect(() => {
|
|
15
16
|
logger.warn('useCrmProperties is an experimental hook and might change or be removed in the future.');
|
|
16
17
|
}, []);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* HOOK OPTIMIZATION:
|
|
20
|
+
*
|
|
21
|
+
* Create stable references for propertyNames and options to prevent unnecessary re-renders and API calls.
|
|
22
|
+
* Then, external developers can pass inline arrays/objects without worrying about memoization
|
|
23
|
+
* We handle the deep equality comparison ourselves, and return the same object reference when content is equivalent.
|
|
24
|
+
*/
|
|
25
|
+
const lastPropertyNamesRef = useRef();
|
|
26
|
+
const lastPropertyNamesKeyRef = useRef();
|
|
27
|
+
const lastOptionsRef = useRef();
|
|
28
|
+
const lastOptionsKeyRef = useRef();
|
|
29
|
+
const stablePropertyNames = useMemo(() => {
|
|
30
|
+
const sortedNames = [...propertyNames].sort();
|
|
31
|
+
const propertyNamesKey = JSON.stringify(sortedNames);
|
|
32
|
+
if (propertyNamesKey === lastPropertyNamesKeyRef.current) {
|
|
33
|
+
return lastPropertyNamesRef.current;
|
|
34
|
+
}
|
|
35
|
+
lastPropertyNamesKeyRef.current = propertyNamesKey;
|
|
36
|
+
lastPropertyNamesRef.current = sortedNames;
|
|
37
|
+
return sortedNames;
|
|
38
|
+
}, [propertyNames]);
|
|
39
|
+
const stableOptions = useMemo(() => {
|
|
40
|
+
const optionsKey = JSON.stringify(options);
|
|
41
|
+
if (optionsKey === lastOptionsKeyRef.current) {
|
|
42
|
+
return lastOptionsRef.current;
|
|
43
|
+
}
|
|
44
|
+
lastOptionsKeyRef.current = optionsKey;
|
|
45
|
+
lastOptionsRef.current = options;
|
|
46
|
+
return options;
|
|
47
|
+
}, [options]);
|
|
20
48
|
// Fetch the properties
|
|
21
49
|
useEffect(() => {
|
|
22
|
-
|
|
50
|
+
let cancelled = false;
|
|
51
|
+
let cleanup = null;
|
|
52
|
+
const fetchData = async () => {
|
|
23
53
|
try {
|
|
24
|
-
|
|
25
|
-
setProperties(propertyData);
|
|
54
|
+
setIsLoading(true);
|
|
26
55
|
setError(null);
|
|
56
|
+
const result = await fetchCrmProperties(stablePropertyNames, setProperties, stableOptions);
|
|
57
|
+
if (!cancelled) {
|
|
58
|
+
setProperties(result.data);
|
|
59
|
+
cleanup = result.cleanup;
|
|
60
|
+
}
|
|
27
61
|
}
|
|
28
62
|
catch (err) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
63
|
+
if (!cancelled) {
|
|
64
|
+
const errorData = err instanceof Error
|
|
65
|
+
? err
|
|
66
|
+
: new Error('Failed to fetch CRM properties');
|
|
67
|
+
setError(errorData);
|
|
68
|
+
setProperties({});
|
|
69
|
+
}
|
|
34
70
|
}
|
|
35
71
|
finally {
|
|
36
|
-
|
|
72
|
+
if (!cancelled) {
|
|
73
|
+
setIsLoading(false);
|
|
74
|
+
}
|
|
37
75
|
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
76
|
+
};
|
|
77
|
+
fetchData();
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
// Call cleanup function to release RPC resources
|
|
81
|
+
if (cleanup) {
|
|
82
|
+
cleanup();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}, [stablePropertyNames, stableOptions]);
|
|
44
86
|
return {
|
|
45
87
|
properties,
|
|
46
88
|
error,
|
|
@@ -83,4 +83,18 @@ declare const FileInput: "FileInput" & {
|
|
|
83
83
|
readonly props?: experimentalTypes.FileInputProps | undefined;
|
|
84
84
|
readonly children?: true | undefined;
|
|
85
85
|
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"FileInput", experimentalTypes.FileInputProps, true>>;
|
|
86
|
-
|
|
86
|
+
/**
|
|
87
|
+
* The TimeInput component renders an input field where a user can select a time.
|
|
88
|
+
*/
|
|
89
|
+
declare const TimeInput: "TimeInput" & {
|
|
90
|
+
readonly type?: "TimeInput" | undefined;
|
|
91
|
+
readonly props?: experimentalTypes.TimeInputProps | undefined;
|
|
92
|
+
readonly children?: true | undefined;
|
|
93
|
+
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"TimeInput", experimentalTypes.TimeInputProps, true>>;
|
|
94
|
+
/** @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates. */
|
|
95
|
+
declare const CurrencyInput: "CurrencyInput" & {
|
|
96
|
+
readonly type?: "CurrencyInput" | undefined;
|
|
97
|
+
readonly props?: experimentalTypes.CurrencyInputProps | undefined;
|
|
98
|
+
readonly children?: true | undefined;
|
|
99
|
+
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"CurrencyInput", experimentalTypes.CurrencyInputProps, true>>;
|
|
100
|
+
export { Iframe, MediaObject, Inline, Stack2, Center, SimpleGrid, GridItem, Grid, SettingsView, ExpandableText, Popover, FileInput, TimeInput, CurrencyInput, };
|
|
@@ -34,8 +34,12 @@ const ExpandableText = createRemoteReactComponent('ExpandableText');
|
|
|
34
34
|
*
|
|
35
35
|
* - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/popover Popover Docs}
|
|
36
36
|
*/
|
|
37
|
-
const Popover = createRemoteReactComponent('Popover'
|
|
38
|
-
fragmentProps: ['header', 'body', 'footer'],
|
|
39
|
-
});
|
|
37
|
+
const Popover = createRemoteReactComponent('Popover');
|
|
40
38
|
const FileInput = createRemoteReactComponent('FileInput');
|
|
41
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The TimeInput component renders an input field where a user can select a time.
|
|
41
|
+
*/
|
|
42
|
+
const TimeInput = createRemoteReactComponent('TimeInput');
|
|
43
|
+
/** @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates. */
|
|
44
|
+
const CurrencyInput = createRemoteReactComponent('CurrencyInput');
|
|
45
|
+
export { Iframe, MediaObject, Inline, Stack2, Center, SimpleGrid, GridItem, Grid, SettingsView, ExpandableText, Popover, FileInput, TimeInput, CurrencyInput, };
|
|
@@ -127,17 +127,39 @@ export interface ExpandableTextProps {
|
|
|
127
127
|
* @experimental do not use in production
|
|
128
128
|
*/
|
|
129
129
|
export interface PopoverProps {
|
|
130
|
+
/**
|
|
131
|
+
* A unique ID for the popover. Used to identify the popover in the overlay system.
|
|
132
|
+
*
|
|
133
|
+
*/
|
|
134
|
+
id: string;
|
|
135
|
+
/**
|
|
136
|
+
* The content to render inside the popover.
|
|
137
|
+
*/
|
|
130
138
|
children: ReactNode;
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
/**
|
|
140
|
+
* The placement of the popover.
|
|
141
|
+
*
|
|
142
|
+
* @defaultValue `top`
|
|
143
|
+
*/
|
|
133
144
|
placement?: 'left' | 'right' | 'top' | 'bottom';
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
/**
|
|
146
|
+
* The variant of the popover.
|
|
147
|
+
*
|
|
148
|
+
* @defaultValue `default`
|
|
149
|
+
*/
|
|
137
150
|
variant?: 'default' | 'shepherd' | 'longform';
|
|
151
|
+
/**
|
|
152
|
+
* If set to `true`, will show the close button in the popover. PopoverHeader required to display close button.
|
|
153
|
+
*
|
|
154
|
+
* @defaultValue `false`
|
|
155
|
+
*/
|
|
138
156
|
showCloseButton?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* The size of the arrow in the popover. If set to `none`, the arrow will not be displayed.
|
|
159
|
+
*
|
|
160
|
+
* @defaultValue `small`
|
|
161
|
+
*/
|
|
139
162
|
arrowSize?: 'none' | 'small' | 'medium';
|
|
140
|
-
onClick?: ReactionsHandler<ExtensionEvent>;
|
|
141
163
|
}
|
|
142
164
|
export interface FileInputProps {
|
|
143
165
|
value?: File | {
|
|
@@ -146,4 +168,167 @@ export interface FileInputProps {
|
|
|
146
168
|
name: string;
|
|
147
169
|
onChange: (event: any) => void;
|
|
148
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* @ignore
|
|
173
|
+
* @experimental do not use in production
|
|
174
|
+
*/
|
|
175
|
+
export interface BaseTime {
|
|
176
|
+
/** The hour for the time (0 to 23) in 24-hour format (e.g. 0 = 12:00 AM, 9 = 9:00 AM, 15 = 3:00 PM). */
|
|
177
|
+
hours: number;
|
|
178
|
+
/** The minutes for the time (0 to 59). */
|
|
179
|
+
minutes: number;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Generic collection of props for all inputs (experimental version)
|
|
183
|
+
* @internal
|
|
184
|
+
* */
|
|
185
|
+
export interface BaseInputProps<T = string, V = string> {
|
|
186
|
+
/**
|
|
187
|
+
* The label text to display for the form input element.
|
|
188
|
+
*/
|
|
189
|
+
label: string;
|
|
190
|
+
/**
|
|
191
|
+
* The unique identifier for the input element, this could be thought of as the HTML5 [Input element's name attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name).
|
|
192
|
+
*/
|
|
193
|
+
name: string;
|
|
194
|
+
/**
|
|
195
|
+
* The value of the input.
|
|
196
|
+
*/
|
|
197
|
+
value?: T;
|
|
198
|
+
/**
|
|
199
|
+
* Determines if the required indicator should be displayed.
|
|
200
|
+
*
|
|
201
|
+
* @defaultValue `false`
|
|
202
|
+
*/
|
|
203
|
+
required?: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Determines if the field is editable or not.
|
|
206
|
+
*
|
|
207
|
+
* @defaultValue `false`
|
|
208
|
+
*/
|
|
209
|
+
readOnly?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Instructional message to display to the user to help understand the purpose of the input.
|
|
212
|
+
*/
|
|
213
|
+
description?: string;
|
|
214
|
+
/**
|
|
215
|
+
* Text that will appear in a tooltip next to the input label.
|
|
216
|
+
*/
|
|
217
|
+
tooltip?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Text that appears in the input when it has no value set.
|
|
220
|
+
*/
|
|
221
|
+
placeholder?: string;
|
|
222
|
+
/**
|
|
223
|
+
* If set to `true`, `validationMessage` is displayed as an error message, if it was provided. The input will also render its error state to let the user know there is an error. If set to `false`, `validationMessage` is displayed as a success message.
|
|
224
|
+
*
|
|
225
|
+
* @defaultValue `false`
|
|
226
|
+
*/
|
|
227
|
+
error?: boolean;
|
|
228
|
+
/**
|
|
229
|
+
* The value of the input on the first render.
|
|
230
|
+
*/
|
|
231
|
+
defaultValue?: T;
|
|
232
|
+
/**
|
|
233
|
+
* The text to show under the input for error or success validations.
|
|
234
|
+
*/
|
|
235
|
+
validationMessage?: string;
|
|
236
|
+
/**
|
|
237
|
+
* A callback function that is invoked when the value is committed. Currently these times are `onBlur` of the input and when the user submits the form.
|
|
238
|
+
*
|
|
239
|
+
* @event
|
|
240
|
+
*/
|
|
241
|
+
onChange?: (value: V) => void;
|
|
242
|
+
/**
|
|
243
|
+
* A function that is called and passed the value every time the field is edited by the user. It is recommended that you do not use this value to update state, that is what `onChange` should be used for. Instead this should be used for validation.
|
|
244
|
+
*
|
|
245
|
+
* @event
|
|
246
|
+
*/
|
|
247
|
+
onInput?: (value: V) => void;
|
|
248
|
+
/**
|
|
249
|
+
* A function that is called and passed the value every time the field loses focus.
|
|
250
|
+
*
|
|
251
|
+
* @event
|
|
252
|
+
*/
|
|
253
|
+
onBlur?: (value: V) => void;
|
|
254
|
+
/**
|
|
255
|
+
* A function that is called and passed the value every time the field gets focused.
|
|
256
|
+
*
|
|
257
|
+
* @event
|
|
258
|
+
*/
|
|
259
|
+
onFocus?: (value: V) => void;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* @ignore
|
|
263
|
+
* @experimental do not use in production
|
|
264
|
+
*
|
|
265
|
+
* The values used to invoke events on the TimeInput component
|
|
266
|
+
*/
|
|
267
|
+
export interface TimeInputEventsPayload extends BaseTime {
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* @internal
|
|
271
|
+
* @ignore
|
|
272
|
+
* */
|
|
273
|
+
type BaseTimeInputForTime = Omit<BaseInputProps<BaseTime | null, TimeInputEventsPayload>, 'onInput' | 'placeholder' | 'onChange'>;
|
|
274
|
+
/**
|
|
275
|
+
* @ignore
|
|
276
|
+
* @experimental do not use in production
|
|
277
|
+
*/
|
|
278
|
+
export interface TimeInputProps extends BaseTimeInputForTime {
|
|
279
|
+
/**
|
|
280
|
+
* A callback function that is invoked when the value is changed.
|
|
281
|
+
*
|
|
282
|
+
* @event
|
|
283
|
+
*/
|
|
284
|
+
onChange?: (value: TimeInputEventsPayload) => void;
|
|
285
|
+
/**
|
|
286
|
+
* Sets the earliest time that will be valid.
|
|
287
|
+
*/
|
|
288
|
+
min?: BaseTime;
|
|
289
|
+
/**
|
|
290
|
+
* Sets the latest time that will be valid.
|
|
291
|
+
*/
|
|
292
|
+
max?: BaseTime;
|
|
293
|
+
/**
|
|
294
|
+
* Sets the interval (in minutes) between the dropdown options.
|
|
295
|
+
*
|
|
296
|
+
* @defaultValue `30`
|
|
297
|
+
*/
|
|
298
|
+
interval?: number;
|
|
299
|
+
/**
|
|
300
|
+
* Sets the timezone that the component will display alongside times in the TimePicker. This will not adjust the available valid inputs.
|
|
301
|
+
*
|
|
302
|
+
*/
|
|
303
|
+
timezone?: 'userTz' | 'portalTz';
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* @ignore
|
|
307
|
+
* @experimental do not use in production
|
|
308
|
+
*/
|
|
309
|
+
type BaseInputForNumber = Omit<BaseInputProps<number, number>, 'onInput'>;
|
|
310
|
+
/**
|
|
311
|
+
* @ignore
|
|
312
|
+
* @experimental do not use in production
|
|
313
|
+
*/
|
|
314
|
+
export interface CurrencyInputProps extends BaseInputForNumber {
|
|
315
|
+
/**
|
|
316
|
+
* ISO 4217 currency code (e.g., "USD", "EUR", "JPY")
|
|
317
|
+
* @defaultValue "USD"
|
|
318
|
+
*/
|
|
319
|
+
currency?: string;
|
|
320
|
+
/**
|
|
321
|
+
* Sets the number of decimal places for the currency
|
|
322
|
+
* If not provided, defaults to currency-specific precision
|
|
323
|
+
*/
|
|
324
|
+
precision?: number;
|
|
325
|
+
/**
|
|
326
|
+
* Sets the lower bound of the input
|
|
327
|
+
*/
|
|
328
|
+
min?: number;
|
|
329
|
+
/**
|
|
330
|
+
* Sets the upper bound of the input
|
|
331
|
+
*/
|
|
332
|
+
max?: number;
|
|
333
|
+
}
|
|
149
334
|
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -1164,7 +1164,7 @@ export interface DateInputProps extends BaseDateInputForDate {
|
|
|
1164
1164
|
*/
|
|
1165
1165
|
format?: 'YYYY-MM-DD' | 'L' | 'LL' | 'll' | 'short' | 'long' | 'medium' | 'standard';
|
|
1166
1166
|
/**
|
|
1167
|
-
* Sets the timezone that the component will
|
|
1167
|
+
* Sets the timezone that the component will use to calculate valid dates.
|
|
1168
1168
|
*
|
|
1169
1169
|
* @defaultValue `"userTz"`
|
|
1170
1170
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"ts-jest": "^29.1.1",
|
|
67
67
|
"typescript": "5.0.4"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "a5c7f8d19d0dc16b44e9235d3e12dacc2cc14fdf"
|
|
70
70
|
}
|