',this.message=this.modal.querySelector("h5"),this.button=this.modal.querySelector("button"),this.reloadParagraph=this.modal.querySelector("p"),this.button.addEventListener("click",function(){return r(a,void 0,void 0,function(){var e;return o(this,function(t){switch(t.label){case 0:this.show(),t.label=1;case 1:return t.trys.push([1,3,,4]),[4,window.Blazor.reconnect()];case 2:return t.sent()||this.rejected(),[3,4];case 3:return e=t.sent(),this.logger.log(i.LogLevel.Error,e),this.failed(),[3,4];case 4:return[2]}})})}),this.reloadParagraph.querySelector("a").addEventListener("click",function(){return location.reload()})}return e.prototype.show=function(){this.addedToDom||(this.addedToDom=!0,this.document.body.appendChild(this.modal)),this.modal.style.display="block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server..."},e.prototype.hide=function(){this.modal.style.display="none"},e.prototype.failed=function(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.message.innerHTML="Reconnection failed. Try reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(12),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function l(e,t,n){var a=e;if(e instanceof Comment&&(s(a)&&s(a).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(u(a))throw new Error("Not implemented: moving existing logical children");var i=s(t);if(n0;)e(r,0);var a=r;a.parentNode.removeChild(a)},t.getLogicalParent=u,t.getLogicalSiblingEnd=function(e){return e[a]||null},t.getLogicalChild=function(e,t){return s(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===c(e).namespaceURI},t.getLogicalChildrenArray=s,t.permuteLogicalChildren=function(e,t){var n=s(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=u(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):d(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,a=r;a;){var i=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=i}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=c},,,,function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,a=null;function i(e){t.push(e)}function l(e,t,n,r){var o=s();if(o.invokeDotNetFromJS){var a=JSON.stringify(r,h),i=o.invokeDotNetFromJS(e,t,n,a);return i?f(i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function u(e,t,r,a){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var i=o++,l=new Promise(function(e,t){n[i]={resolve:e,reject:t}});try{var u=JSON.stringify(a,h);s().beginInvokeDotNetFromJS(i,e,t,r,u)}catch(e){c(i,!1,e)}return l}function s(){if(null!==a)return a;throw new Error("No .NET call dispatcher has been set.")}function c(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function d(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){a=e},e.attachReviver=i,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]-1?a.substring(0,l):"",s=l>-1?a.substring(l+1):a,c=t.monoPlatform.findMethod(e,u,s,i);t.monoPlatform.callMethod(c,null,r)},callMethod:function(e,n,r){if(r.length>4)throw new Error("Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass "+r.length+".");var o=Module.stackSave();try{for(var a=Module.stackAlloc(r.length),l=Module.stackAlloc(4),u=0;u>2,r=Module.HEAPU32[n+1];if(r>v)throw new Error("Cannot read uint64 with high order part "+r+", because the result would exceed Number.MAX_SAFE_INTEGER.");return r*h+Module.HEAPU32[n]},readFloatField:function(e,t){return Module.getValue(e+(t||0),"float")},readObjectField:function(e,t){return Module.getValue(e+(t||0),"i32")},readStringField:function(e,n){var r=Module.getValue(e+(n||0),"i32");return 0===r?null:t.monoPlatform.toJavaScriptString(r)},readStructField:function(e,t){return e+(t||0)}};var b=document.createElement("a");function w(e){return e+12}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(33),o=window.chrome&&navigator.userAgent.indexOf("Edge")<0,a=!1;function i(){return a&&o}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){a=e.some(function(e){return/\.pdb$/.test(r.getFileNameFromUrl(e))});var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(a?o?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Chrome is supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(9),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=a,this.arrayBuilderSegmentReader=i,this.diffReader=l,this.editReader=u,this.frameReader=s}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,a.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*a.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*a.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return c(e,t,l.structLength)},e.prototype.referenceFramesEntry=function(e,t){return c(e,t,s.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=c(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=c(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var a={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},i={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},l={structLength:4+i.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return c(e,t,u.structLength)}},u={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},s={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function c(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}}]);
\ No newline at end of file
+!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=46)}([,,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(25),n(9);var r=n(26),o=n(14),a={},i=!1;function l(e,t,n){var o=a[e];o||(o=a[e]=new r.BrowserRenderer(e)),o.attachRootComponentToLogicalElement(n,t)}t.attachRootComponentToLogicalElement=l,t.attachRootComponentToElement=function(e,t,n){var r=document.querySelector(e);if(!r)throw new Error("Could not find any element matching selector '"+e+"'.");l(n||0,o.toLogicalElement(r,!0),t)},t.renderBatch=function(e,t){var n=a[e];if(!n)throw new Error("There is no browser renderer with ID "+e+".");for(var r=t.arrayRangeReader,o=t.updatedComponents(),l=r.values(o),u=r.count(o),s=t.referenceFrames(),c=r.values(s),f=t.diffReader,d=0;d0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function l(e,t,n){var a=e;if(e instanceof Comment&&(s(a)&&s(a).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(u(a))throw new Error("Not implemented: moving existing logical children");var i=s(t);if(n0;)e(r,0);var a=r;a.parentNode.removeChild(a)},t.getLogicalParent=u,t.getLogicalSiblingEnd=function(e){return e[a]||null},t.getLogicalChild=function(e,t){return s(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===c(e).namespaceURI},t.getLogicalChildrenArray=s,t.permuteLogicalChildren=function(e,t){var n=s(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=u(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):d(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,a=r;a;){var i=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=i}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=c},,,,function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,a=null;function i(e){t.push(e)}function l(e,t,n,r){var o=s();if(o.invokeDotNetFromJS){var a=JSON.stringify(r,h),i=o.invokeDotNetFromJS(e,t,n,a);return i?f(i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function u(e,t,r,a){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var i=o++,l=new Promise(function(e,t){n[i]={resolve:e,reject:t}});try{var u=JSON.stringify(a,h);s().beginInvokeDotNetFromJS(i,e,t,r,u)}catch(e){c(i,!1,e)}return l}function s(){if(null!==a)return a;throw new Error("No .NET call dispatcher has been set.")}function c(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function d(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){a=e},e.attachReviver=i,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]-1?a.substring(0,l):"",s=l>-1?a.substring(l+1):a,c=t.monoPlatform.findMethod(e,u,s,i);t.monoPlatform.callMethod(c,null,r)},callMethod:function(e,n,r){if(r.length>4)throw new Error("Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass "+r.length+".");var o=Module.stackSave();try{for(var a=Module.stackAlloc(r.length),l=Module.stackAlloc(4),u=0;u>2,r=Module.HEAPU32[n+1];if(r>y)throw new Error("Cannot read uint64 with high order part "+r+", because the result would exceed Number.MAX_SAFE_INTEGER.");return r*v+Module.HEAPU32[n]},readFloatField:function(e,t){return Module.getValue(e+(t||0),"float")},readObjectField:function(e,t){return Module.getValue(e+(t||0),"i32")},readStringField:function(e,n){var r=Module.getValue(e+(n||0),"i32");return 0===r?null:t.monoPlatform.toJavaScriptString(r)},readStructField:function(e,t){return e+(t||0)}};var w=document.createElement("a");function E(e){return e+12}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(33),o=window.chrome&&navigator.userAgent.indexOf("Edge")<0,a=!1;function i(){return a&&o}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){a=e.some(function(e){return/\.pdb$/.test(r.getFileNameFromUrl(e))});var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(a?o?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Chrome is supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(9),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=a,this.arrayBuilderSegmentReader=i,this.diffReader=l,this.editReader=u,this.frameReader=s}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,a.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*a.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*a.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return c(e,t,l.structLength)},e.prototype.referenceFramesEntry=function(e,t){return c(e,t,s.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=c(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=c(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var a={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},i={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},l={structLength:4+i.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return c(e,t,u.structLength)}},u={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},s={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function c(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}}]);
\ No newline at end of file
diff --git a/src/Components/Web.JS/src/Boot.Server.ts b/src/Components/Web.JS/src/Boot.Server.ts
index dc2c8a0ae4..4ea227247c 100644
--- a/src/Components/Web.JS/src/Boot.Server.ts
+++ b/src/Components/Web.JS/src/Boot.Server.ts
@@ -2,6 +2,7 @@ import '@dotnet/jsinterop';
import './GlobalExports';
import * as signalR from '@aspnet/signalr';
import { MessagePackHubProtocol } from '@aspnet/signalr-protocol-msgpack';
+import { showErrorNotification } from './BootErrors';
import { shouldAutoStart } from './BootCommon';
import { RenderQueue } from './Platform/Circuits/RenderQueue';
import { ConsoleLogger } from './Platform/Logging/Loggers';
@@ -106,6 +107,7 @@ async function initializeConnection(options: BlazorOptions, logger: Logger, circ
connection.on('JS.Error', error => {
renderingFailed = true;
unhandledError(connection, error, logger);
+ showErrorNotification();
});
window['Blazor']._internal.forceCloseConnection = () => connection.stop();
diff --git a/src/Components/Web.JS/src/BootErrors.ts b/src/Components/Web.JS/src/BootErrors.ts
new file mode 100644
index 0000000000..3db688e427
--- /dev/null
+++ b/src/Components/Web.JS/src/BootErrors.ts
@@ -0,0 +1,30 @@
+let hasFailed = false;
+
+export async function showErrorNotification() {
+ let errorUi = document.querySelector('#blazor-error-ui') as HTMLElement;
+ if (errorUi) {
+ errorUi.style.display = 'block';
+ }
+
+ if (!hasFailed) {
+ hasFailed = true;
+ const errorUiReloads = document.querySelectorAll('#blazor-error-ui .reload');
+ errorUiReloads.forEach(reload => {
+ reload.onclick = function (e) {
+ location.reload();
+ e.preventDefault();
+ };
+ });
+
+ let errorUiDismiss = document.querySelectorAll('#blazor-error-ui .dismiss');
+ errorUiDismiss.forEach(dismiss => {
+ dismiss.onclick = function (e) {
+ const errorUi = document.querySelector('#blazor-error-ui');
+ if (errorUi) {
+ errorUi.style.display = 'none';
+ }
+ e.preventDefault();
+ };
+ });
+ }
+}
diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
index e26110f5a1..997b3d7bca 100644
--- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
+++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
@@ -1,6 +1,7 @@
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getFileNameFromUrl } from '../Url';
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
+import { showErrorNotification } from '../../BootErrors';
const assemblyHandleCache: { [assemblyName: string]: number } = {};
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
@@ -232,7 +233,11 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
const suppressMessages = ['DEBUGGING ENABLED'];
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
- module.printErr = line => console.error(`WASM: ${line}`);
+
+ module.printErr = line => {
+ console.error(`WASM: ${line}`);
+ showErrorNotification();
+ };
module.preRun = [];
module.postRun = [];
module.preloadPlugins = [];
diff --git a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
index 15175a5583..67a01446b9 100644
--- a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
+++ b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
@@ -341,9 +341,18 @@ export class BrowserRenderer {
}
}
- private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null) {
+ private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null): boolean {
// Certain elements have built-in behaviour for their 'value' property
const frameReader = batch.frameReader;
+
+ if (element.tagName === 'INPUT' && element.getAttribute('type') === 'time' && !element.getAttribute('step')) {
+ const timeValue = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
+ if (timeValue) {
+ element['value'] = timeValue.substring(0, 5);
+ return true;
+ }
+ }
+
switch (element.tagName) {
case 'INPUT':
case 'SELECT':
diff --git a/src/Components/Web.JS/src/Rendering/EventForDotNet.ts b/src/Components/Web.JS/src/Rendering/EventForDotNet.ts
index 63dea2cc1f..042322b5a8 100644
--- a/src/Components/Web.JS/src/Rendering/EventForDotNet.ts
+++ b/src/Components/Web.JS/src/Rendering/EventForDotNet.ts
@@ -1,13 +1,19 @@
export class EventForDotNet {
- constructor(public readonly type: EventArgsType, public readonly data: TData) {
+ public constructor(public readonly type: EventArgsType, public readonly data: TData) {
}
- static fromDOMEvent(event: Event): EventForDotNet {
+ public static fromDOMEvent(event: Event): EventForDotNet {
const element = event.target as Element;
switch (event.type) {
case 'input':
case 'change': {
+
+ if (isTimeBasedInput(element)) {
+ const normalizedValue = normalizeTimeBasedValue(element);
+ return new EventForDotNet('change', { type: event.type, value: normalizedValue });
+ }
+
const targetIsCheckbox = isCheckbox(element);
const newValue = targetIsCheckbox ? !!element['checked'] : element['value'];
return new EventForDotNet('change', { type: event.type, value: newValue });
@@ -36,7 +42,7 @@ export class EventForDotNet {
case 'keydown':
case 'keyup':
case 'keypress':
- return new EventForDotNet('keyboard', parseKeyboardEvent(event));
+ return new EventForDotNet('keyboard', parseKeyboardEvent(event as KeyboardEvent));
case 'contextmenu':
case 'click':
@@ -46,10 +52,10 @@ export class EventForDotNet {
case 'mousedown':
case 'mouseup':
case 'dblclick':
- return new EventForDotNet('mouse', parseMouseEvent(event));
+ return new EventForDotNet('mouse', parseMouseEvent(event as MouseEvent));
case 'error':
- return new EventForDotNet('error', parseErrorEvent(event));
+ return new EventForDotNet('error', parseErrorEvent(event as ErrorEvent));
case 'loadstart':
case 'timeout':
@@ -57,7 +63,7 @@ export class EventForDotNet {
case 'load':
case 'loadend':
case 'progress':
- return new EventForDotNet('progress', parseProgressEvent(event));
+ return new EventForDotNet('progress', parseProgressEvent(event as ProgressEvent));
case 'touchcancel':
case 'touchend':
@@ -65,7 +71,7 @@ export class EventForDotNet {
case 'touchenter':
case 'touchleave':
case 'touchstart':
- return new EventForDotNet('touch', parseTouchEvent(event));
+ return new EventForDotNet('touch', parseTouchEvent(event as TouchEvent));
case 'gotpointercapture':
case 'lostpointercapture':
@@ -77,11 +83,11 @@ export class EventForDotNet {
case 'pointerout':
case 'pointerover':
case 'pointerup':
- return new EventForDotNet('pointer', parsePointerEvent(event));
+ return new EventForDotNet('pointer', parsePointerEvent(event as PointerEvent));
case 'wheel':
case 'mousewheel':
- return new EventForDotNet('wheel', parseWheelEvent(event));
+ return new EventForDotNet('wheel', parseWheelEvent(event as WheelEvent));
default:
return new EventForDotNet('unknown', { type: event.type });
@@ -204,8 +210,38 @@ function parseMouseEvent(event: MouseEvent) {
};
}
-function isCheckbox(element: Element | null) {
- return element && element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
+function isCheckbox(element: Element | null): boolean {
+ return !!element && element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
+}
+
+const timeBasedInputs = [
+ 'date',
+ 'datetime-local',
+ 'month',
+ 'time',
+ 'week',
+];
+
+function isTimeBasedInput(element: Element): element is HTMLInputElement {
+ return timeBasedInputs.indexOf(element.getAttribute('type')!) !== -1;
+}
+
+function normalizeTimeBasedValue(element: HTMLInputElement): string {
+ const value = element.value;
+ const type = element.type;
+ switch (type) {
+ case 'date':
+ case 'datetime-local':
+ case 'month':
+ return value;
+ case 'time':
+ return value.length === 5 ? value + ':00' : value; // Convert hh:mm to hh:mm:00
+ case 'week':
+ // For now we are not going to normalize input type week as it is not trivial
+ return value;
+ }
+
+ throw new Error(`Invalid element type '${type}'.`);
}
// The following interfaces must be kept in sync with the UIEventArgs C# classes
diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs
index 7987cfff11..73061225e9 100644
--- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs
+++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs
@@ -172,9 +172,15 @@ namespace Microsoft.AspNetCore.Components.Web
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange", false, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", "value", "value", "onchange", true, "yyyy-MM-dd")]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", null, "value", "onchange", true, "yyyy-MM-dd")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", "value", "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", null, "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("month", "value", "value", "onchange", true, "yyyy-MM")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("month", null, "value", "onchange", true, "yyyy-MM")]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", "value", "value", "onchange", true, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", null, "value", "onchange", true, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("text", null, "value", "onchange", false, null)]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("time", "value", "value", "onchange", true, "HH:mm:ss")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("time", null, "value", "onchange", true, "HH:mm:ss")]
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, "value", "value", "onchange", false, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, null, "value", "onchange", false, null)]
public static partial class BindAttributes
diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs
index 7987cfff11..73061225e9 100644
--- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs
+++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs
@@ -172,9 +172,15 @@ namespace Microsoft.AspNetCore.Components.Web
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange", false, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", "value", "value", "onchange", true, "yyyy-MM-dd")]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", null, "value", "onchange", true, "yyyy-MM-dd")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", "value", "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", null, "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("month", "value", "value", "onchange", true, "yyyy-MM")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("month", null, "value", "onchange", true, "yyyy-MM")]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", "value", "value", "onchange", true, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", null, "value", "onchange", true, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute("text", null, "value", "onchange", false, null)]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("time", "value", "value", "onchange", true, "HH:mm:ss")]
+ [Microsoft.AspNetCore.Components.BindInputElementAttribute("time", null, "value", "onchange", true, "HH:mm:ss")]
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, "value", "value", "onchange", false, null)]
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, null, "value", "onchange", false, null)]
public static partial class BindAttributes
diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs
index 7c8eec1a23..09327bcae6 100644
--- a/src/Components/Web/src/Forms/InputNumber.cs
+++ b/src/Components/Web/src/Forms/InputNumber.cs
@@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Components.Forms
// of it for us. We will only get asked to parse the T for nonempty inputs.
var targetType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
if (targetType == typeof(int) ||
+ targetType == typeof(long) ||
targetType == typeof(float) ||
targetType == typeof(double) ||
targetType == typeof(decimal))
diff --git a/src/Components/Web/src/Web/BindAttributes.cs b/src/Components/Web/src/Web/BindAttributes.cs
index ecff551b7c..a6d5ce9031 100644
--- a/src/Components/Web/src/Web/BindAttributes.cs
+++ b/src/Components/Web/src/Web/BindAttributes.cs
@@ -30,6 +30,20 @@ namespace Microsoft.AspNetCore.Components.Web
[BindInputElement("date", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-dd")]
[BindInputElement("date", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-dd")]
+ // type="datetime-local" is invariant culture with a specific format.
+ // See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for details.
+ [BindInputElement("datetime-local", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-ddTHH:mm:ss")]
+ [BindInputElement("datetime-local", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-ddTHH:mm:ss")]
+
+ // type="month" is invariant culture with a specific format.
+ // See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for details.
+ [BindInputElement("month", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM")]
+ [BindInputElement("month", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM")]
+
+ // type="time" is invariant culture with a specific format.
+ [BindInputElement("time", null, "value", "onchange", isInvariantCulture: true, format: "HH:mm:ss")]
+ [BindInputElement("time", "value", "value", "onchange", isInvariantCulture: true, format: "HH:mm:ss")]
+
[BindElement("select", null, "value", "onchange")]
[BindElement("textarea", null, "value", "onchange")]
public static class BindAttributes
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs
new file mode 100644
index 0000000000..1aefbe2b8e
--- /dev/null
+++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.AspNetCore.E2ETesting;
+using OpenQA.Selenium;
+using TestServer;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
+{
+ public class ComponentWithParametersTest : ServerTestBase>
+ {
+ public ComponentWithParametersTest(
+ BrowserFixture browserFixture,
+ BasicTestAppServerSiteFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
+ {
+ }
+
+ [Fact]
+ public void PassingParametersToComponentsFromThePageWorks()
+ {
+ Navigate("/prerendered/componentwithparameters?QueryValue=testQueryValue");
+
+ BeginInteractivity();
+
+ Browser.Exists(By.CssSelector(".interactive"));
+
+ var parameter1 = Browser.FindElement(By.CssSelector(".Param1"));
+ Assert.Equal(100, parameter1.FindElements(By.CssSelector("li")).Count);
+ Assert.Equal("99 99", parameter1.FindElement(By.CssSelector("li:last-child")).Text);
+
+ // The assigned value is of a more derived type than the declared model type. This check
+ // verifies we use the actual model type during round tripping.
+ var parameter2 = Browser.FindElement(By.CssSelector(".Param2"));
+ Assert.Equal("Value Derived-Value", parameter2.Text);
+
+ // This check verifies CaptureUnmatchedValues works
+ var parameter3 = Browser.FindElements(By.CssSelector(".Param3 li"));
+ Assert.Collection(
+ parameter3,
+ p => Assert.Equal("key1 testQueryValue", p.Text),
+ p => Assert.Equal("key2 43", p.Text));
+ }
+
+ private void BeginInteractivity()
+ {
+ Browser.FindElement(By.Id("load-boot-script")).Click();
+ }
+ }
+}
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs
index cdca53efaf..3e330f7b47 100644
--- a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs
+++ b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs
@@ -174,6 +174,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Browser.Equal(9000.ToString(cultureInfo), () => display.Text);
Browser.Equal(9000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
+ // long
+ input = Browser.FindElement(By.Id("inputnumber_long"));
+ display = Browser.FindElement(By.Id("inputnumber_long_value"));
+ Browser.Equal(4200.ToString(cultureInfo), () => display.Text);
+ Browser.Equal(4200.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
+
+ input.Clear();
+ input.SendKeys(90000000000.ToString(CultureInfo.InvariantCulture));
+ input.SendKeys("\t");
+ Browser.Equal(90000000000.ToString(cultureInfo), () => display.Text);
+ Browser.Equal(90000000000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
+
// decimal
input = Browser.FindElement(By.Id("inputnumber_decimal"));
display = Browser.FindElement(By.Id("inputnumber_decimal_value"));
diff --git a/src/Components/test/E2ETest/Tests/BindTest.cs b/src/Components/test/E2ETest/Tests/BindTest.cs
index a4804336f1..40e9494a9b 100644
--- a/src/Components/test/E2ETest/Tests/BindTest.cs
+++ b/src/Components/test/E2ETest/Tests/BindTest.cs
@@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Text.Json;
using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
+using Moq;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
@@ -1018,5 +1020,263 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Browser.Equal(expected.DateTime, () => DateTimeOffset.Parse(boundValue.Text).DateTime);
Assert.Equal(expected.DateTime, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")).DateTime);
}
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindDateTimeLocalTextboxDateTime()
+ {
+ var target = Browser.FindElement(By.Id("datetime-local-textbox-datetime"));
+ var boundValue = Browser.FindElement(By.Id("datetime-local-textbox-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("datetime-local-textbox-datetime-mirror"));
+ var expected = new DateTime(1985, 3, 4);
+ Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(expected, DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Clear textbox; value updates to 01/01/0001 because that's the default
+ target.Clear();
+ expected = default;
+ Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(expected, DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#datetime-local-textbox-datetime", "2000-01-02T04:05:06");
+ expected = new DateTime(2000, 1, 2, 04, 05, 06);
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindDateTimeLocalTextboxNullableDateTime()
+ {
+ var target = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime"));
+ var boundValue = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime-mirror"));
+ Assert.Equal(string.Empty, target.GetAttribute("value"));
+ Assert.Equal(string.Empty, boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ Browser.Equal("", () => boundValue.Text);
+ Assert.Equal("", mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#datetime-local-textbox-nullable-datetime", "2000-01-02T04:05:06");
+ var expected = new DateTime(2000, 1, 2, 04, 05, 06);
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ target.SendKeys("\t");
+ Browser.Equal(string.Empty, () => boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindMonthTextboxDateTime()
+ {
+ var target = Browser.FindElement(By.Id("month-textbox-datetime"));
+ var boundValue = Browser.FindElement(By.Id("month-textbox-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("month-textbox-datetime-mirror"));
+ var expected = new DateTime(1985, 3, 1);
+ Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
+ // When the value gets displayed the first time it gets truncated to the 1st day,
+ // until there is no change the bound value doesn't get updated.
+ Assert.Equal(expected.AddDays(3), DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected.AddDays(3), DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Clear textbox; value updates to 01/01/0001 because that's the default
+ target.Clear();
+ expected = default;
+ Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(expected, DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#month-textbox-datetime", "2000-02");
+ expected = new DateTime(2000, 2, 1);
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindMonthTextboxNullableDateTime()
+ {
+ var target = Browser.FindElement(By.Id("month-textbox-nullable-datetime"));
+ var boundValue = Browser.FindElement(By.Id("month-textbox-nullable-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("month-textbox-nullable-datetime-mirror"));
+ Assert.Equal(string.Empty, target.GetAttribute("value"));
+ Assert.Equal(string.Empty, boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ Browser.Equal("", () => boundValue.Text);
+ Assert.Equal("", mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#month-textbox-nullable-datetime", "2000-02");
+ var expected = new DateTime(2000, 2, 1);
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ target.SendKeys("\t");
+ Browser.Equal(string.Empty, () => boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindTimeTextboxDateTime()
+ {
+ var target = Browser.FindElement(By.Id("time-textbox-datetime"));
+ var boundValue = Browser.FindElement(By.Id("time-textbox-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("time-textbox-datetime-mirror"));
+ var expected = DateTime.Now.Date.AddHours(8).AddMinutes(5);
+ Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(expected, DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Clear textbox; value updates to 00:00 because that's the default
+ target.Clear();
+ expected = default;
+ Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(default, DateTime.Parse(boundValue.Text));
+ Assert.Equal(default, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#time-textbox-datetime", "04:05");
+ expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 0));
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindTimeTextboxNullableDateTime()
+ {
+ var target = Browser.FindElement(By.Id("time-textbox-nullable-datetime"));
+ var boundValue = Browser.FindElement(By.Id("time-textbox-nullable-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("time-textbox-nullable-datetime-mirror"));
+ Assert.Equal(string.Empty, target.GetAttribute("value"));
+ Assert.Equal(string.Empty, boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ Browser.Equal("", () => boundValue.Text);
+ Assert.Equal("", mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#time-textbox-nullable-datetime", "05:06");
+ var expected = DateTime.Now.Date.Add(new TimeSpan(05, 06, 0));
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ target.SendKeys("\t");
+ Browser.Equal(string.Empty, () => boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindTimeStepTextboxDateTime()
+ {
+ var target = Browser.FindElement(By.Id("time-step-textbox-datetime"));
+ var boundValue = Browser.FindElement(By.Id("time-step-textbox-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("time-step-textbox-datetime-mirror"));
+ var expected = DateTime.Now.Date.Add(new TimeSpan(8, 5, 30));
+ Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(expected, DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Clear textbox; value updates to 00:00 because that's the default
+ target.Clear();
+ expected = default;
+ Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value")));
+ Assert.Equal(default, DateTime.Parse(boundValue.Text));
+ Assert.Equal(default, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#time-step-textbox-datetime", "04:05:06");
+ expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 6));
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+ }
+
+ // For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
+ // Blazor have different formatting behaviour by default.
+ [Fact]
+ public void CanBindTimeStepTextboxNullableDateTime()
+ {
+ var target = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime"));
+ var boundValue = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime-value"));
+ var mirrorValue = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime-mirror"));
+ Assert.Equal(string.Empty, target.GetAttribute("value"));
+ Assert.Equal(string.Empty, boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ Browser.Equal("", () => boundValue.Text);
+ Assert.Equal("", mirrorValue.GetAttribute("value"));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ // We have to do it this way because the browser gets in the way when sending keys to the input
+ // element directly.
+ ApplyInputValue("#time-step-textbox-nullable-datetime", "05:06");
+ var expected = DateTime.Now.Date.Add(new TimeSpan(05, 06, 0));
+ Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
+ Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
+
+ // Modify target; verify value is updated and that textboxes linked to the same data are updated
+ target.Clear();
+ target.SendKeys("\t");
+ Browser.Equal(string.Empty, () => boundValue.Text);
+ Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
+ }
+
+ // Applies an input through javascript to datetime-local/month/time controls.
+ private void ApplyInputValue(string cssSelector, string value)
+ {
+ // It's very difficult to enter an invalid value into an , because
+ // most combinations of keystrokes get normalized to something valid. Additionally,
+ // using Selenium's SendKeys interacts unpredictably with this normalization logic,
+ // most likely based on timings. As a workaround, use JS to apply the values. This
+ // should only be used when strictly necessary, as it doesn't represent actual user
+ // interaction as authentically as SendKeys in other cases.
+ var javascript = (IJavaScriptExecutor)Browser;
+ javascript.ExecuteScript(
+ $"var elem = document.querySelector('{cssSelector}');"
+ + $"elem.value = '{value}';"
+ + "elem.dispatchEvent(new KeyboardEvent('change'));");
+ }
}
}
diff --git a/src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs b/src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs
new file mode 100644
index 0000000000..7c0705acde
--- /dev/null
+++ b/src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs
@@ -0,0 +1,67 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using BasicTestApp;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.AspNetCore.E2ETesting;
+using OpenQA.Selenium;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components.E2ETest.Tests
+{
+ [Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
+ public class ErrorNotificationClientSideTest : ServerTestBase>
+ {
+ public ErrorNotificationClientSideTest(
+ BrowserFixture browserFixture,
+ ToggleExecutionModeServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
+ {
+ }
+
+ protected override void InitializeAsyncCore()
+ {
+ // On WebAssembly, page reloads are expensive so skip if possible
+ Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
+ Browser.MountTestComponent();
+ Browser.Exists(By.Id("blazor-error-ui"));
+ Browser.Exists(By.TagName("button"));
+ }
+
+ [Fact]
+ public void ShowsErrorNotification_OnError_Dismiss()
+ {
+ var errorUi = Browser.FindElement(By.Id("blazor-error-ui"));
+ Assert.Equal("none", errorUi.GetCssValue("display"));
+
+ var causeErrorButton = Browser.FindElement(By.TagName("button"));
+ causeErrorButton.Click();
+
+ Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
+
+ var reload = Browser.FindElement(By.ClassName("reload"));
+ reload.Click();
+
+ Browser.DoesNotExist(By.TagName("button"));
+ }
+
+ [Fact]
+ public void ShowsErrorNotification_OnError_Reload()
+ {
+ var causeErrorButton = Browser.Exists(By.TagName("button"));
+ var errorUi = Browser.FindElement(By.Id("blazor-error-ui"));
+ Assert.Equal("none", errorUi.GetCssValue("display"));
+
+ causeErrorButton.Click();
+ Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"));
+
+ var dismiss = Browser.FindElement(By.ClassName("dismiss"));
+ dismiss.Click();
+ Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: none;']"));
+ }
+ }
+}
diff --git a/src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs b/src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs
new file mode 100644
index 0000000000..6d2e860573
--- /dev/null
+++ b/src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs
@@ -0,0 +1,25 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using BasicTestApp;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests;
+using Microsoft.AspNetCore.E2ETesting;
+using OpenQA.Selenium;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components.E2ETest.Tests
+{
+ [Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
+ public class ErrorNotificationServerSideTest : ErrorNotificationClientSideTest
+ {
+ public ErrorNotificationServerSideTest(
+ BrowserFixture browserFixture,
+ ToggleExecutionModeServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture.WithServerExecution(), output)
+ {
+ }
+ }
+}
diff --git a/src/Components/test/E2ETest/Tests/InteropTest.cs b/src/Components/test/E2ETest/Tests/InteropTest.cs
index 76d2b0d188..2397ea3f9d 100644
--- a/src/Components/test/E2ETest/Tests/InteropTest.cs
+++ b/src/Components/test/E2ETest/Tests/InteropTest.cs
@@ -11,6 +11,7 @@ using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
+using Xunit.Sdk;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
@@ -68,6 +69,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
["testDtoAsync"] = "Same",
["returnPrimitiveAsync"] = "123",
["returnArrayAsync"] = "first,second",
+ ["syncGenericInstanceMethod"] = @"""Initial value""",
+ ["asyncGenericInstanceMethod"] = @"""Updated value 1""",
};
var expectedSyncValues = new Dictionary
@@ -102,6 +105,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
["testDtoSync"] = "Same",
["returnPrimitive"] = "123",
["returnArray"] = "first,second",
+ ["genericInstanceMethod"] = @"""Updated value 2""",
};
// Include the sync assertions only when running under WebAssembly
@@ -132,13 +136,17 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
// Assert
foreach (var expectedValue in expectedValues)
{
+ var actualValue = actualValues[expectedValue.Key];
if (expectedValue.Key.Contains("Exception"))
{
- Assert.StartsWith(expectedValue.Value, actualValues[expectedValue.Key]);
+ Assert.StartsWith(expectedValue.Value, actualValue);
}
else
{
- Assert.Equal(expectedValue.Value, actualValues[expectedValue.Key]);
+ if (expectedValue.Value != actualValue)
+ {
+ throw new AssertActualExpectedException(expectedValue.Value, actualValue, $"Scenario '{expectedValue.Key}' failed. Expected '{expectedValue.Value}, Actual {actualValue}");
+ }
}
}
}
diff --git a/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor b/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor
index 5cdd9087ea..71024bffb8 100644
--- a/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor
+++ b/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor
@@ -254,6 +254,63 @@
@selectMarkupValue
+