mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 07:58:11 -05:00
Compare commits
11 Commits
disable-re
...
alan/firef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45621fdc8e | ||
|
|
819446a5a5 | ||
|
|
a8c027e7c1 | ||
|
|
87f16e3c3b | ||
|
|
d36c88dab4 | ||
|
|
31cc704ee2 | ||
|
|
6660d5c716 | ||
|
|
9f068e9bbe | ||
|
|
336db8e5f5 | ||
|
|
fa5b43bc85 | ||
|
|
b3ed915db2 |
2
NEWS.md
2
NEWS.md
@@ -41,6 +41,8 @@ shiny 1.1.0.9001
|
||||
|
||||
* Fixed [#2162](https://github.com/rstudio/shiny/issues/2162): `selectInput` was sending spurious duplicate values to the server when using backspace. Thanks, @sada1993! [#2187](https://github.com/rstudio/shiny/pull/2187)
|
||||
|
||||
* Fixed [#2142](https://github.com/rstudio/shiny/issues/2142): Dropping files on `fileInput`s stopped working on recent releases of Firefox. Thanks @dmenne for reporting! [#2203](https://github.com/rstudio/shiny/pull/2203)
|
||||
|
||||
### Documentation Updates
|
||||
|
||||
* Addressed [#1864](https://github.com/rstudio/shiny/issues/1864) by changing `optgroup` documentation to use `list` instead of `c`. ([#2084](https://github.com/rstudio/shiny/pull/2084))
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||
|
||||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
@@ -323,227 +321,6 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
if (op === "==") return diff === 0;else if (op === ">=") return diff >= 0;else if (op === ">") return diff > 0;else if (op === "<=") return diff <= 0;else if (op === "<") return diff < 0;else throw "Unknown operator: " + op;
|
||||
};
|
||||
|
||||
// multimethod: Creates functions — "multimethods" — that are polymorphic on one
|
||||
// or more of their arguments.
|
||||
//
|
||||
// Multimethods can take any number of arguments. Arguments are passed to an
|
||||
// applicable function or "method", returning its result. By default, if no
|
||||
// method was applicable, an exception is thrown.
|
||||
//
|
||||
// Methods are searched in the order that they were added, and the first
|
||||
// applicable method found is the one used.
|
||||
//
|
||||
// A method is applicable when the "dispatch value" associated with it
|
||||
// corresponds to the value returned by the dispatch function. The dispatch
|
||||
// function defaults to the value of the first argument passed to the
|
||||
// multimethod.
|
||||
//
|
||||
// The correspondence between the value returned by the dispatch function and
|
||||
// any method's dispatch value is determined by the test function, which is
|
||||
// user-definable and defaults to `equal` or deep equality.
|
||||
//
|
||||
// # Chainable Functions
|
||||
//
|
||||
// The function returned by `multimethod()` exposes functions as properties.
|
||||
// These functions generally return the multimethod, and so can be chained.
|
||||
//
|
||||
// - dispatch([function newDispatch]): Sets the dispatch function. The dispatch
|
||||
// function can take any number of arguments, but must return a dispatch
|
||||
// value. The default dispatch function returns the first argument passed to
|
||||
// the multimethod.
|
||||
//
|
||||
// - test([function newTest]): Sets the test function. The test function takes
|
||||
// two arguments: the dispatch value produced by the dispatch function, and
|
||||
// the dispatch value associated with some method. It must return a boolean
|
||||
// indicating whether or not to select the method. The default test function
|
||||
// is `equal`.
|
||||
//
|
||||
// - when(object dispatchVal, function method): Adds a new dispatch value/method
|
||||
// combination.
|
||||
//
|
||||
// - whenAny(array<object> dispatchVals, function method): Like `when`, but
|
||||
// associates the method with every dispatch value in the `dispatchVals`
|
||||
// array.
|
||||
//
|
||||
// - else(function newDefaultMethod): Sets the default function. This function
|
||||
// is invoked when no methods apply. If left unset, the multimethod will throw
|
||||
// an exception when no methods are applicable.
|
||||
//
|
||||
// - clone(): Returns a new, functionally-equivalent multimethod. This is a way
|
||||
// to extend an existing multimethod in a local context — such as inside a
|
||||
// function — without modifying the original. NOTE: The array of methods is
|
||||
// copied, but the dispatch values themselves are not.
|
||||
//
|
||||
// # Self-reference
|
||||
//
|
||||
// The multimethod function can be obtained inside its method bodies without
|
||||
// referring to it by name.
|
||||
//
|
||||
// This makes it possible for one method to call another, or to pass the
|
||||
// multimethod to other functions as a callback from within methods.
|
||||
//
|
||||
// The mechanism is: the multimethod itself is bound as `this` to methods when
|
||||
// they are called. Since arrow functions cannot be bound to objects, **self-reference
|
||||
// is only possible within methods created using the `function` keyword**.
|
||||
//
|
||||
// # Tail recursion
|
||||
//
|
||||
// A method can call itself in a way that will not overflow the stack by using
|
||||
// `this.recur`.
|
||||
//
|
||||
// `this.recur` is a function available in methods created using `function`.
|
||||
// When the return value of a call to `this.recur` is returned by a method, the
|
||||
// arguments that were supplied to `this.recur` are used to call the
|
||||
// multimethod.
|
||||
//
|
||||
// # Examples
|
||||
//
|
||||
// Handling events:
|
||||
//
|
||||
// var handle = multimethod()
|
||||
// .dispatch(e => [e.target.tagName.toLowerCase(), e.type])
|
||||
// .when(["h1", "click"], e => "you clicked on an h1")
|
||||
// .when(["p", "mouseover"], e => "you moused over a p"})
|
||||
// .else(e => {
|
||||
// let tag = e.target.tagName.toLowerCase();
|
||||
// return `you did ${e.type} to an ${tag}`;
|
||||
// });
|
||||
//
|
||||
// $(document).on("click mouseover mouseup mousedown", e => console.log(handle(e)))
|
||||
//
|
||||
// Self-calls:
|
||||
//
|
||||
// var demoSelfCall = multimethod()
|
||||
// .when(0, function(n) {
|
||||
// this(1);
|
||||
// })
|
||||
// .when(1, function(n) {
|
||||
// doSomething(this);
|
||||
// })
|
||||
// .when(2, _ => console.log("tada"));
|
||||
//
|
||||
// Using (abusing?) the test function:
|
||||
//
|
||||
// var fizzBuzz = multimethod()
|
||||
// .test((x, divs) => divs.map(d => x % d === 0).every(Boolean))
|
||||
// .when([3, 5], x => "FizzBuzz")
|
||||
// .when([3], x => "Fizz")
|
||||
// .when([5], x => "Buzz")
|
||||
// .else(x => x);
|
||||
//
|
||||
// for(let i = 0; i <= 100; i++) console.log(fizzBuzz(i));
|
||||
//
|
||||
// Getting carried away with tail recursion:
|
||||
//
|
||||
// var factorial = multimethod()
|
||||
// .when(0, () => 1)
|
||||
// .when(1, (_, prod = 1) => prod)
|
||||
// .else(function(n, prod = 1) {
|
||||
// return this.recur(n-1, n*prod);
|
||||
// });
|
||||
//
|
||||
// var fibonacci = multimethod()
|
||||
// .when(0, (_, a = 0) => a)
|
||||
// .else(function(n, a = 0, b = 1) {
|
||||
// return this.recur(n-1, b, a+b);
|
||||
// });
|
||||
function multimethod() {
|
||||
var dispatch = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function (firstArg) {
|
||||
return firstArg;
|
||||
};
|
||||
var test = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : equal;
|
||||
var defaultMethod = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
||||
var methods = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
|
||||
|
||||
|
||||
var trampolining = false;
|
||||
|
||||
function Sentinel(args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
function trampoline(f) {
|
||||
return function () {
|
||||
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
||||
args[_key2] = arguments[_key2];
|
||||
}
|
||||
|
||||
trampolining = true;
|
||||
var ret = f.apply(invoke, args);
|
||||
while (ret instanceof Sentinel) {
|
||||
ret = f.apply(invoke, ret.args);
|
||||
}trampolining = false;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
var invoke = trampoline(function () {
|
||||
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
|
||||
args[_key3] = arguments[_key3];
|
||||
}
|
||||
|
||||
var dispatchVal = dispatch.apply(null, args);
|
||||
for (var i = 0; i < methods.length; i++) {
|
||||
var _methods$i = _slicedToArray(methods[i], 2);
|
||||
|
||||
var methodVal = _methods$i[0];
|
||||
var methodFn = _methods$i[1];
|
||||
|
||||
if (test(dispatchVal, methodVal)) {
|
||||
return methodFn.apply(invoke, args);
|
||||
}
|
||||
}
|
||||
if (defaultMethod) {
|
||||
return defaultMethod.apply(invoke, args);
|
||||
} else {
|
||||
throw new Error("No method for dispatch value " + dispatchVal);
|
||||
}
|
||||
});
|
||||
|
||||
invoke.recur = function () {
|
||||
for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
|
||||
args[_key4] = arguments[_key4];
|
||||
}
|
||||
|
||||
if (!trampolining) throw new Error("recur can only be called inside a method");
|
||||
return new Sentinel(args);
|
||||
};
|
||||
|
||||
invoke.dispatch = function (newDispatch) {
|
||||
dispatch = newDispatch;
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.test = function (newTest) {
|
||||
test = newTest;
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.when = function (dispatchVal, methodFn) {
|
||||
methods = methods.concat([[dispatchVal, methodFn]]);
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.whenAny = function (dispatchVals, methodFn) {
|
||||
return dispatchVals.reduce(function (self, val) {
|
||||
return invoke.when(val, methodFn);
|
||||
}, invoke);
|
||||
};
|
||||
|
||||
invoke.else = function () {
|
||||
var newDefaultMethod = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
||||
|
||||
defaultMethod = newDefaultMethod;
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.clone = function () {
|
||||
return multimethod(dispatch, test, defaultMethod, methods.slice());
|
||||
};
|
||||
|
||||
return invoke;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Source file: ../srcjs/browser.js
|
||||
|
||||
@@ -6038,68 +5815,79 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
// This will be used only when restoring a file from a saved state.
|
||||
return 'shiny.file';
|
||||
},
|
||||
_getZone: function _getZone(el) {
|
||||
_zoneOf: function _zoneOf(el) {
|
||||
return $(el).closest("div.input-group");
|
||||
},
|
||||
// This implements draghoverstart/draghoverend events that occur once per
|
||||
// selector, instead of once for every child the way native
|
||||
// dragenter/dragleave do. Inspired by https://gist.github.com/meleyal/3794126
|
||||
_enableDraghover: function _enableDraghover($el) {
|
||||
var ns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
|
||||
|
||||
// Create an empty jQuery collection. This is a set-like data structure that
|
||||
// jQuery normally uses to contain the results of a selection.
|
||||
var collection = $();
|
||||
|
||||
// Attach a dragenter handler to $el and all of its children. When the first
|
||||
// child is entered, trigger a draghoverstart event.
|
||||
$el.on("dragenter.dragHover", function (e) {
|
||||
if (collection.length === 0) {
|
||||
$el.trigger("draghoverstart" + ns, e.originalEvent);
|
||||
}
|
||||
// Every child that has fired dragenter is added to the collection.
|
||||
// Addition is idempotent, which accounts for elements producing dragenter
|
||||
// multiple times.
|
||||
collection = collection.add(e.originalEvent.target);
|
||||
});
|
||||
|
||||
// Attach dragleave and drop handlers to $el and its children. Whenever a
|
||||
// child fires either of these events, remove it from the collection.
|
||||
$el.on("dragleave.dragHover drop.dragHover", function (e) {
|
||||
collection = collection.not(e.originalEvent.target);
|
||||
// When the collection has no elements, all of the children have been
|
||||
// removed, and produce draghoverend event.
|
||||
if (collection.length === 0) {
|
||||
$el.trigger("draghoverend" + ns, e.originalEvent);
|
||||
// This function makes it possible to attach listeners to the dragenter,
|
||||
// dragleave, and drop events of a single element with children. It's not
|
||||
// intuitive to do directly because outer elements fire "dragleave" events
|
||||
// both when the drag leaves the element and when the drag enters a child. To
|
||||
// make it easier, we maintain a count of the elements being dragged across
|
||||
// and trigger 3 new types of event:
|
||||
//
|
||||
// 1. draghover:enter - When a drag enters el and any of its children.
|
||||
// 2. draghover:leave - When the drag leaves el and all of its children.
|
||||
// 3. draghover:drop - When an item is dropped on el or any of its children.
|
||||
_enableDraghover: function _enableDraghover(el) {
|
||||
var $el = $(el),
|
||||
childCounter = 0;
|
||||
$el.on({
|
||||
"dragenter.draghover": function dragenterDraghover(e) {
|
||||
if (childCounter++ === 0) {
|
||||
$el.trigger("draghover:enter", e);
|
||||
}
|
||||
},
|
||||
"dragleave.draghover": function dragleaveDraghover(e) {
|
||||
if (--childCounter === 0) {
|
||||
$el.trigger("draghover:leave", e);
|
||||
}
|
||||
if (childCounter < 0) {
|
||||
console.error("draghover childCounter is negative somehow");
|
||||
}
|
||||
},
|
||||
"dragover.draghover": function dragoverDraghover(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
"drop.draghover": function dropDraghover(e) {
|
||||
childCounter = 0;
|
||||
$el.trigger("draghover:drop", e);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
return $el;
|
||||
},
|
||||
_disableDraghover: function _disableDraghover($el) {
|
||||
$el.off(".dragHover");
|
||||
_disableDraghover: function _disableDraghover(el) {
|
||||
return $(el).off(".draghover");
|
||||
},
|
||||
_ZoneClass: {
|
||||
ACTIVE: "shiny-file-input-active",
|
||||
OVER: "shiny-file-input-over"
|
||||
},
|
||||
_enableDocumentEvents: function _enableDocumentEvents() {
|
||||
var $doc = $("html");
|
||||
var _this2 = this;
|
||||
|
||||
this._enableDraghover($doc);
|
||||
$doc.on({
|
||||
"draghoverstart.fileDrag": function draghoverstartFileDrag(e) {
|
||||
$fileInputs.trigger("showZone.fileDrag");
|
||||
var $doc = $("html");
|
||||
var _ZoneClass = this._ZoneClass;
|
||||
var ACTIVE = _ZoneClass.ACTIVE;
|
||||
var OVER = _ZoneClass.OVER;
|
||||
|
||||
this._enableDraghover($doc).on({
|
||||
"draghover:enter.draghover": function draghoverEnterDraghover(e) {
|
||||
_this2._zoneOf($fileInputs).addClass(ACTIVE);
|
||||
},
|
||||
"draghoverend.fileDrag": function draghoverendFileDrag(e) {
|
||||
$fileInputs.trigger("hideZone.fileDrag");
|
||||
"draghover:leave.draghover": function draghoverLeaveDraghover(e) {
|
||||
_this2._zoneOf($fileInputs).removeClass(ACTIVE);
|
||||
},
|
||||
"dragover.fileDrag drop.fileDrag": function dragoverFileDragDropFileDrag(e) {
|
||||
e.preventDefault();
|
||||
"draghover:drop.draghover": function draghoverDropDraghover(e) {
|
||||
_this2._zoneOf($fileInputs).removeClass(OVER).removeClass(ACTIVE);
|
||||
}
|
||||
});
|
||||
},
|
||||
_disableDocumentEvents: function _disableDocumentEvents() {
|
||||
var $doc = $("html");
|
||||
|
||||
$doc.off(".fileDrag");
|
||||
$doc.off(".draghover");
|
||||
this._disableDraghover($doc);
|
||||
},
|
||||
_zoneEvents: ["showZone.fileDrag", "hideZone.fileDrag", "draghoverstart.zone", "draghoverend.zone", "drop"].join(" "),
|
||||
_canSetFiles: function _canSetFiles(fileList) {
|
||||
var testEl = document.createElement("input");
|
||||
testEl.type = "file";
|
||||
@@ -6127,10 +5915,13 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
// (Chrome, Safari)
|
||||
$el.val("");
|
||||
el.files = e.originalEvent.dataTransfer.files;
|
||||
// Recent versions of Firefox (57+, or "Quantum" and beyond) don't seem to
|
||||
// automatically trigger a change event, so we trigger one manually here.
|
||||
// On browsers that do trigger change, this operation appears to be
|
||||
// idempotent, as el.files doesn't change between events.
|
||||
$el.trigger("change");
|
||||
}
|
||||
},
|
||||
_activeClass: "shiny-file-input-active",
|
||||
_overClass: "shiny-file-input-over",
|
||||
_isIE9: function _isIE9() {
|
||||
try {
|
||||
return window.navigator.userAgent.match(/MSIE 9\./) && true || false;
|
||||
@@ -6139,9 +5930,9 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
}
|
||||
},
|
||||
subscribe: function subscribe(el, callback) {
|
||||
var _this2 = this;
|
||||
var _this3 = this;
|
||||
|
||||
var $el = $(el);
|
||||
$(el).on("change.fileInputBinding", uploadFiles);
|
||||
// Here we try to set up the necessary events for Drag and Drop ("DnD") on
|
||||
// every browser except IE9. We specifically exclude IE9 because it's one
|
||||
// browser that supports just enough of the functionality we need to be
|
||||
@@ -6151,89 +5942,38 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
// supported based on this highlighting would be incorrect.
|
||||
if (!this._isIE9()) {
|
||||
(function () {
|
||||
var $zone = _this2._getZone(el),
|
||||
getState = function getState() {
|
||||
return $el.data("state");
|
||||
},
|
||||
setState = function setState(newState) {
|
||||
return $el.data("state", newState);
|
||||
},
|
||||
transition = multimethod().dispatch(function (e) {
|
||||
return [getState(), e.type];
|
||||
}).when(["plain", "showZone"], function (e) {
|
||||
$zone.removeClass(_this2._overClass);
|
||||
$zone.addClass(_this2._activeClass);
|
||||
setState("activated");
|
||||
}).when(["activated", "hideZone"], function (e) {
|
||||
$zone.removeClass(_this2._overClass);
|
||||
$zone.removeClass(_this2._activeClass);
|
||||
setState("plain");
|
||||
}).when(["activated", "draghoverstart"], function (e) {
|
||||
$zone.addClass(_this2._overClass);
|
||||
$zone.removeClass(_this2._activeClass);
|
||||
setState("over");
|
||||
})
|
||||
// A "drop" event always coincides with a "draghoverend" event. Since
|
||||
// we handle all draghoverend events the same way, by clearing our
|
||||
// over-style and reverting to "activated" state, we only need to
|
||||
// worry about handling the file upload itself here.
|
||||
.when(["over", "drop"], function (e) {
|
||||
_this2._handleDrop(e, el);
|
||||
// State change taken care of by ["over", "draghoverend"] handler.
|
||||
}).when(["over", "draghoverend"], function (e) {
|
||||
$zone.removeClass(_this2._overClass);
|
||||
$zone.addClass(_this2._activeClass);
|
||||
setState("activated");
|
||||
})
|
||||
// This next case happens when the window (like Finder) that a file is
|
||||
// being dragged from occludes the browser window, and the dragged
|
||||
// item first enters the page over a drop zone instead of entering
|
||||
// through a none-zone element.
|
||||
//
|
||||
// The dragenter event that caused this draghoverstart to occur will
|
||||
// bubble to the document, where it will cause a showZone event to be
|
||||
// fired, and drop zones will activate and their states will
|
||||
// transition to "activated".
|
||||
//
|
||||
// We schedule a function to be run *after* that happens, using
|
||||
// setTimeout. The function we schedule will set the current element's
|
||||
// state to "over", preparing us to deal with a subsequent
|
||||
// "draghoverend".
|
||||
.when(["plain", "draghoverstart"], function (e) {
|
||||
window.setTimeout(function () {
|
||||
$zone.addClass(_this2._overClass);
|
||||
$zone.removeClass(_this2._activeClass);
|
||||
setState("over");
|
||||
}, 0);
|
||||
}).else(function (e) {
|
||||
console.log("fileInput DnD unhandled transition", getState(), e.type, e);
|
||||
});
|
||||
|
||||
if ($fileInputs.length === 0) _this2._enableDocumentEvents();
|
||||
setState("plain");
|
||||
$zone.on(_this2._zoneEvents, transition);
|
||||
if ($fileInputs.length === 0) _this3._enableDocumentEvents();
|
||||
$fileInputs = $fileInputs.add(el);
|
||||
_this2._enableDraghover($zone, ".zone");
|
||||
var $zone = _this3._zoneOf(el);
|
||||
var OVER = _this3._ZoneClass.OVER;
|
||||
|
||||
_this3._enableDraghover($zone).on({
|
||||
"draghover:enter.draghover": function draghoverEnterDraghover(e) {
|
||||
$zone.addClass(OVER);
|
||||
},
|
||||
"draghover:leave.draghover": function draghoverLeaveDraghover(e) {
|
||||
$zone.removeClass(OVER);
|
||||
// Prevent this event from bubbling to the document handler,
|
||||
// which would deactivate all zones.
|
||||
e.stopPropagation();
|
||||
},
|
||||
"draghover:drop.draghover": function draghoverDropDraghover(e, dropEvent) {
|
||||
_this3._handleDrop(dropEvent, el);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
$el.on("change.fileInputBinding", uploadFiles);
|
||||
},
|
||||
|
||||
unsubscribe: function unsubscribe(el) {
|
||||
var $el = $(el),
|
||||
$zone = this._getZone(el);
|
||||
$zone = this._zoneOf(el);
|
||||
|
||||
$el.removeData("state");
|
||||
|
||||
$zone.removeClass(this._overClass);
|
||||
$zone.removeClass(this._activeClass);
|
||||
$zone.removeClass(this._ZoneClass.OVER).removeClass(this._ZoneClass.ACTIVE);
|
||||
|
||||
this._disableDraghover($zone);
|
||||
|
||||
// Clean up local event handlers.
|
||||
$el.off(".fileInputBinding");
|
||||
$zone.off(this._zoneEvents);
|
||||
$zone.off(".draghover");
|
||||
|
||||
// Remove el from list of inputs and (maybe) clean up global event handlers.
|
||||
$fileInputs = $fileInputs.not(el);
|
||||
|
||||
File diff suppressed because one or more lines are too long
6
inst/www/shared/shiny.min.js
vendored
6
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -309,72 +309,77 @@ $.extend(fileInputBinding, {
|
||||
// This will be used only when restoring a file from a saved state.
|
||||
return 'shiny.file';
|
||||
},
|
||||
_getZone: function(el) {
|
||||
_zoneOf: function(el) {
|
||||
return $(el).closest("div.input-group");
|
||||
},
|
||||
// This implements draghoverstart/draghoverend events that occur once per
|
||||
// selector, instead of once for every child the way native
|
||||
// dragenter/dragleave do. Inspired by https://gist.github.com/meleyal/3794126
|
||||
_enableDraghover: function($el, ns = "") {
|
||||
// Create an empty jQuery collection. This is a set-like data structure that
|
||||
// jQuery normally uses to contain the results of a selection.
|
||||
let collection = $();
|
||||
|
||||
// Attach a dragenter handler to $el and all of its children. When the first
|
||||
// child is entered, trigger a draghoverstart event.
|
||||
$el.on("dragenter.dragHover", e => {
|
||||
if (collection.length === 0) {
|
||||
$el.trigger("draghoverstart" + ns, e.originalEvent);
|
||||
}
|
||||
// Every child that has fired dragenter is added to the collection.
|
||||
// Addition is idempotent, which accounts for elements producing dragenter
|
||||
// multiple times.
|
||||
collection = collection.add(e.originalEvent.target);
|
||||
});
|
||||
|
||||
// Attach dragleave and drop handlers to $el and its children. Whenever a
|
||||
// child fires either of these events, remove it from the collection.
|
||||
$el.on("dragleave.dragHover drop.dragHover", e => {
|
||||
collection = collection.not(e.originalEvent.target);
|
||||
// When the collection has no elements, all of the children have been
|
||||
// removed, and produce draghoverend event.
|
||||
if (collection.length === 0) {
|
||||
$el.trigger("draghoverend" + ns, e.originalEvent);
|
||||
}
|
||||
});
|
||||
},
|
||||
_disableDraghover: function($el) {
|
||||
$el.off(".dragHover");
|
||||
},
|
||||
_enableDocumentEvents: function() {
|
||||
let $doc = $("html");
|
||||
|
||||
this._enableDraghover($doc);
|
||||
$doc.on({
|
||||
"draghoverstart.fileDrag": e => {
|
||||
$fileInputs.trigger("showZone.fileDrag");
|
||||
// This function makes it possible to attach listeners to the dragenter,
|
||||
// dragleave, and drop events of a single element with children. It's not
|
||||
// intuitive to do directly because outer elements fire "dragleave" events
|
||||
// both when the drag leaves the element and when the drag enters a child. To
|
||||
// make it easier, we maintain a count of the elements being dragged across
|
||||
// and trigger 3 new types of event:
|
||||
//
|
||||
// 1. draghover:enter - When a drag enters el and any of its children.
|
||||
// 2. draghover:leave - When the drag leaves el and all of its children.
|
||||
// 3. draghover:drop - When an item is dropped on el or any of its children.
|
||||
_enableDraghover: function(el) {
|
||||
let $el = $(el),
|
||||
childCounter = 0;
|
||||
$el.on({
|
||||
"dragenter.draghover": e => {
|
||||
if (childCounter++ === 0) {
|
||||
$el.trigger("draghover:enter", e);
|
||||
}
|
||||
},
|
||||
"draghoverend.fileDrag": e => {
|
||||
$fileInputs.trigger("hideZone.fileDrag");
|
||||
"dragleave.draghover": e => {
|
||||
if (--childCounter === 0) {
|
||||
$el.trigger("draghover:leave", e);
|
||||
}
|
||||
if (childCounter < 0) {
|
||||
console.error("draghover childCounter is negative somehow");
|
||||
}
|
||||
},
|
||||
"dragover.fileDrag drop.fileDrag": e => {
|
||||
"dragover.draghover": e => {
|
||||
e.preventDefault();
|
||||
},
|
||||
"drop.draghover": e => {
|
||||
childCounter = 0;
|
||||
$el.trigger("draghover:drop", e);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
return $el;
|
||||
},
|
||||
_disableDraghover: function(el) {
|
||||
return $(el).off(".draghover");
|
||||
},
|
||||
_ZoneClass: {
|
||||
ACTIVE: "shiny-file-input-active",
|
||||
OVER: "shiny-file-input-over"
|
||||
},
|
||||
_enableDocumentEvents: function() {
|
||||
let $doc = $("html"),
|
||||
{ACTIVE, OVER} = this._ZoneClass;
|
||||
this._enableDraghover($doc)
|
||||
.on({
|
||||
"draghover:enter.draghover": e => {
|
||||
this._zoneOf($fileInputs).addClass(ACTIVE);
|
||||
},
|
||||
"draghover:leave.draghover": e => {
|
||||
this._zoneOf($fileInputs).removeClass(ACTIVE);
|
||||
},
|
||||
"draghover:drop.draghover": e => {
|
||||
this._zoneOf($fileInputs)
|
||||
.removeClass(OVER)
|
||||
.removeClass(ACTIVE);
|
||||
}
|
||||
});
|
||||
},
|
||||
_disableDocumentEvents: function() {
|
||||
let $doc = $("html");
|
||||
|
||||
$doc.off(".fileDrag");
|
||||
$doc.off(".draghover");
|
||||
this._disableDraghover($doc);
|
||||
},
|
||||
_zoneEvents: [
|
||||
"showZone.fileDrag",
|
||||
"hideZone.fileDrag",
|
||||
"draghoverstart.zone",
|
||||
"draghoverend.zone",
|
||||
"drop"
|
||||
].join(" "),
|
||||
_canSetFiles: function(fileList) {
|
||||
var testEl = document.createElement("input");
|
||||
testEl.type = "file";
|
||||
@@ -402,10 +407,13 @@ $.extend(fileInputBinding, {
|
||||
// (Chrome, Safari)
|
||||
$el.val("");
|
||||
el.files = e.originalEvent.dataTransfer.files;
|
||||
// Recent versions of Firefox (57+, or "Quantum" and beyond) don't seem to
|
||||
// automatically trigger a change event, so we trigger one manually here.
|
||||
// On browsers that do trigger change, this operation appears to be
|
||||
// idempotent, as el.files doesn't change between events.
|
||||
$el.trigger("change");
|
||||
}
|
||||
},
|
||||
_activeClass: "shiny-file-input-active",
|
||||
_overClass: "shiny-file-input-over",
|
||||
_isIE9: function() {
|
||||
try {
|
||||
return (window.navigator.userAgent.match(/MSIE 9\./) && true) || false;
|
||||
@@ -414,7 +422,7 @@ $.extend(fileInputBinding, {
|
||||
}
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
let $el = $(el);
|
||||
$(el).on("change.fileInputBinding", uploadFiles);
|
||||
// Here we try to set up the necessary events for Drag and Drop ("DnD") on
|
||||
// every browser except IE9. We specifically exclude IE9 because it's one
|
||||
// browser that supports just enough of the functionality we need to be
|
||||
@@ -423,88 +431,39 @@ $.extend(fileInputBinding, {
|
||||
// support the FileList object though, so the user's expectation that DnD is
|
||||
// supported based on this highlighting would be incorrect.
|
||||
if (!this._isIE9()) {
|
||||
let $zone = this._getZone(el),
|
||||
getState = () => $el.data("state"),
|
||||
setState = (newState) => $el.data("state", newState),
|
||||
transition = multimethod()
|
||||
.dispatch(e => [getState(), e.type])
|
||||
.when(["plain", "showZone"], e => {
|
||||
$zone.removeClass(this._overClass);
|
||||
$zone.addClass(this._activeClass);
|
||||
setState("activated");
|
||||
})
|
||||
.when(["activated", "hideZone"], e => {
|
||||
$zone.removeClass(this._overClass);
|
||||
$zone.removeClass(this._activeClass);
|
||||
setState("plain");
|
||||
})
|
||||
.when(["activated", "draghoverstart"], e => {
|
||||
$zone.addClass(this._overClass);
|
||||
$zone.removeClass(this._activeClass);
|
||||
setState("over");
|
||||
})
|
||||
// A "drop" event always coincides with a "draghoverend" event. Since
|
||||
// we handle all draghoverend events the same way, by clearing our
|
||||
// over-style and reverting to "activated" state, we only need to
|
||||
// worry about handling the file upload itself here.
|
||||
.when(["over", "drop"], e => {
|
||||
this._handleDrop(e, el);
|
||||
// State change taken care of by ["over", "draghoverend"] handler.
|
||||
})
|
||||
.when(["over", "draghoverend"], e => {
|
||||
$zone.removeClass(this._overClass);
|
||||
$zone.addClass(this._activeClass);
|
||||
setState("activated");
|
||||
})
|
||||
// This next case happens when the window (like Finder) that a file is
|
||||
// being dragged from occludes the browser window, and the dragged
|
||||
// item first enters the page over a drop zone instead of entering
|
||||
// through a none-zone element.
|
||||
//
|
||||
// The dragenter event that caused this draghoverstart to occur will
|
||||
// bubble to the document, where it will cause a showZone event to be
|
||||
// fired, and drop zones will activate and their states will
|
||||
// transition to "activated".
|
||||
//
|
||||
// We schedule a function to be run *after* that happens, using
|
||||
// setTimeout. The function we schedule will set the current element's
|
||||
// state to "over", preparing us to deal with a subsequent
|
||||
// "draghoverend".
|
||||
.when(["plain", "draghoverstart"], e => {
|
||||
window.setTimeout(() => {
|
||||
$zone.addClass(this._overClass);
|
||||
$zone.removeClass(this._activeClass);
|
||||
setState("over");
|
||||
}, 0);
|
||||
})
|
||||
.else(e => {
|
||||
console.log("fileInput DnD unhandled transition", getState(), e.type, e);
|
||||
});
|
||||
|
||||
if ($fileInputs.length === 0) this._enableDocumentEvents();
|
||||
setState("plain");
|
||||
$zone.on(this._zoneEvents, transition);
|
||||
$fileInputs = $fileInputs.add(el);
|
||||
this._enableDraghover($zone, ".zone");
|
||||
let $zone = this._zoneOf(el),
|
||||
{OVER} = this._ZoneClass;
|
||||
this._enableDraghover($zone)
|
||||
.on({
|
||||
"draghover:enter.draghover": e => {
|
||||
$zone.addClass(OVER);
|
||||
},
|
||||
"draghover:leave.draghover": e => {
|
||||
$zone.removeClass(OVER);
|
||||
// Prevent this event from bubbling to the document handler,
|
||||
// which would deactivate all zones.
|
||||
e.stopPropagation();
|
||||
},
|
||||
"draghover:drop.draghover": (e, dropEvent) => {
|
||||
this._handleDrop(dropEvent, el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$el.on("change.fileInputBinding", uploadFiles);
|
||||
},
|
||||
|
||||
unsubscribe: function(el) {
|
||||
let $el = $(el),
|
||||
$zone = this._getZone(el);
|
||||
$zone = this._zoneOf(el);
|
||||
|
||||
$el.removeData("state");
|
||||
|
||||
$zone.removeClass(this._overClass);
|
||||
$zone.removeClass(this._activeClass);
|
||||
$zone
|
||||
.removeClass(this._ZoneClass.OVER)
|
||||
.removeClass(this._ZoneClass.ACTIVE);
|
||||
|
||||
this._disableDraghover($zone);
|
||||
|
||||
// Clean up local event handlers.
|
||||
$el.off(".fileInputBinding");
|
||||
$zone.off(this._zoneEvents);
|
||||
$zone.off(".draghover");
|
||||
|
||||
// Remove el from list of inputs and (maybe) clean up global event handlers.
|
||||
$fileInputs = $fileInputs.not(el);
|
||||
|
||||
196
srcjs/utils.js
196
srcjs/utils.js
@@ -326,199 +326,3 @@ exports.compareVersion = function(a, op, b) {
|
||||
else if (op === "<") return (diff < 0);
|
||||
else throw `Unknown operator: ${op}`;
|
||||
};
|
||||
|
||||
|
||||
// multimethod: Creates functions — "multimethods" — that are polymorphic on one
|
||||
// or more of their arguments.
|
||||
//
|
||||
// Multimethods can take any number of arguments. Arguments are passed to an
|
||||
// applicable function or "method", returning its result. By default, if no
|
||||
// method was applicable, an exception is thrown.
|
||||
//
|
||||
// Methods are searched in the order that they were added, and the first
|
||||
// applicable method found is the one used.
|
||||
//
|
||||
// A method is applicable when the "dispatch value" associated with it
|
||||
// corresponds to the value returned by the dispatch function. The dispatch
|
||||
// function defaults to the value of the first argument passed to the
|
||||
// multimethod.
|
||||
//
|
||||
// The correspondence between the value returned by the dispatch function and
|
||||
// any method's dispatch value is determined by the test function, which is
|
||||
// user-definable and defaults to `equal` or deep equality.
|
||||
//
|
||||
// # Chainable Functions
|
||||
//
|
||||
// The function returned by `multimethod()` exposes functions as properties.
|
||||
// These functions generally return the multimethod, and so can be chained.
|
||||
//
|
||||
// - dispatch([function newDispatch]): Sets the dispatch function. The dispatch
|
||||
// function can take any number of arguments, but must return a dispatch
|
||||
// value. The default dispatch function returns the first argument passed to
|
||||
// the multimethod.
|
||||
//
|
||||
// - test([function newTest]): Sets the test function. The test function takes
|
||||
// two arguments: the dispatch value produced by the dispatch function, and
|
||||
// the dispatch value associated with some method. It must return a boolean
|
||||
// indicating whether or not to select the method. The default test function
|
||||
// is `equal`.
|
||||
//
|
||||
// - when(object dispatchVal, function method): Adds a new dispatch value/method
|
||||
// combination.
|
||||
//
|
||||
// - whenAny(array<object> dispatchVals, function method): Like `when`, but
|
||||
// associates the method with every dispatch value in the `dispatchVals`
|
||||
// array.
|
||||
//
|
||||
// - else(function newDefaultMethod): Sets the default function. This function
|
||||
// is invoked when no methods apply. If left unset, the multimethod will throw
|
||||
// an exception when no methods are applicable.
|
||||
//
|
||||
// - clone(): Returns a new, functionally-equivalent multimethod. This is a way
|
||||
// to extend an existing multimethod in a local context — such as inside a
|
||||
// function — without modifying the original. NOTE: The array of methods is
|
||||
// copied, but the dispatch values themselves are not.
|
||||
//
|
||||
// # Self-reference
|
||||
//
|
||||
// The multimethod function can be obtained inside its method bodies without
|
||||
// referring to it by name.
|
||||
//
|
||||
// This makes it possible for one method to call another, or to pass the
|
||||
// multimethod to other functions as a callback from within methods.
|
||||
//
|
||||
// The mechanism is: the multimethod itself is bound as `this` to methods when
|
||||
// they are called. Since arrow functions cannot be bound to objects, **self-reference
|
||||
// is only possible within methods created using the `function` keyword**.
|
||||
//
|
||||
// # Tail recursion
|
||||
//
|
||||
// A method can call itself in a way that will not overflow the stack by using
|
||||
// `this.recur`.
|
||||
//
|
||||
// `this.recur` is a function available in methods created using `function`.
|
||||
// When the return value of a call to `this.recur` is returned by a method, the
|
||||
// arguments that were supplied to `this.recur` are used to call the
|
||||
// multimethod.
|
||||
//
|
||||
// # Examples
|
||||
//
|
||||
// Handling events:
|
||||
//
|
||||
// var handle = multimethod()
|
||||
// .dispatch(e => [e.target.tagName.toLowerCase(), e.type])
|
||||
// .when(["h1", "click"], e => "you clicked on an h1")
|
||||
// .when(["p", "mouseover"], e => "you moused over a p"})
|
||||
// .else(e => {
|
||||
// let tag = e.target.tagName.toLowerCase();
|
||||
// return `you did ${e.type} to an ${tag}`;
|
||||
// });
|
||||
//
|
||||
// $(document).on("click mouseover mouseup mousedown", e => console.log(handle(e)))
|
||||
//
|
||||
// Self-calls:
|
||||
//
|
||||
// var demoSelfCall = multimethod()
|
||||
// .when(0, function(n) {
|
||||
// this(1);
|
||||
// })
|
||||
// .when(1, function(n) {
|
||||
// doSomething(this);
|
||||
// })
|
||||
// .when(2, _ => console.log("tada"));
|
||||
//
|
||||
// Using (abusing?) the test function:
|
||||
//
|
||||
// var fizzBuzz = multimethod()
|
||||
// .test((x, divs) => divs.map(d => x % d === 0).every(Boolean))
|
||||
// .when([3, 5], x => "FizzBuzz")
|
||||
// .when([3], x => "Fizz")
|
||||
// .when([5], x => "Buzz")
|
||||
// .else(x => x);
|
||||
//
|
||||
// for(let i = 0; i <= 100; i++) console.log(fizzBuzz(i));
|
||||
//
|
||||
// Getting carried away with tail recursion:
|
||||
//
|
||||
// var factorial = multimethod()
|
||||
// .when(0, () => 1)
|
||||
// .when(1, (_, prod = 1) => prod)
|
||||
// .else(function(n, prod = 1) {
|
||||
// return this.recur(n-1, n*prod);
|
||||
// });
|
||||
//
|
||||
// var fibonacci = multimethod()
|
||||
// .when(0, (_, a = 0) => a)
|
||||
// .else(function(n, a = 0, b = 1) {
|
||||
// return this.recur(n-1, b, a+b);
|
||||
// });
|
||||
function multimethod(dispatch = (firstArg) => firstArg,
|
||||
test = equal,
|
||||
defaultMethod = null,
|
||||
methods = []) {
|
||||
|
||||
var trampolining = false;
|
||||
|
||||
function Sentinel (args) { this.args = args; }
|
||||
|
||||
function trampoline(f) {
|
||||
return (...args) => {
|
||||
trampolining = true;
|
||||
var ret = f.apply(invoke, args);
|
||||
while (ret instanceof Sentinel)
|
||||
ret = f.apply(invoke, ret.args);
|
||||
trampolining = false;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
let invoke = trampoline((...args) => {
|
||||
var dispatchVal = dispatch.apply(null, args);
|
||||
for (let i = 0; i < methods.length; i++) {
|
||||
let [methodVal, methodFn] = methods[i];
|
||||
if (test(dispatchVal, methodVal)) {
|
||||
return methodFn.apply(invoke, args);
|
||||
}
|
||||
}
|
||||
if (defaultMethod) {
|
||||
return defaultMethod.apply(invoke, args);
|
||||
} else {
|
||||
throw new Error(`No method for dispatch value ${dispatchVal}`);
|
||||
}
|
||||
});
|
||||
|
||||
invoke.recur = (...args) => {
|
||||
if (!trampolining) throw new Error("recur can only be called inside a method");
|
||||
return new Sentinel(args);
|
||||
};
|
||||
|
||||
invoke.dispatch = (newDispatch) => {
|
||||
dispatch = newDispatch;
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.test = (newTest) => {
|
||||
test = newTest;
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.when = (dispatchVal, methodFn) => {
|
||||
methods = methods.concat([[dispatchVal, methodFn]]);
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.whenAny = (dispatchVals, methodFn) => {
|
||||
return dispatchVals.reduce((self, val) => invoke.when(val, methodFn), invoke);
|
||||
};
|
||||
|
||||
invoke.else = (newDefaultMethod = null) => {
|
||||
defaultMethod = newDefaultMethod;
|
||||
return invoke;
|
||||
};
|
||||
|
||||
invoke.clone = () => {
|
||||
return multimethod(dispatch, test, defaultMethod, methods.slice());
|
||||
};
|
||||
|
||||
return invoke;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user