pcm-shared-components 2.1.391 → 2.1.393
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { TextField, Icon, IconButton, Button, Paper, Typography, InputAdornment, MenuItem, ToggleButton, ToggleButtonGroup, Checkbox, ListItemText, Chip, Alert, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material';
|
|
4
4
|
import { ThemeProvider } from '@mui/system';
|
|
@@ -54,6 +54,22 @@ const REQUIRED_ERROR_KEY_BY_TYPE = {
|
|
|
54
54
|
yes_no: 'animal.required_error_yes_no'
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
// A native <input type="date"> only opens its calendar popup on its own when the browser's tiny
|
|
58
|
+
// built-in icon is clicked -- clicking anywhere else in the field just places the text caret
|
|
59
|
+
// (still lets someone type the year/month/day directly, which showPicker() doesn't change). Most
|
|
60
|
+
// people expect clicking anywhere in the box to open the calendar. showPicker() is Chrome/Edge/
|
|
61
|
+
// Firefox only as of this writing (not Safari) and throws if called on a disabled/read-only input
|
|
62
|
+
// or outside a user gesture, so this fails silently rather than breaking the click -- on an
|
|
63
|
+
// unsupported browser, the field just falls back to manual typing and the browser's own
|
|
64
|
+
// icon-click affordance, exactly as before.
|
|
65
|
+
const openNativeDatePicker = e => {
|
|
66
|
+
try {
|
|
67
|
+
e.target.showPicker?.();
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore -- unsupported browser or transient DOM state, typing/icon-click still work
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
57
73
|
/**
|
|
58
74
|
* Tab-per-item editor for a lot's animal or vehicle detail fields: one tab per item (green
|
|
59
75
|
* check when every required field is answered, amber warning triangle otherwise), an "Add"
|
|
@@ -179,6 +195,42 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
179
195
|
setPendingFocusIndex(idx);
|
|
180
196
|
};
|
|
181
197
|
const selectedItem = items[selectedIndex];
|
|
198
|
+
|
|
199
|
+
// Ref registry for each field's label element, keyed by field.key -- rebuilt every render (a
|
|
200
|
+
// field can be removed/reordered), read by the layout effect below to measure whether a label
|
|
201
|
+
// actually wrapped onto a second line at its current column width.
|
|
202
|
+
const labelRefs = useRef(new Map());
|
|
203
|
+
const [wrappedKeys, setWrappedKeys] = useState(() => new Set());
|
|
204
|
+
|
|
205
|
+
// A label long enough to wrap onto a second line at half-column width throws that field's
|
|
206
|
+
// control out of vertical alignment with whatever ends up paired next to it -- a one-line label
|
|
207
|
+
// and a two-line label start their inputs at different heights even though both cells are the
|
|
208
|
+
// same width. Admin-typed labels are free text up to 100 characters of wildly different letter
|
|
209
|
+
// widths, so a character-count cutoff would either wrongly wrap a short-but-wide label or miss a
|
|
210
|
+
// long-but-narrow one; this measures the ACTUAL rendered height of every label after each paint
|
|
211
|
+
// instead and promotes any field whose label wrapped to full width too, same treatment as a
|
|
212
|
+
// type-forced or orphaned field.
|
|
213
|
+
//
|
|
214
|
+
// Re-runs whenever the wrapped set itself changes (not just when `fieldConfig` changes), because
|
|
215
|
+
// promoting one field to full width re-pairs its neighbors, which can occasionally make a
|
|
216
|
+
// DIFFERENT field's label newly wrap (or stop wrapping) at its new column width. Safe from an
|
|
217
|
+
// infinite loop: promotions only ever add width, never remove it, so this converges in at most a
|
|
218
|
+
// few passes, and the state setter bails out (no re-render) the moment a pass finds no change.
|
|
219
|
+
useLayoutEffect(() => {
|
|
220
|
+
const nextWrapped = new Set();
|
|
221
|
+
labelRefs.current.forEach((el, fieldKey) => {
|
|
222
|
+
if (!el) return;
|
|
223
|
+
const lineHeight = parseFloat(getComputedStyle(el).lineHeight) || 0;
|
|
224
|
+
if (lineHeight > 0 && el.offsetHeight > lineHeight * 1.4) {
|
|
225
|
+
nextWrapped.add(fieldKey);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
setWrappedKeys(prev => {
|
|
229
|
+
if (prev.size === nextWrapped.size && [...prev].every(k => nextWrapped.has(k))) return prev;
|
|
230
|
+
return nextWrapped;
|
|
231
|
+
});
|
|
232
|
+
}, [fieldConfig, wrappedKeys]);
|
|
233
|
+
const isFullWidthField = f => ['select', 'textarea', 'select_multiple'].includes(f.type) || wrappedKeys.has(f.key);
|
|
182
234
|
return /*#__PURE__*/React.createElement(ThemeProvider, {
|
|
183
235
|
theme: defaultTheme
|
|
184
236
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -286,7 +338,7 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
286
338
|
}, /*#__PURE__*/React.createElement(DeleteOutlineIcon, {
|
|
287
339
|
fontSize: "small"
|
|
288
340
|
}))), (() => {
|
|
289
|
-
const pairableFields = fieldConfig.filter(f => !
|
|
341
|
+
const pairableFields = fieldConfig.filter(f => !isFullWidthField(f));
|
|
290
342
|
const orphanField = pairableFields.length % 2 === 1 ? pairableFields[pairableFields.length - 1] : null;
|
|
291
343
|
return /*#__PURE__*/React.createElement("div", {
|
|
292
344
|
ref: fieldsContainerRef,
|
|
@@ -311,7 +363,7 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
311
363
|
// extra length was long enough to wrap onto a second line in a narrow column,
|
|
312
364
|
// throwing that field's input out of vertical alignment with its paired neighbor.
|
|
313
365
|
const labelText = field.required ? `${field.label} *` : field.label;
|
|
314
|
-
const wrapperClassName =
|
|
366
|
+
const wrapperClassName = isFullWidthField(field) || field === orphanField ? 'col-span-full' : undefined;
|
|
315
367
|
let control;
|
|
316
368
|
if (showAsDrifted) {
|
|
317
369
|
// Generic stringify -- this renders whatever type the field used to be, which
|
|
@@ -479,6 +531,9 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
479
531
|
fullWidth: true,
|
|
480
532
|
InputLabelProps: {
|
|
481
533
|
shrink: true
|
|
534
|
+
},
|
|
535
|
+
inputProps: {
|
|
536
|
+
onClick: openNativeDatePicker
|
|
482
537
|
}
|
|
483
538
|
});
|
|
484
539
|
} else {
|
|
@@ -504,6 +559,9 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
504
559
|
key: revealKey,
|
|
505
560
|
"data-field-invalid": showError ? 'true' : undefined
|
|
506
561
|
}, /*#__PURE__*/React.createElement(Typography, {
|
|
562
|
+
ref: el => {
|
|
563
|
+
if (el) labelRefs.current.set(field.key, el);else labelRefs.current.delete(field.key);
|
|
564
|
+
},
|
|
507
565
|
variant: "body2",
|
|
508
566
|
sx: {
|
|
509
567
|
fontWeight: 500,
|
|
@@ -2524,7 +2524,8 @@
|
|
|
2524
2524
|
"width": "Ancho",
|
|
2525
2525
|
"max_adult": "Máximo de adultos",
|
|
2526
2526
|
"max_children": "Máximo de niños",
|
|
2527
|
-
"max_animal": "Máximo de animales"
|
|
2527
|
+
"max_animal": "Máximo de animales",
|
|
2528
|
+
"animal_fields": "Campos para animales"
|
|
2528
2529
|
}
|
|
2529
2530
|
},
|
|
2530
2531
|
"lot_selection": {
|
|
@@ -2524,7 +2524,8 @@
|
|
|
2524
2524
|
"width": "Largeur",
|
|
2525
2525
|
"max_adult": "Adultes max",
|
|
2526
2526
|
"max_children": "Enfants max",
|
|
2527
|
-
"max_animal": "Animaux max"
|
|
2527
|
+
"max_animal": "Animaux max",
|
|
2528
|
+
"animal_fields": "Champs pour les animaux"
|
|
2528
2529
|
}
|
|
2529
2530
|
},
|
|
2530
2531
|
"lot_selection": {
|