ImageGrid/Viewer/lib/util-dom.js
Alex A. Naanou 27aef3db40 dos linefeeds converted to unix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2024-10-27 11:01:12 +03:00

380 lines
10 KiB
JavaScript
Executable File

/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
/*********************************************************************/
var selectElemText =
module.selectElemText =
function(elem){
var range = document.createRange()
range.selectNodeContents(elem)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range) }
// XXX make this global...
var getCaretOffset =
module.getCaretOffset =
function(elem){
var s = window.getSelection()
if(s.rangeCount == 0){
return -1 }
var r = s.getRangeAt(0)
var pre = r.cloneRange()
pre.selectNodeContents(elem)
pre.setEnd(r.endContainer, r.endOffset)
return pre.toString().length || 0 }
var selectionCollapsed =
module.selectionCollapsed =
function(elem){
var s = window.getSelection()
if(s.rangeCount == 0){
return false }
return s.getRangeAt(0).cloneRange().collapsed }
//---------------------------------------------------------------------
// XXX experiment
if(typeof(jQuery) != typeof(undefined)){
jQuery.fn._drag = function(){
var dragging = false
var s,
px, py
var elem = $(this)
.on('mousedown touchstart', function(evt){
dragging = true
px = evt.clientX
px = evt.clientY
s = elem.rscale() })
.on('mousemove touchmove', function(evt){
if(!dragging){
return }
var x = evt.clientX
var dx = px - x
px = x
var y = evt.clientY
var dy = py - y
py = y
elem
.velocity('stop')
.velocity({
translateX: '-=' + (dx / s),
translateY: '-=' + (dy / s),
}, 0) })
.on('mouseup touchend', function(evt){
dragging = false
elem.velocity('stop') }) }
jQuery.fn.selectText = function(mode){
var range = document.createRange()
this.each(function(){
range.selectNodeContents(this) })
var sel = window.getSelection()
sel.removeAllRanges()
mode === null
|| sel.addRange(range)
return this }
jQuery.fn.deselectText =
function(){
this.selectText(null)
return this }
jQuery.fn.caretOffset =
function(){
return getCaretOffset(this) }
jQuery.fn.selectionCollapsed =
function(){
return selectionCollapsed(this) }
var keyboard = require('lib/keyboard')
// Make element editable...
//
// Options format:
// {
// // activate (focus) element...
// //
// // NOTE: this will also select the element text...
// activate: false,
//
// // set multi-line edit mode...
// multiline: false,
//
// // if true in multi-line mode, accept filed on Enter, while
// // ctrl-Enter / meta-Enter insert a new line; otherwise
// // ctrl-Enter / meta-Enter will accept the edit.
// accept_on_enter: true,
//
// // clear element value on edit...
// clear_on_edit: false,
//
// // reset value on commit/abort...
// // XXX revise default...
// reset_on_commit: true,
// reset_on_abort: true,
//
// // blur element on commit/abort...
// blur_on_commit: false,
// blur_on_abort: false,
//
// // restore focus before disabling the editor...
// keep_focus_on_parent: true,
//
// // clear selection on commit/abort...
// clear_selection_on_commit: true,
// clear_selection_on_abort: true,
//
// // If false unhandled key events will not be propagated to
// // parents...
// propagate_unhandled_keys: true,
//
// // If false the element editable state will not be reset to
// // the original when edit is done...
// reset_on_done: true,
//
// // Keys that will abort the edit...
// abort_keys: [
// 'Esc',
// ],
// }
//
// This listens to these events triggerable by user:
// 'edit-commit' - will commit changes, this is passed the
// new text just edited.
// 'edit-abort' - will reset field, this is passed the
// original text before the edit.
//
// These events get passed the relevant text, but the element is
// likely to be already reset to a different state, to get the
// element before any state change is started use one of the
// following variants:
// 'edit-committing' - triggered within 'edit-commit' but before
// anything is changed, gets passed the final
// text (same as 'edit-commit')
// 'edit-aborting' - triggered within 'edit-abort' but before
// anything is changed, gets passed the
// original text value (same as 'edit-abort')
//
// This will try and preserve element content DOM when resetting.
//
//
// NOTE: removing tabindex will reset focus, so this will attempt to
// focus the first [tabindex] element up the tree...
//
// XXX add option to select the element on start or just focus it...
// .activate: 'select' | true | false
// XXX should we just use form elements???
// ...it's a trade-off, here we add editing functionality and fight
// a bit the original function, in an input we'll need to fight part
// of the editing functionality and add our own navigation...
// XXX move this to a more generic spot...
jQuery.fn.makeEditable = function(options){
var that = this
if(options == false){
this
.removeAttr('contenteditable')
.removeAttr('tabindex')
.removeClass('editable-field')
var events = this.data('editable-field-events')
for(var e in events){
this.off(e, events[e]) }
this.removeData('editable-field-events')
return this }
options = Object.assign({
// defaults...
activate: false,
multiline: false,
accept_on_enter: true,
clear_on_edit: false,
reset_on_commit: true,
reset_on_abort: true,
blur_on_commit: false,
blur_on_abort: false,
keep_focus_on_parent: true,
clear_selection_on_commit: true,
clear_selection_on_abort: true,
propagate_unhandled_keys: true,
reset_on_done: true,
abort_keys: ['Esc'],
}, options || {})
var original_text = this[0].innerText
var original_dom = document.createDocumentFragment()
this[0].childNodes
.forEach(function(node){
original_dom.appendChild(node.cloneNode(true)) })
var resetOriginal = function(){
//that.text(original_text)
that[0].innerHTML = ''
that[0].appendChild(original_dom.cloneNode(true)) }
this.prop('contenteditable', true)
options.activate
&& options.clear_on_edit
// XXX this for some reason breaks on click...
&& this.text('')
// NOTE: this will also focus the element...
options.activate
&& this.selectText()
// do not setup handlers more than once...
if(!this.hasClass('editable-field')){
var events = {}
this
// make the element focusable and selectable...
.attr('tabindex', '0')
.addClass('editable-field')
.keydown(events.keydown = function(in_evt){
var evt = window.event || in_evt
if(!that.prop('contenteditable')){
return }
evt.stopPropagation()
var c = getCaretOffset(this)
var collapsed = selectionCollapsed(this)
var n = keyboard.code2key(evt.keyCode)
// abort...
if((options.abort_keys || []).indexOf(n) >= 0){
that.trigger('edit-abort', original_text)
// done -- single line...
} else if(n == 'Enter'
&& !options.multiline){
evt.preventDefault()
that.trigger('edit-commit',
that.length == 1 ?
that[0].innerText
: that.toArray().map(function(e){ return e.innerText }))
// done -- multi-line...
} else if(options.multiline
&& n == 'Enter'
&& (options.accept_on_enter ?
!(evt.ctrlKey || evt.shiftKey || evt.metaKey)
: (evt.ctrlKey || evt.shiftKey || evt.metaKey)) ){
evt.preventDefault()
that.trigger('edit-commit',
that.length == 1 ?
that[0].innerText
: that.toArray().map(function(e){ return e.innerText }))
// multi-line keep keys...
} else if(options.multiline
&& options.accept_on_enter ?
(n == 'Enter'
&& (evt.ctrlKey || evt.shiftKey || evt.metaKey))
: n == 'Enter'){
return
// multi-line arrow keys -- keep key iff not at first/last position...
} else if(options.multiline
&& n == 'Up'
&& (c > 0 || !collapsed)){
return
} else if(options.multiline
&& n == 'Down'
&& (c < $(this).text().length || !collapsed)){
return
} else if(n == 'Up' || n == 'Down'){
evt.preventDefault()
that.trigger('edit-commit',
that.length == 1 ?
that[0].innerText
: that.toArray().map(function(e){ return e.innerText }))
// continue handling...
} else if(options.propagate_unhandled_keys){
// NOTE: jQuery can't reuse browser events, this
// we need to pass a jq event/proxy here...
$(this).parent().trigger(in_evt || evt) }
})
.blur(events.blur = function(){
window.getSelection().removeAllRanges() })
.on('focus click', events['focus click'] = function(evt){
evt.stopPropagation()
options.clear_on_edit
&& $(this)
.text('')
.selectText() })
// user triggerable events...
.on('edit-abort', events['edit-abort'] = function(evt, text){
that.trigger('edit-aborting', text)
options.clear_selection_on_abort
&& window.getSelection().removeAllRanges()
// reset original value...
options.reset_on_abort
&& resetOriginal()
options.blur_on_abort
&& this.blur()
// restore focus on parent...
options.keep_focus_on_parent
&& that.parents('[tabindex]').first().focus()
options.reset_on_done
&& that.makeEditable(false) })
.on('edit-commit', events['edit-commit'] = function(evt, text){
that.trigger('edit-committing', text)
options.clear_selection_on_commit
&& window.getSelection().removeAllRanges()
// reset original value...
options.reset_on_commit
&& resetOriginal()
options.blur_on_commit
&& this.blur()
// restore focus on parent...
options.keep_focus_on_parent
&& that.parents('[tabindex]').first().focus()
options.reset_on_done
&& that.makeEditable(false) })
this.data('editable-field-events', events) }
return this }
}
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })