@onehat/ui 0.3.67 → 0.3.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.3.67",
3
+ "version": "0.3.69",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -24,6 +24,7 @@ const
24
24
  } = props,
25
25
  [low, setLow] = useState(''),
26
26
  [high, setHigh] = useState(''),
27
+ [isReady, setIsReady] = useState(false),
27
28
  onChangeLow = (value) => {
28
29
  setLow(value);
29
30
  const newValue = {
@@ -59,9 +60,16 @@ const
59
60
  if (value.high !== high) {
60
61
  setHigh(value.high);
61
62
  }
63
+ if (!isReady) {
64
+ setIsReady(true);
65
+ }
62
66
 
63
67
  }, [value]);
64
68
 
69
+ if (!isReady) {
70
+ return null;
71
+ }
72
+
65
73
  return <Row
66
74
  justifyContent="center"
67
75
  alignItems="center"
@@ -72,7 +80,6 @@ const
72
80
  value={low}
73
81
  onChangeValue={onChangeLow}
74
82
  mode={mode}
75
- startingValue={null}
76
83
  // minValue={minValue}
77
84
  // maxValue={maxValue}
78
85
  tooltip={(tooltip ? tooltip + ' ' : '') + 'Low'}
@@ -82,7 +89,6 @@ const
82
89
  value={high}
83
90
  onChangeValue={onChangeHigh}
84
91
  mode={mode}
85
- startingValue={null}
86
92
  // minValue={minValue}
87
93
  // maxValue={maxValue}
88
94
  tooltip={(tooltip ? tooltip + ' ' : '') + 'High'}
