feat: Avoid throwing errors for shared input/output IDs (#4101)

* refactor: Factor out message display from error handler

* feat: Add custom event for sending a client message

* feat: Report binding validity problem via event instead of throwing error

* feat: Don't need to hide shared input/output message

Now that it's not an error, it's safe to report

* refactor: Move `inDevMode()` logic into error console

* refactor: Rename `.error` --> `.event`

* feat: wrap client error message

It's otherwise hard to tell that the error is scrollable
Plus the scrolling is over the whole message rather than the part that overflows

* feat: always send client console messages to browser console as well

* chore: throw if `shiny:client-message` receives an event that isn't CustomEvent

* feat: Handle status in `showShinyClientMessage()`

* Renamed `showMessageInClientConsole()` to `showShinyClientMessage()` to improve clarity

* Added `status` argument to `showShinyClientMessage()` to allow for different message types

* refactor: Don't throw errors for duplicate IDs

Brings dev mode in line with current "prod" behavior,
where errors aren't thrown for duplciates. In both cases
we still get console or client messages.

* refactor: Clean up `status` inside `checkValidity()`

* refactor: Have `checkValidity()` handle emitting the client console event
This commit is contained in:
Garrick Aden-Buie
2024-12-06 16:00:19 -05:00
committed by GitHub
parent ce6a562a3c
commit e5083f4938
8 changed files with 486 additions and 323 deletions

View File

@@ -22,6 +22,8 @@
* Fixed a bug with stack trace capturing that caused reactives with very long async promise chains (hundreds/thousands of steps) to become extremely slow. Chains this long are unlikely to be written by hand, but {coro} async generators and {elmer} async streaming were easily creating problematically long chains. (#4155)
* Duplicate input and output IDs -- e.g. using `"debug"` for two inputs or two outputs -- or shared IDs -- e.g. using `"debug"` as the `inputID` for an input and an output -- now result in a console warning message, but not an error. When `devmode()` is enabled, an informative message is shown in the Shiny Client Console. We recommend all Shiny devs enable `devmode()` when developing Shiny apps locally. (#4101)
* Updating the choices of a `selectizeInput()` via `updateSelectizeInput()` with `server = TRUE` no longer retains the selected choice as a deselected option if the current value is not part of the new choices. (@dvg-p4 #4142)
# shiny 1.9.1

View File

@@ -4551,18 +4551,6 @@
}
});
// node_modules/core-js/internals/define-built-ins.js
var require_define_built_ins = __commonJS({
"node_modules/core-js/internals/define-built-ins.js": function(exports, module) {
var defineBuiltIn6 = require_define_built_in();
module.exports = function(target, src, options) {
for (var key in src)
defineBuiltIn6(target, key, src[key], options);
return target;
};
}
});
// node_modules/core-js/internals/collection.js
var require_collection = __commonJS({
"node_modules/core-js/internals/collection.js": function(exports, module) {
@@ -4666,6 +4654,221 @@
}
});
// node_modules/core-js/internals/define-built-ins.js
var require_define_built_ins = __commonJS({
"node_modules/core-js/internals/define-built-ins.js": function(exports, module) {
var defineBuiltIn6 = require_define_built_in();
module.exports = function(target, src, options) {
for (var key in src)
defineBuiltIn6(target, key, src[key], options);
return target;
};
}
});
// node_modules/core-js/internals/collection-strong.js
var require_collection_strong = __commonJS({
"node_modules/core-js/internals/collection-strong.js": function(exports, module) {
"use strict";
var create3 = require_object_create();
var defineBuiltInAccessor4 = require_define_built_in_accessor();
var defineBuiltIns = require_define_built_ins();
var bind2 = require_function_bind_context();
var anInstance = require_an_instance();
var isNullOrUndefined5 = require_is_null_or_undefined();
var iterate2 = require_iterate();
var defineIterator2 = require_iterator_define();
var createIterResultObject2 = require_create_iter_result_object();
var setSpecies3 = require_set_species();
var DESCRIPTORS10 = require_descriptors();
var fastKey = require_internal_metadata().fastKey;
var InternalStateModule2 = require_internal_state();
var setInternalState2 = InternalStateModule2.set;
var internalStateGetterFor = InternalStateModule2.getterFor;
module.exports = {
getConstructor: function(wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER) {
var Constructor = wrapper(function(that, iterable) {
anInstance(that, Prototype);
setInternalState2(that, {
type: CONSTRUCTOR_NAME,
index: create3(null),
first: void 0,
last: void 0,
size: 0
});
if (!DESCRIPTORS10)
that.size = 0;
if (!isNullOrUndefined5(iterable))
iterate2(iterable, that[ADDER], { that: that, AS_ENTRIES: IS_MAP });
});
var Prototype = Constructor.prototype;
var getInternalState3 = internalStateGetterFor(CONSTRUCTOR_NAME);
var define = function(that, key, value) {
var state = getInternalState3(that);
var entry = getEntry(that, key);
var previous, index;
if (entry) {
entry.value = value;
} else {
state.last = entry = {
index: index = fastKey(key, true),
key: key,
value: value,
previous: previous = state.last,
next: void 0,
removed: false
};
if (!state.first)
state.first = entry;
if (previous)
previous.next = entry;
if (DESCRIPTORS10)
state.size++;
else
that.size++;
if (index !== "F")
state.index[index] = entry;
}
return that;
};
var getEntry = function(that, key) {
var state = getInternalState3(that);
var index = fastKey(key);
var entry;
if (index !== "F")
return state.index[index];
for (entry = state.first; entry; entry = entry.next) {
if (entry.key == key)
return entry;
}
};
defineBuiltIns(Prototype, {
clear: function clear() {
var that = this;
var state = getInternalState3(that);
var data = state.index;
var entry = state.first;
while (entry) {
entry.removed = true;
if (entry.previous)
entry.previous = entry.previous.next = void 0;
delete data[entry.index];
entry = entry.next;
}
state.first = state.last = void 0;
if (DESCRIPTORS10)
state.size = 0;
else
that.size = 0;
},
"delete": function(key) {
var that = this;
var state = getInternalState3(that);
var entry = getEntry(that, key);
if (entry) {
var next2 = entry.next;
var prev = entry.previous;
delete state.index[entry.index];
entry.removed = true;
if (prev)
prev.next = next2;
if (next2)
next2.previous = prev;
if (state.first == entry)
state.first = next2;
if (state.last == entry)
state.last = prev;
if (DESCRIPTORS10)
state.size--;
else
that.size--;
}
return !!entry;
},
forEach: function forEach3(callbackfn) {
var state = getInternalState3(this);
var boundFunction = bind2(callbackfn, arguments.length > 1 ? arguments[1] : void 0);
var entry;
while (entry = entry ? entry.next : state.first) {
boundFunction(entry.value, entry.key, this);
while (entry && entry.removed)
entry = entry.previous;
}
},
has: function has(key) {
return !!getEntry(this, key);
}
});
defineBuiltIns(Prototype, IS_MAP ? {
get: function get3(key) {
var entry = getEntry(this, key);
return entry && entry.value;
},
set: function set(key, value) {
return define(this, key === 0 ? 0 : key, value);
}
} : {
add: function add(value) {
return define(this, value = value === 0 ? 0 : value, value);
}
});
if (DESCRIPTORS10)
defineBuiltInAccessor4(Prototype, "size", {
configurable: true,
get: function() {
return getInternalState3(this).size;
}
});
return Constructor;
},
setStrong: function(Constructor, CONSTRUCTOR_NAME, IS_MAP) {
var ITERATOR_NAME = CONSTRUCTOR_NAME + " Iterator";
var getInternalCollectionState = internalStateGetterFor(CONSTRUCTOR_NAME);
var getInternalIteratorState = internalStateGetterFor(ITERATOR_NAME);
defineIterator2(Constructor, CONSTRUCTOR_NAME, function(iterated, kind) {
setInternalState2(this, {
type: ITERATOR_NAME,
target: iterated,
state: getInternalCollectionState(iterated),
kind: kind,
last: void 0
});
}, function() {
var state = getInternalIteratorState(this);
var kind = state.kind;
var entry = state.last;
while (entry && entry.removed)
entry = entry.previous;
if (!state.target || !(state.last = entry = entry ? entry.next : state.state.first)) {
state.target = void 0;
return createIterResultObject2(void 0, true);
}
if (kind == "keys")
return createIterResultObject2(entry.key, false);
if (kind == "values")
return createIterResultObject2(entry.value, false);
return createIterResultObject2([entry.key, entry.value], false);
}, IS_MAP ? "entries" : "values", !IS_MAP, true);
setSpecies3(CONSTRUCTOR_NAME);
}
};
}
});
// node_modules/core-js/modules/es.map.constructor.js
var require_es_map_constructor = __commonJS({
"node_modules/core-js/modules/es.map.constructor.js": function() {
"use strict";
var collection = require_collection();
var collectionStrong = require_collection_strong();
collection("Map", function(init2) {
return function Map2() {
return init2(this, arguments.length ? arguments[0] : void 0);
};
}, collectionStrong);
}
});
// node_modules/core-js/internals/collection-weak.js
var require_collection_weak = __commonJS({
"node_modules/core-js/internals/collection-weak.js": function(exports, module) {
@@ -4903,209 +5106,6 @@
}
});
// node_modules/core-js/internals/collection-strong.js
var require_collection_strong = __commonJS({
"node_modules/core-js/internals/collection-strong.js": function(exports, module) {
"use strict";
var create3 = require_object_create();
var defineBuiltInAccessor4 = require_define_built_in_accessor();
var defineBuiltIns = require_define_built_ins();
var bind2 = require_function_bind_context();
var anInstance = require_an_instance();
var isNullOrUndefined5 = require_is_null_or_undefined();
var iterate2 = require_iterate();
var defineIterator2 = require_iterator_define();
var createIterResultObject2 = require_create_iter_result_object();
var setSpecies3 = require_set_species();
var DESCRIPTORS10 = require_descriptors();
var fastKey = require_internal_metadata().fastKey;
var InternalStateModule2 = require_internal_state();
var setInternalState2 = InternalStateModule2.set;
var internalStateGetterFor = InternalStateModule2.getterFor;
module.exports = {
getConstructor: function(wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER) {
var Constructor = wrapper(function(that, iterable) {
anInstance(that, Prototype);
setInternalState2(that, {
type: CONSTRUCTOR_NAME,
index: create3(null),
first: void 0,
last: void 0,
size: 0
});
if (!DESCRIPTORS10)
that.size = 0;
if (!isNullOrUndefined5(iterable))
iterate2(iterable, that[ADDER], { that: that, AS_ENTRIES: IS_MAP });
});
var Prototype = Constructor.prototype;
var getInternalState3 = internalStateGetterFor(CONSTRUCTOR_NAME);
var define = function(that, key, value) {
var state = getInternalState3(that);
var entry = getEntry(that, key);
var previous, index;
if (entry) {
entry.value = value;
} else {
state.last = entry = {
index: index = fastKey(key, true),
key: key,
value: value,
previous: previous = state.last,
next: void 0,
removed: false
};
if (!state.first)
state.first = entry;
if (previous)
previous.next = entry;
if (DESCRIPTORS10)
state.size++;
else
that.size++;
if (index !== "F")
state.index[index] = entry;
}
return that;
};
var getEntry = function(that, key) {
var state = getInternalState3(that);
var index = fastKey(key);
var entry;
if (index !== "F")
return state.index[index];
for (entry = state.first; entry; entry = entry.next) {
if (entry.key == key)
return entry;
}
};
defineBuiltIns(Prototype, {
clear: function clear() {
var that = this;
var state = getInternalState3(that);
var data = state.index;
var entry = state.first;
while (entry) {
entry.removed = true;
if (entry.previous)
entry.previous = entry.previous.next = void 0;
delete data[entry.index];
entry = entry.next;
}
state.first = state.last = void 0;
if (DESCRIPTORS10)
state.size = 0;
else
that.size = 0;
},
"delete": function(key) {
var that = this;
var state = getInternalState3(that);
var entry = getEntry(that, key);
if (entry) {
var next2 = entry.next;
var prev = entry.previous;
delete state.index[entry.index];
entry.removed = true;
if (prev)
prev.next = next2;
if (next2)
next2.previous = prev;
if (state.first == entry)
state.first = next2;
if (state.last == entry)
state.last = prev;
if (DESCRIPTORS10)
state.size--;
else
that.size--;
}
return !!entry;
},
forEach: function forEach3(callbackfn) {
var state = getInternalState3(this);
var boundFunction = bind2(callbackfn, arguments.length > 1 ? arguments[1] : void 0);
var entry;
while (entry = entry ? entry.next : state.first) {
boundFunction(entry.value, entry.key, this);
while (entry && entry.removed)
entry = entry.previous;
}
},
has: function has(key) {
return !!getEntry(this, key);
}
});
defineBuiltIns(Prototype, IS_MAP ? {
get: function get3(key) {
var entry = getEntry(this, key);
return entry && entry.value;
},
set: function set(key, value) {
return define(this, key === 0 ? 0 : key, value);
}
} : {
add: function add(value) {
return define(this, value = value === 0 ? 0 : value, value);
}
});
if (DESCRIPTORS10)
defineBuiltInAccessor4(Prototype, "size", {
configurable: true,
get: function() {
return getInternalState3(this).size;
}
});
return Constructor;
},
setStrong: function(Constructor, CONSTRUCTOR_NAME, IS_MAP) {
var ITERATOR_NAME = CONSTRUCTOR_NAME + " Iterator";
var getInternalCollectionState = internalStateGetterFor(CONSTRUCTOR_NAME);
var getInternalIteratorState = internalStateGetterFor(ITERATOR_NAME);
defineIterator2(Constructor, CONSTRUCTOR_NAME, function(iterated, kind) {
setInternalState2(this, {
type: ITERATOR_NAME,
target: iterated,
state: getInternalCollectionState(iterated),
kind: kind,
last: void 0
});
}, function() {
var state = getInternalIteratorState(this);
var kind = state.kind;
var entry = state.last;
while (entry && entry.removed)
entry = entry.previous;
if (!state.target || !(state.last = entry = entry ? entry.next : state.state.first)) {
state.target = void 0;
return createIterResultObject2(void 0, true);
}
if (kind == "keys")
return createIterResultObject2(entry.key, false);
if (kind == "values")
return createIterResultObject2(entry.value, false);
return createIterResultObject2([entry.key, entry.value], false);
}, IS_MAP ? "entries" : "values", !IS_MAP, true);
setSpecies3(CONSTRUCTOR_NAME);
}
};
}
});
// node_modules/core-js/modules/es.map.constructor.js
var require_es_map_constructor = __commonJS({
"node_modules/core-js/modules/es.map.constructor.js": function() {
"use strict";
var collection = require_collection();
var collectionStrong = require_collection_strong();
collection("Map", function(init2) {
return function Map2() {
return init2(this, arguments.length ? arguments[0] : void 0);
};
}, collectionStrong);
}
});
// node_modules/core-js/modules/es.set.constructor.js
var require_es_set_constructor = __commonJS({
"node_modules/core-js/modules/es.set.constructor.js": function() {
@@ -15815,6 +15815,9 @@
// srcts/src/components/errorConsole.ts
var import_es_array_iterator36 = __toESM(require_es_array_iterator());
// node_modules/core-js/modules/es.map.js
require_es_map_constructor();
// node_modules/@lit/reactive-element/reactive-element.js
var import_es_regexp_exec10 = __toESM(require_es_regexp_exec(), 1);
@@ -15850,9 +15853,6 @@
// node_modules/core-js/modules/es.weak-map.js
require_es_weak_map_constructor();
// node_modules/core-js/modules/es.map.js
require_es_map_constructor();
// node_modules/core-js/modules/es.set.js
require_es_set_constructor();
@@ -16587,7 +16587,7 @@
}
function _wrapNativeSuper(Class) {
var _cache = typeof Map === "function" ? /* @__PURE__ */ new Map() : void 0;
_wrapNativeSuper = function _wrapNativeSuper3(Class2) {
_wrapNativeSuper = function _wrapNativeSuper4(Class2) {
if (Class2 === null || !_isNativeFunction(Class2))
return Class2;
if (typeof Class2 !== "function") {
@@ -16610,7 +16610,7 @@
if (_isNativeReflectConstruct21()) {
_construct = Reflect.construct.bind();
} else {
_construct = function _construct3(Parent2, args2, Class2) {
_construct = function _construct4(Parent2, args2, Class2) {
var a3 = [null];
a3.push.apply(a3, args2);
var Constructor = Function.bind.apply(Parent2, a3);
@@ -18333,7 +18333,7 @@
}
function _wrapNativeSuper2(Class) {
var _cache = typeof Map === "function" ? /* @__PURE__ */ new Map() : void 0;
_wrapNativeSuper2 = function _wrapNativeSuper3(Class2) {
_wrapNativeSuper2 = function _wrapNativeSuper4(Class2) {
if (Class2 === null || !_isNativeFunction2(Class2))
return Class2;
if (typeof Class2 !== "function") {
@@ -18356,7 +18356,7 @@
if (_isNativeReflectConstruct24()) {
_construct2 = Reflect.construct.bind();
} else {
_construct2 = function _construct3(Parent2, args2, Class2) {
_construct2 = function _construct4(Parent2, args2, Class2) {
var a3 = [null];
a3.push.apply(a3, args2);
var Constructor = Function.bind.apply(Parent2, a3);
@@ -18454,6 +18454,46 @@
var _templateObject3;
var _templateObject4;
var _templateObject5;
function _wrapNativeSuper3(Class) {
var _cache = typeof Map === "function" ? /* @__PURE__ */ new Map() : void 0;
_wrapNativeSuper3 = function _wrapNativeSuper4(Class2) {
if (Class2 === null || !_isNativeFunction3(Class2))
return Class2;
if (typeof Class2 !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class2))
return _cache.get(Class2);
_cache.set(Class2, Wrapper);
}
function Wrapper() {
return _construct3(Class2, arguments, _getPrototypeOf25(this).constructor);
}
Wrapper.prototype = Object.create(Class2.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } });
return _setPrototypeOf25(Wrapper, Class2);
};
return _wrapNativeSuper3(Class);
}
function _construct3(Parent, args, Class) {
if (_isNativeReflectConstruct25()) {
_construct3 = Reflect.construct.bind();
} else {
_construct3 = function _construct4(Parent2, args2, Class2) {
var a3 = [null];
a3.push.apply(a3, args2);
var Constructor = Function.bind.apply(Parent2, a3);
var instance = new Constructor();
if (Class2)
_setPrototypeOf25(instance, Class2.prototype);
return instance;
};
}
return _construct3.apply(null, arguments);
}
function _isNativeFunction3(fn) {
return Function.toString.call(fn).indexOf("[native code]") !== -1;
}
function _regeneratorRuntime6() {
"use strict";
_regeneratorRuntime6 = function _regeneratorRuntime15() {
@@ -18956,12 +18996,36 @@
headline: {},
message: {}
});
_defineProperty11(ShinyErrorMessage, "styles", [i(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(['\n :host {\n color: var(--red-11);\n display: block;\n font-size: var(--font-md);\n\n position: relative;\n --icon-size: var(--font-lg)\n\n /* Reset box sizing */\n box-sizing: border-box;\n }\n\n .container {\n display: flex;\n gap: var(--space-2);\n }\n\n .contents {\n width: 40ch;\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n padding-block-start: 0;\n padding-block-end: var(--space-3);\n overflow: auto;\n }\n\n :host(:last-of-type) .contents {\n\n padding-block-end: var(--space-1);\n }\n\n .contents > h3 {\n font-size: 1em;\n font-weight: 500;\n color: var(--red-12);\n }\n\n .contents > * {\n margin-block: 0;\n }\n\n .error-message {\n font-family: "Courier New", Courier, monospace;\n }\n\n .decoration-container {\n flex-shrink: 0;\n position: relative;\n\n --line-w: 2px;\n --dot-size: 11px;\n }\n\n :host(:hover) .decoration-container {\n --scale: 1.25;\n }\n\n .vertical-line {\n margin-inline: auto;\n width: var(--line-w);\n height: 100%;\n\n background-color: var(--red-10);\n }\n\n :host(:first-of-type) .vertical-line {\n height: calc(100% - var(--dot-size));\n margin-top: var(--dot-size);\n }\n\n .dot {\n position: absolute;\n width: var(--dot-size);\n height: var(--dot-size);\n top: calc(-1px + var(--dot-size) / 2);\n left: calc(50% - var(--dot-size) / 2);\n border-radius: 100%;\n transform: scale(var(--scale, 1));\n\n color: var(--red-6);\n background-color: var(--red-10);\n }\n\n .actions {\n transform: scaleX(0);\n transition: transform calc(var(--animation-speed) / 2) ease-in-out;\n display: flex;\n justify-content: center;\n flex-direction: column;\n }\n\n /* Delay transition on mouseout so the buttons don\'t jump away if the user\n overshoots them with their mouse */\n :host(:not(:hover)) .actions {\n transition-delay: 0.15s;\n }\n\n :host(:hover) .actions {\n transform: scaleX(1);\n }\n\n ', "\n\n .copy-button {\n padding: 0;\n width: var(--space-8);\n height: var(--space-8);\n position: relative;\n --pad: var(--space-2);\n }\n\n .copy-button-inner {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n transition: transform 0.5s;\n transform-style: preserve-3d;\n }\n\n /* Animate flipping to the other side when the .copy-success class is\n added to the host */\n :host(.copy-success) .copy-button-inner {\n transform: rotateY(180deg);\n }\n\n /* Position the front and back side */\n .copy-button .front,\n .copy-button .back {\n --side: calc(100% - 2 * var(--pad));\n position: absolute;\n inset: var(--pad);\n height: var(--side);\n width: var(--side);\n -webkit-backface-visibility: hidden; /* Safari */\n backface-visibility: hidden;\n }\n\n .copy-button:hover .copy-button-inner {\n background-color: var(--gray-2);\n }\n\n /* Style the back side */\n .copy-button .back {\n --pad: var(--space-1);\n color: var(--green-8);\n transform: rotateY(180deg);\n }\n "])), buttonStyles)]);
_defineProperty11(ShinyErrorMessage, "styles", [i(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(['\n :host {\n color: var(--red-11);\n display: block;\n font-size: var(--font-md);\n\n position: relative;\n --icon-size: var(--font-lg)\n\n /* Reset box sizing */\n box-sizing: border-box;\n }\n\n .container {\n display: flex;\n gap: var(--space-2);\n }\n\n .contents {\n width: 40ch;\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n padding-block-start: 0;\n padding-block-end: var(--space-3);\n overflow: auto;\n }\n\n :host(:last-of-type) .contents {\n\n padding-block-end: var(--space-1);\n }\n\n .contents > h3 {\n font-size: 1em;\n font-weight: 500;\n color: var(--red-12);\n }\n\n .contents > * {\n margin-block: 0;\n }\n\n .error-message {\n font-family: "Courier New", Courier, monospace;\n white-space: pre-wrap;\n }\n\n .decoration-container {\n flex-shrink: 0;\n position: relative;\n\n --line-w: 2px;\n --dot-size: 11px;\n }\n\n :host(:hover) .decoration-container {\n --scale: 1.25;\n }\n\n .vertical-line {\n margin-inline: auto;\n width: var(--line-w);\n height: 100%;\n\n background-color: var(--red-10);\n }\n\n :host(:first-of-type) .vertical-line {\n height: calc(100% - var(--dot-size));\n margin-top: var(--dot-size);\n }\n\n .dot {\n position: absolute;\n width: var(--dot-size);\n height: var(--dot-size);\n top: calc(-1px + var(--dot-size) / 2);\n left: calc(50% - var(--dot-size) / 2);\n border-radius: 100%;\n transform: scale(var(--scale, 1));\n\n color: var(--red-6);\n background-color: var(--red-10);\n }\n\n .actions {\n transform: scaleX(0);\n transition: transform calc(var(--animation-speed) / 2) ease-in-out;\n display: flex;\n justify-content: center;\n flex-direction: column;\n }\n\n /* Delay transition on mouseout so the buttons don\'t jump away if the user\n overshoots them with their mouse */\n :host(:not(:hover)) .actions {\n transition-delay: 0.15s;\n }\n\n :host(:hover) .actions {\n transform: scaleX(1);\n }\n\n ', "\n\n .copy-button {\n padding: 0;\n width: var(--space-8);\n height: var(--space-8);\n position: relative;\n --pad: var(--space-2);\n }\n\n .copy-button-inner {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n transition: transform 0.5s;\n transform-style: preserve-3d;\n }\n\n /* Animate flipping to the other side when the .copy-success class is\n added to the host */\n :host(.copy-success) .copy-button-inner {\n transform: rotateY(180deg);\n }\n\n /* Position the front and back side */\n .copy-button .front,\n .copy-button .back {\n --side: calc(100% - 2 * var(--pad));\n position: absolute;\n inset: var(--pad);\n height: var(--side);\n width: var(--side);\n -webkit-backface-visibility: hidden; /* Safari */\n backface-visibility: hidden;\n }\n\n .copy-button:hover .copy-button-inner {\n background-color: var(--gray-2);\n }\n\n /* Style the back side */\n .copy-button .back {\n --pad: var(--space-1);\n color: var(--green-8);\n transform: rotateY(180deg);\n }\n "])), buttonStyles)]);
customElements.define("shiny-error-message", ShinyErrorMessage);
function showErrorInClientConsole(e4) {
function showShinyClientMessage(_ref) {
var _ref$headline = _ref.headline, headline = _ref$headline === void 0 ? "" : _ref$headline, message = _ref.message, _ref$status = _ref.status, status = _ref$status === void 0 ? "warning" : _ref$status;
var consoleMessage = "[shiny] ".concat(headline).concat(headline ? " - " : "").concat(message);
switch (status) {
case "error":
console.error(consoleMessage);
break;
case "warning":
console.warn(consoleMessage);
break;
default:
console.log(consoleMessage);
break;
}
if (!Shiny.inDevMode()) {
return;
}
var errorConsoleContainer = document.querySelector("shiny-error-console");
if (!errorConsoleContainer) {
errorConsoleContainer = document.createElement("shiny-error-console");
document.body.appendChild(errorConsoleContainer);
}
var errorConsole = document.createElement("shiny-error-message");
errorConsole.setAttribute("headline", headline);
errorConsole.setAttribute("message", message);
errorConsoleContainer.appendChild(errorConsole);
}
function showErrorInClientConsole(e4) {
var errorMsg = null;
var headline = "Error on client while running Shiny app";
if (typeof e4 === "string") {
@@ -18974,16 +19038,39 @@
} else {
errorMsg = "Unknown error";
}
var errorConsoleContainer = document.querySelector("shiny-error-console");
if (!errorConsoleContainer) {
errorConsoleContainer = document.createElement("shiny-error-console");
document.body.appendChild(errorConsoleContainer);
}
var errorConsole = document.createElement("shiny-error-message");
errorConsole.setAttribute("headline", headline || "");
errorConsole.setAttribute("message", errorMsg);
errorConsoleContainer.appendChild(errorConsole);
showShinyClientMessage({
headline: headline,
message: errorMsg,
status: "error"
});
}
var ShinyClientMessageEvent = /* @__PURE__ */ function(_CustomEvent) {
_inherits25(ShinyClientMessageEvent2, _CustomEvent);
var _super3 = _createSuper25(ShinyClientMessageEvent2);
function ShinyClientMessageEvent2(detail) {
_classCallCheck33(this, ShinyClientMessageEvent2);
return _super3.call(this, "shiny:client-message", {
detail: detail,
bubbles: true,
cancelable: true
});
}
return _createClass33(ShinyClientMessageEvent2);
}(/* @__PURE__ */ _wrapNativeSuper3(CustomEvent));
window.addEventListener("shiny:client-message", function(ev) {
if (!(ev instanceof CustomEvent)) {
throw new Error("[shiny] shiny:client-message expected a CustomEvent");
}
var _ev$detail = ev.detail, headline = _ev$detail.headline, message = _ev$detail.message, status = _ev$detail.status;
if (!message) {
throw new Error("[shiny] shiny:client-message expected a `message` property in `event.detail`.");
}
showShinyClientMessage({
headline: headline,
message: message,
status: status
});
});
// srcts/src/imageutils/resetBrush.ts
function resetBrush(brushId) {
@@ -20526,8 +20613,9 @@
}
var bindingsRegistry = function() {
var bindings = /* @__PURE__ */ new Map();
function checkValidity() {
function checkValidity(scope) {
var duplicateIds = /* @__PURE__ */ new Map();
var problems = /* @__PURE__ */ new Set();
bindings.forEach(function(idTypes, id) {
var counts = {
input: 0,
@@ -20536,17 +20624,22 @@
idTypes.forEach(function(type) {
return counts[type] += 1;
});
if (counts.input === 1 && counts.output === 1 && !Shiny.inDevMode()) {
if (counts.input + counts.output < 2) {
return;
}
if (counts.input + counts.output > 1) {
duplicateIds.set(id, counts);
duplicateIds.set(id, counts);
if (counts.input > 1) {
problems.add("input");
}
if (counts.output > 1) {
problems.add("output");
}
if (counts.input >= 1 && counts.output >= 1) {
problems.add("shared");
}
});
if (duplicateIds.size === 0)
return {
status: "ok"
};
return;
var duplicateIdMsg = Array.from(duplicateIds.entries()).map(function(_ref) {
var _ref2 = _slicedToArray4(_ref, 2), id = _ref2[0], counts = _ref2[1];
var messages = [pluralize(counts.input, "input"), pluralize(counts.output, "output")].filter(function(msg) {
@@ -20554,13 +20647,25 @@
}).join(" and ");
return '- "'.concat(id, '": ').concat(messages);
}).join("\n");
return {
status: "error",
error: new ShinyClientError({
headline: "Duplicate input/output IDs found",
message: "The following ".concat(duplicateIds.size === 1 ? "ID was" : "IDs were", " repeated:\n").concat(duplicateIdMsg)
})
};
var txtVerb = "Duplicate";
var txtNoun = "input/output";
if (problems.has("input") && problems.has("output")) {
} else if (problems.has("input")) {
txtNoun = "input";
} else if (problems.has("output")) {
txtNoun = "output";
} else if (problems.has("shared")) {
txtVerb = "Shared";
}
var txtIdsWere = duplicateIds.size == 1 ? "ID was" : "IDs were";
var headline = "".concat(txtVerb, " ").concat(txtNoun, " ").concat(txtIdsWere, " found");
var message = "The following ".concat(txtIdsWere, " used for more than one ").concat(problems.has("shared") ? "input/output" : txtNoun, ":\n").concat(duplicateIdMsg);
var event = new ShinyClientMessageEvent({
headline: headline,
message: message
});
var scopeElement = scope instanceof HTMLElement ? scope : scope.get(0);
(scopeElement || window).dispatchEvent(event);
}
function addBinding(id, bindingType) {
var existingBinding = bindings.get(id);
@@ -20793,7 +20898,7 @@
}
function _bindAll2() {
_bindAll2 = _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2(shinyCtx, scope) {
var currentInputs, bindingValidity;
var currentInputs;
return _regeneratorRuntime8().wrap(function _callee2$(_context2) {
while (1)
switch (_context2.prev = _context2.next) {
@@ -20802,21 +20907,9 @@
return bindOutputs(shinyCtx, scope);
case 2:
currentInputs = bindInputs(shinyCtx, scope);
bindingValidity = bindingsRegistry.checkValidity();
if (!(bindingValidity.status === "error")) {
_context2.next = 10;
break;
}
if (!Shiny.inDevMode()) {
_context2.next = 9;
break;
}
throw bindingValidity.error;
case 9:
console.warn("[shiny] " + bindingValidity.error.message);
case 10:
bindingsRegistry.checkValidity(scope);
return _context2.abrupt("return", currentInputs);
case 11:
case 5:
case "end":
return _context2.stop();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -307,6 +307,7 @@ export class ShinyErrorMessage extends LitElement {
.error-message {
font-family: "Courier New", Courier, monospace;
white-space: pre-wrap;
}
.decoration-container {
@@ -489,6 +490,52 @@ export class ShinyErrorMessage extends LitElement {
customElements.define("shiny-error-message", ShinyErrorMessage);
export type ShinyClientMessage = {
message: string;
headline?: string;
status?: "error" | "info" | "warning";
};
function showShinyClientMessage({
headline = "",
message,
status = "warning",
}: ShinyClientMessage): void {
const consoleMessage = `[shiny] ${headline}${
headline ? " - " : ""
}${message}`;
switch (status) {
case "error":
console.error(consoleMessage);
break;
case "warning":
console.warn(consoleMessage);
break;
default:
console.log(consoleMessage);
break;
}
if (!Shiny.inDevMode()) {
return;
}
// Check to see if an Error Console Container element already exists. If it
// doesn't we need to add it before putting an error on the screen
let errorConsoleContainer = document.querySelector("shiny-error-console");
if (!errorConsoleContainer) {
errorConsoleContainer = document.createElement("shiny-error-console");
document.body.appendChild(errorConsoleContainer);
}
const errorConsole = document.createElement("shiny-error-message");
errorConsole.setAttribute("headline", headline);
errorConsole.setAttribute("message", message);
errorConsoleContainer.appendChild(errorConsole);
}
/**
* Function to show an error message to user in shiny-error-message web
* component. Only shows the error if we're in development mode.
@@ -497,11 +544,6 @@ customElements.define("shiny-error-message", ShinyErrorMessage);
* object.
*/
export function showErrorInClientConsole(e: unknown): void {
if (!Shiny.inDevMode()) {
// If we're in production, don't show the error to the user
return;
}
let errorMsg: string | null = null;
let headline = "Error on client while running Shiny app";
@@ -516,17 +558,24 @@ export function showErrorInClientConsole(e: unknown): void {
errorMsg = "Unknown error";
}
// Check to see if an Error Console Container element already exists. If it
// doesn't we need to add it before putting an error on the screen
let errorConsoleContainer = document.querySelector("shiny-error-console");
if (!errorConsoleContainer) {
errorConsoleContainer = document.createElement("shiny-error-console");
document.body.appendChild(errorConsoleContainer);
}
const errorConsole = document.createElement("shiny-error-message");
errorConsole.setAttribute("headline", headline || "");
errorConsole.setAttribute("message", errorMsg);
errorConsoleContainer.appendChild(errorConsole);
showShinyClientMessage({ headline, message: errorMsg, status: "error" });
}
export class ShinyClientMessageEvent extends CustomEvent<ShinyClientMessage> {
constructor(detail: ShinyClientMessage) {
super("shiny:client-message", { detail, bubbles: true, cancelable: true });
}
}
window.addEventListener("shiny:client-message", (ev: Event) => {
if (!(ev instanceof CustomEvent)) {
throw new Error("[shiny] shiny:client-message expected a CustomEvent");
}
const { headline, message, status } = ev.detail;
if (!message) {
throw new Error(
"[shiny] shiny:client-message expected a `message` property in `event.detail`."
);
}
showShinyClientMessage({ headline, message, status });
});

View File

@@ -3,11 +3,11 @@ import { Shiny } from "..";
import type { InputBinding, OutputBinding } from "../bindings";
import { OutputBindingAdapter } from "../bindings/outputAdapter";
import type { BindingRegistry } from "../bindings/registry";
import { ShinyClientMessageEvent } from "../components/errorConsole";
import type {
InputRateDecorator,
InputValidateDecorator,
} from "../inputPolicies";
import { ShinyClientError } from "./error";
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
import { sendImageSizeFns } from "./sendImageSize";
@@ -75,14 +75,13 @@ const bindingsRegistry = (() => {
* accessibility and other reasons. However, in practice our bindings still
* work as long as inputs the IDs within a binding type don't overlap.
*
* @returns ShinyClientError if current ID bindings are invalid, otherwise
* returns an ok status.
* @returns ShinyClientMessageEvent if current ID bindings are invalid,
* otherwise returns an ok status.
*/
function checkValidity():
| { status: "error"; error: ShinyClientError }
| { status: "ok" } {
function checkValidity(scope: BindScope): void {
type BindingCounts = { [T in BindingTypes]: number };
const duplicateIds = new Map<string, BindingCounts>();
const problems: Set<string> = new Set();
// count duplicate IDs of each binding type
bindings.forEach((idTypes, id) => {
@@ -90,22 +89,30 @@ const bindingsRegistry = (() => {
idTypes.forEach((type) => (counts[type] += 1));
// If there's a single duplication of ids across both binding types, then
// when we're not in devmode, we allow this to pass because a good amount of
// existing applications use this pattern even though its invalid. Eventually
// this behavior should be removed.
if (counts.input === 1 && counts.output === 1 && !Shiny.inDevMode()) {
if (counts.input + counts.output < 2) {
return;
}
// We have duplicated IDs, add them to the set of duplicated IDs to be
// reported to the user.
duplicateIds.set(id, counts);
// If we have duplicated IDs, then add them to the set of duplicated IDs
// to be reported to the user.
if (counts.input + counts.output > 1) {
duplicateIds.set(id, counts);
if (counts.input > 1) {
problems.add("input");
}
if (counts.output > 1) {
problems.add("output");
}
if (counts.input >= 1 && counts.output >= 1) {
problems.add("shared");
}
});
if (duplicateIds.size === 0) return { status: "ok" };
if (duplicateIds.size === 0) return;
// Duplicated IDs are now always a warning. Before the ShinyClient console
// was added duplicate output IDs were errors in "production" mode. After
// the Shiny Client console was introduced, duplicate IDs were no longer
// production errors but *would* break apps in dev mode. Now, in v1.10+,
// duplicate IDs are always warnings in all modes for consistency.
const duplicateIdMsg = Array.from(duplicateIds.entries())
.map(([id, counts]) => {
@@ -120,15 +127,27 @@ const bindingsRegistry = (() => {
})
.join("\n");
return {
status: "error",
error: new ShinyClientError({
headline: "Duplicate input/output IDs found",
message: `The following ${
duplicateIds.size === 1 ? "ID was" : "IDs were"
} repeated:\n${duplicateIdMsg}`,
}),
};
let txtVerb = "Duplicate";
let txtNoun = "input/output";
if (problems.has("input") && problems.has("output")) {
// base case
} else if (problems.has("input")) {
txtNoun = "input";
} else if (problems.has("output")) {
txtNoun = "output";
} else if (problems.has("shared")) {
txtVerb = "Shared";
}
const txtIdsWere = duplicateIds.size == 1 ? "ID was" : "IDs were";
const headline = `${txtVerb} ${txtNoun} ${txtIdsWere} found`;
const message = `The following ${txtIdsWere} used for more than one ${
problems.has("shared") ? "input/output" : txtNoun
}:\n${duplicateIdMsg}`;
const event = new ShinyClientMessageEvent({ headline, message });
const scopeElement = scope instanceof HTMLElement ? scope : scope.get(0);
(scopeElement || window).dispatchEvent(event);
}
/**
@@ -423,15 +442,7 @@ async function _bindAll(
// complete error message that contains everything they will need to fix. If
// we threw as we saw collisions then the user would fix the first collision,
// re-run, and then see the next collision, etc.
const bindingValidity = bindingsRegistry.checkValidity();
if (bindingValidity.status === "error") {
// Only throw if we're in dev mode. Otherwise, just log a warning.
if (Shiny.inDevMode()) {
throw bindingValidity.error;
} else {
console.warn("[shiny] " + bindingValidity.error.message);
}
}
bindingsRegistry.checkValidity(scope);
return currentInputs;
}

View File

@@ -10,6 +10,11 @@ export declare class ShinyErrorMessage extends LitElement {
copyErrorToClipboard(): Promise<void>;
render(): import("lit-html").TemplateResult<1>;
}
export type ShinyClientMessage = {
message: string;
headline?: string;
status?: "error" | "info" | "warning";
};
/**
* Function to show an error message to user in shiny-error-message web
* component. Only shows the error if we're in development mode.
@@ -18,3 +23,6 @@ export declare class ShinyErrorMessage extends LitElement {
* object.
*/
export declare function showErrorInClientConsole(e: unknown): void;
export declare class ShinyClientMessageEvent extends CustomEvent<ShinyClientMessage> {
constructor(detail: ShinyClientMessage);
}