@fovestta2/web-react 1.1.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.
Files changed (55) hide show
  1. package/dist/components/AddUpdateForm.d.ts +64 -0
  2. package/dist/components/AddUpdateForm.js +244 -0
  3. package/dist/components/FvCheckbox.d.ts +12 -0
  4. package/dist/components/FvCheckbox.js +19 -0
  5. package/dist/components/FvDateField.d.ts +19 -0
  6. package/dist/components/FvDateField.js +123 -0
  7. package/dist/components/FvDocumentField.d.ts +22 -0
  8. package/dist/components/FvDocumentField.js +242 -0
  9. package/dist/components/FvDropdown.d.ts +33 -0
  10. package/dist/components/FvDropdown.js +346 -0
  11. package/dist/components/FvEmailField.d.ts +18 -0
  12. package/dist/components/FvEmailField.js +103 -0
  13. package/dist/components/FvEntryField.d.ts +21 -0
  14. package/dist/components/FvEntryField.js +101 -0
  15. package/dist/components/FvEsiField.d.ts +16 -0
  16. package/dist/components/FvEsiField.js +92 -0
  17. package/dist/components/FvFileSelector.d.ts +22 -0
  18. package/dist/components/FvFileSelector.js +153 -0
  19. package/dist/components/FvIbanField.d.ts +16 -0
  20. package/dist/components/FvIbanField.js +92 -0
  21. package/dist/components/FvIfscField.d.ts +16 -0
  22. package/dist/components/FvIfscField.js +92 -0
  23. package/dist/components/FvImageSelector.d.ts +22 -0
  24. package/dist/components/FvImageSelector.js +317 -0
  25. package/dist/components/FvMicrField.d.ts +16 -0
  26. package/dist/components/FvMicrField.js +92 -0
  27. package/dist/components/FvMonthYearField.d.ts +19 -0
  28. package/dist/components/FvMonthYearField.js +139 -0
  29. package/dist/components/FvNameCode.d.ts +23 -0
  30. package/dist/components/FvNameCode.js +239 -0
  31. package/dist/components/FvNumberField.d.ts +21 -0
  32. package/dist/components/FvNumberField.js +98 -0
  33. package/dist/components/FvPasswordField.d.ts +18 -0
  34. package/dist/components/FvPasswordField.js +114 -0
  35. package/dist/components/FvPfField.d.ts +16 -0
  36. package/dist/components/FvPfField.js +106 -0
  37. package/dist/components/FvPhoneField.d.ts +16 -0
  38. package/dist/components/FvPhoneField.js +95 -0
  39. package/dist/components/FvRadioGroup.d.ts +20 -0
  40. package/dist/components/FvRadioGroup.js +64 -0
  41. package/dist/components/FvRichTextEditor.d.ts +20 -0
  42. package/dist/components/FvRichTextEditor.js +199 -0
  43. package/dist/components/FvServicePeriod.d.ts +10 -0
  44. package/dist/components/FvServicePeriod.js +122 -0
  45. package/dist/components/FvTimeField.d.ts +17 -0
  46. package/dist/components/FvTimeField.js +190 -0
  47. package/dist/components/FvToggle.d.ts +12 -0
  48. package/dist/components/FvToggle.js +39 -0
  49. package/dist/components/FvUanField.d.ts +16 -0
  50. package/dist/components/FvUanField.js +92 -0
  51. package/dist/components/QueryForm.d.ts +31 -0
  52. package/dist/components/QueryForm.js +214 -0
  53. package/dist/index.d.ts +28 -0
  54. package/dist/index.js +47 -0
  55. package/package.json +39 -0
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.FvDocumentField = void 0;
27
+ const react_1 = __importStar(require("react"));
28
+ const validation_engine_1 = require("@fovestta2/validation-engine");
29
+ const FvDocumentField = ({ label = 'Document', placeholder = 'Upload', value, schema, disabled = false, accept = 'application/pdf,image/*', maxSize, className = '', errorClassName = '', onChange, onBlur, }) => {
30
+ const [errorMessage, setErrorMessage] = (0, react_1.useState)(null);
31
+ const [showPreview, setShowPreview] = (0, react_1.useState)(false);
32
+ const [previewUrl, setPreviewUrl] = (0, react_1.useState)(null);
33
+ const fileInputRef = (0, react_1.useRef)(null);
34
+ (0, react_1.useEffect)(() => {
35
+ validateValue(value);
36
+ }, [value, schema]);
37
+ // Handle URL revocations when previewUrl changes or component unmounts
38
+ (0, react_1.useEffect)(() => {
39
+ return () => {
40
+ if (previewUrl && previewUrl.startsWith('blob:')) {
41
+ URL.revokeObjectURL(previewUrl);
42
+ }
43
+ };
44
+ }, [previewUrl]);
45
+ const validateValue = (val) => {
46
+ if (!schema)
47
+ return;
48
+ const result = validation_engine_1.Validator.validate(val, schema);
49
+ if (!result.isValid && result.errorKey) {
50
+ setErrorMessage(result.errorKey);
51
+ }
52
+ else {
53
+ setErrorMessage(null);
54
+ }
55
+ };
56
+ const handleFileChange = (e) => {
57
+ const files = e.target.files;
58
+ if (files && files.length > 0) {
59
+ const file = files[0];
60
+ if (maxSize && file.size > maxSize) {
61
+ setErrorMessage('ERR_FILE_SIZE');
62
+ if (fileInputRef.current)
63
+ fileInputRef.current.value = '';
64
+ return;
65
+ }
66
+ const fileInfo = {
67
+ file: file,
68
+ name: file.name,
69
+ size: file.size,
70
+ type: file.type,
71
+ };
72
+ onChange(fileInfo);
73
+ if (fileInputRef.current)
74
+ fileInputRef.current.value = '';
75
+ if (onBlur)
76
+ onBlur();
77
+ }
78
+ };
79
+ const openFileDialog = () => {
80
+ if (!disabled && fileInputRef.current) {
81
+ fileInputRef.current.click();
82
+ }
83
+ };
84
+ const removeFile = (e) => {
85
+ e.stopPropagation();
86
+ if (disabled)
87
+ return;
88
+ onChange(null);
89
+ if (fileInputRef.current) {
90
+ fileInputRef.current.value = '';
91
+ }
92
+ if (onBlur)
93
+ onBlur();
94
+ };
95
+ const viewFile = (e) => {
96
+ e.stopPropagation();
97
+ if (!value)
98
+ return;
99
+ const type = value.type || '';
100
+ const isImage = type.startsWith('image/');
101
+ const isPdf = type === 'application/pdf' || (typeof value.file === 'string' && value.file.toLowerCase().endsWith('.pdf'));
102
+ if (isPdf) {
103
+ downloadFile(); // Using download behavior for PDF mimicking Angular
104
+ }
105
+ else if (isImage) {
106
+ openPreview(value);
107
+ }
108
+ else {
109
+ downloadFile();
110
+ }
111
+ };
112
+ const openPreview = (fileInfo) => {
113
+ if (typeof fileInfo.file === 'string') {
114
+ setPreviewUrl(fileInfo.file);
115
+ }
116
+ else {
117
+ const url = URL.createObjectURL(fileInfo.file);
118
+ setPreviewUrl(url);
119
+ }
120
+ setShowPreview(true);
121
+ };
122
+ const closePreview = () => {
123
+ setShowPreview(false);
124
+ if (previewUrl && previewUrl.startsWith('blob:')) {
125
+ URL.revokeObjectURL(previewUrl);
126
+ }
127
+ setPreviewUrl(null);
128
+ };
129
+ const downloadFile = () => {
130
+ if (!value)
131
+ return;
132
+ let url;
133
+ const name = value.name || 'document';
134
+ if (typeof value.file === 'string') {
135
+ url = value.file;
136
+ }
137
+ else {
138
+ url = URL.createObjectURL(value.file);
139
+ }
140
+ const link = document.createElement('a');
141
+ link.href = url;
142
+ link.download = name;
143
+ document.body.appendChild(link);
144
+ link.click();
145
+ document.body.removeChild(link);
146
+ if (typeof value.file !== 'string') {
147
+ setTimeout(() => URL.revokeObjectURL(url), 100);
148
+ }
149
+ };
150
+ const formatFileSize = (bytes) => {
151
+ if (bytes === undefined || bytes === 0)
152
+ return '0 Bytes';
153
+ const k = 1024;
154
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
155
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
156
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
157
+ };
158
+ const isRequired = () => {
159
+ var _a;
160
+ return ((_a = schema === null || schema === void 0 ? void 0 : schema.rules) === null || _a === void 0 ? void 0 : _a.some((r) => { var _a; return r.name === 'required' && ((_a = r.params) === null || _a === void 0 ? void 0 : _a['enabled']); })) || false;
161
+ };
162
+ const getErrorMessage = () => {
163
+ if (!errorMessage)
164
+ return '';
165
+ const errorMessages = {
166
+ ERR_REQUIRED: 'This field is required',
167
+ ERR_INVALID_FILE: 'Invalid file',
168
+ ERR_FILE_SIZE: 'File size exceeds limit',
169
+ };
170
+ return errorMessages[errorMessage] || errorMessage;
171
+ };
172
+ return (react_1.default.createElement("div", { className: `fv-document-container ${className}`, style: { marginBottom: '16px', display: 'flex', flexDirection: 'column' } },
173
+ label && (react_1.default.createElement("label", { className: "fv-label", style: { marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: disabled ? '#999' : '#333' } },
174
+ label,
175
+ isRequired() && react_1.default.createElement("span", { className: "fv-required", style: { color: '#dc3545', fontWeight: 'bold' } }, " *"))),
176
+ react_1.default.createElement("div", { className: `document-upload-box ${errorMessage ? 'error' : ''} ${disabled ? 'disabled' : ''}`, onClick: openFileDialog, style: {
177
+ display: 'flex',
178
+ alignItems: 'center',
179
+ justifyContent: 'space-between',
180
+ padding: '12px 16px',
181
+ border: errorMessage ? '1px dashed #dc3545' : '1px dashed #8CBBA8',
182
+ borderRadius: '8px',
183
+ backgroundColor: disabled ? '#f9f9f9' : '#F6F9F8',
184
+ cursor: disabled ? 'not-allowed' : 'pointer',
185
+ transition: 'all 0.2sease',
186
+ opacity: disabled ? 0.6 : 1
187
+ } },
188
+ react_1.default.createElement("div", { style: { display: 'flex', alignItems: 'center', flex: 1, overflow: 'hidden' } },
189
+ react_1.default.createElement("span", { className: "doc-icon", style: { fontSize: '24px', marginRight: '12px' } }, "\uD83D\uDCC4"),
190
+ react_1.default.createElement("div", { className: "doc-info", style: { display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden' } }, value ? (react_1.default.createElement(react_1.default.Fragment, null,
191
+ react_1.default.createElement("span", { className: "doc-name", style: { fontSize: '14px', fontWeight: 500, color: '#1f2b41', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, value.name),
192
+ value.size ? (react_1.default.createElement("span", { className: "doc-size", style: { fontSize: '12px', color: '#666' } }, formatFileSize(value.size))) : null)) : (react_1.default.createElement("span", { className: "placeholder-text", style: { fontSize: '14px', color: '#888' } }, placeholder)))),
193
+ value && !disabled && (react_1.default.createElement("div", { className: "doc-actions", style: { display: 'flex', alignItems: 'center', gap: '8px' } },
194
+ react_1.default.createElement("button", { type: "button", className: "action-btn view-btn", onClick: viewFile, title: "View / Download", style: { background: 'none', border: 'none', cursor: 'pointer', padding: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center' } }, "\uD83D\uDC41"),
195
+ react_1.default.createElement("button", { type: "button", className: "action-btn remove-btn", onClick: removeFile, title: "Remove", style: { background: 'none', border: 'none', cursor: 'pointer', padding: '4px', color: '#e74c3c', display: 'flex', alignItems: 'center', justifyContent: 'center' } }, "\u2715")))),
196
+ react_1.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileChange, accept: accept, disabled: disabled, onClick: (e) => { e.stopPropagation(); }, style: { display: 'none' } }),
197
+ errorMessage && (react_1.default.createElement("span", { className: `fv-error-text ${errorClassName}`, style: { marginTop: '4px', fontSize: '12px', color: '#dc3545' } },
198
+ "\u26A0 ",
199
+ getErrorMessage())),
200
+ showPreview && previewUrl && (react_1.default.createElement("div", { className: "preview-modal-overlay", onClick: closePreview, style: {
201
+ position: 'fixed',
202
+ top: 0, left: 0, right: 0, bottom: 0,
203
+ backgroundColor: 'rgba(0,0,0,0.7)',
204
+ display: 'flex',
205
+ alignItems: 'center',
206
+ justifyContent: 'center',
207
+ zIndex: 9999
208
+ } },
209
+ react_1.default.createElement("div", { className: "preview-modal-content", onClick: (e) => e.stopPropagation(), style: {
210
+ position: 'relative',
211
+ maxWidth: '90%',
212
+ maxHeight: '90%',
213
+ backgroundColor: '#fff',
214
+ padding: '10px',
215
+ borderRadius: '8px',
216
+ display: 'flex',
217
+ flexDirection: 'column'
218
+ } },
219
+ react_1.default.createElement("button", { className: "close-preview-btn", onClick: closePreview, style: {
220
+ position: 'absolute',
221
+ top: '-15px',
222
+ right: '-15px',
223
+ background: '#e74c3c',
224
+ color: '#fff',
225
+ border: 'none',
226
+ borderRadius: '50%',
227
+ width: '30px',
228
+ height: '30px',
229
+ cursor: 'pointer',
230
+ fontWeight: 'bold',
231
+ display: 'flex',
232
+ alignItems: 'center',
233
+ justifyContent: 'center',
234
+ boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
235
+ } }, "\u00D7"),
236
+ react_1.default.createElement("img", { src: previewUrl, alt: "Preview", style: {
237
+ maxWidth: '100%',
238
+ maxHeight: 'calc(90vh - 40px)',
239
+ objectFit: 'contain'
240
+ } }))))));
241
+ };
242
+ exports.FvDocumentField = FvDocumentField;
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { ValidationSchema } from '@fovestta2/validation-engine';
3
+ export interface DropdownOption {
4
+ label: string;
5
+ value: string | number;
6
+ }
7
+ export interface DropdownGroup {
8
+ label: string;
9
+ items: DropdownOption[];
10
+ }
11
+ export interface DropdownViewItem {
12
+ isGroupLabel: boolean;
13
+ label: string;
14
+ value?: string | number;
15
+ option?: DropdownOption;
16
+ isChild?: boolean;
17
+ }
18
+ export interface FvDropdownProps {
19
+ label?: string;
20
+ placeholder?: string;
21
+ options: (DropdownOption | DropdownGroup)[];
22
+ value: string | number | null | undefined;
23
+ schema?: ValidationSchema;
24
+ disabled?: boolean;
25
+ className?: string;
26
+ inputClassName?: string;
27
+ labelClassName?: string;
28
+ errorClassName?: string;
29
+ onChange: (value: string | number) => void;
30
+ onBlur?: () => void;
31
+ onFocus?: () => void;
32
+ }
33
+ export declare const FvDropdown: React.FC<FvDropdownProps>;
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.FvDropdown = void 0;
27
+ const react_1 = __importStar(require("react"));
28
+ const validation_engine_1 = require("@fovestta2/validation-engine");
29
+ const FvDropdown = ({ label = '', placeholder = 'Select an option', options = [], value, schema, disabled = false, className = '', inputClassName = '', labelClassName = '', errorClassName = '', onChange, onBlur, onFocus, }) => {
30
+ const [errorMessage, setErrorMessage] = (0, react_1.useState)(null);
31
+ const [isOpen, setIsOpen] = (0, react_1.useState)(false);
32
+ const [searchText, setSearchText] = (0, react_1.useState)('');
33
+ const [filteredOptions, setFilteredOptions] = (0, react_1.useState)([]);
34
+ const [highlightedIndex, setHighlightedIndex] = (0, react_1.useState)(-1);
35
+ const containerRef = (0, react_1.useRef)(null);
36
+ const inputRef = (0, react_1.useRef)(null);
37
+ const listRef = (0, react_1.useRef)(null);
38
+ // Helper type guards
39
+ const isGroup = (item) => {
40
+ return 'items' in item && Array.isArray(item.items);
41
+ };
42
+ const findOption = (val) => {
43
+ for (const item of options) {
44
+ if (isGroup(item)) {
45
+ const found = item.items.find(opt => opt.value === val);
46
+ if (found)
47
+ return found;
48
+ }
49
+ else {
50
+ if (item.value === val)
51
+ return item;
52
+ }
53
+ }
54
+ return undefined;
55
+ };
56
+ const generateViewItems = (opts, term) => {
57
+ const result = [];
58
+ const lowerTerm = term ? term.toLowerCase() : '';
59
+ for (const item of opts) {
60
+ if (isGroup(item)) {
61
+ let matchingItems = [...item.items];
62
+ if (lowerTerm) {
63
+ matchingItems = item.items.filter(opt => opt.label.toLowerCase().includes(lowerTerm));
64
+ }
65
+ if (matchingItems.length > 0) {
66
+ result.push({ isGroupLabel: true, label: item.label });
67
+ matchingItems.forEach(opt => {
68
+ result.push({ isGroupLabel: false, label: opt.label, value: opt.value, option: opt, isChild: true });
69
+ });
70
+ }
71
+ }
72
+ else {
73
+ if (!lowerTerm || item.label.toLowerCase().includes(lowerTerm)) {
74
+ result.push({ isGroupLabel: false, label: item.label, value: item.value, option: item, isChild: false });
75
+ }
76
+ }
77
+ }
78
+ return result;
79
+ };
80
+ const filterOptions = (term) => {
81
+ const nextOpts = generateViewItems(options, term);
82
+ setFilteredOptions(nextOpts);
83
+ if (nextOpts.length === 1 && !nextOpts[0].isGroupLabel) {
84
+ setHighlightedIndex(0);
85
+ }
86
+ else {
87
+ setHighlightedIndex(-1);
88
+ }
89
+ };
90
+ // Sync initial and prop changes
91
+ (0, react_1.useEffect)(() => {
92
+ validateValue(value);
93
+ const selected = findOption(value);
94
+ if (selected) {
95
+ setSearchText(selected.label);
96
+ }
97
+ else if (!value) {
98
+ setSearchText('');
99
+ }
100
+ }, [value, options, schema]);
101
+ // Click outside to close
102
+ (0, react_1.useEffect)(() => {
103
+ const handleClickOutside = (event) => {
104
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
105
+ if (isOpen) {
106
+ closeDropdown();
107
+ if (onBlur)
108
+ onBlur();
109
+ }
110
+ }
111
+ };
112
+ document.addEventListener('mousedown', handleClickOutside);
113
+ return () => {
114
+ document.removeEventListener('mousedown', handleClickOutside);
115
+ };
116
+ }, [isOpen, value, options]);
117
+ const validateValue = (val) => {
118
+ if (!schema)
119
+ return;
120
+ const result = validation_engine_1.Validator.validate(val, schema);
121
+ if (!result.isValid && result.errorKey) {
122
+ setErrorMessage(result.errorKey);
123
+ }
124
+ else {
125
+ setErrorMessage(null);
126
+ }
127
+ };
128
+ const openDropdown = () => {
129
+ if (disabled)
130
+ return;
131
+ setIsOpen(true);
132
+ if (onFocus)
133
+ onFocus();
134
+ const viewItems = generateViewItems(options, searchText);
135
+ setFilteredOptions(viewItems);
136
+ if (value) {
137
+ const idx = viewItems.findIndex(v => !v.isGroupLabel && v.value === value);
138
+ if (idx >= 0)
139
+ setHighlightedIndex(idx);
140
+ }
141
+ else if (viewItems.length === 1 && !viewItems[0].isGroupLabel) {
142
+ setHighlightedIndex(0);
143
+ }
144
+ else {
145
+ setHighlightedIndex(-1);
146
+ }
147
+ };
148
+ const closeDropdown = () => {
149
+ setIsOpen(false);
150
+ setHighlightedIndex(-1);
151
+ // Restore label
152
+ const selected = findOption(value);
153
+ if (selected) {
154
+ setSearchText(selected.label);
155
+ }
156
+ else {
157
+ if (!value)
158
+ setSearchText('');
159
+ }
160
+ validateValue(value);
161
+ };
162
+ const toggleDropdown = () => {
163
+ var _a;
164
+ if (disabled)
165
+ return;
166
+ if (!isOpen) {
167
+ openDropdown();
168
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
169
+ }
170
+ else {
171
+ closeDropdown();
172
+ }
173
+ };
174
+ const handleSearchChange = (e) => {
175
+ const term = e.target.value;
176
+ setSearchText(term);
177
+ filterOptions(term);
178
+ if (!isOpen)
179
+ openDropdown();
180
+ };
181
+ const selectOption = (viewItem) => {
182
+ var _a;
183
+ if (viewItem.isGroupLabel || viewItem.value === undefined)
184
+ return;
185
+ onChange(viewItem.value);
186
+ setSearchText(viewItem.label);
187
+ setIsOpen(false);
188
+ setHighlightedIndex(-1);
189
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
190
+ if (onBlur)
191
+ onBlur();
192
+ };
193
+ const scrollToHighlighted = (index) => {
194
+ if (!listRef.current)
195
+ return;
196
+ const items = listRef.current.querySelectorAll('li');
197
+ const item = items[index];
198
+ if (item) {
199
+ const listRect = listRef.current.getBoundingClientRect();
200
+ const itemRect = item.getBoundingClientRect();
201
+ if (itemRect.bottom > listRect.bottom) {
202
+ listRef.current.scrollTop += (itemRect.bottom - listRect.bottom);
203
+ }
204
+ else if (itemRect.top < listRect.top) {
205
+ listRef.current.scrollTop -= (listRect.top - itemRect.top);
206
+ }
207
+ }
208
+ };
209
+ const moveHighlight = (step) => {
210
+ let nextIndex = highlightedIndex;
211
+ const len = filteredOptions.length;
212
+ if (len === 0)
213
+ return;
214
+ for (let i = 0; i < len; i++) {
215
+ nextIndex = (nextIndex + step + len) % len;
216
+ if (!filteredOptions[nextIndex].isGroupLabel) {
217
+ setHighlightedIndex(nextIndex);
218
+ scrollToHighlighted(nextIndex);
219
+ return;
220
+ }
221
+ }
222
+ };
223
+ const handleKeyDown = (e) => {
224
+ if (!isOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter')) {
225
+ openDropdown();
226
+ e.preventDefault();
227
+ return;
228
+ }
229
+ if (!isOpen)
230
+ return;
231
+ switch (e.key) {
232
+ case 'ArrowDown':
233
+ e.preventDefault();
234
+ moveHighlight(1);
235
+ break;
236
+ case 'ArrowUp':
237
+ e.preventDefault();
238
+ moveHighlight(-1);
239
+ break;
240
+ case 'Enter':
241
+ e.preventDefault();
242
+ if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
243
+ selectOption(filteredOptions[highlightedIndex]);
244
+ }
245
+ break;
246
+ case 'Escape':
247
+ case 'Tab':
248
+ closeDropdown();
249
+ break;
250
+ }
251
+ };
252
+ const isRequired = () => {
253
+ var _a;
254
+ return ((_a = schema === null || schema === void 0 ? void 0 : schema.rules) === null || _a === void 0 ? void 0 : _a.some((r) => { var _a; return r.name === 'required' && ((_a = r.params) === null || _a === void 0 ? void 0 : _a['enabled']); })) || false;
255
+ };
256
+ const getErrorMessage = () => {
257
+ if (!errorMessage)
258
+ return '';
259
+ const errorMessages = {
260
+ ERR_REQUIRED: 'This field is required',
261
+ ERR_INVALID_VALUE: 'Invalid selection',
262
+ ERR_DUPLICATE: 'This selection already exists',
263
+ };
264
+ return errorMessages[errorMessage] || errorMessage;
265
+ };
266
+ return (react_1.default.createElement("div", { ref: containerRef, className: `fv-dropdown-container ${className}`, style: { marginBottom: '16px', display: 'flex', flexDirection: 'column', position: 'relative' }, onKeyDown: handleKeyDown },
267
+ label && (react_1.default.createElement("label", { className: `fv-dropdown-label ${labelClassName}`, style: { marginBottom: '6px', fontSize: '14px', fontWeight: 500, color: disabled ? '#999' : '#333' } },
268
+ label,
269
+ isRequired() && react_1.default.createElement("span", { className: "fv-required", style: { color: '#dc3545', fontWeight: 'bold' } }, " *"))),
270
+ react_1.default.createElement("div", { className: `fv-dropdown-control ${errorMessage ? 'fv-dropdown-error' : ''}`, style: {
271
+ display: 'flex',
272
+ border: errorMessage ? '1px solid #dc3545' : '1px solid #ccc',
273
+ borderRadius: '4px',
274
+ backgroundColor: disabled ? '#f5f5f5' : '#fff',
275
+ cursor: disabled ? 'not-allowed' : 'text',
276
+ position: 'relative'
277
+ }, onClick: (e) => {
278
+ var _a;
279
+ if (disabled)
280
+ return;
281
+ const target = e.target;
282
+ if (target.tagName !== 'INPUT' && target.tagName !== 'BUTTON') {
283
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
284
+ if (!isOpen)
285
+ openDropdown();
286
+ }
287
+ } },
288
+ react_1.default.createElement("input", { ref: inputRef, type: "text", className: `fv-dropdown-input ${inputClassName}`, value: searchText, onChange: handleSearchChange, onFocus: openDropdown, disabled: disabled, placeholder: placeholder, style: {
289
+ flex: 1,
290
+ padding: '10px',
291
+ border: 'none',
292
+ background: 'transparent',
293
+ outline: 'none',
294
+ fontSize: '14px',
295
+ color: '#333',
296
+ ...((disabled) ? { opacity: 0.6 } : {})
297
+ } }),
298
+ react_1.default.createElement("button", { type: "button", onClick: toggleDropdown, disabled: disabled, className: "fv-dropdown-arrow", style: {
299
+ padding: '0 10px',
300
+ background: 'none',
301
+ border: 'none',
302
+ cursor: disabled ? 'not-allowed' : 'pointer',
303
+ display: 'flex',
304
+ alignItems: 'center',
305
+ justifyContent: 'center'
306
+ } },
307
+ react_1.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "#666", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { transform: isOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' } },
308
+ react_1.default.createElement("polyline", { points: "6 9 12 15 18 9" })))),
309
+ isOpen && (react_1.default.createElement("ul", { ref: listRef, className: "fv-dropdown-options", style: {
310
+ position: 'absolute',
311
+ top: '100%',
312
+ left: 0,
313
+ right: 0,
314
+ marginTop: '4px',
315
+ padding: '0',
316
+ margin: '4px 0 0 0',
317
+ listStyle: 'none',
318
+ backgroundColor: '#fff',
319
+ border: '1px solid #ccc',
320
+ borderRadius: '4px',
321
+ boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
322
+ maxHeight: '250px',
323
+ overflowY: 'auto',
324
+ zIndex: 1000
325
+ } }, filteredOptions.length === 0 ? (react_1.default.createElement("li", { style: { padding: '10px', textAlign: 'center', color: '#999', fontSize: '14px' } }, "No options found")) : (filteredOptions.map((item, index) => {
326
+ if (item.isGroupLabel) {
327
+ return (react_1.default.createElement("li", { key: `group-${index}`, style: { padding: '8px 10px', fontWeight: 'bold', backgroundColor: '#f9f9f9', fontSize: '13px', color: '#666', borderBottom: '1px solid #eee' } }, item.label));
328
+ }
329
+ const isSelected = item.value === value;
330
+ const isHighlighted = highlightedIndex === index;
331
+ return (react_1.default.createElement("li", { key: `opt-${index}`, className: "fv-dropdown-option", onClick: (e) => { e.stopPropagation(); selectOption(item); }, onMouseEnter: () => setHighlightedIndex(index), style: {
332
+ padding: '10px 10px 10px',
333
+ paddingLeft: item.isChild ? '24px' : '10px',
334
+ cursor: 'pointer',
335
+ fontSize: '14px',
336
+ backgroundColor: isHighlighted ? '#f0f7ff' : (isSelected ? '#e6f2ff' : '#fff'),
337
+ color: isSelected ? '#007bff' : '#333',
338
+ fontWeight: isSelected ? 500 : 400,
339
+ borderBottom: '1px solid #fafafa'
340
+ } }, item.label));
341
+ })))),
342
+ errorMessage && (react_1.default.createElement("span", { className: `fv-error-text ${errorClassName}`, style: { marginTop: '4px', fontSize: '12px', color: '#dc3545' } },
343
+ "\u26A0 ",
344
+ getErrorMessage()))));
345
+ };
346
+ exports.FvDropdown = FvDropdown;
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { ValidationSchema } from '@fovestta2/validation-engine';
3
+ export interface FvEmailFieldProps {
4
+ label?: string;
5
+ placeholder?: string;
6
+ value: string;
7
+ schema?: ValidationSchema;
8
+ disabled?: boolean;
9
+ readonly?: boolean;
10
+ className?: string;
11
+ inputClassName?: string;
12
+ labelClassName?: string;
13
+ errorClassName?: string;
14
+ onChange: (value: string) => void;
15
+ onBlur?: () => void;
16
+ onFocus?: () => void;
17
+ }
18
+ export declare const FvEmailField: React.FC<FvEmailFieldProps>;