Last active
May 23, 2019 13:25
-
-
Save nfreear/04edb91e7c53caa5bbfa5cae40f59029 to your computer and use it in GitHub Desktop.
Accessible keyboard/mouse drag, via Drag-on-drop | https://schne324.github.io/dragon-drop/demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> <title> v-drag-on-drop </title> | |
<style> | |
body { margin: 1rem auto; max-width: 42rem; } | |
.demo > li, | |
.live-log { | |
background: #eee; | |
border: 1px dotted gray; | |
border-radius: .2rem; | |
margin: 1rem 0; | |
min-height: 1.5rem; | |
padding: 4px; | |
X-box-shadow: 0 0 2px #000; | |
} | |
.demo .dragon-active { | |
background: wheat; | |
} | |
.demo .handle { | |
background: orange; | |
border-radius: .3rem; | |
cursor: ns-resize; | |
font-size: 1.5rem; | |
min-height: 1rem; | |
min-width: 1rem; | |
padding: .1rem .6rem; | |
margin-right: .5rem; | |
} | |
/* Important ! | |
*/ | |
.offscreen { | |
position: absolute; | |
width: 1px; | |
height: 1px; | |
overflow: hidden; | |
clip: rect(1px 1px 1px 1px); | |
clip: rect(1px, 1px, 1px, 1px); | |
} | |
/* Below are styles for the dragula elements. | |
*/ | |
.XX--gu-hide { | |
display: none !important; | |
} | |
.gu-unselectable { | |
-webkit-user-select: none !important; | |
-moz-user-select: none !important; | |
-ms-user-select: none !important; | |
user-select: none !important; | |
} | |
.gu-transit { | |
opacity: 0.2; | |
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; | |
filter: alpha(opacity=20); | |
list-style: none; | |
} | |
</style> | |
<h1> Dragon Drop, with Vue.js </h1> | |
<div id="app"> | |
<p id="global-help"> | |
Activate the reorder button and use the arrow keys to reorder the list or use your mouse to | |
drag/reorder. Press escape to cancel the reordering. | |
<span class="offscreen">Ensure screen reader is in focus mode.</span> | |
</p> | |
<h3 id="items-head"> Rank the bands </h3> | |
<p> With drag handle </p> | |
<drag-on-drop v-bind:items="items" v-console="items" /> | |
</div> | |
<div class="live-log" id="live-log"></div> | |
<template id="drag-on-drop-template"> | |
<ol class="demo" id="demo-1" aria-labelledby="items-head"> | |
<li v-for="item in dataItems" v-bind:data-id="item.id" > | |
<button type="button" :title="'Reorder / drag item '+ item.id" class="fa fa-bars handle" v-bind:id="'item-btn-' + item.id" | |
:aria-describedby="globalHelpId" :aria-labelledby="'item-btn-' + item.id + ' item-text-'+ item.id" | |
v-html="handleContent" | |
> | |
</button> | |
<span :id="'item-text-' + item.id">Item {{ item.id }} <!-- Frank Zappa --></span> | |
</li> | |
<ol> | |
</template> | |
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> | |
<script src="https://unpkg.com/drag-on-drop@3.3.2/dist/dragon-drop.min.js"></script> | |
<script id="filter-directive.js"> | |
Vue.filter('raw', function (prefix, value) { | |
// console.warn('raw:', value) | |
return prefix + value; | |
}) | |
Vue.directive('console', function (elem, binding) { | |
console.warn('v-console:', binding.value) | |
}) | |
</script> | |
<script id="DragOnDrop.vue.js"> | |
Vue.component('DragOnDrop', { | |
template: '#drag-on-drop-template', | |
props: { // Public! | |
items: { | |
type: Array, | |
required: true, | |
// validator: value => value.length <= MAX_COUNT, // Not applicable! | |
}, | |
}, | |
data () { // Private! | |
return { | |
DEBUG: false, // true, | |
DRAG_MOUNT_DELAY_MS: 50, // Milliseconds. IMPORTANT !! | |
dataItems: null, // Array, | |
dragonDrop: null, // Object. | |
handleLabel: '↕', // :arrow_up_down: ?? | |
liveLog: document.querySelector('#live-log'), | |
globalHelpId: 'global-help', | |
} | |
}, | |
computed: { | |
handleContent () { | |
return this.handleLabel + '<i class="offscreen"> Reorder </i>' | |
}, | |
}, | |
methods: { | |
onAnnouncement (msg) { | |
let $liveLog = this.liveLog; | |
let $elem = document.createElement('div'); | |
$elem.innerHTML = msg; | |
$liveLog.appendChild($elem); | |
$liveLog.scrollTop = $liveLog.scrollHeight; | |
// clean it up after 7 seconds | |
window.setTimeout(() => { | |
$liveLog.removeChild($elem); | |
}, 7e3); | |
}, | |
setupDragEvents () { | |
this.dragonDrop | |
.on('grabbed', (container, item) => console.log('Dragon.grabbed: ', item) ) | |
.on('dropped', (container, item) => console.log('Dragon.dropped: ', item) ) | |
.on('reorder', (container, item) => console.log('Dragon.reorder: ', item) ) | |
.on('announcement', this.onAnnouncement) | |
.on('cancel', () => console.log('Dragon.reordering cancelled') ); | |
this.dragonDrop.dragula | |
.on('drag', (el, source) => console.warn('Dragula.drag:', el, source)) | |
.on('drop', (el, target, source, sibling) => console.warn('Dragula.drop', el, target, source, sibling)) // target === source; | |
}, | |
}, | |
mounted () { | |
this.dataItems = this.items; | |
if (this.DEBUG) { | |
localStorage.debug = 'drag-on-drop:*'; | |
} | |
// Must wait ... !! | |
window.setTimeout(() => { | |
// Vue.nextTick(() => { | |
const DRAG = this.dragonDrop = new DragonDrop(this.$el, { | |
handle: '.handle', | |
// item: ':scope > li', // IMPORTANT! a selector that targets only a single list's items! | |
// Live log. | |
announcement: { | |
grabbed: el => el.querySelector('span').innerText + ' grabbed', | |
dropped: el => el.querySelector('span').innerText + ' dropped', | |
reorder: (el, items) => { | |
var pos = items.indexOf(el) + 1; | |
var text = el.querySelector('span').innerText; | |
return 'The rankings have been updated, ' + text + ' is now ranked #' + pos + ' of ' + items.length; | |
}, | |
cancel: () => 'Reranking cancelled.', | |
} | |
}) | |
this.setupDragEvents() | |
console.warn('DragOnDrop.mounted:', DRAG.items.length, DRAG.handles.length, this.dragonDrop, this) | |
}, this.DRAG_MOUNT_DELAY_MS); | |
}, | |
}) | |
</script> | |
<script id="App.js"> | |
new Vue({ | |
el: '#app', | |
data () { // Private! | |
return { | |
DEFAULT_COUNT: 8, | |
} | |
}, | |
computed: { | |
items () { | |
let theItems = []; | |
// https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n/54996234 | |
const MAKE_ARRAY = Array.from(Array(this.DEFAULT_COUNT).keys()) | |
MAKE_ARRAY.forEach((name, id) => theItems.push({ id: (id + 1), name })) | |
console.warn('App.theItems:', theItems); | |
return theItems.reverse() | |
}, | |
}, | |
}) | |
</script> | |
<pre> | |
© Nick Freear, 17-May-2019. | |
* https://schne324.github.io/dragon-drop/demo/ | |
* https://npmjs.com/package/drag-on-drop | |
* https://npmjs.com/package/dragula | |
* https://npmjs.com/package/vue-i18n-extract | |
* 2009, https://dev.opera.com/articles/accessible-drag-and-drop/example.html | |
</pre> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment