@onehat/ui 0.3.238 → 0.3.241

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.238",
3
+ "version": "0.3.241",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState, useEffect, useRef, } from 'react';
2
2
  import {
3
3
  TextArea,
4
4
  } from 'native-base';
@@ -10,12 +10,49 @@ import _ from 'lodash';
10
10
 
11
11
  const
12
12
  TextAreaElement = (props) => {
13
- const
13
+ let { // so localValue can be changed, if needed
14
+ setValue,
15
+ autoSubmit = true, // automatically setValue after user stops typing for autoSubmitDelay
16
+ autoSubmitDelay = UiGlobals.autoSubmitDelay,
17
+ onChangeText,
18
+ } = props,
19
+ value = _.isNil(props.value) ? '' : props.value, // null value may not actually reset this TextArea, so set it explicitly to empty string
14
20
  styles = UiGlobals.styles,
15
- value = _.isNil(props.value) ? '' : props.value; // null value may not actually reset this TextArea, so set it explicitly to empty string
21
+ debouncedSetValueRef = useRef(),
22
+ [localValue, setLocalValue] = useState(value),
23
+ onChangeTextLocal = (value) => {
24
+ if (value === '') {
25
+ value = null; // empty string makes value null
26
+ }
27
+ setLocalValue(value);
28
+ if (autoSubmit) {
29
+ debouncedSetValueRef.current(value);
30
+ }
31
+ if (onChangeText) {
32
+ onChangeText(value);
33
+ }
34
+ };
35
+
36
+ useEffect(() => {
37
+ // Set up debounce fn
38
+ // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
39
+ debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
40
+ }, [setValue]);
41
+
42
+ useEffect(() => {
43
+
44
+ // Make local value conform to externally changed value
45
+ setLocalValue(value);
46
+
47
+ }, [value]);
48
+
49
+ if (localValue === null || typeof localValue === 'undefined') {
50
+ localValue = ''; // If the value is null or undefined, don't let this be an uncontrolled input
51
+ }
52
+
16
53
  return <TextArea
17
54
  ref={props.outerRef}
18
- onChangeText={props.setValue}
55
+ onChangeText={onChangeTextLocal}
19
56
  flex={1}
20
57
  bg={styles.FORM_TEXTAREA_BG}
21
58
  _focus={{
@@ -24,7 +61,7 @@ const
24
61
  fontSize={styles.FORM_TEXTAREA_FONTSIZE}
25
62
  h={styles.FORM_TEXTAREA_HEIGHT}
26
63
  {...props}
27
- value={value}
64
+ value={localValue}
28
65
  />;
29
66
  },
30
67
  TextAreaField = withComponent(withValue(TextAreaElement));
@@ -193,6 +193,7 @@ function Form(props) {
193
193
  _.each(columnsConfig, (config, ix) => {
194
194
  let {
195
195
  fieldName,
196
+ editField,
196
197
  isEditable,
197
198
  editor,
198
199
  renderer,
@@ -211,9 +212,10 @@ function Form(props) {
211
212
  <Text numberOfLines={1} ellipsizeMode="head">{renderedValue}</Text>
212
213
  </Box>);
213
214
  } else {
215
+ const fieldToEdit = editField || fieldName;
214
216
  elements.push(<Controller
215
217
  key={'controller-' + ix}
216
- name={fieldName}
218
+ name={fieldToEdit}
217
219
  // rules={rules}
218
220
  control={control}
219
221
  render={(args) => {
@@ -236,8 +238,8 @@ function Form(props) {
236
238
  } = fieldState;
237
239
  let editorProps = {};
238
240
  if (!editor) {
239
- const propertyDef = fieldName && Repository?.getSchema().getPropertyDefinition(fieldName);
240
- editor = propertyDef && propertyDef[fieldName].editorType;
241
+ const propertyDef = fieldToEdit && Repository?.getSchema().getPropertyDefinition(fieldToEdit);
242
+ editor = propertyDef?.editorType;
241
243
  if (_.isPlainObject(editor)) {
242
244
  const {
243
245
  type,
@@ -248,6 +250,13 @@ function Form(props) {
248
250
  editor = type;
249
251
  }
250
252
  }
253
+ if (!editor) {
254
+ editor = 'Text';
255
+ }
256
+ if (!editor.match(/Toggle/)) {
257
+ editorProps.h = '40px'; // Toggle height gets applied incorrectly; just skip it
258
+ }
259
+
251
260
  const Element = getComponentFromType(editor);
252
261
 
253
262
  if (useSelectorId) {
@@ -258,21 +267,25 @@ function Form(props) {
258
267
  let element = <Element
259
268
  name={name}
260
269
  value={value}
261
- setValue={(newValue) => {
270
+ onChangeValue={(newValue) => {
271
+ if (newValue === undefined) {
272
+ newValue = null; // React Hook Form doesn't respond well when setting value to undefined
273
+ }
262
274
  onChange(newValue);
263
- if (onEditorChange) {
275
+ if (typeof onEditorChange !== 'undefined' && onEditorChange) {
264
276
  onEditorChange(newValue, formSetValue, formGetValues, formState);
265
277
  }
266
278
  }}
267
279
  onBlur={onBlur}
268
280
  flex={1}
269
- {...editorProps}
270
281
  parent={self}
271
- reference={fieldName}
272
- // {...defaults}
273
- // {...propsToPass}
282
+ reference={fieldToEdit}
283
+ {...editorProps}
274
284
  />;
275
285
 
286
+ if (editor.match(/Tag/)) {
287
+ columnProps.overflow = 'auto';
288
+ }
276
289
  // element = <Tooltip key={ix} label={header} placement="bottom">
277
290
  // {element}
278
291
  // </Tooltip>;
@@ -516,22 +529,20 @@ function Form(props) {
516
529
  {...editorTypeProps}
517
530
  {...dynamicProps}
518
531
  />;
519
- if (editorType !== EDITOR_TYPE__INLINE) {
520
- let message = null;
521
- if (error) {
522
- message = error.message;
523
- if (label && error.ref?.name) {
524
- message = message.replace(error.ref.name, label);
525
- }
532
+ let message = null;
533
+ if (error) {
534
+ message = error.message;
535
+ if (label && error.ref?.name) {
536
+ message = message.replace(error.ref.name, label);
526
537
  }
527
- if (message) {
528
- message = <Text color="#f00">{message}</Text>;
529
- }
530
- element = <Column pt={1} flex={1}>
531
- {element}
532
- {message}
533
- </Column>;
534
538
  }
539
+ if (message) {
540
+ message = <Text color="#f00">{message}</Text>;
541
+ }
542
+ element = <Column pt={1} flex={1}>
543
+ {element}
544
+ {message}
545
+ </Column>;
535
546
 
536
547
  if (item.additionalEditButtons) {
537
548
  const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
@@ -830,9 +841,17 @@ function Form(props) {
830
841
  }
831
842
 
832
843
  if (editorType === EDITOR_TYPE__INLINE) {
833
- buttonGroupProps.position = 'fixed';
834
- buttonGroupProps.left = 10; // TODO: I would prefer to have this be centered, but it's a lot more complex than just making it stick to the left
835
- footerProps.alignItems = 'flex-start';
844
+ footerProps.position = 'sticky';
845
+ footerProps.alignSelf = 'flex-start';
846
+ footerProps.justifyContent = 'center';
847
+ footerProps.top = '100px';
848
+ footerProps.left = '20px';
849
+ footerProps.width = '200px';
850
+ footerProps.bg = 'primary.100';
851
+ footerProps.p = 0;
852
+ footerProps.px = 4;
853
+ footerProps.py = 2;
854
+ footerProps.borderBottomRadius = 5;
836
855
  }
837
856
 
838
857
  if (onDelete && editorMode === EDITOR_MODE__EDIT && isSingle) {
@@ -868,11 +887,11 @@ function Form(props) {
868
887
  }
869
888
  }
870
889
 
871
- return <Column {...sizeProps} onLayout={onLayoutDecorated} ref={formRef}>
890
+ return <Column {...sizeProps} onLayout={onLayoutDecorated} ref={formRef} testID="form">
872
891
  {!!containerWidth && <>
873
892
  {editorType === EDITOR_TYPE__INLINE &&
874
- <ScrollView
875
- horizontal={true}
893
+ <Row
894
+ display="inline-block"
876
895
  flex={1}
877
896
  bg="#fff"
878
897
  py={1}
@@ -880,14 +899,14 @@ function Form(props) {
880
899
  borderBottomWidth={5}
881
900
  borderTopColor="primary.100"
882
901
  borderBottomColor="primary.100"
883
- >{editor}</ScrollView>}
902
+ >{editor}</Row>}
884
903
  {editorType !== EDITOR_TYPE__INLINE &&
885
904
  <ScrollView _web={{ minHeight, }} width="100%" pb={1}>
886
905
  {formButtons}
887
906
  {editor}
888
907
  </ScrollView>}
889
908
 
890
- <Footer justifyContent="flex-end" {...footerProps} {...savingProps}>
909
+ <Footer justifyContent="flex-end" {...footerProps} {...savingProps}>
891
910
  {onDelete && editorMode === EDITOR_MODE__EDIT && isSingle &&
892
911
 
893
912
  <Row flex={1} justifyContent="flex-start">
@@ -44,6 +44,7 @@ import withMultiSelection from '../Hoc/withMultiSelection.js';
44
44
  import withSelection from '../Hoc/withSelection.js';
45
45
  import withWindowedEditor from '../Hoc/withWindowedEditor.js';
46
46
  import withInlineEditor from '../Hoc/withInlineEditor.js';
47
+ import withInlineSideEditor from '../Hoc/withInlineSideEditor.js';
47
48
  import getSaved from '../../Functions/getSaved.js';
48
49
  import setSaved from '../../Functions/setSaved.js';
49
50
  import getIconButtonFromConfig from '../../Functions/getIconButtonFromConfig.js';
@@ -1155,4 +1156,29 @@ export const InlineGridEditor = withComponent(
1155
1156
  )
1156
1157
  );
1157
1158
 
1159
+ // export const InlineSideGridEditor = withComponent(
1160
+ // withAlert(
1161
+ // withEvents(
1162
+ // withData(
1163
+ // withDropTarget(
1164
+ // withMultiSelection(
1165
+ // withSelection(
1166
+ // withInlineSideEditor(
1167
+ // withFilters(
1168
+ // withPresetButtons(
1169
+ // withContextMenu(
1170
+ // GridComponent
1171
+ // )
1172
+ // ),
1173
+ // true // isGrid
1174
+ // )
1175
+ // )
1176
+ // )
1177
+ // )
1178
+ // )
1179
+ // )
1180
+ // )
1181
+ // )
1182
+ // );
1183
+
1158
1184
  export default Grid;
@@ -31,10 +31,10 @@ function withAdditionalProps(WrappedComponent) {
31
31
  }
32
32
 
33
33
  // NOTE: Effectivtly, the HOC composition is:
34
- // withAdditionalProps(withEditor(withWindowedEditor))
34
+ // withAdditionalProps(withEditor(withInlineEditor))
35
35
 
36
- export default function withInlineEditor(WrappedComponent) {
37
- return withAdditionalProps(withEditor((props) => {
36
+ export default function withInlineEditor(WrappedComponent, skipWrappers = false) {
37
+ const InlineEditor = (props) => {
38
38
  const {
39
39
  editorType,
40
40
  isEditorShown = false,
@@ -82,11 +82,17 @@ export default function withInlineEditor(WrappedComponent) {
82
82
  editorBounds = editor.parentElement.getBoundingClientRect(), // reference parentElement, because this doesn't change based on last moveEditor call
83
83
  delta = editorBounds.top - rowBounds.top;
84
84
 
85
- editorStyle.top = (-1 * delta) -20 + 'px';
85
+ editorStyle.top = (-1 * delta) + 'px';
86
86
  };
87
+
88
+ useEffect(() => {
89
+ if (maskRef.current) {
90
+ maskRef.current.focus();
91
+ }
92
+ }, [isEditorShown]);
87
93
 
88
94
  if (isEditorShown && selection.length < 1) {
89
- throw new Error('Lost the selection!');
95
+ return null; // phantom record may have just been deleted
90
96
  }
91
97
  if (isEditorShown && selection.length !== 1) {
92
98
  throw new Error('Can only edit one at a time with inline editor!');
@@ -100,57 +106,59 @@ export default function withInlineEditor(WrappedComponent) {
100
106
  inlineEditor = <>
101
107
  {isEditorShown && <Box
102
108
  ref={maskRef}
109
+ testID="mask"
103
110
  position="fixed"
104
111
  w="100vw"
105
112
  h="100vh"
106
113
  top="0"
107
114
  left="0"
108
115
  bg="#000"
109
- opacity={0.35}
116
+ opacity={0.3}
110
117
  zIndex={0}
111
118
  onClick={(e) => {
112
119
  e.preventDefault();
113
120
  e.stopPropagation();
114
121
  onEditorCancel();
115
122
  }}
123
+ tabIndex={-1}
124
+ onKeyDown={(e) => {
125
+ if (e.key === 'Escape') {
126
+ onEditorCancel();
127
+ }
128
+ }}
116
129
  ></Box>}
117
130
  <Column
118
131
  ref={inlineEditorRef}
119
132
  position="absolute"
120
133
  zIndex={10}
134
+ testID="inline-editor"
135
+ h={isEditorShown ? '100px' : 0}
136
+ minWidth="100%"
137
+ display="inline-block"
138
+ whiteSpace="nowrap"
121
139
  >
122
- {isEditorShown && <Form
123
- parent={self}
124
- reference="form"
125
- editorType={EDITOR_TYPE__INLINE}
126
- editorStateRef={editorStateRef}
127
- record={selection[0]}
128
- Repository={Repository}
129
- isMultiple={selection.length > 1}
130
- isEditorViewOnly={isEditorViewOnly}
131
- columnsConfig={localColumnsConfig}
132
- onCancel={onEditorCancel}
133
- onSave={onEditorSave}
134
- onClose={onEditorClose}
135
- footerProps={{
136
- justifyContent: 'center',
137
- bg: null, // make bg transparent
138
- p: 0,
139
- }}
140
- buttonGroupProps={{
141
- bg: 'primary.100',
142
- borderBottomRadius: 5,
143
- px: 4,
144
- py: 2,
145
- }}
146
- bg="#fff"
147
- borderTopWidth={4}
148
- borderTopColor={styles.GRID_INLINE_EDITOR_BORDER_COLOR}
149
- borderBottomWidth={4}
150
- borderBottomColor={styles.GRID_INLINE_EDITOR_BORDER_COLOR}
151
- py={1}
152
- px={0}
153
- />}
140
+ {isEditorShown &&
141
+ <Form
142
+ parent={self}
143
+ reference="form"
144
+ editorType={EDITOR_TYPE__INLINE}
145
+ editorStateRef={editorStateRef}
146
+ record={selection[0]}
147
+ Repository={Repository}
148
+ isMultiple={selection.length > 1}
149
+ isEditorViewOnly={isEditorViewOnly}
150
+ columnsConfig={localColumnsConfig}
151
+ onCancel={onEditorCancel}
152
+ onSave={onEditorSave}
153
+ onClose={onEditorClose}
154
+ bg="#fff"
155
+ borderTopWidth={4}
156
+ borderTopColor={styles.GRID_INLINE_EDITOR_BORDER_COLOR}
157
+ borderBottomWidth={4}
158
+ borderBottomColor={styles.GRID_INLINE_EDITOR_BORDER_COLOR}
159
+ py={1}
160
+ px={0}
161
+ />}
154
162
  </Column>
155
163
  </>;
156
164
  }
@@ -162,5 +170,9 @@ export default function withInlineEditor(WrappedComponent) {
162
170
  inlineEditor={inlineEditor}
163
171
  isInlineEditorShown={isEditorShown}
164
172
  />;
165
- }));
173
+ };
174
+ if (skipWrappers) {
175
+ return InlineEditor; // this is for InlineSideEditor, not yet implemented
176
+ }
177
+ return withAdditionalProps(withEditor(InlineEditor));
166
178
  }
@@ -0,0 +1,9 @@
1
+
2
+ import withInlineEditor from './withInlineEditor.js';
3
+ import withSideEditor from './withSideEditor.js';
4
+
5
+
6
+ export default function withInlineSideEditor(WrappedComponent) {
7
+ throw Error('Not yet implemented');
8
+ return withSideEditor(withInlineEditor(WrappedComponent, true));
9
+ }
@@ -20,7 +20,7 @@ function withAdditionalProps(WrappedComponent) {
20
20
  // withAdditionalProps(withEditor(withSideEditor))
21
21
 
22
22
  export default function withSideEditor(WrappedComponent, isTree = false) {
23
- return withAdditionalProps(withEditor((props) => {
23
+ const SideEditor = (props) => {
24
24
  const {
25
25
  Editor,
26
26
  editorProps = {},
@@ -57,5 +57,6 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
57
57
  reference="editor"
58
58
  />}
59
59
  />;
60
- }));
60
+ };
61
+ return withAdditionalProps(withEditor(SideEditor));
61
62
  }
@@ -42,7 +42,7 @@ function withAdditionalProps(WrappedComponent) {
42
42
  // withAdditionalProps(withEditor(withWindowedEditor))
43
43
 
44
44
  export default function withWindowedEditor(WrappedComponent, isTree = false) {
45
- return withAdditionalProps(withEditor((props) => {
45
+ const WindowedEditor = (props) => {
46
46
  const {
47
47
  isEditorShown = false,
48
48
  setIsEditorShown,
@@ -81,5 +81,6 @@ export default function withWindowedEditor(WrappedComponent, isTree = false) {
81
81
  />
82
82
  </Modal>}
83
83
  </>;
84
- }, isTree));
84
+ };
85
+ return withAdditionalProps(withEditor(WindowedEditor, isTree));
85
86
  }
@@ -1,6 +1,7 @@
1
1
  export const EDITOR_TYPE__INLINE = 'EDITOR_TYPE__INLINE';
2
2
  export const EDITOR_TYPE__WINDOWED = 'EDITOR_TYPE__WINDOWED';
3
3
  export const EDITOR_TYPE__SIDE = 'EDITOR_TYPE__SIDE';
4
+ export const EDITOR_TYPE__INLINE_SIDE = 'EDITOR_TYPE__INLINE_SIDE';
4
5
  export const EDITOR_TYPE__SMART = 'EDITOR_TYPE__SMART';
5
6
  export const EDITOR_TYPE__PLAIN = 'EDITOR_TYPE__PLAIN';
6
7
  export const EDITOR_MODE__VIEW = 'EDITOR_MODE__VIEW';