Skip to content

Commit f34d9ab

Browse files
authored
Merge pull request #328 from rackerlabs/surf-1039-build-drag-drop-zone
surf-1039-build-drag-drop-zone
2 parents e2c1f31 + 749480d commit f34d9ab

File tree

10 files changed

+369
-0
lines changed

10 files changed

+369
-0
lines changed

docs/_data/nav.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
{ label: '<hx-checkbox>', path: 'hx-checkbox' },
7676
{ label: '<hx-disclosure>', path: 'hx-disclosure' },
7777
{ label: '<hx-div>', path: 'hx-div' },
78+
{ label: '<hx-drop-zone>', path: 'hx-drop-zone' },
7879
{ label: '<hx-error>', path: 'hx-error' },
7980
{ label: '<hx-file-icon>', path: 'hx-file-icon' },
8081
{ label: '<hx-file-input>', path: 'hx-file-input' },
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
if (document.getElementById('vue-dropZoneDemo')) {
2+
new Vue({
3+
el: '#vue-dropZoneDemo',
4+
methods: {
5+
onDrop: function (evt) {
6+
if (evt.dataTransfer.items) {
7+
// Use DataTransferItemList interface to access the file(s)
8+
Array.from(evt.dataTransfer.items).forEach(item => {
9+
// If dropped items aren't files, reject them
10+
if (item.kind === 'file') {
11+
let file = item.getAsFile();
12+
alert('File Drop Successful! ' + file.name);
13+
}
14+
});
15+
} else {
16+
// TODO: find solution, unsure how to pull file info in IE11
17+
alert('We detected a file drop!');
18+
}
19+
},
20+
},
21+
});
22+
}

docs/components/files/index.html

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,64 @@
22
title: Files
33
minver: 0.12.0
44
also:
5+
components/files/test-drop-zones.html: Testing - Drop Zones
56
components/files/test-file-selectors.html: Testing - File Selectors
67
components/files/test-file-tiles.html: Testing - File Tiles
78
components/Icons: Icons
9+
elements/hx-drop-zone: <hx-drop-zone>
810
elements/hx-file-input: <hx-file-input>
911
elements/hx-file-tile: <hx-file-tile>
1012
---
1113
{% extends 'component.njk' %}
1214
{% block content %}
15+
<section>
16+
<h2 id="drop-zone">Drop Zone</h2>
17+
<p class="hxSubBody">Added: v0.14.0</p>
18+
<p>
19+
A container that provides a visual overlay when dragging a file, marking
20+
the boundaries of where a user can drop.
21+
</p>
22+
<p class="hxSubdued hxSubBody">
23+
<hx-icon type="info-circle"></hx-icon>
24+
The drop zone ignores any padding applied to it.
25+
</p>
26+
<div class="example" id="vue-dropZoneDemo">
27+
<div>
28+
<hx-drop-zone @drop.prevent="onDrop">
29+
<div class="hxFence">
30+
<hx-file-icon type="paperclip"></hx-file-icon>
31+
<p>
32+
Drop files here or
33+
<hx-file-input
34+
class="hxTertiary"
35+
label="browse your files">
36+
<input type="file" />
37+
</hx-file-input>.
38+
</p>
39+
</div>
40+
</hx-drop-zone>
41+
</div>
42+
43+
<footer>
44+
{% code 'html' %}
45+
<hx-drop-zone>
46+
<div class="hxFence">
47+
<hx-file-icon type="paperclip"></hx-file-icon>
48+
<p>
49+
Drop files here or
50+
<hx-file-input
51+
class="hxTertiary"
52+
label="browse your files">
53+
<input type="file" />
54+
</hx-file-input>.
55+
</p>
56+
</div>
57+
</hx-drop-zone>
58+
{% endcode %}
59+
</footer>
60+
</div>
61+
</section>
62+
1363
<section>
1464
<h2 id="file-selector">File Selector</h2>
1565
<p class="hxSubBody">Added: v0.13.0</p>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
title: Testing - Drop Zones
3+
---
4+
{% extends 'test.njk' %}
5+
{% block content %}
6+
<section>
7+
<nav class="hxBreadcrumb">
8+
<a href="components/files">Files</a>
9+
<hx-icon class="delimiter" type="angle-right"></hx-icon>
10+
<a href="#">{{page.title}}</a>
11+
</nav>
12+
</section>
13+
14+
<section>
15+
<h2>Invisible Drop Zone</h2>
16+
<hx-drop-zone>
17+
<p>Invisible Drop Zone</p>
18+
</hx-drop-zone>
19+
</section>
20+
21+
<section>
22+
<h2>Visible Drop Zone (file not over element)</h2>
23+
<hx-drop-zone drag="away">
24+
<p>Visible Drop Zone (file not over element)</p>
25+
</hx-drop-zone>
26+
</section>
27+
28+
<section>
29+
<h2>Visible Drop Zone (file over element)</h2>
30+
<hx-drop-zone drag="over">
31+
<p>Visible Drop Zone (file over element)</p>
32+
</hx-drop-zone>
33+
</section>
34+
35+
<section>
36+
<h2>Drop Zone with Child Fence</h2>
37+
<hx-drop-zone>
38+
<div class="hxFence hxBox hxMd">
39+
<p>Invisible Drop Zone</p>
40+
</div>
41+
</hx-drop-zone>
42+
</section>
43+
44+
<section>
45+
<h2>Visible Drop Zone with Child Fence (file not over element)</h2>
46+
<hx-drop-zone drag="away">
47+
<div class="hxFence hxBox hxMd">
48+
<p>Visible Drop Zone (file not over element)</p>
49+
</div>
50+
</hx-drop-zone>
51+
</section>
52+
53+
<section>
54+
<h2>Visible Drop Zone with Child Fence (file over element)</h2>
55+
<hx-drop-zone drag="over">
56+
<div class="hxFence hxBox hxMd">
57+
<p>Visible Drop Zone (file over element)</p>
58+
</div>
59+
</hx-drop-zone>
60+
</section>
61+
{% endblock %}

