@paroicms/react-ui 0.5.15 → 0.6.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.
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { clsx } from "clsx";
3
+ import { useInputCursorFix } from "./use-input-cursor-fix.js";
3
4
  import "../styles/InputNumber.css";
4
5
  export function InputNumber({ className, label, error, value, onChange, min, max, step, ref, ...props }) {
6
+ const { inputRef, saveCursorPos } = useInputCursorFix();
5
7
  const handleChange = (e) => {
8
+ saveCursorPos(e);
6
9
  const val = e.target.value;
7
10
  if (val === "") {
8
11
  onChange?.(undefined, e);
@@ -14,6 +17,12 @@ export function InputNumber({ className, label, error, value, onChange, min, max
14
17
  }
15
18
  }
16
19
  };
17
- const inputElement = (_jsx("input", { ref: ref, type: "number", className: clsx("PaNumberInput", error && "error", !label && className), value: value ?? "", onChange: handleChange, min: min, max: max, step: step, ...props }));
20
+ const inputElement = (_jsx("input", { ref: (el) => {
21
+ inputRef.current = el;
22
+ if (typeof ref === "function")
23
+ ref(el);
24
+ else if (ref)
25
+ ref.current = el;
26
+ }, type: "number", className: clsx("PaNumberInput", error && "error", !label && className), value: value ?? "", onChange: handleChange, min: min, max: max, step: step, ...props }));
18
27
  return (_jsxs(_Fragment, { children: [label ? (_jsxs("label", { className: clsx("PaField", error && "error", className), children: [_jsx("span", { className: "PaField-label", children: label }), inputElement] })) : (inputElement), error && _jsx("span", { className: "PaFieldError", children: error })] }));
19
28
  }
package/dist/InputText.js CHANGED
@@ -1,12 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { clsx } from "clsx";
3
3
  import "../styles/InputText.css";
4
+ import { useInputCursorFix } from "./use-input-cursor-fix.js";
4
5
  export function InputText({ value, onChange, label, error, className, inputClassName, endElement, icon, iconPosition = "left", ...rest }) {
6
+ const hasWrapper = Boolean(endElement || icon);
7
+ const { inputRef, saveCursorPos } = useInputCursorFix();
5
8
  const handleChange = (e) => {
9
+ saveCursorPos(e);
6
10
  onChange?.(e.target.value, e);
7
11
  };
8
- const inputElement = (_jsx("input", { className: clsx("PaTextInput", inputClassName), value: value, onChange: handleChange, ...rest }));
9
- const hasWrapper = Boolean(endElement || icon);
10
- const fieldContent = endElement ? (_jsxs("span", { className: "PaTextField-row", children: [inputElement, endElement] })) : icon ? (_jsxs("span", { className: clsx("PaTextField-iconWrapper", iconPosition === "right" && "iconRight"), children: [_jsx("span", { className: "PaTextField-icon", children: icon }), inputElement] })) : (inputElement);
11
- return (_jsxs(_Fragment, { children: [label ? (_jsxs("label", { className: clsx("PaTextField PaField", error && "error", className), children: [_jsx("span", { className: "PaField-label", children: label }), fieldContent] })) : hasWrapper ? (_jsx("span", { className: clsx("PaTextField", error && "error", className), children: fieldContent })) : (_jsx("input", { className: clsx("PaTextInput", inputClassName, className), value: value, onChange: handleChange, ...rest })), error && _jsx("span", { className: "PaFieldError", children: error })] }));
12
+ let fieldContent;
13
+ if (label || hasWrapper) {
14
+ const inputElement = (_jsx("input", { ref: inputRef, className: clsx("PaTextInput", inputClassName), value: value, onChange: handleChange, ...rest }));
15
+ fieldContent = endElement ? (_jsxs("span", { className: "PaTextField-row", children: [inputElement, endElement] })) : icon ? (_jsxs("span", { className: clsx("PaTextField-iconWrapper", iconPosition === "right" && "iconRight"), children: [_jsx("span", { className: "PaTextField-icon", children: icon }), inputElement] })) : (inputElement);
16
+ }
17
+ return (_jsxs(_Fragment, { children: [label ? (_jsxs("label", { className: clsx("PaTextField PaField", error && "error", className), children: [_jsx("span", { className: "PaField-label", children: label }), fieldContent] })) : hasWrapper ? (_jsx("span", { className: clsx("PaTextField", error && "error", className), children: fieldContent })) : (_jsx("input", { ref: inputRef, className: clsx("PaTextInput", inputClassName, className), value: value, onChange: handleChange, ...rest })), error && _jsx("span", { className: "PaFieldError", children: error })] }));
12
18
  }
@@ -3,11 +3,20 @@ import "../styles/PasswordInput.css";
3
3
  import { clsx } from "clsx";
4
4
  import { Eye, EyeOff } from "lucide-react";
5
5
  import { useState } from "react";
6
+ import { useInputCursorFix } from "./use-input-cursor-fix.js";
6
7
  export function PasswordInput({ className, inputClassName, label, error, value, onChange, ref, ...props }) {
7
8
  const [visible, setVisible] = useState(false);
9
+ const { inputRef, saveCursorPos } = useInputCursorFix();
8
10
  const handleChange = (e) => {
11
+ saveCursorPos(e);
9
12
  onChange?.(e.target.value, e);
10
13
  };
11
- const inputWithToggle = (_jsxs("span", { className: clsx("PaPasswordInput-inputWrapper", error && "error"), children: [_jsx("input", { ref: ref, type: visible ? "text" : "password", className: clsx("PaPasswordInput-input", inputClassName), value: value, onChange: handleChange, ...props }), _jsx("button", { type: "button", className: "PaPasswordInput-toggle", onClick: () => setVisible(!visible), tabIndex: -1, "aria-label": visible ? "Hide password" : "Show password", children: visible ? _jsx(EyeOff, { size: 16 }) : _jsx(Eye, { size: 16 }) })] }));
14
+ const inputWithToggle = (_jsxs("span", { className: clsx("PaPasswordInput-inputWrapper", error && "error"), children: [_jsx("input", { ref: (el) => {
15
+ inputRef.current = el;
16
+ if (typeof ref === "function")
17
+ ref(el);
18
+ else if (ref)
19
+ ref.current = el;
20
+ }, type: visible ? "text" : "password", className: clsx("PaPasswordInput-input", inputClassName), value: value, onChange: handleChange, ...props }), _jsx("button", { type: "button", className: "PaPasswordInput-toggle", onClick: () => setVisible(!visible), tabIndex: -1, "aria-label": visible ? "Hide password" : "Show password", children: visible ? _jsx(EyeOff, { size: 16 }) : _jsx(Eye, { size: 16 }) })] }));
12
21
  return (_jsxs(_Fragment, { children: [label ? (_jsxs("label", { className: clsx("PaPasswordInput PaField", error && "error", className), children: [_jsx("span", { className: "PaField-label", children: label }), inputWithToggle] })) : (inputWithToggle), error && _jsx("span", { className: "PaFieldError", children: error })] }));
13
22
  }
package/dist/Textarea.js CHANGED
@@ -1,10 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import "../styles/Textarea.css";
3
3
  import { clsx } from "clsx";
4
+ import { useInputCursorFix } from "./use-input-cursor-fix.js";
4
5
  export function Textarea({ className, inputClassName, label, error, value, onChange, ref, ...props }) {
6
+ const { inputRef, saveCursorPos } = useInputCursorFix();
5
7
  const handleChange = (e) => {
8
+ saveCursorPos(e);
6
9
  onChange?.(e.target.value, e);
7
10
  };
8
- const textareaElement = (_jsx("textarea", { ref: ref, className: clsx("PaTextarea", error && "error", inputClassName, !label && className), value: value, onChange: handleChange, ...props }));
11
+ const textareaElement = (_jsx("textarea", { ref: (el) => {
12
+ inputRef.current = el;
13
+ if (typeof ref === "function")
14
+ ref(el);
15
+ else if (ref)
16
+ ref.current = el;
17
+ }, className: clsx("PaTextarea", error && "error", inputClassName, !label && className), value: value, onChange: handleChange, ...props }));
9
18
  return (_jsxs(_Fragment, { children: [label ? (_jsxs("label", { className: clsx("PaField", error && "error", className), children: [_jsx("span", { className: "PaField-label", children: label }), textareaElement] })) : (textareaElement), error && _jsx("span", { className: "PaFieldError", children: error })] }));
10
19
  }
@@ -0,0 +1,16 @@
1
+ import { type ChangeEvent, type RefObject } from "react";
2
+ type TextInput = HTMLInputElement | HTMLTextAreaElement;
3
+ /**
4
+ * Firefox workaround: after a controlled input re-renders inside a `<label>`,
5
+ * Firefox fires a spurious focus+select that selects all text. This hook saves
6
+ * the cursor position on each change event and restores it when a spurious
7
+ * focus is detected.
8
+ *
9
+ * Returns `{ inputRef, saveCursorPos }`. Wire `inputRef` to the element's `ref`
10
+ * and call `saveCursorPos(e)` at the start of your onChange handler.
11
+ */
12
+ export declare function useInputCursorFix<T extends TextInput>(): {
13
+ inputRef: RefObject<T | null>;
14
+ saveCursorPos: (e: ChangeEvent<T>) => void;
15
+ };
16
+ export {};
@@ -0,0 +1,34 @@
1
+ import { useEffect, useRef } from "react";
2
+ /**
3
+ * Firefox workaround: after a controlled input re-renders inside a `<label>`,
4
+ * Firefox fires a spurious focus+select that selects all text. This hook saves
5
+ * the cursor position on each change event and restores it when a spurious
6
+ * focus is detected.
7
+ *
8
+ * Returns `{ inputRef, saveCursorPos }`. Wire `inputRef` to the element's `ref`
9
+ * and call `saveCursorPos(e)` at the start of your onChange handler.
10
+ */
11
+ export function useInputCursorFix() {
12
+ const inputRef = useRef(null);
13
+ const cursorPos = useRef(null);
14
+ const saveCursorPos = (e) => {
15
+ cursorPos.current = e.target.selectionStart;
16
+ };
17
+ useEffect(() => {
18
+ const el = inputRef.current;
19
+ if (!el)
20
+ return;
21
+ const onFocus = () => {
22
+ const pos = cursorPos.current;
23
+ if (pos !== null) {
24
+ cursorPos.current = null;
25
+ queueMicrotask(() => {
26
+ el.setSelectionRange(pos, pos);
27
+ });
28
+ }
29
+ };
30
+ el.addEventListener("focus", onFocus);
31
+ return () => el.removeEventListener("focus", onFocus);
32
+ }, []);
33
+ return { inputRef, saveCursorPos };
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paroicms/react-ui",
3
- "version": "0.5.15",
3
+ "version": "0.6.0",
4
4
  "description": "React UI toolkit for ParoiCMS.",
5
5
  "keywords": [
6
6
  "paroicms",