@rolster/react-components 19.8.21 → 19.9.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/dist/es/index.mjs CHANGED
@@ -482,41 +482,110 @@ function RlsAmountComponent({ value, className, decimals, rlsTheme, symbol }) {
482
482
  }
483
483
  const RlsAmount = memo(RlsAmountComponent);
484
484
 
485
- function RlsAreaTextComponent({ autoComplete, disabled, formControl, identifier, onBlur, onEnter, onFocus, onKeyDown, onKeyUp, onValue, placeholder, readOnly, resize, rows = 1, value }) {
485
+ const AREA_TEXT_MAX_ROWS = 8;
486
+ const AREA_TEXT_MIN_ROWS = 3;
487
+ function writeAreaValue(element, valueArea) {
488
+ const rawArea = element.value;
489
+ if (rawArea === valueArea) {
490
+ return;
491
+ }
492
+ const selection = element.selectionStart;
493
+ const offset = valueArea.length - rawArea.length;
494
+ const caret = Math.max(0, Math.min(selection + offset, valueArea.length));
495
+ element.value = valueArea;
496
+ element.setSelectionRange(caret, caret);
497
+ }
498
+ function RlsAreaTextComponent({ autoComplete, disabled, formControl, identifier, maxLength, maxRows, minRows, onBlur, onEnter, onFocus, onKeyDown, onKeyUp, onValue, placeholder, readOnly, resize = 'vertical', value }) {
486
499
  const valueInitial = String(formControl?.value ?? value ?? '');
500
+ const rowsMin = minRows ?? AREA_TEXT_MIN_ROWS;
501
+ const rowsMax = Math.max(maxRows ?? AREA_TEXT_MAX_ROWS, rowsMin);
487
502
  const [valueArea, setValueArea] = useState(valueInitial);
488
503
  const [focused, setFocused] = useState(false);
489
504
  const elementRef = useRef(null);
490
505
  const areaRef = formControl?.elementRef ?? elementRef;
491
- const changeIsInternal = useRef(false);
506
+ const composing = useRef(false);
507
+ const valueSent = useRef(formControl?.value);
508
+ const valueSource = useRef(formControl ? formControl.value : value);
509
+ const heightIsManual = useRef(false);
510
+ const heightApplied = useRef(0);
492
511
  const refreshHeight = useCallback(() => {
493
512
  const element = areaRef.current;
494
- if (element) {
495
- element.style.height = 'auto';
496
- element.style.height = `${element.scrollHeight}px`;
513
+ if (!element || heightIsManual.current) {
514
+ return;
497
515
  }
498
- }, [areaRef]);
516
+ element.style.height = 'auto';
517
+ const { lineHeight, paddingBottom, paddingTop } = getComputedStyle(element);
518
+ const heightLine = parseFloat(lineHeight);
519
+ const heightContent = element.scrollHeight;
520
+ if (!heightLine) {
521
+ element.style.height = `${heightContent}px`;
522
+ heightApplied.current = heightContent;
523
+ return;
524
+ }
525
+ const heightPadding = parseFloat(paddingTop) + parseFloat(paddingBottom);
526
+ const heightMin = heightLine * rowsMin + heightPadding;
527
+ const heightMax = heightLine * rowsMax + heightPadding;
528
+ const height = Math.min(Math.max(heightContent, heightMin), heightMax);
529
+ element.style.height = `${height}px`;
530
+ element.style.overflowY = heightContent > heightMax ? 'auto' : 'hidden';
531
+ heightApplied.current = height;
532
+ }, [areaRef, rowsMax, rowsMin]);
499
533
  useEffect(() => {
500
- if (!changeIsInternal.current) {
501
- const nextValue = String(formControl?.value ?? value ?? '');
534
+ const source = formControl ? formControl.value : value;
535
+ const isEcho = Object.is(source, valueSent.current);
536
+ const isSame = Object.is(source, valueSource.current);
537
+ valueSource.current = source;
538
+ if (!isEcho && !isSame) {
539
+ const nextValue = String(source ?? '');
502
540
  if (valueArea !== nextValue) {
503
541
  setValueArea(nextValue);
504
542
  }
505
543
  }
506
- changeIsInternal.current = false;
507
544
  }, [formControl?.value, value, valueArea]);
508
545
  useEffect(() => {
509
546
  refreshHeight();
510
547
  }, [refreshHeight, valueArea]);
511
- const onChangeArea = useCallback((event) => {
512
- const nextValue = event.target.value;
513
- changeIsInternal.current = true;
548
+ useEffect(() => {
549
+ const element = areaRef.current;
550
+ if (!element ||
551
+ resize === 'none' ||
552
+ typeof ResizeObserver === 'undefined') {
553
+ return;
554
+ }
555
+ const observer = new ResizeObserver(() => {
556
+ if (heightIsManual.current || !heightApplied.current) {
557
+ return;
558
+ }
559
+ if (Math.abs(element.offsetHeight - heightApplied.current) > 1) {
560
+ heightIsManual.current = true;
561
+ element.style.overflowY = 'auto';
562
+ }
563
+ });
564
+ observer.observe(element);
565
+ return () => observer.disconnect();
566
+ }, [areaRef, resize]);
567
+ const applyArea = useCallback((element) => {
568
+ const rawArea = element.value;
569
+ const formatter = formControl?.formatter;
570
+ const nextValue = formatter && formControl?.formatOn !== 'blur' && !composing.current
571
+ ? (formatter(rawArea) ?? '')
572
+ : rawArea;
573
+ writeAreaValue(element, nextValue);
574
+ valueSent.current = nextValue;
514
575
  onValue?.(nextValue);
515
576
  setValueArea(nextValue);
516
577
  formControl?.setValue(nextValue);
517
- event.target.style.height = 'auto';
518
- event.target.style.height = `${event.target.scrollHeight}px`;
519
578
  }, [formControl, onValue]);
579
+ const onChangeArea = useCallback((event) => {
580
+ applyArea(event.target);
581
+ }, [applyArea]);
582
+ const onCompositionStart = useCallback(() => {
583
+ composing.current = true;
584
+ }, []);
585
+ const onCompositionEnd = useCallback((event) => {
586
+ composing.current = false;
587
+ applyArea(event.currentTarget);
588
+ }, [applyArea]);
520
589
  const onKeyDownArea = useCallback((event) => {
521
590
  onKeyDown?.(event);
522
591
  if (event.key === 'Enter') {
@@ -536,12 +605,14 @@ function RlsAreaTextComponent({ autoComplete, disabled, formControl, identifier,
536
605
  setFocused(() => false);
537
606
  onBlur?.();
538
607
  }, [formControl, onBlur]);
608
+ const disabledArea = formControl?.disabled || disabled;
539
609
  const className = renderClassStatus('rls-area-text', {
540
- disabled: formControl?.disabled || disabled,
610
+ disabled: disabledArea,
541
611
  focused: formControl?.focused ?? focused,
542
- readonly: readOnly
612
+ readonly: readOnly,
613
+ resizable: resize !== 'none' && !disabledArea && !readOnly
543
614
  });
544
- return (jsxRuntimeExports.jsxs("div", { id: identifier, className: className, children: [jsxRuntimeExports.jsx("textarea", { ref: areaRef, className: "rls-area-text__component", autoComplete: autoComplete ?? 'off', placeholder: placeholder, disabled: formControl?.disabled || disabled, readOnly: readOnly, rows: rows, style: { resize }, onFocus: onFocusArea, onBlur: onBlurArea, onChange: onChangeArea, onKeyDown: onKeyDownArea, onKeyUp: onKeyUpArea, value: valueArea }), jsxRuntimeExports.jsx("span", { className: "rls-area-text__value", children: valueArea })] }));
615
+ return (jsxRuntimeExports.jsxs("div", { id: identifier, className: className, children: [jsxRuntimeExports.jsx("textarea", { ref: areaRef, className: "rls-area-text__component", autoComplete: autoComplete ?? 'off', placeholder: placeholder, disabled: disabledArea, readOnly: readOnly, maxLength: maxLength, rows: rowsMin, style: { resize }, onFocus: onFocusArea, onBlur: onBlurArea, onChange: onChangeArea, onCompositionStart: onCompositionStart, onCompositionEnd: onCompositionEnd, onKeyDown: onKeyDownArea, onKeyUp: onKeyUpArea, value: valueArea }), jsxRuntimeExports.jsx("span", { className: "rls-area-text__value", "aria-hidden": "true", children: valueArea })] }));
545
616
  }
546
617
  const RlsAreaText = memo(RlsAreaTextComponent);
547
618
 
@@ -648,8 +719,11 @@ function RlsCheckBoxControlComponent({ formControl, disabled, identifier, rlsThe
648
719
  }
649
720
  const RlsCheckBoxControl = memo(RlsCheckBoxControlComponent);
650
721
 
651
- function RlsSkeletonComponent({ rlsTheme }) {
652
- return jsxRuntimeExports.jsx("div", { className: "rls-skeleton", "rls-theme": rlsTheme });
722
+ function RlsSkeletonComponent({ animation, rlsTheme }) {
723
+ const className = renderClassStatus('rls-skeleton', {
724
+ animation: animation !== 'shimmer' && animation
725
+ });
726
+ return jsxRuntimeExports.jsx("div", { className: className, "rls-theme": rlsTheme });
653
727
  }
654
728
  const RlsSkeleton = memo(RlsSkeletonComponent);
655
729
 
@@ -667,30 +741,82 @@ function RlsImageComponent({ src, rlsTheme }) {
667
741
  }
668
742
  const RlsImage = memo(RlsImageComponent);
669
743
 
744
+ function valueToInput(value) {
745
+ return value ? String(value) : '';
746
+ }
747
+ function selectionStartOf(element) {
748
+ try {
749
+ // Lanza o retorna null en inputs sin selección (number, email, etc.)
750
+ return element.selectionStart;
751
+ }
752
+ catch {
753
+ return null;
754
+ }
755
+ }
756
+ /**
757
+ * Escribe el valor formateado directamente en el DOM y conserva la posición
758
+ * del cursor, corrigiéndola por la diferencia de longitud. Es necesario
759
+ * porque, cuando el formateador rechaza un carácter, el estado del control
760
+ * no cambia y React no vuelve a escribir el input.
761
+ */
762
+ function writeInputValue(element, valueInput) {
763
+ const rawInput = element.value;
764
+ if (rawInput === valueInput) {
765
+ return;
766
+ }
767
+ const selection = selectionStartOf(element);
768
+ element.value = valueInput;
769
+ if (selection !== null) {
770
+ const offset = valueInput.length - rawInput.length;
771
+ const caret = Math.max(0, Math.min(selection + offset, valueInput.length));
772
+ element.setSelectionRange(caret, caret);
773
+ }
774
+ }
670
775
  function RlsInputComponent({ children, decimals, disabled, formControl, identifier, onBlur, onEnter, onFocus, onKeyDown, onKeyUp, onValue, placeholder, readOnly, type, value }) {
671
- const valueInitial = (formControl?.value ?? value) ? String(value) : '';
672
- const [valueInput, setValueInput] = useState(valueInitial);
776
+ const [valueInput, setValueInput] = useState(() => valueToInput(formControl?.value ?? value));
673
777
  const [focused, setFocused] = useState(false);
674
- const changeIsInternal = useRef(false);
778
+ const composing = useRef(false);
779
+ const valueSent = useRef(formControl?.value);
675
780
  useEffect(() => {
676
- if (!changeIsInternal.current && formControl) {
677
- const valueControl = formControl.value ? String(formControl.value) : '';
781
+ if (formControl && !Object.is(formControl.value, valueSent.current)) {
782
+ const valueControl = valueToInput(formControl.value);
678
783
  if (valueInput !== valueControl) {
679
784
  setValueInput(valueControl);
680
785
  }
681
786
  }
682
- changeIsInternal.current = false;
683
787
  }, [formControl?.value]);
684
- const onChangeInput = useCallback((event) => {
685
- const valueInput = event.target.value;
686
- const value = type === 'number'
687
- ? parseFloat((+valueInput).toFixed(decimals))
688
- : valueInput;
689
- changeIsInternal.current = true;
788
+ const applyInput = useCallback((element) => {
789
+ const rawInput = element.value;
790
+ const parsed = type === 'number'
791
+ ? parseFloat((+rawInput).toFixed(decimals))
792
+ : rawInput;
793
+ const formatter = formControl?.formatter;
794
+ const value = formatter && formControl?.formatOn !== 'blur' && !composing.current
795
+ ? formatter(parsed)
796
+ : parsed;
797
+ const textInput = Object.is(value, parsed)
798
+ ? rawInput
799
+ : type === 'number'
800
+ ? Number.isNaN(value)
801
+ ? ''
802
+ : String(value)
803
+ : valueToInput(value);
804
+ writeInputValue(element, textInput);
805
+ valueSent.current = value;
690
806
  onValue?.(value);
691
- setValueInput(valueInput);
807
+ setValueInput(textInput);
692
808
  formControl?.setValue(value);
693
809
  }, [formControl, onValue, type, decimals]);
810
+ const onChangeInput = useCallback((event) => {
811
+ applyInput(event.target);
812
+ }, [applyInput]);
813
+ const onCompositionStart = useCallback(() => {
814
+ composing.current = true;
815
+ }, []);
816
+ const onCompositionEnd = useCallback((event) => {
817
+ composing.current = false;
818
+ applyInput(event.currentTarget);
819
+ }, [applyInput]);
694
820
  const onKeyDownInput = useCallback((event) => {
695
821
  onKeyDown?.(event);
696
822
  if (event.key === 'Enter') {
@@ -714,7 +840,7 @@ function RlsInputComponent({ children, decimals, disabled, formControl, identifi
714
840
  disabled: formControl?.disabled || disabled,
715
841
  focused: formControl?.focused ?? focused
716
842
  });
717
- return (jsxRuntimeExports.jsxs("div", { id: identifier, className: className, children: [jsxRuntimeExports.jsx("input", { ref: formControl?.elementRef, className: "rls-input__component", autoComplete: "off", type: type ?? 'text', placeholder: placeholder, disabled: formControl?.disabled || disabled, readOnly: readOnly, onFocus: onFocusInput, onBlur: onBlurInput, onChange: onChangeInput, onKeyDown: onKeyDownInput, onKeyUp: onKeyUpInput, value: valueInput }), jsxRuntimeExports.jsx("span", { className: "rls-input__value", children: children })] }));
843
+ return (jsxRuntimeExports.jsxs("div", { id: identifier, className: className, children: [jsxRuntimeExports.jsx("input", { ref: formControl?.elementRef, className: "rls-input__component", autoComplete: "off", type: type ?? 'text', placeholder: placeholder, disabled: formControl?.disabled || disabled, readOnly: readOnly, onFocus: onFocusInput, onBlur: onBlurInput, onChange: onChangeInput, onCompositionStart: onCompositionStart, onCompositionEnd: onCompositionEnd, onKeyDown: onKeyDownInput, onKeyUp: onKeyUpInput, value: valueInput }), jsxRuntimeExports.jsx("span", { className: "rls-input__value", children: children })] }));
718
844
  }
719
845
  const RlsInput = memo(RlsInputComponent);
720
846
 
@@ -1258,8 +1384,8 @@ function RlsRadioButtonComponent({ checked, disabled, identifier, rlsTheme, onCl
1258
1384
  }
1259
1385
  const RlsRadioButton = memo(RlsRadioButtonComponent);
1260
1386
 
1261
- function RlsSkeletonTextComponent({ active, children, rlsTheme }) {
1262
- return (jsxRuntimeExports.jsx("div", { className: "rls-skeleton-text", "rls-theme": rlsTheme, children: active ? (jsxRuntimeExports.jsx(RlsSkeleton, {})) : (jsxRuntimeExports.jsx("div", { className: "rls-skeleton-text__value", children: children })) }));
1387
+ function RlsSkeletonTextComponent({ active, animation, children, rlsTheme }) {
1388
+ return (jsxRuntimeExports.jsx("div", { className: "rls-skeleton-text", "rls-theme": rlsTheme, children: active ? (jsxRuntimeExports.jsx(RlsSkeleton, { animation: animation })) : (jsxRuntimeExports.jsx("div", { className: "rls-skeleton-text__value", children: children })) }));
1263
1389
  }
1264
1390
  const RlsSkeletonText = memo(RlsSkeletonTextComponent);
1265
1391
 
@@ -1548,17 +1674,31 @@ function RlsMessageFormErrorComponent({ className, formControl }) {
1548
1674
  const RlsMessageFormError = memo(RlsMessageFormErrorComponent);
1549
1675
 
1550
1676
  function RlsFieldAreaComponent(props) {
1551
- const { children, formControl, identifier, rlsTheme } = props;
1677
+ const { children, formControl, identifier, maxLength, rlsTheme } = props;
1678
+ const { onValue } = props;
1552
1679
  const disabled = useMemo(() => {
1553
1680
  return formControl?.disabled || props.disabled;
1554
1681
  }, [formControl?.disabled, props.disabled]);
1682
+ const valueSource = formControl?.value ?? props.value;
1683
+ const [valueLength, setValueLength] = useState(() => {
1684
+ return (valueSource ?? '').length;
1685
+ });
1686
+ useEffect(() => {
1687
+ if (valueSource !== undefined) {
1688
+ setValueLength(valueSource.length);
1689
+ }
1690
+ }, [valueSource]);
1691
+ const onValueArea = useCallback((value) => {
1692
+ setValueLength(value.length);
1693
+ onValue?.(value);
1694
+ }, [onValue]);
1555
1695
  const className = renderClassStatus('rls-field-box', {
1556
1696
  disabled,
1557
1697
  error: formControl?.wrong,
1558
1698
  focused: formControl?.focused && !disabled,
1559
1699
  readonly: props.readOnly
1560
1700
  }, 'rls-field-area');
1561
- return (jsxRuntimeExports.jsxs("div", { id: identifier, className: className, "rls-theme": rlsTheme, children: [children && jsxRuntimeExports.jsx("span", { className: "rls-field-box__label", children: children }), jsxRuntimeExports.jsx("div", { className: "rls-field-box__component", children: jsxRuntimeExports.jsx("div", { className: "rls-field-box__body", children: jsxRuntimeExports.jsx(RlsAreaText, { ...props }) }) }), !props.msgErrorDisabled && (jsxRuntimeExports.jsx(RlsMessageFormError, { className: "rls-field-box__error", formControl: formControl }))] }));
1701
+ return (jsxRuntimeExports.jsxs("div", { id: identifier, className: className, "rls-theme": rlsTheme, children: [children && jsxRuntimeExports.jsx("span", { className: "rls-field-box__label", children: children }), jsxRuntimeExports.jsx("div", { className: "rls-field-box__component", children: jsxRuntimeExports.jsx("div", { className: "rls-field-box__body", children: jsxRuntimeExports.jsx(RlsAreaText, { ...props, onValue: onValueArea }) }) }), props.counter && (jsxRuntimeExports.jsx("span", { className: "rls-field-box__helper rls-field-area__counter", children: maxLength ? `${valueLength} / ${maxLength}` : valueLength })), !props.msgErrorDisabled && (jsxRuntimeExports.jsx(RlsMessageFormError, { className: "rls-field-box__error", formControl: formControl }))] }));
1562
1702
  }
1563
1703
  const RlsFieldArea = memo(RlsFieldAreaComponent);
1564
1704