ember-primitives 0.63.1 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/declarations/components/file-drop/files.d.ts +31 -0
- package/declarations/components/file-drop/files.d.ts.map +1 -0
- package/declarations/components/file-drop.d.ts +122 -0
- package/declarations/components/file-drop.d.ts.map +1 -0
- package/declarations/index.d.ts +1 -0
- package/declarations/index.d.ts.map +1 -1
- package/dist/components/file-drop/files.js +89 -0
- package/dist/components/file-drop/files.js.map +1 -0
- package/dist/components/file-drop.js +128 -0
- package/dist/components/file-drop.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/file-drop/files.ts +109 -0
- package/src/components/file-drop.gts +211 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matches a file against an `accept` list, as `<input type="file">` reads it.
|
|
3
|
+
*
|
|
4
|
+
* The file picker filters by `accept` itself, but a drop does not,
|
|
5
|
+
* so dropped files need this check.
|
|
6
|
+
*/
|
|
7
|
+
export declare function accepts(file: File, accept: string | undefined): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* A drag can carry text or a link instead of files.
|
|
10
|
+
* During the drag, only the types are readable, not the files.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hasFiles(event: DragEvent): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The files of a pick or a drop that match `accept`, and only the first one
|
|
15
|
+
* unless `multiple` is set.
|
|
16
|
+
*/
|
|
17
|
+
export declare function filesFrom(list: FileList | null | undefined, accept: string | undefined, multiple: boolean | undefined): File[];
|
|
18
|
+
/**
|
|
19
|
+
* Whether files are dragged over an element.
|
|
20
|
+
*
|
|
21
|
+
* `dragenter` and `dragleave` fire for every child that the pointer crosses,
|
|
22
|
+
* so a single flag would flicker. The depth counts the enters that did not leave yet.
|
|
23
|
+
*/
|
|
24
|
+
export declare class DragState {
|
|
25
|
+
#private;
|
|
26
|
+
isDragging: boolean;
|
|
27
|
+
enter(event: DragEvent): void;
|
|
28
|
+
leave(event: DragEvent): void;
|
|
29
|
+
end(): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/components/file-drop/files.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYvE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAUlD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACjC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,GAC5B,IAAI,EAAE,CAcR;AAED;;;;;GAKG;AACH,qBAAa,SAAS;;IACX,UAAU,UAAS;IAI5B,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAO7B,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAQ7B,GAAG,IAAI,IAAI;CAIZ"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import "./visually-hidden.css";
|
|
2
|
+
import Component from "@glimmer/component";
|
|
3
|
+
import { DragState } from "./file-drop/files.ts";
|
|
4
|
+
import type Owner from "@ember/owner";
|
|
5
|
+
interface FileArgs {
|
|
6
|
+
/**
|
|
7
|
+
* Called with the files that match `@accept`.
|
|
8
|
+
* It is not called when no file matches.
|
|
9
|
+
*/
|
|
10
|
+
onFiles: (files: File[]) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Which files to take, in the format of the `accept` attribute
|
|
13
|
+
* of `<input type="file">`, for example `"video/*,.mkv"`.
|
|
14
|
+
*
|
|
15
|
+
* The file picker shows only these files.
|
|
16
|
+
* A drop of other files does nothing.
|
|
17
|
+
*/
|
|
18
|
+
accept?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Take more than one file. Without it, only the first file that matches.
|
|
21
|
+
*/
|
|
22
|
+
multiple?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface Signature {
|
|
25
|
+
/**
|
|
26
|
+
* A `<label>` for a hidden `<input type="file">`,
|
|
27
|
+
* so a click or a key press on the zone opens the file picker.
|
|
28
|
+
*
|
|
29
|
+
* It has `data-dragging` while files are dragged over it.
|
|
30
|
+
*/
|
|
31
|
+
Element: HTMLLabelElement;
|
|
32
|
+
Args: FileArgs;
|
|
33
|
+
Blocks: {
|
|
34
|
+
default: [
|
|
35
|
+
{
|
|
36
|
+
/**
|
|
37
|
+
* Files are dragged over the zone.
|
|
38
|
+
*/
|
|
39
|
+
isDragging: boolean;
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A zone to drop files on, or to click to pick files.
|
|
46
|
+
*
|
|
47
|
+
* ```gjs
|
|
48
|
+
* import { FileDrop } from 'ember-primitives/components/file-drop';
|
|
49
|
+
*
|
|
50
|
+
* const load = (files) => console.log(files);
|
|
51
|
+
*
|
|
52
|
+
* <template>
|
|
53
|
+
* <FileDrop @accept="video/*" @onFiles={{load}}>
|
|
54
|
+
* Drop a video here, or click to choose one
|
|
55
|
+
* </FileDrop>
|
|
56
|
+
* </template>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class FileDrop extends Component<Signature> {
|
|
60
|
+
drag: DragState;
|
|
61
|
+
inputId: string;
|
|
62
|
+
onDragEnter: (event: DragEvent) => void;
|
|
63
|
+
onDragLeave: (event: DragEvent) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Without this, the browser does not allow the drop.
|
|
66
|
+
*/
|
|
67
|
+
onDragOver: (event: DragEvent) => void;
|
|
68
|
+
/**
|
|
69
|
+
* `preventDefault` also tells a `DocumentFileDrop` that this zone took the drop.
|
|
70
|
+
*/
|
|
71
|
+
onDrop: (event: DragEvent) => void;
|
|
72
|
+
/**
|
|
73
|
+
* The input is cleared after each pick, so that picking the same file again
|
|
74
|
+
* is a change too.
|
|
75
|
+
*/
|
|
76
|
+
onPick: (event: Event) => void;
|
|
77
|
+
take(list: FileList | null | undefined): void;
|
|
78
|
+
}
|
|
79
|
+
export interface DocumentSignature {
|
|
80
|
+
Args: FileArgs;
|
|
81
|
+
Blocks: {
|
|
82
|
+
default: [
|
|
83
|
+
{
|
|
84
|
+
/**
|
|
85
|
+
* Files are dragged over the page.
|
|
86
|
+
*/
|
|
87
|
+
isDragging: boolean;
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Takes files dropped anywhere on the page. It renders no element.
|
|
94
|
+
*
|
|
95
|
+
* - The block gets `isDragging`, for example to show where a drop goes.
|
|
96
|
+
* - A drop outside every `FileDrop` does not open the file in the browser tab.
|
|
97
|
+
* - A drop on a `FileDrop` goes only to that `FileDrop`.
|
|
98
|
+
*
|
|
99
|
+
* ```gjs
|
|
100
|
+
* import { DocumentFileDrop } from 'ember-primitives/components/file-drop';
|
|
101
|
+
*
|
|
102
|
+
* const load = (files) => console.log(files);
|
|
103
|
+
*
|
|
104
|
+
* <template>
|
|
105
|
+
* <DocumentFileDrop @accept="video/*" @onFiles={{load}} as |page|>
|
|
106
|
+
* {{#if page.isDragging}}
|
|
107
|
+
* <div class="overlay">Drop to load</div>
|
|
108
|
+
* {{/if}}
|
|
109
|
+
* </DocumentFileDrop>
|
|
110
|
+
* </template>
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare class DocumentFileDrop extends Component<DocumentSignature> {
|
|
114
|
+
drag: DragState;
|
|
115
|
+
constructor(owner: Owner, args: DocumentSignature["Args"]);
|
|
116
|
+
onDragEnter: (event: DragEvent) => void;
|
|
117
|
+
onDragLeave: (event: DragEvent) => void;
|
|
118
|
+
onDragOver: (event: DragEvent) => void;
|
|
119
|
+
onDrop: (event: DragEvent) => void;
|
|
120
|
+
}
|
|
121
|
+
export {};
|
|
122
|
+
//# sourceMappingURL=file-drop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-drop.d.ts","sourceRoot":"","sources":["../../src/components/file-drop.gts"],"names":[],"mappings":"AAmNA,OAAO,uBAAuB,CAAC;AAE/B,OAAO,SAAS,MAAM,oBAAoB,CAAC;AAM3C,OAAO,EAAE,SAAS,EAAuB,MAAM,sBAAsB,CAAC;AAEtE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,UAAU,QAAQ;IAChB;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAEjC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE;QACN,OAAO,EAAE;YACP;gBACE;;mBAEG;gBACH,UAAU,EAAE,OAAO,CAAC;aACrB;SACF,CAAC;KACH,CAAC;CACH;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,QAAS,SAAQ,SAAS,CAAC,SAAS,CAAC;IAChD,IAAI,YAAmB;IACvB,OAAO,SAAc;IAErB,WAAW,GAAI,OAAO,SAAS,UAA4B;IAC3D,WAAW,GAAI,OAAO,SAAS,UAA4B;IAE3D;;OAEG;IACH,UAAU,GAAI,OAAO,SAAS,UAE5B;IAEF;;OAEG;IACH,MAAM,GAAI,OAAO,SAAS,UAOxB;IAEF;;;OAGG;IACH,MAAM,GAAI,OAAO,KAAK,UAKpB;IAEF,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS;CA4CvC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE;QACN,OAAO,EAAE;YACP;gBACE;;mBAEG;gBACH,UAAU,EAAE,OAAO,CAAC;aACrB;SACF,CAAC;KACH,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,gBAAiB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAChE,IAAI,YAAmB;gBAEX,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC;IAgBzD,WAAW,GAAI,OAAO,SAAS,UAA4B;IAC3D,WAAW,GAAI,OAAO,SAAS,UAA4B;IAE3D,UAAU,GAAI,OAAO,SAAS,UAE5B;IAEF,MAAM,GAAI,OAAO,SAAS,UAUxB;CAQH"}
|
package/declarations/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { CommandPalette } from './components/command-palette';
|
|
|
6
6
|
export { Dialog, Modal } from './components/dialog';
|
|
7
7
|
export { Drawer } from './components/drawer';
|
|
8
8
|
export { ExternalLink } from './components/external-link';
|
|
9
|
+
export { DocumentFileDrop, FileDrop } from './components/file-drop';
|
|
9
10
|
export { Form } from './components/form';
|
|
10
11
|
export { IncrementalEach } from './components/incremental-each';
|
|
11
12
|
export { Key, KeyCombo } from './components/keys';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,YAAY,EACV,iCAAiC,EACjC,gCAAgC,EAChC,8BAA8B,EAC9B,iCAAiC,GAClC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EACL,SAAS,EACT,MAAM,IAAI,eAAe,EACzB,KAAK,IAAI,cAAc,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,YAAY,EACV,iCAAiC,EACjC,gCAAgC,EAChC,8BAA8B,EAC9B,iCAAiC,GAClC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EACL,SAAS,EACT,MAAM,IAAI,eAAe,EACzB,KAAK,IAAI,cAAc,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { tracked } from '@glimmer/tracking';
|
|
2
|
+
import { g, i } from 'decorator-transforms/runtime';
|
|
3
|
+
|
|
4
|
+
function acceptsOne(file, entry) {
|
|
5
|
+
if (entry.startsWith('.')) {
|
|
6
|
+
return file.name.toLowerCase().endsWith(entry.toLowerCase());
|
|
7
|
+
}
|
|
8
|
+
if (entry.endsWith('/*')) {
|
|
9
|
+
return file.type.startsWith(entry.slice(0, -1));
|
|
10
|
+
}
|
|
11
|
+
return file.type === entry;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Matches a file against an `accept` list, as `<input type="file">` reads it.
|
|
16
|
+
*
|
|
17
|
+
* The file picker filters by `accept` itself, but a drop does not,
|
|
18
|
+
* so dropped files need this check.
|
|
19
|
+
*/
|
|
20
|
+
function accepts(file, accept) {
|
|
21
|
+
if (!accept) return true;
|
|
22
|
+
const entries = accept.split(',');
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const trimmed = entry.trim();
|
|
25
|
+
if (trimmed && acceptsOne(file, trimmed)) return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A drag can carry text or a link instead of files.
|
|
32
|
+
* During the drag, only the types are readable, not the files.
|
|
33
|
+
*/
|
|
34
|
+
function hasFiles(event) {
|
|
35
|
+
const types = event.dataTransfer?.types;
|
|
36
|
+
if (!types) return false;
|
|
37
|
+
for (const type of types) {
|
|
38
|
+
if (type === 'Files') return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The files of a pick or a drop that match `accept`, and only the first one
|
|
45
|
+
* unless `multiple` is set.
|
|
46
|
+
*/
|
|
47
|
+
function filesFrom(list, accept, multiple) {
|
|
48
|
+
const files = [];
|
|
49
|
+
if (!list) return files;
|
|
50
|
+
for (const file of list) {
|
|
51
|
+
if (!accepts(file, accept)) continue;
|
|
52
|
+
files.push(file);
|
|
53
|
+
if (!multiple) break;
|
|
54
|
+
}
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether files are dragged over an element.
|
|
60
|
+
*
|
|
61
|
+
* `dragenter` and `dragleave` fire for every child that the pointer crosses,
|
|
62
|
+
* so a single flag would flicker. The depth counts the enters that did not leave yet.
|
|
63
|
+
*/
|
|
64
|
+
class DragState {
|
|
65
|
+
static {
|
|
66
|
+
g(this.prototype, "isDragging", [tracked], function () {
|
|
67
|
+
return false;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
#isDragging = (i(this, "isDragging"), void 0);
|
|
71
|
+
#depth = 0;
|
|
72
|
+
enter(event) {
|
|
73
|
+
if (!hasFiles(event)) return;
|
|
74
|
+
this.#depth++;
|
|
75
|
+
this.isDragging = true;
|
|
76
|
+
}
|
|
77
|
+
leave(event) {
|
|
78
|
+
if (!hasFiles(event)) return;
|
|
79
|
+
this.#depth = Math.max(0, this.#depth - 1);
|
|
80
|
+
if (this.#depth === 0) this.isDragging = false;
|
|
81
|
+
}
|
|
82
|
+
end() {
|
|
83
|
+
this.#depth = 0;
|
|
84
|
+
this.isDragging = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { DragState, accepts, filesFrom, hasFiles };
|
|
89
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sources":["../../../src/components/file-drop/files.ts"],"sourcesContent":["import { tracked } from '@glimmer/tracking';\n\n/**\n * One entry of an `accept` list: an extension (`.mp4`),\n * a type with a wildcard (`video/*`), or an exact type (`video/webm`).\n */\nfunction acceptsOne(file: File, entry: string): boolean {\n if (entry.startsWith('.')) {\n return file.name.toLowerCase().endsWith(entry.toLowerCase());\n }\n\n if (entry.endsWith('/*')) {\n return file.type.startsWith(entry.slice(0, -1));\n }\n\n return file.type === entry;\n}\n\n/**\n * Matches a file against an `accept` list, as `<input type=\"file\">` reads it.\n *\n * The file picker filters by `accept` itself, but a drop does not,\n * so dropped files need this check.\n */\nexport function accepts(file: File, accept: string | undefined): boolean {\n if (!accept) return true;\n\n const entries = accept.split(',');\n\n for (const entry of entries) {\n const trimmed = entry.trim();\n\n if (trimmed && acceptsOne(file, trimmed)) return true;\n }\n\n return false;\n}\n\n/**\n * A drag can carry text or a link instead of files.\n * During the drag, only the types are readable, not the files.\n */\nexport function hasFiles(event: DragEvent): boolean {\n const types = event.dataTransfer?.types;\n\n if (!types) return false;\n\n for (const type of types) {\n if (type === 'Files') return true;\n }\n\n return false;\n}\n\n/**\n * The files of a pick or a drop that match `accept`, and only the first one\n * unless `multiple` is set.\n */\nexport function filesFrom(\n list: FileList | null | undefined,\n accept: string | undefined,\n multiple: boolean | undefined\n): File[] {\n const files: File[] = [];\n\n if (!list) return files;\n\n for (const file of list) {\n if (!accepts(file, accept)) continue;\n\n files.push(file);\n\n if (!multiple) break;\n }\n\n return files;\n}\n\n/**\n * Whether files are dragged over an element.\n *\n * `dragenter` and `dragleave` fire for every child that the pointer crosses,\n * so a single flag would flicker. The depth counts the enters that did not leave yet.\n */\nexport class DragState {\n @tracked isDragging = false;\n\n #depth = 0;\n\n enter(event: DragEvent): void {\n if (!hasFiles(event)) return;\n\n this.#depth++;\n this.isDragging = true;\n }\n\n leave(event: DragEvent): void {\n if (!hasFiles(event)) return;\n\n this.#depth = Math.max(0, this.#depth - 1);\n\n if (this.#depth === 0) this.isDragging = false;\n }\n\n end(): void {\n this.#depth = 0;\n this.isDragging = false;\n }\n}\n"],"names":["acceptsOne","file","entry","startsWith","name","toLowerCase","endsWith","type","slice","accepts","accept","entries","split","trimmed","trim","hasFiles","event","types","dataTransfer","filesFrom","list","multiple","files","push","DragState","g","prototype","tracked","i","enter","isDragging","leave","Math","max","end"],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import '../visually-hidden-CGP1FSjt.js';
|
|
2
|
+
import Component from '@glimmer/component';
|
|
3
|
+
import { registerDestructor } from '@ember/destroyable';
|
|
4
|
+
import { hash } from '@ember/helper';
|
|
5
|
+
import { on } from '@ember/modifier';
|
|
6
|
+
import { uniqueId } from '../utils.js';
|
|
7
|
+
import { DragState, hasFiles, filesFrom } from './file-drop/files.js';
|
|
8
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
9
|
+
import { setComponentTemplate } from '@ember/component';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A zone to drop files on, or to click to pick files.
|
|
13
|
+
*
|
|
14
|
+
* ```gjs
|
|
15
|
+
* import { FileDrop } from 'ember-primitives/components/file-drop';
|
|
16
|
+
*
|
|
17
|
+
* const load = (files) => console.log(files);
|
|
18
|
+
*
|
|
19
|
+
* <template>
|
|
20
|
+
* <FileDrop @accept="video/*" @onFiles={{load}}>
|
|
21
|
+
* Drop a video here, or click to choose one
|
|
22
|
+
* </FileDrop>
|
|
23
|
+
* </template>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
class FileDrop extends Component {
|
|
27
|
+
drag = new DragState();
|
|
28
|
+
inputId = uniqueId();
|
|
29
|
+
onDragEnter = event => this.drag.enter(event);
|
|
30
|
+
onDragLeave = event => this.drag.leave(event);
|
|
31
|
+
/**
|
|
32
|
+
* Without this, the browser does not allow the drop.
|
|
33
|
+
*/
|
|
34
|
+
onDragOver = event => {
|
|
35
|
+
if (hasFiles(event)) event.preventDefault();
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* `preventDefault` also tells a `DocumentFileDrop` that this zone took the drop.
|
|
39
|
+
*/
|
|
40
|
+
onDrop = event => {
|
|
41
|
+
this.drag.end();
|
|
42
|
+
if (!hasFiles(event)) return;
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
this.take(event.dataTransfer?.files);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The input is cleared after each pick, so that picking the same file again
|
|
48
|
+
* is a change too.
|
|
49
|
+
*/
|
|
50
|
+
onPick = event => {
|
|
51
|
+
const input = event.currentTarget;
|
|
52
|
+
this.take(input.files);
|
|
53
|
+
input.value = "";
|
|
54
|
+
};
|
|
55
|
+
take(list) {
|
|
56
|
+
const files = filesFrom(list, this.args.accept, this.args.multiple);
|
|
57
|
+
if (files.length > 0) this.args.onFiles(files);
|
|
58
|
+
}
|
|
59
|
+
static {
|
|
60
|
+
setComponentTemplate(precompileTemplate("<label for={{this.inputId}} data-dragging={{if this.drag.isDragging \"\"}} {{on \"dragenter\" this.onDragEnter}} {{on \"dragover\" this.onDragOver}} {{on \"dragleave\" this.onDragLeave}} {{on \"drop\" this.onDrop}} ...attributes>\n {{yield (hash isDragging=this.drag.isDragging)}}\n <input id={{this.inputId}} class=\"ember-primitives__visually-hidden\" type=\"file\" accept={{@accept}} multiple={{@multiple}} {{on \"change\" this.onPick}} />\n</label>", {
|
|
61
|
+
strictMode: true,
|
|
62
|
+
scope: () => ({
|
|
63
|
+
on,
|
|
64
|
+
hash
|
|
65
|
+
})
|
|
66
|
+
}), this);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Takes files dropped anywhere on the page. It renders no element.
|
|
71
|
+
*
|
|
72
|
+
* - The block gets `isDragging`, for example to show where a drop goes.
|
|
73
|
+
* - A drop outside every `FileDrop` does not open the file in the browser tab.
|
|
74
|
+
* - A drop on a `FileDrop` goes only to that `FileDrop`.
|
|
75
|
+
*
|
|
76
|
+
* ```gjs
|
|
77
|
+
* import { DocumentFileDrop } from 'ember-primitives/components/file-drop';
|
|
78
|
+
*
|
|
79
|
+
* const load = (files) => console.log(files);
|
|
80
|
+
*
|
|
81
|
+
* <template>
|
|
82
|
+
* <DocumentFileDrop @accept="video/*" @onFiles={{load}} as |page|>
|
|
83
|
+
* {{#if page.isDragging}}
|
|
84
|
+
* <div class="overlay">Drop to load</div>
|
|
85
|
+
* {{/if}}
|
|
86
|
+
* </DocumentFileDrop>
|
|
87
|
+
* </template>
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
class DocumentFileDrop extends Component {
|
|
91
|
+
drag = new DragState();
|
|
92
|
+
constructor(owner, args) {
|
|
93
|
+
super(owner, args);
|
|
94
|
+
document.addEventListener("dragenter", this.onDragEnter);
|
|
95
|
+
document.addEventListener("dragover", this.onDragOver);
|
|
96
|
+
document.addEventListener("dragleave", this.onDragLeave);
|
|
97
|
+
document.addEventListener("drop", this.onDrop);
|
|
98
|
+
registerDestructor(this, () => {
|
|
99
|
+
document.removeEventListener("dragenter", this.onDragEnter);
|
|
100
|
+
document.removeEventListener("dragover", this.onDragOver);
|
|
101
|
+
document.removeEventListener("dragleave", this.onDragLeave);
|
|
102
|
+
document.removeEventListener("drop", this.onDrop);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
onDragEnter = event => this.drag.enter(event);
|
|
106
|
+
onDragLeave = event => this.drag.leave(event);
|
|
107
|
+
onDragOver = event => {
|
|
108
|
+
if (hasFiles(event)) event.preventDefault();
|
|
109
|
+
};
|
|
110
|
+
onDrop = event => {
|
|
111
|
+
this.drag.end();
|
|
112
|
+
if (!hasFiles(event) || event.defaultPrevented) return;
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
const files = filesFrom(event.dataTransfer?.files, this.args.accept, this.args.multiple);
|
|
115
|
+
if (files.length > 0) this.args.onFiles(files);
|
|
116
|
+
};
|
|
117
|
+
static {
|
|
118
|
+
setComponentTemplate(precompileTemplate("{{yield (hash isDragging=this.drag.isDragging)}}", {
|
|
119
|
+
strictMode: true,
|
|
120
|
+
scope: () => ({
|
|
121
|
+
hash
|
|
122
|
+
})
|
|
123
|
+
}), this);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { DocumentFileDrop, FileDrop };
|
|
128
|
+
//# sourceMappingURL=file-drop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-drop.js","sources":["../../src/components/file-drop.gts"],"sourcesContent":["import \"./visually-hidden.css\";\n\nimport Component from \"@glimmer/component\";\nimport { registerDestructor } from \"@ember/destroyable\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { uniqueId } from \"../utils.ts\";\nimport { DragState, filesFrom, hasFiles } from \"./file-drop/files.ts\";\n\nimport type Owner from \"@ember/owner\";\n\ninterface FileArgs {\n /**\n * Called with the files that match `@accept`.\n * It is not called when no file matches.\n */\n onFiles: (files: File[]) => void;\n\n /**\n * Which files to take, in the format of the `accept` attribute\n * of `<input type=\"file\">`, for example `\"video/*,.mkv\"`.\n *\n * The file picker shows only these files.\n * A drop of other files does nothing.\n */\n accept?: string;\n\n /**\n * Take more than one file. Without it, only the first file that matches.\n */\n multiple?: boolean;\n}\n\nexport interface Signature {\n /**\n * A `<label>` for a hidden `<input type=\"file\">`,\n * so a click or a key press on the zone opens the file picker.\n *\n * It has `data-dragging` while files are dragged over it.\n */\n Element: HTMLLabelElement;\n Args: FileArgs;\n Blocks: {\n default: [\n {\n /**\n * Files are dragged over the zone.\n */\n isDragging: boolean;\n },\n ];\n };\n}\n\n/**\n * A zone to drop files on, or to click to pick files.\n *\n * ```gjs\n * import { FileDrop } from 'ember-primitives/components/file-drop';\n *\n * const load = (files) => console.log(files);\n *\n * <template>\n * <FileDrop @accept=\"video/*\" @onFiles={{load}}>\n * Drop a video here, or click to choose one\n * </FileDrop>\n * </template>\n * ```\n */\nexport class FileDrop extends Component<Signature> {\n drag = new DragState();\n inputId = uniqueId();\n\n onDragEnter = (event: DragEvent) => this.drag.enter(event);\n onDragLeave = (event: DragEvent) => this.drag.leave(event);\n\n /**\n * Without this, the browser does not allow the drop.\n */\n onDragOver = (event: DragEvent) => {\n if (hasFiles(event)) event.preventDefault();\n };\n\n /**\n * `preventDefault` also tells a `DocumentFileDrop` that this zone took the drop.\n */\n onDrop = (event: DragEvent) => {\n this.drag.end();\n\n if (!hasFiles(event)) return;\n\n event.preventDefault();\n this.take(event.dataTransfer?.files);\n };\n\n /**\n * The input is cleared after each pick, so that picking the same file again\n * is a change too.\n */\n onPick = (event: Event) => {\n const input = event.currentTarget as HTMLInputElement;\n\n this.take(input.files);\n input.value = \"\";\n };\n\n take(list: FileList | null | undefined) {\n const files = filesFrom(list, this.args.accept, this.args.multiple);\n\n if (files.length > 0) this.args.onFiles(files);\n }\n\n <template>\n <label\n for={{this.inputId}}\n data-dragging={{if this.drag.isDragging \"\"}}\n {{on \"dragenter\" this.onDragEnter}}\n {{on \"dragover\" this.onDragOver}}\n {{on \"dragleave\" this.onDragLeave}}\n {{on \"drop\" this.onDrop}}\n ...attributes\n >\n {{yield (hash isDragging=this.drag.isDragging)}}\n <input\n id={{this.inputId}}\n class=\"ember-primitives__visually-hidden\"\n type=\"file\"\n accept={{@accept}}\n multiple={{@multiple}}\n {{on \"change\" this.onPick}}\n />\n </label>\n </template>\n}\n\nexport interface DocumentSignature {\n Args: FileArgs;\n Blocks: {\n default: [\n {\n /**\n * Files are dragged over the page.\n */\n isDragging: boolean;\n },\n ];\n };\n}\n\n/**\n * Takes files dropped anywhere on the page. It renders no element.\n *\n * - The block gets `isDragging`, for example to show where a drop goes.\n * - A drop outside every `FileDrop` does not open the file in the browser tab.\n * - A drop on a `FileDrop` goes only to that `FileDrop`.\n *\n * ```gjs\n * import { DocumentFileDrop } from 'ember-primitives/components/file-drop';\n *\n * const load = (files) => console.log(files);\n *\n * <template>\n * <DocumentFileDrop @accept=\"video/*\" @onFiles={{load}} as |page|>\n * {{#if page.isDragging}}\n * <div class=\"overlay\">Drop to load</div>\n * {{/if}}\n * </DocumentFileDrop>\n * </template>\n * ```\n */\nexport class DocumentFileDrop extends Component<DocumentSignature> {\n drag = new DragState();\n\n constructor(owner: Owner, args: DocumentSignature[\"Args\"]) {\n super(owner, args);\n\n document.addEventListener(\"dragenter\", this.onDragEnter);\n document.addEventListener(\"dragover\", this.onDragOver);\n document.addEventListener(\"dragleave\", this.onDragLeave);\n document.addEventListener(\"drop\", this.onDrop);\n\n registerDestructor(this, () => {\n document.removeEventListener(\"dragenter\", this.onDragEnter);\n document.removeEventListener(\"dragover\", this.onDragOver);\n document.removeEventListener(\"dragleave\", this.onDragLeave);\n document.removeEventListener(\"drop\", this.onDrop);\n });\n }\n\n onDragEnter = (event: DragEvent) => this.drag.enter(event);\n onDragLeave = (event: DragEvent) => this.drag.leave(event);\n\n onDragOver = (event: DragEvent) => {\n if (hasFiles(event)) event.preventDefault();\n };\n\n onDrop = (event: DragEvent) => {\n this.drag.end();\n\n if (!hasFiles(event) || event.defaultPrevented) return;\n\n event.preventDefault();\n\n const files = filesFrom(event.dataTransfer?.files, this.args.accept, this.args.multiple);\n\n if (files.length > 0) this.args.onFiles(files);\n };\n\n <template>{{yield (hash isDragging=this.drag.isDragging)}}</template>\n}\n"],"names":["FileDrop","Component","drag","DragState","inputId","uniqueId","onDragEnter","event","enter","onDragLeave","leave","onDragOver","hasFiles","preventDefault","onDrop","end","take","dataTransfer","files","onPick","input","currentTarget","value","list","filesFrom","args","accept","multiple","length","onFiles","setComponentTemplate","precompileTemplate","strictMode","scope","on","hash","DocumentFileDrop","constructor","owner","document","addEventListener","registerDestructor","removeEventListener","defaultPrevented"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { CommandPalette } from './components/command-palette.js';
|
|
|
6
6
|
export { Dialog, Modal } from './components/dialog.js';
|
|
7
7
|
export { Drawer } from './components/drawer.js';
|
|
8
8
|
export { ExternalLink } from './components/external-link.js';
|
|
9
|
+
export { DocumentFileDrop, FileDrop } from './components/file-drop.js';
|
|
9
10
|
export { Form } from './components/form.js';
|
|
10
11
|
export { IncrementalEach } from './components/incremental-each.js';
|
|
11
12
|
export { Key, KeyCombo } from './components/keys.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * DANGER: this is a *barrel file*\n *\n * It forces the whole library to be loaded and all dependencies.\n *\n * If you have a small app, you probably don't want to import from here -- instead import from each sub-path.\n */\nimport { importSync, isDevelopingApp, macroCondition } from '@embroider/macros';\n\nif (macroCondition(isDevelopingApp())) {\n importSync('./components/violations.css');\n}\n\nexport { Accordion } from './components/accordion.gts';\nexport type {\n AccordionContentExternalSignature,\n AccordionHeaderExternalSignature,\n AccordionItemExternalSignature,\n AccordionTriggerExternalSignature,\n} from './components/accordion/public.ts';\nexport { Avatar } from './components/avatar.gts';\nexport { Breadcrumb } from './components/breadcrumb.gts';\nexport { CommandPalette } from './components/command-palette.gts';\nexport { Dialog, Modal } from './components/dialog.gts';\nexport { Drawer } from './components/drawer.gts';\nexport { ExternalLink } from './components/external-link.gts';\nexport { Form } from './components/form.gts';\nexport { IncrementalEach } from './components/incremental-each.gts';\nexport { Key, KeyCombo } from './components/keys.gts';\nexport { StickyFooter } from './components/layout/sticky-footer.gts';\nexport { Link } from './components/link.gts';\nexport { Menu } from './components/menu.gts';\nexport { OTP, OTPInput } from './components/one-time-password.gts';\nexport { Popover } from './components/popover.gts';\nexport { Portal } from './components/portal.gts';\nexport { PortalTargets } from './components/portal-targets.gts';\nexport { TARGETS as PORTALS } from './components/portal-targets.gts';\nexport { Progress } from './components/progress.gts';\nexport { Rating } from './components/rating.gts';\nexport {\n Resizable,\n Handle as ResizableHandle,\n Panel as ResizablePanel,\n} from './components/resizable.gts';\nexport { Scroller } from './components/scroller.gts';\nexport { Separator } from './components/separator.gts';\nexport { Shadowed } from './components/shadowed.gts';\nexport { Slider } from './components/slider.gts';\nexport { Switch } from './components/switch.gts';\nexport { Toggle } from './components/toggle.gts';\nexport { ToggleGroup } from './components/toggle-group.gts';\nexport { VisuallyHidden } from './components/visually-hidden.gts';\nexport { Zoetrope } from './components/zoetrope.ts';\nexport * from './helpers.ts';\n"],"names":["macroCondition","isDevelopingApp","importSync"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * DANGER: this is a *barrel file*\n *\n * It forces the whole library to be loaded and all dependencies.\n *\n * If you have a small app, you probably don't want to import from here -- instead import from each sub-path.\n */\nimport { importSync, isDevelopingApp, macroCondition } from '@embroider/macros';\n\nif (macroCondition(isDevelopingApp())) {\n importSync('./components/violations.css');\n}\n\nexport { Accordion } from './components/accordion.gts';\nexport type {\n AccordionContentExternalSignature,\n AccordionHeaderExternalSignature,\n AccordionItemExternalSignature,\n AccordionTriggerExternalSignature,\n} from './components/accordion/public.ts';\nexport { Avatar } from './components/avatar.gts';\nexport { Breadcrumb } from './components/breadcrumb.gts';\nexport { CommandPalette } from './components/command-palette.gts';\nexport { Dialog, Modal } from './components/dialog.gts';\nexport { Drawer } from './components/drawer.gts';\nexport { ExternalLink } from './components/external-link.gts';\nexport { DocumentFileDrop, FileDrop } from './components/file-drop.gts';\nexport { Form } from './components/form.gts';\nexport { IncrementalEach } from './components/incremental-each.gts';\nexport { Key, KeyCombo } from './components/keys.gts';\nexport { StickyFooter } from './components/layout/sticky-footer.gts';\nexport { Link } from './components/link.gts';\nexport { Menu } from './components/menu.gts';\nexport { OTP, OTPInput } from './components/one-time-password.gts';\nexport { Popover } from './components/popover.gts';\nexport { Portal } from './components/portal.gts';\nexport { PortalTargets } from './components/portal-targets.gts';\nexport { TARGETS as PORTALS } from './components/portal-targets.gts';\nexport { Progress } from './components/progress.gts';\nexport { Rating } from './components/rating.gts';\nexport {\n Resizable,\n Handle as ResizableHandle,\n Panel as ResizablePanel,\n} from './components/resizable.gts';\nexport { Scroller } from './components/scroller.gts';\nexport { Separator } from './components/separator.gts';\nexport { Shadowed } from './components/shadowed.gts';\nexport { Slider } from './components/slider.gts';\nexport { Switch } from './components/switch.gts';\nexport { Toggle } from './components/toggle.gts';\nexport { ToggleGroup } from './components/toggle-group.gts';\nexport { VisuallyHidden } from './components/visually-hidden.gts';\nexport { Zoetrope } from './components/zoetrope.ts';\nexport * from './helpers.ts';\n"],"names":["macroCondition","isDevelopingApp","importSync"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA,IAAIA,cAAc,CAACC,eAAe,EAAE,CAAC,EAAE;EACrCC,UAAU,CAAC,6BAA6B,CAAC;AAC3C"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { tracked } from '@glimmer/tracking';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One entry of an `accept` list: an extension (`.mp4`),
|
|
5
|
+
* a type with a wildcard (`video/*`), or an exact type (`video/webm`).
|
|
6
|
+
*/
|
|
7
|
+
function acceptsOne(file: File, entry: string): boolean {
|
|
8
|
+
if (entry.startsWith('.')) {
|
|
9
|
+
return file.name.toLowerCase().endsWith(entry.toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (entry.endsWith('/*')) {
|
|
13
|
+
return file.type.startsWith(entry.slice(0, -1));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return file.type === entry;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Matches a file against an `accept` list, as `<input type="file">` reads it.
|
|
21
|
+
*
|
|
22
|
+
* The file picker filters by `accept` itself, but a drop does not,
|
|
23
|
+
* so dropped files need this check.
|
|
24
|
+
*/
|
|
25
|
+
export function accepts(file: File, accept: string | undefined): boolean {
|
|
26
|
+
if (!accept) return true;
|
|
27
|
+
|
|
28
|
+
const entries = accept.split(',');
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const trimmed = entry.trim();
|
|
32
|
+
|
|
33
|
+
if (trimmed && acceptsOne(file, trimmed)) return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A drag can carry text or a link instead of files.
|
|
41
|
+
* During the drag, only the types are readable, not the files.
|
|
42
|
+
*/
|
|
43
|
+
export function hasFiles(event: DragEvent): boolean {
|
|
44
|
+
const types = event.dataTransfer?.types;
|
|
45
|
+
|
|
46
|
+
if (!types) return false;
|
|
47
|
+
|
|
48
|
+
for (const type of types) {
|
|
49
|
+
if (type === 'Files') return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The files of a pick or a drop that match `accept`, and only the first one
|
|
57
|
+
* unless `multiple` is set.
|
|
58
|
+
*/
|
|
59
|
+
export function filesFrom(
|
|
60
|
+
list: FileList | null | undefined,
|
|
61
|
+
accept: string | undefined,
|
|
62
|
+
multiple: boolean | undefined
|
|
63
|
+
): File[] {
|
|
64
|
+
const files: File[] = [];
|
|
65
|
+
|
|
66
|
+
if (!list) return files;
|
|
67
|
+
|
|
68
|
+
for (const file of list) {
|
|
69
|
+
if (!accepts(file, accept)) continue;
|
|
70
|
+
|
|
71
|
+
files.push(file);
|
|
72
|
+
|
|
73
|
+
if (!multiple) break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Whether files are dragged over an element.
|
|
81
|
+
*
|
|
82
|
+
* `dragenter` and `dragleave` fire for every child that the pointer crosses,
|
|
83
|
+
* so a single flag would flicker. The depth counts the enters that did not leave yet.
|
|
84
|
+
*/
|
|
85
|
+
export class DragState {
|
|
86
|
+
@tracked isDragging = false;
|
|
87
|
+
|
|
88
|
+
#depth = 0;
|
|
89
|
+
|
|
90
|
+
enter(event: DragEvent): void {
|
|
91
|
+
if (!hasFiles(event)) return;
|
|
92
|
+
|
|
93
|
+
this.#depth++;
|
|
94
|
+
this.isDragging = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
leave(event: DragEvent): void {
|
|
98
|
+
if (!hasFiles(event)) return;
|
|
99
|
+
|
|
100
|
+
this.#depth = Math.max(0, this.#depth - 1);
|
|
101
|
+
|
|
102
|
+
if (this.#depth === 0) this.isDragging = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
end(): void {
|
|
106
|
+
this.#depth = 0;
|
|
107
|
+
this.isDragging = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import "./visually-hidden.css";
|
|
2
|
+
|
|
3
|
+
import Component from "@glimmer/component";
|
|
4
|
+
import { registerDestructor } from "@ember/destroyable";
|
|
5
|
+
import { hash } from "@ember/helper";
|
|
6
|
+
import { on } from "@ember/modifier";
|
|
7
|
+
|
|
8
|
+
import { uniqueId } from "../utils.ts";
|
|
9
|
+
import { DragState, filesFrom, hasFiles } from "./file-drop/files.ts";
|
|
10
|
+
|
|
11
|
+
import type Owner from "@ember/owner";
|
|
12
|
+
|
|
13
|
+
interface FileArgs {
|
|
14
|
+
/**
|
|
15
|
+
* Called with the files that match `@accept`.
|
|
16
|
+
* It is not called when no file matches.
|
|
17
|
+
*/
|
|
18
|
+
onFiles: (files: File[]) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Which files to take, in the format of the `accept` attribute
|
|
22
|
+
* of `<input type="file">`, for example `"video/*,.mkv"`.
|
|
23
|
+
*
|
|
24
|
+
* The file picker shows only these files.
|
|
25
|
+
* A drop of other files does nothing.
|
|
26
|
+
*/
|
|
27
|
+
accept?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Take more than one file. Without it, only the first file that matches.
|
|
31
|
+
*/
|
|
32
|
+
multiple?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Signature {
|
|
36
|
+
/**
|
|
37
|
+
* A `<label>` for a hidden `<input type="file">`,
|
|
38
|
+
* so a click or a key press on the zone opens the file picker.
|
|
39
|
+
*
|
|
40
|
+
* It has `data-dragging` while files are dragged over it.
|
|
41
|
+
*/
|
|
42
|
+
Element: HTMLLabelElement;
|
|
43
|
+
Args: FileArgs;
|
|
44
|
+
Blocks: {
|
|
45
|
+
default: [
|
|
46
|
+
{
|
|
47
|
+
/**
|
|
48
|
+
* Files are dragged over the zone.
|
|
49
|
+
*/
|
|
50
|
+
isDragging: boolean;
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A zone to drop files on, or to click to pick files.
|
|
58
|
+
*
|
|
59
|
+
* ```gjs
|
|
60
|
+
* import { FileDrop } from 'ember-primitives/components/file-drop';
|
|
61
|
+
*
|
|
62
|
+
* const load = (files) => console.log(files);
|
|
63
|
+
*
|
|
64
|
+
* <template>
|
|
65
|
+
* <FileDrop @accept="video/*" @onFiles={{load}}>
|
|
66
|
+
* Drop a video here, or click to choose one
|
|
67
|
+
* </FileDrop>
|
|
68
|
+
* </template>
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export class FileDrop extends Component<Signature> {
|
|
72
|
+
drag = new DragState();
|
|
73
|
+
inputId = uniqueId();
|
|
74
|
+
|
|
75
|
+
onDragEnter = (event: DragEvent) => this.drag.enter(event);
|
|
76
|
+
onDragLeave = (event: DragEvent) => this.drag.leave(event);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Without this, the browser does not allow the drop.
|
|
80
|
+
*/
|
|
81
|
+
onDragOver = (event: DragEvent) => {
|
|
82
|
+
if (hasFiles(event)) event.preventDefault();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* `preventDefault` also tells a `DocumentFileDrop` that this zone took the drop.
|
|
87
|
+
*/
|
|
88
|
+
onDrop = (event: DragEvent) => {
|
|
89
|
+
this.drag.end();
|
|
90
|
+
|
|
91
|
+
if (!hasFiles(event)) return;
|
|
92
|
+
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
this.take(event.dataTransfer?.files);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The input is cleared after each pick, so that picking the same file again
|
|
99
|
+
* is a change too.
|
|
100
|
+
*/
|
|
101
|
+
onPick = (event: Event) => {
|
|
102
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
103
|
+
|
|
104
|
+
this.take(input.files);
|
|
105
|
+
input.value = "";
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
take(list: FileList | null | undefined) {
|
|
109
|
+
const files = filesFrom(list, this.args.accept, this.args.multiple);
|
|
110
|
+
|
|
111
|
+
if (files.length > 0) this.args.onFiles(files);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
<template>
|
|
115
|
+
<label
|
|
116
|
+
for={{this.inputId}}
|
|
117
|
+
data-dragging={{if this.drag.isDragging ""}}
|
|
118
|
+
{{on "dragenter" this.onDragEnter}}
|
|
119
|
+
{{on "dragover" this.onDragOver}}
|
|
120
|
+
{{on "dragleave" this.onDragLeave}}
|
|
121
|
+
{{on "drop" this.onDrop}}
|
|
122
|
+
...attributes
|
|
123
|
+
>
|
|
124
|
+
{{yield (hash isDragging=this.drag.isDragging)}}
|
|
125
|
+
<input
|
|
126
|
+
id={{this.inputId}}
|
|
127
|
+
class="ember-primitives__visually-hidden"
|
|
128
|
+
type="file"
|
|
129
|
+
accept={{@accept}}
|
|
130
|
+
multiple={{@multiple}}
|
|
131
|
+
{{on "change" this.onPick}}
|
|
132
|
+
/>
|
|
133
|
+
</label>
|
|
134
|
+
</template>
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface DocumentSignature {
|
|
138
|
+
Args: FileArgs;
|
|
139
|
+
Blocks: {
|
|
140
|
+
default: [
|
|
141
|
+
{
|
|
142
|
+
/**
|
|
143
|
+
* Files are dragged over the page.
|
|
144
|
+
*/
|
|
145
|
+
isDragging: boolean;
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Takes files dropped anywhere on the page. It renders no element.
|
|
153
|
+
*
|
|
154
|
+
* - The block gets `isDragging`, for example to show where a drop goes.
|
|
155
|
+
* - A drop outside every `FileDrop` does not open the file in the browser tab.
|
|
156
|
+
* - A drop on a `FileDrop` goes only to that `FileDrop`.
|
|
157
|
+
*
|
|
158
|
+
* ```gjs
|
|
159
|
+
* import { DocumentFileDrop } from 'ember-primitives/components/file-drop';
|
|
160
|
+
*
|
|
161
|
+
* const load = (files) => console.log(files);
|
|
162
|
+
*
|
|
163
|
+
* <template>
|
|
164
|
+
* <DocumentFileDrop @accept="video/*" @onFiles={{load}} as |page|>
|
|
165
|
+
* {{#if page.isDragging}}
|
|
166
|
+
* <div class="overlay">Drop to load</div>
|
|
167
|
+
* {{/if}}
|
|
168
|
+
* </DocumentFileDrop>
|
|
169
|
+
* </template>
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export class DocumentFileDrop extends Component<DocumentSignature> {
|
|
173
|
+
drag = new DragState();
|
|
174
|
+
|
|
175
|
+
constructor(owner: Owner, args: DocumentSignature["Args"]) {
|
|
176
|
+
super(owner, args);
|
|
177
|
+
|
|
178
|
+
document.addEventListener("dragenter", this.onDragEnter);
|
|
179
|
+
document.addEventListener("dragover", this.onDragOver);
|
|
180
|
+
document.addEventListener("dragleave", this.onDragLeave);
|
|
181
|
+
document.addEventListener("drop", this.onDrop);
|
|
182
|
+
|
|
183
|
+
registerDestructor(this, () => {
|
|
184
|
+
document.removeEventListener("dragenter", this.onDragEnter);
|
|
185
|
+
document.removeEventListener("dragover", this.onDragOver);
|
|
186
|
+
document.removeEventListener("dragleave", this.onDragLeave);
|
|
187
|
+
document.removeEventListener("drop", this.onDrop);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
onDragEnter = (event: DragEvent) => this.drag.enter(event);
|
|
192
|
+
onDragLeave = (event: DragEvent) => this.drag.leave(event);
|
|
193
|
+
|
|
194
|
+
onDragOver = (event: DragEvent) => {
|
|
195
|
+
if (hasFiles(event)) event.preventDefault();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
onDrop = (event: DragEvent) => {
|
|
199
|
+
this.drag.end();
|
|
200
|
+
|
|
201
|
+
if (!hasFiles(event) || event.defaultPrevented) return;
|
|
202
|
+
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
|
|
205
|
+
const files = filesFrom(event.dataTransfer?.files, this.args.accept, this.args.multiple);
|
|
206
|
+
|
|
207
|
+
if (files.length > 0) this.args.onFiles(files);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
<template>{{yield (hash isDragging=this.drag.isDragging)}}</template>
|
|
211
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { CommandPalette } from './components/command-palette.gts';
|
|
|
24
24
|
export { Dialog, Modal } from './components/dialog.gts';
|
|
25
25
|
export { Drawer } from './components/drawer.gts';
|
|
26
26
|
export { ExternalLink } from './components/external-link.gts';
|
|
27
|
+
export { DocumentFileDrop, FileDrop } from './components/file-drop.gts';
|
|
27
28
|
export { Form } from './components/form.gts';
|
|
28
29
|
export { IncrementalEach } from './components/incremental-each.gts';
|
|
29
30
|
export { Key, KeyCombo } from './components/keys.gts';
|