@onehat/ui 0.4.115 → 0.4.116

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.4.115",
3
+ "version": "0.4.116",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -115,6 +115,7 @@ function Container(props) {
115
115
  southRef = useRef(null),
116
116
  eastRef = useRef(null),
117
117
  westRef = useRef(null),
118
+ centerWidthRef = useRef(null), // used to store the width of the center area, which is used to calculate max widths for east and west
118
119
  northHeightRef = useRef(northInitialHeight),
119
120
  southHeightRef = useRef(southInitialHeight),
120
121
  eastWidthRef = useRef(eastInitialWidth),
@@ -202,6 +203,12 @@ function Container(props) {
202
203
  localEastIsCollapsedRef.current = bool;
203
204
  }
204
205
 
206
+ if (!bool) {
207
+ setSideWidth('east', getEastWidth() ?? eastInitialWidth, {
208
+ ignoreCollapsedCheck: true,
209
+ });
210
+ }
211
+
205
212
  if (id) {
206
213
  setSaved(id + '-eastIsCollapsed', bool);
207
214
  }
@@ -220,6 +227,12 @@ function Container(props) {
220
227
  localWestIsCollapsedRef.current = bool;
221
228
  }
222
229
 
230
+ if (!bool) {
231
+ setSideWidth('west', getWestWidth() ?? westInitialWidth, {
232
+ ignoreCollapsedCheck: true,
233
+ });
234
+ }
235
+
223
236
  if (id) {
224
237
  setSaved(id + '-westIsCollapsed', bool);
225
238
  }
@@ -253,24 +266,76 @@ function Container(props) {
253
266
  getSouthHeight = () => {
254
267
  return southHeightRef.current;
255
268
  },
256
- setEastWidth = (width) => {
257
- if (!getEastIsCollapsed()) {
258
- eastWidthRef.current = width;
269
+ setCenterWidth = (width) => {
270
+ centerWidthRef.current = width;
271
+ },
272
+ getCenterWidth = () => {
273
+ return centerWidthRef.current;
274
+ },
275
+ getMaxSideWidth = () => {
276
+ const width = getCenterWidth();
277
+ if (!_.isFinite(width) || width <= 0) {
278
+ return null;
279
+ }
280
+
281
+ return width;
282
+ },
283
+ clampSideWidth = (width) => {
284
+ if (_.isNil(width)) {
285
+ return width;
286
+ }
287
+
288
+ const maxSideWidth = getMaxSideWidth();
289
+ if (_.isNil(maxSideWidth)) {
290
+ return width;
291
+ }
292
+
293
+ return Math.min(width, maxSideWidth);
294
+ },
295
+ setSideWidth = (side, width, opts = {}) => {
296
+ const
297
+ {
298
+ ignoreCollapsedCheck = false,
299
+ } = opts,
300
+ isCollapsed = side === 'east' ? getEastIsCollapsed() : getWestIsCollapsed();
301
+
302
+ if (!ignoreCollapsedCheck && isCollapsed) {
303
+ return;
304
+ }
305
+
306
+ const clampedWidth = clampSideWidth(width);
307
+ if (side === 'east') {
308
+ eastWidthRef.current = clampedWidth;
259
309
  if (id) {
260
- setSaved(id + '-eastWidth', width);
310
+ setSaved(id + '-eastWidth', clampedWidth);
261
311
  }
312
+ } else {
313
+ westWidthRef.current = clampedWidth;
314
+ if (id) {
315
+ setSaved(id + '-westWidth', clampedWidth);
316
+ }
317
+ }
318
+ },
319
+ normalizeSideWidthForRender = (width, initialWidth) => {
320
+ if (_.isNil(width)) {
321
+ return width;
262
322
  }
323
+
324
+ const maxSideWidth = getMaxSideWidth();
325
+ if (_.isNil(maxSideWidth)) {
326
+ return _.isFinite(initialWidth) && initialWidth > 0 ? initialWidth : width;
327
+ }
328
+
329
+ return Math.min(width, maxSideWidth);
330
+ },
331
+ setEastWidth = (width) => {
332
+ setSideWidth('east', width);
263
333
  },
264
334
  getEastWidth = () => {
265
335
  return eastWidthRef.current;
266
336
  },
267
337
  setWestWidth = (width) => {
268
- if (!getWestIsCollapsed()) {
269
- westWidthRef.current = width;
270
- if (id) {
271
- setSaved(id + '-westWidth', width);
272
- }
273
- }
338
+ setSideWidth('west', width);
274
339
  },
275
340
  getWestWidth = () => {
276
341
  return westWidthRef.current;
@@ -409,6 +474,7 @@ function Container(props) {
409
474
  if (!isReady) {
410
475
  return null;
411
476
  }
477
+
412
478
 
413
479
  let componentProps = { _panel: { ...center?.props?._panel }, },
414
480
  wrapperProps = null,
@@ -533,11 +599,17 @@ function Container(props) {
533
599
  width: 33,
534
600
  };
535
601
  } else {
536
- const eastWidth = getEastWidth();
602
+ const eastWidth = normalizeSideWidthForRender(getEastWidth(), eastInitialWidth);
537
603
  if (_.isNil(eastWidth)) {
538
- wrapperProps.style = { flex: eastInitialFlex || 50, };
604
+ wrapperProps.style = {
605
+ flex: eastInitialFlex || 50,
606
+ maxWidth: '100%',
607
+ };
539
608
  } else {
540
- wrapperProps.style = { width: eastWidth, };
609
+ wrapperProps.style = {
610
+ width: eastWidth,
611
+ maxWidth: '100%',
612
+ };
541
613
  }
542
614
  }
543
615
  componentProps._panel.collapseDirection = HORIZONTAL;
@@ -576,11 +648,17 @@ function Container(props) {
576
648
  width: 33,
577
649
  };
578
650
  } else {
579
- const westWidth = getWestWidth();
651
+ const westWidth = normalizeSideWidthForRender(getWestWidth(), westInitialWidth);
580
652
  if (_.isNil(westWidth)) {
581
- wrapperProps.style = { flex: westInitialFlex || 50, };
653
+ wrapperProps.style = {
654
+ flex: westInitialFlex || 50,
655
+ maxWidth: '100%',
656
+ };
582
657
  } else {
583
- wrapperProps.style = { width: westWidth, };
658
+ wrapperProps.style = {
659
+ width: westWidth,
660
+ maxWidth: '100%',
661
+ };
584
662
  }
585
663
  }
586
664
  componentProps._panel.collapseDirection = HORIZONTAL;
@@ -600,13 +678,31 @@ function Container(props) {
600
678
  {cloneElement(west, componentProps)}
601
679
  </BoxNative>;
602
680
  }
603
- return <VStack className="Container-all w-full flex-1">
681
+ return <VStack className="Container-all flex-1 min-w-0">
604
682
  {northComponent}
605
683
  {!getNorthIsCollapsed() && northSplitter}
606
- <HStack className="Container-mid w-full flex-[100]">
684
+ <HStack
685
+ className="Container-mid w-full flex-[100] min-w-0"
686
+ onLayout={(e) => {
687
+ // Measure available horizontal space for side panels.
688
+ const width = parseFloat(e.nativeEvent.layout.width);
689
+ if (width && width !== getCenterWidth()) {
690
+ // Save latest width and clamp east/west if they exceed it.
691
+ setCenterWidth(width);
692
+ if (getEastWidth() > width) {
693
+ setEastWidth(width);
694
+ }
695
+ if (getWestWidth() > width) {
696
+ setWestWidth(width);
697
+ }
698
+ // Trigger a render so updated widths are applied immediately.
699
+ forceUpdate();
700
+ }
701
+ }}
702
+ >
607
703
  {westComponent}
608
704
  {!getWestIsCollapsed() && westSplitter}
609
- <VStack className="Container-center h-full overflow-auto flex-[100]">
705
+ <VStack className="Container-center h-full overflow-auto flex-[100] min-w-0">
610
706
  {centerComponent}
611
707
  </VStack>
612
708
  {!getEastIsCollapsed() && eastSplitter}
@@ -262,6 +262,7 @@ function Form(props) {
262
262
  context: { isPhantom },
263
263
  }),
264
264
  currentEditorMode = getEditorMode(),
265
+ resolvedEditorMode = currentEditorMode || props.editorMode || null,
265
266
  buildFromColumnsConfig = () => {
266
267
  // Only used in InlineEditor
267
268
  // Build the fields that match the current columnsConfig in the grid
@@ -1162,6 +1163,21 @@ function Form(props) {
1162
1163
  onReset(values, formSetValue, formGetValues, trigger);
1163
1164
  }
1164
1165
  },
1166
+ resetFromRecord = () => {
1167
+ if (!record || _.isArray(record) || record.isDestroyed) {
1168
+ return;
1169
+ }
1170
+
1171
+ const
1172
+ currentValues = formGetValues(),
1173
+ recordValues = record.submitValues || record,
1174
+ nextValues = {
1175
+ ...currentValues,
1176
+ ..._.pick(recordValues, Object.keys(currentValues)),
1177
+ };
1178
+
1179
+ doReset(nextValues);
1180
+ },
1165
1181
  onSaveDecorated = async (data, e) => {
1166
1182
  // reset the form after a save
1167
1183
  const result = await onSave(data, e);
@@ -1226,6 +1242,21 @@ function Form(props) {
1226
1242
  }
1227
1243
  }, [record]);
1228
1244
 
1245
+ useEffect(() => {
1246
+ // If this form is bound to a single record, reset the form whenever that record emits a 'reload' event.
1247
+ // This ensures the form stays in sync with the latest data for that record.
1248
+
1249
+ if (skipAll || !isSingle || !record?.on || !record?.off) {
1250
+ return;
1251
+ }
1252
+
1253
+ record.on('reload', resetFromRecord);
1254
+
1255
+ return () => {
1256
+ record.off('reload', resetFromRecord);
1257
+ };
1258
+ }, [record, skipAll, isSingle]);
1259
+
1229
1260
  useEffect(() => {
1230
1261
  if (skipAll) {
1231
1262
  return;
@@ -1350,10 +1381,18 @@ function Form(props) {
1350
1381
  showCancelBtn = false,
1351
1382
  showSaveBtn = false,
1352
1383
  showSubmitBtn = false,
1353
- isAddMode = getEditorMode() === EDITOR_MODE__ADD,
1354
- isEditableMode =
1355
- getEditorMode() === EDITOR_MODE__ADD ||
1356
- getEditorMode() === EDITOR_MODE__EDIT;
1384
+ isAddMode = resolvedEditorMode === EDITOR_MODE__ADD,
1385
+ isEditableMode = (() => {
1386
+ // Keep explicit editor modes authoritative, but preserve legacy modal behavior:
1387
+ // if no mode is supplied, treat forms with onSave as editable so Save can render.
1388
+ if (resolvedEditorMode === EDITOR_MODE__ADD || resolvedEditorMode === EDITOR_MODE__EDIT) {
1389
+ return true;
1390
+ }
1391
+ if (resolvedEditorMode === EDITOR_MODE__VIEW) {
1392
+ return false;
1393
+ }
1394
+ return !!onSave;
1395
+ })();
1357
1396
  if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
1358
1397
 
1359
1398
  // create editor
@@ -1378,7 +1417,7 @@ function Form(props) {
1378
1417
  additionalButtons = buildAdditionalButtons(additionalEditButtons);
1379
1418
 
1380
1419
  if (inArray(editorType, [EDITOR_TYPE__SIDE, EDITOR_TYPE__SMART, EDITOR_TYPE__WINDOWED]) &&
1381
- isSingle && getEditorMode() === EDITOR_MODE__EDIT &&
1420
+ isSingle && resolvedEditorMode === EDITOR_MODE__EDIT &&
1382
1421
  (onBack || onViewMode || isEditorModeControlledByParent)) {
1383
1422
  modeHeader = <Toolbar>
1384
1423
  <HStack className="flex-1 items-center">
@@ -1414,7 +1453,7 @@ function Form(props) {
1414
1453
  />}
1415
1454
  </Toolbar>;
1416
1455
  }
1417
- if (getEditorMode() === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
1456
+ if (resolvedEditorMode === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
1418
1457
  formButtons = <Toolbar className="justify-end flex-wrap gap-2">
1419
1458
  {additionalButtons}
1420
1459
  </Toolbar>;
@@ -1439,7 +1478,7 @@ function Form(props) {
1439
1478
  if (_.isEmpty(formState.dirtyFields) && !isPhantom && !isAddMode) {
1440
1479
  isSaveDisabled = true;
1441
1480
  }
1442
- if (onDelete && getEditorMode() === EDITOR_MODE__EDIT && isSingle) {
1481
+ if (onDelete && resolvedEditorMode === EDITOR_MODE__EDIT && isSingle) {
1443
1482
  showDeleteBtn = true;
1444
1483
  }
1445
1484
  if (!isEditorViewOnly && isEditableMode && !hideResetButton) {
@@ -93,7 +93,7 @@ function FileCardCustom(props) {
93
93
  onPress={() => {
94
94
  downloadInBackground(downloadUrl);
95
95
  }}
96
- className="Pressable px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
96
+ className="Pressable max-w-full px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
97
97
  >
98
98
  {isDownloading &&
99
99
  <Spinner className="mr-2" />}
@@ -103,7 +103,7 @@ function FileCardCustom(props) {
103
103
  icon={Eye}
104
104
  onPress={() => onSee(id)}
105
105
  />}
106
- <Text>{filename}</Text>
106
+ <Text className="max-w-[220px] truncate">{filename}</Text>
107
107
  {onDelete &&
108
108
  <IconButton
109
109
  className="ml-1"
@@ -868,7 +868,9 @@ function AttachmentsElement(props) {
868
868
  className={clsx(
869
869
  'AttachmentsElement-HStack',
870
870
  'flex-1',
871
+ 'w-full',
871
872
  'min-h-0',
873
+ 'min-w-0',
872
874
  'overflow-y-auto',
873
875
  'gap-2',
874
876
  'flex-wrap',
@@ -908,7 +910,7 @@ function AttachmentsElement(props) {
908
910
 
909
911
  return <Box
910
912
  key={file.id}
911
- className="mr-2"
913
+ className="mr-2 min-w-0 max-w-full"
912
914
  >
913
915
  {useFileMosaic &&
914
916
  <DraggableFileMosaic
@@ -1142,7 +1144,7 @@ function AttachmentsElement(props) {
1142
1144
  className: '!hidden',
1143
1145
  deleteFiles: false,
1144
1146
  }}
1145
- className="attachments-dropzone flex-1 h-full" // Add flex classes to ensure full height
1147
+ className="attachments-dropzone flex-1 h-full min-w-0 overflow-x-hidden" // Keep horizontal containment while allowing vertical scrolling
1146
1148
  onUploadStart={onUploadStart}
1147
1149
  onUploadFinish={onUploadFinish}
1148
1150
  background={styles.ATTACHMENTS_BG}
@@ -1285,6 +1287,8 @@ function AttachmentsElement(props) {
1285
1287
  'AttachmentsElement',
1286
1288
  'testx',
1287
1289
  'w-full',
1290
+ 'min-w-0',
1291
+ 'overflow-x-hidden',
1288
1292
  'h-[400px]',
1289
1293
  'border-2',
1290
1294
  'rounded-[5px]',
@@ -1,14 +1,19 @@
1
- html, body {
2
- /* -webkit-user-select: none;
3
- user-select: none; */
4
- }
5
-
6
1
  /* to fix the inline editor */
7
2
  [data-testid="gridContainer"] > [data-testid="ScrollView"] > div {
8
3
  height: 100%;
9
4
  }
10
5
 
11
6
  /* Custom styles for attachments dropzone */
7
+ .attachments-dropzone {
8
+ width: 100%;
9
+ min-width: 0;
10
+ overflow-x: hidden;
11
+ }
12
+
12
13
  .attachments-dropzone .files-ui-dropzone-children-container {
13
14
  padding: 0 !important;
15
+ width: 100%;
16
+ min-width: 0;
17
+ min-height: 0;
18
+ overflow-x: hidden;
14
19
  }