@fajarmaulana/komerce-lp-helper 0.1.1 → 0.2.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/README.md +63 -22
- package/dist/components/Form.d.ts +18 -0
- package/dist/components/Form.js +55 -0
- package/dist/components/LazyBackground.d.ts +3 -0
- package/dist/components/LazyBackground.js +26 -0
- package/dist/components/hooks/index.d.ts +1 -0
- package/dist/components/hooks/index.js +1 -0
- package/dist/components/hooks/useLazyBackground.d.ts +17 -0
- package/dist/components/hooks/useLazyBackground.js +44 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +2 -0
- package/dist/constants/regex.d.ts +40 -0
- package/dist/constants/regex.js +40 -0
- package/dist/hooks/form.d.ts +44 -0
- package/dist/hooks/form.js +94 -0
- package/dist/hooks/sectionObserver.d.ts +8 -0
- package/dist/hooks/sectionObserver.js +41 -0
- package/dist/index.d.ts +15 -11
- package/dist/index.js +15 -11
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/api.d.ts +2 -2
- package/dist/utils/api.js +33 -45
- package/dist/utils/error-provider.d.ts +67 -0
- package/dist/utils/error-provider.js +158 -0
- package/package.json +23 -7
package/README.md
CHANGED
|
@@ -17,38 +17,39 @@ npm install komerce-lp-helper
|
|
|
17
17
|
A wrapper around `fetch` with support for interceptors, caching, and retries.
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
|
-
import { http } from
|
|
20
|
+
import { http } from 'komerce-lp-helper'
|
|
21
21
|
|
|
22
22
|
// GET request
|
|
23
|
-
const users = await http.get(
|
|
23
|
+
const users = await http.get('/users')
|
|
24
24
|
|
|
25
25
|
// POST request with body
|
|
26
|
-
await http.post(
|
|
26
|
+
await http.post('/users', { name: 'John Doe' })
|
|
27
27
|
|
|
28
28
|
// Using cache
|
|
29
|
-
const cachedData = http.getCache(
|
|
29
|
+
const cachedData = http.getCache('my-key')
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
#### `createApi`
|
|
33
33
|
|
|
34
|
-
Creates a new API instance with built-in React hooks (`fetch`, `mutation`, `infinite`) for performing typed data
|
|
34
|
+
Creates a new API instance with built-in React hooks (`fetch`, `mutation`, `infinite`) for performing typed data
|
|
35
|
+
fetching and mutations with progress tracking.
|
|
35
36
|
|
|
36
37
|
```tsx
|
|
37
|
-
import { createApi } from
|
|
38
|
+
import { createApi } from 'komerce-lp-helper'
|
|
38
39
|
|
|
39
|
-
const api = createApi({ baseURL:
|
|
40
|
+
const api = createApi({ baseURL: '/api' })
|
|
40
41
|
|
|
41
42
|
// Fetch
|
|
42
|
-
const { data, isLoading, refetch } = api.fetch<User[]>(
|
|
43
|
+
const { data, isLoading, refetch } = api.fetch<User[]>('/users')
|
|
43
44
|
|
|
44
45
|
// Mutation (POST, PUT, DELETE)
|
|
45
|
-
const { mutate, isLoading, progress } = api.mutation<User, FormData>(
|
|
46
|
+
const { mutate, isLoading, progress } = api.mutation<User, FormData>('/users', { method: 'POST' })
|
|
46
47
|
|
|
47
48
|
// Infinite Fetch
|
|
48
|
-
const { data, fetchNextPage } = api.infinite<User[]>(
|
|
49
|
+
const { data, fetchNextPage } = api.infinite<User[]>('/users', {
|
|
49
50
|
initialOffset: 0,
|
|
50
51
|
setOffset: (lastItems, allItems, lastOffset) => (lastItems.length ? lastOffset + 1 : null),
|
|
51
|
-
})
|
|
52
|
+
})
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
### Hooks
|
|
@@ -58,8 +59,8 @@ const { data, fetchNextPage } = api.infinite<User[]>("/users", {
|
|
|
58
59
|
Returns a debounced version of a value. Useful for delaying expensive operations.
|
|
59
60
|
|
|
60
61
|
```typescript
|
|
61
|
-
import { useDebounce } from
|
|
62
|
-
const debouncedSearchTerm = useDebounce(searchTerm, 500)
|
|
62
|
+
import { useDebounce } from 'komerce-lp-helper'
|
|
63
|
+
const debouncedSearchTerm = useDebounce(searchTerm, 500)
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
#### `useDebounceFunc`
|
|
@@ -67,8 +68,8 @@ const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
|
67
68
|
Returns a debounced version of a callback function.
|
|
68
69
|
|
|
69
70
|
```typescript
|
|
70
|
-
import { useDebounceFunc } from
|
|
71
|
-
const debouncedSearch = useDebounceFunc(
|
|
71
|
+
import { useDebounceFunc } from 'komerce-lp-helper'
|
|
72
|
+
const debouncedSearch = useDebounceFunc(q => fetchResults(q), 300)
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
#### `useConditionalDebounce`
|
|
@@ -80,12 +81,12 @@ Conditionally executes a callback function after a specified debounce delay.
|
|
|
80
81
|
A custom router API built on top of React Router DOM that provides easier navigation methods and state management.
|
|
81
82
|
|
|
82
83
|
```typescript
|
|
83
|
-
import { useRouter } from
|
|
84
|
+
import { useRouter } from 'komerce-lp-helper'
|
|
84
85
|
|
|
85
|
-
const { push, replace, back, query, params } = useRouter()
|
|
86
|
+
const { push, replace, back, query, params } = useRouter()
|
|
86
87
|
|
|
87
88
|
// Navigate with query params
|
|
88
|
-
push({ pathname:
|
|
89
|
+
push({ pathname: '/dashboard', query: { tab: 'settings' } })
|
|
89
90
|
```
|
|
90
91
|
|
|
91
92
|
#### `useQueryParams`
|
|
@@ -93,8 +94,8 @@ push({ pathname: "/dashboard", query: { tab: "settings" } });
|
|
|
93
94
|
A hook for reading and updating query parameters in the URL locally.
|
|
94
95
|
|
|
95
96
|
```typescript
|
|
96
|
-
import { useQueryParams } from
|
|
97
|
-
const [queryObj, updateQuery] = useQueryParams<{ page: string }>()
|
|
97
|
+
import { useQueryParams } from 'komerce-lp-helper'
|
|
98
|
+
const [queryObj, updateQuery] = useQueryParams<{ page: string }>()
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
#### `useSlider`
|
|
@@ -102,8 +103,35 @@ const [queryObj, updateQuery] = useQueryParams<{ page: string }>();
|
|
|
102
103
|
Manages logic for custom slider components, including touch/drag support and navigation.
|
|
103
104
|
|
|
104
105
|
```typescript
|
|
105
|
-
import { useSlider } from
|
|
106
|
-
const slider = useSlider({ data: items })
|
|
106
|
+
import { useSlider } from 'komerce-lp-helper'
|
|
107
|
+
const slider = useSlider({ data: items })
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### `useForm`
|
|
111
|
+
|
|
112
|
+
Manages form fields, retrieval of values, and error handling for both named inputs and standalone fields.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { useForm } from 'komerce-lp-helper'
|
|
116
|
+
|
|
117
|
+
const { form, fields, fieldsWithoutName } = useForm<{ email: string }>(['custom-input-id'])
|
|
118
|
+
|
|
119
|
+
const handleSubmit = e => {
|
|
120
|
+
e.preventDefault()
|
|
121
|
+
const data = fields()
|
|
122
|
+
console.log(data.email.field_value)
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `useSectionObserver`
|
|
127
|
+
|
|
128
|
+
Trigger animations or state changes when a section comes into view.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { useSectionObserver } from 'komerce-lp-helper'
|
|
132
|
+
|
|
133
|
+
const ref = useRef(null)
|
|
134
|
+
useSectionObserver({ triggerRef: ref, targetId: 'target-section' })
|
|
107
135
|
```
|
|
108
136
|
|
|
109
137
|
### Utilities
|
|
@@ -154,6 +182,19 @@ Common DOM and string utilities.
|
|
|
154
182
|
- `acronym(name)`: Generates a 2-letter acronym from a name.
|
|
155
183
|
- `isNotPrimitive(value)`: Checks if a value is an object/array.
|
|
156
184
|
|
|
185
|
+
#### Form Validation
|
|
186
|
+
|
|
187
|
+
Helpers for validating inputs and displaying error messages.
|
|
188
|
+
|
|
189
|
+
- `provideFieldError(params)`: Validates a field against a set of rules and updates the error element.
|
|
190
|
+
- `providePasswordFieldError(params)`: Specialized validation for password strength (length, uppercase, lowercase,
|
|
191
|
+
numbers, symbols).
|
|
192
|
+
|
|
193
|
+
### Components
|
|
194
|
+
|
|
195
|
+
- `Form`: A wrapper component for HTML forms.
|
|
196
|
+
- `LazyBackground`: Component for lazy loading background images.
|
|
197
|
+
|
|
157
198
|
## License
|
|
158
199
|
|
|
159
200
|
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TFormProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A reusable form component that simplifies form submission handling.
|
|
4
|
+
*
|
|
5
|
+
* ### Features:
|
|
6
|
+
* - Prevents the default browser submit behavior.
|
|
7
|
+
* - Collects form values into a `FormData` object.
|
|
8
|
+
* - Passes the `FormData` to the `action` callback.
|
|
9
|
+
* - Exposes the underlying `<form>` element via `ref`.
|
|
10
|
+
*
|
|
11
|
+
* Props for the `Form` component.
|
|
12
|
+
*
|
|
13
|
+
* @property {(formData: FormData) => void} action - Function called when the form is submitted.
|
|
14
|
+
* @property {ComponentPropsWithRef<'form'>} - All attribute of form element.
|
|
15
|
+
* @property {React.ReactNode} [children] - The form’s inner content (inputs, buttons, etc.).
|
|
16
|
+
*/
|
|
17
|
+
declare const Form: ({ action, ref, children, ...props }: TFormProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export default Form;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
13
|
+
var t = {};
|
|
14
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
15
|
+
t[p] = s[p];
|
|
16
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
17
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
18
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
19
|
+
t[p[i]] = s[p[i]];
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
};
|
|
23
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
24
|
+
import { useImperativeHandle, useRef } from 'react';
|
|
25
|
+
/**
|
|
26
|
+
* A reusable form component that simplifies form submission handling.
|
|
27
|
+
*
|
|
28
|
+
* ### Features:
|
|
29
|
+
* - Prevents the default browser submit behavior.
|
|
30
|
+
* - Collects form values into a `FormData` object.
|
|
31
|
+
* - Passes the `FormData` to the `action` callback.
|
|
32
|
+
* - Exposes the underlying `<form>` element via `ref`.
|
|
33
|
+
*
|
|
34
|
+
* Props for the `Form` component.
|
|
35
|
+
*
|
|
36
|
+
* @property {(formData: FormData) => void} action - Function called when the form is submitted.
|
|
37
|
+
* @property {ComponentPropsWithRef<'form'>} - All attribute of form element.
|
|
38
|
+
* @property {React.ReactNode} [children] - The form’s inner content (inputs, buttons, etc.).
|
|
39
|
+
*/
|
|
40
|
+
var Form = function (_a) {
|
|
41
|
+
var action = _a.action, ref = _a.ref, children = _a.children, props = __rest(_a, ["action", "ref", "children"]);
|
|
42
|
+
var innerRef = useRef(null);
|
|
43
|
+
useImperativeHandle(ref, function () { return innerRef.current; });
|
|
44
|
+
var handleSubmit = function (e) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
var activeEl = document.activeElement;
|
|
47
|
+
activeEl.blur();
|
|
48
|
+
if (!innerRef.current)
|
|
49
|
+
return;
|
|
50
|
+
var formData = new FormData(innerRef.current);
|
|
51
|
+
action(formData);
|
|
52
|
+
};
|
|
53
|
+
return (_jsx("form", __assign({}, props, { ref: innerRef, onSubmit: handleSubmit, children: children })));
|
|
54
|
+
};
|
|
55
|
+
export default Form;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { useLazyBackground } from './hooks';
|
|
4
|
+
/**
|
|
5
|
+
* A wrapper component that lazily loads a background image when it enters the viewport.
|
|
6
|
+
*
|
|
7
|
+
* Uses the `useLazyBackground` hook to observe the element via the Intersection Observer API
|
|
8
|
+
* and apply the background image only when visible.
|
|
9
|
+
* This helps improve performance by deferring image loading until needed.
|
|
10
|
+
*
|
|
11
|
+
* Props for the `LazyBackground` component.
|
|
12
|
+
*
|
|
13
|
+
* @property {string} url - The image URL to be lazily loaded as the background.
|
|
14
|
+
* @property {React.ReactNode} [children] - Optional elements or content rendered inside the container.
|
|
15
|
+
* @property {string} [className] - Additional CSS class names applied to the outer container.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* The component renders a `<div>` with a `ref` attached for the lazy observer.
|
|
19
|
+
* The `url` is passed to `useLazyBackground`, which handles loading behavior internally.
|
|
20
|
+
*/
|
|
21
|
+
var LazyBackground = function (_a) {
|
|
22
|
+
var url = _a.url, children = _a.children, className = _a.className;
|
|
23
|
+
var ref = useLazyBackground({ url: url }).ref;
|
|
24
|
+
return (_jsx("div", { ref: ref, className: className || '', children: children }));
|
|
25
|
+
};
|
|
26
|
+
export default memo(LazyBackground);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useLazyBackground';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useLazyBackground';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for the `useLazyBackground` hook.
|
|
3
|
+
*
|
|
4
|
+
* @property url - The URL of the background image to load lazily.
|
|
5
|
+
*/
|
|
6
|
+
export type TLazyBackground = {
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* `useLazyBackground` is a custom React hook that applies a background image
|
|
11
|
+
* to a `<div>` element only when it enters the viewport (lazy-loading).
|
|
12
|
+
*
|
|
13
|
+
* @param {TLazyBackground} props - The props object containing the image URL.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useLazyBackground: ({ url }: TLazyBackground) => {
|
|
16
|
+
ref: import("react").RefObject<HTMLDivElement | null>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
2
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
3
|
+
if (!m) return o;
|
|
4
|
+
var i = m.call(o), r, ar = [], e;
|
|
5
|
+
try {
|
|
6
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
7
|
+
}
|
|
8
|
+
catch (error) { e = { error: error }; }
|
|
9
|
+
finally {
|
|
10
|
+
try {
|
|
11
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
12
|
+
}
|
|
13
|
+
finally { if (e) throw e.error; }
|
|
14
|
+
}
|
|
15
|
+
return ar;
|
|
16
|
+
};
|
|
17
|
+
import { useEffect, useRef } from 'react';
|
|
18
|
+
/**
|
|
19
|
+
* `useLazyBackground` is a custom React hook that applies a background image
|
|
20
|
+
* to a `<div>` element only when it enters the viewport (lazy-loading).
|
|
21
|
+
*
|
|
22
|
+
* @param {TLazyBackground} props - The props object containing the image URL.
|
|
23
|
+
*/
|
|
24
|
+
export var useLazyBackground = function (_a) {
|
|
25
|
+
var url = _a.url;
|
|
26
|
+
var ref = useRef(null);
|
|
27
|
+
useEffect(function () {
|
|
28
|
+
var el = ref.current;
|
|
29
|
+
if (!el)
|
|
30
|
+
return;
|
|
31
|
+
var observer = new IntersectionObserver(function (_a, obs) {
|
|
32
|
+
var _b = __read(_a, 1), entry = _b[0];
|
|
33
|
+
if (entry.isIntersecting) {
|
|
34
|
+
el.style.backgroundImage = "url(".concat(url, ")");
|
|
35
|
+
obs.unobserve(el);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
observer.observe(el);
|
|
39
|
+
return function () { return observer.disconnect(); };
|
|
40
|
+
}, [url]);
|
|
41
|
+
return {
|
|
42
|
+
ref: ref,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const VALID_EMAIL: {
|
|
2
|
+
re: RegExp;
|
|
3
|
+
text: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const DOTALPHANUM: {
|
|
6
|
+
re: RegExp;
|
|
7
|
+
text: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const REPEATED_DOT: {
|
|
10
|
+
re: RegExp;
|
|
11
|
+
text: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const NUMBER_ONLY: {
|
|
14
|
+
re: RegExp;
|
|
15
|
+
text: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const ALPHASPACE: {
|
|
18
|
+
re: RegExp;
|
|
19
|
+
text: string;
|
|
20
|
+
};
|
|
21
|
+
export declare const ANY_UPPERCASE: {
|
|
22
|
+
re: RegExp;
|
|
23
|
+
text: string;
|
|
24
|
+
};
|
|
25
|
+
export declare const ANY_LOWERCASE: {
|
|
26
|
+
re: RegExp;
|
|
27
|
+
text: string;
|
|
28
|
+
};
|
|
29
|
+
export declare const ANY_NUMBER: {
|
|
30
|
+
re: RegExp;
|
|
31
|
+
text: string;
|
|
32
|
+
};
|
|
33
|
+
export declare const ANY_SYMBOL: {
|
|
34
|
+
re: RegExp;
|
|
35
|
+
text: string;
|
|
36
|
+
};
|
|
37
|
+
export declare const NO_SPACE: {
|
|
38
|
+
re: RegExp;
|
|
39
|
+
text: string;
|
|
40
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export var VALID_EMAIL = {
|
|
2
|
+
re: /^(?![.])[A-Za-z0-9._-]+(?<![.])@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,4}$/,
|
|
3
|
+
text: 'masukkan email yang valid',
|
|
4
|
+
};
|
|
5
|
+
export var DOTALPHANUM = {
|
|
6
|
+
re: /^[a-zA-Z0-9._-]+$/,
|
|
7
|
+
text: " hanya boleh berisi huruf, angka, '.', '_' atau '-'",
|
|
8
|
+
};
|
|
9
|
+
export var REPEATED_DOT = {
|
|
10
|
+
re: /(\.\.|__|--)/,
|
|
11
|
+
text: ' tidak boleh berisi simbol berulang',
|
|
12
|
+
};
|
|
13
|
+
export var NUMBER_ONLY = {
|
|
14
|
+
re: /[^\d]/,
|
|
15
|
+
text: ' hanya boleh berisi angka',
|
|
16
|
+
};
|
|
17
|
+
export var ALPHASPACE = {
|
|
18
|
+
re: /^[A-Za-z]+(?: [A-Za-z]+)*$/,
|
|
19
|
+
text: ' hanya boleh berisi huruf dan spasi antar kata',
|
|
20
|
+
};
|
|
21
|
+
export var ANY_UPPERCASE = {
|
|
22
|
+
re: /[A-Z]/,
|
|
23
|
+
text: ' harus berisi minimal 1 huruf kapital',
|
|
24
|
+
};
|
|
25
|
+
export var ANY_LOWERCASE = {
|
|
26
|
+
re: /[a-z]/,
|
|
27
|
+
text: ' harus berisi minimal 1 huruf kecil',
|
|
28
|
+
};
|
|
29
|
+
export var ANY_NUMBER = {
|
|
30
|
+
re: /\d/,
|
|
31
|
+
text: ' harus berisi minimal 1 angka',
|
|
32
|
+
};
|
|
33
|
+
export var ANY_SYMBOL = {
|
|
34
|
+
re: /[\W+_]/,
|
|
35
|
+
text: ' harus berisi minimal 1 simbol',
|
|
36
|
+
};
|
|
37
|
+
export var NO_SPACE = {
|
|
38
|
+
re: /^\S+$/,
|
|
39
|
+
text: ' tidak boleh berisi spasi',
|
|
40
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TPrimitive } from '@/types';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a single form field item, containing its value, element ID, and related helper elements.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of the field value.
|
|
6
|
+
* @property {T} field_value - The current value of the field (string, number, boolean, etc.).
|
|
7
|
+
* @property {string} field_id - The ID attribute of the input element.
|
|
8
|
+
* @property {HTMLElement | null} [field_error] - The element that displays the error message (e.g. `#field_error`).
|
|
9
|
+
* @property {HTMLElement | null} [field_info] - The element that displays extra info (e.g. `#field_info`).
|
|
10
|
+
*/
|
|
11
|
+
export type TFieldItem<T> = {
|
|
12
|
+
field_value: T;
|
|
13
|
+
field_id: string;
|
|
14
|
+
field_error?: HTMLElement | null;
|
|
15
|
+
field_info?: HTMLElement | null;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Represents a collection of fields keyed by their input names, where each key maps to a `TFieldItem`.
|
|
19
|
+
*
|
|
20
|
+
* @template T - The record type mapping field names to primitive values.
|
|
21
|
+
*/
|
|
22
|
+
type TFields<T extends Record<string, TPrimitive>> = {
|
|
23
|
+
[K in keyof T & string]: TFieldItem<T[K]>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A custom React hook for managing form fields and their associated elements.
|
|
27
|
+
*
|
|
28
|
+
* This hook provides access to both named form inputs (retrieved from an HTML `<form>`)
|
|
29
|
+
* and standalone fields that do not belong to the form (`fieldsWithoutName`).
|
|
30
|
+
*
|
|
31
|
+
* It simplifies working with dynamic forms by returning structured data that includes
|
|
32
|
+
* field values, IDs, and linked error/info elements.
|
|
33
|
+
*
|
|
34
|
+
* @template T - The type for form fields inside the `<form>`.
|
|
35
|
+
* @template U - The type for standalone fields outside the `<form>`.
|
|
36
|
+
*
|
|
37
|
+
* @param {string[]} [fieldsWithoutNameIds=[]] - A list of field IDs that exist outside the form element.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useForm<T extends Record<string, TPrimitive>, U extends Record<string, TPrimitive> = Record<string, TPrimitive>>(fieldsWithoutNameIds?: (keyof U & string)[]): {
|
|
40
|
+
form: import("react").RefObject<HTMLFormElement | null>;
|
|
41
|
+
fields: () => TFields<T>;
|
|
42
|
+
fieldsWithoutName: () => TFields<U>;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var __values = (this && this.__values) || function(o) {
|
|
2
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
3
|
+
if (m) return m.call(o);
|
|
4
|
+
if (o && typeof o.length === "number") return {
|
|
5
|
+
next: function () {
|
|
6
|
+
if (o && i >= o.length) o = void 0;
|
|
7
|
+
return { value: o && o[i++], done: !o };
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
11
|
+
};
|
|
12
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
13
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
14
|
+
if (!m) return o;
|
|
15
|
+
var i = m.call(o), r, ar = [], e;
|
|
16
|
+
try {
|
|
17
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
18
|
+
}
|
|
19
|
+
catch (error) { e = { error: error }; }
|
|
20
|
+
finally {
|
|
21
|
+
try {
|
|
22
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
23
|
+
}
|
|
24
|
+
finally { if (e) throw e.error; }
|
|
25
|
+
}
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
import { useRef } from 'react';
|
|
29
|
+
import { getById } from '@/utils/general';
|
|
30
|
+
/**
|
|
31
|
+
* A custom React hook for managing form fields and their associated elements.
|
|
32
|
+
*
|
|
33
|
+
* This hook provides access to both named form inputs (retrieved from an HTML `<form>`)
|
|
34
|
+
* and standalone fields that do not belong to the form (`fieldsWithoutName`).
|
|
35
|
+
*
|
|
36
|
+
* It simplifies working with dynamic forms by returning structured data that includes
|
|
37
|
+
* field values, IDs, and linked error/info elements.
|
|
38
|
+
*
|
|
39
|
+
* @template T - The type for form fields inside the `<form>`.
|
|
40
|
+
* @template U - The type for standalone fields outside the `<form>`.
|
|
41
|
+
*
|
|
42
|
+
* @param {string[]} [fieldsWithoutNameIds=[]] - A list of field IDs that exist outside the form element.
|
|
43
|
+
*/
|
|
44
|
+
export function useForm(fieldsWithoutNameIds) {
|
|
45
|
+
if (fieldsWithoutNameIds === void 0) { fieldsWithoutNameIds = []; }
|
|
46
|
+
var form = useRef(null);
|
|
47
|
+
var fields = function () {
|
|
48
|
+
var e_1, _a;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (!form.current)
|
|
51
|
+
return result;
|
|
52
|
+
var formData = new FormData(form.current);
|
|
53
|
+
try {
|
|
54
|
+
for (var _b = __values(formData.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
55
|
+
var _d = __read(_c.value, 2), name_1 = _d[0], value = _d[1];
|
|
56
|
+
var key = name_1;
|
|
57
|
+
var input = form.current.querySelector("[name=\"".concat(key, "\"]"));
|
|
58
|
+
var fieldValue = value;
|
|
59
|
+
if ((input === null || input === void 0 ? void 0 : input.type) === 'checkbox')
|
|
60
|
+
fieldValue = input.checked;
|
|
61
|
+
result[key] = {
|
|
62
|
+
field_value: fieldValue,
|
|
63
|
+
field_id: (input === null || input === void 0 ? void 0 : input.id) || '',
|
|
64
|
+
field_error: form.current.querySelector("#".concat(key, "_error")),
|
|
65
|
+
field_info: form.current.querySelector("#".concat(key, "_info")),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
70
|
+
finally {
|
|
71
|
+
try {
|
|
72
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
73
|
+
}
|
|
74
|
+
finally { if (e_1) throw e_1.error; }
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
};
|
|
78
|
+
var fieldsWithoutName = function () {
|
|
79
|
+
var result = {};
|
|
80
|
+
fieldsWithoutNameIds.forEach(function (id) {
|
|
81
|
+
var field = getById(id);
|
|
82
|
+
var key = id;
|
|
83
|
+
var value = field.type === 'radio' || field.type === 'checkbox' ? field.checked : field.value;
|
|
84
|
+
result[key] = {
|
|
85
|
+
field_value: value,
|
|
86
|
+
field_id: field.id,
|
|
87
|
+
field_error: getById("".concat(key, "_error")),
|
|
88
|
+
field_info: getById("".concat(key, "_info")),
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
return { form: form, fields: fields, fieldsWithoutName: fieldsWithoutName };
|
|
94
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
type TSectionObserverProps = {
|
|
3
|
+
triggerRef: RefObject<HTMLElement | null>;
|
|
4
|
+
targetId: string;
|
|
5
|
+
threshold?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const useSectionObserver: ({ triggerRef, targetId, threshold }: TSectionObserverProps) => void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
2
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
3
|
+
if (!m) return o;
|
|
4
|
+
var i = m.call(o), r, ar = [], e;
|
|
5
|
+
try {
|
|
6
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
7
|
+
}
|
|
8
|
+
catch (error) { e = { error: error }; }
|
|
9
|
+
finally {
|
|
10
|
+
try {
|
|
11
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
12
|
+
}
|
|
13
|
+
finally { if (e) throw e.error; }
|
|
14
|
+
}
|
|
15
|
+
return ar;
|
|
16
|
+
};
|
|
17
|
+
import { useEffect } from 'react';
|
|
18
|
+
import { getById } from '@/utils/general';
|
|
19
|
+
export var useSectionObserver = function (_a) {
|
|
20
|
+
var triggerRef = _a.triggerRef, targetId = _a.targetId, _b = _a.threshold, threshold = _b === void 0 ? 0.8 : _b;
|
|
21
|
+
useEffect(function () {
|
|
22
|
+
var container = getById(targetId);
|
|
23
|
+
var trigger = triggerRef.current;
|
|
24
|
+
if (!trigger || !container)
|
|
25
|
+
return;
|
|
26
|
+
var observer = new IntersectionObserver(function (_a) {
|
|
27
|
+
var _b = __read(_a, 1), entry = _b[0];
|
|
28
|
+
if (entry.isIntersecting) {
|
|
29
|
+
container.setAttribute('data-visible', 'true');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (entry.boundingClientRect.top > 0) {
|
|
33
|
+
container.setAttribute('data-visible', 'false');
|
|
34
|
+
}
|
|
35
|
+
}, { threshold: threshold });
|
|
36
|
+
observer.observe(trigger);
|
|
37
|
+
return function () {
|
|
38
|
+
observer.disconnect();
|
|
39
|
+
};
|
|
40
|
+
}, [triggerRef, targetId, threshold]);
|
|
41
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export
|
|
1
|
+
export * from './components';
|
|
2
|
+
export * from './constants';
|
|
3
|
+
export * from './hooks/debounce';
|
|
4
|
+
export * from './hooks/form';
|
|
5
|
+
export * from './hooks/router';
|
|
6
|
+
export * from './hooks/sectionObserver';
|
|
7
|
+
export * from './hooks/slider';
|
|
8
|
+
export * from './types';
|
|
9
|
+
export * from './utils/api';
|
|
10
|
+
export * from './utils/cookie';
|
|
11
|
+
export * from './utils/error-provider';
|
|
12
|
+
export * from './utils/file';
|
|
13
|
+
export * from './utils/general';
|
|
14
|
+
export * from './utils/local';
|
|
15
|
+
export { default as createApi } from './utils/useApi';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export
|
|
1
|
+
export * from './components';
|
|
2
|
+
export * from './constants';
|
|
3
|
+
export * from './hooks/debounce';
|
|
4
|
+
export * from './hooks/form';
|
|
5
|
+
export * from './hooks/router';
|
|
6
|
+
export * from './hooks/sectionObserver';
|
|
7
|
+
export * from './hooks/slider';
|
|
8
|
+
export * from './types';
|
|
9
|
+
export * from './utils/api';
|
|
10
|
+
export * from './utils/cookie';
|
|
11
|
+
export * from './utils/error-provider';
|
|
12
|
+
export * from './utils/file';
|
|
13
|
+
export * from './utils/general';
|
|
14
|
+
export * from './utils/local';
|
|
15
|
+
export { default as createApi } from './utils/useApi';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export type * from
|
|
1
|
+
export type * from './general.d.ts';
|
|
2
|
+
export type * from './meta.d.ts';
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { TPrimitive } from '@/types';
|
|
2
2
|
export type TProgress = {
|
|
3
3
|
loaded: number;
|
|
4
4
|
total: number;
|
|
@@ -38,7 +38,7 @@ export type TApiConfig = {
|
|
|
38
38
|
/** Endpoint URL (relative or absolute) */
|
|
39
39
|
url: string;
|
|
40
40
|
/** HTTP method for the request */
|
|
41
|
-
method?:
|
|
41
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
42
42
|
/** Request body content */
|
|
43
43
|
body?: globalThis.BodyInit;
|
|
44
44
|
/** Request mode (e.g., 'cors', 'same-origin') */
|
package/dist/utils/api.js
CHANGED
|
@@ -83,13 +83,13 @@ export var buildURL = function (url, params) {
|
|
|
83
83
|
.map(function (_a) {
|
|
84
84
|
var _b = __read(_a, 2), key = _b[0], value = _b[1];
|
|
85
85
|
if (Array.isArray(value)) {
|
|
86
|
-
var joined = value.map(function (v) { return encodeURIComponent(v); }).join(
|
|
86
|
+
var joined = value.map(function (v) { return encodeURIComponent(v); }).join(',');
|
|
87
87
|
return "".concat(encodeURIComponent(key), "=").concat(joined);
|
|
88
88
|
}
|
|
89
89
|
return "".concat(encodeURIComponent(key), "=").concat(encodeURIComponent(value));
|
|
90
90
|
})
|
|
91
|
-
.join(
|
|
92
|
-
return url.includes(
|
|
91
|
+
.join('&');
|
|
92
|
+
return url.includes('?') ? "".concat(url, "&").concat(queryString) : "".concat(url, "?").concat(queryString);
|
|
93
93
|
};
|
|
94
94
|
var ApiMeta = /** @class */ (function (_super) {
|
|
95
95
|
__extends(ApiMeta, _super);
|
|
@@ -106,9 +106,9 @@ var ApiInstance = /** @class */ (function () {
|
|
|
106
106
|
function ApiInstance(options) {
|
|
107
107
|
if (options === void 0) { options = {}; }
|
|
108
108
|
this.interceptors = {};
|
|
109
|
-
this.baseURL = options.baseURL ||
|
|
109
|
+
this.baseURL = options.baseURL || '';
|
|
110
110
|
this.defaultHeaders = options.headers || {};
|
|
111
|
-
this.source = "komcards-".concat(options.source ||
|
|
111
|
+
this.source = "komcards-".concat(options.source || 'default');
|
|
112
112
|
this.cache = new Map();
|
|
113
113
|
this.maxCacheSize = options.maxCacheSize || 100;
|
|
114
114
|
this.cacheAccessOrder = [];
|
|
@@ -141,7 +141,7 @@ var ApiInstance = /** @class */ (function () {
|
|
|
141
141
|
return __generator(this, function (_a) {
|
|
142
142
|
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
143
143
|
var xhr = new XMLHttpRequest();
|
|
144
|
-
var method = (init.method ||
|
|
144
|
+
var method = (init.method || 'GET').toUpperCase();
|
|
145
145
|
xhr.open(method, input);
|
|
146
146
|
if (init.headers) {
|
|
147
147
|
var headers = new Headers(init.headers);
|
|
@@ -150,7 +150,7 @@ var ApiInstance = /** @class */ (function () {
|
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
if (onUpload && xhr.upload) {
|
|
153
|
-
xhr.upload.addEventListener(
|
|
153
|
+
xhr.upload.addEventListener('progress', function (e) {
|
|
154
154
|
var total = e.lengthComputable ? e.total : 0;
|
|
155
155
|
var percentage = e.lengthComputable ? Math.round((e.loaded / e.total) * 100) : 0;
|
|
156
156
|
onUpload({
|
|
@@ -161,8 +161,8 @@ var ApiInstance = /** @class */ (function () {
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
if (onDownload) {
|
|
164
|
-
xhr.responseType =
|
|
165
|
-
xhr.addEventListener(
|
|
164
|
+
xhr.responseType = 'blob';
|
|
165
|
+
xhr.addEventListener('progress', function (e) {
|
|
166
166
|
var total = e.lengthComputable ? e.total : 0;
|
|
167
167
|
var percentage = e.lengthComputable ? Math.round((e.loaded / e.total) * 100) : 0;
|
|
168
168
|
onDownload({
|
|
@@ -173,18 +173,18 @@ var ApiInstance = /** @class */ (function () {
|
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
if (init.signal) {
|
|
176
|
-
init.signal.addEventListener(
|
|
176
|
+
init.signal.addEventListener('abort', function () {
|
|
177
177
|
xhr.abort();
|
|
178
|
-
reject(new DOMException(
|
|
178
|
+
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
181
|
xhr.onload = function () {
|
|
182
182
|
var headers = new Headers();
|
|
183
183
|
xhr
|
|
184
184
|
.getAllResponseHeaders()
|
|
185
|
-
.split(
|
|
185
|
+
.split('\r\n')
|
|
186
186
|
.forEach(function (line) {
|
|
187
|
-
var parts = line.split(
|
|
187
|
+
var parts = line.split(': ');
|
|
188
188
|
if (parts.length === 2) {
|
|
189
189
|
headers.append(parts[0], parts[1]);
|
|
190
190
|
}
|
|
@@ -196,8 +196,8 @@ var ApiInstance = /** @class */ (function () {
|
|
|
196
196
|
});
|
|
197
197
|
resolve(response);
|
|
198
198
|
};
|
|
199
|
-
xhr.onerror = function () { return reject(new TypeError(
|
|
200
|
-
xhr.ontimeout = function () { return reject(new TypeError(
|
|
199
|
+
xhr.onerror = function () { return reject(new TypeError('Network request failed')); };
|
|
200
|
+
xhr.ontimeout = function () { return reject(new TypeError('Network request timeout')); };
|
|
201
201
|
xhr.send(init.body);
|
|
202
202
|
})];
|
|
203
203
|
});
|
|
@@ -242,7 +242,7 @@ var ApiInstance = /** @class */ (function () {
|
|
|
242
242
|
case 6: return [2 /*return*/, { value: res }];
|
|
243
243
|
case 7:
|
|
244
244
|
err_1 = _b.sent();
|
|
245
|
-
if (err_1 instanceof Error && err_1.name ===
|
|
245
|
+
if (err_1 instanceof Error && err_1.name === 'AbortError') {
|
|
246
246
|
throw err_1;
|
|
247
247
|
}
|
|
248
248
|
lastErr = err_1;
|
|
@@ -356,7 +356,7 @@ var ApiInstance = /** @class */ (function () {
|
|
|
356
356
|
case 1:
|
|
357
357
|
finalConfig = _d.sent();
|
|
358
358
|
cacheKey = this.buildcacheKey(finalConfig);
|
|
359
|
-
if (finalConfig.cache && finalConfig.cache.enabled && finalConfig.method ===
|
|
359
|
+
if (finalConfig.cache && finalConfig.cache.enabled && finalConfig.method === 'GET') {
|
|
360
360
|
cached = this.getCacheEntry(cacheKey);
|
|
361
361
|
if (cached) {
|
|
362
362
|
expired = cached.ttl && Date.now() - cached.timestamp > cached.ttl;
|
|
@@ -370,13 +370,9 @@ var ApiInstance = /** @class */ (function () {
|
|
|
370
370
|
finalURL = buildURL(url, finalConfig.params);
|
|
371
371
|
isFormData = finalConfig.body instanceof FormData;
|
|
372
372
|
return [4 /*yield*/, this.fetchWithRetry(finalURL, {
|
|
373
|
-
method: finalConfig.method ||
|
|
374
|
-
headers: __assign(__assign(__assign({}, (isFormData ? {} : {
|
|
375
|
-
body: isFormData
|
|
376
|
-
? finalConfig.body
|
|
377
|
-
: finalConfig.body
|
|
378
|
-
? JSON.stringify(finalConfig.body)
|
|
379
|
-
: undefined,
|
|
373
|
+
method: finalConfig.method || 'GET',
|
|
374
|
+
headers: __assign(__assign(__assign({}, (isFormData ? {} : { 'Content-Type': 'application/json' })), this.defaultHeaders), finalConfig.headers),
|
|
375
|
+
body: isFormData ? finalConfig.body : finalConfig.body ? JSON.stringify(finalConfig.body) : undefined,
|
|
380
376
|
}, (_b = finalConfig.retry) !== null && _b !== void 0 ? _b : 0, finalConfig.signal, finalConfig.onUpload, finalConfig.onDownload)];
|
|
381
377
|
case 2:
|
|
382
378
|
response = _d.sent();
|
|
@@ -404,15 +400,15 @@ var ApiInstance = /** @class */ (function () {
|
|
|
404
400
|
errorText = _d.sent();
|
|
405
401
|
throw new Error(errorText || finalResponse.statusText);
|
|
406
402
|
case 8:
|
|
407
|
-
contentType = finalResponse.headers.get(
|
|
403
|
+
contentType = finalResponse.headers.get('content-type') || '';
|
|
408
404
|
data = void 0;
|
|
409
|
-
if (!contentType.includes(
|
|
405
|
+
if (!contentType.includes('application/json')) return [3 /*break*/, 10];
|
|
410
406
|
return [4 /*yield*/, finalResponse.json()];
|
|
411
407
|
case 9:
|
|
412
408
|
data = (_d.sent());
|
|
413
409
|
return [3 /*break*/, 14];
|
|
414
410
|
case 10:
|
|
415
|
-
if (!contentType.includes(
|
|
411
|
+
if (!contentType.includes('text/')) return [3 /*break*/, 12];
|
|
416
412
|
return [4 /*yield*/, finalResponse.text()];
|
|
417
413
|
case 11:
|
|
418
414
|
data = (_d.sent());
|
|
@@ -422,7 +418,7 @@ var ApiInstance = /** @class */ (function () {
|
|
|
422
418
|
data = (_d.sent());
|
|
423
419
|
_d.label = 14;
|
|
424
420
|
case 14:
|
|
425
|
-
if (finalConfig.cache && finalConfig.cache.enabled && finalConfig.method ===
|
|
421
|
+
if (finalConfig.cache && finalConfig.cache.enabled && finalConfig.method === 'GET') {
|
|
426
422
|
this.setCacheEntry(cacheKey, {
|
|
427
423
|
data: data,
|
|
428
424
|
timestamp: Date.now(),
|
|
@@ -440,23 +436,23 @@ var ApiInstance = /** @class */ (function () {
|
|
|
440
436
|
};
|
|
441
437
|
ApiInstance.prototype.get = function (url, config) {
|
|
442
438
|
if (config === void 0) { config = {}; }
|
|
443
|
-
return this.request(__assign(__assign({}, config), { url: url, method:
|
|
439
|
+
return this.request(__assign(__assign({}, config), { url: url, method: 'GET' }));
|
|
444
440
|
};
|
|
445
441
|
ApiInstance.prototype.post = function (url, body, config) {
|
|
446
442
|
if (config === void 0) { config = {}; }
|
|
447
|
-
return this.request(__assign(__assign({}, config), { url: url, method:
|
|
443
|
+
return this.request(__assign(__assign({}, config), { url: url, method: 'POST', body: body }));
|
|
448
444
|
};
|
|
449
445
|
ApiInstance.prototype.put = function (url, body, config) {
|
|
450
446
|
if (config === void 0) { config = {}; }
|
|
451
|
-
return this.request(__assign(__assign({}, config), { url: url, method:
|
|
447
|
+
return this.request(__assign(__assign({}, config), { url: url, method: 'PUT', body: body }));
|
|
452
448
|
};
|
|
453
449
|
ApiInstance.prototype.patch = function (url, body, config) {
|
|
454
450
|
if (config === void 0) { config = {}; }
|
|
455
|
-
return this.request(__assign(__assign({}, config), { url: url, method:
|
|
451
|
+
return this.request(__assign(__assign({}, config), { url: url, method: 'PATCH', body: body }));
|
|
456
452
|
};
|
|
457
453
|
ApiInstance.prototype.delete = function (url, body, config) {
|
|
458
454
|
if (config === void 0) { config = {}; }
|
|
459
|
-
return this.request(__assign(__assign({}, config), { url: url, method:
|
|
455
|
+
return this.request(__assign(__assign({}, config), { url: url, method: 'DELETE', body: body }));
|
|
460
456
|
};
|
|
461
457
|
ApiInstance.prototype.getCache = function (key) {
|
|
462
458
|
var entry = this.getCacheEntry(key);
|
|
@@ -478,18 +474,10 @@ export { ApiInstance };
|
|
|
478
474
|
var instance = new ApiInstance();
|
|
479
475
|
var http = {
|
|
480
476
|
get: function (url, config) { return instance.get(url, config); },
|
|
481
|
-
post: function (url, body, config) {
|
|
482
|
-
|
|
483
|
-
},
|
|
484
|
-
|
|
485
|
-
return instance.put(url, body, config);
|
|
486
|
-
},
|
|
487
|
-
patch: function (url, body, config) {
|
|
488
|
-
return instance.patch(url, body, config);
|
|
489
|
-
},
|
|
490
|
-
delete: function (url, body, config) {
|
|
491
|
-
return instance.delete(url, body, config);
|
|
492
|
-
},
|
|
477
|
+
post: function (url, body, config) { return instance.post(url, body, config); },
|
|
478
|
+
put: function (url, body, config) { return instance.put(url, body, config); },
|
|
479
|
+
patch: function (url, body, config) { return instance.patch(url, body, config); },
|
|
480
|
+
delete: function (url, body, config) { return instance.delete(url, body, config); },
|
|
493
481
|
getCache: function (key) { return instance.getCache(key); },
|
|
494
482
|
setCache: function (key, data, ttl) { return instance.setCache(key, data, ttl); },
|
|
495
483
|
removeCache: function (key) { return instance.removeCache(key); },
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { TFieldItem } from '@/hooks/form';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a generic input field and updates its associated error element and label styling based on defined rules.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of the input field value.
|
|
6
|
+
*
|
|
7
|
+
* @param {TFieldItem<T> & { rules: Record<string, boolean> }} params - The field metadata and validation rules.
|
|
8
|
+
* @param {string} params.field_id - The ID of the input element being validated.
|
|
9
|
+
* @param {T} params.field_value - The current value of the input field.
|
|
10
|
+
* @param {HTMLElement | null} params.field_error - The error message element associated with this field.
|
|
11
|
+
* @param {Record<string, boolean>} params.rules - A mapping of error messages to their boolean validation results (`true` = invalid).
|
|
12
|
+
*
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* provideFieldError({
|
|
17
|
+
* field_id: 'email',
|
|
18
|
+
* field_value: 'invalid-email',
|
|
19
|
+
* field_error: document.getElementById('email_error'),
|
|
20
|
+
* rules: {
|
|
21
|
+
* 'Email is required': !email,
|
|
22
|
+
* 'Email format is invalid': !email.includes('@')
|
|
23
|
+
* }
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* - If a rule evaluates to `true`, its message is displayed in the error element.
|
|
29
|
+
* - The function automatically applies a red border style (`!border-error-main`) to the associated `<label>`.
|
|
30
|
+
* - When all rules pass, the error text is cleared and the border style is removed.
|
|
31
|
+
*/
|
|
32
|
+
export declare const provideFieldError: <T>({ field_id, field_value, field_error, rules, }: TFieldItem<T> & {
|
|
33
|
+
rules: Record<string, boolean>;
|
|
34
|
+
}) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Performs advanced password validation and updates the error message and styling accordingly.
|
|
37
|
+
*
|
|
38
|
+
* This function enforces password strength requirements such as:
|
|
39
|
+
* - Minimum length (8 characters)
|
|
40
|
+
* - Presence of uppercase letters, lowercase letters, numbers, and symbols
|
|
41
|
+
* - No spaces allowed
|
|
42
|
+
*
|
|
43
|
+
* It dynamically generates an error message that lists missing requirements and adjusts the label border color to indicate errors.
|
|
44
|
+
*
|
|
45
|
+
* @template T - The type of the input field value.
|
|
46
|
+
*
|
|
47
|
+
* @param {TFieldItem<T>} params - The password field metadata.
|
|
48
|
+
* @param {string} params.field_id - The ID of the password input field.
|
|
49
|
+
* @param {T} params.field_value - The current value of the password field.
|
|
50
|
+
* @param {HTMLElement | null} params.field_error - The element where password validation messages will appear.
|
|
51
|
+
*
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* providePasswordFieldError({
|
|
56
|
+
* field_id: 'password',
|
|
57
|
+
* field_value: 'weakPass',
|
|
58
|
+
* field_error: document.getElementById('password_error')
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* - Displays context-aware feedback such as “add numbers or symbols to strengthen your password.”
|
|
64
|
+
* - Automatically applies or removes the red border class (`!border-error-main`) on the associated `<label>`.
|
|
65
|
+
* - Uses predefined regex constants from `@/constants/regex` for validation checks.
|
|
66
|
+
*/
|
|
67
|
+
export declare const providePasswordFieldError: <T>({ field_id, field_value, field_error }: TFieldItem<T>) => void;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
2
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
3
|
+
if (!m) return o;
|
|
4
|
+
var i = m.call(o), r, ar = [], e;
|
|
5
|
+
try {
|
|
6
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
7
|
+
}
|
|
8
|
+
catch (error) { e = { error: error }; }
|
|
9
|
+
finally {
|
|
10
|
+
try {
|
|
11
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
12
|
+
}
|
|
13
|
+
finally { if (e) throw e.error; }
|
|
14
|
+
}
|
|
15
|
+
return ar;
|
|
16
|
+
};
|
|
17
|
+
import { ANY_LOWERCASE, ANY_NUMBER, ANY_SYMBOL, ANY_UPPERCASE, NO_SPACE } from '@/constants/regex';
|
|
18
|
+
import { getByAny } from './general';
|
|
19
|
+
/**
|
|
20
|
+
* Validates a generic input field and updates its associated error element and label styling based on defined rules.
|
|
21
|
+
*
|
|
22
|
+
* @template T - The type of the input field value.
|
|
23
|
+
*
|
|
24
|
+
* @param {TFieldItem<T> & { rules: Record<string, boolean> }} params - The field metadata and validation rules.
|
|
25
|
+
* @param {string} params.field_id - The ID of the input element being validated.
|
|
26
|
+
* @param {T} params.field_value - The current value of the input field.
|
|
27
|
+
* @param {HTMLElement | null} params.field_error - The error message element associated with this field.
|
|
28
|
+
* @param {Record<string, boolean>} params.rules - A mapping of error messages to their boolean validation results (`true` = invalid).
|
|
29
|
+
*
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* provideFieldError({
|
|
34
|
+
* field_id: 'email',
|
|
35
|
+
* field_value: 'invalid-email',
|
|
36
|
+
* field_error: document.getElementById('email_error'),
|
|
37
|
+
* rules: {
|
|
38
|
+
* 'Email is required': !email,
|
|
39
|
+
* 'Email format is invalid': !email.includes('@')
|
|
40
|
+
* }
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* - If a rule evaluates to `true`, its message is displayed in the error element.
|
|
46
|
+
* - The function automatically applies a red border style (`!border-error-main`) to the associated `<label>`.
|
|
47
|
+
* - When all rules pass, the error text is cleared and the border style is removed.
|
|
48
|
+
*/
|
|
49
|
+
export var provideFieldError = function (_a) {
|
|
50
|
+
var field_id = _a.field_id, field_value = _a.field_value, field_error = _a.field_error, rules = _a.rules;
|
|
51
|
+
var error = false;
|
|
52
|
+
var label = getByAny("label[for=\"".concat(field_id, "\"]"));
|
|
53
|
+
Object.entries(rules).forEach(function (_a) {
|
|
54
|
+
var _b = __read(_a, 2), message = _b[0], isError = _b[1];
|
|
55
|
+
if (isError && (!error || !!field_value)) {
|
|
56
|
+
field_error.textContent = message;
|
|
57
|
+
error = true;
|
|
58
|
+
label === null || label === void 0 ? void 0 : label.classList.add('!border-error-main');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
if (!error) {
|
|
62
|
+
field_error.textContent = '';
|
|
63
|
+
label === null || label === void 0 ? void 0 : label.classList.remove('!border-error-main');
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Performs advanced password validation and updates the error message and styling accordingly.
|
|
68
|
+
*
|
|
69
|
+
* This function enforces password strength requirements such as:
|
|
70
|
+
* - Minimum length (8 characters)
|
|
71
|
+
* - Presence of uppercase letters, lowercase letters, numbers, and symbols
|
|
72
|
+
* - No spaces allowed
|
|
73
|
+
*
|
|
74
|
+
* It dynamically generates an error message that lists missing requirements and adjusts the label border color to indicate errors.
|
|
75
|
+
*
|
|
76
|
+
* @template T - The type of the input field value.
|
|
77
|
+
*
|
|
78
|
+
* @param {TFieldItem<T>} params - The password field metadata.
|
|
79
|
+
* @param {string} params.field_id - The ID of the password input field.
|
|
80
|
+
* @param {T} params.field_value - The current value of the password field.
|
|
81
|
+
* @param {HTMLElement | null} params.field_error - The element where password validation messages will appear.
|
|
82
|
+
*
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* providePasswordFieldError({
|
|
87
|
+
* field_id: 'password',
|
|
88
|
+
* field_value: 'weakPass',
|
|
89
|
+
* field_error: document.getElementById('password_error')
|
|
90
|
+
* })
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* - Displays context-aware feedback such as “add numbers or symbols to strengthen your password.”
|
|
95
|
+
* - Automatically applies or removes the red border class (`!border-error-main`) on the associated `<label>`.
|
|
96
|
+
* - Uses predefined regex constants from `@/constants/regex` for validation checks.
|
|
97
|
+
*/
|
|
98
|
+
export var providePasswordFieldError = function (_a) {
|
|
99
|
+
var _b;
|
|
100
|
+
var field_id = _a.field_id, field_value = _a.field_value, field_error = _a.field_error;
|
|
101
|
+
var error = false;
|
|
102
|
+
var value = field_value;
|
|
103
|
+
var label = getByAny("label[for=\"".concat(field_id, "\"]"));
|
|
104
|
+
var firstRules = {
|
|
105
|
+
'password harus diisi': !value,
|
|
106
|
+
'panjang password minimal adalah 8 karakter': value.length < 8,
|
|
107
|
+
};
|
|
108
|
+
Object.entries(firstRules).forEach(function (_a) {
|
|
109
|
+
var _b = __read(_a, 2), message = _b[0], isError = _b[1];
|
|
110
|
+
if (isError && (!error || !!value)) {
|
|
111
|
+
field_error.textContent = message;
|
|
112
|
+
error = true;
|
|
113
|
+
label === null || label === void 0 ? void 0 : label.classList.add('!border-error-main');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
var nextRules = (_b = {},
|
|
117
|
+
_b['huruf kapital'] = !ANY_UPPERCASE.re.test(value),
|
|
118
|
+
_b['huruf kecil'] = !ANY_LOWERCASE.re.test(value),
|
|
119
|
+
_b['angka'] = !ANY_NUMBER.re.test(value),
|
|
120
|
+
_b['simbol'] = !ANY_SYMBOL.re.test(value),
|
|
121
|
+
_b);
|
|
122
|
+
var nextError = Object.entries(nextRules).filter(function (_a) {
|
|
123
|
+
var _b = __read(_a, 2), _ = _b[0], rule = _b[1];
|
|
124
|
+
return rule;
|
|
125
|
+
});
|
|
126
|
+
var nextMessage = 'tambahkan ' +
|
|
127
|
+
nextError
|
|
128
|
+
.map(function (_a, idx) {
|
|
129
|
+
var _b = __read(_a, 1), message = _b[0];
|
|
130
|
+
var len = nextError.length;
|
|
131
|
+
if (len > 1 && idx >= len - 1) {
|
|
132
|
+
if (len <= 2)
|
|
133
|
+
return " dan ".concat(message);
|
|
134
|
+
return ", dan ".concat(message);
|
|
135
|
+
}
|
|
136
|
+
if (idx > 0)
|
|
137
|
+
return ", ".concat(message);
|
|
138
|
+
return message;
|
|
139
|
+
})
|
|
140
|
+
.join('');
|
|
141
|
+
if (nextError.length > 0 && (!error || !!value)) {
|
|
142
|
+
field_error.textContent = nextMessage + ' untuk memperkuat passwordmu.';
|
|
143
|
+
if (nextError.length >= Object.keys(nextError).length - 1) {
|
|
144
|
+
field_error.textContent = 'password terlalu lemah. ' + nextMessage + '.';
|
|
145
|
+
}
|
|
146
|
+
error = true;
|
|
147
|
+
label === null || label === void 0 ? void 0 : label.classList.add('!border-error-main');
|
|
148
|
+
}
|
|
149
|
+
if (!NO_SPACE.re.test(value) && (!error || !!value)) {
|
|
150
|
+
field_error.textContent = "password ".concat(NO_SPACE.text);
|
|
151
|
+
error = true;
|
|
152
|
+
label === null || label === void 0 ? void 0 : label.classList.add('!border-error-main');
|
|
153
|
+
}
|
|
154
|
+
if (!error) {
|
|
155
|
+
field_error.textContent = '';
|
|
156
|
+
label === null || label === void 0 ? void 0 : label.classList.remove('!border-error-main');
|
|
157
|
+
}
|
|
158
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fajarmaulana/komerce-lp-helper",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Helper functions, hooks and utils for Komerce LP",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Helper functions, hooks, and utils for Komerce LP",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -11,14 +11,30 @@
|
|
|
11
11
|
"build": "tsc"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"react": ">=
|
|
15
|
-
"react-dom": ">=
|
|
16
|
-
"react-router-dom": ">=
|
|
14
|
+
"react": ">=19",
|
|
15
|
+
"react-dom": ">=19",
|
|
16
|
+
"react-router-dom": ">=7"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@changesets/cli": "^2.29.8",
|
|
20
|
-
"@
|
|
21
|
-
"@types/react
|
|
20
|
+
"@eslint/js": "^9.33.0",
|
|
21
|
+
"@types/react": "^19.0.8",
|
|
22
|
+
"@types/react-dom": "^19.0.3",
|
|
23
|
+
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
|
24
|
+
"@typescript-eslint/parser": "^8.39.1",
|
|
25
|
+
"eslint": "^9.18.0",
|
|
26
|
+
"eslint-config-prettier": "^10.1.8",
|
|
27
|
+
"eslint-plugin-import": "^2.32.0",
|
|
28
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
29
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
30
|
+
"eslint-plugin-react": "^7.37.5",
|
|
31
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
32
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
33
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
34
|
+
"eslint-plugin-unused-imports": "^4.1.4",
|
|
35
|
+
"globals": "^15.14.0",
|
|
36
|
+
"prettier": "^3.6.2",
|
|
37
|
+
"react-router-dom": "^7.11.0",
|
|
22
38
|
"typescript": "^5.2.0"
|
|
23
39
|
},
|
|
24
40
|
"author": "Fajar Maulana",
|