@festo-ui/react 8.2.0-dev.625 → 8.2.0-dev.629

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.
@@ -2,7 +2,7 @@
2
2
  /* eslint-disable jsx-a11y/click-events-have-key-events */
3
3
 
4
4
  import classNames from "classnames";
5
- import { forwardRef, useEffect, useRef } from "react";
5
+ import { forwardRef, useEffect, useRef, useCallback } from "react";
6
6
  import ReactDOM from "react-dom";
7
7
  import { CSSTransition } from "react-transition-group";
8
8
  import { RemoveScroll } from "react-remove-scroll";
@@ -24,6 +24,8 @@ const ModalBase = /*#__PURE__*/forwardRef((_ref, ref) => {
24
24
  const mouseDownOnBackdropActive = useRef(false);
25
25
  const allRefs = useForkRef(ref, modalRef);
26
26
  const container = modalRef?.current?.ownerDocument || document;
27
+
28
+ // Handle Escape key to close modal
27
29
  useEffect(() => {
28
30
  const handleKeyDown = event => {
29
31
  if (event.key === "Escape" && isOpen) {
@@ -33,6 +35,52 @@ const ModalBase = /*#__PURE__*/forwardRef((_ref, ref) => {
33
35
  container.addEventListener("keydown", handleKeyDown);
34
36
  return () => container.removeEventListener("keydown", handleKeyDown);
35
37
  }, [onClose, isOpen, container]);
38
+
39
+ // Focus trap implementation
40
+ const handleTabKey = useCallback(e => {
41
+ if (e.key === "Tab" && modalRef.current && isOpen) {
42
+ // Query for all focusable elements
43
+ const focusableElements = modalRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
44
+ const firstElement = focusableElements[0];
45
+ const lastElement = focusableElements[focusableElements.length - 1];
46
+
47
+ // If shift+tab and we're on the first element, move to the last
48
+ if (e.shiftKey && document.activeElement === firstElement) {
49
+ e.preventDefault();
50
+ lastElement?.focus();
51
+ }
52
+ // If tab and we're on the last element, move to the first
53
+ else if (!e.shiftKey && document.activeElement === lastElement) {
54
+ e.preventDefault();
55
+ firstElement?.focus();
56
+ }
57
+ }
58
+ }, [isOpen]);
59
+
60
+ // Set up the focus trap listener
61
+ useEffect(() => {
62
+ if (isOpen) {
63
+ container.addEventListener("keydown", handleTabKey);
64
+
65
+ // Auto-focus first focusable element when modal opens
66
+ setTimeout(() => {
67
+ if (modalRef.current) {
68
+ const focusableElements = modalRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
69
+
70
+ // Focus the first input element, or the first focusable element if no input exists
71
+ const firstInput = Array.from(focusableElements).find(el => el.tagName.toLowerCase() === "input");
72
+ if (firstInput) {
73
+ firstInput.focus();
74
+ } else if (focusableElements.length > 0) {
75
+ focusableElements[0].focus();
76
+ }
77
+ }
78
+ }, 100);
79
+ }
80
+ return () => {
81
+ container.removeEventListener("keydown", handleTabKey);
82
+ };
83
+ }, [isOpen, handleTabKey, container]);
36
84
  return /*#__PURE__*/_jsx(_Fragment, {
37
85
  children: /*#__PURE__*/ReactDOM.createPortal(/*#__PURE__*/_jsxs(_Fragment, {
38
86
  children: [/*#__PURE__*/_jsx(CSSTransition, {
@@ -41,7 +41,8 @@ export default function Prompt(_ref) {
41
41
  onChange: handleChange,
42
42
  label: label,
43
43
  hint: hint,
44
- value: innerValue
44
+ value: innerValue,
45
+ autoFocus: true
45
46
  }),
46
47
  ...props,
47
48
  children: (cancel || ok) && /*#__PURE__*/_jsxs(ModalFooter, {
@@ -16,6 +16,8 @@ interface TextInputProps {
16
16
  hint?: string;
17
17
  error?: string;
18
18
  labelClassName?: string;
19
+ icon?: React.ReactNode;
20
+ autoFocus?: boolean;
19
21
  }
20
22
  declare const TextInput: (props: TextInputProps & React.HTMLProps<HTMLInputElement> & React.RefAttributes<HTMLLabelElement>) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
21
23
  export default TextInput;
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useState } from "react";
1
+ import React, { forwardRef, useEffect, useState, useRef } from "react";
2
2
  import classNames from "classnames";
3
3
  import useId from "../../helper/useId.js";
4
4
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -20,17 +20,34 @@ const TextInput = /*#__PURE__*/forwardRef((_ref, ref) => {
20
20
  hint,
21
21
  label,
22
22
  labelClassName,
23
+ icon,
23
24
  id: idProps,
25
+ autoFocus,
24
26
  ...props
25
27
  } = _ref;
26
28
  const id = useId(idProps);
27
29
  const [innerValue, setInnerValue] = useState(value);
30
+ const inputRef = useRef(null);
28
31
  useEffect(() => {
29
32
  setInnerValue(value);
30
33
  }, [value]);
34
+
35
+ // Programmatically focus the input instead of using autoFocus attribute
36
+ useEffect(() => {
37
+ if (autoFocus && inputRef.current) {
38
+ // Short delay to ensure component is fully rendered
39
+ const timer = setTimeout(() => {
40
+ inputRef.current?.focus();
41
+ }, 0);
42
+ return () => clearTimeout(timer);
43
+ }
44
+ return undefined;
45
+ }, [autoFocus]);
31
46
  const supported = ["text", "number", "password", "datetime-local"];
32
47
  const innerType = type && supported.indexOf(type) !== -1 ? type : "text";
33
- const labelClasses = classNames("fwe-input-text", labelClassName);
48
+ const labelClasses = classNames("fwe-input-text", {
49
+ "fwe-input-text-icon": icon
50
+ }, labelClassName);
34
51
  const hintClasses = classNames("fwe-input-text-info");
35
52
  function handleChange(e) {
36
53
  setInnerValue(e.target.value);
@@ -58,8 +75,9 @@ const TextInput = /*#__PURE__*/forwardRef((_ref, ref) => {
58
75
  type: innerType,
59
76
  value: innerValue,
60
77
  id: id,
78
+ ref: inputRef,
61
79
  ...props
62
- }), /*#__PURE__*/_jsx("span", {
80
+ }), icon, /*#__PURE__*/_jsx("span", {
63
81
  className: "fwe-input-text-label",
64
82
  children: label
65
83
  }), hint && /*#__PURE__*/_jsx("span", {
@@ -31,6 +31,8 @@ const ModalBase = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
31
31
  const mouseDownOnBackdropActive = (0, _react.useRef)(false);
32
32
  const allRefs = (0, _useForkRef.default)(ref, modalRef);
33
33
  const container = modalRef?.current?.ownerDocument || document;
34
+
35
+ // Handle Escape key to close modal
34
36
  (0, _react.useEffect)(() => {
35
37
  const handleKeyDown = event => {
36
38
  if (event.key === "Escape" && isOpen) {
@@ -40,6 +42,52 @@ const ModalBase = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
40
42
  container.addEventListener("keydown", handleKeyDown);
41
43
  return () => container.removeEventListener("keydown", handleKeyDown);
42
44
  }, [onClose, isOpen, container]);
45
+
46
+ // Focus trap implementation
47
+ const handleTabKey = (0, _react.useCallback)(e => {
48
+ if (e.key === "Tab" && modalRef.current && isOpen) {
49
+ // Query for all focusable elements
50
+ const focusableElements = modalRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
51
+ const firstElement = focusableElements[0];
52
+ const lastElement = focusableElements[focusableElements.length - 1];
53
+
54
+ // If shift+tab and we're on the first element, move to the last
55
+ if (e.shiftKey && document.activeElement === firstElement) {
56
+ e.preventDefault();
57
+ lastElement?.focus();
58
+ }
59
+ // If tab and we're on the last element, move to the first
60
+ else if (!e.shiftKey && document.activeElement === lastElement) {
61
+ e.preventDefault();
62
+ firstElement?.focus();
63
+ }
64
+ }
65
+ }, [isOpen]);
66
+
67
+ // Set up the focus trap listener
68
+ (0, _react.useEffect)(() => {
69
+ if (isOpen) {
70
+ container.addEventListener("keydown", handleTabKey);
71
+
72
+ // Auto-focus first focusable element when modal opens
73
+ setTimeout(() => {
74
+ if (modalRef.current) {
75
+ const focusableElements = modalRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
76
+
77
+ // Focus the first input element, or the first focusable element if no input exists
78
+ const firstInput = Array.from(focusableElements).find(el => el.tagName.toLowerCase() === "input");
79
+ if (firstInput) {
80
+ firstInput.focus();
81
+ } else if (focusableElements.length > 0) {
82
+ focusableElements[0].focus();
83
+ }
84
+ }
85
+ }, 100);
86
+ }
87
+ return () => {
88
+ container.removeEventListener("keydown", handleTabKey);
89
+ };
90
+ }, [isOpen, handleTabKey, container]);
43
91
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
44
92
  children: /*#__PURE__*/_reactDom.default.createPortal(/*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
45
93
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactTransitionGroup.CSSTransition, {
@@ -50,7 +50,8 @@ function Prompt(_ref) {
50
50
  onChange: handleChange,
51
51
  label: label,
52
52
  hint: hint,
53
- value: innerValue
53
+ value: innerValue,
54
+ autoFocus: true
54
55
  }),
55
56
  ...props,
56
57
  children: (cancel || ok) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_ModalFooter.default, {
@@ -29,17 +29,34 @@ const TextInput = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
29
29
  hint,
30
30
  label,
31
31
  labelClassName,
32
+ icon,
32
33
  id: idProps,
34
+ autoFocus,
33
35
  ...props
34
36
  } = _ref;
35
37
  const id = (0, _useId.default)(idProps);
36
38
  const [innerValue, setInnerValue] = (0, _react.useState)(value);
39
+ const inputRef = (0, _react.useRef)(null);
37
40
  (0, _react.useEffect)(() => {
38
41
  setInnerValue(value);
39
42
  }, [value]);
43
+
44
+ // Programmatically focus the input instead of using autoFocus attribute
45
+ (0, _react.useEffect)(() => {
46
+ if (autoFocus && inputRef.current) {
47
+ // Short delay to ensure component is fully rendered
48
+ const timer = setTimeout(() => {
49
+ inputRef.current?.focus();
50
+ }, 0);
51
+ return () => clearTimeout(timer);
52
+ }
53
+ return undefined;
54
+ }, [autoFocus]);
40
55
  const supported = ["text", "number", "password", "datetime-local"];
41
56
  const innerType = type && supported.indexOf(type) !== -1 ? type : "text";
42
- const labelClasses = (0, _classnames.default)("fwe-input-text", labelClassName);
57
+ const labelClasses = (0, _classnames.default)("fwe-input-text", {
58
+ "fwe-input-text-icon": icon
59
+ }, labelClassName);
43
60
  const hintClasses = (0, _classnames.default)("fwe-input-text-info");
44
61
  function handleChange(e) {
45
62
  setInnerValue(e.target.value);
@@ -67,8 +84,9 @@ const TextInput = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
67
84
  type: innerType,
68
85
  value: innerValue,
69
86
  id: id,
87
+ ref: inputRef,
70
88
  ...props
71
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
89
+ }), icon, /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
72
90
  className: "fwe-input-text-label",
73
91
  children: label
74
92
  }), hint && /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@festo-ui/react",
3
- "version": "8.2.0-dev.625",
3
+ "version": "8.2.0-dev.629",
4
4
  "author": "Festo UI (styleguide@festo.com)",
5
5
  "copyright": "Copyright (c) 2025 Festo SE & Co. KG. All rights reserved.",
6
6
  "license": "apache-2.0",