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