@operato/utils 10.29.23 → 10.29.37
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/CHANGELOG.md +18 -0
- package/coverage/lcov-report/index.html +124 -19
- package/coverage/lcov.info +19165 -0
- package/dist/src/file-acceptance.d.ts +55 -0
- package/dist/src/file-acceptance.js +91 -0
- package/dist/src/file-acceptance.js.map +1 -0
- package/dist/src/file-drop-helper.d.ts +2 -1
- package/dist/src/file-drop-helper.js +14 -11
- package/dist/src/file-drop-helper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/test/file-acceptance.test.d.ts +1 -0
- package/dist/test/file-acceptance.test.js +93 -0
- package/dist/test/file-acceptance.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/test/file-acceptance.test.ts +112 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a file picker may take, decided once for every picker.
|
|
3
|
+
*
|
|
4
|
+
* The server owns the size and count limits (things-factory shell `fileUpload.maxFileSize` /
|
|
5
|
+
* `maxFiles`). Before this, a picker learned of them only after the whole file had gone up, as a
|
|
6
|
+
* GraphQL error sentence. The shell registers a reader for those numbers once
|
|
7
|
+
* (`registerFileUploadLimits`); a picker asks `fileUploadLimits()` and refuses on the spot.
|
|
8
|
+
* No picker writes a number of its own.
|
|
9
|
+
*/
|
|
10
|
+
export type FileUploadLimits = {
|
|
11
|
+
/** bytes */
|
|
12
|
+
maxFileSize?: number;
|
|
13
|
+
maxFiles?: number;
|
|
14
|
+
};
|
|
15
|
+
/** What the rule reads of a file — a browser File, or its description (name, type, size). */
|
|
16
|
+
export type PickedFile = {
|
|
17
|
+
name?: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
size?: number;
|
|
20
|
+
};
|
|
21
|
+
export type FileRefusal<T extends PickedFile = File> = {
|
|
22
|
+
file: T;
|
|
23
|
+
reason: 'type' | 'size' | 'count';
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The application tells where the limits come from — read once, on the first pick.
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerFileUploadLimits(limits: () => Promise<FileUploadLimits>): void;
|
|
29
|
+
/**
|
|
30
|
+
* The server's limits; empty when nothing is registered or the read failed (the server still refuses).
|
|
31
|
+
* A failed read is not kept, so the next pick asks again.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fileUploadLimits(): Promise<FileUploadLimits>;
|
|
34
|
+
/**
|
|
35
|
+
* The tighter of several limits — a screen's own (an AI mode's policy, handed down by the server) never loosens the
|
|
36
|
+
* server's upload limit.
|
|
37
|
+
*/
|
|
38
|
+
export declare function narrowLimits(...all: (FileUploadLimits | undefined)[]): FileUploadLimits;
|
|
39
|
+
/**
|
|
40
|
+
* The `accept` attribute's own rule: `.ext`, `type/*` or `type/subtype`, comma separated.
|
|
41
|
+
* A drop never passes through the native filter, so the picker applies it itself.
|
|
42
|
+
*/
|
|
43
|
+
export declare function acceptsFile(file: PickedFile, accept?: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Splits the picked files into the ones taken and the ones refused, in the order picked.
|
|
46
|
+
* `already` is how many the picker holds before these, so the count limit covers both.
|
|
47
|
+
*/
|
|
48
|
+
export declare function sortPickedFiles<T extends PickedFile = File>(files: T[], { accept, limits, already }: {
|
|
49
|
+
accept?: string;
|
|
50
|
+
limits?: FileUploadLimits;
|
|
51
|
+
already?: number;
|
|
52
|
+
}): {
|
|
53
|
+
taken: T[];
|
|
54
|
+
refused: FileRefusal<T>[];
|
|
55
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a file picker may take, decided once for every picker.
|
|
3
|
+
*
|
|
4
|
+
* The server owns the size and count limits (things-factory shell `fileUpload.maxFileSize` /
|
|
5
|
+
* `maxFiles`). Before this, a picker learned of them only after the whole file had gone up, as a
|
|
6
|
+
* GraphQL error sentence. The shell registers a reader for those numbers once
|
|
7
|
+
* (`registerFileUploadLimits`); a picker asks `fileUploadLimits()` and refuses on the spot.
|
|
8
|
+
* No picker writes a number of its own.
|
|
9
|
+
*/
|
|
10
|
+
let reader;
|
|
11
|
+
let read;
|
|
12
|
+
/**
|
|
13
|
+
* The application tells where the limits come from — read once, on the first pick.
|
|
14
|
+
*/
|
|
15
|
+
export function registerFileUploadLimits(limits) {
|
|
16
|
+
reader = limits;
|
|
17
|
+
read = undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The server's limits; empty when nothing is registered or the read failed (the server still refuses).
|
|
21
|
+
* A failed read is not kept, so the next pick asks again.
|
|
22
|
+
*/
|
|
23
|
+
export function fileUploadLimits() {
|
|
24
|
+
if (!reader)
|
|
25
|
+
return Promise.resolve({});
|
|
26
|
+
return (read !== null && read !== void 0 ? read : (read = reader().catch(() => {
|
|
27
|
+
read = undefined;
|
|
28
|
+
return {};
|
|
29
|
+
})));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The tighter of several limits — a screen's own (an AI mode's policy, handed down by the server) never loosens the
|
|
33
|
+
* server's upload limit.
|
|
34
|
+
*/
|
|
35
|
+
export function narrowLimits(...all) {
|
|
36
|
+
const least = (values) => {
|
|
37
|
+
const set = values.filter((one) => typeof one === 'number' && one > 0);
|
|
38
|
+
return set.length ? Math.min(...set) : undefined;
|
|
39
|
+
};
|
|
40
|
+
const maxFileSize = least(all.map(one => one === null || one === void 0 ? void 0 : one.maxFileSize));
|
|
41
|
+
const maxFiles = least(all.map(one => one === null || one === void 0 ? void 0 : one.maxFiles));
|
|
42
|
+
return { ...(maxFileSize ? { maxFileSize } : {}), ...(maxFiles ? { maxFiles } : {}) };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The `accept` attribute's own rule: `.ext`, `type/*` or `type/subtype`, comma separated.
|
|
46
|
+
* A drop never passes through the native filter, so the picker applies it itself.
|
|
47
|
+
*/
|
|
48
|
+
export function acceptsFile(file, accept) {
|
|
49
|
+
const patterns = (accept || '')
|
|
50
|
+
.split(',')
|
|
51
|
+
.map(one => one.trim().toLowerCase())
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
if (!patterns.length)
|
|
54
|
+
return true;
|
|
55
|
+
const name = (file.name || '').toLowerCase();
|
|
56
|
+
const type = (file.type || '').toLowerCase();
|
|
57
|
+
return patterns.some(pattern => {
|
|
58
|
+
if (pattern === '*/*' || pattern === '*')
|
|
59
|
+
return true;
|
|
60
|
+
if (pattern.startsWith('.'))
|
|
61
|
+
return name.endsWith(pattern);
|
|
62
|
+
if (pattern.endsWith('/*'))
|
|
63
|
+
return type.startsWith(pattern.slice(0, -1));
|
|
64
|
+
return type === pattern;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Splits the picked files into the ones taken and the ones refused, in the order picked.
|
|
69
|
+
* `already` is how many the picker holds before these, so the count limit covers both.
|
|
70
|
+
*/
|
|
71
|
+
export function sortPickedFiles(files, { accept, limits = {}, already = 0 }) {
|
|
72
|
+
var _a;
|
|
73
|
+
const taken = [];
|
|
74
|
+
const refused = [];
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
if (!acceptsFile(file, accept)) {
|
|
77
|
+
refused.push({ file, reason: 'type' });
|
|
78
|
+
}
|
|
79
|
+
else if (limits.maxFileSize && ((_a = file.size) !== null && _a !== void 0 ? _a : 0) > limits.maxFileSize) {
|
|
80
|
+
refused.push({ file, reason: 'size' });
|
|
81
|
+
}
|
|
82
|
+
else if (limits.maxFiles && already + taken.length >= limits.maxFiles) {
|
|
83
|
+
refused.push({ file, reason: 'count' });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
taken.push(file);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { taken, refused };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=file-acceptance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-acceptance.js","sourceRoot":"","sources":["../../src/file-acceptance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,IAAI,MAAqD,CAAA;AACzD,IAAI,IAA2C,CAAA;AAE/C;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAuC;IAC9E,MAAM,GAAG,MAAM,CAAA;IACf,IAAI,GAAG,SAAS,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAEvC,OAAO,CAAC,IAAI,aAAJ,IAAI,cAAJ,IAAI,IAAJ,IAAI,GAAK,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QACnC,IAAI,GAAG,SAAS,CAAA;QAChB,OAAO,EAAE,CAAA;IACX,CAAC,CAAC,EAAC,CAAA;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAG,GAAqC;IACnE,MAAM,KAAK,GAAG,CAAC,MAA8B,EAAE,EAAE;QAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,GAAG,CAAC,CAAC,CAAA;QACrF,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAClD,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,WAAW,CAAC,CAAC,CAAA;IAC3D,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,QAAQ,CAAC,CAAC,CAAA;IAErD,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACvF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAgB,EAAE,MAAe;IAC3D,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;SAC5B,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;SACpC,MAAM,CAAC,OAAO,CAAC,CAAA;IAElB,IAAI,CAAC,QAAQ,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAEjC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAC5C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAE5C,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QAC7B,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QACrD,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAC1D,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACxE,OAAO,IAAI,KAAK,OAAO,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAU,EACV,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE,EAAE,OAAO,GAAG,CAAC,EAAoE;;IAEtG,MAAM,KAAK,GAAQ,EAAE,CAAA;IACrB,MAAM,OAAO,GAAqB,EAAE,CAAA;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;QACxC,CAAC;aAAM,IAAI,MAAM,CAAC,WAAW,IAAI,CAAC,MAAA,IAAI,CAAC,IAAI,mCAAI,CAAC,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;YACvE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;QACxC,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,IAAI,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACxE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;QACzC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AAC3B,CAAC","sourcesContent":["/**\n * What a file picker may take, decided once for every picker.\n *\n * The server owns the size and count limits (things-factory shell `fileUpload.maxFileSize` /\n * `maxFiles`). Before this, a picker learned of them only after the whole file had gone up, as a\n * GraphQL error sentence. The shell registers a reader for those numbers once\n * (`registerFileUploadLimits`); a picker asks `fileUploadLimits()` and refuses on the spot.\n * No picker writes a number of its own.\n */\n\nexport type FileUploadLimits = {\n /** bytes */\n maxFileSize?: number\n maxFiles?: number\n}\n\n/** What the rule reads of a file — a browser File, or its description (name, type, size). */\nexport type PickedFile = { name?: string; type?: string; size?: number }\n\nexport type FileRefusal<T extends PickedFile = File> = {\n file: T\n reason: 'type' | 'size' | 'count'\n}\n\nlet reader: (() => Promise<FileUploadLimits>) | undefined\nlet read: Promise<FileUploadLimits> | undefined\n\n/**\n * The application tells where the limits come from — read once, on the first pick.\n */\nexport function registerFileUploadLimits(limits: () => Promise<FileUploadLimits>) {\n reader = limits\n read = undefined\n}\n\n/**\n * The server's limits; empty when nothing is registered or the read failed (the server still refuses).\n * A failed read is not kept, so the next pick asks again.\n */\nexport function fileUploadLimits(): Promise<FileUploadLimits> {\n if (!reader) return Promise.resolve({})\n\n return (read ??= reader().catch(() => {\n read = undefined\n return {}\n }))\n}\n\n/**\n * The tighter of several limits — a screen's own (an AI mode's policy, handed down by the server) never loosens the\n * server's upload limit.\n */\nexport function narrowLimits(...all: (FileUploadLimits | undefined)[]): FileUploadLimits {\n const least = (values: (number | undefined)[]) => {\n const set = values.filter((one): one is number => typeof one === 'number' && one > 0)\n return set.length ? Math.min(...set) : undefined\n }\n\n const maxFileSize = least(all.map(one => one?.maxFileSize))\n const maxFiles = least(all.map(one => one?.maxFiles))\n\n return { ...(maxFileSize ? { maxFileSize } : {}), ...(maxFiles ? { maxFiles } : {}) }\n}\n\n/**\n * The `accept` attribute's own rule: `.ext`, `type/*` or `type/subtype`, comma separated.\n * A drop never passes through the native filter, so the picker applies it itself.\n */\nexport function acceptsFile(file: PickedFile, accept?: string): boolean {\n const patterns = (accept || '')\n .split(',')\n .map(one => one.trim().toLowerCase())\n .filter(Boolean)\n\n if (!patterns.length) return true\n\n const name = (file.name || '').toLowerCase()\n const type = (file.type || '').toLowerCase()\n\n return patterns.some(pattern => {\n if (pattern === '*/*' || pattern === '*') return true\n if (pattern.startsWith('.')) return name.endsWith(pattern)\n if (pattern.endsWith('/*')) return type.startsWith(pattern.slice(0, -1))\n return type === pattern\n })\n}\n\n/**\n * Splits the picked files into the ones taken and the ones refused, in the order picked.\n * `already` is how many the picker holds before these, so the count limit covers both.\n */\nexport function sortPickedFiles<T extends PickedFile = File>(\n files: T[],\n { accept, limits = {}, already = 0 }: { accept?: string; limits?: FileUploadLimits; already?: number }\n): { taken: T[]; refused: FileRefusal<T>[] } {\n const taken: T[] = []\n const refused: FileRefusal<T>[] = []\n\n for (const file of files) {\n if (!acceptsFile(file, accept)) {\n refused.push({ file, reason: 'type' })\n } else if (limits.maxFileSize && (file.size ?? 0) > limits.maxFileSize) {\n refused.push({ file, reason: 'size' })\n } else if (limits.maxFiles && already + taken.length >= limits.maxFiles) {\n refused.push({ file, reason: 'count' })\n } else {\n taken.push(file)\n }\n }\n\n return { taken, refused }\n}\n"]}
|
|
@@ -10,7 +10,8 @@ export declare class FileDropHelper {
|
|
|
10
10
|
* 파일 드롭 영역에서의 file drag&drop과 관련된 이벤트에 대한 처리를 설정한다.
|
|
11
11
|
* @param dropArea file drag&drop target area
|
|
12
12
|
* @param [judge] file drag&drop과 관련된 이벤트에 대한 judge callback function
|
|
13
|
+
* @returns 설정한 이벤트를 모두 걷어 내는 함수 — 드롭 영역을 옮기거나 떼어 낼 때 부른다
|
|
13
14
|
*/
|
|
14
|
-
static set(dropArea: HTMLElement, judge?: JudgeCallback): void;
|
|
15
|
+
static set(dropArea: HTMLElement, judge?: JudgeCallback): () => void;
|
|
15
16
|
}
|
|
16
17
|
export {};
|
|
@@ -6,6 +6,7 @@ export class FileDropHelper {
|
|
|
6
6
|
* 파일 드롭 영역에서의 file drag&drop과 관련된 이벤트에 대한 처리를 설정한다.
|
|
7
7
|
* @param dropArea file drag&drop target area
|
|
8
8
|
* @param [judge] file drag&drop과 관련된 이벤트에 대한 judge callback function
|
|
9
|
+
* @returns 설정한 이벤트를 모두 걷어 내는 함수 — 드롭 영역을 옮기거나 떼어 낼 때 부른다
|
|
9
10
|
*/
|
|
10
11
|
static set(dropArea, judge) {
|
|
11
12
|
var preventDefaults = (e) => {
|
|
@@ -24,16 +25,7 @@ export class FileDropHelper {
|
|
|
24
25
|
dropArea.classList.remove('candrop');
|
|
25
26
|
}
|
|
26
27
|
};
|
|
27
|
-
|
|
28
|
-
dropArea.addEventListener(event, preventDefaults, false);
|
|
29
|
-
});
|
|
30
|
-
['dragenter', 'dragover'].forEach(event => {
|
|
31
|
-
dropArea.addEventListener(event, highlight, false);
|
|
32
|
-
});
|
|
33
|
-
['dragleave', 'drop'].forEach(event => {
|
|
34
|
-
dropArea.addEventListener(event, unhighlight, false);
|
|
35
|
-
});
|
|
36
|
-
dropArea.addEventListener('drop', e => {
|
|
28
|
+
var drop = (e) => {
|
|
37
29
|
if (!judge || judge()) {
|
|
38
30
|
let dt = e.dataTransfer;
|
|
39
31
|
let files = dt.files;
|
|
@@ -41,7 +33,18 @@ export class FileDropHelper {
|
|
|
41
33
|
detail: [...Array.from(files)]
|
|
42
34
|
}));
|
|
43
35
|
}
|
|
44
|
-
}
|
|
36
|
+
};
|
|
37
|
+
const listeners = [
|
|
38
|
+
...['dragenter', 'dragover', 'dragleave', 'drop'].map(event => [event, preventDefaults]),
|
|
39
|
+
...['dragenter', 'dragover'].map(event => [event, highlight]),
|
|
40
|
+
...['dragleave', 'drop'].map(event => [event, unhighlight]),
|
|
41
|
+
['drop', drop]
|
|
42
|
+
];
|
|
43
|
+
listeners.forEach(([event, listener]) => dropArea.addEventListener(event, listener, false));
|
|
44
|
+
return () => {
|
|
45
|
+
listeners.forEach(([event, listener]) => dropArea.removeEventListener(event, listener, false));
|
|
46
|
+
dropArea.classList.remove('candrop');
|
|
47
|
+
};
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
//# sourceMappingURL=file-drop-helper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-drop-helper.js","sourceRoot":"","sources":["../../src/file-drop-helper.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,OAAO,cAAc;IACzB
|
|
1
|
+
{"version":3,"file":"file-drop-helper.js","sourceRoot":"","sources":["../../src/file-drop-helper.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,OAAO,cAAc;IACzB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,QAAqB,EAAE,KAAqB;QACrD,IAAI,eAAe,GAAG,CAAC,CAAQ,EAAE,EAAE;YACjC,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,EAAE,CAAC;gBACtB,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,CAAC,CAAC,eAAe,EAAE,CAAA;YACrB,CAAC;QACH,CAAC,CAAA;QAED,IAAI,SAAS,GAAG,CAAC,CAAQ,EAAE,EAAE;YAC3B,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,EAAE,CAAC;gBACtB,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YACnC,CAAC;QACH,CAAC,CAAA;QAED,IAAI,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;YAC7B,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,EAAE,CAAC;gBACtB,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACtC,CAAC;QACH,CAAC,CAAA;QAED,IAAI,IAAI,GAAG,CAAC,CAAY,EAAE,EAAE;YAC1B,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,EAAE,CAAC;gBACtB,IAAI,EAAE,GAAG,CAAC,CAAC,YAAa,CAAA;gBACxB,IAAI,KAAK,GAAG,EAAE,CAAC,KAAK,CAAA;gBAEpB,QAAQ,CAAC,aAAa,CACpB,IAAI,WAAW,CAAC,WAAW,EAAE;oBAC3B,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;iBAC/B,CAAC,CACH,CAAA;YACH,CAAC;QACH,CAAC,CAAA;QAED,MAAM,SAAS,GAA8B;YAC3C,GAAG,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,eAAe,CAA4B,CAAC;YACnH,GAAG,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,CAA4B,CAAC;YACxF,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,CAA4B,CAAC;YACtF,CAAC,MAAM,EAAE,IAAqB,CAAC;SAChC,CAAA;QAED,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAA;QAE3F,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAA;YAC9F,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QACtC,CAAC,CAAA;IACH,CAAC;CACF","sourcesContent":["/**\n * judge callback function for FileDropHelper.set\n */\ntype JudgeCallback = () => boolean\n\n/**\n * file drop assist class\n */\nexport class FileDropHelper {\n /**\n * 파일 드롭 영역에서의 file drag&drop과 관련된 이벤트에 대한 처리를 설정한다.\n * @param dropArea file drag&drop target area\n * @param [judge] file drag&drop과 관련된 이벤트에 대한 judge callback function\n * @returns 설정한 이벤트를 모두 걷어 내는 함수 — 드롭 영역을 옮기거나 떼어 낼 때 부른다\n */\n static set(dropArea: HTMLElement, judge?: JudgeCallback): () => void {\n var preventDefaults = (e: Event) => {\n if (!judge || judge()) {\n e.preventDefault()\n e.stopPropagation()\n }\n }\n\n var highlight = (e: Event) => {\n if (!judge || judge()) {\n dropArea.classList.add('candrop')\n }\n }\n\n var unhighlight = (e: Event) => {\n if (!judge || judge()) {\n dropArea.classList.remove('candrop')\n }\n }\n\n var drop = (e: DragEvent) => {\n if (!judge || judge()) {\n let dt = e.dataTransfer!\n let files = dt.files\n\n dropArea.dispatchEvent(\n new CustomEvent('file-drop', {\n detail: [...Array.from(files)]\n })\n )\n }\n }\n\n const listeners: [string, EventListener][] = [\n ...['dragenter', 'dragover', 'dragleave', 'drop'].map(event => [event, preventDefaults] as [string, EventListener]),\n ...['dragenter', 'dragover'].map(event => [event, highlight] as [string, EventListener]),\n ...['dragleave', 'drop'].map(event => [event, unhighlight] as [string, EventListener]),\n ['drop', drop as EventListener]\n ]\n\n listeners.forEach(([event, listener]) => dropArea.addEventListener(event, listener, false))\n\n return () => {\n listeners.forEach(([event, listener]) => dropArea.removeEventListener(event, listener, false))\n dropArea.classList.remove('candrop')\n }\n }\n}\n"]}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA,CAAC,kCAAkC;AACvE,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AAEvC,cAAc,iCAAiC,CAAA","sourcesContent":["export * from './sleep.js'\nexport * from './async-lock.js'\nexport * from './file-drop-helper.js'\nexport * from './context-path.js'\nexport * from './os.js'\nexport * from './swipe-listener.js'\nexport * from './fullscreen.js'\nexport * from './parse-jwt.js'\nexport * from './password-pattern.js'\nexport * from './closest-element.js'\nexport * from './detect-overflow.js' /* deprecated by 'has-overflow' */\nexport * from './has-overflow.js'\nexport * from './timecapsule/index.js'\nexport * from './clipboard.js'\nexport * from './format.js'\nexport * from './adjust-list-param.js'\nexport * from './is-unvalued.js'\nexport * from './stringify-bignum.js'\nexport * from './encode-form-params.js'\nexport * from './cookie.js'\nexport * from './number-parser.js'\nexport * from './longpressable.js'\nexport * from './decode-html.js'\nexport * from './script-loader.js'\nexport * from './google-maps-loader.js'\n\nexport * from './reactive-controllers/index.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA,CAAC,kCAAkC;AACvE,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AAEvC,cAAc,iCAAiC,CAAA","sourcesContent":["export * from './sleep.js'\nexport * from './async-lock.js'\nexport * from './file-drop-helper.js'\nexport * from './file-acceptance.js'\nexport * from './context-path.js'\nexport * from './os.js'\nexport * from './swipe-listener.js'\nexport * from './fullscreen.js'\nexport * from './parse-jwt.js'\nexport * from './password-pattern.js'\nexport * from './closest-element.js'\nexport * from './detect-overflow.js' /* deprecated by 'has-overflow' */\nexport * from './has-overflow.js'\nexport * from './timecapsule/index.js'\nexport * from './clipboard.js'\nexport * from './format.js'\nexport * from './adjust-list-param.js'\nexport * from './is-unvalued.js'\nexport * from './stringify-bignum.js'\nexport * from './encode-form-params.js'\nexport * from './cookie.js'\nexport * from './number-parser.js'\nexport * from './longpressable.js'\nexport * from './decode-html.js'\nexport * from './script-loader.js'\nexport * from './google-maps-loader.js'\n\nexport * from './reactive-controllers/index.js'\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { acceptsFile, fileUploadLimits, narrowLimits, registerFileUploadLimits, sortPickedFiles } from '../src/file-acceptance.js';
|
|
3
|
+
import { FileDropHelper } from '../src/file-drop-helper.js';
|
|
4
|
+
/**
|
|
5
|
+
* What a picker takes and refuses.
|
|
6
|
+
*
|
|
7
|
+
* ## Why this is tested
|
|
8
|
+
*
|
|
9
|
+
* The server refuses a file over `fileUpload.maxFileSize` only after the whole file went up, and a
|
|
10
|
+
* drop never passes the native `accept` filter. Every picker refuses here first, so these rules are
|
|
11
|
+
* the one place a wrong answer would let a file through or turn a good one away.
|
|
12
|
+
*/
|
|
13
|
+
describe('file acceptance', () => {
|
|
14
|
+
const file = (name, type, size = 10) => new File([new Uint8Array(size)], name, { type });
|
|
15
|
+
it('takes everything with no accept', () => {
|
|
16
|
+
expect(acceptsFile(file('a.bin', ''), '')).to.equal(true);
|
|
17
|
+
expect(acceptsFile(file('a.bin', ''), undefined)).to.equal(true);
|
|
18
|
+
});
|
|
19
|
+
it('reads extensions, type families and exact types', () => {
|
|
20
|
+
const accept = 'image/*, application/pdf, .docx';
|
|
21
|
+
expect(acceptsFile(file('scan.png', 'image/png'), accept)).to.equal(true);
|
|
22
|
+
expect(acceptsFile(file('paper.pdf', 'application/pdf'), accept)).to.equal(true);
|
|
23
|
+
expect(acceptsFile(file('Articles.DOCX', ''), accept), 'extension case').to.equal(true);
|
|
24
|
+
expect(acceptsFile(file('sheet.xlsx', 'application/vnd.ms-excel'), accept)).to.equal(false);
|
|
25
|
+
expect(acceptsFile(file('clip.mp4', 'video/mp4'), accept)).to.equal(false);
|
|
26
|
+
});
|
|
27
|
+
it('refuses by type, size and count, keeping the picked order', () => {
|
|
28
|
+
const files = [
|
|
29
|
+
file('a.png', 'image/png', 5),
|
|
30
|
+
file('b.txt', 'text/plain', 5),
|
|
31
|
+
file('c.png', 'image/png', 50),
|
|
32
|
+
file('d.png', 'image/png', 5),
|
|
33
|
+
file('e.png', 'image/png', 5)
|
|
34
|
+
];
|
|
35
|
+
const { taken, refused } = sortPickedFiles(files, {
|
|
36
|
+
accept: 'image/*',
|
|
37
|
+
limits: { maxFileSize: 20, maxFiles: 3 },
|
|
38
|
+
already: 1
|
|
39
|
+
});
|
|
40
|
+
expect(taken.map(one => one.name)).to.deep.equal(['a.png', 'd.png']);
|
|
41
|
+
expect(refused.map(one => `${one.file.name}:${one.reason}`)).to.deep.equal(['b.txt:type', 'c.png:size', 'e.png:count']);
|
|
42
|
+
});
|
|
43
|
+
it('takes the tighter of a screen limit and the server limit, never a looser one', () => {
|
|
44
|
+
expect(narrowLimits({ maxFileSize: 5, maxFiles: 10 }, { maxFileSize: 10, maxFiles: 3 })).to.deep.equal({ maxFileSize: 5, maxFiles: 3 });
|
|
45
|
+
expect(narrowLimits({ maxFileSize: 50 }, { maxFileSize: 10 }), 'a screen loosened the server').to.deep.equal({ maxFileSize: 10 });
|
|
46
|
+
expect(narrowLimits(undefined, {})).to.deep.equal({});
|
|
47
|
+
});
|
|
48
|
+
it('reads a file description as well as a file', () => {
|
|
49
|
+
const { taken, refused } = sortPickedFiles([{ name: 'a.png', type: 'image/png', size: 5 }, { name: 'b.png', type: 'image/png', size: 50 }], {
|
|
50
|
+
accept: 'image/*',
|
|
51
|
+
limits: { maxFileSize: 20 }
|
|
52
|
+
});
|
|
53
|
+
expect(taken.map(one => one.name)).to.deep.equal(['a.png']);
|
|
54
|
+
expect(refused.map(one => one.reason)).to.deep.equal(['size']);
|
|
55
|
+
});
|
|
56
|
+
it('reads the registered limits once and asks again after a failure', async () => {
|
|
57
|
+
let calls = 0;
|
|
58
|
+
registerFileUploadLimits(async () => {
|
|
59
|
+
calls++;
|
|
60
|
+
if (calls === 1)
|
|
61
|
+
throw new Error('offline');
|
|
62
|
+
return { maxFileSize: 100 };
|
|
63
|
+
});
|
|
64
|
+
expect(await fileUploadLimits(), 'a failed read is empty').to.deep.equal({});
|
|
65
|
+
expect(await fileUploadLimits()).to.deep.equal({ maxFileSize: 100 });
|
|
66
|
+
expect(await fileUploadLimits()).to.deep.equal({ maxFileSize: 100 });
|
|
67
|
+
expect(calls).to.equal(2);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('file drop area', () => {
|
|
71
|
+
it('stops taking drops once undone', () => {
|
|
72
|
+
const area = document.createElement('div');
|
|
73
|
+
document.body.appendChild(area);
|
|
74
|
+
let drops = 0;
|
|
75
|
+
area.addEventListener('file-drop', () => drops++);
|
|
76
|
+
const undo = FileDropHelper.set(area);
|
|
77
|
+
const drop = () => {
|
|
78
|
+
const dataTransfer = new DataTransfer();
|
|
79
|
+
dataTransfer.items.add(new File(['x'], 'x.txt'));
|
|
80
|
+
area.dispatchEvent(new DragEvent('drop', { dataTransfer, bubbles: true, cancelable: true }));
|
|
81
|
+
};
|
|
82
|
+
drop();
|
|
83
|
+
expect(drops).to.equal(1);
|
|
84
|
+
area.dispatchEvent(new DragEvent('dragenter', { bubbles: true }));
|
|
85
|
+
expect(area.classList.contains('candrop')).to.equal(true);
|
|
86
|
+
undo();
|
|
87
|
+
expect(area.classList.contains('candrop'), 'highlight left behind').to.equal(false);
|
|
88
|
+
drop();
|
|
89
|
+
expect(drops, 'a drop after undo still landed').to.equal(1);
|
|
90
|
+
area.remove();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=file-acceptance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-acceptance.test.js","sourceRoot":"","sources":["../../test/file-acceptance.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { acceptsFile, fileUploadLimits, narrowLimits, registerFileUploadLimits, sortPickedFiles } from '../src/file-acceptance.js'\nimport { FileDropHelper } from '../src/file-drop-helper.js'\n\n/**\n * What a picker takes and refuses.\n *\n * ## Why this is tested\n *\n * The server refuses a file over `fileUpload.maxFileSize` only after the whole file went up, and a\n * drop never passes the native `accept` filter. Every picker refuses here first, so these rules are\n * the one place a wrong answer would let a file through or turn a good one away.\n */\ndescribe('file acceptance', () => {\n const file = (name: string, type: string, size = 10) => new File([new Uint8Array(size)], name, { type })\n\n it('takes everything with no accept', () => {\n expect(acceptsFile(file('a.bin', ''), '')).to.equal(true)\n expect(acceptsFile(file('a.bin', ''), undefined)).to.equal(true)\n })\n\n it('reads extensions, type families and exact types', () => {\n const accept = 'image/*, application/pdf, .docx'\n\n expect(acceptsFile(file('scan.png', 'image/png'), accept)).to.equal(true)\n expect(acceptsFile(file('paper.pdf', 'application/pdf'), accept)).to.equal(true)\n expect(acceptsFile(file('Articles.DOCX', ''), accept), 'extension case').to.equal(true)\n expect(acceptsFile(file('sheet.xlsx', 'application/vnd.ms-excel'), accept)).to.equal(false)\n expect(acceptsFile(file('clip.mp4', 'video/mp4'), accept)).to.equal(false)\n })\n\n it('refuses by type, size and count, keeping the picked order', () => {\n const files = [\n file('a.png', 'image/png', 5),\n file('b.txt', 'text/plain', 5),\n file('c.png', 'image/png', 50),\n file('d.png', 'image/png', 5),\n file('e.png', 'image/png', 5)\n ]\n\n const { taken, refused } = sortPickedFiles(files, {\n accept: 'image/*',\n limits: { maxFileSize: 20, maxFiles: 3 },\n already: 1\n })\n\n expect(taken.map(one => one.name)).to.deep.equal(['a.png', 'd.png'])\n expect(refused.map(one => `${one.file.name}:${one.reason}`)).to.deep.equal(['b.txt:type', 'c.png:size', 'e.png:count'])\n })\n\n it('takes the tighter of a screen limit and the server limit, never a looser one', () => {\n expect(narrowLimits({ maxFileSize: 5, maxFiles: 10 }, { maxFileSize: 10, maxFiles: 3 })).to.deep.equal({ maxFileSize: 5, maxFiles: 3 })\n expect(narrowLimits({ maxFileSize: 50 }, { maxFileSize: 10 }), 'a screen loosened the server').to.deep.equal({ maxFileSize: 10 })\n expect(narrowLimits(undefined, {})).to.deep.equal({})\n })\n\n it('reads a file description as well as a file', () => {\n const { taken, refused } = sortPickedFiles([{ name: 'a.png', type: 'image/png', size: 5 }, { name: 'b.png', type: 'image/png', size: 50 }], {\n accept: 'image/*',\n limits: { maxFileSize: 20 }\n })\n expect(taken.map(one => one.name)).to.deep.equal(['a.png'])\n expect(refused.map(one => one.reason)).to.deep.equal(['size'])\n })\n\n it('reads the registered limits once and asks again after a failure', async () => {\n let calls = 0\n registerFileUploadLimits(async () => {\n calls++\n if (calls === 1) throw new Error('offline')\n return { maxFileSize: 100 }\n })\n\n expect(await fileUploadLimits(), 'a failed read is empty').to.deep.equal({})\n expect(await fileUploadLimits()).to.deep.equal({ maxFileSize: 100 })\n expect(await fileUploadLimits()).to.deep.equal({ maxFileSize: 100 })\n expect(calls).to.equal(2)\n })\n})\n\ndescribe('file drop area', () => {\n it('stops taking drops once undone', () => {\n const area = document.createElement('div')\n document.body.appendChild(area)\n\n let drops = 0\n area.addEventListener('file-drop', () => drops++)\n\n const undo = FileDropHelper.set(area)\n\n const drop = () => {\n const dataTransfer = new DataTransfer()\n dataTransfer.items.add(new File(['x'], 'x.txt'))\n area.dispatchEvent(new DragEvent('drop', { dataTransfer, bubbles: true, cancelable: true }))\n }\n\n drop()\n expect(drops).to.equal(1)\n\n area.dispatchEvent(new DragEvent('dragenter', { bubbles: true }))\n expect(area.classList.contains('candrop')).to.equal(true)\n\n undo()\n expect(area.classList.contains('candrop'), 'highlight left behind').to.equal(false)\n\n drop()\n expect(drops, 'a drop after undo still landed').to.equal(1)\n\n area.remove()\n })\n})\n"]}
|