pesona-ui 1.0.29 → 1.0.30

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
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState, useRef, useMemo, forwardRef, useCallback } from 'react';
1
+ import React, { useEffect, useState, useRef, useMemo, forwardRef, useImperativeHandle, useCallback } from 'react';
2
2
  import ReactDOM, { createPortal } from 'react-dom';
3
3
 
4
4
  const Button = ({ type = 'button', className, children, ...rest }) => {
@@ -8189,95 +8189,44 @@ const useDropdownPositionAndScroll = (isOpen, dropdownOptionsRef) => {
8189
8189
 
8190
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
+ const [internalValue, setInternalValue] = useState(value);
8193
8193
  const dropdownRef = useRef(null);
8194
8194
  const dropdownOptionsRef = useRef(null);
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) => {
8203
- const selectedOption = options.find((opt) => opt.value === optionValue);
8204
- if (!selectedOption)
8205
- return;
8206
- hiddenSelectRef.current.value = String(selectedOption.value);
8207
- onChange?.(createSyntheticEvent(selectedOption.value));
8195
+ const selectRef = useRef(null);
8196
+ // Update internal value when value prop changes
8197
+ useEffect(() => {
8198
+ setInternalValue(value);
8199
+ }, [value]);
8200
+ // Expose selectRef to parent via forwardRef
8201
+ useImperativeHandle(ref, () => selectRef.current);
8202
+ // Handle option click
8203
+ const handleOptionClick = (optionValue) => {
8204
+ setInternalValue(optionValue);
8205
+ // Trigger onChange
8206
+ onChange?.({
8207
+ target: { name, value: optionValue, type: 'select-one' },
8208
+ });
8209
+ if (selectRef.current) {
8210
+ selectRef.current.value = optionValue.toString();
8211
+ selectRef.current.dispatchEvent(new Event('change', { bubbles: true }));
8212
+ }
8208
8213
  setIsOpen(false);
8209
- setTimeout(() => {
8210
- onBlur?.(createSyntheticEvent(selectedOption.value));
8211
- }, 0);
8212
- }, [options, onChange, onBlur, createSyntheticEvent]);
8213
- const handleOutsideClick = useCallback(() => {
8214
+ };
8215
+ useOutsideClick([dropdownRef], () => {
8214
8216
  setIsOpen(false);
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);
8217
+ selectRef.current?.blur();
8218
+ });
8222
8219
  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';
8226
- useEffect(() => {
8227
- if (hiddenSelectRef.current)
8228
- hiddenSelectRef.current.value = normalizedValue;
8229
- }, [normalizedValue]);
8230
8220
  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) {
8221
+ return () => {
8249
8222
  setIsOpen(false);
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]);
8223
+ };
8224
+ }, []);
8225
+ const displayText = internalValue
8226
+ ? options.find((option) => option.value === internalValue)?.label
8227
+ : selectLabel || 'Select an option';
8273
8228
  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: {
8229
+ React.createElement("select", { ref: selectRef, name: name, value: internalValue || '', onChange: onChange, onBlur: onBlur, required: required, disabled: disabled, style: {
8281
8230
  position: 'absolute',
8282
8231
  left: '-9999px',
8283
8232
  opacity: 0,
@@ -8292,15 +8241,15 @@ const Select = forwardRef(({ name, label, message, selectLabel, size = 'md', flo
8292
8241
  " ",
8293
8242
  required && React.createElement("span", { className: "text-danger" }, "*"))),
8294
8243
  React.createElement("div", { className: `dropdown-select-container ${size}`, ref: dropdownRef },
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" },
8244
+ React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), "aria-haspopup": "listbox", "aria-expanded": isOpen },
8296
8245
  React.createElement("span", { className: "label" }, displayText),
8297
8246
  React.createElement(FiChevronDown, { className: "arrow" })),
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)))))),
8247
+ 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 ${internalValue === option.value ? 'selected' : ''}`, onClick: () => !disabled && handleOptionClick(option.value), role: "option", "aria-selected": internalValue === option.value }, option.label)))))),
8299
8248
  label && floatingLabel && (React.createElement("label", { htmlFor: name },
8300
8249
  label,
8301
8250
  " ",
8302
8251
  required && React.createElement("span", { className: "text-danger" }, "*"))),
8303
- error ? (React.createElement("small", { className: "form-message text-danger", role: "alert" }, error)) : (message && React.createElement("small", { className: "form-message text-muted" }, message))));
8252
+ error ? (React.createElement("small", { className: "form-message text-danger" }, error)) : (message && React.createElement("small", { className: "form-message text-muted" }, message))));
8304
8253
  });
8305
8254
  Select.displayName = 'Select';
8306
8255
 
@@ -8648,12 +8597,15 @@ const RadioButtonGroup = React.forwardRef(({ name, label, message, size = 'md',
8648
8597
  });
8649
8598
  RadioButtonGroup.displayName = 'RadioButtonGroup';
8650
8599
 
8651
- const SelectMultiple = forwardRef(({ name, label, message, selectLabel, floatingLabel = false, error, options, value = [], onChange, required, disabled, className, }, ref) => {
8600
+ const SelectMultiple = forwardRef(({ name, label, message, selectLabel, floatingLabel = false, error, options, value = [], onChange, onBlur, required, disabled, className, }, ref) => {
8652
8601
  const [isOpen, setIsOpen] = useState(false);
8653
8602
  const [selectedValues, setSelectedValues] = useState(value);
8654
8603
  const dropdownRef = useRef(null);
8655
8604
  const dropdownOptionsRef = useRef(null);
8656
- // Update selectedValues hanya jika value dari props berubah
8605
+ const inputRef = useRef(null);
8606
+ // expose to RHF
8607
+ useImperativeHandle(ref, () => inputRef.current);
8608
+ // update when external value changes
8657
8609
  useEffect(() => {
8658
8610
  if (JSON.stringify(value) !== JSON.stringify(selectedValues)) {
8659
8611
  setSelectedValues(value);
@@ -8664,14 +8616,16 @@ const SelectMultiple = forwardRef(({ name, label, message, selectLabel, floating
8664
8616
  ? selectedValues.filter((val) => val !== optionValue)
8665
8617
  : [...selectedValues, optionValue];
8666
8618
  setSelectedValues(newSelectedValues);
8667
- if (onChange) {
8668
- onChange({
8669
- target: { name, value: newSelectedValues },
8670
- });
8671
- }
8619
+ onChange?.({
8620
+ target: {
8621
+ name,
8622
+ value: newSelectedValues,
8623
+ },
8624
+ });
8672
8625
  };
8673
8626
  useOutsideClick([dropdownRef], () => {
8674
8627
  setIsOpen(false);
8628
+ inputRef.current?.blur();
8675
8629
  });
8676
8630
  useDropdownPositionAndScroll(isOpen, dropdownOptionsRef);
8677
8631
  const selectedLabels = selectedValues
@@ -8679,12 +8633,13 @@ const SelectMultiple = forwardRef(({ name, label, message, selectLabel, floating
8679
8633
  .filter(Boolean)
8680
8634
  .join(', ');
8681
8635
  return (React.createElement(React.Fragment, null,
8636
+ React.createElement("input", { type: "hidden", name: name, value: selectedValues.join(','), ref: inputRef, onBlur: onBlur, required: required }),
8682
8637
  label && !floatingLabel && (React.createElement("label", { htmlFor: name },
8683
8638
  label,
8684
8639
  " ",
8685
8640
  required && React.createElement("span", { className: "text-danger" }, "*"))),
8686
8641
  React.createElement("div", { className: `dropdown-select-container ${className || ''}`, ref: dropdownRef },
8687
- React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), ref: ref },
8642
+ React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen) },
8688
8643
  React.createElement("span", { className: "label" }, selectedLabels || selectLabel || 'Select an option'),
8689
8644
  React.createElement(FiChevronDown, { className: `arrow ${isOpen ? 'open' : ''}` })),
8690
8645
  isOpen && !disabled && (React.createElement("div", { className: "dropdown-options scrollbar", ref: dropdownOptionsRef, role: "listbox" }, options?.map((option) => (React.createElement("div", { key: option.value, className: `option ${selectedValues.includes(option.value) ? 'selected' : ''}` },