@@ -70,6 +70,7 @@ export function ComboComponent(props) {
70
70
  [gridSelection, setGridSelection] = useState(null),
71
71
  [textInputValue, setTextInputValue] = useState(''),
72
72
  [newEntityDisplayValue, setNewEntityDisplayValue] = useState(null),
73
+ [filteredData, setFilteredData] = useState(data),
73
74
  [width, setWidth] = useState(0),
74
75
  [top, setTop] = useState(0),
75
76
  [left, setLeft] = useState(0),
@@ -129,6 +130,7 @@ export function ComboComponent(props) {
129
130
  displayValue = [];
130
131
  if (Repository) {
131
132
  if (!Repository.isLoaded) {
133
+ debugger;
132
134
  throw Error('Not yet implemented'); // Would a Combo ever have multiple remote selections? Shouldn't that be a Tag field??
133
135
  }
134
136
  if (Repository.isLoading) {
@@ -168,9 +170,9 @@ export function ComboComponent(props) {
168
170
  }
169
171
 
170
172
  displayValueRef.current = displayValue;
171
- resetInputTextValue();
173
+ resetTextInputValue();
172
174
  },
173
- resetInputTextValue = () => {
175
+ resetTextInputValue = () => {
174
176
  setTextInputValue(getDisplayValue());
175
177
  },
176
178
  onInputKeyPress = (e, inputValue) => {
@@ -180,7 +182,7 @@ export function ComboComponent(props) {
180
182
  switch(e.key) {
181
183
  case 'Escape':
182
184
  setIsSearchMode(false);
183
- resetInputTextValue();
185
+ resetTextInputValue();
184
186
  hideMenu();
185
187
  break;
186
188
  case 'Enter':
@@ -245,8 +247,8 @@ export function ComboComponent(props) {
245
247
  }, 300);
246
248
  },
247
249
  onInputFocus = (e) => {
248
- if (inputRef.current.select) {
249
- inputRef.current.select();
250
+ if (inputRef.current?.select) {
251
+ inputRef.current?.select();
250
252
  }
251
253
  },
252
254
  onInputBlur = (e) => {
@@ -256,7 +258,7 @@ export function ComboComponent(props) {
256
258
  }
257
259
 
258
260
  setIsSearchMode(false);
259
- resetInputTextValue();
261
+ resetTextInputValue();
260
262
  hideMenu();
261
263
  },
262
264
  onTriggerPress = (e) => {
@@ -277,7 +279,7 @@ export function ComboComponent(props) {
277
279
  }
278
280
 
279
281
  setIsSearchMode(false);
280
- resetInputTextValue();
282
+ resetTextInputValue();
281
283
  hideMenu();
282
284
  },
283
285
  onClearBtn = () => {
@@ -303,7 +305,7 @@ export function ComboComponent(props) {
303
305
  }
304
306
 
305
307
  // clear filter
306
- if (Repository.isRemote) {
308
+ if (Repository.isRemote || Repository.remote) {
307
309
  let searchField = 'q';
308
310
  const searchValue = null;
309
311
 
@@ -316,10 +318,12 @@ export function ComboComponent(props) {
316
318
  searchField = displayFieldName + ' LIKE';
317
319
  }
318
320
 
319
- Repository.clear();
320
- await Repository.filter(searchField, searchValue);
321
- if (!this.isAutoLoad) {
322
- await Repository.reload();
321
+ if (Repository.hasFilter(searchField)) {
322
+ Repository.clear();
323
+ await Repository.filter(searchField, searchValue);
324
+ if (!this.isAutoLoad) {
325
+ await Repository.reload();
326
+ }
323
327
  }
324
328
 
325
329
  } else {
@@ -327,7 +331,7 @@ export function ComboComponent(props) {
327
331
  }
328
332
 
329
333
  } else {
330
- // throw Error('Not yet implemented');
334
+ setFilteredData(data);
331
335
  }
332
336
  },
333
337
  searchForMatches = async (value) => {
@@ -339,21 +343,26 @@ export function ComboComponent(props) {
339
343
 
340
344
  let found;
341
345
  if (Repository) {
346
+
347
+ if (_.isEmpty(value) && Repository.hasFilters) {
348
+ Repository.clearFilters();
349
+ return;
350
+ }
351
+
342
352
  if (Repository.isLoading) {
343
353
  await Repository.waitUntilDoneLoading();
344
354
  }
345
355
 
346
356
  // Set filter
347
- let filter = {};
348
357
  if (Repository.isRemote) {
349
- let searchField = 'q';
350
- const searchValue = _.isEmpty(value) ? null : value + '%';
351
-
352
- // Check to see if displayField is a real field
353
358
  const
354
359
  schema = Repository.getSchema(),
355
360
  displayFieldName = schema.model.displayProperty,
356
- displayFieldDef = schema.getPropertyDefinition(displayFieldName);
361
+ displayFieldDef = schema.getPropertyDefinition(displayFieldName),
362
+ searchValue = _.isEmpty(value) ? null : value + '%';
363
+ let searchField = 'q';
364
+
365
+ // Verify displayField is a real field
357
366
  if (!displayFieldDef.isVirtual) {
358
367
  searchField = displayFieldName + ' LIKE';
359
368
  }
@@ -362,39 +371,28 @@ export function ComboComponent(props) {
362
371
  if (!this.isAutoLoad) {
363
372
  await Repository.reload();
364
373
  }
365
-
366
374
  } else {
367
- throw Error('Not yet implemented');
368
-
369
- // Fuzzy search with getBy filter function
370
- filter = (entity) => {
375
+ // local filter
376
+ Repository.filter((entity) => {
371
377
  const
372
378
  displayValue = entity.displayValue,
373
379
  regex = new RegExp('^' + value);
374
380
  return displayValue.match(regex);
375
- };
376
- Repository.filter(filter);
381
+ });
377
382
  }
378
383
 
379
384
  setNewEntityDisplayValue(value); // capture the search query so we can tell Grid what to use for a new entity's displayValue
380
385
 
381
386
  } else {
382
-
383
- throw Error('Not yet implemented'); // NOTE: When implementing this, also implement clearGridFilters
384
-
385
387
  // Search through data
386
- found = _.find(data, (item) => {
388
+ const regex = new RegExp('^' + value);
389
+ found = _.filter(data, (item) => {
387
390
  if (_.isString(item[displayIx]) && _.isString(value)) {
388
- return item[displayIx].toLowerCase() === value.toLowerCase();
391
+ return item[displayIx].match(regex);
389
392
  }
390
- return item[displayIx] === value;
393
+ return item[displayIx] == value; // equality, not identity
391
394
  });
392
- // if (found) {
393
- // const
394
- // newSelection = [found];
395
-
396
- // setTextInputValue(newTextValue);
397
- // }
395
+ setFilteredData(found);
398
396
  }
399
397
  };
400
398
 
@@ -588,7 +586,7 @@ export function ComboComponent(props) {
588
586
  'Editor',
589
587
  'model',
590
588
  'Repository',
591
- 'data',
589
+ // 'data',
592
590
  'idIx',
593
591
  'displayIx',
594
592
  'value',
@@ -614,6 +612,7 @@ export function ComboComponent(props) {
614
612
  }}
615
613
  autoAdjustPageSizeToHeight={false}
616
614
  {...gridProps}
615
+ data={filteredData}
617
616
  reference="dropdownGrid"
618
617
  parent={self}
619
618
  h={UiGlobals.mode === UI_MODE_WEB ? styles.FORM_COMBO_MENU_HEIGHT + 'px' : null}
@@ -638,7 +637,7 @@ export function ComboComponent(props) {
638
637
  // when user selected the record matching the current value, kill search mode
639
638
  if (selection[0]?.id === value) {
640
639
  setIsSearchMode(false);
641
- resetInputTextValue();
640
+ resetTextInputValue();
642
641
  if (hideMenuOnSelection) {
643
642
  hideMenu();
644
643
  }
@@ -657,7 +656,7 @@ export function ComboComponent(props) {
657
656
  // when user selected the record matching the current value, kill search mode
658
657
  if (selection[0] && selection[0][idIx] === value) {
659
658
  setIsSearchMode(false);
660
- resetInputTextValue();
659
+ resetTextInputValue();
661
660
  if (hideMenuOnSelection) {
662
661
  hideMenu();
663
662
  }
@@ -1,6 +1,8 @@
1
1
  import React, { useState, useEffect, useRef, } from 'react';
2
2
  import {
3
+ Box,
3
4
  Icon,
5
+ Modal,
4
6
  Popover,
5
7
  Pressable,
6
8
  Row,
@@ -13,6 +15,7 @@ import {
13
15
  DATETIME,
14
16
  } from '../../../Constants/Date.js';
15
17
  import {
18
+ UI_MODE_REACT_NATIVE,
16
19
  UI_MODE_WEB,
17
20
  } from '../../../Constants/UiModes.js';
18
21
  import UiGlobals from '../../../UiGlobals.js';
@@ -20,24 +23,36 @@ import Formatters from '@onehat/data/src/Util/Formatters.js';
20
23
  import Parsers from '@onehat/data/src/Util/Parsers.js';
21
24
  import Input from '../Field/Input.js';
22
25
  import IconButton from '../../Buttons/IconButton.js';
26
+ import Xmark from '../../Icons/Xmark.js';
23
27
  import withComponent from '../../Hoc/withComponent.js';
24
28
  import withValue from '../../Hoc/withValue.js';
25
29
  import emptyFn from '../../../Functions/emptyFn.js';
26
30
  import Calendar from '../../Icons/Calendar.js';
27
31
  import getComponentFromType from '../../../Functions/getComponentFromType.js';
32
+ import moment from 'moment';
28
33
  import _ from 'lodash';
29
34
 
30
35
 
31
36
  export function DateElement(props) {
32
37
  const {
33
- placeholderText,
34
- value,
35
- setValue,
36
38
  format,
37
39
  mode = DATE,
38
- tooltip = 'Choose a date.',
39
- tooltipPlacement = 'bottom',
40
+
41
+ additionalButtons,
42
+ tooltipRef = null,
43
+ tooltip = null,
44
+ menuMinWidth = 150,
45
+ disableDirectEntry = false,
46
+ hideMenuOnSelection = true,
47
+ showXButton = false,
48
+ _input = {},
40
49
  isDisabled = false,
50
+ tooltipPlacement = 'bottom',
51
+ placeholder = 'Choose a date.',
52
+
53
+ // withValue
54
+ value,
55
+ setValue,
41
56
  } = props,
42
57
  styles = UiGlobals.styles,
43
58
  Datetime = getComponentFromType('Datetime'),
@@ -46,6 +61,7 @@ export function DateElement(props) {
46
61
  pickerRef = useRef(),
47
62
  [isPickerShown, setIsPickerShown] = useState(false),
48
63
  [isRendered, setIsRendered] = useState(false),
64
+ [textInputValue, setTextInputValue] = useState(value),
49
65
  [isTranslateX, setIsTranslateX] = useState(false),
50
66
  [isTranslateY, setIsTranslateY] = useState(false),
51
67
  [top, setTop] = useState(0),
@@ -83,7 +99,16 @@ export function DateElement(props) {
83
99
  }
84
100
  setIsPickerShown(false);
85
101
  },
86
- onInputKeyPress = (e) => {
102
+ togglePicker = () => {
103
+ setIsPickerShown(!isPickerShown);
104
+ },
105
+ onInputKeyPress = (e, inputValue) => {
106
+ if (disableDirectEntry) {
107
+ return;
108
+ }
109
+ if (UiGlobals.mode !== UI_MODE_WEB) {
110
+ return;
111
+ }
87
112
  switch(e.key) {
88
113
  case 'Escape':
89
114
  case 'Enter':
@@ -93,13 +118,16 @@ export function DateElement(props) {
93
118
  }
94
119
  },
95
120
  onInputBlur = (e) => {
96
- const {
97
- relatedTarget
98
- } = e;
99
- if (!relatedTarget ||
100
- (!inputRef.current.contains(relatedTarget) && triggerRef.current !== relatedTarget && (!pickerRef.current || !pickerRef.current.contains(relatedTarget)))) {
101
- // hidePicker();
102
- }
121
+ // if (UiGlobals.mode !== UI_MODE_WEB) {
122
+ // return;
123
+ // }
124
+ // const {
125
+ // relatedTarget
126
+ // } = e;
127
+ // if (!relatedTarget ||
128
+ // (!inputRef.current.contains(relatedTarget) && triggerRef.current !== relatedTarget && (!pickerRef.current || !pickerRef.current.contains(relatedTarget)))) {
129
+ // // hidePicker();
130
+ // }
103
131
  },
104
132
  onInputClick = (e) => {
105
133
  if (!isRendered) {
@@ -107,9 +135,13 @@ export function DateElement(props) {
107
135
  }
108
136
  showPicker();
109
137
  },
110
- onInputSetValue = (value) => {
111
- if (value === '') {
138
+ onInputChangeText = (value) => {
139
+ if (disableDirectEntry) {
140
+ return;
141
+ }
142
+ if (_.isEmpty(value)) {
112
143
  setValue(null);
144
+ setTextInputValue('');
113
145
  return;
114
146
  }
115
147
  switch(mode) {
@@ -133,7 +165,20 @@ export function DateElement(props) {
133
165
  break;
134
166
  default:
135
167
  }
136
- setValue(value);
168
+
169
+ if (value !== 'Invalid date') {
170
+ setValue(value);
171
+ }
172
+
173
+ setTextInputValue(value);
174
+ if (!isPickerShown) {
175
+ showPicker();
176
+ }
177
+ },
178
+ onInputFocus = (e) => {
179
+ if (inputRef.current?.select) {
180
+ inputRef.current?.select();
181
+ }
137
182
  },
138
183
  onTriggerPress = (e) => {
139
184
  if (!isRendered) {
@@ -144,10 +189,10 @@ export function DateElement(props) {
144
189
  } else {
145
190
  showPicker();
146
191
  }
147
- inputRef.current.focus();
192
+ inputRef.current?.focus();
148
193
  },
149
194
  onTriggerBlur = (e) => {
150
- if (!isPickerShown) {
195
+ if (!isPickerShown || UiGlobals.mode !== UI_MODE_WEB) {
151
196
  return;
152
197
  }
153
198
  const {
@@ -172,28 +217,15 @@ export function DateElement(props) {
172
217
  break;
173
218
  default:
174
219
  }
175
- setValue(value);
176
- };
177
220
 
178
-
179
- // place the picker in a convenient spot
180
- const
181
- translateParts = [],
182
- translateProps = {};
183
- if (isTranslateX) {
184
- translateParts.push('translateX(-100%)');
185
- }
186
- if (isTranslateY) {
187
- translateParts.push('translateY(-100%)');
188
- }
189
- if (!_.isEmpty(translateParts)) {
190
- translateProps.style = {
191
- transform: translateParts.join(' '),
221
+ if (moment && moment?.isValid()) {
222
+ setValue(value);
223
+ setTextInputValue(value);
224
+ }
192
225
  };
193
- }
194
-
226
+
195
227
  // Format the display date/time/datetime
196
- let title = placeholderText,
228
+ let title = placeholder,
197
229
  pickerValue = null,
198
230
  height = 300,
199
231
  width = 300;
@@ -226,147 +258,346 @@ export function DateElement(props) {
226
258
  pickerValue = pickerValue.toDate();
227
259
  }
228
260
 
229
- // Web version
230
- return <Tooltip label={tooltip} placement={tooltipPlacement}>
231
- <Row flex={1} h="100%" alignItems="center" onLayout={() => setIsRendered(true)}>
232
- <IconButton
233
- ref={triggerRef}
234
- icon={<Icon as={Calendar} color={styles.FORM_DATE_ICON_COLOR} />}
235
- onPress={onTriggerPress}
236
- onBlur={onTriggerBlur}
237
- h={10}
238
- w={10}
261
+ let xButton = null,
262
+ inputAndTrigger = null,
263
+ grid = null,
264
+ dropdownMenu = null,
265
+ assembledComponents = null;
266
+
267
+ if (showXButton && !_.isNil(value)) {
268
+ xButton = <IconButton
269
+ _icon={{
270
+ as: Xmark,
271
+ color: 'trueGray.600',
272
+ size: 'sm',
273
+ }}
239
274
  isDisabled={isDisabled}
240
- borderTopLeftRadius={6}
241
- borderBottomLeftRadius={6}
242
- borderTopRightRadius={0}
243
- borderBottomRightRadius={0}
244
- bg={styles.FORM_DATE_ICON_BG}
275
+ onPress={onClearBtn}
276
+ h="100%"
277
+ bg={styles.FORM_COMBO_TRIGGER_BG}
245
278
  _hover={{
246
- bg: styles.FORM_DATE_ICON_BG_HOVER,
279
+ bg: styles.FORM_COMBO_TRIGGER_HOVER_BG,
247
280
  }}
248
- />
249
- <Input
250
- ref={inputRef}
251
- value={title}
252
- setValue={onInputSetValue}
253
- onKeyPress={onInputKeyPress}
254
- onBlur={onInputBlur}
255
- onClick={onInputClick}
256
- flex={1}
257
- h="100%"
258
- p={2}
259
- borderWidth={1}
260
- borderColor="trueGray.300"
261
- borderLeftWidth={0}
262
- borderTopLeftRadius={0}
263
- borderBottomLeftRadius={0}
264
- borderTopRightRadius={6}
265
- borderBottomRightRadius={6}
266
- fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
267
- bg={styles.FORM_DATE_INPUT_BG}
268
- _focus={{
269
- bg: styles.FORM_DATE_INPUT_FOCUS_BG,
281
+ />;
282
+ }
283
+
284
+ if (UiGlobals.mode === UI_MODE_WEB) {
285
+ inputAndTrigger = <>
286
+ <IconButton
287
+ ref={triggerRef}
288
+ _icon={{
289
+ as: Calendar,
290
+ color: styles.FORM_DATE_ICON_COLOR,
291
+ size: 'sm',
292
+ }}
293
+ isDisabled={isDisabled}
294
+ onPress={onTriggerPress}
295
+ onBlur={onTriggerBlur}
296
+ h="100%"
297
+ w={10}
298
+ borderWidth={1}
299
+ borderColor="#bbb"
300
+ borderLeftRadius="md"
301
+ borderRighttRadius={0}
302
+ bg={styles.FORM_DATE_ICON_BG}
303
+ _hover={{
304
+ bg: styles.FORM_DATE_ICON_BG_HOVER,
305
+ }}
306
+ />
307
+ {disableDirectEntry ?
308
+ <Pressable
309
+ onPress={togglePicker}
310
+ flex={1}
311
+ h="100%"
312
+ >
313
+ <Text
314
+ ref={inputRef}
315
+ flex={1}
316
+ h="100%"
317
+ numberOfLines={1}
318
+ ellipsizeMode="head"
319
+ m={0}
320
+ p={2}
321
+ borderWidth={1}
322
+ borderColor="trueGray.400"
323
+ borderLeftWidth={0}
324
+ borderLeftRadius={0}
325
+ borderRightRadius="md"
326
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
327
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
328
+ bg={styles.FORM_DATE_INPUT_BG}
329
+ _focus={{
330
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
331
+ }}
332
+ >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
333
+ </Pressable> :
334
+ <Input
335
+ ref={inputRef}
336
+ value={textInputValue}
337
+ autoSubmit={true}
338
+ isDisabled={isDisabled}
339
+ onChangeValue={onInputChangeText}
340
+ onKeyPress={onInputKeyPress}
341
+ onFocus={onInputFocus}
342
+ onBlur={onInputBlur}
343
+ onLayout={(e) => {
344
+ const {
345
+ height,
346
+ width,
347
+ } = e.nativeEvent.layout;
348
+ setWidth(Math.round(width));
349
+ setTop(Math.round(height));
350
+ }}
351
+ flex={1}
352
+ h="100%"
353
+ m={0}
354
+ autoSubmitDelay={1000}
355
+ borderTopRightRadius={0}
356
+ borderBottomRightRadius={0}
357
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
358
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
359
+ bg={styles.FORM_DATE_INPUT_BG}
360
+ _focus={{
361
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
362
+ }}
363
+ placeholder={placeholder}
364
+ {..._input}
365
+ />}
366
+ </>;
367
+ }
368
+
369
+ if (UiGlobals.mode === UI_MODE_REACT_NATIVE) {
370
+ // This input and trigger are for show
371
+ // The just show the current value and open the menu
372
+ inputAndTrigger = <>
373
+ <IconButton
374
+ ref={triggerRef}
375
+ _icon={{
376
+ as: Calendar,
377
+ color: styles.FORM_DATE_ICON_COLOR,
378
+ size: 'sm',
379
+ }}
380
+ isDisabled={isDisabled}
381
+ onPress={onTriggerPress}
382
+ onBlur={onTriggerBlur}
383
+ h="100%"
384
+ w={10}
385
+ borderWidth={1}
386
+ borderColor="#bbb"
387
+ borderLeftRadius="md"
388
+ borderRightWidth={0}
389
+ borderRighttRadius={0}
390
+ bg={styles.FORM_DATE_ICON_BG}
391
+ _hover={{
392
+ bg: styles.FORM_DATE_ICON_BG_HOVER,
393
+ }}
394
+ />
395
+ <Pressable
396
+ onPress={togglePicker}
397
+ flex={1}
398
+ >
399
+ <Text
400
+ flex={1}
401
+ h="100%"
402
+ numberOfLines={1}
403
+ ellipsizeMode="head"
404
+ m={0}
405
+ p={2}
406
+ borderWidth={1}
407
+ borderColor="trueGray.400"
408
+ borderLeftWidth={0}
409
+ borderLeftRadius={0}
410
+ borderRightRadius="md"
411
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
412
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
413
+ bg={styles.FORM_DATE_INPUT_BG}
414
+ _focus={{
415
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
416
+ }}
417
+ >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
418
+ </Pressable>
419
+ </>;
420
+ }
421
+
422
+ if (isPickerShown) {
423
+ if (UiGlobals.mode === UI_MODE_WEB) {
424
+
425
+ // place the picker in a convenient spot
426
+ const
427
+ translateParts = [],
428
+ translateProps = {};
429
+ if (isTranslateX) {
430
+ translateParts.push('translateX(-100%)');
431
+ }
432
+ if (isTranslateY) {
433
+ translateParts.push('translateY(-100%)');
434
+ }
435
+ if (!_.isEmpty(translateParts)) {
436
+ translateProps.style = {
437
+ transform: translateParts.join(' '),
438
+ };
439
+ }
440
+ dropdownMenu = <Popover
441
+ isOpen={isPickerShown}
442
+ onClose={() => {
443
+ hidePicker();
444
+ }}
445
+ trigger={emptyFn}
446
+ trapFocus={false}
447
+ placement={'auto'}
448
+ {...props}
449
+ >
450
+ <Popover.Content
451
+ position="absolute"
452
+ top={top + 'px'}
453
+ left={left + 'px'}
454
+ w={width + 'px'}
455
+ minWidth={menuMinWidth}
456
+ overflow="auto"
457
+ bg="#fff"
458
+ >
459
+ <Popover.Body
460
+ ref={pickerRef}
461
+ borderWidth={1}
462
+ borderColor='trueGray.400'
463
+ borderTopWidth={0}
464
+ p={0}
465
+ >
466
+ <Datetime
467
+ open={true}
468
+ input={false}
469
+ closeOnClickOutside={false}
470
+ value={pickerValue}
471
+ dateFormat={mode === DATE || mode === DATETIME ? 'YYYY-MM-DD' : false}
472
+ timeFormat={mode === TIME || mode === DATETIME ? 'HH:mm:ss' : false}
473
+ onChange={onPickerChange}
474
+ />
475
+ </Popover.Body>
476
+ </Popover.Content>
477
+ </Popover>;
478
+ }
479
+ if (UiGlobals.mode === UI_MODE_REACT_NATIVE) {
480
+ const inputAndTriggerClone = // for RN, this is the actual input and trigger, as we need them to appear up above in the modal
481
+ <Row h={10}>
482
+ <IconButton
483
+ _icon={{
484
+ as: Calendar,
485
+ color: styles.FORM_DATE_ICON_COLOR,
486
+ size: 'sm',
270
487
  }}
271
488
  isDisabled={isDisabled}
272
- numberOfLines={1}
273
- ellipsizeMode="head"
274
- onLayout={(e) => {
275
- // On web, this is not needed, but on RN it might be, so leave it in for now
276
- const {
277
- height,
278
- top,
279
- left,
280
- } = e.nativeEvent.layout;
281
- setTop(top + height);
282
- setLeft(left);
489
+ onPress={() => hidePicker()}
490
+ h="100%"
491
+ w={10}
492
+ borderWidth={1}
493
+ borderColor="#bbb"
494
+ borderLeftRadius="md"
495
+ borderRightWidth={0}
496
+ borderRighttRadius={0}
497
+ bg={styles.FORM_DATE_ICON_BG}
498
+ _hover={{
499
+ bg: styles.FORM_DATE_ICON_BG_HOVER,
283
500
  }}
284
- w={props.w || null}
285
501
  />
286
- {/* <Pressable
287
- flex={1}
288
- h="100%"
289
- onPress={showPicker}
290
- >
502
+ {disableDirectEntry ?
291
503
  <Text
504
+ ref={inputRef}
292
505
  flex={1}
293
506
  h="100%"
294
- ml={1}
295
- p={2}
296
- fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
297
- borderWidth={1}
298
- borderColor="trueGray.300"
299
- borderRadius={4}
300
507
  numberOfLines={1}
301
508
  ellipsizeMode="head"
302
- >{title}</Text>
303
- </Pressable> */}
304
- <Popover
305
- isOpen={isPickerShown}
306
- onClose={() => {
307
- hidePicker();
308
- }}
309
- trigger={emptyFn}
310
- trapFocus={false}
311
- placement={'auto'}
312
- {...props}
313
- >
314
- <Popover.Content
315
- position="absolute"
316
- top={top + 'px'}
317
- left={left + 'px'}
318
- w={width}
319
- h={height}
320
- {...translateProps}
321
- >
322
- <Popover.Body
323
- ref={pickerRef}
324
- p={0}
509
+ m={0}
510
+ p={2}
511
+ borderWidth={1}
512
+ borderColor="trueGray.400"
513
+ borderLeftWidth={0}
514
+ borderLeftRadius={0}
515
+ borderRightRadius="md"
516
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
517
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
518
+ bg={styles.FORM_DATE_INPUT_BG}
519
+ _focus={{
520
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
521
+ }}
522
+ >{textInputValue}</Text> :
523
+ <Input
524
+ ref={inputRef}
525
+ value={textInputValue}
526
+ autoSubmit={true}
527
+ isDisabled={isDisabled}
528
+ onChangeValue={onInputChangeText}
529
+ onKeyPress={onInputKeyPress}
530
+ onFocus={onInputFocus}
531
+ onBlur={onInputBlur}
532
+ flex={1}
533
+ h="100%"
534
+ m={0}
535
+ autoSubmitDelay={1000}
536
+ borderTopRightRadius={0}
537
+ borderBottomRightRadius={0}
538
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
539
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
540
+ bg={styles.FORM_DATE_INPUT_BG}
541
+ _focus={{
542
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
543
+ }}
544
+ placeholder={placeholder}
545
+ {..._input}
546
+ />}
547
+ </Row>;
548
+ dropdownMenu = <Modal
549
+ isOpen={true}
550
+ safeAreaTop={true}
551
+ onClose={() => setIsPickerShown(false)}
552
+ mt="auto"
553
+ mb="auto"
554
+ w="100%"
555
+ h={400}
556
+ p={5}
325
557
  >
326
- <Datetime
558
+ {inputAndTriggerClone}
559
+ {/* <Datetime
327
560
  open={true}
328
561
  input={false}
562
+ mode={mode === DATE ? 'date' : mode === TIME ? 'time' : mode === DATETIME ? 'datetime' : null}
329
563
  closeOnClickOutside={false}
330
564
  value={pickerValue}
331
565
  dateFormat={mode === DATE || mode === DATETIME ? 'YYYY-MM-DD' : false}
332
566
  timeFormat={mode === TIME || mode === DATETIME ? 'HH:mm:ss' : false}
333
567
  onChange={onPickerChange}
334
- />
335
- </Popover.Body>
336
- </Popover.Content>
337
- </Popover>
338
- </Row>
339
- </Tooltip>;
568
+ /> */}
569
+ <Box bg="#fff">
570
+ <Datetime
571
+ selectedStartDate={moment(value).toDate()}
572
+ onDateChange={onPickerChange}
573
+ todayBackgroundColor="#eee"
574
+ selectedDayColor="#f00"
575
+ selectedDayTextColor="#fff"
576
+ />
577
+ </Box>
578
+ </Modal>;
579
+ }
580
+ }
340
581
 
341
- // React Native v1
342
- // return <Row>
343
- // <Icon as={Calendar} />
344
- // <Text>{title}</Text>
345
- // {isPickerShown && <DateTimePicker
346
- // value={value}
347
- // mode={mode}
348
- // display="default"
349
- // onChange={(e, value) => {
350
- // setValue(value);
351
- // }}
352
- // {...propsToPass}
353
- // />}
354
- // </Row>;
582
+ const refProps = {};
583
+ if (tooltipRef) {
584
+ refProps.ref = tooltipRef;
585
+ }
586
+ assembledComponents = <Row {...refProps} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
587
+ {xButton}
588
+ {inputAndTrigger}
589
+ {additionalButtons}
590
+ {dropdownMenu}
591
+ </Row>;
355
592
 
356
- // React Native v2
357
- // return <Box>
358
- // <Button
359
- // leftIcon={<Icon as={Calendar} />}
360
- // onPress={showPicker}
361
- // >{title}</Button>
362
- // <DateTimePickerModal
363
- // isPickerShown={isPickerShown}
364
- // mode="date"
365
- // onConfirm={handleConfirm}
366
- // onCancel={hidePicker}
367
- // {...propsToPass}
368
- // />
369
- // </Box>;
593
+ if (tooltip) {
594
+ assembledComponents = <Tooltip label={tooltip} placement={tooltipPlacement}>
595
+ {assembledComponents}
596
+ </Tooltip>;
597
+ }
598
+
599
+ return assembledComponents;
600
+
370
601
  };
371
602
 
372
603
  export default withComponent(withValue(DateElement));
@@ -17,7 +17,7 @@ export default function withComponent(WrappedComponent) {
17
17
  componentMethods,
18
18
  ...propsToPass
19
19
  } = props,
20
- reference = _.isEmpty(props.reference) ? uuid() : props.reference,
20
+ reference = !_.isEmpty(props.reference) ? props.reference : uuid(),
21
21
  childrenRef = useRef({}),
22
22
  selfRef = useRef({
23
23
  parent,
@@ -70,6 +70,7 @@ export default function withComponent(WrappedComponent) {
70
70
  // parent={parent}
71
71
  self={selfRef.current}
72
72
  {...propsToPass}
73
+ reference={reference}
73
74
  />;
74
75
 
75
76
  };
@@ -265,48 +265,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
265
265
  setIsEditorShown(true);
266
266
  },
267
267
  onRemoteDuplicate = async () => {
268
-
269
- // Call /duplicate on server
270
- const
271
- Model = Repository.getSchema().name,
268
+ const
272
269
  entity = selection[0],
273
- id = entity.id;
274
- const result = await Repository._send('POST', Model + '/duplicate', { id });
275
- if (!result) {
276
- return;
277
- }
278
- const {
279
- root,
280
- success,
281
- total,
282
- message
283
- } = Repository._processServerResponse(result);
284
-
285
- if (!success) {
286
- throw Error(message);
287
- }
288
-
289
- const duplicateId = root.id;
290
-
291
- // TODO: I don't like this.
292
- // Currently, we filter the repository by only the new Entity, then select the entity for editing.
293
- // There is a 2-second delay between filtering and being able to select, and this is unacceptable.
294
- // Why do we filter for just the new entity? Because it's not guaranteed to show up in the grid based on sorting.
295
- // Can't we just manually add this record to the repository at the top and then edit it?
296
-
297
- // Filter the grid with only the duplicate's ID, and open it for editing.
298
- self.filterById(duplicateId, () => { // because of the way useFilters is made, we have to use a callback, not await a Promise.
299
-
300
- // Select the only node
301
- const duplicateEntity = Repository.getById(duplicateId);
302
- setTimeout(() => {
303
- setSelection([duplicateEntity]);
304
-
305
- onEdit();
306
- }, 2000); // we need this delay!
307
-
308
- });
270
+ duplicateEntity = await Repository.remoteDuplicate(entity);
309
271
 
272
+ setSelection([duplicateEntity]);
273
+ onEdit();
310
274
  },
311
275
  onEditorSave = async (data, e) => {
312
276
  const
@@ -2,14 +2,14 @@ import {
2
2
  Row,
3
3
  Spinner,
4
4
  } from 'native-base';
5
- // import ScreenContainer from '../ScreenContainer.js';
5
+ import ScreenContainer from '../Container/ScreenContainer.js';
6
6
 
7
7
  export default function Loading(props) {
8
- // if (props.isScreen) {
9
- // return <ScreenContainer {...props}>
10
- // <Spinner flex={1} color="primary.500" />
11
- // </ScreenContainer>;
12
- // }
8
+ if (props.isScreen) {
9
+ return <ScreenContainer {...props}>
10
+ <Spinner flex={1} color="primary.500" />
11
+ </ScreenContainer>;
12
+ }
13
13
  return <Row justifyContent="center" minHeight={100} {...props}>
14
14
  <Spinner flex={1} color="primary.500" />
15
15
  </Row>;
@@ -1,10 +1,10 @@
1
1
  import * as ImageManipulator from 'expo-image-manipulator';
2
2
 
3
- export default async function processImage(uri) {
3
+ export default async function processImage(uri, width = 1000) {
4
4
  const file = await ImageManipulator.manipulateAsync(uri,
5
5
  [{
6
6
  resize: {
7
- width: 1000
7
+ width,
8
8
  }
9
9
  }],
10
10
  {
@@ -2,6 +2,7 @@ import UiGlobals from '../UiGlobals.js';
2
2
  import Datetime from '../PlatformImports/ReactNative/Datetime';
3
3
  import Draggable from '../PlatformImports/ReactNative/Draggable';
4
4
  import ScreenContainer from '../Components/Container/ScreenContainer';
5
+ import useWindowSize from '../PlatformImports/ReactNative/useWindowSize.js';
5
6
  import _ from 'lodash';
6
7
 
7
8
  export default function registerReactNativeComponents() {
@@ -9,5 +10,6 @@ export default function registerReactNativeComponents() {
9
10
  Datetime,
10
11
  Draggable,
11
12
  ScreenContainer,
13
+ useWindowSize,
12
14
  });
13
15
  }
@@ -4,6 +4,7 @@ import Attachments from '../PlatformImports/Web/Attachments.js';
4
4
  import Datetime from '../PlatformImports/Web/Datetime.js';
5
5
  import Draggable from '../PlatformImports/Web/Draggable.js';
6
6
  // import File from '../PlatformImports/Web/Attachments.js';
7
+ import useWindowSize from '../PlatformImports/Web/useWindowSize.js';
7
8
  import _ from 'lodash';
8
9
 
9
10
  export default function registerWebComponents() {
@@ -13,5 +14,6 @@ export default function registerWebComponents() {
13
14
  Datetime,
14
15
  Draggable,
15
16
  // File,
17
+ useWindowSize,
16
18
  });
17
19
  }
@@ -1,11 +1,13 @@
1
- import useWindowSize from './useWindowSize.js';
1
+ import getComponentFromType from '../Functions/getComponentFromType.js';
2
2
 
3
3
  // This hook takes the submitted window size and adjusts it
4
4
  // to fit the actual screen size
5
5
 
6
6
  export default function(width, height, percentage = 1) {
7
7
 
8
- const windowSize = useWindowSize();
8
+ const
9
+ useWindowSize = getComponentFromType('useWindowSize'),
10
+ windowSize = useWindowSize();
9
11
 
10
12
  if (width > windowSize.width) {
11
13
  width = windowSize.width * percentage;
@@ -1,4 +1,5 @@
1
1
  // import DateTimePickerModal from 'react-native-modal-datetime-picker'; // https://github.com/mmazzarolo/react-native-modal-datetime-picker
2
- import Datetime from '@react-native-community/datetimepicker'; // https://github.com/react-native-datetimepicker/datetimepicker
2
+ // import Datetime from '@react-native-community/datetimepicker'; // https://github.com/react-native-datetimepicker/datetimepicker
3
+ import CalendarPicker from 'react-native-calendar-picker'; // https://www.npmjs.com/package/react-native-calendar-picker
3
4
 
4
- export default Datetime.default || Datetime;
5
+ export default CalendarPicker.default || CalendarPicker;
@@ -0,0 +1,4 @@
1
+ import { useWindowDimensions } from 'react-native';
2
+
3
+ const useWindowSize = useWindowDimensions;
4
+ export default useWindowSize;
@@ -3,7 +3,6 @@
3
3
  import { useLayoutEffect, useState } from 'react';
4
4
  import _ from 'lodash';
5
5
 
6
- // For web only!
7
6
  export default function useWindowSize() {
8
7
  const [windowSize, setWindowSize] = useState({
9
8
  width: window?.innerWidth || 0,