@visns-studio/visns-components 6.24.3 → 6.25.0
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 +4 -2
- package/src/components/Autocomplete.jsx +189 -119
- package/src/components/DataGrid.jsx +472 -31
- package/src/components/Navigation.jsx +475 -51
- package/src/components/auth/ClientAuthFrame.jsx +5 -0
- package/src/components/auth/ClientAuthScreen.jsx +29 -0
- package/src/components/columns/ColumnRenderers.jsx +3 -46
- package/src/components/columns/StackedRow.jsx +186 -0
- package/src/components/controls/DataGridSearch.jsx +110 -2
- package/src/components/controls/DataGridSortSheet.jsx +155 -0
- package/src/components/generic/GenericAuth.jsx +50 -18
- package/src/components/generic/GenericDashboard.jsx +20 -1
- package/src/components/generic/GenericDetail.jsx +446 -259
- package/src/components/mapboxSearchBox.js +640 -0
- package/src/components/navBadges.js +63 -1
- package/src/components/navDrawer.js +147 -0
- package/src/components/sms/SmsThreadPanel.jsx +34 -6
- package/src/components/sms/smsHelpers.js +15 -0
- package/src/components/styles/ClientAuth.module.scss +39 -0
- package/src/components/styles/DataGrid.module.scss +158 -5
- package/src/components/styles/Field.module.scss +52 -1
- package/src/components/styles/Form.module.scss +82 -0
- package/src/components/styles/GenericClientPortal.module.scss +72 -20
- package/src/components/styles/GenericDashboard.module.scss +50 -0
- package/src/components/styles/GenericDetail.module.scss +63 -1
- package/src/components/styles/GenericDynamic.module.scss +23 -0
- package/src/components/styles/GenericFormBuilder.module.scss +11 -0
- package/src/components/styles/GenericIndex.module.scss +6 -1
- package/src/components/styles/Navigation.module.scss +460 -7
- package/src/components/styles/Sms.module.scss +92 -0
- package/src/components/styles/StackedRow.module.scss +182 -0
- package/src/components/styles/TicketConversation.module.scss +76 -0
- package/src/components/styles/Vault.module.scss +192 -0
- package/src/components/styles/density.css +10 -0
- package/src/components/styles/global-datagrid.css +220 -0
- package/src/components/styles/global.css +20 -0
- package/src/components/tickets/TicketConversation.jsx +13 -8
- package/src/components/utils/ConfirmDialog.js +22 -3
- package/src/components/utils/cardLayout.js +666 -0
- package/src/components/utils/contactChannels.js +130 -0
- package/src/components/utils/editPlacement.js +95 -0
- package/src/components/utils/useDensity.js +303 -7
- package/src/index.js +32 -0
|
@@ -47,9 +47,20 @@ import { resolveFormVariant } from '../utils/formLayout';
|
|
|
47
47
|
import { confirmDialog } from './utils/ConfirmDialog';
|
|
48
48
|
import {
|
|
49
49
|
useDensity,
|
|
50
|
+
useNarrowViewport,
|
|
50
51
|
useAvailableHeight,
|
|
52
|
+
useCardRowsEnabled,
|
|
51
53
|
rowsForHeight,
|
|
54
|
+
gridHeightFloor,
|
|
55
|
+
NARROW_COLUMN_MIN_WIDTH,
|
|
52
56
|
} from './utils/useDensity';
|
|
57
|
+
import {
|
|
58
|
+
CARD_COLUMN_NAME,
|
|
59
|
+
MAX_BODY_LINES,
|
|
60
|
+
cardRowHeight,
|
|
61
|
+
deriveCardLayout,
|
|
62
|
+
} from './utils/cardLayout';
|
|
63
|
+
import StackedRow from './columns/StackedRow';
|
|
53
64
|
import { openRecordWindow } from './utils/popupWindow';
|
|
54
65
|
import { DEFAULT_INTELLIGENT_SORTING_CONFIG } from '../utils/relationshipSortingUtils';
|
|
55
66
|
import {
|
|
@@ -1072,7 +1083,145 @@ const DataGrid = forwardRef(
|
|
|
1072
1083
|
// unreliable after auto-refresh on an idle tab (background-throttled
|
|
1073
1084
|
// rAF/timers). A fixed height bypasses measurement entirely and
|
|
1074
1085
|
// prevents action buttons from being clipped.
|
|
1075
|
-
const { grid } = useDensity();
|
|
1086
|
+
const { grid, density } = useDensity();
|
|
1087
|
+
|
|
1088
|
+
// Width, which is a different question from density — see
|
|
1089
|
+
// NARROW_VIEWPORT_QUERY. A phone is both; a browser window dragged to
|
|
1090
|
+
// a third of a monitor is only this one.
|
|
1091
|
+
const isNarrow = useNarrowViewport();
|
|
1092
|
+
|
|
1093
|
+
// ---------------------------------------------------------------
|
|
1094
|
+
// CARD MODE — the same grid, projected onto one synthetic column
|
|
1095
|
+
// ---------------------------------------------------------------
|
|
1096
|
+
//
|
|
1097
|
+
// Below 640px the columns are built exactly as they always are — every
|
|
1098
|
+
// per-type renderer, the trailing Action column, the lot — and then
|
|
1099
|
+
// ONE column is handed to the grid whose `render` draws them stacked.
|
|
1100
|
+
//
|
|
1101
|
+
// This is the whole design decision, and it is what is NOT rewritten
|
|
1102
|
+
// that makes it worth having: `dataSource`, the search debounce, the
|
|
1103
|
+
// `where` clauses, `suppressRepeated`, `setTotal`/`setTableData`,
|
|
1104
|
+
// auto-refresh, selection, the pager and its finished ≤640px CSS all
|
|
1105
|
+
// carry on knowing nothing about any of it. Above all `onRowClick`
|
|
1106
|
+
// resolves a link by CELL INDEX, and the card IS a cell — so a tap on
|
|
1107
|
+
// a card follows the same link the table's first column does, through
|
|
1108
|
+
// the same code.
|
|
1109
|
+
//
|
|
1110
|
+
// A `renderRow` override was the obvious alternative and needs
|
|
1111
|
+
// `rowHeight={null}` plus natural measurement, which is precisely the
|
|
1112
|
+
// configuration `utils/useDensity.js` documents as unreliable on a
|
|
1113
|
+
// coarse pointer. A sibling card list re-owns skip/limit/sortInfo/
|
|
1114
|
+
// filterValue and becomes the second fetch path.
|
|
1115
|
+
const cardRowsEnabled = useCardRowsEnabled();
|
|
1116
|
+
const cardOverrides = tableSetting?.cardLayout;
|
|
1117
|
+
|
|
1118
|
+
const cardMapping = useMemo(() => {
|
|
1119
|
+
// Precedence, highest first: a view that says no; a view that
|
|
1120
|
+
// supplies its own mapping (which opts itself in, whatever the
|
|
1121
|
+
// app-wide attribute says); the app-wide attribute.
|
|
1122
|
+
if (cardOverrides === false) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const viewOptedIn =
|
|
1127
|
+
cardOverrides !== null &&
|
|
1128
|
+
cardOverrides !== undefined &&
|
|
1129
|
+
typeof cardOverrides === 'object';
|
|
1130
|
+
|
|
1131
|
+
if (!cardRowsEnabled && !viewOptedIn) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// A grouped grid draws bucket headers between its rows and the
|
|
1136
|
+
// card knows nothing about them. Hard off rather than half-right.
|
|
1137
|
+
if (ajaxSetting?.groupBy) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return deriveCardLayout(columns, {
|
|
1142
|
+
overrides: cardOverrides,
|
|
1143
|
+
maxLines:
|
|
1144
|
+
density === 'large'
|
|
1145
|
+
? MAX_BODY_LINES.large
|
|
1146
|
+
: MAX_BODY_LINES.default,
|
|
1147
|
+
log: debugLog,
|
|
1148
|
+
});
|
|
1149
|
+
}, [
|
|
1150
|
+
columns,
|
|
1151
|
+
cardOverrides,
|
|
1152
|
+
cardRowsEnabled,
|
|
1153
|
+
ajaxSetting?.groupBy,
|
|
1154
|
+
density,
|
|
1155
|
+
]);
|
|
1156
|
+
|
|
1157
|
+
// The reader's own escape hatch, per grid and for this session only.
|
|
1158
|
+
// Nothing about card mode is a preference worth persisting to a
|
|
1159
|
+
// profile: it answers "I need the filter row for the next minute", and
|
|
1160
|
+
// a choice that outlives the tab is a choice somebody has to undo on
|
|
1161
|
+
// a device they are no longer holding.
|
|
1162
|
+
const cardPreferenceKey = `visns:cardRows:${ajaxSetting?.url ?? ''}`;
|
|
1163
|
+
const [cardPreference, setCardPreference] = useState('');
|
|
1164
|
+
|
|
1165
|
+
useEffect(() => {
|
|
1166
|
+
let stored = '';
|
|
1167
|
+
|
|
1168
|
+
try {
|
|
1169
|
+
stored =
|
|
1170
|
+
window.sessionStorage.getItem(cardPreferenceKey) || '';
|
|
1171
|
+
} catch (unavailable) {
|
|
1172
|
+
// Private browsing, or storage disabled. The default stands.
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
setCardPreference(stored);
|
|
1176
|
+
}, [cardPreferenceKey]);
|
|
1177
|
+
|
|
1178
|
+
const chooseCardPreference = useCallback(
|
|
1179
|
+
(next) => {
|
|
1180
|
+
setCardPreference(next);
|
|
1181
|
+
|
|
1182
|
+
try {
|
|
1183
|
+
window.sessionStorage.setItem(cardPreferenceKey, next);
|
|
1184
|
+
} catch (unavailable) {
|
|
1185
|
+
// The choice still holds for as long as the grid is mounted.
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
[cardPreferenceKey]
|
|
1189
|
+
);
|
|
1190
|
+
|
|
1191
|
+
const cardCapable = isNarrow && Boolean(cardMapping);
|
|
1192
|
+
const cardMode = cardCapable && cardPreference !== 'table';
|
|
1193
|
+
|
|
1194
|
+
// `tableSetting` is SPREAD onto the datagrid, so every key a view puts
|
|
1195
|
+
// in it becomes a prop. `cardLayout` is ours and means nothing to the
|
|
1196
|
+
// toolkit; leaving it in the spread hands ReactDataGrid an unknown
|
|
1197
|
+
// prop, and unknown props on this component end up on the DOM node as
|
|
1198
|
+
// React attribute warnings.
|
|
1199
|
+
const gridTableSetting = useMemo(() => {
|
|
1200
|
+
if (!tableSetting || tableSetting.cardLayout === undefined) {
|
|
1201
|
+
return tableSetting;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const { cardLayout, ...rest } = tableSetting;
|
|
1205
|
+
|
|
1206
|
+
return rest;
|
|
1207
|
+
}, [tableSetting]);
|
|
1208
|
+
|
|
1209
|
+
// Every row is this tall, computed from the mapping rather than
|
|
1210
|
+
// measured — see `cardRowHeight`. It reaches the grid in FOUR places
|
|
1211
|
+
// and all four have to agree: `rowHeight`, `minRowHeight`, the page
|
|
1212
|
+
// size (`rowsForHeight`, or the pager reads "Page 1 of 236") and the
|
|
1213
|
+
// height floor's own pitch.
|
|
1214
|
+
const cardHeight = useMemo(() => {
|
|
1215
|
+
if (!cardMode) {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const hasActions =
|
|
1220
|
+
(memoizedSettings?.length ?? 0) > 0 ||
|
|
1221
|
+
(cardMapping?.actionColumns?.length ?? 0) > 0;
|
|
1222
|
+
|
|
1223
|
+
return cardRowHeight(cardMapping, { density, hasActions });
|
|
1224
|
+
}, [cardMode, cardMapping, memoizedSettings, density]);
|
|
1076
1225
|
|
|
1077
1226
|
// ---------------------------------------------------------------
|
|
1078
1227
|
// Per-column autoGrow
|
|
@@ -3123,9 +3272,27 @@ const DataGrid = forwardRef(
|
|
|
3123
3272
|
})),
|
|
3124
3273
|
});
|
|
3125
3274
|
|
|
3275
|
+
// "The last column is the row-action column; its icons handle
|
|
3276
|
+
// their own clicks, so a click on the column itself is not a
|
|
3277
|
+
// row click." THE RULE IS POSITIONAL AND THE POSITION IS NOT
|
|
3278
|
+
// ALWAYS THE ACTION COLUMN: in card mode the columns are
|
|
3279
|
+
// `[__checkbox-column, __card]`, the card IS last, and every
|
|
3280
|
+
// tap on a card would be swallowed here.
|
|
3281
|
+
//
|
|
3282
|
+
// The exemption names the card rather than re-testing the
|
|
3283
|
+
// trailing column for `name === 'setting'`, which is what it
|
|
3284
|
+
// ought to be. A grid whose config carries no `settings` gets
|
|
3285
|
+
// no trailing action column at all, so its last DATA column is
|
|
3286
|
+
// the one this branch currently ignores — making that column
|
|
3287
|
+
// clickable is a behaviour change across three applications,
|
|
3288
|
+
// and this pass has no reason to make it. Above 640px nothing
|
|
3289
|
+
// reaches here differently than it did.
|
|
3290
|
+
const clickedColumn = rowProps.columns[columnIndex];
|
|
3291
|
+
|
|
3126
3292
|
if (
|
|
3127
3293
|
rowProps.columns.length > 1 &&
|
|
3128
|
-
columnIndex === rowProps.columns.length - 1
|
|
3294
|
+
columnIndex === rowProps.columns.length - 1 &&
|
|
3295
|
+
clickedColumn?.name !== CARD_COLUMN_NAME
|
|
3129
3296
|
) {
|
|
3130
3297
|
debugLog('🔍 Last column clicked - no action');
|
|
3131
3298
|
// Handle last column click
|
|
@@ -4286,13 +4453,27 @@ const DataGrid = forwardRef(
|
|
|
4286
4453
|
// ("dd / mm / yyyy" in Firefox, glyph included -
|
|
4287
4454
|
// Firefox will not hide it); below ~160px the
|
|
4288
4455
|
// native input clips its own value.
|
|
4456
|
+
// On a narrow viewport the floor rises to
|
|
4457
|
+
// NARROW_COLUMN_MIN_WIDTH, because 80px is where
|
|
4458
|
+
// seven flex columns land on a phone and 80px is
|
|
4459
|
+
// an ellipsis, not a column — every header on
|
|
4460
|
+
// /contacts rendered as "FI…", "LA…", "TI…". The
|
|
4461
|
+
// columns then total more than the screen and the
|
|
4462
|
+
// grid's own body scrolls sideways, which it is
|
|
4463
|
+
// already built to do; the page does not move.
|
|
4464
|
+
//
|
|
4465
|
+
// A view's own `column.minWidth` still wins, so a
|
|
4466
|
+
// column that really is narrow stays narrow.
|
|
4289
4467
|
minWidth:
|
|
4290
4468
|
column.minWidth ??
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4469
|
+
Math.max(
|
|
4470
|
+
isNarrow ? NARROW_COLUMN_MIN_WIDTH : 0,
|
|
4471
|
+
['date', 'datetime', 'datetime-local'].includes(
|
|
4472
|
+
column.type
|
|
4473
|
+
)
|
|
4474
|
+
? Math.max(grid.columnMinWidth, 160)
|
|
4475
|
+
: grid.columnMinWidth
|
|
4476
|
+
),
|
|
4296
4477
|
maxWidth: column.maxWidth ?? undefined,
|
|
4297
4478
|
textAlign: column.textAlign ?? undefined,
|
|
4298
4479
|
textVerticalAlign:
|
|
@@ -5424,7 +5605,131 @@ const DataGrid = forwardRef(
|
|
|
5424
5605
|
});
|
|
5425
5606
|
}
|
|
5426
5607
|
|
|
5427
|
-
|
|
5608
|
+
// CARD MODE — hand the grid ONE column that draws the rest.
|
|
5609
|
+
//
|
|
5610
|
+
// Every descriptor above still exists and is still the only
|
|
5611
|
+
// thing that formats a value; the card calls them. That is
|
|
5612
|
+
// the point of building the full array first and throwing
|
|
5613
|
+
// away nothing but the LAYOUT.
|
|
5614
|
+
//
|
|
5615
|
+
// The filter defaults below are computed and set either
|
|
5616
|
+
// way, and they still reach the server: the datagrid's
|
|
5617
|
+
// `getFilterValueForColumns` is a `map`, not a `filter`, so
|
|
5618
|
+
// an entry whose column is not mounted passes through
|
|
5619
|
+
// untouched. A view whose Stage filter defaults to "active
|
|
5620
|
+
// deals only" opens on active deals here as well.
|
|
5621
|
+
if (cardMode && cardMapping) {
|
|
5622
|
+
// `newColumns` is `columns.map(...)` with the Action
|
|
5623
|
+
// column pushed after it, so index i of one is index i
|
|
5624
|
+
// of the other for every config column.
|
|
5625
|
+
const builtByColumn = new Map(
|
|
5626
|
+
columns.map((column, index) => [
|
|
5627
|
+
column,
|
|
5628
|
+
newColumns[index],
|
|
5629
|
+
])
|
|
5630
|
+
);
|
|
5631
|
+
|
|
5632
|
+
const valueOf = (column, row) => {
|
|
5633
|
+
const path = Array.isArray(column.id)
|
|
5634
|
+
? column.id
|
|
5635
|
+
: String(column.id ?? '').split('.');
|
|
5636
|
+
|
|
5637
|
+
return path.reduce(
|
|
5638
|
+
(value, segment) =>
|
|
5639
|
+
value === null || value === undefined
|
|
5640
|
+
? value
|
|
5641
|
+
: value[segment],
|
|
5642
|
+
row
|
|
5643
|
+
);
|
|
5644
|
+
};
|
|
5645
|
+
|
|
5646
|
+
const renderCardCell = (column, row) => {
|
|
5647
|
+
const built = builtByColumn.get(column);
|
|
5648
|
+
|
|
5649
|
+
if (!built || typeof built.render !== 'function') {
|
|
5650
|
+
return null;
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5653
|
+
return built.render({
|
|
5654
|
+
data: row,
|
|
5655
|
+
value: valueOf(column, row),
|
|
5656
|
+
column: built,
|
|
5657
|
+
});
|
|
5658
|
+
};
|
|
5659
|
+
|
|
5660
|
+
// The rail, and it is the trailing Action column's own
|
|
5661
|
+
// render restated — same `splitSettingEntries`, same
|
|
5662
|
+
// exclusion of settings an action column already draws,
|
|
5663
|
+
// same role and per-row gating inside `renderSetting`.
|
|
5664
|
+
// Both entry points already stopPropagation, which is
|
|
5665
|
+
// what makes an icon safe inside a clickable card.
|
|
5666
|
+
const renderCardActions = (row) => {
|
|
5667
|
+
if (row?.__treeParent) {
|
|
5668
|
+
return null;
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5671
|
+
const { inlineActions, standard } =
|
|
5672
|
+
splitSettingEntries(memoizedSettings);
|
|
5673
|
+
|
|
5674
|
+
const controls = [
|
|
5675
|
+
...inlineActions.map((config) =>
|
|
5676
|
+
renderRowAction(config, row)
|
|
5677
|
+
),
|
|
5678
|
+
...standard
|
|
5679
|
+
.filter(
|
|
5680
|
+
(setting) =>
|
|
5681
|
+
!actionColumnSettingIds.has(
|
|
5682
|
+
setting.id
|
|
5683
|
+
)
|
|
5684
|
+
)
|
|
5685
|
+
.map((setting) =>
|
|
5686
|
+
renderSetting(setting, row)
|
|
5687
|
+
),
|
|
5688
|
+
...cardMapping.actionColumns.map((column) =>
|
|
5689
|
+
renderCardCell(column, row)
|
|
5690
|
+
),
|
|
5691
|
+
].filter(Boolean);
|
|
5692
|
+
|
|
5693
|
+
return controls.length > 0 ? controls : null;
|
|
5694
|
+
};
|
|
5695
|
+
|
|
5696
|
+
setGridColumns([
|
|
5697
|
+
{
|
|
5698
|
+
name: CARD_COLUMN_NAME,
|
|
5699
|
+
header: '',
|
|
5700
|
+
sortable: false,
|
|
5701
|
+
defaultFlex: 1,
|
|
5702
|
+
// Lands on the CELL, which is what the ≤640px
|
|
5703
|
+
// block in global-datagrid.css needs: the
|
|
5704
|
+
// checkbox column is a cell too and must keep
|
|
5705
|
+
// the grid's padding and centring.
|
|
5706
|
+
className: 'datagrid-card-cell',
|
|
5707
|
+
// The NARROW floor is deliberately NOT applied.
|
|
5708
|
+
// It exists to stop seven flex columns
|
|
5709
|
+
// collapsing to an ellipsis each; one flex
|
|
5710
|
+
// column has no such problem, and a 160px floor
|
|
5711
|
+
// on it is how a card would find a horizontal
|
|
5712
|
+
// scrollbar it has no use for.
|
|
5713
|
+
minWidth: 0,
|
|
5714
|
+
// Inherited verbatim from whichever column the
|
|
5715
|
+
// mapping decided the row follows, so
|
|
5716
|
+
// `onRowClick` runs the same navigate /
|
|
5717
|
+
// window.open / openRecordWindow cascade it
|
|
5718
|
+
// runs for the table's first cell.
|
|
5719
|
+
link: cardMapping.link ?? {},
|
|
5720
|
+
render: ({ data }) => (
|
|
5721
|
+
<StackedRow
|
|
5722
|
+
row={data}
|
|
5723
|
+
mapping={cardMapping}
|
|
5724
|
+
renderCell={renderCardCell}
|
|
5725
|
+
renderActions={renderCardActions}
|
|
5726
|
+
/>
|
|
5727
|
+
),
|
|
5728
|
+
},
|
|
5729
|
+
]);
|
|
5730
|
+
} else {
|
|
5731
|
+
setGridColumns(newColumns);
|
|
5732
|
+
}
|
|
5428
5733
|
|
|
5429
5734
|
const newFilterValue = columns
|
|
5430
5735
|
.filter((column) => column.filter && column.filter.type) // Only consider columns with a filter type
|
|
@@ -5511,7 +5816,22 @@ const DataGrid = forwardRef(
|
|
|
5511
5816
|
.catch((error) => {
|
|
5512
5817
|
console.error('Error in fetching dropdown data: ', error);
|
|
5513
5818
|
});
|
|
5514
|
-
|
|
5819
|
+
// `isNarrow` is in here because the column floor is built from it:
|
|
5820
|
+
// without it, rotating a phone or dragging a window across the
|
|
5821
|
+
// 640px line keeps the widths the grid was first built with.
|
|
5822
|
+
//
|
|
5823
|
+
// `cardMode` and `cardMapping` ride along for the same reason —
|
|
5824
|
+
// crossing 640px, or a reader pressing Table, has to rebuild the
|
|
5825
|
+
// column array, not just restyle it.
|
|
5826
|
+
}, [
|
|
5827
|
+
columns,
|
|
5828
|
+
filterDataSource,
|
|
5829
|
+
memoizedSettings,
|
|
5830
|
+
grid,
|
|
5831
|
+
isNarrow,
|
|
5832
|
+
cardMode,
|
|
5833
|
+
cardMapping,
|
|
5834
|
+
]);
|
|
5515
5835
|
|
|
5516
5836
|
// Force re-render when filterDataSource updates to ensure dropdown filters get their data
|
|
5517
5837
|
const [renderKey, setRenderKey] = useState(0);
|
|
@@ -5692,11 +6012,38 @@ const DataGrid = forwardRef(
|
|
|
5692
6012
|
return el ? el.getBoundingClientRect().height : 0;
|
|
5693
6013
|
};
|
|
5694
6014
|
|
|
6015
|
+
// TWO BUGS LIVED IN THIS SUM, AND THEY CANCELLED.
|
|
6016
|
+
//
|
|
6017
|
+
// `.InovuaReactDataGrid__header-wrapper` ALREADY CONTAINS the
|
|
6018
|
+
// filter row, so adding the filter wrapper counted it twice. And
|
|
6019
|
+
// the pager is `.inovua-react-pagination-toolbar` — the toolkit's
|
|
6020
|
+
// own toolbar component, with no grid prefix on it — so
|
|
6021
|
+
// `.InovuaReactDataGrid__pagination-toolbar` matched nothing and
|
|
6022
|
+
// the pager was never counted at all.
|
|
6023
|
+
//
|
|
6024
|
+
// On /contacts both errors are the same 41px and the total came
|
|
6025
|
+
// out right by accident. They stop cancelling the moment the two
|
|
6026
|
+
// differ, which is any density change: the filter row steps to
|
|
6027
|
+
// 32px under a coarse pointer while the pager stays 41.
|
|
6028
|
+
//
|
|
6029
|
+
// So: take the header wrapper WHOLE when it is there, and only
|
|
6030
|
+
// fall back to summing the column-header and filter rows when it
|
|
6031
|
+
// is not.
|
|
6032
|
+
const headerWrapper = heightOf(
|
|
6033
|
+
'.InovuaReactDataGrid__header-wrapper'
|
|
6034
|
+
);
|
|
6035
|
+
const header =
|
|
6036
|
+
headerWrapper > 0
|
|
6037
|
+
? headerWrapper
|
|
6038
|
+
: heightOf('.InovuaReactDataGrid__column-header') +
|
|
6039
|
+
heightOf(
|
|
6040
|
+
'.InovuaReactDataGrid__column-header__filter-wrapper'
|
|
6041
|
+
);
|
|
6042
|
+
|
|
5695
6043
|
const chrome =
|
|
5696
|
-
|
|
5697
|
-
heightOf('.InovuaReactDataGrid__column-header__filter-wrapper') +
|
|
6044
|
+
header +
|
|
5698
6045
|
heightOf('.InovuaReactDataGrid__footer-wrapper, .InovuaReactDataGrid__footer') +
|
|
5699
|
-
heightOf('.
|
|
6046
|
+
heightOf('.inovua-react-pagination-toolbar');
|
|
5700
6047
|
|
|
5701
6048
|
return chrome > 0 ? chrome : null;
|
|
5702
6049
|
};
|
|
@@ -5758,14 +6105,33 @@ const DataGrid = forwardRef(
|
|
|
5758
6105
|
? resolvedHeight
|
|
5759
6106
|
: fitToRows(dataCount, window.innerHeight);
|
|
5760
6107
|
|
|
6108
|
+
// The chrome and the row pitch, in the one place that reads both.
|
|
6109
|
+
// The pitch rule is `contentHeight`'s, restated here rather than
|
|
6110
|
+
// duplicated in spirit: measurement first, floored by the density
|
|
6111
|
+
// unit and then by ROW_PITCH_FLOOR, so a stale or unavailable
|
|
6112
|
+
// reading can only ever over-allocate.
|
|
6113
|
+
//
|
|
6114
|
+
// In card mode the pitch is the CARD's height, not the density
|
|
6115
|
+
// unit — a card is 84–122px against a rowUnit of 52, and costing
|
|
6116
|
+
// five of them at 52 sizes the body less than half what it needs.
|
|
6117
|
+
// It is floored the same way for the same reason: measurement
|
|
6118
|
+
// reads the previous render.
|
|
6119
|
+
const gridMetrics = () => ({
|
|
6120
|
+
chrome: measureGridChrome() ?? GRID_CHROME,
|
|
6121
|
+
pitch: Math.max(
|
|
6122
|
+
measureRowPitch() ?? 0,
|
|
6123
|
+
cardHeight ?? 0,
|
|
6124
|
+
grid.rowUnit ?? 0,
|
|
6125
|
+
ROW_PITCH_FLOOR
|
|
6126
|
+
),
|
|
6127
|
+
});
|
|
6128
|
+
|
|
5761
6129
|
// A window-sized grid asks for as many rows as fit — 37 on a 1440p
|
|
5762
6130
|
// screen. A table holding four of them rendered the other 33 as
|
|
5763
6131
|
// empty body, which is the single most dated thing about an index
|
|
5764
6132
|
// page: a thousand pixels of nothing under the last row, with the
|
|
5765
6133
|
// pager stranded at the bottom of it.
|
|
5766
6134
|
const contentHeight = () => {
|
|
5767
|
-
const chrome = measureGridChrome() ?? GRID_CHROME;
|
|
5768
|
-
|
|
5769
6135
|
// ERR HIGH, DELIBERATELY.
|
|
5770
6136
|
//
|
|
5771
6137
|
// The two errors here are not symmetrical. Over-allocating
|
|
@@ -5777,12 +6143,11 @@ const DataGrid = forwardRef(
|
|
|
5777
6143
|
// use; measured, rows render at 44px against a rowUnit of 32.
|
|
5778
6144
|
// Measurement is preferred when it is available, but it reads
|
|
5779
6145
|
// the DOM and the DOM is not always there when this runs — so
|
|
5780
|
-
// there is a floor under whatever it returns.
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
);
|
|
6146
|
+
// there is a floor under whatever it returns. That is
|
|
6147
|
+
// `gridMetrics`, shared with the floor below: the ceiling and
|
|
6148
|
+
// the floor costing a row differently is how a grid ends up
|
|
6149
|
+
// asking for rows it has no room to draw.
|
|
6150
|
+
const { chrome, pitch } = gridMetrics();
|
|
5786
6151
|
|
|
5787
6152
|
const rows = Math.max(dataCount || 0, MIN_VISIBLE_ROWS);
|
|
5788
6153
|
|
|
@@ -5801,7 +6166,28 @@ const DataGrid = forwardRef(
|
|
|
5801
6166
|
// being expressed, not a measurement. Honouring it literally put
|
|
5802
6167
|
// ~975px of empty body under the two rows on a customer's Sites
|
|
5803
6168
|
// tab.
|
|
5804
|
-
|
|
6169
|
+
// ...and a FLOOR under the result, because a ceiling alone let the
|
|
6170
|
+
// grid be drawn shorter than its own chrome. See `gridHeightFloor`
|
|
6171
|
+
// in useDensity for the measurements; the short version is that a
|
|
6172
|
+
// phone leaves ~130px under the stat strip and the grid's header,
|
|
6173
|
+
// filter row and pager need 123 of it, so the body got seven
|
|
6174
|
+
// pixels and painted nothing. `contentHeight()` already carries
|
|
6175
|
+
// MIN_VISIBLE_ROWS, but it only ever reaches the grid through a
|
|
6176
|
+
// `Math.min`, so it could make the grid shorter and never taller.
|
|
6177
|
+
//
|
|
6178
|
+
// Below the floor the grid overflows its slot and the PAGE
|
|
6179
|
+
// scrolls, which is what a page whose content does not fit is
|
|
6180
|
+
// supposed to do.
|
|
6181
|
+
const floorFor = () => {
|
|
6182
|
+
const { chrome, pitch } = gridMetrics();
|
|
6183
|
+
|
|
6184
|
+
return gridHeightFloor(chrome, pitch, MIN_VISIBLE_ROWS);
|
|
6185
|
+
};
|
|
6186
|
+
|
|
6187
|
+
const renderedHeight = Math.max(
|
|
6188
|
+
Math.min(availableHeight, contentHeight()),
|
|
6189
|
+
floorFor()
|
|
6190
|
+
);
|
|
5805
6191
|
|
|
5806
6192
|
// Re-measure ONCE MORE after the browser has painted.
|
|
5807
6193
|
//
|
|
@@ -5816,7 +6202,10 @@ const DataGrid = forwardRef(
|
|
|
5816
6202
|
// stops it looping: it only writes when the corrected height
|
|
5817
6203
|
// actually differs.
|
|
5818
6204
|
const frame = requestAnimationFrame(() => {
|
|
5819
|
-
const corrected = Math.
|
|
6205
|
+
const corrected = Math.max(
|
|
6206
|
+
Math.min(availableHeight, contentHeight()),
|
|
6207
|
+
floorFor()
|
|
6208
|
+
);
|
|
5820
6209
|
|
|
5821
6210
|
if (Math.abs(corrected - renderedHeight) > 1) {
|
|
5822
6211
|
setGridStyle((prev) =>
|
|
@@ -5833,8 +6222,20 @@ const DataGrid = forwardRef(
|
|
|
5833
6222
|
height: renderedHeight,
|
|
5834
6223
|
}));
|
|
5835
6224
|
|
|
5836
|
-
// Everything below sizes the REQUEST, so it reads availableHeight
|
|
5837
|
-
|
|
6225
|
+
// Everything below sizes the REQUEST, so it reads availableHeight
|
|
6226
|
+
// — floored to the same minimum the grid is DRAWN at.
|
|
6227
|
+
//
|
|
6228
|
+
// Without the floor the two disagree in the worst possible
|
|
6229
|
+
// direction: the pager on a phone read "Page 1 of 236" for 236
|
|
6230
|
+
// contacts, because `rowsForHeight(130, 123, 52)` is
|
|
6231
|
+
// `max(1, floor(7 / 52))` — one contact per page, 236 pages of
|
|
6232
|
+
// them. The floor is applied here rather than swapping in
|
|
6233
|
+
// `renderedHeight` because `renderedHeight` follows the CONTENT,
|
|
6234
|
+
// and sizing the request from it is the feedback loop the comment
|
|
6235
|
+
// above warns about: a short table shrinks the grid, a shorter
|
|
6236
|
+
// grid asks for fewer rows, and it can never grow back to find out
|
|
6237
|
+
// there was more data.
|
|
6238
|
+
const minHeight = Math.max(availableHeight, floorFor());
|
|
5838
6239
|
|
|
5839
6240
|
// How many rows to ask for.
|
|
5840
6241
|
//
|
|
@@ -5886,7 +6287,14 @@ const DataGrid = forwardRef(
|
|
|
5886
6287
|
// first pass, before the grid exists to be measured.
|
|
5887
6288
|
const chrome = measureGridChrome() ?? GRID_CHROME;
|
|
5888
6289
|
|
|
5889
|
-
|
|
6290
|
+
// THE UNIT, NOT THE DENSITY UNIT, in card mode. `rowUnit` is
|
|
6291
|
+
// 52 under a coarse pointer and a card is 122; asking for
|
|
6292
|
+
// rowsForHeight(…, 52) on a phone requests twelve rows into a
|
|
6293
|
+
// body with room for five, which is the "Page 1 of 236" shape
|
|
6294
|
+
// of bug the floor above was added to fix at the other end.
|
|
6295
|
+
setLimit(
|
|
6296
|
+
rowsForHeight(minHeight, chrome, cardHeight ?? grid.rowUnit)
|
|
6297
|
+
);
|
|
5890
6298
|
}
|
|
5891
6299
|
return () => cancelAnimationFrame(frame);
|
|
5892
6300
|
}, [
|
|
@@ -5895,6 +6303,7 @@ const DataGrid = forwardRef(
|
|
|
5895
6303
|
resolvedHeight,
|
|
5896
6304
|
window.innerHeight,
|
|
5897
6305
|
grid,
|
|
6306
|
+
cardHeight,
|
|
5898
6307
|
ajaxSetting?.take,
|
|
5899
6308
|
userProfile?.rows_per_page,
|
|
5900
6309
|
userProfile?.settings?.rows_per_page,
|
|
@@ -6001,6 +6410,17 @@ const DataGrid = forwardRef(
|
|
|
6001
6410
|
pageData={pageData}
|
|
6002
6411
|
pageSetting={pageSetting}
|
|
6003
6412
|
onBulkUploadClick={handleBulkUploadClick}
|
|
6413
|
+
cardCapable={cardCapable}
|
|
6414
|
+
cardMode={cardMode}
|
|
6415
|
+
onCardModeChange={chooseCardPreference}
|
|
6416
|
+
columns={columns}
|
|
6417
|
+
columnsMetadata={columnsMetadata}
|
|
6418
|
+
sortBy={ajaxSetting?.sortBy ?? null}
|
|
6419
|
+
sort={ajaxSetting?.sort ?? null}
|
|
6420
|
+
// The SAME function a column header calls, so
|
|
6421
|
+
// there is one sort implementation and the
|
|
6422
|
+
// sheet cannot drift from it.
|
|
6423
|
+
onSort={handleSort}
|
|
6004
6424
|
/>
|
|
6005
6425
|
</div>
|
|
6006
6426
|
<div className={styles.dataGridTopRightContainer}>
|
|
@@ -6063,7 +6483,7 @@ const DataGrid = forwardRef(
|
|
|
6063
6483
|
key={`datagrid-${ajaxSetting?.url || ''}-${
|
|
6064
6484
|
ajaxSetting?.groupBy ? 'grouped' : 'ungrouped'
|
|
6065
6485
|
}-${groupRenderKey}`}
|
|
6066
|
-
{...
|
|
6486
|
+
{...gridTableSetting}
|
|
6067
6487
|
columns={gridColumns}
|
|
6068
6488
|
dataSource={dataSource}
|
|
6069
6489
|
{...(ajaxSetting && ajaxSetting.groupBy
|
|
@@ -6591,7 +7011,7 @@ const DataGrid = forwardRef(
|
|
|
6591
7011
|
}
|
|
6592
7012
|
headerProps={headerProps}
|
|
6593
7013
|
idProperty="id"
|
|
6594
|
-
minRowHeight={grid.minRowHeight}
|
|
7014
|
+
minRowHeight={cardHeight ?? grid.minRowHeight}
|
|
6595
7015
|
onFilterValueChange={(fv) => {
|
|
6596
7016
|
handleFilterChange(fv, filterValue);
|
|
6597
7017
|
}}
|
|
@@ -6608,7 +7028,19 @@ const DataGrid = forwardRef(
|
|
|
6608
7028
|
// need — measurement is unreliable in a
|
|
6609
7029
|
// background-throttled tab and clips action
|
|
6610
7030
|
// buttons. See the density table in useDensity.
|
|
6611
|
-
|
|
7031
|
+
//
|
|
7032
|
+
// A card is always a number, computed per view
|
|
7033
|
+
// from its mapping. Same argument, more force: the
|
|
7034
|
+
// one platform card mode runs on is the one this
|
|
7035
|
+
// comment says not to trust measurement on.
|
|
7036
|
+
rowHeight={cardHeight ?? grid.rowHeight}
|
|
7037
|
+
// Header AND filter row in one prop: the header
|
|
7038
|
+
// wrapper returns null for the whole thing, and
|
|
7039
|
+
// a column header over a card is a header for a
|
|
7040
|
+
// column that does not exist. See the Sort control
|
|
7041
|
+
// in DataGridSearch for what replaces the header's
|
|
7042
|
+
// other job.
|
|
7043
|
+
{...(cardMode ? { showHeader: false } : {})}
|
|
6612
7044
|
rowStyle={getRowStyle}
|
|
6613
7045
|
selected={selected}
|
|
6614
7046
|
showZebraRows={true}
|
|
@@ -6629,11 +7061,20 @@ const DataGrid = forwardRef(
|
|
|
6629
7061
|
border: '1px solid #d1d5db',
|
|
6630
7062
|
overflow: 'hidden',
|
|
6631
7063
|
}}
|
|
6632
|
-
className={
|
|
7064
|
+
className={[
|
|
6633
7065
|
ajaxSetting?.groupBy
|
|
6634
7066
|
? 'InovuaReactDataGrid--grouped datagrid-with-grouping datagrid-fixed-group-headers'
|
|
6635
|
-
: 'datagrid-without-grouping'
|
|
6636
|
-
|
|
7067
|
+
: 'datagrid-without-grouping',
|
|
7068
|
+
// The hook the ≤640px block in
|
|
7069
|
+
// global-datagrid.css needs to strip the cell's
|
|
7070
|
+
// padding and its right border — a card owns
|
|
7071
|
+
// its own gutters, and a cell divider down the
|
|
7072
|
+
// right of a full-width row is a line to
|
|
7073
|
+
// nowhere.
|
|
7074
|
+
cardMode ? 'datagrid-card-mode' : '',
|
|
7075
|
+
]
|
|
7076
|
+
.filter(Boolean)
|
|
7077
|
+
.join(' ')}
|
|
6637
7078
|
// The real prop is `checkboxColumn` (bool | column
|
|
6638
7079
|
// object) — {...tableSetting} spreads the view's
|
|
6639
7080
|
// `checkboxColumn: true` and this object, placed
|