@isikk/core 0.4.0 → 0.7.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/dist/configError-BtEWY-oE.d.ts +79 -0
- package/dist/drf/index.cjs +55 -0
- package/dist/drf/index.cjs.map +1 -0
- package/dist/drf/index.d.cts +27 -0
- package/dist/drf/index.d.ts +27 -0
- package/dist/drf/index.js +28 -0
- package/dist/drf/index.js.map +1 -0
- package/dist/hooks/index.cjs +18 -10
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +18 -10
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/next/config/browser.d.ts +6 -4
- package/dist/next/config/browser.js +62 -12
- package/dist/next/config/browser.js.map +1 -1
- package/dist/next/config/index.d.ts +7 -13
- package/dist/next/config/index.js +65 -15
- package/dist/next/config/index.js.map +1 -1
- package/dist/next/middleware/index.cjs.map +1 -1
- package/dist/next/middleware/index.js.map +1 -1
- package/dist/next/request/index.cjs.map +1 -1
- package/dist/next/request/index.d.cts +2 -4
- package/dist/next/request/index.d.ts +2 -4
- package/dist/next/request/index.js.map +1 -1
- package/dist/node/index.cjs +4 -3
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +4 -3
- package/dist/node/index.js.map +1 -1
- package/package.json +11 -2
- package/dist/shared-NQ6Ct9hr.d.ts +0 -37
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type Caster<T> = ((value: string) => T) & {
|
|
4
|
+
missingDefault?: T;
|
|
5
|
+
errorDefault?: T;
|
|
6
|
+
};
|
|
7
|
+
declare function caster<T>(fn: (value: string) => T): (options?: {
|
|
8
|
+
missingDefault?: T;
|
|
9
|
+
errorDefault?: T;
|
|
10
|
+
}) => Caster<T>;
|
|
11
|
+
declare const string: (options?: {
|
|
12
|
+
missingDefault?: string | undefined;
|
|
13
|
+
errorDefault?: string | undefined;
|
|
14
|
+
}) => Caster<string>;
|
|
15
|
+
declare const integer: (options?: {
|
|
16
|
+
missingDefault?: number | undefined;
|
|
17
|
+
errorDefault?: number | undefined;
|
|
18
|
+
}) => Caster<number>;
|
|
19
|
+
declare const float: (options?: {
|
|
20
|
+
missingDefault?: number | undefined;
|
|
21
|
+
errorDefault?: number | undefined;
|
|
22
|
+
}) => Caster<number>;
|
|
23
|
+
declare const boolean: (options?: {
|
|
24
|
+
missingDefault?: boolean | undefined;
|
|
25
|
+
errorDefault?: boolean | undefined;
|
|
26
|
+
}) => Caster<boolean>;
|
|
27
|
+
declare const commaSeparatedList: (options?: {
|
|
28
|
+
missingDefault?: string[] | undefined;
|
|
29
|
+
errorDefault?: string[] | undefined;
|
|
30
|
+
}) => Caster<string[]>;
|
|
31
|
+
declare const commaSeparatedIntList: (options?: {
|
|
32
|
+
missingDefault?: number[] | undefined;
|
|
33
|
+
errorDefault?: number[] | undefined;
|
|
34
|
+
}) => Caster<number[]>;
|
|
35
|
+
declare const commaSeparatedFloatList: (options?: {
|
|
36
|
+
missingDefault?: number[] | undefined;
|
|
37
|
+
errorDefault?: number[] | undefined;
|
|
38
|
+
}) => Caster<number[]>;
|
|
39
|
+
|
|
40
|
+
type ConfigSchema = {
|
|
41
|
+
[key: string]: Caster<unknown> | ConfigSchema;
|
|
42
|
+
};
|
|
43
|
+
type InferConfig<S> = {
|
|
44
|
+
[K in keyof S]: S[K] extends Caster<infer T> ? T : S[K] extends ConfigSchema ? InferConfig<S[K]> : never;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
interface PublicConfigOptions {
|
|
48
|
+
/**
|
|
49
|
+
* The property the payload is injected under on `window`. Required, with no default and no
|
|
50
|
+
* derived fallback: the name belongs in your application's namespace, not this package's, and
|
|
51
|
+
* naming it at the call site is what stops two configs from silently landing on a name neither
|
|
52
|
+
* of them chose.
|
|
53
|
+
*/
|
|
54
|
+
globalKey: string;
|
|
55
|
+
/** Prepended to every environment variable name this call reads, joined with `sep`. */
|
|
56
|
+
prefix?: string;
|
|
57
|
+
/** Joins the prefix and the nested key path into a variable name. Defaults to `"__"`. */
|
|
58
|
+
sep?: string;
|
|
59
|
+
}
|
|
60
|
+
interface PublicConfigScriptProps {
|
|
61
|
+
/** Forwarded to the injected `<script>` so a CSP with a per-request nonce keeps working. */
|
|
62
|
+
nonce?: string;
|
|
63
|
+
}
|
|
64
|
+
type PublicConfigScriptComponent = (props: PublicConfigScriptProps) => ReactNode | Promise<ReactNode>;
|
|
65
|
+
interface PublicConfig<S extends ConfigSchema> {
|
|
66
|
+
CONFIG: InferConfig<S>;
|
|
67
|
+
PublicConfigScript: PublicConfigScriptComponent;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Isolated in its own module - with no `process.env` access anywhere in it - so that the browser
|
|
72
|
+
* half of `@isikk/core/next/config` can throw the same error type without importing
|
|
73
|
+
* anything that reads the environment. Keeping the split structural means the guarantee holds
|
|
74
|
+
* because of what the file contains, not because a bundler happened to tree-shake it away.
|
|
75
|
+
*/
|
|
76
|
+
declare class ConfigError extends Error {
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { type ConfigSchema as C, type InferConfig as I, type PublicConfigOptions as P, type PublicConfig as a, type Caster as b, ConfigError as c, type PublicConfigScriptComponent as d, type PublicConfigScriptProps as e, boolean as f, caster as g, commaSeparatedFloatList as h, commaSeparatedIntList as i, commaSeparatedList as j, float as k, integer as l, string as s };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/drf/index.ts
|
|
21
|
+
var drf_exports = {};
|
|
22
|
+
__export(drf_exports, {
|
|
23
|
+
detailOf: () => detailOf,
|
|
24
|
+
messagesOf: () => messagesOf,
|
|
25
|
+
toFormErrors: () => toFormErrors
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(drf_exports);
|
|
28
|
+
function toFormErrors(body) {
|
|
29
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
30
|
+
const errors = {};
|
|
31
|
+
for (const [field, value] of Object.entries(body)) {
|
|
32
|
+
if (field === "detail") continue;
|
|
33
|
+
if (typeof value === "string") errors[field] = [value];
|
|
34
|
+
else if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) errors[field] = value;
|
|
35
|
+
}
|
|
36
|
+
return Object.keys(errors).length > 0 ? errors : void 0;
|
|
37
|
+
}
|
|
38
|
+
function detailOf(body) {
|
|
39
|
+
if (!body) return void 0;
|
|
40
|
+
const detail = body.detail;
|
|
41
|
+
return typeof detail === "string" ? detail : void 0;
|
|
42
|
+
}
|
|
43
|
+
function messagesOf(errors) {
|
|
44
|
+
if (!errors) return void 0;
|
|
45
|
+
const { non_field_errors: nonField = [], ...fields } = errors;
|
|
46
|
+
const messages = [...nonField, ...Object.values(fields).flat()];
|
|
47
|
+
return messages.length > 0 ? messages.join(" ") : void 0;
|
|
48
|
+
}
|
|
49
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
50
|
+
0 && (module.exports = {
|
|
51
|
+
detailOf,
|
|
52
|
+
messagesOf,
|
|
53
|
+
toFormErrors
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/drf/index.ts"],"sourcesContent":["export type FormErrors = Record<string, string[]> & { non_field_errors?: string[] }\n\n/**\n * DRF refuses in two shapes, and which one arrives is decided by the status.\n *\n * A 400 is a validation failure and carries the fields: `{name: [\"This field may not be blank.\"]}`,\n * with `non_field_errors` for anything about the form as a whole. Everything else - 401, 403, 404,\n * 409 - carries `{detail: \"one sentence\"}`, written for a person by whoever raised it.\n *\n * This reads the first shape; `detailOf` reads the second.\n */\nexport function toFormErrors(body: unknown): FormErrors | undefined {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return undefined\n\n const errors: FormErrors = {}\n for (const [field, value] of Object.entries(body as Record<string, unknown>)) {\n // `detail` is the other shape, not a field. A serializer with a field genuinely called `detail`\n // would lose it here - that is the price of keeping the two shapes apart.\n if (field === 'detail') continue\n if (typeof value === 'string') errors[field] = [value]\n else if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) errors[field] = value\n }\n return Object.keys(errors).length > 0 ? errors : undefined\n}\n\n/** The sentence DRF wrote for a refusal that is not about a field. */\nexport function detailOf(body: unknown): string | undefined {\n // `!body` is the whole guard: reading a property off a primitive is legal, and the two values that\n // would throw are the two this catches.\n if (!body) return undefined\n const detail = (body as { detail?: unknown }).detail\n return typeof detail === 'string' ? detail : undefined\n}\n\n/**\n * Every message in a refusal, as one line, for a caller with nowhere to put them per field.\n *\n * A button or a switch gets the same 400 body a form does. Keeping only `non_field_errors` there\n * would lose the only thing the server said.\n *\n * The field names are left out: they are the serializer's `snake_case`, and DRF's messages read as\n * whole sentences without them.\n */\nexport function messagesOf(errors: FormErrors | undefined): string | undefined {\n if (!errors) return undefined\n const { non_field_errors: nonField = [], ...fields } = errors\n const messages = [...nonField, ...Object.values(fields).flat()]\n return messages.length > 0 ? messages.join(' ') : undefined\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,SAAS,aAAa,MAAuC;AAClE,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,QAAO;AAErE,QAAM,SAAqB,CAAC;AAC5B,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAG5E,QAAI,UAAU,SAAU;AACxB,QAAI,OAAO,UAAU,SAAU,QAAO,KAAK,IAAI,CAAC,KAAK;AAAA,aAC5C,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ,EAAG,QAAO,KAAK,IAAI;AAAA,EACtG;AACA,SAAO,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS;AACnD;AAGO,SAAS,SAAS,MAAmC;AAG1D,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAU,KAA8B;AAC9C,SAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;AAWO,SAAS,WAAW,QAAoD;AAC7E,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,EAAE,kBAAkB,WAAW,CAAC,GAAG,GAAG,OAAO,IAAI;AACvD,QAAM,WAAW,CAAC,GAAG,UAAU,GAAG,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC;AAC9D,SAAO,SAAS,SAAS,IAAI,SAAS,KAAK,GAAG,IAAI;AACpD;","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type FormErrors = Record<string, string[]> & {
|
|
2
|
+
non_field_errors?: string[];
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* DRF refuses in two shapes, and which one arrives is decided by the status.
|
|
6
|
+
*
|
|
7
|
+
* A 400 is a validation failure and carries the fields: `{name: ["This field may not be blank."]}`,
|
|
8
|
+
* with `non_field_errors` for anything about the form as a whole. Everything else - 401, 403, 404,
|
|
9
|
+
* 409 - carries `{detail: "one sentence"}`, written for a person by whoever raised it.
|
|
10
|
+
*
|
|
11
|
+
* This reads the first shape; `detailOf` reads the second.
|
|
12
|
+
*/
|
|
13
|
+
declare function toFormErrors(body: unknown): FormErrors | undefined;
|
|
14
|
+
/** The sentence DRF wrote for a refusal that is not about a field. */
|
|
15
|
+
declare function detailOf(body: unknown): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Every message in a refusal, as one line, for a caller with nowhere to put them per field.
|
|
18
|
+
*
|
|
19
|
+
* A button or a switch gets the same 400 body a form does. Keeping only `non_field_errors` there
|
|
20
|
+
* would lose the only thing the server said.
|
|
21
|
+
*
|
|
22
|
+
* The field names are left out: they are the serializer's `snake_case`, and DRF's messages read as
|
|
23
|
+
* whole sentences without them.
|
|
24
|
+
*/
|
|
25
|
+
declare function messagesOf(errors: FormErrors | undefined): string | undefined;
|
|
26
|
+
|
|
27
|
+
export { type FormErrors, detailOf, messagesOf, toFormErrors };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type FormErrors = Record<string, string[]> & {
|
|
2
|
+
non_field_errors?: string[];
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* DRF refuses in two shapes, and which one arrives is decided by the status.
|
|
6
|
+
*
|
|
7
|
+
* A 400 is a validation failure and carries the fields: `{name: ["This field may not be blank."]}`,
|
|
8
|
+
* with `non_field_errors` for anything about the form as a whole. Everything else - 401, 403, 404,
|
|
9
|
+
* 409 - carries `{detail: "one sentence"}`, written for a person by whoever raised it.
|
|
10
|
+
*
|
|
11
|
+
* This reads the first shape; `detailOf` reads the second.
|
|
12
|
+
*/
|
|
13
|
+
declare function toFormErrors(body: unknown): FormErrors | undefined;
|
|
14
|
+
/** The sentence DRF wrote for a refusal that is not about a field. */
|
|
15
|
+
declare function detailOf(body: unknown): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Every message in a refusal, as one line, for a caller with nowhere to put them per field.
|
|
18
|
+
*
|
|
19
|
+
* A button or a switch gets the same 400 body a form does. Keeping only `non_field_errors` there
|
|
20
|
+
* would lose the only thing the server said.
|
|
21
|
+
*
|
|
22
|
+
* The field names are left out: they are the serializer's `snake_case`, and DRF's messages read as
|
|
23
|
+
* whole sentences without them.
|
|
24
|
+
*/
|
|
25
|
+
declare function messagesOf(errors: FormErrors | undefined): string | undefined;
|
|
26
|
+
|
|
27
|
+
export { type FormErrors, detailOf, messagesOf, toFormErrors };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/drf/index.ts
|
|
2
|
+
function toFormErrors(body) {
|
|
3
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
4
|
+
const errors = {};
|
|
5
|
+
for (const [field, value] of Object.entries(body)) {
|
|
6
|
+
if (field === "detail") continue;
|
|
7
|
+
if (typeof value === "string") errors[field] = [value];
|
|
8
|
+
else if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) errors[field] = value;
|
|
9
|
+
}
|
|
10
|
+
return Object.keys(errors).length > 0 ? errors : void 0;
|
|
11
|
+
}
|
|
12
|
+
function detailOf(body) {
|
|
13
|
+
if (!body) return void 0;
|
|
14
|
+
const detail = body.detail;
|
|
15
|
+
return typeof detail === "string" ? detail : void 0;
|
|
16
|
+
}
|
|
17
|
+
function messagesOf(errors) {
|
|
18
|
+
if (!errors) return void 0;
|
|
19
|
+
const { non_field_errors: nonField = [], ...fields } = errors;
|
|
20
|
+
const messages = [...nonField, ...Object.values(fields).flat()];
|
|
21
|
+
return messages.length > 0 ? messages.join(" ") : void 0;
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
detailOf,
|
|
25
|
+
messagesOf,
|
|
26
|
+
toFormErrors
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/drf/index.ts"],"sourcesContent":["export type FormErrors = Record<string, string[]> & { non_field_errors?: string[] }\n\n/**\n * DRF refuses in two shapes, and which one arrives is decided by the status.\n *\n * A 400 is a validation failure and carries the fields: `{name: [\"This field may not be blank.\"]}`,\n * with `non_field_errors` for anything about the form as a whole. Everything else - 401, 403, 404,\n * 409 - carries `{detail: \"one sentence\"}`, written for a person by whoever raised it.\n *\n * This reads the first shape; `detailOf` reads the second.\n */\nexport function toFormErrors(body: unknown): FormErrors | undefined {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return undefined\n\n const errors: FormErrors = {}\n for (const [field, value] of Object.entries(body as Record<string, unknown>)) {\n // `detail` is the other shape, not a field. A serializer with a field genuinely called `detail`\n // would lose it here - that is the price of keeping the two shapes apart.\n if (field === 'detail') continue\n if (typeof value === 'string') errors[field] = [value]\n else if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) errors[field] = value\n }\n return Object.keys(errors).length > 0 ? errors : undefined\n}\n\n/** The sentence DRF wrote for a refusal that is not about a field. */\nexport function detailOf(body: unknown): string | undefined {\n // `!body` is the whole guard: reading a property off a primitive is legal, and the two values that\n // would throw are the two this catches.\n if (!body) return undefined\n const detail = (body as { detail?: unknown }).detail\n return typeof detail === 'string' ? detail : undefined\n}\n\n/**\n * Every message in a refusal, as one line, for a caller with nowhere to put them per field.\n *\n * A button or a switch gets the same 400 body a form does. Keeping only `non_field_errors` there\n * would lose the only thing the server said.\n *\n * The field names are left out: they are the serializer's `snake_case`, and DRF's messages read as\n * whole sentences without them.\n */\nexport function messagesOf(errors: FormErrors | undefined): string | undefined {\n if (!errors) return undefined\n const { non_field_errors: nonField = [], ...fields } = errors\n const messages = [...nonField, ...Object.values(fields).flat()]\n return messages.length > 0 ? messages.join(' ') : undefined\n}\n"],"mappings":";AAWO,SAAS,aAAa,MAAuC;AAClE,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,QAAO;AAErE,QAAM,SAAqB,CAAC;AAC5B,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAG5E,QAAI,UAAU,SAAU;AACxB,QAAI,OAAO,UAAU,SAAU,QAAO,KAAK,IAAI,CAAC,KAAK;AAAA,aAC5C,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ,EAAG,QAAO,KAAK,IAAI;AAAA,EACtG;AACA,SAAO,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS;AACnD;AAGO,SAAS,SAAS,MAAmC;AAG1D,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAU,KAA8B;AAC9C,SAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;AAWO,SAAS,WAAW,QAAoD;AAC7E,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,EAAE,kBAAkB,WAAW,CAAC,GAAG,GAAG,OAAO,IAAI;AACvD,QAAM,WAAW,CAAC,GAAG,UAAU,GAAG,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC;AAC9D,SAAO,SAAS,SAAS,IAAI,SAAS,KAAK,GAAG,IAAI;AACpD;","names":[]}
|
package/dist/hooks/index.cjs
CHANGED
|
@@ -127,10 +127,14 @@ function useFilePaste({
|
|
|
127
127
|
const [files, setFiles] = (0, import_react.useState)([]);
|
|
128
128
|
const [isLoading, setIsLoading] = (0, import_react.useState)(false);
|
|
129
129
|
const [error, setError] = (0, import_react.useState)(null);
|
|
130
|
-
const clearFiles = (0, import_react.useCallback)(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
const clearFiles = (0, import_react.useCallback)(
|
|
131
|
+
() => {
|
|
132
|
+
setFiles([]);
|
|
133
|
+
setError(null);
|
|
134
|
+
},
|
|
135
|
+
// Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.
|
|
136
|
+
[]
|
|
137
|
+
);
|
|
134
138
|
const validateFiles = (0, import_react.useCallback)(
|
|
135
139
|
(pastedFiles) => {
|
|
136
140
|
if (acceptedTypes && acceptedTypes.length > 0) {
|
|
@@ -317,12 +321,16 @@ function useFileDragDrop(options = {}) {
|
|
|
317
321
|
}
|
|
318
322
|
return void 0;
|
|
319
323
|
}, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop]);
|
|
320
|
-
const reset = (0, import_react.useCallback)(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
const reset = (0, import_react.useCallback)(
|
|
325
|
+
() => {
|
|
326
|
+
setState({
|
|
327
|
+
isDragging: false,
|
|
328
|
+
files: null
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
// Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.
|
|
332
|
+
[]
|
|
333
|
+
);
|
|
326
334
|
return {
|
|
327
335
|
ref,
|
|
328
336
|
isDragging: state.isDragging,
|
package/dist/hooks/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["import type { ChangeEvent, DependencyList, DragEvent, EffectCallback } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport function useElementAttributes<T extends HTMLElement, K extends keyof T>(attributeKeys: K[]) {\n const ref = useRef<T | null>(null)\n const [attributeValues, setAttributeValue] = useState<Partial<Pick<T, K>>>({})\n\n const updateAttribute = useCallback(() => {\n if (ref.current) {\n const values = {} as Partial<Pick<T, K>>\n for (const key of attributeKeys) {\n // Object.defineProperty (unlike a plain `values[key] = value` assignment) always creates/\n // overwrites an own property, even if a consumer's T somehow has a key like \"__proto__\" -\n // matches the same defensive pattern used everywhere else in this package that writes a\n // non-literal key onto an object.\n Object.defineProperty(values, key, {\n value: ref.current[key],\n writable: true,\n configurable: true,\n enumerable: true,\n })\n }\n\n setAttributeValue((prevValues) => {\n const hasChanged = attributeKeys.some((key) => prevValues[key] !== values[key])\n return hasChanged ? values : prevValues\n })\n }\n }, [attributeKeys])\n\n useEffect(() => {\n updateAttribute()\n window.addEventListener('resize', updateAttribute)\n\n if (ref.current) {\n const observer = new MutationObserver(updateAttribute)\n observer.observe(ref.current, { attributes: true })\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n observer.disconnect()\n }\n }\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n }\n }, [updateAttribute])\n\n return { ref, attributeValues }\n}\n\ntype InputEventType = ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>\n\nexport function useFormState<T>(initialState: T) {\n const [formState, setFormState] = useState<T>(initialState)\n const [formErrors, setFormErrors] = useState<Partial<Record<keyof T, string[]>> & { non_field_errors?: string[] }>()\n\n function resetFormState() {\n setFormState(initialState)\n }\n\n function handleFormState<K extends keyof T, HandlerInputType>({\n key,\n inputType,\n }: {\n key: K\n inputType: 'event' | 'value'\n }) {\n return (event: HandlerInputType) => {\n let value: T[K]\n if (inputType === 'event') {\n const target = (event as InputEventType).target\n if (target.type === 'checkbox') {\n value = (target as HTMLInputElement).checked as T[K]\n } else if (target.type === 'number' || target.type === 'range') {\n value = (target as HTMLInputElement).valueAsNumber as T[K]\n } else {\n value = target.value as T[K]\n }\n } else {\n value = event as T[K]\n }\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n }\n\n const handleFormStateValue = <K extends keyof T>(key: K) => handleFormState<K, T[K]>({ key, inputType: 'value' })\n const handleFormStateEvent = <K extends keyof T>(key: K) =>\n handleFormState<K, InputEventType>({ key, inputType: 'event' })\n const handleFormStateOnClick =\n <K extends keyof T>(key: K, value: T[K]) =>\n () => {\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n\n return {\n formState,\n setFormState,\n formErrors,\n setFormErrors,\n handleFormStateValue,\n handleFormStateEvent,\n handleFormStateOnClick,\n resetFormState,\n }\n}\n\nexport function useEffectAfterMount(effect: EffectCallback, deps: DependencyList) {\n const isFirstRender = useRef(true)\n\n // No manual \"did deps actually change\" recheck needed here: React's own useEffect already\n // only re-invokes this callback when a dependency's value changed (shallow-compared against\n // the last time *this exact effect* ran) - which is exactly the same comparison a hand-rolled\n // check against a \"previous deps\" ref would be making, just duplicated.\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n return effect()\n }, deps)\n}\n\nexport function useFilePaste({\n acceptedTypes,\n maxSize,\n targetElement,\n enabled = true,\n}: {\n acceptedTypes?: string[]\n maxSize?: number\n targetElement?: HTMLElement | null\n enabled?: boolean\n} = {}): {\n files: File[]\n isLoading: boolean\n error: string | null\n clearFiles: () => void\n} {\n const [files, setFiles] = useState<File[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const clearFiles = useCallback(() => {\n setFiles([])\n setError(null)\n }, [])\n\n const validateFiles = useCallback(\n (pastedFiles: File[]): { valid: true } | { valid: false; error: string } => {\n if (acceptedTypes && acceptedTypes.length > 0) {\n const wildcardAcceptedTypes = acceptedTypes\n .filter((type) => type.endsWith('/*'))\n .map((type) => type.slice(0, -1))\n const normalizedAcceptedTypes = acceptedTypes.map((type) => (type.endsWith('/*') ? type.slice(0, -2) : type))\n\n const invalidFiles = pastedFiles.filter((file) => {\n const fileType = file.type\n return (\n !normalizedAcceptedTypes.includes(fileType) &&\n !wildcardAcceptedTypes.some((wildcardType) => fileType.startsWith(wildcardType))\n )\n })\n if (invalidFiles.length > 0) {\n const invalidFileNames = invalidFiles.map((file) => file.name).join(', ')\n return {\n valid: false,\n error: `Invalid file types: ${invalidFileNames}. Accepted types: ${acceptedTypes.join(', ')}`,\n }\n }\n }\n\n if (maxSize) {\n const oversizedFile = pastedFiles.find((file) => file.size > maxSize)\n if (oversizedFile) {\n const fileSizeMB = (oversizedFile.size / (1024 * 1024)).toFixed(2)\n const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2)\n return {\n valid: false,\n error: `File \"${oversizedFile.name}\" (${fileSizeMB} MB) exceeds the maximum size of ${maxSizeMB} MB`,\n }\n }\n }\n\n return { valid: true }\n },\n [acceptedTypes, maxSize]\n )\n\n const handlePaste = useCallback(\n (event: ClipboardEvent) => {\n const { clipboardData } = event\n if (!clipboardData) return\n\n const hasFiles = clipboardData.files && clipboardData.files.length > 0\n if (!hasFiles) return\n\n event.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const pastedFiles = Array.from(clipboardData.files)\n const validation = validateFiles(pastedFiles)\n\n if (!validation.valid) {\n setError(validation.error)\n setFiles([])\n } else {\n setFiles(pastedFiles)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to process pasted files')\n setFiles([])\n } finally {\n setIsLoading(false)\n }\n },\n [validateFiles]\n )\n\n useEffect(() => {\n if (!enabled) return\n\n const target = targetElement || document\n\n target.addEventListener('paste', handlePaste as EventListener)\n return () => {\n target.removeEventListener('paste', handlePaste as EventListener)\n }\n }, [enabled, handlePaste, targetElement])\n\n return { files, isLoading, error, clearFiles }\n}\n\ntype FileDragDropState = {\n isDragging: boolean\n files: File[] | null\n}\n\ntype FileDragDropOptions = {\n onDrop?: (files: File[]) => void\n onDragOver?: (e: DragEvent<HTMLElement>) => void\n onDragLeave?: (e: DragEvent<HTMLElement>) => void\n acceptedFileTypes?: string[]\n maxFileSize?: number\n multiple?: boolean\n}\n\nexport function useFileDragDrop<T extends HTMLElement = HTMLDivElement>(options: FileDragDropOptions = {}) {\n const { onDrop, onDragOver, onDragLeave, acceptedFileTypes, maxFileSize, multiple = true } = options\n\n const [state, setState] = useState<FileDragDropState>({\n isDragging: false,\n files: null,\n })\n\n const ref = useRef<T | null>(null)\n const dragCounter = useRef(0)\n\n const handleDragOver = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n onDragOver?.(e)\n },\n [onDragOver]\n )\n\n const handleDragEnter = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current += 1\n\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n const hasValidItems = Array.from(e.dataTransfer.items).some((item) => {\n if (item.kind !== 'file') {\n return false\n }\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n return acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return item.type.startsWith(`${category}/`)\n }\n return item.type === type\n })\n }\n\n return true\n })\n\n if (hasValidItems) {\n setState((prevState) => ({\n ...prevState,\n isDragging: true,\n }))\n }\n }\n },\n [acceptedFileTypes]\n )\n\n const handleDragLeave = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current -= 1\n\n if (dragCounter.current === 0) {\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n }\n\n onDragLeave?.(e)\n },\n [onDragLeave]\n )\n\n const handleDrop = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current = 0\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n let validFiles = Array.from(e.dataTransfer.files)\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n validFiles = validFiles.filter((file) =>\n acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return file.type.startsWith(`${category}/`)\n }\n return file.type === type\n })\n )\n }\n\n if (maxFileSize) {\n validFiles = validFiles.filter((file) => file.size <= maxFileSize)\n }\n\n if (!multiple && validFiles.length > 0) {\n validFiles = [validFiles[0]]\n }\n\n if (validFiles.length > 0) {\n setState((prevState) => ({\n ...prevState,\n files: validFiles,\n }))\n\n onDrop?.(validFiles)\n }\n }\n },\n [onDrop, acceptedFileTypes, maxFileSize, multiple]\n )\n\n useEffect(() => {\n const currentRef = ref.current\n if (currentRef) {\n currentRef.addEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.addEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.addEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.addEventListener('drop', handleDrop as unknown as EventListener)\n\n return () => {\n currentRef.removeEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.removeEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.removeEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.removeEventListener('drop', handleDrop as unknown as EventListener)\n }\n }\n return undefined\n }, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop])\n\n const reset = useCallback(() => {\n setState({\n isDragging: false,\n files: null,\n })\n }, [])\n\n return {\n ref,\n isDragging: state.isDragging,\n files: state.files,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["import type { ChangeEvent, DependencyList, DragEvent, EffectCallback } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport function useElementAttributes<T extends HTMLElement, K extends keyof T>(attributeKeys: K[]) {\n const ref = useRef<T | null>(null)\n const [attributeValues, setAttributeValue] = useState<Partial<Pick<T, K>>>({})\n\n const updateAttribute = useCallback(() => {\n if (ref.current) {\n const values = {} as Partial<Pick<T, K>>\n for (const key of attributeKeys) {\n // Object.defineProperty (unlike a plain `values[key] = value` assignment) always creates/\n // overwrites an own property, even if a consumer's T somehow has a key like \"__proto__\" -\n // matches the same defensive pattern used everywhere else in this package that writes a\n // non-literal key onto an object.\n Object.defineProperty(values, key, {\n value: ref.current[key],\n writable: true,\n configurable: true,\n enumerable: true,\n })\n }\n\n setAttributeValue((prevValues) => {\n const hasChanged = attributeKeys.some((key) => prevValues[key] !== values[key])\n return hasChanged ? values : prevValues\n })\n }\n }, [attributeKeys])\n\n useEffect(() => {\n updateAttribute()\n window.addEventListener('resize', updateAttribute)\n\n if (ref.current) {\n const observer = new MutationObserver(updateAttribute)\n observer.observe(ref.current, { attributes: true })\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n observer.disconnect()\n }\n }\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n }\n }, [updateAttribute])\n\n return { ref, attributeValues }\n}\n\ntype InputEventType = ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>\n\nexport function useFormState<T>(initialState: T) {\n const [formState, setFormState] = useState<T>(initialState)\n const [formErrors, setFormErrors] = useState<Partial<Record<keyof T, string[]>> & { non_field_errors?: string[] }>()\n\n function resetFormState() {\n setFormState(initialState)\n }\n\n function handleFormState<K extends keyof T, HandlerInputType>({\n key,\n inputType,\n }: {\n key: K\n inputType: 'event' | 'value'\n }) {\n return (event: HandlerInputType) => {\n let value: T[K]\n if (inputType === 'event') {\n const target = (event as InputEventType).target\n if (target.type === 'checkbox') {\n value = (target as HTMLInputElement).checked as T[K]\n } else if (target.type === 'number' || target.type === 'range') {\n value = (target as HTMLInputElement).valueAsNumber as T[K]\n } else {\n value = target.value as T[K]\n }\n } else {\n value = event as T[K]\n }\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n }\n\n // Stryker disable next-line StringLiteral: equivalent mutant. handleFormState only ever\n // compares inputType against 'event' - any other string, including this one, takes the exact\n // same branch, so the specific label 'value' carries no behavior of its own.\n const handleFormStateValue = <K extends keyof T>(key: K) => handleFormState<K, T[K]>({ key, inputType: 'value' })\n const handleFormStateEvent = <K extends keyof T>(key: K) =>\n handleFormState<K, InputEventType>({ key, inputType: 'event' })\n const handleFormStateOnClick =\n <K extends keyof T>(key: K, value: T[K]) =>\n () => {\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n\n return {\n formState,\n setFormState,\n formErrors,\n setFormErrors,\n handleFormStateValue,\n handleFormStateEvent,\n handleFormStateOnClick,\n resetFormState,\n }\n}\n\nexport function useEffectAfterMount(effect: EffectCallback, deps: DependencyList) {\n const isFirstRender = useRef(true)\n\n // No manual \"did deps actually change\" recheck needed here: React's own useEffect already\n // only re-invokes this callback when a dependency's value changed (shallow-compared against\n // the last time *this exact effect* ran) - which is exactly the same comparison a hand-rolled\n // check against a \"previous deps\" ref would be making, just duplicated.\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n return effect()\n }, deps)\n}\n\nexport function useFilePaste({\n acceptedTypes,\n maxSize,\n targetElement,\n enabled = true,\n}: {\n acceptedTypes?: string[]\n maxSize?: number\n targetElement?: HTMLElement | null\n enabled?: boolean\n} = {}): {\n files: File[]\n isLoading: boolean\n error: string | null\n clearFiles: () => void\n} {\n const [files, setFiles] = useState<File[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // The callback body below closes over nothing but the stable setFiles/setError setters, so\n // its dependency array's contents can never affect whether useCallback returns the same\n // function - a literal added there would compare equal (Object.is) to itself every render.\n const clearFiles = useCallback(\n () => {\n setFiles([])\n setError(null)\n }, // Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.\n []\n )\n\n const validateFiles = useCallback(\n (pastedFiles: File[]): { valid: true } | { valid: false; error: string } => {\n if (acceptedTypes && acceptedTypes.length > 0) {\n const wildcardAcceptedTypes = acceptedTypes\n .filter((type) => type.endsWith('/*'))\n .map((type) => type.slice(0, -1))\n const normalizedAcceptedTypes = acceptedTypes.map((type) => (type.endsWith('/*') ? type.slice(0, -2) : type))\n\n const invalidFiles = pastedFiles.filter((file) => {\n const fileType = file.type\n return (\n !normalizedAcceptedTypes.includes(fileType) &&\n !wildcardAcceptedTypes.some((wildcardType) => fileType.startsWith(wildcardType))\n )\n })\n if (invalidFiles.length > 0) {\n const invalidFileNames = invalidFiles.map((file) => file.name).join(', ')\n return {\n valid: false,\n error: `Invalid file types: ${invalidFileNames}. Accepted types: ${acceptedTypes.join(', ')}`,\n }\n }\n }\n\n if (maxSize) {\n const oversizedFile = pastedFiles.find((file) => file.size > maxSize)\n if (oversizedFile) {\n const fileSizeMB = (oversizedFile.size / (1024 * 1024)).toFixed(2)\n const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2)\n return {\n valid: false,\n error: `File \"${oversizedFile.name}\" (${fileSizeMB} MB) exceeds the maximum size of ${maxSizeMB} MB`,\n }\n }\n }\n\n return { valid: true }\n },\n [acceptedTypes, maxSize]\n )\n\n const handlePaste = useCallback(\n (event: ClipboardEvent) => {\n const { clipboardData } = event\n if (!clipboardData) return\n\n const hasFiles = clipboardData.files && clipboardData.files.length > 0\n if (!hasFiles) return\n\n event.preventDefault()\n // Stryker disable next-line BooleanLiteral: equivalent mutant. validateFiles never yields\n // (no await between here and the `finally` below), so this and the `finally`'s\n // setIsLoading(false) land in the same synchronous React commit - an observer can only ever\n // see the final `false`, never a transient `true`, no matter what this call passes.\n setIsLoading(true)\n setError(null)\n\n try {\n const pastedFiles = Array.from(clipboardData.files)\n const validation = validateFiles(pastedFiles)\n\n if (!validation.valid) {\n setError(validation.error)\n setFiles([])\n } else {\n setFiles(pastedFiles)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to process pasted files')\n setFiles([])\n } finally {\n setIsLoading(false)\n }\n },\n [validateFiles]\n )\n\n useEffect(() => {\n if (!enabled) return\n\n const target = targetElement || document\n\n target.addEventListener('paste', handlePaste as EventListener)\n return () => {\n target.removeEventListener('paste', handlePaste as EventListener)\n }\n }, [enabled, handlePaste, targetElement])\n\n return { files, isLoading, error, clearFiles }\n}\n\ntype FileDragDropState = {\n isDragging: boolean\n files: File[] | null\n}\n\ntype FileDragDropOptions = {\n onDrop?: (files: File[]) => void\n onDragOver?: (e: DragEvent<HTMLElement>) => void\n onDragLeave?: (e: DragEvent<HTMLElement>) => void\n acceptedFileTypes?: string[]\n maxFileSize?: number\n multiple?: boolean\n}\n\nexport function useFileDragDrop<T extends HTMLElement = HTMLDivElement>(options: FileDragDropOptions = {}) {\n const { onDrop, onDragOver, onDragLeave, acceptedFileTypes, maxFileSize, multiple = true } = options\n\n const [state, setState] = useState<FileDragDropState>({\n isDragging: false,\n files: null,\n })\n\n const ref = useRef<T | null>(null)\n const dragCounter = useRef(0)\n\n const handleDragOver = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n onDragOver?.(e)\n },\n [onDragOver]\n )\n\n const handleDragEnter = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current += 1\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator: equivalent mutant on\n // the `.length > 0` half specifically. When items is truthy but empty, entering the block\n // anyway still runs Array.from([]).some(...), which is vacuously false - the same no-op as\n // skipping it - so relaxing this to `>= 0` (or dropping it) can't change the outcome.\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n const hasValidItems = Array.from(e.dataTransfer.items).some((item) => {\n if (item.kind !== 'file') {\n return false\n }\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n return acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return item.type.startsWith(`${category}/`)\n }\n return item.type === type\n })\n }\n\n return true\n })\n\n if (hasValidItems) {\n setState((prevState) => ({\n ...prevState,\n isDragging: true,\n }))\n }\n }\n },\n [acceptedFileTypes]\n )\n\n const handleDragLeave = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current -= 1\n\n if (dragCounter.current === 0) {\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n }\n\n onDragLeave?.(e)\n },\n [onDragLeave]\n )\n\n const handleDrop = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current = 0\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator: equivalent mutant on\n // the `.length > 0` half specifically. When files is truthy but empty, entering the block\n // anyway still runs Array.from([]) and every downstream filter/length check on it, which\n // stay vacuously empty - the same no-op as skipping it - so relaxing this to `>= 0` (or\n // dropping it) can't change the outcome.\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n let validFiles = Array.from(e.dataTransfer.files)\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n validFiles = validFiles.filter((file) =>\n acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return file.type.startsWith(`${category}/`)\n }\n return file.type === type\n })\n )\n }\n\n if (maxFileSize) {\n validFiles = validFiles.filter((file) => file.size <= maxFileSize)\n }\n\n if (!multiple && validFiles.length > 0) {\n validFiles = [validFiles[0]]\n }\n\n if (validFiles.length > 0) {\n setState((prevState) => ({\n ...prevState,\n files: validFiles,\n }))\n\n onDrop?.(validFiles)\n }\n }\n },\n [onDrop, acceptedFileTypes, maxFileSize, multiple]\n )\n\n useEffect(() => {\n const currentRef = ref.current\n if (currentRef) {\n currentRef.addEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.addEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.addEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.addEventListener('drop', handleDrop as unknown as EventListener)\n\n return () => {\n currentRef.removeEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.removeEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.removeEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.removeEventListener('drop', handleDrop as unknown as EventListener)\n }\n }\n return undefined\n }, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop])\n\n // The callback body below closes over nothing but the stable setState setter, so its\n // dependency array's contents can never affect whether useCallback returns the same function -\n // a literal added there would compare equal (Object.is) to itself every render.\n const reset = useCallback(\n () => {\n setState({\n isDragging: false,\n files: null,\n })\n }, // Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.\n []\n )\n\n return {\n ref,\n isDragging: state.isDragging,\n files: state.files,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/hooks/index.js
CHANGED
|
@@ -99,10 +99,14 @@ function useFilePaste({
|
|
|
99
99
|
const [files, setFiles] = useState([]);
|
|
100
100
|
const [isLoading, setIsLoading] = useState(false);
|
|
101
101
|
const [error, setError] = useState(null);
|
|
102
|
-
const clearFiles = useCallback(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
const clearFiles = useCallback(
|
|
103
|
+
() => {
|
|
104
|
+
setFiles([]);
|
|
105
|
+
setError(null);
|
|
106
|
+
},
|
|
107
|
+
// Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.
|
|
108
|
+
[]
|
|
109
|
+
);
|
|
106
110
|
const validateFiles = useCallback(
|
|
107
111
|
(pastedFiles) => {
|
|
108
112
|
if (acceptedTypes && acceptedTypes.length > 0) {
|
|
@@ -289,12 +293,16 @@ function useFileDragDrop(options = {}) {
|
|
|
289
293
|
}
|
|
290
294
|
return void 0;
|
|
291
295
|
}, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop]);
|
|
292
|
-
const reset = useCallback(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
const reset = useCallback(
|
|
297
|
+
() => {
|
|
298
|
+
setState({
|
|
299
|
+
isDragging: false,
|
|
300
|
+
files: null
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
// Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.
|
|
304
|
+
[]
|
|
305
|
+
);
|
|
298
306
|
return {
|
|
299
307
|
ref,
|
|
300
308
|
isDragging: state.isDragging,
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["import type { ChangeEvent, DependencyList, DragEvent, EffectCallback } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport function useElementAttributes<T extends HTMLElement, K extends keyof T>(attributeKeys: K[]) {\n const ref = useRef<T | null>(null)\n const [attributeValues, setAttributeValue] = useState<Partial<Pick<T, K>>>({})\n\n const updateAttribute = useCallback(() => {\n if (ref.current) {\n const values = {} as Partial<Pick<T, K>>\n for (const key of attributeKeys) {\n // Object.defineProperty (unlike a plain `values[key] = value` assignment) always creates/\n // overwrites an own property, even if a consumer's T somehow has a key like \"__proto__\" -\n // matches the same defensive pattern used everywhere else in this package that writes a\n // non-literal key onto an object.\n Object.defineProperty(values, key, {\n value: ref.current[key],\n writable: true,\n configurable: true,\n enumerable: true,\n })\n }\n\n setAttributeValue((prevValues) => {\n const hasChanged = attributeKeys.some((key) => prevValues[key] !== values[key])\n return hasChanged ? values : prevValues\n })\n }\n }, [attributeKeys])\n\n useEffect(() => {\n updateAttribute()\n window.addEventListener('resize', updateAttribute)\n\n if (ref.current) {\n const observer = new MutationObserver(updateAttribute)\n observer.observe(ref.current, { attributes: true })\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n observer.disconnect()\n }\n }\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n }\n }, [updateAttribute])\n\n return { ref, attributeValues }\n}\n\ntype InputEventType = ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>\n\nexport function useFormState<T>(initialState: T) {\n const [formState, setFormState] = useState<T>(initialState)\n const [formErrors, setFormErrors] = useState<Partial<Record<keyof T, string[]>> & { non_field_errors?: string[] }>()\n\n function resetFormState() {\n setFormState(initialState)\n }\n\n function handleFormState<K extends keyof T, HandlerInputType>({\n key,\n inputType,\n }: {\n key: K\n inputType: 'event' | 'value'\n }) {\n return (event: HandlerInputType) => {\n let value: T[K]\n if (inputType === 'event') {\n const target = (event as InputEventType).target\n if (target.type === 'checkbox') {\n value = (target as HTMLInputElement).checked as T[K]\n } else if (target.type === 'number' || target.type === 'range') {\n value = (target as HTMLInputElement).valueAsNumber as T[K]\n } else {\n value = target.value as T[K]\n }\n } else {\n value = event as T[K]\n }\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n }\n\n const handleFormStateValue = <K extends keyof T>(key: K) => handleFormState<K, T[K]>({ key, inputType: 'value' })\n const handleFormStateEvent = <K extends keyof T>(key: K) =>\n handleFormState<K, InputEventType>({ key, inputType: 'event' })\n const handleFormStateOnClick =\n <K extends keyof T>(key: K, value: T[K]) =>\n () => {\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n\n return {\n formState,\n setFormState,\n formErrors,\n setFormErrors,\n handleFormStateValue,\n handleFormStateEvent,\n handleFormStateOnClick,\n resetFormState,\n }\n}\n\nexport function useEffectAfterMount(effect: EffectCallback, deps: DependencyList) {\n const isFirstRender = useRef(true)\n\n // No manual \"did deps actually change\" recheck needed here: React's own useEffect already\n // only re-invokes this callback when a dependency's value changed (shallow-compared against\n // the last time *this exact effect* ran) - which is exactly the same comparison a hand-rolled\n // check against a \"previous deps\" ref would be making, just duplicated.\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n return effect()\n }, deps)\n}\n\nexport function useFilePaste({\n acceptedTypes,\n maxSize,\n targetElement,\n enabled = true,\n}: {\n acceptedTypes?: string[]\n maxSize?: number\n targetElement?: HTMLElement | null\n enabled?: boolean\n} = {}): {\n files: File[]\n isLoading: boolean\n error: string | null\n clearFiles: () => void\n} {\n const [files, setFiles] = useState<File[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const clearFiles = useCallback(() => {\n setFiles([])\n setError(null)\n }, [])\n\n const validateFiles = useCallback(\n (pastedFiles: File[]): { valid: true } | { valid: false; error: string } => {\n if (acceptedTypes && acceptedTypes.length > 0) {\n const wildcardAcceptedTypes = acceptedTypes\n .filter((type) => type.endsWith('/*'))\n .map((type) => type.slice(0, -1))\n const normalizedAcceptedTypes = acceptedTypes.map((type) => (type.endsWith('/*') ? type.slice(0, -2) : type))\n\n const invalidFiles = pastedFiles.filter((file) => {\n const fileType = file.type\n return (\n !normalizedAcceptedTypes.includes(fileType) &&\n !wildcardAcceptedTypes.some((wildcardType) => fileType.startsWith(wildcardType))\n )\n })\n if (invalidFiles.length > 0) {\n const invalidFileNames = invalidFiles.map((file) => file.name).join(', ')\n return {\n valid: false,\n error: `Invalid file types: ${invalidFileNames}. Accepted types: ${acceptedTypes.join(', ')}`,\n }\n }\n }\n\n if (maxSize) {\n const oversizedFile = pastedFiles.find((file) => file.size > maxSize)\n if (oversizedFile) {\n const fileSizeMB = (oversizedFile.size / (1024 * 1024)).toFixed(2)\n const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2)\n return {\n valid: false,\n error: `File \"${oversizedFile.name}\" (${fileSizeMB} MB) exceeds the maximum size of ${maxSizeMB} MB`,\n }\n }\n }\n\n return { valid: true }\n },\n [acceptedTypes, maxSize]\n )\n\n const handlePaste = useCallback(\n (event: ClipboardEvent) => {\n const { clipboardData } = event\n if (!clipboardData) return\n\n const hasFiles = clipboardData.files && clipboardData.files.length > 0\n if (!hasFiles) return\n\n event.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const pastedFiles = Array.from(clipboardData.files)\n const validation = validateFiles(pastedFiles)\n\n if (!validation.valid) {\n setError(validation.error)\n setFiles([])\n } else {\n setFiles(pastedFiles)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to process pasted files')\n setFiles([])\n } finally {\n setIsLoading(false)\n }\n },\n [validateFiles]\n )\n\n useEffect(() => {\n if (!enabled) return\n\n const target = targetElement || document\n\n target.addEventListener('paste', handlePaste as EventListener)\n return () => {\n target.removeEventListener('paste', handlePaste as EventListener)\n }\n }, [enabled, handlePaste, targetElement])\n\n return { files, isLoading, error, clearFiles }\n}\n\ntype FileDragDropState = {\n isDragging: boolean\n files: File[] | null\n}\n\ntype FileDragDropOptions = {\n onDrop?: (files: File[]) => void\n onDragOver?: (e: DragEvent<HTMLElement>) => void\n onDragLeave?: (e: DragEvent<HTMLElement>) => void\n acceptedFileTypes?: string[]\n maxFileSize?: number\n multiple?: boolean\n}\n\nexport function useFileDragDrop<T extends HTMLElement = HTMLDivElement>(options: FileDragDropOptions = {}) {\n const { onDrop, onDragOver, onDragLeave, acceptedFileTypes, maxFileSize, multiple = true } = options\n\n const [state, setState] = useState<FileDragDropState>({\n isDragging: false,\n files: null,\n })\n\n const ref = useRef<T | null>(null)\n const dragCounter = useRef(0)\n\n const handleDragOver = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n onDragOver?.(e)\n },\n [onDragOver]\n )\n\n const handleDragEnter = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current += 1\n\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n const hasValidItems = Array.from(e.dataTransfer.items).some((item) => {\n if (item.kind !== 'file') {\n return false\n }\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n return acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return item.type.startsWith(`${category}/`)\n }\n return item.type === type\n })\n }\n\n return true\n })\n\n if (hasValidItems) {\n setState((prevState) => ({\n ...prevState,\n isDragging: true,\n }))\n }\n }\n },\n [acceptedFileTypes]\n )\n\n const handleDragLeave = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current -= 1\n\n if (dragCounter.current === 0) {\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n }\n\n onDragLeave?.(e)\n },\n [onDragLeave]\n )\n\n const handleDrop = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current = 0\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n let validFiles = Array.from(e.dataTransfer.files)\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n validFiles = validFiles.filter((file) =>\n acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return file.type.startsWith(`${category}/`)\n }\n return file.type === type\n })\n )\n }\n\n if (maxFileSize) {\n validFiles = validFiles.filter((file) => file.size <= maxFileSize)\n }\n\n if (!multiple && validFiles.length > 0) {\n validFiles = [validFiles[0]]\n }\n\n if (validFiles.length > 0) {\n setState((prevState) => ({\n ...prevState,\n files: validFiles,\n }))\n\n onDrop?.(validFiles)\n }\n }\n },\n [onDrop, acceptedFileTypes, maxFileSize, multiple]\n )\n\n useEffect(() => {\n const currentRef = ref.current\n if (currentRef) {\n currentRef.addEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.addEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.addEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.addEventListener('drop', handleDrop as unknown as EventListener)\n\n return () => {\n currentRef.removeEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.removeEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.removeEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.removeEventListener('drop', handleDrop as unknown as EventListener)\n }\n }\n return undefined\n }, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop])\n\n const reset = useCallback(() => {\n setState({\n isDragging: false,\n files: null,\n })\n }, [])\n\n return {\n ref,\n isDragging: state.isDragging,\n files: state.files,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["import type { ChangeEvent, DependencyList, DragEvent, EffectCallback } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport function useElementAttributes<T extends HTMLElement, K extends keyof T>(attributeKeys: K[]) {\n const ref = useRef<T | null>(null)\n const [attributeValues, setAttributeValue] = useState<Partial<Pick<T, K>>>({})\n\n const updateAttribute = useCallback(() => {\n if (ref.current) {\n const values = {} as Partial<Pick<T, K>>\n for (const key of attributeKeys) {\n // Object.defineProperty (unlike a plain `values[key] = value` assignment) always creates/\n // overwrites an own property, even if a consumer's T somehow has a key like \"__proto__\" -\n // matches the same defensive pattern used everywhere else in this package that writes a\n // non-literal key onto an object.\n Object.defineProperty(values, key, {\n value: ref.current[key],\n writable: true,\n configurable: true,\n enumerable: true,\n })\n }\n\n setAttributeValue((prevValues) => {\n const hasChanged = attributeKeys.some((key) => prevValues[key] !== values[key])\n return hasChanged ? values : prevValues\n })\n }\n }, [attributeKeys])\n\n useEffect(() => {\n updateAttribute()\n window.addEventListener('resize', updateAttribute)\n\n if (ref.current) {\n const observer = new MutationObserver(updateAttribute)\n observer.observe(ref.current, { attributes: true })\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n observer.disconnect()\n }\n }\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n }\n }, [updateAttribute])\n\n return { ref, attributeValues }\n}\n\ntype InputEventType = ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>\n\nexport function useFormState<T>(initialState: T) {\n const [formState, setFormState] = useState<T>(initialState)\n const [formErrors, setFormErrors] = useState<Partial<Record<keyof T, string[]>> & { non_field_errors?: string[] }>()\n\n function resetFormState() {\n setFormState(initialState)\n }\n\n function handleFormState<K extends keyof T, HandlerInputType>({\n key,\n inputType,\n }: {\n key: K\n inputType: 'event' | 'value'\n }) {\n return (event: HandlerInputType) => {\n let value: T[K]\n if (inputType === 'event') {\n const target = (event as InputEventType).target\n if (target.type === 'checkbox') {\n value = (target as HTMLInputElement).checked as T[K]\n } else if (target.type === 'number' || target.type === 'range') {\n value = (target as HTMLInputElement).valueAsNumber as T[K]\n } else {\n value = target.value as T[K]\n }\n } else {\n value = event as T[K]\n }\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n }\n\n // Stryker disable next-line StringLiteral: equivalent mutant. handleFormState only ever\n // compares inputType against 'event' - any other string, including this one, takes the exact\n // same branch, so the specific label 'value' carries no behavior of its own.\n const handleFormStateValue = <K extends keyof T>(key: K) => handleFormState<K, T[K]>({ key, inputType: 'value' })\n const handleFormStateEvent = <K extends keyof T>(key: K) =>\n handleFormState<K, InputEventType>({ key, inputType: 'event' })\n const handleFormStateOnClick =\n <K extends keyof T>(key: K, value: T[K]) =>\n () => {\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n\n return {\n formState,\n setFormState,\n formErrors,\n setFormErrors,\n handleFormStateValue,\n handleFormStateEvent,\n handleFormStateOnClick,\n resetFormState,\n }\n}\n\nexport function useEffectAfterMount(effect: EffectCallback, deps: DependencyList) {\n const isFirstRender = useRef(true)\n\n // No manual \"did deps actually change\" recheck needed here: React's own useEffect already\n // only re-invokes this callback when a dependency's value changed (shallow-compared against\n // the last time *this exact effect* ran) - which is exactly the same comparison a hand-rolled\n // check against a \"previous deps\" ref would be making, just duplicated.\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n return effect()\n }, deps)\n}\n\nexport function useFilePaste({\n acceptedTypes,\n maxSize,\n targetElement,\n enabled = true,\n}: {\n acceptedTypes?: string[]\n maxSize?: number\n targetElement?: HTMLElement | null\n enabled?: boolean\n} = {}): {\n files: File[]\n isLoading: boolean\n error: string | null\n clearFiles: () => void\n} {\n const [files, setFiles] = useState<File[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // The callback body below closes over nothing but the stable setFiles/setError setters, so\n // its dependency array's contents can never affect whether useCallback returns the same\n // function - a literal added there would compare equal (Object.is) to itself every render.\n const clearFiles = useCallback(\n () => {\n setFiles([])\n setError(null)\n }, // Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.\n []\n )\n\n const validateFiles = useCallback(\n (pastedFiles: File[]): { valid: true } | { valid: false; error: string } => {\n if (acceptedTypes && acceptedTypes.length > 0) {\n const wildcardAcceptedTypes = acceptedTypes\n .filter((type) => type.endsWith('/*'))\n .map((type) => type.slice(0, -1))\n const normalizedAcceptedTypes = acceptedTypes.map((type) => (type.endsWith('/*') ? type.slice(0, -2) : type))\n\n const invalidFiles = pastedFiles.filter((file) => {\n const fileType = file.type\n return (\n !normalizedAcceptedTypes.includes(fileType) &&\n !wildcardAcceptedTypes.some((wildcardType) => fileType.startsWith(wildcardType))\n )\n })\n if (invalidFiles.length > 0) {\n const invalidFileNames = invalidFiles.map((file) => file.name).join(', ')\n return {\n valid: false,\n error: `Invalid file types: ${invalidFileNames}. Accepted types: ${acceptedTypes.join(', ')}`,\n }\n }\n }\n\n if (maxSize) {\n const oversizedFile = pastedFiles.find((file) => file.size > maxSize)\n if (oversizedFile) {\n const fileSizeMB = (oversizedFile.size / (1024 * 1024)).toFixed(2)\n const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2)\n return {\n valid: false,\n error: `File \"${oversizedFile.name}\" (${fileSizeMB} MB) exceeds the maximum size of ${maxSizeMB} MB`,\n }\n }\n }\n\n return { valid: true }\n },\n [acceptedTypes, maxSize]\n )\n\n const handlePaste = useCallback(\n (event: ClipboardEvent) => {\n const { clipboardData } = event\n if (!clipboardData) return\n\n const hasFiles = clipboardData.files && clipboardData.files.length > 0\n if (!hasFiles) return\n\n event.preventDefault()\n // Stryker disable next-line BooleanLiteral: equivalent mutant. validateFiles never yields\n // (no await between here and the `finally` below), so this and the `finally`'s\n // setIsLoading(false) land in the same synchronous React commit - an observer can only ever\n // see the final `false`, never a transient `true`, no matter what this call passes.\n setIsLoading(true)\n setError(null)\n\n try {\n const pastedFiles = Array.from(clipboardData.files)\n const validation = validateFiles(pastedFiles)\n\n if (!validation.valid) {\n setError(validation.error)\n setFiles([])\n } else {\n setFiles(pastedFiles)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to process pasted files')\n setFiles([])\n } finally {\n setIsLoading(false)\n }\n },\n [validateFiles]\n )\n\n useEffect(() => {\n if (!enabled) return\n\n const target = targetElement || document\n\n target.addEventListener('paste', handlePaste as EventListener)\n return () => {\n target.removeEventListener('paste', handlePaste as EventListener)\n }\n }, [enabled, handlePaste, targetElement])\n\n return { files, isLoading, error, clearFiles }\n}\n\ntype FileDragDropState = {\n isDragging: boolean\n files: File[] | null\n}\n\ntype FileDragDropOptions = {\n onDrop?: (files: File[]) => void\n onDragOver?: (e: DragEvent<HTMLElement>) => void\n onDragLeave?: (e: DragEvent<HTMLElement>) => void\n acceptedFileTypes?: string[]\n maxFileSize?: number\n multiple?: boolean\n}\n\nexport function useFileDragDrop<T extends HTMLElement = HTMLDivElement>(options: FileDragDropOptions = {}) {\n const { onDrop, onDragOver, onDragLeave, acceptedFileTypes, maxFileSize, multiple = true } = options\n\n const [state, setState] = useState<FileDragDropState>({\n isDragging: false,\n files: null,\n })\n\n const ref = useRef<T | null>(null)\n const dragCounter = useRef(0)\n\n const handleDragOver = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n onDragOver?.(e)\n },\n [onDragOver]\n )\n\n const handleDragEnter = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current += 1\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator: equivalent mutant on\n // the `.length > 0` half specifically. When items is truthy but empty, entering the block\n // anyway still runs Array.from([]).some(...), which is vacuously false - the same no-op as\n // skipping it - so relaxing this to `>= 0` (or dropping it) can't change the outcome.\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n const hasValidItems = Array.from(e.dataTransfer.items).some((item) => {\n if (item.kind !== 'file') {\n return false\n }\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n return acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return item.type.startsWith(`${category}/`)\n }\n return item.type === type\n })\n }\n\n return true\n })\n\n if (hasValidItems) {\n setState((prevState) => ({\n ...prevState,\n isDragging: true,\n }))\n }\n }\n },\n [acceptedFileTypes]\n )\n\n const handleDragLeave = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current -= 1\n\n if (dragCounter.current === 0) {\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n }\n\n onDragLeave?.(e)\n },\n [onDragLeave]\n )\n\n const handleDrop = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current = 0\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator: equivalent mutant on\n // the `.length > 0` half specifically. When files is truthy but empty, entering the block\n // anyway still runs Array.from([]) and every downstream filter/length check on it, which\n // stay vacuously empty - the same no-op as skipping it - so relaxing this to `>= 0` (or\n // dropping it) can't change the outcome.\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n let validFiles = Array.from(e.dataTransfer.files)\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n validFiles = validFiles.filter((file) =>\n acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return file.type.startsWith(`${category}/`)\n }\n return file.type === type\n })\n )\n }\n\n if (maxFileSize) {\n validFiles = validFiles.filter((file) => file.size <= maxFileSize)\n }\n\n if (!multiple && validFiles.length > 0) {\n validFiles = [validFiles[0]]\n }\n\n if (validFiles.length > 0) {\n setState((prevState) => ({\n ...prevState,\n files: validFiles,\n }))\n\n onDrop?.(validFiles)\n }\n }\n },\n [onDrop, acceptedFileTypes, maxFileSize, multiple]\n )\n\n useEffect(() => {\n const currentRef = ref.current\n if (currentRef) {\n currentRef.addEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.addEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.addEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.addEventListener('drop', handleDrop as unknown as EventListener)\n\n return () => {\n currentRef.removeEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.removeEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.removeEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.removeEventListener('drop', handleDrop as unknown as EventListener)\n }\n }\n return undefined\n }, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop])\n\n // The callback body below closes over nothing but the stable setState setter, so its\n // dependency array's contents can never affect whether useCallback returns the same function -\n // a literal added there would compare equal (Object.is) to itself every render.\n const reset = useCallback(\n () => {\n setState({\n isDragging: false,\n files: null,\n })\n }, // Stryker disable next-line ArrayDeclaration: equivalent mutant, see above.\n []\n )\n\n return {\n ref,\n isDragging: state.isDragging,\n files: state.files,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -230,7 +230,11 @@ function slugify(value, allowUnicode = false) {
|
|
|
230
230
|
if (allowUnicode) {
|
|
231
231
|
value = value.normalize("NFKC").replace(/[^\p{L}\p{N}_\s-]/gu, "");
|
|
232
232
|
} else {
|
|
233
|
-
value = value.normalize("NFKD")
|
|
233
|
+
value = value.normalize("NFKD");
|
|
234
|
+
value = value.replace(/[^\x00-\x7F]/g, "");
|
|
235
|
+
value = value.replace(/[\r\n]+/g, " ");
|
|
236
|
+
value = value.trim();
|
|
237
|
+
value = value.replace(/[^\w\s-]/g, "");
|
|
234
238
|
}
|
|
235
239
|
value = value.toLowerCase();
|
|
236
240
|
return value.replace(/[-\s]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
@@ -267,6 +271,8 @@ function guessImageMimeType(filename) {
|
|
|
267
271
|
const withoutQueryOrHash = filename.split(/[?#]/)[0];
|
|
268
272
|
const extension = withoutQueryOrHash.split(".").pop()?.toLowerCase();
|
|
269
273
|
switch (extension) {
|
|
274
|
+
// Stryker disable next-line StringLiteral: equivalent mutant. This case and `default` below
|
|
275
|
+
// both return 'image/png', so no input can distinguish which of the two branches ran it.
|
|
270
276
|
case "png":
|
|
271
277
|
return "image/png";
|
|
272
278
|
case "jfif":
|