@skalfa/skalfa-app-core 1.0.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 (41) hide show
  1. package/dist/api.util.d.ts +49 -0
  2. package/dist/api.util.js +125 -0
  3. package/dist/auth.util.d.ts +10 -0
  4. package/dist/auth.util.js +37 -0
  5. package/dist/cavity.util.d.ts +20 -0
  6. package/dist/cavity.util.js +124 -0
  7. package/dist/cn.util.d.ts +3 -0
  8. package/dist/cn.util.js +45 -0
  9. package/dist/commands/barrels.d.ts +1 -0
  10. package/dist/commands/barrels.js +22 -0
  11. package/dist/commands/blueprint.d.ts +3 -0
  12. package/dist/commands/blueprint.js +306 -0
  13. package/dist/commands/light.d.ts +1 -0
  14. package/dist/commands/light.js +16 -0
  15. package/dist/commands/logger.d.ts +10 -0
  16. package/dist/commands/logger.js +36 -0
  17. package/dist/commands/use-pdf.d.ts +1 -0
  18. package/dist/commands/use-pdf.js +17 -0
  19. package/dist/conversion.util.d.ts +10 -0
  20. package/dist/conversion.util.js +53 -0
  21. package/dist/encryption.util.d.ts +6 -0
  22. package/dist/encryption.util.js +56 -0
  23. package/dist/form.util.d.ts +87 -0
  24. package/dist/form.util.js +294 -0
  25. package/dist/index.d.ts +27 -0
  26. package/dist/index.js +79 -0
  27. package/dist/langs/index.d.ts +1 -0
  28. package/dist/langs/index.js +1 -0
  29. package/dist/langs/validation.langs.d.ts +17 -0
  30. package/dist/langs/validation.langs.js +17 -0
  31. package/dist/registry/index.d.ts +19 -0
  32. package/dist/registry/index.js +10 -0
  33. package/dist/resource.util.d.ts +36 -0
  34. package/dist/resource.util.js +81 -0
  35. package/dist/shortcut.util.d.ts +12 -0
  36. package/dist/shortcut.util.js +22 -0
  37. package/dist/table.util.d.ts +51 -0
  38. package/dist/table.util.js +140 -0
  39. package/dist/validation.util.d.ts +18 -0
  40. package/dist/validation.util.js +150 -0
  41. package/package.json +47 -0