docs/docs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import './components/box/box-demo';
88
import './components/buttons/button-demo';
99
import './components/checkboxes/checkbox-demo';
1010
import './components/choice-tiles/choice-tile-demo';
11+
import './components/files/drop-zone-demo';
1112
import './components/files/file-input-demo';
1213
import './components/files/file-tile-demo';
1314
import './components/icons/icon-demo';
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: <hx-drop-zone>
3+
minver: 0.14.0
4+
also:
5+
components/files: Files
6+
---
7+
{% extends 'element.njk' %}
8+
{% block content %}
9+
<section>
10+
<p>
11+
The custom <code>{{page.title}}</code> element provides a visual overlay
12+
when dragging a file, marking the boundaries of where a user can drop.
13+
</p>
14+
15+
<dl class="hxBox-md metadata hxList">
16+
<div>
17+
<dt>Permitted Parents</dt>
18+
<dd>any</dd>
19+
</div>
20+
<div>
21+
<dt>Permitted Children</dt>
22+
<dd>any</dd>
23+
</div>
24+
<div>
25+
<dt>Events</dt>
26+
<dd>
27+
Any of the following:
28+
<ul>
29+
<li>
30+
<code>drop</code> - See MDN's
31+
<a href="https://goo.gl/aKLYsQ" target="_blank">
32+
"File Drag and Drop"
33+
<hx-icon type="external-link"></hx-icon>
34+
</a>
35+
for more information.
36+
</li>
37+
</ul>
38+
</dd>
39+
</div>
40+
</dl>
41+
</section>
42+
{% endblock %}
43+
44+
{% block attributes %}
45+
<dt>drag {String}</dt>
46+
<dd>
47+
<i>(read-only)</i>
48+
<p>
49+
Its value is automatically set by the element itself and mainly used for CSS styling purposes.
50+
</p>
51+
</dd>
52+
{% endblock %}
53+
54+
{% block properties %}
55+
<dt>drag {String}</dt>
56+
<dd>
57+
<i>(read-only)</i>
58+
<p>
59+
A value of "away" means a file drag is detected on the document,
60+
away from the drop zone.
61+
</p>
62+
<p>
63+
A value of "over" means a file drag is detected on the document,
64+
over the drop zone.
65+
</p>
66+
</dd>
67+
{% endblock %}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { HXElement } from './HXElement';
2+
3+
export class HXDropZoneElement extends HXElement {
4+
static get is () {
5+
return 'hx-drop-zone';
6+
}
7+
8+
$onCreate () {
9+
this._isDocDragging = false;
10+
this._isZoneDragging = false;
11+
this._onDocDragLeave = this._onDocDragLeave.bind(this);
12+
this._onDocDragOver = this._onDocDragOver.bind(this);
13+
this._onDrop = this._onDrop.bind(this);
14+
this._stopDragging = this._stopDragging.bind(this);
15+
}
16+
17+
$onConnect () {
18+
document.addEventListener('dragleave', this._onDocDragLeave);
19+
document.addEventListener('dragover', this._onDocDragOver);
20+
document.addEventListener('drop', this._onDrop);
21+
this.addEventListener('dragleave', this._onDragLeave);
22+
this.addEventListener('dragover', this._onDragOver);
23+
this.addEventListener('drop', this._onDrop);
24+
}
25+
26+
$onDisconnect () {
27+
document.removeEventListener('dragleave', this._onDocDragLeave);
28+
document.removeEventListener('dragover', this._onDocDragOver);
29+
document.removeEventListener('drop', this._onDrop);
30+
this.removeEventListener('dragleave', this._onDragLeave);
31+
this.removeEventListener('dragover', this._onDragOver);
32+
this.removeEventListener('drop', this._onDrop);
33+
}
34+
35+
/**
36+
* @readonly
37+
* @type {String}
38+
*/
39+
get drag () {
40+
return this.getAttribute('drag');
41+
}
42+
43+
/**
44+
* @private
45+
* @returns {Boolean}
46+
*/
47+
_isFileDrag (evt) {
48+
let _types = evt.dataTransfer.types;
49+
if (_types) {
50+
if (_types.indexOf) {
51+
return (_types.indexOf('Files') !== -1);
52+
} else {
53+
return _types.contains('Files');
54+
}
55+
} else {
56+
return false;
57+
}
58+
}
59+
60+
// #2 this gets called when the dragged item leaves the document
61+
// (leaves to a child element or window altogether)
62+
/** @private */
63+
_onDocDragLeave () {
64+
window.clearTimeout(this._docDragLeaveTimeout);
65+
// callback must be an arrow function to preserve "this"
66+
this._docDragLeaveTimeout = window.setTimeout(this._stopDragging, 250);
67+
}
68+
69+
// #1 this handler fires continuously as long as the user is dragging on the page
70+
/** @private */
71+
_onDocDragOver (evt) {
72+
if (!this._isDocDragging) {
73+
this._isDocDragging = true;
74+
if (this._isFileDrag(evt)) {
75+
this.setAttribute('drag', 'away');
76+
}
77+
}
78+
window.clearTimeout(this._docDragLeaveTimeout);
79+
}
80+
81+
// #4 this gets called when the dragged item leaves the zone
82+
// (leaves to a child element or zone altogether)
83+
/** @private */
84+
_onDragLeave () {
85+
window.clearTimeout(this._zoneDragLeaveTimeout);
86+
// callback must be an arrow function to preserve "this"
87+
this._zoneDragLeaveTimeout = window.setTimeout(() => {
88+
this._isZoneDragging = false;
89+
this.setAttribute('drag', 'away');
90+
}, 0);
91+
}
92+
93+
// #3 this handler fires continuously as long as the user is dragging on the zone
94+
/** @private */
95+
_onDragOver (evt) {
96+
evt.preventDefault(); // needed for onDrop to work
97+
if (!this._isZoneDragging) {
98+
this._isZoneDragging = true;
99+
if (this._isFileDrag(evt)) {
100+
this.setAttribute('drag', 'over');
101+
}
102+
}
103+
window.clearTimeout(this._docDragLeaveTimeout);
104+
window.clearTimeout(this._zoneDragLeaveTimeout);
105+
}
106+
107+
/** @private */
108+
_onDrop () {
109+
this._stopDragging();
110+
}
111+
112+
/** @private */
113+
_stopDragging () {
114+
this.removeAttribute('drag');
115+
this._isDocDragging = false;
116+
this._isZoneDragging = false;
117+
}
118+
}

src/helix-ui/elements/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export { HXBusyElement } from './HXBusyElement';
55
export { HXCheckboxElement } from './HXCheckboxElement';
66
export { HXDisclosureElement } from './HXDisclosureElement';
77
export { HXDivElement } from './HXDivElement';
8+
export { HXDropZoneElement } from './HXDropZoneElement';
89
export { HXElement } from './HXElement';
910
export { HXErrorElement } from './HXErrorElement';
1011
export { HXFileIconElement } from './HXFileIconElement';

src/helix-ui/styles/elements.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
@import 'elements/hx-disclosure';
1616
@import 'elements/hx-div';
1717
@import 'elements/hx-dl';
18+
@import 'elements/hx-drop-zone';
1819
@import 'elements/hx-error';
1920
@import 'elements/hx-file-icon';
2021
@import 'elements/hx-file-input';

0 commit comments

Comments
 (0)