pesona-ui 1.0.28 → 1.0.29
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/dist/index.esm.js
CHANGED
|
@@ -8187,53 +8187,120 @@ const useDropdownPositionAndScroll = (isOpen, dropdownOptionsRef) => {
|
|
|
8187
8187
|
}, [isOpen, dropdownOptionsRef]);
|
|
8188
8188
|
};
|
|
8189
8189
|
|
|
8190
|
-
const Select = forwardRef(({ name, label, message, selectLabel, size = 'md', floatingLabel = false, error, options, value = '', onChange, required, disabled, }, ref) => {
|
|
8190
|
+
const Select = forwardRef(({ name, label, message, selectLabel, size = 'md', floatingLabel = false, error, options, value = '', onChange, onBlur, required, disabled, }, ref) => {
|
|
8191
8191
|
const [isOpen, setIsOpen] = useState(false);
|
|
8192
|
+
const [highlightIndex, setHighlightIndex] = useState(-1);
|
|
8192
8193
|
const dropdownRef = useRef(null);
|
|
8193
8194
|
const dropdownOptionsRef = useRef(null);
|
|
8194
|
-
const
|
|
8195
|
+
const hiddenSelectRef = useRef(null);
|
|
8196
|
+
const shouldReturnNumber = useMemo(() => options.every((opt) => typeof opt.value === 'number'), [options]);
|
|
8197
|
+
const coerceValue = useCallback((val) => (shouldReturnNumber ? Number(val) : val), [shouldReturnNumber]);
|
|
8198
|
+
const createSyntheticEvent = useCallback((val) => ({
|
|
8199
|
+
target: { name, value: coerceValue(val), type: 'select-one' },
|
|
8200
|
+
currentTarget: { name, value: coerceValue(val), type: 'select-one' },
|
|
8201
|
+
}), [name, coerceValue]);
|
|
8202
|
+
const handleOptionSelect = useCallback((optionValue) => {
|
|
8195
8203
|
const selectedOption = options.find((opt) => opt.value === optionValue);
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
if (onChange) {
|
|
8201
|
-
onChange({
|
|
8202
|
-
target: {
|
|
8203
|
-
name,
|
|
8204
|
-
value: actualValue,
|
|
8205
|
-
},
|
|
8206
|
-
});
|
|
8207
|
-
}
|
|
8204
|
+
if (!selectedOption)
|
|
8205
|
+
return;
|
|
8206
|
+
hiddenSelectRef.current.value = String(selectedOption.value);
|
|
8207
|
+
onChange?.(createSyntheticEvent(selectedOption.value));
|
|
8208
8208
|
setIsOpen(false);
|
|
8209
|
-
|
|
8210
|
-
|
|
8209
|
+
setTimeout(() => {
|
|
8210
|
+
onBlur?.(createSyntheticEvent(selectedOption.value));
|
|
8211
|
+
}, 0);
|
|
8212
|
+
}, [options, onChange, onBlur, createSyntheticEvent]);
|
|
8213
|
+
const handleOutsideClick = useCallback(() => {
|
|
8211
8214
|
setIsOpen(false);
|
|
8212
|
-
|
|
8215
|
+
if (onBlur && value !== undefined && value !== null && value !== '') {
|
|
8216
|
+
setTimeout(() => {
|
|
8217
|
+
onBlur(createSyntheticEvent(value));
|
|
8218
|
+
}, 0);
|
|
8219
|
+
}
|
|
8220
|
+
}, [value, onBlur, createSyntheticEvent]);
|
|
8221
|
+
useOutsideClick([dropdownRef], handleOutsideClick);
|
|
8213
8222
|
useDropdownPositionAndScroll(isOpen, dropdownOptionsRef);
|
|
8223
|
+
const normalizedValue = useMemo(() => (value === null || value === undefined || value === '' ? '' : String(value)), [value]);
|
|
8224
|
+
const selectedOption = useMemo(() => options.find((opt) => String(opt.value) === normalizedValue), [options, normalizedValue]);
|
|
8225
|
+
const displayText = selectedOption?.label || selectLabel || 'Select an option';
|
|
8214
8226
|
useEffect(() => {
|
|
8215
|
-
|
|
8227
|
+
if (hiddenSelectRef.current)
|
|
8228
|
+
hiddenSelectRef.current.value = normalizedValue;
|
|
8229
|
+
}, [normalizedValue]);
|
|
8230
|
+
useEffect(() => {
|
|
8231
|
+
if (isOpen) {
|
|
8232
|
+
const index = options.findIndex((opt) => String(opt.value) === normalizedValue);
|
|
8233
|
+
setHighlightIndex(index >= 0 ? index : 0);
|
|
8234
|
+
}
|
|
8235
|
+
}, [isOpen, options, normalizedValue]);
|
|
8236
|
+
const handleKeyDown = useCallback((e) => {
|
|
8237
|
+
if (disabled)
|
|
8238
|
+
return;
|
|
8239
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
8240
|
+
e.preventDefault();
|
|
8241
|
+
if (isOpen && highlightIndex >= 0) {
|
|
8242
|
+
handleOptionSelect(options[highlightIndex].value);
|
|
8243
|
+
}
|
|
8244
|
+
else {
|
|
8245
|
+
setIsOpen((prev) => !prev);
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
else if (e.key === 'Escape' && isOpen) {
|
|
8216
8249
|
setIsOpen(false);
|
|
8217
|
-
}
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8250
|
+
}
|
|
8251
|
+
else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
8252
|
+
e.preventDefault();
|
|
8253
|
+
if (!isOpen) {
|
|
8254
|
+
setIsOpen(true);
|
|
8255
|
+
}
|
|
8256
|
+
else {
|
|
8257
|
+
setHighlightIndex((prev) => {
|
|
8258
|
+
const next = e.key === 'ArrowDown'
|
|
8259
|
+
? (prev + 1) % options.length
|
|
8260
|
+
: (prev - 1 + options.length) % options.length;
|
|
8261
|
+
return next;
|
|
8262
|
+
});
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
}, [disabled, isOpen, highlightIndex, options, handleOptionSelect]);
|
|
8266
|
+
useEffect(() => {
|
|
8267
|
+
if (isOpen && dropdownOptionsRef.current && highlightIndex >= 0) {
|
|
8268
|
+
const optionElements = dropdownOptionsRef.current.querySelectorAll('.option');
|
|
8269
|
+
const highlighted = optionElements[highlightIndex];
|
|
8270
|
+
highlighted?.scrollIntoView({ block: 'nearest' });
|
|
8271
|
+
}
|
|
8272
|
+
}, [highlightIndex, isOpen]);
|
|
8222
8273
|
return (React.createElement(React.Fragment, null,
|
|
8274
|
+
React.createElement("select", { ref: (el) => {
|
|
8275
|
+
hiddenSelectRef.current = el;
|
|
8276
|
+
if (typeof ref === 'function')
|
|
8277
|
+
ref(el);
|
|
8278
|
+
else if (ref)
|
|
8279
|
+
ref.current = el;
|
|
8280
|
+
}, name: name, value: normalizedValue, onChange: onChange, onBlur: onBlur, required: required, disabled: disabled, style: {
|
|
8281
|
+
position: 'absolute',
|
|
8282
|
+
left: '-9999px',
|
|
8283
|
+
opacity: 0,
|
|
8284
|
+
pointerEvents: 'none',
|
|
8285
|
+
width: '1px',
|
|
8286
|
+
height: '1px',
|
|
8287
|
+
}, tabIndex: -1, "aria-hidden": "true" },
|
|
8288
|
+
React.createElement("option", { value: "" }, "Select an option"),
|
|
8289
|
+
options.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label)))),
|
|
8223
8290
|
label && !floatingLabel && (React.createElement("label", { htmlFor: name },
|
|
8224
8291
|
label,
|
|
8225
8292
|
" ",
|
|
8226
8293
|
required && React.createElement("span", { className: "text-danger" }, "*"))),
|
|
8227
8294
|
React.createElement("div", { className: `dropdown-select-container ${size}`, ref: dropdownRef },
|
|
8228
|
-
React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen),
|
|
8295
|
+
React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''} ${error ? 'error' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), onKeyDown: handleKeyDown, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-invalid": !!error, tabIndex: disabled ? -1 : 0, role: "combobox" },
|
|
8229
8296
|
React.createElement("span", { className: "label" }, displayText),
|
|
8230
8297
|
React.createElement(FiChevronDown, { className: "arrow" })),
|
|
8231
|
-
isOpen && (React.createElement("div", { className:
|
|
8298
|
+
isOpen && (React.createElement("div", { className: "dropdown-options scrollbar", ref: dropdownOptionsRef, role: "listbox" }, options.map((option, index) => (React.createElement("div", { key: `${option.value}-${index}`, className: `option ${String(value) === String(option.value) ? 'selected' : ''} ${highlightIndex === index ? 'highlight' : ''}`, onClick: () => !disabled && handleOptionSelect(option.value), role: "option", "aria-selected": String(value) === String(option.value), tabIndex: 0 }, option.label)))))),
|
|
8232
8299
|
label && floatingLabel && (React.createElement("label", { htmlFor: name },
|
|
8233
8300
|
label,
|
|
8234
8301
|
" ",
|
|
8235
8302
|
required && React.createElement("span", { className: "text-danger" }, "*"))),
|
|
8236
|
-
error ? (React.createElement("small", { className: "form-message text-danger" }, error)) : (message && React.createElement("small", { className: "form-message text-muted" }, message))));
|
|
8303
|
+
error ? (React.createElement("small", { className: "form-message text-danger", role: "alert" }, error)) : (message && React.createElement("small", { className: "form-message text-muted" }, message))));
|
|
8237
8304
|
});
|
|
8238
8305
|
Select.displayName = 'Select';
|
|
8239
8306
|
|