@page-speed/forms 0.1.4 → 0.1.6
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/README.md +1 -1
- package/dist/core.cjs +376 -21
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +356 -1
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +376 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +356 -1
- package/dist/index.js.map +1 -1
- package/dist/inputs.cjs +253 -0
- package/dist/inputs.cjs.map +1 -1
- package/dist/inputs.d.cts +77 -1
- package/dist/inputs.d.ts +77 -1
- package/dist/inputs.js +253 -1
- package/dist/inputs.js.map +1 -1
- package/dist/integration.cjs +243 -0
- package/dist/integration.cjs.map +1 -0
- package/dist/integration.d.cts +381 -0
- package/dist/integration.d.ts +381 -0
- package/dist/integration.js +217 -0
- package/dist/integration.js.map +1 -0
- package/dist/upload.cjs +348 -0
- package/dist/upload.cjs.map +1 -0
- package/dist/upload.d.cts +174 -0
- package/dist/upload.d.ts +174 -0
- package/dist/upload.js +326 -0
- package/dist/upload.js.map +1 -0
- package/dist/validation-rules.cjs +231 -75
- package/dist/validation-rules.cjs.map +1 -1
- package/dist/validation-rules.js +215 -1
- package/dist/validation-rules.js.map +1 -1
- package/dist/validation-utils.cjs +133 -43
- package/dist/validation-utils.cjs.map +1 -1
- package/dist/validation-utils.js +125 -1
- package/dist/validation-utils.js.map +1 -1
- package/dist/validation.cjs +364 -115
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.js +339 -2
- package/dist/validation.js.map +1 -1
- package/package.json +14 -4
- package/dist/chunk-2FXAQT7S.cjs +0 -236
- package/dist/chunk-2FXAQT7S.cjs.map +0 -1
- package/dist/chunk-A3UV7BIN.js +0 -357
- package/dist/chunk-A3UV7BIN.js.map +0 -1
- package/dist/chunk-P37YLBFA.cjs +0 -138
- package/dist/chunk-P37YLBFA.cjs.map +0 -1
- package/dist/chunk-WHQMBQNI.js +0 -127
- package/dist/chunk-WHQMBQNI.js.map +0 -1
- package/dist/chunk-YTTOWHBZ.js +0 -217
- package/dist/chunk-YTTOWHBZ.js.map +0 -1
- package/dist/chunk-ZQCPEOB6.cjs +0 -382
- package/dist/chunk-ZQCPEOB6.cjs.map +0 -1
package/dist/inputs.cjs
CHANGED
|
@@ -22,6 +22,7 @@ function _interopNamespace(e) {
|
|
|
22
22
|
|
|
23
23
|
var React6__namespace = /*#__PURE__*/_interopNamespace(React6);
|
|
24
24
|
|
|
25
|
+
// src/inputs/TextInput.tsx
|
|
25
26
|
function TextInput({
|
|
26
27
|
name,
|
|
27
28
|
value,
|
|
@@ -668,9 +669,261 @@ function Select({
|
|
|
668
669
|
);
|
|
669
670
|
}
|
|
670
671
|
Select.displayName = "Select";
|
|
672
|
+
function FileInput({
|
|
673
|
+
name,
|
|
674
|
+
value = [],
|
|
675
|
+
onChange,
|
|
676
|
+
onBlur,
|
|
677
|
+
placeholder = "Choose file(s)...",
|
|
678
|
+
disabled = false,
|
|
679
|
+
required = false,
|
|
680
|
+
error = false,
|
|
681
|
+
className = "",
|
|
682
|
+
accept,
|
|
683
|
+
maxSize = 5 * 1024 * 1024,
|
|
684
|
+
// 5MB default
|
|
685
|
+
maxFiles = 1,
|
|
686
|
+
multiple = false,
|
|
687
|
+
showPreview = true,
|
|
688
|
+
onValidationError,
|
|
689
|
+
onFileRemove,
|
|
690
|
+
...props
|
|
691
|
+
}) {
|
|
692
|
+
const inputRef = React6__namespace.useRef(null);
|
|
693
|
+
const [dragActive, setDragActive] = React6__namespace.useState(false);
|
|
694
|
+
const validateFile = React6__namespace.useCallback(
|
|
695
|
+
(file) => {
|
|
696
|
+
if (accept) {
|
|
697
|
+
const acceptedTypes = accept.split(",").map((t) => t.trim());
|
|
698
|
+
const isValidType = acceptedTypes.some((type) => {
|
|
699
|
+
if (type.startsWith(".")) {
|
|
700
|
+
return file.name.toLowerCase().endsWith(type.toLowerCase());
|
|
701
|
+
} else if (type.endsWith("/*")) {
|
|
702
|
+
const baseType = type.split("/")[0];
|
|
703
|
+
return file.type.startsWith(baseType + "/");
|
|
704
|
+
} else {
|
|
705
|
+
return file.type === type;
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
if (!isValidType) {
|
|
709
|
+
return {
|
|
710
|
+
file,
|
|
711
|
+
error: "type",
|
|
712
|
+
message: `File type "${file.type}" is not accepted. Accepted types: ${accept}`
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (file.size > maxSize) {
|
|
717
|
+
const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2);
|
|
718
|
+
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(2);
|
|
719
|
+
return {
|
|
720
|
+
file,
|
|
721
|
+
error: "size",
|
|
722
|
+
message: `File size ${fileSizeMB}MB exceeds maximum ${maxSizeMB}MB`
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
return null;
|
|
726
|
+
},
|
|
727
|
+
[accept, maxSize]
|
|
728
|
+
);
|
|
729
|
+
const handleFiles = React6__namespace.useCallback(
|
|
730
|
+
(fileList) => {
|
|
731
|
+
if (!fileList || fileList.length === 0) return;
|
|
732
|
+
const newFiles = Array.from(fileList);
|
|
733
|
+
const validationErrors = [];
|
|
734
|
+
const validFiles = [];
|
|
735
|
+
for (const file of newFiles) {
|
|
736
|
+
const validationError = validateFile(file);
|
|
737
|
+
if (validationError) {
|
|
738
|
+
validationErrors.push(validationError);
|
|
739
|
+
} else {
|
|
740
|
+
validFiles.push(file);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const totalFiles = value.length + validFiles.length;
|
|
744
|
+
if (totalFiles > maxFiles) {
|
|
745
|
+
validationErrors.push({
|
|
746
|
+
file: validFiles[0],
|
|
747
|
+
// Use first file as reference
|
|
748
|
+
error: "count",
|
|
749
|
+
message: `Maximum ${maxFiles} file(s) allowed. Attempting to add ${validFiles.length} to existing ${value.length}.`
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
if (validationErrors.length > 0 && onValidationError) {
|
|
753
|
+
onValidationError(validationErrors);
|
|
754
|
+
}
|
|
755
|
+
if (validFiles.length > 0 && totalFiles <= maxFiles) {
|
|
756
|
+
const updatedFiles = multiple ? [...value, ...validFiles] : validFiles;
|
|
757
|
+
onChange(updatedFiles.slice(0, maxFiles));
|
|
758
|
+
}
|
|
759
|
+
if (inputRef.current) {
|
|
760
|
+
inputRef.current.value = "";
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
[value, onChange, validateFile, maxFiles, multiple, onValidationError]
|
|
764
|
+
);
|
|
765
|
+
const handleChange = (e) => {
|
|
766
|
+
handleFiles(e.target.files);
|
|
767
|
+
};
|
|
768
|
+
const handleRemove = (index) => {
|
|
769
|
+
const fileToRemove = value[index];
|
|
770
|
+
const updatedFiles = value.filter((_, i) => i !== index);
|
|
771
|
+
onChange(updatedFiles);
|
|
772
|
+
if (onFileRemove && fileToRemove) {
|
|
773
|
+
onFileRemove(fileToRemove, index);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
const handleDrag = (e) => {
|
|
777
|
+
e.preventDefault();
|
|
778
|
+
e.stopPropagation();
|
|
779
|
+
if (e.type === "dragenter" || e.type === "dragover") {
|
|
780
|
+
setDragActive(true);
|
|
781
|
+
} else if (e.type === "dragleave") {
|
|
782
|
+
setDragActive(false);
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
const handleDrop = (e) => {
|
|
786
|
+
e.preventDefault();
|
|
787
|
+
e.stopPropagation();
|
|
788
|
+
setDragActive(false);
|
|
789
|
+
if (disabled) return;
|
|
790
|
+
handleFiles(e.dataTransfer.files);
|
|
791
|
+
};
|
|
792
|
+
const handleClick = () => {
|
|
793
|
+
inputRef.current?.click();
|
|
794
|
+
};
|
|
795
|
+
const handleKeyDown = (e) => {
|
|
796
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
797
|
+
e.preventDefault();
|
|
798
|
+
handleClick();
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
const formatFileSize = (bytes) => {
|
|
802
|
+
if (bytes === 0) return "0 Bytes";
|
|
803
|
+
const k = 1024;
|
|
804
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
805
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
806
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
|
|
807
|
+
};
|
|
808
|
+
const getPreviewUrl = (file) => {
|
|
809
|
+
if (file.type.startsWith("image/")) {
|
|
810
|
+
return URL.createObjectURL(file);
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
};
|
|
814
|
+
React6__namespace.useEffect(() => {
|
|
815
|
+
return () => {
|
|
816
|
+
value.forEach((file) => {
|
|
817
|
+
const previewUrl = getPreviewUrl(file);
|
|
818
|
+
if (previewUrl) {
|
|
819
|
+
URL.revokeObjectURL(previewUrl);
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
};
|
|
823
|
+
}, [value]);
|
|
824
|
+
const baseClassName = "file-input";
|
|
825
|
+
const errorClassName = error ? "file-input--error" : "";
|
|
826
|
+
const dragClassName = dragActive ? "file-input--drag-active" : "";
|
|
827
|
+
const disabledClassName = disabled ? "file-input--disabled" : "";
|
|
828
|
+
const combinedClassName = `${baseClassName} ${errorClassName} ${dragClassName} ${disabledClassName} ${className}`.trim();
|
|
829
|
+
return /* @__PURE__ */ React6__namespace.createElement("div", { className: combinedClassName }, /* @__PURE__ */ React6__namespace.createElement(
|
|
830
|
+
"input",
|
|
831
|
+
{
|
|
832
|
+
ref: inputRef,
|
|
833
|
+
type: "file",
|
|
834
|
+
name,
|
|
835
|
+
onChange: handleChange,
|
|
836
|
+
onBlur,
|
|
837
|
+
accept,
|
|
838
|
+
multiple,
|
|
839
|
+
disabled,
|
|
840
|
+
required: required && value.length === 0,
|
|
841
|
+
className: "file-input__native",
|
|
842
|
+
"aria-invalid": error || props["aria-invalid"],
|
|
843
|
+
"aria-describedby": props["aria-describedby"],
|
|
844
|
+
"aria-required": required || props["aria-required"],
|
|
845
|
+
style: { display: "none" }
|
|
846
|
+
}
|
|
847
|
+
), /* @__PURE__ */ React6__namespace.createElement(
|
|
848
|
+
"div",
|
|
849
|
+
{
|
|
850
|
+
className: "file-input__dropzone",
|
|
851
|
+
onDragEnter: handleDrag,
|
|
852
|
+
onDragLeave: handleDrag,
|
|
853
|
+
onDragOver: handleDrag,
|
|
854
|
+
onDrop: handleDrop,
|
|
855
|
+
onClick: handleClick,
|
|
856
|
+
onKeyDown: handleKeyDown,
|
|
857
|
+
role: "button",
|
|
858
|
+
tabIndex: disabled ? -1 : 0,
|
|
859
|
+
"aria-label": placeholder,
|
|
860
|
+
"aria-disabled": disabled
|
|
861
|
+
},
|
|
862
|
+
/* @__PURE__ */ React6__namespace.createElement("div", { className: "file-input__dropzone-content" }, /* @__PURE__ */ React6__namespace.createElement(
|
|
863
|
+
"svg",
|
|
864
|
+
{
|
|
865
|
+
className: "file-input__icon",
|
|
866
|
+
width: "48",
|
|
867
|
+
height: "48",
|
|
868
|
+
viewBox: "0 0 24 24",
|
|
869
|
+
fill: "none",
|
|
870
|
+
stroke: "currentColor",
|
|
871
|
+
strokeWidth: "2",
|
|
872
|
+
strokeLinecap: "round",
|
|
873
|
+
strokeLinejoin: "round",
|
|
874
|
+
"aria-hidden": "true"
|
|
875
|
+
},
|
|
876
|
+
/* @__PURE__ */ React6__namespace.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
877
|
+
/* @__PURE__ */ React6__namespace.createElement("polyline", { points: "17 8 12 3 7 8" }),
|
|
878
|
+
/* @__PURE__ */ React6__namespace.createElement("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
879
|
+
), /* @__PURE__ */ React6__namespace.createElement("p", { className: "file-input__placeholder" }, value.length > 0 ? `${value.length} file(s) selected` : placeholder), accept && /* @__PURE__ */ React6__namespace.createElement("p", { className: "file-input__hint" }, "Accepted: ", accept), maxSize && /* @__PURE__ */ React6__namespace.createElement("p", { className: "file-input__hint" }, "Max size: ", formatFileSize(maxSize)))
|
|
880
|
+
), value.length > 0 && /* @__PURE__ */ React6__namespace.createElement("ul", { className: "file-input__list", role: "list" }, value.map((file, index) => {
|
|
881
|
+
const previewUrl = showPreview ? getPreviewUrl(file) : null;
|
|
882
|
+
return /* @__PURE__ */ React6__namespace.createElement("li", { key: `${file.name}-${index}`, className: "file-input__item" }, previewUrl && /* @__PURE__ */ React6__namespace.createElement(
|
|
883
|
+
"img",
|
|
884
|
+
{
|
|
885
|
+
src: previewUrl,
|
|
886
|
+
alt: file.name,
|
|
887
|
+
className: "file-input__preview",
|
|
888
|
+
width: "48",
|
|
889
|
+
height: "48"
|
|
890
|
+
}
|
|
891
|
+
), /* @__PURE__ */ React6__namespace.createElement("div", { className: "file-input__details" }, /* @__PURE__ */ React6__namespace.createElement("span", { className: "file-input__filename" }, file.name), /* @__PURE__ */ React6__namespace.createElement("span", { className: "file-input__filesize" }, formatFileSize(file.size))), /* @__PURE__ */ React6__namespace.createElement(
|
|
892
|
+
"button",
|
|
893
|
+
{
|
|
894
|
+
type: "button",
|
|
895
|
+
onClick: (e) => {
|
|
896
|
+
e.stopPropagation();
|
|
897
|
+
handleRemove(index);
|
|
898
|
+
},
|
|
899
|
+
disabled,
|
|
900
|
+
className: "file-input__remove",
|
|
901
|
+
"aria-label": `Remove ${file.name}`
|
|
902
|
+
},
|
|
903
|
+
/* @__PURE__ */ React6__namespace.createElement(
|
|
904
|
+
"svg",
|
|
905
|
+
{
|
|
906
|
+
width: "20",
|
|
907
|
+
height: "20",
|
|
908
|
+
viewBox: "0 0 24 24",
|
|
909
|
+
fill: "none",
|
|
910
|
+
stroke: "currentColor",
|
|
911
|
+
strokeWidth: "2",
|
|
912
|
+
strokeLinecap: "round",
|
|
913
|
+
strokeLinejoin: "round",
|
|
914
|
+
"aria-hidden": "true"
|
|
915
|
+
},
|
|
916
|
+
/* @__PURE__ */ React6__namespace.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
917
|
+
/* @__PURE__ */ React6__namespace.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
918
|
+
)
|
|
919
|
+
));
|
|
920
|
+
})));
|
|
921
|
+
}
|
|
922
|
+
FileInput.displayName = "FileInput";
|
|
671
923
|
|
|
672
924
|
exports.Checkbox = Checkbox;
|
|
673
925
|
exports.CheckboxGroup = CheckboxGroup;
|
|
926
|
+
exports.FileInput = FileInput;
|
|
674
927
|
exports.Radio = Radio;
|
|
675
928
|
exports.Select = Select;
|
|
676
929
|
exports.TextArea = TextArea;
|