@kirill.konshin/utils 0.0.14 → 0.0.16
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/.ctirc +2 -1
- package/.turbo/turbo-build.log +18 -13
- package/.turbo/turbo-test.log +76 -23
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/demo/{cache-demo-clean.ts → worker-demo.ts} +1 -1
- package/dist/bootstrap/controls.d.ts +5 -2
- package/dist/bootstrap/controls.d.ts.map +1 -1
- package/dist/bootstrap/controls.js +1 -0
- package/dist/bootstrap/controls.js.map +1 -1
- package/dist/bootstrap/controls.stories.d.ts +9 -0
- package/dist/bootstrap/controls.stories.d.ts.map +1 -0
- package/dist/bootstrap/error.stories.d.ts +8 -0
- package/dist/bootstrap/error.stories.d.ts.map +1 -0
- package/dist/bootstrap/field.stories.d.ts +8 -0
- package/dist/bootstrap/field.stories.d.ts.map +1 -0
- package/dist/bootstrap/globalLoading.stories.d.ts +7 -0
- package/dist/bootstrap/globalLoading.stories.d.ts.map +1 -0
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/loading.stories.d.ts +9 -0
- package/dist/bootstrap/loading.stories.d.ts.map +1 -0
- package/dist/bootstrap/screen.stories.d.ts +14 -0
- package/dist/bootstrap/screen.stories.d.ts.map +1 -0
- package/dist/bootstrap/toaster.stories.d.ts +8 -0
- package/dist/bootstrap/toaster.stories.d.ts.map +1 -0
- package/dist/bootstrap/useModal.stories.d.ts +9 -0
- package/dist/bootstrap/useModal.stories.d.ts.map +1 -0
- package/dist/core/cache.d.ts +7 -8
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +7 -4
- package/dist/core/cache.js.map +1 -1
- package/dist/core/cache.test.d.ts +2 -0
- package/dist/core/cache.test.d.ts.map +1 -0
- package/dist/core/files.d.ts +3 -0
- package/dist/core/files.d.ts.map +1 -0
- package/dist/core/files.js +27 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +14 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/ls.d.ts +6 -0
- package/dist/core/ls.d.ts.map +1 -0
- package/dist/core/ls.js +12 -0
- package/dist/core/ls.js.map +1 -0
- package/dist/core/worker.d.ts +23 -9
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +93 -90
- package/dist/core/worker.js.map +1 -1
- package/dist/core/worker.test.d.ts +2 -0
- package/dist/core/worker.test.d.ts.map +1 -0
- package/dist/core/workerUtils.d.ts +3 -0
- package/dist/core/workerUtils.d.ts.map +1 -0
- package/dist/core/workerUtils.js +31 -0
- package/dist/core/workerUtils.js.map +1 -0
- package/dist/core/wrpc.d.ts +45 -0
- package/dist/core/wrpc.d.ts.map +1 -0
- package/dist/core/wrpc.fixture.d.ts +29 -0
- package/dist/core/wrpc.fixture.d.ts.map +1 -0
- package/dist/core/wrpc.js +242 -0
- package/dist/core/wrpc.js.map +1 -0
- package/dist/core/wrpc.test.d.ts +1 -0
- package/dist/core/wrpc.test.d.ts.map +1 -0
- package/dist/mui/formControlFieldset.stories.d.ts +7 -0
- package/dist/mui/formControlFieldset.stories.d.ts.map +1 -0
- package/dist/react/form/form.d.ts +7 -6
- package/dist/react/form/form.d.ts.map +1 -1
- package/dist/react/form/form.js +14 -20
- package/dist/react/form/form.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +8 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/keyboard.d.ts +11 -0
- package/dist/react/keyboard.d.ts.map +1 -0
- package/dist/react/keyboard.js +38 -0
- package/dist/react/keyboard.js.map +1 -0
- package/package.json +5 -2
- package/src/bootstrap/controls.stories.tsx +2 -2
- package/src/bootstrap/controls.tsx +3 -3
- package/src/bootstrap/error.stories.tsx +3 -3
- package/src/bootstrap/field.stories.tsx +2 -2
- package/src/bootstrap/globalLoading.stories.tsx +2 -2
- package/src/bootstrap/loading.stories.tsx +2 -2
- package/src/bootstrap/screen.stories.tsx +2 -2
- package/src/bootstrap/toaster.stories.tsx +2 -2
- package/src/bootstrap/useModal.stories.tsx +3 -3
- package/src/core/README.md +91 -0
- package/src/core/cache.ts +12 -12
- package/src/core/files.ts +25 -0
- package/src/core/index.ts +4 -0
- package/src/core/ls.ts +11 -0
- package/src/core/worker.test.ts +197 -0
- package/src/core/worker.ts +106 -93
- package/src/core/workerUtils.ts +32 -0
- package/src/core/wrpc.fixture.ts +72 -0
- package/src/core/wrpc.test.ts +225 -0
- package/src/core/wrpc.ts +359 -0
- package/src/mui/formControlFieldset.stories.tsx +1 -1
- package/src/react/form/form.tsx +53 -33
- package/src/react/index.ts +1 -0
- package/src/react/keyboard.tsx +49 -0
- package/src/tailwind/README.md +8 -0
- package/tsconfig.json +2 -1
- package/vite.config.ts +10 -3
- package/vite.exports.ts +3 -3
- package/demo/cache-demo.ts +0 -343
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sources":["../../../src/react/form/form.tsx"],"sourcesContent":["import { typeToFlattenedError, z, SafeParseReturnType, TypeOf } from 'zod';\n\nconst nonEmpty = 'This field cannot be empty';\n\nexport const stringRequired = () => z.string({ required_error: nonEmpty }).min(1, nonEmpty);\nexport const maxLength = (schema: z.ZodString): number => schema.maxLength || 0;\nexport const minLength = (schema: z.ZodString): number => schema.minLength || 0;\nexport const isRequired = (schema: z.ZodString): boolean => minLength(schema) > 0;\n\nexport type ZodObject = z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>>; // z.ZodType<any, any, any>\nexport type MaybeTypeOf<S extends ZodObject> = Partial<TypeOf<S>>;\nexport type SafeTypeOf<S extends ZodObject> = SafeParseReturnType<TypeOf<S>, TypeOf<S>>['data'];\nexport type Errors<S extends ZodObject> = typeToFlattenedError<TypeOf<S>>['fieldErrors'];\nexport type Validation<S extends ZodObject> =\n | {\n success: true; // this is true only if form was validated successfully\n data: SafeTypeOf<S>;\n errors?: never;\n }\n | {\n success: false;\n data?: MaybeTypeOf<S>;\n errors?: Errors<S>;\n };\n\nexport
|
|
1
|
+
{"version":3,"file":"form.js","sources":["../../../src/react/form/form.tsx"],"sourcesContent":["import { typeToFlattenedError, z, SafeParseReturnType, TypeOf } from 'zod';\nimport { createContext, useContext, useMemo } from 'react';\nimport clsx from 'clsx';\n\nconst nonEmpty = 'This field cannot be empty';\n\nexport const stringRequired = () => z.string({ required_error: nonEmpty }).min(1, nonEmpty);\nexport const maxLength = (schema: z.ZodString): number => schema.maxLength || 0;\nexport const minLength = (schema: z.ZodString): number => schema.minLength || 0;\nexport const isRequired = (schema: z.ZodString): boolean => minLength(schema) > 0;\n\nexport type ZodObject = z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>>; // z.ZodType<any, any, any>\nexport type MaybeTypeOf<S extends ZodObject> = Partial<TypeOf<S>>;\nexport type SafeTypeOf<S extends ZodObject> = SafeParseReturnType<TypeOf<S>, TypeOf<S>>['data'];\nexport type Errors<S extends ZodObject> = typeToFlattenedError<TypeOf<S>>['fieldErrors'];\nexport type Validation<S extends ZodObject> =\n | {\n success: true; // this is true only if form was validated successfully\n data: SafeTypeOf<S>;\n errors?: never;\n }\n | {\n success: false;\n data?: MaybeTypeOf<S>;\n errors?: Errors<S>;\n };\n\nexport const FormContext = createContext<{\n schema: ZodObject;\n}>(null as never);\n\nexport const Form = <S extends ZodObject>({ schema, children }: { schema: S; children: any }) => {\n const value = useMemo(() => ({ schema }), [schema]);\n return <FormContext.Provider value={value}>{children}</FormContext.Provider>;\n};\n\nconst getShape = <S extends ZodObject>(schema: S) =>\n (schema as z.ZodObject<any>).shape || (schema as z.ZodEffects<z.ZodObject<any>>).sourceType().shape;\n\nexport function create<S extends ZodObject>(schema: S) {\n if (!getShape(schema)) {\n throw new Error('Invalid schema: only z.object() or z.object().refine() are supported');\n }\n\n function register(name: keyof TypeOf<S>, data?: MaybeTypeOf<S>, errors?: Errors<S>, mui: boolean = false) {\n const field = getShape(schema)[name];\n return {\n name,\n id: name,\n required: isRequired(field),\n maxLength: maxLength(field),\n type: field.isEmail ? 'email' : (name as string).includes('password') ? 'password' : 'text',\n defaultValue: data?.[name],\n ...(mui\n ? {\n label: field.description,\n helperText: errors?.[name]?.join(', '),\n error: !!errors?.[name]?.length,\n }\n : {}),\n };\n }\n\n function validationError(data: MaybeTypeOf<S>, errors: Errors<S>): Validation<S> {\n return {\n success: false,\n data, // data is undefined if there are errors\n errors, // Next.js will butcher error object, so we provide something more primitive\n };\n }\n\n function validate(formData: FormData): Validation<S> {\n const rawData = Object.fromEntries(formData) as TypeOf<S>;\n const { error, data } = schema.safeParse(rawData);\n\n // console.log('Validate result', { error, data, rawData });\n\n if (error) {\n // data is undefined if there are errors\n // Next.js will butcher error object, so we provide something more primitive\n return validationError(rawData, error.flatten().fieldErrors as any);\n }\n\n return { success: true, data };\n }\n\n return { register, validate, validationError };\n}\n\nfunction Field<S extends ZodObject>({\n children,\n name,\n errors,\n hint,\n className,\n labelProps,\n ...props\n}: {\n children?: any;\n name: keyof TypeOf<S>;\n errors?: Validation<S>['errors'];\n hint?: string;\n className?: string;\n labelProps?: any;\n}) {\n const { schema } = useContext(FormContext);\n const { description } = getShape(schema)[name];\n\n return (\n <div {...props} className={clsx('form-row', className)}>\n {description && (\n <label {...labelProps} htmlFor={name}>\n {description}\n </label>\n )}\n {children}\n {hint && <Hint>{hint}</Hint>}\n {errors?.[name]?.map((e: string) => (\n <Hint error key={e}>\n {e}\n </Hint>\n ))}\n </div>\n );\n}\n\nexport function Hint({ children, error }: { children: any; error?: boolean }) {\n return <div className={`hint ${error ? 'hint-error' : ''}`}>{children}</div>;\n}\n"],"names":[],"mappings":";;;;AAIA,MAAM,WAAW;AAEJ,MAAA,iBAAiB,MAAM,EAAE,OAAO,EAAE,gBAAgB,UAAU,EAAE,IAAI,GAAG,QAAQ;AACnF,MAAM,YAAY,CAAC,WAAgC,OAAO,aAAa;AACvE,MAAM,YAAY,CAAC,WAAgC,OAAO,aAAa;AACvE,MAAM,aAAa,CAAC,WAAiC,UAAU,MAAM,IAAI;AAkBnE,MAAA,cAAc,cAExB,IAAa;AAET,MAAM,OAAO,CAAsB,EAAE,QAAQ,eAA6C;AACvF,QAAA,QAAQ,QAAQ,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC;AAClD,SAAQ,oBAAA,YAAY,UAAZ,EAAqB,OAAe,SAAS,CAAA;AACzD;AAEA,MAAM,WAAW,CAAsB,WAClC,OAA4B,SAAU,OAA0C,aAAa;AAE3F,SAAS,OAA4B,QAAW;AAC/C,MAAA,CAAC,SAAS,MAAM,GAAG;AACb,UAAA,IAAI,MAAM,sEAAsE;AAAA,EAAA;AAG1F,WAAS,SAAS,MAAuB,MAAuB,QAAoB,MAAe,OAAO;;AACtG,UAAM,QAAQ,SAAS,MAAM,EAAE,IAAI;AAC5B,WAAA;AAAA,MACH;AAAA,MACA,IAAI;AAAA,MACJ,UAAU,WAAW,KAAK;AAAA,MAC1B,WAAW,UAAU,KAAK;AAAA,MAC1B,MAAM,MAAM,UAAU,UAAW,KAAgB,SAAS,UAAU,IAAI,aAAa;AAAA,MACrF,cAAc,6BAAO;AAAA,MACrB,GAAI,MACE;AAAA,QACI,OAAO,MAAM;AAAA,QACb,aAAY,sCAAS,UAAT,mBAAgB,KAAK;AAAA,QACjC,OAAO,CAAC,GAAC,sCAAS,UAAT,mBAAgB;AAAA,MAAA,IAE7B,CAAA;AAAA,IACV;AAAA,EAAA;AAGK,WAAA,gBAAgB,MAAsB,QAAkC;AACtE,WAAA;AAAA,MACH,SAAS;AAAA,MACT;AAAA;AAAA,MACA;AAAA;AAAA,IACJ;AAAA,EAAA;AAGJ,WAAS,SAAS,UAAmC;AAC3C,UAAA,UAAU,OAAO,YAAY,QAAQ;AAC3C,UAAM,EAAE,OAAO,KAAA,IAAS,OAAO,UAAU,OAAO;AAIhD,QAAI,OAAO;AAGP,aAAO,gBAAgB,SAAS,MAAM,QAAA,EAAU,WAAkB;AAAA,IAAA;AAG/D,WAAA,EAAE,SAAS,MAAM,KAAK;AAAA,EAAA;AAG1B,SAAA,EAAE,UAAU,UAAU,gBAAgB;AACjD;AAuCO,SAAS,KAAK,EAAE,UAAU,SAA6C;AACnE,SAAA,oBAAC,SAAI,WAAW,QAAQ,QAAQ,eAAe,EAAE,IAAK,SAAS,CAAA;AAC1E;"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { createClient } from "./form/client.js";
|
|
2
|
-
import { Hint, create, isRequired, maxLength, minLength, stringRequired } from "./form/form.js";
|
|
2
|
+
import { Form, FormContext, Hint, create, isRequired, maxLength, minLength, stringRequired } from "./form/form.js";
|
|
3
3
|
import { apiCall, jsonContentType } from "./apiCall.js";
|
|
4
|
+
import { HotkeysContext, HotkeysProvider, useHotkeys } from "./keyboard.js";
|
|
4
5
|
import { useFetch } from "./useFetch.js";
|
|
5
6
|
import { useFetcher } from "./useFetcher.js";
|
|
6
7
|
export {
|
|
8
|
+
Form,
|
|
9
|
+
FormContext,
|
|
7
10
|
Hint,
|
|
11
|
+
HotkeysContext,
|
|
12
|
+
HotkeysProvider,
|
|
8
13
|
apiCall,
|
|
9
14
|
create,
|
|
10
15
|
createClient,
|
|
@@ -14,6 +19,7 @@ export {
|
|
|
14
19
|
minLength,
|
|
15
20
|
stringRequired,
|
|
16
21
|
useFetch,
|
|
17
|
-
useFetcher
|
|
22
|
+
useFetcher,
|
|
23
|
+
useHotkeys
|
|
18
24
|
};
|
|
19
25
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
export type Hotkeys = Record<KeyboardEvent['code'], (e: KeyboardEvent) => void>;
|
|
3
|
+
export declare const HotkeysContext: import('react').Context<{
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
setEnabled: Dispatch<SetStateAction<boolean>>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const HotkeysProvider: ({ children }: {
|
|
8
|
+
children: any;
|
|
9
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare const useHotkeys: (hotkeys: Hotkeys) => void;
|
|
11
|
+
//# sourceMappingURL=keyboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../src/react/keyboard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+C,QAAQ,EAAE,cAAc,EAAc,MAAM,OAAO,CAAC;AAM1G,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;AAEhF,eAAO,MAAM,cAAc;aACd,OAAO;gBACJ,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;EAChC,CAAC;AAElB,eAAO,MAAM,eAAe;;6CAM3B,CAAC;AAEF,eAAO,MAAM,UAAU,YAAa,OAAO,SAyB1C,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useState, useMemo, useContext, useEffect } from "react";
|
|
4
|
+
const isCtrlOrMeta = (e) => e.metaKey || e.ctrlKey;
|
|
5
|
+
const EVENT = "keydown";
|
|
6
|
+
const HotkeysContext = createContext(null);
|
|
7
|
+
const HotkeysProvider = ({ children }) => {
|
|
8
|
+
const [enabled, setEnabled] = useState(true);
|
|
9
|
+
const control = useMemo(() => ({ enabled, setEnabled }), [enabled, setEnabled]);
|
|
10
|
+
return /* @__PURE__ */ jsx(HotkeysContext.Provider, { value: control, children });
|
|
11
|
+
};
|
|
12
|
+
const useHotkeys = (hotkeys) => {
|
|
13
|
+
const { enabled } = useContext(HotkeysContext);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (typeof document === "undefined" || !enabled) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const listeners = (e) => {
|
|
19
|
+
if (!isCtrlOrMeta(e)) return;
|
|
20
|
+
for (const [code, callback] of Object.entries(hotkeys)) {
|
|
21
|
+
if (e.code === code) {
|
|
22
|
+
callback(e);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
window.addEventListener(EVENT, listeners, true);
|
|
28
|
+
return () => {
|
|
29
|
+
window.removeEventListener(EVENT, listeners);
|
|
30
|
+
};
|
|
31
|
+
}, [hotkeys, enabled]);
|
|
32
|
+
};
|
|
33
|
+
export {
|
|
34
|
+
HotkeysContext,
|
|
35
|
+
HotkeysProvider,
|
|
36
|
+
useHotkeys
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=keyboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.js","sources":["../../src/react/keyboard.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, createContext, useMemo, useState, Dispatch, SetStateAction, useContext } from 'react';\n\nconst isCtrlOrMeta = (e: KeyboardEvent) => e.metaKey || e.ctrlKey;\n\nconst EVENT = 'keydown';\n\nexport type Hotkeys = Record<KeyboardEvent['code'], (e: KeyboardEvent) => void>;\n\nexport const HotkeysContext = createContext<{\n enabled: boolean;\n setEnabled: Dispatch<SetStateAction<boolean>>;\n}>(null as never);\n\nexport const HotkeysProvider = ({ children }) => {\n const [enabled, setEnabled] = useState(true);\n\n const control = useMemo(() => ({ enabled, setEnabled }), [enabled, setEnabled]);\n\n return <HotkeysContext.Provider value={control}>{children}</HotkeysContext.Provider>;\n};\n\nexport const useHotkeys = (hotkeys: Hotkeys) => {\n const { enabled } = useContext(HotkeysContext);\n\n useEffect(() => {\n if (typeof document === 'undefined' || !enabled) {\n return;\n }\n\n const listeners = (e: KeyboardEvent) => {\n if (!isCtrlOrMeta(e)) return;\n\n for (const [code, callback] of Object.entries(hotkeys)) {\n if (e.code === code) {\n callback(e);\n return;\n }\n }\n };\n\n window.addEventListener(EVENT, listeners, true);\n\n return () => {\n window.removeEventListener(EVENT, listeners);\n };\n }, [hotkeys, enabled]);\n};\n"],"names":[],"mappings":";;;AAIA,MAAM,eAAe,CAAC,MAAqB,EAAE,WAAW,EAAE;AAE1D,MAAM,QAAQ;AAID,MAAA,iBAAiB,cAG3B,IAAa;AAET,MAAM,kBAAkB,CAAC,EAAE,eAAe;AAC7C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAErC,QAAA,UAAU,QAAQ,OAAO,EAAE,SAAS,eAAe,CAAC,SAAS,UAAU,CAAC;AAE9E,6BAAQ,eAAe,UAAf,EAAwB,OAAO,SAAU,UAAS;AAC9D;AAEa,MAAA,aAAa,CAAC,YAAqB;AAC5C,QAAM,EAAE,QAAA,IAAY,WAAW,cAAc;AAE7C,YAAU,MAAM;AACZ,QAAI,OAAO,aAAa,eAAe,CAAC,SAAS;AAC7C;AAAA,IAAA;AAGE,UAAA,YAAY,CAAC,MAAqB;AAChC,UAAA,CAAC,aAAa,CAAC,EAAG;AAEtB,iBAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,OAAO,GAAG;AAChD,YAAA,EAAE,SAAS,MAAM;AACjB,mBAAS,CAAC;AACV;AAAA,QAAA;AAAA,MACJ;AAAA,IAER;AAEO,WAAA,iBAAiB,OAAO,WAAW,IAAI;AAE9C,WAAO,MAAM;AACF,aAAA,oBAAoB,OAAO,SAAS;AAAA,IAC/C;AAAA,EAAA,GACD,CAAC,SAAS,OAAO,CAAC;AACzB;"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirill.konshin/utils",
|
|
3
3
|
"description": "Utilities",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.16",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"----- BUILD -----": "",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"expo-file-system": "^18.0.7",
|
|
25
25
|
"expo-sharing": "^13.0.1",
|
|
26
26
|
"expo-updates": "^0.26.13",
|
|
27
|
+
"file-saver": "^2.0.5",
|
|
27
28
|
"many-keys-map": "^2.0.1",
|
|
28
29
|
"psd": "^3.4.0",
|
|
29
30
|
"react-hook-form": "^7.54.2",
|
|
@@ -43,8 +44,10 @@
|
|
|
43
44
|
"@storybook/react": "^8.5.2",
|
|
44
45
|
"@storybook/react-vite": "^8.5.2",
|
|
45
46
|
"@storybook/test": "^8.5.2",
|
|
47
|
+
"@types/file-saver": "^2",
|
|
46
48
|
"@types/psd": "^3.4.3",
|
|
47
49
|
"@vitest/coverage-v8": "^3.0.4",
|
|
50
|
+
"@vitest/web-worker": "^3.0.5",
|
|
48
51
|
"bootstrap": "^5.3.3",
|
|
49
52
|
"bootstrap-icons": "^1.11.3",
|
|
50
53
|
"create-ts-index": "^1.14.0",
|
|
@@ -179,4 +182,4 @@
|
|
|
179
182
|
"main": "./dist/core/index.js",
|
|
180
183
|
"module": "./dist/core/index.js",
|
|
181
184
|
"types": "./dist/core/index.d.ts"
|
|
182
|
-
}
|
|
185
|
+
}
|
|
@@ -7,7 +7,7 @@ import { Select, Control, Range, Checkbox } from './controls';
|
|
|
7
7
|
const defaultOptions = { num: 1, bool: false, select: 'bar' };
|
|
8
8
|
|
|
9
9
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
10
|
-
const meta = {
|
|
10
|
+
const meta: Meta<typeof Control> = {
|
|
11
11
|
title: 'Bootstrap / Controls',
|
|
12
12
|
parameters: {
|
|
13
13
|
layout: 'centered',
|
|
@@ -32,7 +32,7 @@ const meta = {
|
|
|
32
32
|
|
|
33
33
|
return <div style={{ width: '300px' }}>{cloneElement(children as any, { ...safeArgs, ...controlProps })}</div>;
|
|
34
34
|
},
|
|
35
|
-
}
|
|
35
|
+
};
|
|
36
36
|
|
|
37
37
|
export default meta;
|
|
38
38
|
|
|
@@ -4,7 +4,7 @@ const lcFirst = (str) => str[0].toLowerCase() + str.substring(1, str.length);
|
|
|
4
4
|
|
|
5
5
|
const toProperty = (str) => lcFirst(str).split(' ').join('');
|
|
6
6
|
|
|
7
|
-
interface ControlProps {
|
|
7
|
+
export interface ControlProps {
|
|
8
8
|
value?: any;
|
|
9
9
|
setValue?: (v: any) => void;
|
|
10
10
|
defaultValue?: any;
|
|
@@ -22,9 +22,9 @@ interface ControlProps {
|
|
|
22
22
|
hideValue?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
type LabelProps = Pick<ControlProps, 'name' | 'value' | 'defaultValue'> & { reset: () => void };
|
|
25
|
+
export type LabelProps = Pick<ControlProps, 'name' | 'value' | 'defaultValue'> & { reset: () => void };
|
|
26
26
|
|
|
27
|
-
const Label = memo<LabelProps>(function Label({ name, reset, value, defaultValue }) {
|
|
27
|
+
export const Label = memo<LabelProps>(function Label({ name, reset, value, defaultValue }) {
|
|
28
28
|
return (
|
|
29
29
|
<label className="flex-grow-1 mb-0 d-flex align-items-center justify-content-start gap-2">
|
|
30
30
|
{name}
|
|
@@ -4,7 +4,7 @@ import { fn } from '@storybook/test';
|
|
|
4
4
|
import { ErrorAlert } from './error';
|
|
5
5
|
|
|
6
6
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
7
|
-
const meta = {
|
|
7
|
+
const meta: Meta<typeof ErrorAlert> = {
|
|
8
8
|
title: 'Bootstrap / ErrorAlert',
|
|
9
9
|
component: ErrorAlert,
|
|
10
10
|
parameters: {
|
|
@@ -16,7 +16,7 @@ const meta = {
|
|
|
16
16
|
children: { control: 'text' },
|
|
17
17
|
},
|
|
18
18
|
args: { onRetry: fn() },
|
|
19
|
-
}
|
|
19
|
+
};
|
|
20
20
|
|
|
21
21
|
export default meta;
|
|
22
22
|
type Story = StoryObj<typeof meta>;
|
|
@@ -29,6 +29,6 @@ export const String: Story = {
|
|
|
29
29
|
|
|
30
30
|
export const ErrorObject: Story = {
|
|
31
31
|
args: {
|
|
32
|
-
children: new Error('An error occurred'),
|
|
32
|
+
children: new Error('An error occurred') as any,
|
|
33
33
|
},
|
|
34
34
|
};
|
|
@@ -6,7 +6,7 @@ import { FieldGroup } from './field';
|
|
|
6
6
|
import { FormControl } from 'react-bootstrap';
|
|
7
7
|
|
|
8
8
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
9
|
-
const meta = {
|
|
9
|
+
const meta: Meta<typeof FieldGroup> = {
|
|
10
10
|
title: 'Bootstrap / FieldGroup',
|
|
11
11
|
component: FieldGroup,
|
|
12
12
|
parameters: {
|
|
@@ -19,7 +19,7 @@ const meta = {
|
|
|
19
19
|
horizontal: { control: 'check' },
|
|
20
20
|
},
|
|
21
21
|
args: { onClick: fn() },
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
23
|
|
|
24
24
|
export default meta;
|
|
25
25
|
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
|
|
3
3
|
import { GlobalLoading } from './globalLoading';
|
|
4
4
|
|
|
5
|
-
const meta = {
|
|
5
|
+
const meta: Meta<typeof GlobalLoading> = {
|
|
6
6
|
title: 'Bootstrap / GlobalLoading',
|
|
7
7
|
component: GlobalLoading,
|
|
8
8
|
parameters: {
|
|
@@ -12,7 +12,7 @@ const meta = {
|
|
|
12
12
|
argTypes: {
|
|
13
13
|
loading: { control: 'check' },
|
|
14
14
|
},
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
16
|
|
|
17
17
|
export default meta;
|
|
18
18
|
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
|
|
3
3
|
import { Loading } from './loading';
|
|
4
4
|
|
|
5
|
-
const meta = {
|
|
5
|
+
const meta: Meta<typeof Loading> = {
|
|
6
6
|
title: 'Bootstrap / Loading',
|
|
7
7
|
component: Loading,
|
|
8
8
|
parameters: {
|
|
@@ -14,7 +14,7 @@ const meta = {
|
|
|
14
14
|
show: { control: 'check' },
|
|
15
15
|
size: { control: 'select', options: ['sm', 'md'] },
|
|
16
16
|
},
|
|
17
|
-
}
|
|
17
|
+
};
|
|
18
18
|
|
|
19
19
|
export default meta;
|
|
20
20
|
|
|
@@ -45,7 +45,7 @@ const lorem = (
|
|
|
45
45
|
</>
|
|
46
46
|
);
|
|
47
47
|
|
|
48
|
-
const meta = {
|
|
48
|
+
const meta: Meta<typeof Screen> = {
|
|
49
49
|
title: 'Bootstrap / Screen',
|
|
50
50
|
component: Screen,
|
|
51
51
|
parameters: {
|
|
@@ -75,7 +75,7 @@ const meta = {
|
|
|
75
75
|
),
|
|
76
76
|
backCb: fn(),
|
|
77
77
|
},
|
|
78
|
-
}
|
|
78
|
+
};
|
|
79
79
|
|
|
80
80
|
export default meta;
|
|
81
81
|
|
|
@@ -4,7 +4,7 @@ import { fn } from '@storybook/test';
|
|
|
4
4
|
import { Toaster } from './toaster';
|
|
5
5
|
|
|
6
6
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
7
|
-
const meta = {
|
|
7
|
+
const meta: Meta<typeof Toaster> = {
|
|
8
8
|
title: 'Bootstrap / Toaster',
|
|
9
9
|
component: Toaster,
|
|
10
10
|
parameters: {
|
|
@@ -13,7 +13,7 @@ const meta = {
|
|
|
13
13
|
tags: ['autodocs'],
|
|
14
14
|
argTypes: {},
|
|
15
15
|
args: { onClick: fn() },
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
17
|
|
|
18
18
|
export default meta;
|
|
19
19
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { FC } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { useModal, UseModalProps } from './useModal';
|
|
4
|
-
import { Button
|
|
4
|
+
import { Button } from 'react-bootstrap';
|
|
5
5
|
|
|
6
6
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
7
|
-
const meta = {
|
|
7
|
+
const meta: Meta<FC<UseModalProps>> = {
|
|
8
8
|
title: 'Bootstrap / useModal',
|
|
9
9
|
parameters: {
|
|
10
10
|
layout: 'centered',
|
|
@@ -34,7 +34,7 @@ const meta = {
|
|
|
34
34
|
);
|
|
35
35
|
},
|
|
36
36
|
// args: { onSubmit: (e) => e.preventDefault() },
|
|
37
|
-
}
|
|
37
|
+
};
|
|
38
38
|
|
|
39
39
|
export default meta;
|
|
40
40
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# WRPC
|
|
2
|
+
|
|
3
|
+
TODO
|
|
4
|
+
|
|
5
|
+
- [ ] Add to readme https://stackoverflow.com/questions/77727664/how-to-get-returned-value-from-async-generator-when-using-for-await
|
|
6
|
+
- [ ] Value passed to next are ignored https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/next#sending_values_to_the_generator
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
sequenceDiagram
|
|
10
|
+
box Main
|
|
11
|
+
participant Code
|
|
12
|
+
participant Caller
|
|
13
|
+
end
|
|
14
|
+
box Worker
|
|
15
|
+
participant Worker
|
|
16
|
+
participant Resolver
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
%% autonumber
|
|
20
|
+
|
|
21
|
+
Code->>Caller: caller.generator(payload)
|
|
22
|
+
|
|
23
|
+
Caller-->>Code: iterator & promise
|
|
24
|
+
|
|
25
|
+
Code->>Code: Start main CONSUMER loop
|
|
26
|
+
|
|
27
|
+
Code->>Caller: iterator[Symbol]()
|
|
28
|
+
|
|
29
|
+
Caller->>Caller: Start replica GENERATOR loop
|
|
30
|
+
|
|
31
|
+
Code->>Caller: iterator.next()
|
|
32
|
+
|
|
33
|
+
Caller->>Worker: postMessage(payload)
|
|
34
|
+
|
|
35
|
+
Worker->>Resolver: resolver(payload)
|
|
36
|
+
|
|
37
|
+
Resolver-->>Worker: iterator
|
|
38
|
+
|
|
39
|
+
Worker->>Worker: Start replica CONSUMER loop
|
|
40
|
+
|
|
41
|
+
Worker->>Resolver: iterator[Symbol]()
|
|
42
|
+
|
|
43
|
+
Resolver->>Resolver: Start GENERATOR loop
|
|
44
|
+
|
|
45
|
+
rect rgb(232, 244, 248)
|
|
46
|
+
|
|
47
|
+
loop Synchronized iteration
|
|
48
|
+
Worker->>+Resolver: iterator.next(payload)
|
|
49
|
+
|
|
50
|
+
Resolver-->>-Worker: yield value
|
|
51
|
+
|
|
52
|
+
Worker-->>Caller: postMessage(value, done=false)
|
|
53
|
+
|
|
54
|
+
Note over Worker: Wait for ACK or ABORT
|
|
55
|
+
|
|
56
|
+
Caller-->>Code: yield value
|
|
57
|
+
|
|
58
|
+
Code->>Caller: iterator.next(nextPayload)
|
|
59
|
+
|
|
60
|
+
Caller->>Worker: postMessage(ack, nextPayload)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
break Break or AbortSignal (maybe)
|
|
66
|
+
Code-->>Code: Loop exits
|
|
67
|
+
Code->>Caller: signal.abort()
|
|
68
|
+
|
|
69
|
+
Caller-->>Caller: Loop exits
|
|
70
|
+
|
|
71
|
+
Caller->>Worker: postMessage(abort)
|
|
72
|
+
|
|
73
|
+
Worker-->>Worker: Loop exits via Break
|
|
74
|
+
|
|
75
|
+
Resolver-->>Resolver: Loop exits
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
opt Occurs on Error
|
|
79
|
+
Resolver-->>Worker: throw Error
|
|
80
|
+
|
|
81
|
+
Worker-->>Caller: postMessage(error, done=true)
|
|
82
|
+
|
|
83
|
+
Caller-->>Code: throw Error
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Resolver-->>Worker: return value
|
|
87
|
+
|
|
88
|
+
Worker-->>Caller: postMessage(value, done=true)
|
|
89
|
+
|
|
90
|
+
Caller-->>Code: return value
|
|
91
|
+
```
|
package/src/core/cache.ts
CHANGED
|
@@ -64,6 +64,7 @@ export function memo<Key extends any[], Val, SerializedKey extends any[] = Key>(
|
|
|
64
64
|
) {
|
|
65
65
|
const map = new ManyKeysMap<SerializedKey, Val>();
|
|
66
66
|
|
|
67
|
+
//TODO Extend ManyKeysMap
|
|
67
68
|
const clear = (...condition: Key | any[]) => {
|
|
68
69
|
if (!condition?.length) {
|
|
69
70
|
map.forEach((value, serializedKey) => {
|
|
@@ -114,17 +115,12 @@ export function memo<Key extends any[], Val, SerializedKey extends any[] = Key>(
|
|
|
114
115
|
*
|
|
115
116
|
* This makes possible to implement various one-off and subsequent transformations.
|
|
116
117
|
*
|
|
117
|
-
* 1. `write`:
|
|
118
|
-
* 2. `read`:
|
|
119
|
-
* TODO Better names: once, folloing
|
|
120
|
-
* TODO Better names: memoize, recover
|
|
121
|
-
* TODO Better names: writeTransformer, readTransformer
|
|
122
|
-
* TODO Better names: writeModifier, readModifier
|
|
123
|
-
* TODO Better names: write, read
|
|
118
|
+
* 1. `write`: Transform value BEFORE writing to cache, called only once if cache IS NOT present or IS NOT valid
|
|
119
|
+
* 2. `read`: Transform value AFTER it's read from cache, always called if cache IS valid, keep in mind this transform is applied on top of BEFORE
|
|
124
120
|
*
|
|
125
121
|
* Both should return same type or null.
|
|
126
122
|
*
|
|
127
|
-
* If newValue is null, old is returned, and no cache is set.
|
|
123
|
+
* If `newValue` is null, old is returned, and no cache is set.
|
|
128
124
|
*
|
|
129
125
|
* ```ts
|
|
130
126
|
* class InputCache extends TypedCache<string, ImageBitmap> {
|
|
@@ -186,7 +182,7 @@ export function memo<Key extends any[], Val, SerializedKey extends any[] = Key>(
|
|
|
186
182
|
*/
|
|
187
183
|
export abstract class TransformerMap<Key, Val = Key> extends Map<Key, Val> {
|
|
188
184
|
constructor(protected readonly name: string) {
|
|
189
|
-
console.warn('CREATE cache', name);
|
|
185
|
+
// console.warn('CREATE cache', name);
|
|
190
186
|
super();
|
|
191
187
|
}
|
|
192
188
|
|
|
@@ -199,13 +195,17 @@ export abstract class TransformerMap<Key, Val = Key> extends Map<Key, Val> {
|
|
|
199
195
|
}
|
|
200
196
|
|
|
201
197
|
delete(key: Key) {
|
|
202
|
-
const
|
|
203
|
-
|
|
198
|
+
const val = this.get(key);
|
|
199
|
+
const has = typeof val !== 'undefined';
|
|
200
|
+
if (has) this.dispose(val, key);
|
|
204
201
|
super.delete(key);
|
|
205
202
|
return has;
|
|
206
203
|
}
|
|
207
204
|
|
|
208
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Do something with the value and key before removing it from cache
|
|
207
|
+
* For example, close a file or a bitmap
|
|
208
|
+
*/
|
|
209
209
|
protected dispose(value: Val, key: Key) {}
|
|
210
210
|
|
|
211
211
|
clear() {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import saver from 'file-saver';
|
|
2
|
+
|
|
3
|
+
export const downloadFile = async (filename, text, type = 'application/json') => {
|
|
4
|
+
//FIXME https://github.com/eligrey/FileSaver.js/issues/731
|
|
5
|
+
//FIXME https://github.com/eligrey/FileSaver.js/issues/471
|
|
6
|
+
saver.saveAs(new Blob([text], { type }), filename);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const openFile = (): Promise<string> =>
|
|
10
|
+
new Promise((res, rej) => {
|
|
11
|
+
const input = document.createElement('input');
|
|
12
|
+
input.type = 'file';
|
|
13
|
+
input.addEventListener('change', function readFile(e) {
|
|
14
|
+
input.removeEventListener('change', readFile);
|
|
15
|
+
// @ts-expect-error file is always there
|
|
16
|
+
const [file] = e.target.files;
|
|
17
|
+
if (!file) return;
|
|
18
|
+
const reader = new FileReader();
|
|
19
|
+
reader.onload = (e) => res(e.target?.result as string);
|
|
20
|
+
reader.onerror = rej;
|
|
21
|
+
reader.onabort = rej;
|
|
22
|
+
reader.readAsText(file);
|
|
23
|
+
});
|
|
24
|
+
input.click();
|
|
25
|
+
});
|
package/src/core/index.ts
CHANGED
package/src/core/ls.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function getStorage(pref: string) {
|
|
2
|
+
const prefix = (key) => `${pref}-${key}`;
|
|
3
|
+
|
|
4
|
+
const ls = (typeof window !== 'undefined' ? localStorage : null) as Storage;
|
|
5
|
+
|
|
6
|
+
const read = (key: string) => JSON.parse(ls?.getItem(prefix(key)) || 'null');
|
|
7
|
+
const write = (key: string, value: any) => ls?.setItem(prefix(key), JSON.stringify(value));
|
|
8
|
+
const remove = (key: string) => ls?.removeItem(prefix(key));
|
|
9
|
+
|
|
10
|
+
return { read, write, remove };
|
|
11
|
+
}
|