@operato/utils 10.29.23 → 10.29.36

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,44 @@
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
+ export type FileRefusal = {
16
+ file: File;
17
+ reason: 'type' | 'size' | 'count';
18
+ };
19
+ /**
20
+ * The application tells where the limits come from — read once, on the first pick.
21
+ */
22
+ export declare function registerFileUploadLimits(limits: () => Promise<FileUploadLimits>): void;
23
+ /**
24
+ * The server's limits; empty when nothing is registered or the read failed (the server still refuses).
25
+ * A failed read is not kept, so the next pick asks again.
26
+ */
27
+ export declare function fileUploadLimits(): Promise<FileUploadLimits>;
28
+ /**
29
+ * The `accept` attribute's own rule: `.ext`, `type/*` or `type/subtype`, comma separated.
30
+ * A drop never passes through the native filter, so the picker applies it itself.
31
+ */
32
+ export declare function acceptsFile(file: File, accept?: string): boolean;
33
+ /**
34
+ * Splits the picked files into the ones taken and the ones refused, in the order picked.
35
+ * `already` is how many the picker holds before these, so the count limit covers both.
36
+ */
37
+ export declare function sortPickedFiles(files: File[], { accept, limits, already }: {
38
+ accept?: string;
39
+ limits?: FileUploadLimits;
40
+ already?: number;
41
+ }): {
42
+ taken: File[];
43
+ refused: FileRefusal[];
44
+ };
@@ -0,0 +1,77 @@
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 `accept` attribute's own rule: `.ext`, `type/*` or `type/subtype`, comma separated.
33
+ * A drop never passes through the native filter, so the picker applies it itself.
34
+ */
35
+ export function acceptsFile(file, accept) {
36
+ const patterns = (accept || '')
37
+ .split(',')
38
+ .map(one => one.trim().toLowerCase())
39
+ .filter(Boolean);
40
+ if (!patterns.length)
41
+ return true;
42
+ const name = file.name.toLowerCase();
43
+ const type = (file.type || '').toLowerCase();
44
+ return patterns.some(pattern => {
45
+ if (pattern === '*/*' || pattern === '*')
46
+ return true;
47
+ if (pattern.startsWith('.'))
48
+ return name.endsWith(pattern);
49
+ if (pattern.endsWith('/*'))
50
+ return type.startsWith(pattern.slice(0, -1));
51
+ return type === pattern;
52
+ });
53
+ }
54
+ /**
55
+ * Splits the picked files into the ones taken and the ones refused, in the order picked.
56
+ * `already` is how many the picker holds before these, so the count limit covers both.
57
+ */
58
+ export function sortPickedFiles(files, { accept, limits = {}, already = 0 }) {
59
+ const taken = [];
60
+ const refused = [];
61
+ for (const file of files) {
62
+ if (!acceptsFile(file, accept)) {
63
+ refused.push({ file, reason: 'type' });
64
+ }
65
+ else if (limits.maxFileSize && file.size > limits.maxFileSize) {
66
+ refused.push({ file, reason: 'size' });
67
+ }
68
+ else if (limits.maxFiles && already + taken.length >= limits.maxFiles) {
69
+ refused.push({ file, reason: 'count' });
70
+ }
71
+ else {
72
+ taken.push(file);
73
+ }
74
+ }
75
+ return { taken, refused };
76
+ }
77
+ //# sourceMappingURL=file-acceptance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-acceptance.js","sourceRoot":"","sources":["../../src/file-acceptance.ts"],"names":[],"mappings":"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","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\nexport type FileRefusal = {\n file: File\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 `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: File, 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(\n files: File[],\n { accept, limits = {}, already = 0 }: { accept?: string; limits?: FileUploadLimits; already?: number }\n): { taken: File[]; refused: FileRefusal[] } {\n const taken: File[] = []\n const refused: FileRefusal[] = []\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 > 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
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
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
- }, false);
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;;;;OAIG;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,CAEA;QAAA,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YAC9D,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,CAAA;QAC1D,CAAC,CAAC,CACD;QAAA,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACzC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;QACpD,CAAC,CAAC,CACD;QAAA,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACrC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,CAAA;QACtD,CAAC,CAAC,CAAA;QAEF,QAAQ,CAAC,gBAAgB,CACvB,MAAM,EACN,CAAC,CAAC,EAAE;YACF,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,EACD,KAAK,CACN,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 */\n static set(dropArea: HTMLElement, judge?: JudgeCallback) {\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 ;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {\n dropArea.addEventListener(event, preventDefaults, false)\n })\n ;['dragenter', 'dragover'].forEach(event => {\n dropArea.addEventListener(event, highlight, false)\n })\n ;['dragleave', 'drop'].forEach(event => {\n dropArea.addEventListener(event, unhighlight, false)\n })\n\n dropArea.addEventListener(\n 'drop',\n e => {\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 false\n )\n }\n}\n"]}
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"]}
@@ -1,6 +1,7 @@
1
1
  export * from './sleep.js';
2
2
  export * from './async-lock.js';
3
3
  export * from './file-drop-helper.js';
4
+ export * from './file-acceptance.js';
4
5
  export * from './context-path.js';
5
6
  export * from './os.js';
6
7
  export * from './swipe-listener.js';
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './sleep.js';
2
2
  export * from './async-lock.js';
3
3
  export * from './file-drop-helper.js';
4
+ export * from './file-acceptance.js';
4
5
  export * from './context-path.js';
5
6
  export * from './os.js';
6
7
  export * from './swipe-listener.js';
@@ -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,80 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { acceptsFile, fileUploadLimits, 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('reads the registered limits once and asks again after a failure', async () => {
44
+ let calls = 0;
45
+ registerFileUploadLimits(async () => {
46
+ calls++;
47
+ if (calls === 1)
48
+ throw new Error('offline');
49
+ return { maxFileSize: 100 };
50
+ });
51
+ expect(await fileUploadLimits(), 'a failed read is empty').to.deep.equal({});
52
+ expect(await fileUploadLimits()).to.deep.equal({ maxFileSize: 100 });
53
+ expect(await fileUploadLimits()).to.deep.equal({ maxFileSize: 100 });
54
+ expect(calls).to.equal(2);
55
+ });
56
+ });
57
+ describe('file drop area', () => {
58
+ it('stops taking drops once undone', () => {
59
+ const area = document.createElement('div');
60
+ document.body.appendChild(area);
61
+ let drops = 0;
62
+ area.addEventListener('file-drop', () => drops++);
63
+ const undo = FileDropHelper.set(area);
64
+ const drop = () => {
65
+ const dataTransfer = new DataTransfer();
66
+ dataTransfer.items.add(new File(['x'], 'x.txt'));
67
+ area.dispatchEvent(new DragEvent('drop', { dataTransfer, bubbles: true, cancelable: true }));
68
+ };
69
+ drop();
70
+ expect(drops).to.equal(1);
71
+ area.dispatchEvent(new DragEvent('dragenter', { bubbles: true }));
72
+ expect(area.classList.contains('candrop')).to.equal(true);
73
+ undo();
74
+ expect(area.classList.contains('candrop'), 'highlight left behind').to.equal(false);
75
+ drop();
76
+ expect(drops, 'a drop after undo still landed').to.equal(1);
77
+ area.remove();
78
+ });
79
+ });
80
+ //# 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, 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('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"]}