@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.
@@ -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":[]}
@@ -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
- setFiles([]);
132
- setError(null);
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
- setState({
322
- isDragging: false,
323
- files: null
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,
@@ -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":[]}
@@ -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
- setFiles([]);
104
- setError(null);
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
- setState({
294
- isDragging: false,
295
- files: null
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,
@@ -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").replace(/[^\x00-\x7F]/g, "").replace(/[\r\n]+/g, " ").trim().replace(/[^\w\s-]/g, "");
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":