@@ -0,0 +1,294 @@
1
+ "use client";
2
+ import { useReducer, useEffect, useState } from "react";
3
+ import { registry } from "./registry";
4
+ import { validation } from "./validation.util";
5
+ import { api } from "./api.util";
6
+ const initialState = {
7
+ formRegisters: [],
8
+ formValues: [],
9
+ formErrors: [],
10
+ loading: false,
11
+ showConfirm: false,
12
+ };
13
+ // ==============================>
14
+ // ## Form state handler
15
+ // ==============================>
16
+ const formReducer = (state, action) => {
17
+ switch (action.type) {
18
+ // ==============================>
19
+ // ## Register handler
20
+ // ==============================>
21
+ case "SET_REGISTER": return {
22
+ ...state,
23
+ formRegisters: [
24
+ ...state.formRegisters.filter((reg) => reg.name !== action.payload.name),
25
+ action.payload,
26
+ ],
27
+ };
28
+ // ==============================>
29
+ // ## Unregister single field — removes register, value, and error
30
+ // ==============================>
31
+ case "UNREGISTER": return {
32
+ ...state,
33
+ formRegisters: state.formRegisters.filter((reg) => reg.name !== action.payload),
34
+ formValues: state.formValues.filter((val) => val.name !== action.payload),
35
+ formErrors: state.formErrors.filter((err) => err.name !== action.payload),
36
+ };
37
+ // ==============================>
38
+ // ## Unregister all fields matching prefix — for cluster group removal
39
+ // ==============================>
40
+ case "UNREGISTER_PREFIX": return {
41
+ ...state,
42
+ formRegisters: state.formRegisters.filter((reg) => !reg.name.startsWith(action.payload)),
43
+ formValues: state.formValues.filter((val) => !val.name.startsWith(action.payload)),
44
+ formErrors: state.formErrors.filter((err) => !err.name.startsWith(action.payload)),
45
+ };
46
+ // ==============================>
47
+ // ## Multiple values handler
48
+ // ==============================>
49
+ case "SET_VALUES": return {
50
+ ...state,
51
+ formValues: action.payload,
52
+ };
53
+ // ==============================>
54
+ // ## Single value handler
55
+ // ==============================>
56
+ case "SET_VALUE": return {
57
+ ...state,
58
+ formValues: [
59
+ ...state.formValues.filter((val) => val.name !== action.payload.name),
60
+ { name: action.payload.name, value: action.payload.value },
61
+ ],
62
+ };
63
+ // ==============================>
64
+ // ## Errors handler
65
+ // ==============================>
66
+ case "SET_ERRORS": return { ...state, formErrors: action.payload };
67
+ // ==============================>
68
+ // ## Loading handler
69
+ // ==============================>
70
+ case "SET_LOADING": return { ...state, loading: action.payload };
71
+ // ==============================>
72
+ // ## Confirm handler
73
+ // ==============================>
74
+ case "SET_CONFIRM": return { ...state, showConfirm: action.payload };
75
+ // ==============================>
76
+ // ## Reset handler
77
+ // ==============================>
78
+ case "RESET": return { ...initialState };
79
+ // ==============================>
80
+ // ## Return state
81
+ // ==============================>
82
+ default: return state;
83
+ }
84
+ };
85
+ // ==============================>
86
+ // ## Hook form
87
+ // ==============================>
88
+ export const useForm = (submitControl) => {
89
+ const isApiSubmit = !!submitControl?.path || !!submitControl?.url;
90
+ const isIdbSubmit = !!submitControl?.idb;
91
+ const [state, dispatch] = useReducer(formReducer, initialState);
92
+ const { payload, confirmation, onSuccess, onFailed } = submitControl;
93
+ // ==============================>
94
+ // ## Reset when first load
95
+ // ==============================>
96
+ useEffect(() => dispatch({ type: "RESET" }), [submitControl?.path, submitControl?.url, submitControl?.idb]);
97
+ // ==============================>
98
+ // ## Set value from changes
99
+ // ==============================>
100
+ const onChange = (name, value) => dispatch({ type: "SET_VALUE", payload: { name, value: value ?? "" } });
101
+ // ==============================>
102
+ // ## FormControl handler
103
+ // ==============================>
104
+ const formControl = (name) => ({
105
+ register: (_, regValidations) => dispatch({
106
+ type: "SET_REGISTER",
107
+ payload: { name, validations: regValidations },
108
+ }),
109
+ unregister: () => dispatch({ type: "UNREGISTER", payload: name }),
110
+ onChange: (e) => onChange(name, e),
111
+ value: state.formValues.find((val) => val.name === name)?.value || undefined,
112
+ invalid: state.formErrors.find((err) => err.name === name)?.error || undefined,
113
+ });
114
+ const getObjectValues = () => {
115
+ const registeredNames = new Set(state.formRegisters.map(r => r.name));
116
+ return state.formValues.reduce((acc, val) => {
117
+ if (registeredNames.has(val.name))
118
+ acc[val.name] = val.value;
119
+ return acc;
120
+ }, {});
121
+ };
122
+ const submitIdb = async () => {
123
+ const values = payload ? await payload(getObjectValues()) : getObjectValues();
124
+ const idb = registry.get("idb");
125
+ if (!idb) {
126
+ throw new Error("IndexedDB (IDB) extension is not installed or registered.");
127
+ }
128
+ const client = submitControl?.idb?.schema ? idb.useSchema(submitControl?.idb?.schema) : idb;
129
+ await client.put(submitControl?.idb?.store || "", values);
130
+ return { status: 200, data: values };
131
+ };
132
+ const submitApi = async () => {
133
+ const formData = new FormData();
134
+ const values = payload ? await payload(getObjectValues()) : getObjectValues();
135
+ Object.entries(values).forEach(([k, v]) => {
136
+ formData.append(k, v ?? "");
137
+ });
138
+ return api({
139
+ url: submitControl.url,
140
+ path: submitControl.path,
141
+ method: submitControl.method || "POST",
142
+ bearer: submitControl.bearer,
143
+ headers: submitControl.headers,
144
+ payload: formData,
145
+ });
146
+ };
147
+ // ==============================>
148
+ // ## Fetch to api
149
+ // ==============================>
150
+ const fetch = async () => {
151
+ dispatch({ type: "SET_LOADING", payload: true });
152
+ let execute;
153
+ if (isApiSubmit) {
154
+ execute = await submitApi();
155
+ }
156
+ else if (isIdbSubmit) {
157
+ execute = await submitIdb();
158
+ }
159
+ else {
160
+ throw new Error("Invalid submitControl");
161
+ }
162
+ if (execute?.status === 200 || execute?.status === 201) {
163
+ // ==============================>
164
+ // ## When success
165
+ // ==============================>
166
+ dispatch({ type: "SET_LOADING", payload: false });
167
+ onSuccess?.(execute.data);
168
+ dispatch({ type: "RESET" });
169
+ }
170
+ else if (isApiSubmit && execute?.status === 422) {
171
+ // ==============================>
172
+ // ## When error invalid
173
+ // ==============================>
174
+ const errors = Object.keys(execute.data.errors).map((key) => ({
175
+ name: key,
176
+ error: execute.data.errors[key][0],
177
+ }));
178
+ onFailed?.(execute?.status || 500);
179
+ dispatch({ type: "SET_ERRORS", payload: errors });
180
+ dispatch({ type: "SET_LOADING", payload: false });
181
+ dispatch({ type: "SET_CONFIRM", payload: false });
182
+ }
183
+ else {
184
+ // ==============================>
185
+ // ## When error server
186
+ // ==============================>
187
+ onFailed?.(execute?.status || 500);
188
+ dispatch({ type: "SET_CONFIRM", payload: false });
189
+ dispatch({ type: "SET_LOADING", payload: false });
190
+ }
191
+ };
192
+ // ==============================>
193
+ // ## Submit handler
194
+ // ==============================>
195
+ const submit = async (e) => {
196
+ e?.preventDefault();
197
+ dispatch({ type: "SET_ERRORS", payload: [] });
198
+ const newErrors = [];
199
+ // ==============================>
200
+ // ## Check register validation
201
+ // ==============================>
202
+ state.formRegisters.forEach((form) => {
203
+ const { valid, message } = validation.check({
204
+ value: state.formValues.find((val) => val.name === form.name)?.value,
205
+ rules: form.validations,
206
+ });
207
+ if (!valid) {
208
+ newErrors.push({ name: form.name, error: message });
209
+ }
210
+ });
211
+ if (newErrors.length) {
212
+ dispatch({ type: "SET_ERRORS", payload: newErrors });
213
+ return;
214
+ }
215
+ // ==============================>
216
+ // ## Execute handler
217
+ // ==============================>
218
+ if (confirmation) {
219
+ dispatch({ type: "SET_CONFIRM", payload: true });
220
+ }
221
+ else {
222
+ fetch();
223
+ }
224
+ };
225
+ // ==============================>
226
+ // ## Confirmation handler
227
+ // ==============================>
228
+ const onConfirm = () => fetch();
229
+ // ==============================>
230
+ // ## Set default value
231
+ // ==============================>
232
+ const setDefaultValues = (values) => {
233
+ const newValues = values ? Object.keys(values).map((keyName) => ({
234
+ name: keyName,
235
+ value: values[keyName],
236
+ })) : [];
237
+ dispatch({ type: "SET_VALUES", payload: newValues });
238
+ };
239
+ // ==============================>
240
+ // ## Return hook handler
241
+ // ==============================>
242
+ return {
243
+ submit,
244
+ formControl,
245
+ setDefaultValues,
246
+ values: state.formValues,
247
+ setValues: (values) => dispatch({ type: "SET_VALUES", payload: values || [] }),
248
+ errors: state.formErrors,
249
+ setErrors: (errors) => dispatch({ type: "SET_ERRORS", payload: errors }),
250
+ setRegister: (inputs) => dispatch({ type: "SET_REGISTER", payload: inputs }),
251
+ unregister: (name) => dispatch({ type: "UNREGISTER", payload: name }),
252
+ unregisterPrefix: (prefix) => dispatch({ type: "UNREGISTER_PREFIX", payload: prefix }),
253
+ loading: state.loading,
254
+ confirm: {
255
+ onConfirm,
256
+ show: state.showConfirm,
257
+ onClose: () => dispatch({ type: "SET_CONFIRM", payload: false }),
258
+ },
259
+ };
260
+ };
261
+ // ==============================>
262
+ // ## Generate random id
263
+ // ==============================>
264
+ export const useInputRandomId = () => {
265
+ const [randomId, setRandomId] = useState("");
266
+ useEffect(() => {
267
+ setRandomId(Math.random().toString(36).substring(7));
268
+ }, []);
269
+ return randomId;
270
+ };
271
+ // ==============================>
272
+ // ## Input handle
273
+ // ==============================>
274
+ export const useInputHandler = (name, value, validations, register, unregister, isFile) => {
275
+ const [inputValue, setInputValue] = useState("");
276
+ const [focus, setFocus] = useState(false);
277
+ const [idle, setIdle] = useState(true);
278
+ useEffect(() => {
279
+ name && register?.(name || "", validations);
280
+ return () => { name && unregister?.(name); };
281
+ }, [name, validations]);
282
+ useEffect(() => {
283
+ setInputValue(value && (!isFile || value instanceof File) ? value : "");
284
+ value && setIdle(false);
285
+ }, [value]);
286
+ return {
287
+ value: inputValue,
288
+ setValue: setInputValue,
289
+ idle,
290
+ setIdle,
291
+ focus,
292
+ setFocus
293
+ };
294
+ };
@@ -0,0 +1,27 @@
1
+ export * from "./api.util";
2
+ export * from "./auth.util";
3
+ export * from "./cavity.util";
4
+ export * from "./encryption.util";
5
+ export * from "./cn.util";
6
+ export * from "./form.util";
7
+ export * from "./resource.util";
8
+ export * from "./table.util";
9
+ export * from "./validation.util";
10
+ export * from "./conversion.util";
11
+ export * from "./shortcut.util";
12
+ export * from "./commands/logger";
13
+ export * from "./registry";
14
+ export declare const useResponsive: () => {
15
+ isXs: boolean;
16
+ isSm: boolean;
17
+ isMd: boolean;
18
+ isLg: boolean;
19
+ isXl: boolean;
20
+ isMobile: boolean;
21
+ isTablet: boolean;
22
+ isDesktop: boolean;
23
+ width: number;
24
+ height: number;
25
+ };
26
+ export declare function useKeyboardOpen(): boolean;
27
+ export declare const useLazySearch: (keyword: string) => string[];
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ // ==============================>
4
+ // ## Export all from core utils
5
+ // ==============================>
6
+ export * from "./api.util";
7
+ export * from "./auth.util";
8
+ export * from "./cavity.util";
9
+ export * from "./encryption.util";
10
+ export * from "./cn.util";
11
+ export * from "./form.util";
12
+ export * from "./resource.util";
13
+ export * from "./table.util";
14
+ export * from "./validation.util";
15
+ export * from "./conversion.util";
16
+ export * from "./shortcut.util";
17
+ export * from "./commands/logger";
18
+ export * from "./registry";
19
+ // ==============================>
20
+ // ## Detect device size
21
+ // ==============================>
22
+ export const useResponsive = () => {
23
+ const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
24
+ useEffect(() => {
25
+ if (typeof window === "undefined")
26
+ return;
27
+ const handleResize = () => {
28
+ setWindowSize({ width: window.innerWidth, height: window.innerHeight });
29
+ };
30
+ handleResize();
31
+ window.addEventListener("resize", handleResize);
32
+ return () => window.removeEventListener("resize", handleResize);
33
+ }, []);
34
+ return {
35
+ isXs: windowSize.width < 640,
36
+ isSm: windowSize.width < 768,
37
+ isMd: windowSize.width < 1024,
38
+ isLg: windowSize.width < 1280,
39
+ isXl: windowSize.width >= 1280,
40
+ isMobile: windowSize.width < 768,
41
+ isTablet: windowSize.width >= 768 && windowSize.width < 1024,
42
+ isDesktop: windowSize.width >= 1024,
43
+ width: windowSize.width,
44
+ height: windowSize.height,
45
+ };
46
+ };
47
+ // ==============================>
48
+ // ## Detect keyboard open
49
+ // ==============================>
50
+ export function useKeyboardOpen() {
51
+ const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
52
+ useEffect(() => {
53
+ const handleResize = () => {
54
+ if (window.visualViewport) {
55
+ const viewportHeight = window.visualViewport.height;
56
+ const windowHeight = window.innerHeight;
57
+ setIsKeyboardOpen(viewportHeight < windowHeight);
58
+ }
59
+ };
60
+ window.visualViewport?.addEventListener("resize", handleResize);
61
+ return () => window.visualViewport?.removeEventListener("resize", handleResize);
62
+ }, []);
63
+ return isKeyboardOpen;
64
+ }
65
+ // ==============================>
66
+ // ## Search with typing reference
67
+ // ==============================>
68
+ export const useLazySearch = (keyword) => {
69
+ const [keywordSearch, setKeywordSearch] = useState("");
70
+ const [doSearch, setDoSearch] = useState(false);
71
+ useEffect(() => {
72
+ if (keyword != undefined) {
73
+ const delaySearch = setTimeout(() => setDoSearch(!doSearch), 500);
74
+ return () => clearTimeout(delaySearch);
75
+ }
76
+ }, [keyword]);
77
+ useEffect(() => setKeywordSearch(keyword), [doSearch]);
78
+ return [keywordSearch];
79
+ };
@@ -0,0 +1 @@
1
+ export * from "./validation.langs";
@@ -0,0 +1 @@
1
+ export * from "./validation.langs";
@@ -0,0 +1,17 @@
1
+ export declare const validationLangs: {
2
+ required: string;
3
+ min: string;
4
+ max: string;
5
+ min_max: string;
6
+ phone: string;
7
+ url: string;
8
+ uppercase: string;
9
+ lowercase: string;
10
+ numeric: string;
11
+ email: string;
12
+ in: string;
13
+ not_in: string;
14
+ regex: string;
15
+ invalid_file_type: string;
16
+ max_file_size: string;
17
+ };
@@ -0,0 +1,17 @@
1
+ export const validationLangs = {
2
+ required: "Please fill in this field!",
3
+ min: "Field must contain more than @min Character!",
4
+ max: "Field must be less than @max Character!",
5
+ min_max: "Field must be @min - @max Character!",
6
+ phone: "Please enter valid mobile number!",
7
+ url: "Please enter valid url!",
8
+ uppercase: "Field must be at least 1 uppercase!",
9
+ lowercase: "Field must be at least 1 lowercase!",
10
+ numeric: "Field must be at least 1 numeric!",
11
+ email: "Please enter valid email!",
12
+ in: "Field must be one of @keywords",
13
+ not_in: "Field can't one of @keywords",
14
+ regex: "Please enter valid format",
15
+ invalid_file_type: "Only allow extensions @extension",
16
+ max_file_size: "Max file size @maxFileSize Mb",
17
+ };
@@ -0,0 +1,19 @@
1
+ export interface Registry {
2
+ idb?: any;
3
+ socket?: any;
4
+ ExportExcel?: any;
5
+ ImportExcel?: any;
6
+ [key: string]: any;
7
+ }
8
+ export type DBSchema = {
9
+ name: string;
10
+ version: number;
11
+ stores: Record<string, any>;
12
+ };
13
+ declare class ServiceRegistry {
14
+ private services;
15
+ register<K extends keyof Registry>(name: K, service: Registry[K]): void;
16
+ get<K extends keyof Registry>(name: K): Registry[K];
17
+ }
18
+ export declare const registry: ServiceRegistry;
19
+ export {};
@@ -0,0 +1,10 @@
1
+ class ServiceRegistry {
2
+ services = {};
3
+ register(name, service) {
4
+ this.services[name] = service;
5
+ }
6
+ get(name) {
7
+ return this.services[name];
8
+ }
9
+ }
10
+ export const registry = new ServiceRegistry();
@@ -0,0 +1,36 @@
1
+ import { ApiType } from "./api.util";
2
+ export type ResourceParams = {
3
+ page?: number;
4
+ paginate?: number;
5
+ search?: string;
6
+ sort?: string[];
7
+ expand?: string[];
8
+ filter?: any[];
9
+ };
10
+ export type UseResourceApi = ApiType & {
11
+ method?: "GET";
12
+ };
13
+ export type UseResourceIdb = {
14
+ store: string;
15
+ schema?: any;
16
+ };
17
+ export type UseResourceProps = ({
18
+ path?: string;
19
+ url?: string;
20
+ } & UseResourceApi) | ({
21
+ idb: UseResourceIdb;
22
+ });
23
+ export declare function useResource(props: UseResourceProps & {
24
+ params?: ResourceParams;
25
+ }): {
26
+ loading: boolean;
27
+ data: any;
28
+ reset: () => Promise<"" | undefined>;
29
+ } | {
30
+ loading: boolean;
31
+ data: {
32
+ data: any[];
33
+ total_row: number;
34
+ } | null;
35
+ reset: () => Promise<void>;
36
+ };
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ import { useGetApi } from "./api.util";
4
+ import { registry } from "./registry";
5
+ export function useResource(props) {
6
+ const isApi = "path" in props || "url" in props;
7
+ const apiResult = useGetApi(isApi ? props : {}, !isApi);
8
+ // =====================
9
+ // IDB MODE
10
+ // =====================
11
+ const [loading, setLoading] = useState(false);
12
+ const [data, setData] = useState(null);
13
+ const idbParams = props.params || {};
14
+ const fetchIdb = async () => {
15
+ if (!("idb" in props))
16
+ return;
17
+ setLoading(true);
18
+ try {
19
+ const idb = registry.get("idb");
20
+ if (!idb) {
21
+ throw new Error("IndexedDB (IDB) extension is not installed or registered.");
22
+ }
23
+ const idbClient = props.idb.schema
24
+ ? idb.useSchema(props.idb.schema)
25
+ : idb;
26
+ let q = await idbClient.query(props.idb.store);
27
+ if (idbParams.search) {
28
+ const keyword = idbParams.search.toLowerCase();
29
+ q = q.where((row) => Object.values(row).some((v) => String(v).toLowerCase().includes(keyword)));
30
+ }
31
+ if (Array.isArray(idbParams.filter)) {
32
+ for (const f of idbParams.filter) {
33
+ if (f?.field && f?.value !== undefined) {
34
+ q = q.where((row) => row[f.field] === f.value);
35
+ }
36
+ }
37
+ }
38
+ if (Array.isArray(idbParams.sort) && idbParams.sort.length) {
39
+ q = q.usingIndex(idbParams.sort[0]?.split(" ")?.at(0) || "created_at").order(idbParams.sort[0]?.split(" ")?.at(1) == "asc" ? "asc" : "desc");
40
+ }
41
+ if (idbParams.paginate) {
42
+ q = q.paginate(idbParams.page || 0, idbParams.paginate);
43
+ }
44
+ const [rows, total] = await Promise.all([
45
+ q.get(),
46
+ q.count(),
47
+ ]);
48
+ // const rows = await q.get()
49
+ setData({ data: rows, total_row: total });
50
+ }
51
+ finally {
52
+ setLoading(false);
53
+ }
54
+ };
55
+ useEffect(() => {
56
+ if (!isApi && "idb" in props)
57
+ fetchIdb();
58
+ }, [
59
+ isApi,
60
+ idbParams.search,
61
+ JSON.stringify(idbParams.filter),
62
+ JSON.stringify(idbParams.sort),
63
+ idbParams.paginate,
64
+ idbParams.page,
65
+ ]);
66
+ // =====================
67
+ // Unified return
68
+ // =====================
69
+ if (isApi) {
70
+ return {
71
+ loading: apiResult.loading,
72
+ data: apiResult.data,
73
+ reset: apiResult.reset,
74
+ };
75
+ }
76
+ return {
77
+ loading,
78
+ data: data,
79
+ reset: fetchIdb,
80
+ };
81
+ }
@@ -0,0 +1,12 @@
1
+ export type ShortcutHandler = (e: KeyboardEvent) => void;
2
+ export type ShortcutType = {
3
+ key: string;
4
+ description?: string;
5
+ handler: ShortcutHandler;
6
+ };
7
+ export declare const shortcut: {
8
+ register: (key: string, handler: ShortcutHandler, description?: string) => void;
9
+ unregister: (key: string) => boolean;
10
+ list: () => ShortcutType[];
11
+ init: () => void;
12
+ };
@@ -0,0 +1,22 @@
1
+ const handlers = new Map();
2
+ export const shortcut = {
3
+ register: (key, handler, description) => {
4
+ handlers.set(key, { key, handler, description });
5
+ },
6
+ unregister: (key) => handlers.delete(key),
7
+ list: () => Array.from(handlers.values()),
8
+ init: () => {
9
+ window.addEventListener("keydown", (e) => {
10
+ const target = e.target;
11
+ if (target?.tagName === "INPUT" || target?.tagName === "TEXTAREA" || target?.isContentEditable)
12
+ return;
13
+ const combo = [e.ctrlKey && "ctrl", e.shiftKey && "shift", e.altKey && "alt", e.key.toLowerCase()].filter(Boolean).join("+");
14
+ const meta = handlers.get(combo);
15
+ if (meta) {
16
+ e.preventDefault();
17
+ e.stopPropagation();
18
+ meta.handler(e);
19
+ }
20
+ }, true);
21
+ }
22
+ };
@@ -0,0 +1,51 @@
1
+ import { ApiFilterType, ApiParamsType } from "./api.util";
2
+ import { ResourceParams, UseResourceProps } from "./resource.util";
3
+ export type TableStateType = {
4
+ params?: ApiParamsType;
5
+ data?: Record<string, any>[];
6
+ selected?: Record<string, any> | null;
7
+ checks?: (string | number)[] | null;
8
+ focus?: number | null;
9
+ };
10
+ export type FetchControlType = {
11
+ path?: string;
12
+ url?: string;
13
+ headers?: Record<string, any>;
14
+ params?: ApiParamsType;
15
+ includeParams?: object;
16
+ bearer?: string;
17
+ };
18
+ export declare const useTable: (fetchControl: UseResourceProps & {
19
+ params?: ResourceParams;
20
+ }, id: string | undefined, title: string | undefined, urlParam: boolean | {
21
+ compressed?: boolean;
22
+ }) => {
23
+ tableKey: string;
24
+ data: any;
25
+ reset: (() => Promise<"" | undefined>) | (() => Promise<void>);
26
+ loading: boolean;
27
+ params: ApiParamsType | undefined;
28
+ setParam: <K extends keyof ApiParamsType>(key: K, value: ApiParamsType[K]) => void;
29
+ focus: number | null | undefined;
30
+ setFocus: (focus: number | null) => void;
31
+ selected: Record<string, any> | null | undefined;
32
+ setSelected: (selected: Record<string, any> | null) => void;
33
+ checks: (string | number)[] | null | undefined;
34
+ setChecks: (checks: (string | number)[] | null) => void;
35
+ tableControl: {
36
+ loading: boolean;
37
+ sortBy: string[] | undefined;
38
+ onChangeSortBy: (e: string[]) => void;
39
+ search: string | undefined;
40
+ onChangeSearch: (e: string) => void;
41
+ filter: ApiFilterType[] | undefined;
42
+ onChangeFilter: (e: ApiFilterType[]) => void;
43
+ onRefresh: () => Promise<void> | Promise<"" | undefined>;
44
+ pagination: {
45
+ totalRow: any;
46
+ page: number;
47
+ paginate: number;
48
+ onChange: (_: number, paginate: number, page: number) => void;
49
+ };
50
+ };
51
+ };