diff --git a/NEWS.md b/NEWS.md index 4c64bea7a..c357db745 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,9 @@ * Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066) +* When `textInput()` is called with `updateOn="blur"`, instead of updating as the user types, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183) + + ## Bug fixes * Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173) diff --git a/R/input-numeric.R b/R/input-numeric.R index 8bcc1a38f..3d7beac2f 100644 --- a/R/input-numeric.R +++ b/R/input-numeric.R @@ -29,22 +29,36 @@ #' A numeric vector of length 1. #' #' @export -numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA, - width = NULL) { +numericInput <- function( + inputId, + label, + value, + min = NA, + max = NA, + step = NA, + width = NULL, + ..., + updateOn = c("change", "blur") +) { + rlang::check_dots_empty() + updateOn <- rlang::arg_match(updateOn) value <- restoreInput(id = inputId, default = value) # build input tag - inputTag <- tags$input(id = inputId, type = "number", class="shiny-input-number form-control", - value = formatNoSci(value)) - if (!is.na(min)) - inputTag$attribs$min = min - if (!is.na(max)) - inputTag$attribs$max = max - if (!is.na(step)) - inputTag$attribs$step = step + inputTag <- tags$input( + id = inputId, + type = "number", + class = "shiny-input-number form-control", + value = formatNoSci(value), + `data-update-on` = updateOn + ) + if (!is.na(min)) inputTag$attribs$min = min + if (!is.na(max)) inputTag$attribs$max = max + if (!is.na(step)) inputTag$attribs$step = step - div(class = "form-group shiny-input-container", + div( + class = "form-group shiny-input-container", style = css(width = validateCssUnit(width)), shinyInputLabel(inputId, label), inputTag diff --git a/R/input-password.R b/R/input-password.R index d3608563b..88b247626 100644 --- a/R/input-password.R +++ b/R/input-password.R @@ -30,12 +30,29 @@ #' shinyApp(ui, server) #' } #' @export -passwordInput <- function(inputId, label, value = "", width = NULL, - placeholder = NULL) { - div(class = "form-group shiny-input-container", +passwordInput <- function( + inputId, + label, + value = "", + width = NULL, + placeholder = NULL, + ..., + updateOn = c("change", "blur") +) { + rlang::check_dots_empty() + updateOn <- rlang::arg_match(updateOn) + + div( + class = "form-group shiny-input-container", style = css(width = validateCssUnit(width)), shinyInputLabel(inputId, label), - tags$input(id = inputId, type="password", class="shiny-input-password form-control", value=value, - placeholder = placeholder) + tags$input( + id = inputId, + type = "password", + class = "shiny-input-password form-control", + value = value, + placeholder = placeholder, + `data-update-on` = updateOn + ) ) } diff --git a/R/input-text.R b/R/input-text.R index ec1b96877..1e21583ec 100644 --- a/R/input-text.R +++ b/R/input-text.R @@ -10,6 +10,14 @@ #' @param placeholder A character string giving the user a hint as to what can #' be entered into the control. Internet Explorer 8 and 9 do not support this #' option. +#' @param ... Ignored, included to require named arguments and for future +#' feature expansion. +#' @param updateOn A character vector specifying when the input should be +#' updated. Options are `"change"` (default) and `"blur"`. Use `"change"` to +#' update the input immediately whenever the value changes. Use `"blur"`to +#' delay the input update until the input loses focus (the user moves away +#' from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for +#' [textAreaInput()]). #' @return A text input control that can be added to a UI definition. #' #' @family input elements @@ -34,15 +42,31 @@ #' unless `value` is provided. #' #' @export -textInput <- function(inputId, label, value = "", width = NULL, - placeholder = NULL) { +textInput <- function( + inputId, + label, + value = "", + width = NULL, + placeholder = NULL, + ..., + updateOn = c("change", "blur") +) { + rlang::check_dots_empty() + updateOn <- rlang::arg_match(updateOn) value <- restoreInput(id = inputId, default = value) - div(class = "form-group shiny-input-container", + div( + class = "form-group shiny-input-container", style = css(width = validateCssUnit(width)), shinyInputLabel(inputId, label), - tags$input(id = inputId, type="text", class="shiny-input-text form-control", value=value, - placeholder = placeholder) + tags$input( + id = inputId, + type = "text", + class = "shiny-input-text form-control", + value = value, + placeholder = placeholder, + `data-update-on` = updateOn + ) ) } diff --git a/R/input-textarea.R b/R/input-textarea.R index 46a982cda..c37fcab4e 100644 --- a/R/input-textarea.R +++ b/R/input-textarea.R @@ -41,8 +41,21 @@ #' unless `value` is provided. #' #' @export -textAreaInput <- function(inputId, label, value = "", width = NULL, height = NULL, - cols = NULL, rows = NULL, placeholder = NULL, resize = NULL) { +textAreaInput <- function( + inputId, + label, + value = "", + width = NULL, + height = NULL, + cols = NULL, + rows = NULL, + placeholder = NULL, + resize = NULL, + ..., + updateOn = c("change", "blur") +) { + rlang::check_dots_empty() + updateOn <- rlang::arg_match(updateOn) value <- restoreInput(id = inputId, default = value) @@ -57,7 +70,8 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL resize = resize ) - div(class = "form-group shiny-input-container", + div( + class = "form-group shiny-input-container", shinyInputLabel(inputId, label), style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"), tags$textarea( @@ -67,6 +81,7 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL style = style, rows = rows, cols = cols, + `data-update-on` = updateOn, value ) ) diff --git a/inst/www/shared/shiny.js b/inst/www/shared/shiny.js index a516f442a..7b51c845e 100644 --- a/inst/www/shared/shiny.js +++ b/inst/www/shared/shiny.js @@ -1453,18 +1453,35 @@ value; } subscribe(el, callback) { - (0, import_jquery13.default)(el).on( - "keyup.textInputBinding input.textInputBinding", - function() { - callback(true); - } - ); - (0, import_jquery13.default)(el).on( - "change.textInputBinding", - function() { + const $el = (0, import_jquery13.default)(el); + const updateOn = $el.data("update-on") || "change"; + if (updateOn === "change") { + $el.on( + "keyup.textInputBinding input.textInputBinding", + function() { + callback(true); + } + ); + } else if (updateOn === "blur") { + $el.on("blur.textInputBinding", function() { + callback(false); + }); + $el.on("keydown.textInputBinding", function(event) { + if (event.key !== "Enter") + return; + if ($el.is("textarea")) { + if (!(event.ctrlKey || event.metaKey)) + return; + } callback(false); + }); + } + $el.on("change.textInputBinding", function() { + if (updateOn === "blur" && $el.is(":focus")) { + return; } - ); + callback(false); + }); } unsubscribe(el) { (0, import_jquery13.default)(el).off(".textInputBinding"); diff --git a/inst/www/shared/shiny.js.map b/inst/www/shared/shiny.js.map index c7b3860fc..1b81eab80 100644 --- a/inst/www/shared/shiny.js.map +++ b/inst/www/shared/shiny.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["globals:jquery", "../../../srcts/src/initialize/browser.ts", "../../../srcts/src/utils/browser.ts", "../../../srcts/src/utils/userAgent.ts", "../../../srcts/src/initialize/disableForm.ts", "../../../srcts/src/initialize/history.ts", "../../../srcts/src/shiny/index.ts", "../../../srcts/src/utils/index.ts", "../../../srcts/src/window/pixelRatio.ts", "../../../srcts/src/utils/object.ts", "../../../srcts/src/bindings/registry.ts", "../../../srcts/src/bindings/input/inputBinding.ts", "../../../srcts/src/bindings/input/actionbutton.ts", "../../../srcts/src/bindings/input/checkbox.ts", "../../../srcts/src/bindings/input/checkboxgroup.ts", "../../../srcts/src/bindings/input/date.ts", "../../../srcts/src/bindings/input/daterange.ts", "../../../srcts/src/bindings/input/fileinput.ts", "../../../srcts/src/file/fileProcessor.ts", "../../../srcts/src/events/inputChanged.ts", "../../../srcts/src/shiny/initedMethods.ts", "../../../srcts/src/bindings/input/number.ts", "../../../srcts/src/bindings/input/text.ts", "../../../srcts/src/bindings/input/password.ts", "../../../srcts/src/bindings/input/radio.ts", "../../../srcts/src/bindings/input/selectInput.ts", "../../../srcts/src/utils/eval.ts", "../../../srcts/src/bindings/input/slider.ts", "../../../srcts/src/bindings/input/tabinput.ts", "../../../srcts/src/bindings/input/textarea.ts", "../../../srcts/src/bindings/input/index.ts", "../../../srcts/src/bindings/output/datatable.ts", "../../../srcts/src/time/debounce.ts", "../../../srcts/src/time/invoke.ts", "../../../srcts/src/time/throttle.ts", "../../../srcts/src/bindings/output/outputBinding.ts", "../../../srcts/src/bindings/output/downloadlink.ts", "../../../srcts/src/bindings/output/html.ts", "../../../srcts/src/shiny/render.ts", "../../../srcts/src/shiny/sendImageSize.ts", "../../../srcts/src/shiny/singletons.ts", "../../../srcts/src/bindings/output/image.ts", "../../../srcts/src/imageutils/createBrush.ts", "../../../srcts/src/imageutils/initCoordmap.ts", "../../../srcts/src/imageutils/initPanelScales.ts", "../../../srcts/src/imageutils/findbox.ts", "../../../srcts/src/imageutils/shiftToRange.ts", "../../../srcts/src/imageutils/createClickInfo.ts", "../../../srcts/src/imageutils/createHandlers.ts", "../../../srcts/src/imageutils/disableDrag.ts", "../../../srcts/src/bindings/output/text.ts", "../../../srcts/src/bindings/output/index.ts", "../../../node_modules/@lit/reactive-element/src/css-tag.ts", "../../../node_modules/@lit/reactive-element/src/reactive-element.ts", "../../../node_modules/lit-html/src/lit-html.ts", "../../../node_modules/lit-element/src/lit-element.ts", "../../../srcts/src/shiny/error.ts", "../../../srcts/src/components/errorConsole.ts", "../../../srcts/src/imageutils/resetBrush.ts", "../../../srcts/src/inputPolicies/inputBatchSender.ts", "../../../srcts/src/inputPolicies/inputDeferDecorator.ts", "../../../srcts/src/inputPolicies/inputEventDecorator.ts", "../../../srcts/src/inputPolicies/splitInputNameType.ts", "../../../srcts/src/inputPolicies/inputNoResendDecorator.ts", "../../../srcts/src/inputPolicies/inputRateDecorator.ts", "../../../srcts/src/inputPolicies/inputValidateDecorator.ts", "../../../srcts/src/utils/promise.ts", "../../../srcts/src/shiny/bind.ts", "../../../srcts/src/bindings/outputAdapter.ts", "../../../srcts/src/shiny/modal.ts", "../../../srcts/src/shiny/notifications.ts", "../../../srcts/src/shiny/reconnectDialog.ts", "../../../srcts/src/shiny/shinyapp.ts", "../../../srcts/src/utils/asyncQueue.ts", "../../../srcts/src/shiny/outputProgress.ts", "../../../srcts/src/window/userAgent.ts", "../../../srcts/src/shiny/reactlog.ts", "../../../srcts/src/initialize/index.ts", "../../../srcts/src/index.ts"], - "sourcesContent": ["module.exports = window.jQuery", "import $ from \"jquery\";\n\nimport { isIE, setIEVersion, setIsIE, setIsQt } from \"../utils/browser\";\nimport { userAgent } from \"../utils/userAgent\";\n\nfunction getIEVersion() {\n const msie = userAgent.indexOf(\"MSIE \");\n\n if (isIE() && msie > 0) {\n // IE 10 or older => return version number\n return parseInt(\n userAgent.substring(msie + 5, userAgent.indexOf(\".\", msie)),\n 10\n );\n }\n const trident = userAgent.indexOf(\"Trident/\");\n\n if (trident > 0) {\n // IE 11 => return version number\n const rv = userAgent.indexOf(\"rv:\");\n\n return parseInt(\n userAgent.substring(rv + 3, userAgent.indexOf(\".\", rv)),\n 10\n );\n }\n return -1;\n}\n\nfunction determineBrowserInfo(): void {\n // For easy handling of Qt quirks using CSS\n\n if (/\\bQt\\//.test(userAgent)) {\n $(document.documentElement).addClass(\"qt\");\n setIsQt(true);\n } else {\n setIsQt(false);\n }\n\n // For Qt on Mac. Note that the target string as of RStudio 1.4.173\n // is \"QtWebEngine\" and does not have a trailing slash.\n if (/\\bQt/.test(userAgent) && /\\bMacintosh/.test(userAgent)) {\n $(document.documentElement).addClass(\"qtmac\");\n }\n\n // Enable special treatment for Qt 5 quirks on Linux\n if (/\\bQt\\/5/.test(userAgent) && /Linux/.test(userAgent)) {\n $(document.documentElement).addClass(\"qt5\");\n }\n\n // Detect IE and older (pre-Chromium) Edge\n setIsIE(/MSIE|Trident|Edge/.test(userAgent));\n\n setIEVersion(getIEVersion());\n}\n\nexport { determineBrowserInfo };\n", "let isQtVal = false;\nlet isIEVal = false;\nlet versionIE = -1;\n\nfunction setIsQt(isQt: boolean): void {\n isQtVal = isQt;\n}\nfunction setIsIE(isIE: boolean): void {\n isIEVal = isIE;\n}\nfunction setIEVersion(versionIE_: number): void {\n versionIE = versionIE_;\n}\n\nfunction isQt(): boolean {\n return isQtVal;\n}\nfunction isIE(): boolean {\n return isIEVal;\n}\n\n// (Name existed before TS conversion)\n// eslint-disable-next-line @typescript-eslint/naming-convention\nfunction IEVersion(): number {\n return versionIE;\n}\n\nexport { isQt, isIE, IEVersion, setIsQt, setIsIE, setIEVersion };\n", "type UserAgent = typeof window.navigator.userAgent;\n\nlet userAgent: UserAgent;\n\nfunction setUserAgent(userAgent_: UserAgent): void {\n userAgent = userAgent_;\n}\n\nexport type { UserAgent };\nexport { userAgent, setUserAgent };\n", "import $ from \"jquery\";\n\nfunction disableFormSubmission(): void {\n // disable form submissions\n $(document).on(\"submit\", \"form:not([action])\", function (e) {\n e.preventDefault();\n });\n}\n\nexport { disableFormSubmission };\n", "import $ from \"jquery\";\n\nfunction trackHistory(): void {\n const origPushState = window.history.pushState;\n\n window.history.pushState = function (...args) {\n const result = origPushState.apply(this, args);\n\n $(document).trigger(\"pushstate\");\n return result;\n };\n}\n\nexport { trackHistory };\n", "import $ from \"jquery\";\n\nimport { InputBinding, OutputBinding } from \"../bindings\";\nimport { initInputBindings } from \"../bindings/input\";\nimport { initOutputBindings } from \"../bindings/output\";\nimport type { BindingRegistry } from \"../bindings/registry\";\nimport { showErrorInClientConsole } from \"../components/errorConsole\";\nimport { resetBrush } from \"../imageutils/resetBrush\";\nimport type { InputPolicy } from \"../inputPolicies\";\nimport {\n InputBatchSender,\n InputDeferDecorator,\n InputEventDecorator,\n InputNoResendDecorator,\n InputRateDecorator,\n InputValidateDecorator,\n} from \"../inputPolicies\";\nimport type { InputPolicyOpts } from \"../inputPolicies/inputPolicy\";\nimport { addDefaultInputOpts } from \"../inputPolicies/inputValidateDecorator\";\nimport { debounce, Debouncer } from \"../time\";\nimport {\n $escape,\n compareVersion,\n getBoundingClientSizeBeforeZoom,\n getComputedLinkColor,\n getStyle,\n hasDefinedProperty,\n mapValues,\n pixelRatio,\n} from \"../utils\";\nimport { createInitStatus, type InitStatusPromise } from \"../utils/promise\";\nimport type { BindInputsCtx, BindScope } from \"./bind\";\nimport { bindAll, unbindAll, _bindAll } from \"./bind\";\nimport type {\n shinyBindAll,\n shinyForgetLastInputValue,\n shinyInitializeInputs,\n shinySetInputValue,\n shinyUnbindAll,\n} from \"./initedMethods\";\nimport { setFileInputBinding, setShinyObj } from \"./initedMethods\";\nimport { removeModal, showModal } from \"./modal\";\nimport { removeNotification, showNotification } from \"./notifications\";\nimport { hideReconnectDialog, showReconnectDialog } from \"./reconnectDialog\";\nimport {\n registerDependency,\n renderContent,\n renderContentAsync,\n renderDependencies,\n renderDependenciesAsync,\n renderHtml,\n renderHtmlAsync,\n} from \"./render\";\nimport { sendImageSizeFns } from \"./sendImageSize\";\nimport { addCustomMessageHandler, ShinyApp, type Handler } from \"./shinyapp\";\nimport { registerNames as singletonsRegisterNames } from \"./singletons\";\n\nclass ShinyClass {\n version: string;\n $escape: typeof $escape;\n compareVersion: typeof compareVersion;\n inputBindings: BindingRegistry;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n InputBinding: typeof InputBinding;\n outputBindings: BindingRegistry;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n OutputBinding: typeof OutputBinding;\n resetBrush: typeof resetBrush;\n notifications: {\n show: typeof showNotification;\n remove: typeof removeNotification;\n };\n modal: { show: typeof showModal; remove: typeof removeModal };\n showReconnectDialog: typeof showReconnectDialog;\n hideReconnectDialog: typeof hideReconnectDialog;\n renderDependenciesAsync: typeof renderDependenciesAsync;\n renderDependencies: typeof renderDependencies;\n renderContentAsync: typeof renderContentAsync;\n renderContent: typeof renderContent;\n renderHtmlAsync: typeof renderHtmlAsync;\n renderHtml: typeof renderHtml;\n addCustomMessageHandler: typeof addCustomMessageHandler;\n\n // The following are added in the initialization, by initShiny()\n createSocket?: () => WebSocket;\n user?: string;\n progressHandlers?: ShinyApp[\"progressHandlers\"];\n shinyapp?: ShinyApp;\n setInputValue?: typeof shinySetInputValue;\n onInputChange?: typeof shinySetInputValue;\n forgetLastInputValue?: typeof shinyForgetLastInputValue;\n bindAll?: typeof shinyBindAll;\n unbindAll?: typeof shinyUnbindAll;\n initializeInputs?: typeof shinyInitializeInputs;\n\n // Promise-like object that is resolved after initialization.\n initializedPromise: InitStatusPromise;\n\n // Eventually deprecate\n // For old-style custom messages - should deprecate and migrate to new\n oncustommessage?: Handler;\n\n constructor() {\n // `process.env.SHINY_VERSION` is overwritten to the Shiny version at build time.\n // During testing, the `Shiny.version` will be `\"development\"`\n this.version = process.env.SHINY_VERSION || \"development\";\n\n const { inputBindings, fileInputBinding } = initInputBindings();\n const { outputBindings } = initOutputBindings();\n\n setFileInputBinding(fileInputBinding);\n\n this.$escape = $escape;\n this.compareVersion = compareVersion;\n this.inputBindings = inputBindings;\n this.InputBinding = InputBinding;\n this.outputBindings = outputBindings;\n this.OutputBinding = OutputBinding;\n this.resetBrush = resetBrush;\n this.notifications = {\n show: showNotification,\n remove: removeNotification,\n };\n this.modal = { show: showModal, remove: removeModal };\n\n this.addCustomMessageHandler = addCustomMessageHandler;\n this.showReconnectDialog = showReconnectDialog;\n this.hideReconnectDialog = hideReconnectDialog;\n this.renderDependenciesAsync = renderDependenciesAsync;\n this.renderDependencies = renderDependencies;\n this.renderContentAsync = renderContentAsync;\n this.renderContent = renderContent;\n this.renderHtmlAsync = renderHtmlAsync;\n this.renderHtml = renderHtml;\n\n this.initializedPromise = createInitStatus();\n\n $(() => {\n // Init Shiny a little later than document ready, so user code can\n // run first (i.e. to register bindings)\n setTimeout(async () => {\n try {\n await this.initialize();\n } catch (e) {\n showErrorInClientConsole(e);\n throw e;\n }\n }, 1);\n });\n }\n\n /**\n * Method to check if Shiny is running in development mode. By packaging as a\n * method, we can we can avoid needing to look for the `__SHINY_DEV_MODE__`\n * variable in the global scope.\n * @returns `true` if Shiny is running in development mode, `false` otherwise.\n */\n inDevMode(): boolean {\n if (\"__SHINY_DEV_MODE__\" in window)\n return Boolean(window.__SHINY_DEV_MODE__);\n\n return false;\n }\n\n async initialize(): Promise {\n setShinyObj(this);\n this.shinyapp = new ShinyApp();\n const shinyapp = this.shinyapp;\n\n this.progressHandlers = shinyapp.progressHandlers;\n\n const inputBatchSender = new InputBatchSender(shinyapp);\n const inputsNoResend = new InputNoResendDecorator(inputBatchSender);\n const inputsEvent = new InputEventDecorator(inputsNoResend);\n const inputsRate = new InputRateDecorator(inputsEvent);\n const inputsDefer = new InputDeferDecorator(inputsEvent);\n\n let target: InputPolicy;\n\n if ($('input[type=\"submit\"], button[type=\"submit\"]').length > 0) {\n // If there is a submit button on the page, use defer decorator\n target = inputsDefer;\n\n $('input[type=\"submit\"], button[type=\"submit\"]').each(function () {\n $(this).click(function (event) {\n event.preventDefault();\n inputsDefer.submit();\n });\n });\n } else {\n // By default, use rate decorator\n target = inputsRate;\n }\n\n const inputs = new InputValidateDecorator(target);\n\n this.setInputValue = this.onInputChange = function (\n name: string,\n value: unknown,\n opts: Partial = {}\n ): void {\n const newOpts = addDefaultInputOpts(opts);\n\n inputs.setInput(name, value, newOpts);\n };\n\n // By default, Shiny deduplicates input value changes; that is, if\n // `setInputValue` is called with the same value as the input already\n // has, the call is ignored (unless opts.priority = \"event\"). Calling\n // `forgetLastInputValue` tells Shiny that the very next call to\n // `setInputValue` for this input id shouldn't be ignored, even if it\n // is a dupe of the existing value.\n this.forgetLastInputValue = function (name) {\n inputsNoResend.forget(name);\n };\n\n // MUST be called after `setShiny()`\n const inputBindings = this.inputBindings;\n const outputBindings = this.outputBindings;\n\n function shinyBindCtx(): BindInputsCtx {\n return {\n inputs,\n inputsRate,\n sendOutputHiddenState,\n maybeAddThemeObserver,\n inputBindings,\n outputBindings,\n initDeferredIframes,\n };\n }\n\n this.bindAll = async function (scope: BindScope) {\n await bindAll(shinyBindCtx(), scope);\n };\n this.unbindAll = function (scope: BindScope, includeSelf = false) {\n unbindAll(shinyBindCtx(), scope, includeSelf);\n };\n\n // Calls .initialize() for all of the input objects in all input bindings,\n // in the given scope.\n function initializeInputs(scope: BindScope = document.documentElement) {\n const bindings = inputBindings.getBindings();\n\n // Iterate over all bindings\n for (let i = 0; i < bindings.length; i++) {\n const binding = bindings[i].binding;\n const inputObjects = binding.find(scope);\n\n if (inputObjects) {\n // Iterate over all input objects for this binding\n for (let j = 0; j < inputObjects.length; j++) {\n const $inputObjectJ = $(inputObjects[j]);\n\n if (!$inputObjectJ.data(\"_shiny_initialized\")) {\n $inputObjectJ.data(\"_shiny_initialized\", true);\n binding.initialize(inputObjects[j]);\n }\n }\n }\n }\n }\n this.initializeInputs = initializeInputs;\n\n function getIdFromEl(el: HTMLElement) {\n const $el = $(el);\n const bindingAdapter = $el.data(\"shiny-output-binding\");\n\n if (!bindingAdapter) return null;\n else return bindingAdapter.getId();\n }\n\n // Initialize all input objects in the document, before binding\n initializeInputs(document.documentElement);\n\n // The input values returned by _bindAll() each have a structure like this:\n // { value: 123, opts: { ... } }\n // We want to only keep the value. This is because when the initialValues is\n // passed to ShinyApp.connect(), the ShinyApp object stores the\n // initialValues object for the duration of the session, and the opts may\n // have a reference to the DOM element, which would prevent it from being\n // GC'd.\n const initialValues = mapValues(\n await _bindAll(shinyBindCtx(), document.documentElement),\n (x) => x.value\n );\n\n // The server needs to know the size of each image and plot output element,\n // in case it is auto-sizing\n $(\".shiny-image-output, .shiny-plot-output, .shiny-report-size\").each(\n function () {\n const id = getIdFromEl(this),\n rect = getBoundingClientSizeBeforeZoom(this);\n\n if (rect.width !== 0 || rect.height !== 0) {\n initialValues[\".clientdata_output_\" + id + \"_width\"] = rect.width;\n initialValues[\".clientdata_output_\" + id + \"_height\"] = rect.height;\n }\n }\n );\n\n function getComputedBgColor(\n el: HTMLElement | null\n ): string | null | undefined {\n if (!el) {\n // Top of document, can't recurse further\n return null;\n }\n\n const bgColor = getStyle(el, \"background-color\");\n\n if (!bgColor) return bgColor;\n const m = bgColor.match(\n /^rgba\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*\\)$/\n );\n\n if (bgColor === \"transparent\" || (m && parseFloat(m[4]) === 0)) {\n // No background color on this element. See if it has a background image.\n const bgImage = getStyle(el, \"background-image\");\n\n if (bgImage && bgImage !== \"none\") {\n // Failed to detect background color, since it has a background image\n return null;\n } else {\n // Recurse\n return getComputedBgColor(el.parentElement);\n }\n }\n return bgColor;\n }\n\n function getComputedFont(el: HTMLElement) {\n const fontFamily = getStyle(el, \"font-family\");\n const fontSize = getStyle(el, \"font-size\");\n\n return {\n families: fontFamily?.replace(/\"/g, \"\").split(\", \"),\n size: fontSize,\n };\n }\n\n $(\".shiny-image-output, .shiny-plot-output, .shiny-report-theme\").each(\n function () {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const el = this;\n const id = getIdFromEl(el);\n\n initialValues[\".clientdata_output_\" + id + \"_bg\"] =\n getComputedBgColor(el);\n initialValues[\".clientdata_output_\" + id + \"_fg\"] = getStyle(\n el,\n \"color\"\n );\n initialValues[\".clientdata_output_\" + id + \"_accent\"] =\n getComputedLinkColor(el);\n initialValues[\".clientdata_output_\" + id + \"_font\"] =\n getComputedFont(el);\n maybeAddThemeObserver(el);\n }\n );\n\n // Resend computed styles if *an output element's* class or style attribute changes.\n // This gives us some level of confidence that getCurrentOutputInfo() will be\n // properly invalidated if output container is mutated; but unfortunately,\n // we don't have a reasonable way to detect change in *inherited* styles\n // (other than session$setCurrentTheme())\n // https://github.com/rstudio/shiny/issues/3196\n // https://github.com/rstudio/shiny/issues/2998\n function maybeAddThemeObserver(el: HTMLElement): void {\n if (!window.MutationObserver) {\n return; // IE10 and lower\n }\n\n const cl = el.classList;\n const reportTheme =\n cl.contains(\"shiny-image-output\") ||\n cl.contains(\"shiny-plot-output\") ||\n cl.contains(\"shiny-report-theme\");\n\n if (!reportTheme) {\n return;\n }\n\n const $el = $(el);\n\n if ($el.data(\"shiny-theme-observer\")) {\n return; // i.e., observer is already observing\n }\n\n const observerCallback = new Debouncer(null, () => doSendTheme(el), 100);\n const observer = new MutationObserver(() =>\n observerCallback.normalCall()\n );\n const config = { attributes: true, attributeFilter: [\"style\", \"class\"] };\n\n observer.observe(el, config);\n $el.data(\"shiny-theme-observer\", observer);\n }\n\n function doSendTheme(el: HTMLElement): void {\n // Sending theme info on error isn't necessary (it'd add an unnecessary additional round-trip)\n if (el.classList.contains(\"shiny-output-error\")) {\n return;\n }\n const id = getIdFromEl(el);\n\n inputs.setInput(\n \".clientdata_output_\" + id + \"_bg\",\n getComputedBgColor(el)\n );\n inputs.setInput(\n \".clientdata_output_\" + id + \"_fg\",\n getStyle(el, \"color\")\n );\n inputs.setInput(\n \".clientdata_output_\" + id + \"_accent\",\n getComputedLinkColor(el)\n );\n inputs.setInput(\n \".clientdata_output_\" + id + \"_font\",\n getComputedFont(el)\n );\n }\n\n function doSendImageSize() {\n $(\".shiny-image-output, .shiny-plot-output, .shiny-report-size\").each(\n function () {\n const id = getIdFromEl(this),\n rect = getBoundingClientSizeBeforeZoom(this);\n\n if (rect.width !== 0 || rect.height !== 0) {\n inputs.setInput(\".clientdata_output_\" + id + \"_width\", rect.width);\n inputs.setInput(\n \".clientdata_output_\" + id + \"_height\",\n rect.height\n );\n }\n }\n );\n\n $(\".shiny-image-output, .shiny-plot-output, .shiny-report-theme\").each(\n function () {\n doSendTheme(this);\n }\n );\n\n $(\".shiny-bound-output\").each(function () {\n const $this = $(this),\n binding = $this.data(\"shiny-output-binding\");\n\n $this.trigger({\n type: \"shiny:visualchange\",\n // @ts-expect-error; Can not remove info on a established, malformed Event object\n visible: !isHidden(this),\n binding: binding,\n });\n binding.onResize();\n });\n }\n\n sendImageSizeFns.setImageSend(inputBatchSender, doSendImageSize);\n\n // Return true if the object or one of its ancestors in the DOM tree has\n // style='display:none'; otherwise return false.\n function isHidden(obj: HTMLElement | null): boolean {\n // null means we've hit the top of the tree. If width or height is\n // non-zero, then we know that no ancestor has display:none.\n if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {\n return false;\n } else if (getStyle(obj, \"display\") === \"none\") {\n return true;\n } else {\n return isHidden(obj.parentNode as HTMLElement | null);\n }\n }\n let lastKnownVisibleOutputs: { [key: string]: boolean } = {};\n // Set initial state of outputs to hidden, if needed\n\n $(\".shiny-bound-output\").each(function () {\n const id = getIdFromEl(this);\n\n if (isHidden(this)) {\n initialValues[\".clientdata_output_\" + id + \"_hidden\"] = true;\n } else {\n lastKnownVisibleOutputs[id] = true;\n initialValues[\".clientdata_output_\" + id + \"_hidden\"] = false;\n }\n });\n // Send update when hidden state changes\n function doSendOutputHiddenState() {\n const visibleOutputs: { [key: string]: boolean } = {};\n\n $(\".shiny-bound-output\").each(function () {\n const id = getIdFromEl(this);\n\n delete lastKnownVisibleOutputs[id];\n // Assume that the object is hidden when width and height are 0\n const hidden = isHidden(this),\n evt = {\n type: \"shiny:visualchange\",\n visible: !hidden,\n };\n\n if (hidden) {\n inputs.setInput(\".clientdata_output_\" + id + \"_hidden\", true);\n } else {\n visibleOutputs[id] = true;\n inputs.setInput(\".clientdata_output_\" + id + \"_hidden\", false);\n }\n const $this = $(this);\n\n // @ts-expect-error; Can not remove info on a established, malformed Event object\n evt.binding = $this.data(\"shiny-output-binding\");\n // @ts-expect-error; Can not remove info on a established, malformed Event object\n $this.trigger(evt);\n });\n // Anything left in lastKnownVisibleOutputs is orphaned\n for (const name in lastKnownVisibleOutputs) {\n if (hasDefinedProperty(lastKnownVisibleOutputs, name))\n inputs.setInput(\".clientdata_output_\" + name + \"_hidden\", true);\n }\n // Update the visible outputs for next time\n lastKnownVisibleOutputs = visibleOutputs;\n }\n // sendOutputHiddenState gets called each time DOM elements are shown or\n // hidden. This can be in the hundreds or thousands of times at startup.\n // We'll debounce it, so that we do the actual work once per tick.\n const sendOutputHiddenStateDebouncer = new Debouncer(\n null,\n doSendOutputHiddenState,\n 0\n );\n\n function sendOutputHiddenState() {\n sendOutputHiddenStateDebouncer.normalCall();\n }\n // We need to make sure doSendOutputHiddenState actually gets called before\n // the inputBatchSender sends data to the server. The lastChanceCallback\n // here does that - if the debouncer has a pending call, flush it.\n inputBatchSender.lastChanceCallback.push(function () {\n if (sendOutputHiddenStateDebouncer.isPending())\n sendOutputHiddenStateDebouncer.immediateCall();\n });\n\n // Given a namespace and a handler function, return a function that invokes\n // the handler only when e's namespace matches. For example, if the\n // namespace is \"bs\", it would match when e.namespace is \"bs\" or \"bs.tab\".\n // If the namespace is \"bs.tab\", it would match for \"bs.tab\", but not \"bs\".\n function filterEventsByNamespace(\n namespace: string,\n handler: (...handlerArgs: any[]) => void,\n ...args: any[]\n ) {\n const namespaceArr = namespace.split(\".\");\n\n return function (this: HTMLElement, e: JQuery.TriggeredEvent) {\n const eventNamespace = e.namespace?.split(\".\") ?? [];\n\n // If any of the namespace strings aren't present in this event, quit.\n for (let i = 0; i < namespaceArr.length; i++) {\n if (eventNamespace.indexOf(namespaceArr[i]) === -1) return;\n }\n\n handler.apply(this, [namespaceArr, handler, ...args]);\n };\n }\n\n // The size of each image may change either because the browser window was\n // resized, or because a tab was shown/hidden (hidden elements report size\n // of 0x0). It's OK to over-report sizes because the input pipeline will\n // filter out values that haven't changed.\n $(window).resize(debounce(500, sendImageSizeFns.regular));\n // Need to register callbacks for each Bootstrap 3 class.\n const bs3classes = [\n \"modal\",\n \"dropdown\",\n \"tab\",\n \"tooltip\",\n \"popover\",\n \"collapse\",\n ];\n\n $.each(bs3classes, function (idx, classname) {\n $(document.body).on(\n \"shown.bs.\" + classname + \".sendImageSize\",\n \"*\",\n filterEventsByNamespace(\"bs\", sendImageSizeFns.regular)\n );\n $(document.body).on(\n \"shown.bs.\" +\n classname +\n \".sendOutputHiddenState \" +\n \"hidden.bs.\" +\n classname +\n \".sendOutputHiddenState\",\n \"*\",\n filterEventsByNamespace(\"bs\", sendOutputHiddenState)\n );\n });\n\n // This is needed for Bootstrap 2 compatibility and for non-Bootstrap\n // related shown/hidden events (like conditionalPanel)\n $(document.body).on(\"shown.sendImageSize\", \"*\", sendImageSizeFns.regular);\n $(document.body).on(\n \"shown.sendOutputHiddenState hidden.sendOutputHiddenState\",\n \"*\",\n sendOutputHiddenState\n );\n\n // Send initial pixel ratio, and update it if it changes\n initialValues[\".clientdata_pixelratio\"] = pixelRatio();\n $(window).resize(function () {\n inputs.setInput(\".clientdata_pixelratio\", pixelRatio());\n });\n\n // Send initial URL\n initialValues[\".clientdata_url_protocol\"] = window.location.protocol;\n initialValues[\".clientdata_url_hostname\"] = window.location.hostname;\n initialValues[\".clientdata_url_port\"] = window.location.port;\n initialValues[\".clientdata_url_pathname\"] = window.location.pathname;\n\n // Send initial URL search (query string) and update it if it changes\n initialValues[\".clientdata_url_search\"] = window.location.search;\n\n $(window).on(\"pushstate\", function (e) {\n inputs.setInput(\".clientdata_url_search\", window.location.search);\n return;\n e;\n });\n\n $(window).on(\"popstate\", function (e) {\n inputs.setInput(\".clientdata_url_search\", window.location.search);\n return;\n e;\n });\n\n // This is only the initial value of the hash. The hash can change, but\n // a reactive version of this isn't sent because watching for changes can\n // require polling on some browsers. The JQuery hashchange plugin can be\n // used if this capability is important.\n initialValues[\".clientdata_url_hash_initial\"] = window.location.hash;\n initialValues[\".clientdata_url_hash\"] = window.location.hash;\n\n $(window).on(\"hashchange\", function (e) {\n inputs.setInput(\".clientdata_url_hash\", window.location.hash);\n return;\n e;\n });\n\n // The server needs to know what singletons were rendered as part of\n // the page loading\n const singletonText = (initialValues[\".clientdata_singletons\"] = $(\n 'script[type=\"application/shiny-singletons\"]'\n ).text());\n\n singletonsRegisterNames(singletonText.split(/,/));\n\n const dependencyText = $(\n 'script[type=\"application/html-dependencies\"]'\n ).text();\n\n $.each(dependencyText.split(/;/), function (i, depStr) {\n const match = /\\s*^(.+)\\[(.+)\\]\\s*$/.exec(depStr);\n\n if (match) {\n registerDependency(match[1], match[2]);\n }\n });\n\n // We've collected all the initial values--start the server process!\n inputsNoResend.reset(initialValues);\n shinyapp.connect(initialValues);\n $(document).one(\"shiny:connected\", () => {\n initDeferredIframes();\n });\n\n $(document).one(\"shiny:sessioninitialized\", () => {\n this.initializedPromise.resolve();\n });\n }\n}\n\n// Give any deferred iframes a chance to load.\nfunction initDeferredIframes(): void {\n // TODO-barret; This method uses `window.Shiny`. Could be replaced with `fullShinyObj_.shinyapp?.isConnected()`,\n // but that would not use `window.Shiny`. Is it a problem???\n if (\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;\n // Can not expect error when combining with window available Shiny definition\n !window.Shiny ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;\n // Can not expect error when combining with window available Shiny definition\n !window.Shiny.shinyapp ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;\n // Can not expect error when combining with window available Shiny definition\n !window.Shiny.shinyapp.isConnected()\n ) {\n // If somehow we accidentally call this before the server connection is\n // established, just ignore the call. At the time of this writing it\n // doesn't happen, but it's easy to imagine a later refactoring putting\n // us in this situation and it'd be hard to notice with either manual\n // testing or automated tests, because the only effect is on HTTP request\n // timing. (Update: Actually Aron saw this being called without even\n // window.Shiny being defined, but it was hard to repro.)\n return;\n }\n\n $(\".shiny-frame-deferred\").each(function (i, el) {\n const $el = $(el);\n\n $el.removeClass(\"shiny-frame-deferred\");\n // @ts-expect-error; If it is undefined, set using the undefined value\n $el.attr(\"src\", $el.attr(\"data-deferred-src\"));\n $el.attr(\"data-deferred-src\", null);\n });\n}\n\nexport { ShinyClass };\n", "import $ from \"jquery\";\nimport { windowDevicePixelRatio } from \"../window/pixelRatio\";\nimport type { MapValuesUnion, MapWithResult } from \"./extraTypes\";\nimport { hasDefinedProperty, hasOwnProperty } from \"./object\";\n\nfunction escapeHTML(str: string): string {\n /* eslint-disable @typescript-eslint/naming-convention */\n const escaped: { [key: string]: string } = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n // eslint-disable-next-line prettier/prettier\n '\"': \""\",\n \"'\": \"'\",\n \"/\": \"/\",\n };\n /* eslint-enable @typescript-eslint/naming-convention */\n\n return str.replace(/[&<>'\"/]/g, function (m) {\n return escaped[m] as string;\n });\n}\n\nfunction randomId(): string {\n return Math.floor(0x100000000 + Math.random() * 0xf00000000).toString(16);\n}\n\nfunction strToBool(str: string): boolean | undefined {\n if (!str || !str.toLowerCase) return undefined;\n\n switch (str.toLowerCase()) {\n case \"true\":\n return true;\n case \"false\":\n return false;\n default:\n return undefined;\n }\n}\n\n// A wrapper for getComputedStyle that is compatible with older browsers.\n// This is significantly faster than jQuery's .css() function.\nfunction getStyle(el: Element, styleProp: string): string | undefined {\n let x = undefined;\n\n if (\"currentStyle\" in el) {\n // @ts-expect-error; Old, IE 5+ attribute only - https://developer.mozilla.org/en-US/docs/Web/API/Element/currentStyle\n x = el.currentStyle[styleProp];\n } else {\n // getComputedStyle can return null when we're inside a hidden iframe on\n // Firefox; don't attempt to retrieve style props in this case.\n // https://bugzilla.mozilla.org/show_bug.cgi?id=548397\n const style = document?.defaultView?.getComputedStyle(el, null);\n\n if (style) x = style.getPropertyValue(styleProp);\n }\n return x;\n}\n\n// Convert a number to a string with leading zeros\nfunction padZeros(n: number, digits: number): string {\n let str = n.toString();\n\n while (str.length < digits) str = \"0\" + str;\n return str;\n}\n\n// Round to a specified number of significant digits.\nfunction roundSignif(x: number, digits = 1): number {\n if (digits < 1) throw \"Significant digits must be at least 1.\";\n\n // This converts to a string and back to a number, which is inelegant, but\n // is less prone to FP rounding error than an alternate method which used\n // Math.round().\n return parseFloat(x.toPrecision(digits));\n}\n\n// Take a string with format \"YYYY-MM-DD\" and return a Date object.\n// IE8 and QTWebKit don't support YYYY-MM-DD, but they support YYYY/MM/DD\nfunction parseDate(dateString: string): Date {\n let date = new Date(dateString);\n\n if (date.toString() === \"Invalid Date\") {\n date = new Date(dateString.replace(/-/g, \"/\"));\n }\n return date;\n}\n\n// Given a Date object, return a string in yyyy-mm-dd format, using the\n// UTC date. This may be a day off from the date in the local time zone.\nfunction formatDateUTC(x: Date): string;\nfunction formatDateUTC(date: Date | null): string | null {\n if (date instanceof Date) {\n return (\n date.getUTCFullYear() +\n \"-\" +\n padZeros(date.getUTCMonth() + 1, 2) +\n \"-\" +\n padZeros(date.getUTCDate(), 2)\n );\n } else {\n return null;\n }\n}\n\n// Given an element and a function(width, height), returns a function(). When\n// the output function is called, it calls the input function with the offset\n// width and height of the input element--but only if the size of the element\n// is non-zero and the size is different than the last time the output\n// function was called.\n//\n// Basically we are trying to filter out extraneous calls to func, so that\n// when the window size changes or whatever, we don't run resize logic for\n// elements that haven't actually changed size or aren't visible anyway.\ntype LastSizeInterface = {\n w?: number;\n h?: number;\n};\nfunction makeResizeFilter(\n el: HTMLElement,\n func: (\n width: HTMLElement[\"offsetWidth\"],\n height: HTMLElement[\"offsetHeight\"]\n ) => void\n): () => void {\n let lastSize: LastSizeInterface = {};\n\n return function () {\n const rect = el.getBoundingClientRect();\n const size = { w: rect.width, h: rect.height };\n\n if (size.w === 0 && size.h === 0) return;\n if (size.w === lastSize.w && size.h === lastSize.h) return;\n lastSize = size;\n func(size.w, size.h);\n };\n}\n\nfunction pixelRatio(): number {\n if (windowDevicePixelRatio()) {\n return Math.round(windowDevicePixelRatio() * 100) / 100;\n } else {\n return 1;\n }\n}\n\nfunction getBoundingClientSizeBeforeZoom(el: HTMLElement): {\n width: number;\n height: number;\n} {\n const rect = el.getBoundingClientRect();\n // Cast to any because currentCSSZoom isn't in the type def of HTMLElement\n // TODO: typescript >= 5.5.2 added this property to the type definition\n const zoom = (el as any).currentCSSZoom || 1;\n return {\n width: rect.width / zoom,\n height: rect.height / zoom,\n };\n}\n\n// Takes a string expression and returns a function that takes an argument.\n//\n// When the function is executed, it will evaluate that expression using\n// \"with\" on the argument value, and return the result.\nfunction scopeExprToFunc(expr: string): (scope: unknown) => unknown {\n /*jshint evil: true */\n const exprEscaped = expr\n .replace(/[\\\\\"']/g, \"\\\\$&\")\n // eslint-disable-next-line no-control-regex\n .replace(/\\u0000/g, \"\\\\0\")\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n // \\b has a special meaning; need [\\b] to match backspace char.\n .replace(/[\\b]/g, \"\\\\b\");\n\n let func: () => unknown;\n\n try {\n // @ts-expect-error; Do not know how to type this _dangerous_ situation\n func = new Function(\n `with (this) {\n try {\n return (${expr});\n } catch (e) {\n console.error('Error evaluating expression: ${exprEscaped}');\n throw e;\n }\n }`\n );\n } catch (e) {\n console.error(\"Error parsing expression: \" + expr);\n throw e;\n }\n\n return function (scope: unknown): unknown {\n return func.call(scope);\n };\n}\n\nfunction asArray(value: T | T[] | null | undefined): T[] {\n if (value === null || value === undefined) return [];\n if (Array.isArray(value)) return value;\n return [value];\n}\n\n// We need a stable sorting algorithm for ordering\n// bindings by priority and insertion order.\nfunction mergeSort(\n list: Item[],\n sortfunc: (a: Item, b: Item) => boolean | number\n): Item[] {\n function merge(a: Item[], b: Item[]) {\n let ia = 0;\n let ib = 0;\n const sorted = [];\n\n while (ia < a.length && ib < b.length) {\n if (sortfunc(a[ia], b[ib]) <= 0) {\n sorted.push(a[ia++]);\n } else {\n sorted.push(b[ib++]);\n }\n }\n while (ia < a.length) sorted.push(a[ia++]);\n while (ib < b.length) sorted.push(b[ib++]);\n return sorted;\n }\n\n // Don't mutate list argument\n list = list.slice(0);\n\n for (let chunkSize = 1; chunkSize < list.length; chunkSize *= 2) {\n for (let i = 0; i < list.length; i += chunkSize * 2) {\n const listA = list.slice(i, i + chunkSize);\n const listB = list.slice(i + chunkSize, i + chunkSize * 2);\n const merged = merge(listA, listB);\n const args = [i, merged.length] as [number, number];\n\n Array.prototype.push.apply(args, merged);\n Array.prototype.splice.apply(list, args);\n }\n }\n\n return list;\n}\n\n// Escape jQuery selector metacharacters: !\"#$%&'()*+,./:;<=>?@[\\]^`{|}~\nfunction $escape(val: undefined): undefined;\nfunction $escape(val: string): string;\nfunction $escape(val: string | undefined): string | undefined {\n if (typeof val === \"undefined\") return val;\n return val.replace(/([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\n// Maps a function over an object, preserving keys. Like the mapValues\n// function from lodash.\nfunction mapValues(\n obj: T,\n f: (value: MapValuesUnion, key: string, object: typeof obj) => R\n): MapWithResult {\n const newObj = {} as MapWithResult;\n\n Object.keys(obj).forEach((key: keyof typeof obj) => {\n newObj[key] = f(obj[key], key as string, obj);\n });\n return newObj;\n}\n\n// This is does the same as Number.isNaN, but that function unfortunately does\n// not exist in any version of IE.\nfunction isnan(x: unknown): boolean {\n return typeof x === \"number\" && isNaN(x);\n}\n\n// Binary equality function used by the equal function.\n// (Name existed before TS conversion)\n// eslint-disable-next-line @typescript-eslint/naming-convention\nfunction _equal(x: unknown, y: unknown): boolean {\n if ($.type(x) === \"object\" && $.type(y) === \"object\") {\n const xo = x as { [key: string]: unknown };\n const yo = y as { [key: string]: unknown };\n\n if (Object.keys(xo).length !== Object.keys(yo).length) return false;\n for (const prop in xo) {\n if (!hasOwnProperty(yo, prop) || !_equal(xo[prop], yo[prop]))\n return false;\n }\n return true;\n } else if ($.type(x) === \"array\" && $.type(y) === \"array\") {\n const xa = x as unknown[];\n const ya = y as unknown[];\n\n if (xa.length !== ya.length) return false;\n for (let i = 0; i < xa.length; i++) if (!_equal(xa[i], ya[i])) return false;\n return true;\n } else {\n return x === y;\n }\n}\n\n// Structural or \"deep\" equality predicate. Tests two or more arguments for\n// equality, traversing arrays and objects (as determined by $.type) as\n// necessary.\n//\n// Objects other than objects and arrays are tested for equality using ===.\nfunction equal(...args: unknown[]): boolean {\n if (args.length < 2)\n throw new Error(\"equal requires at least two arguments.\");\n for (let i = 0; i < args.length - 1; i++) {\n if (!_equal(args[i], args[i + 1])) return false;\n }\n return true;\n}\n\n// Compare version strings like \"1.0.1\", \"1.4-2\". `op` must be a string like\n// \"==\" or \"<\".\nconst compareVersion = function (\n a: string,\n op: \"<\" | \"<=\" | \"==\" | \">\" | \">=\",\n b: string\n): boolean {\n function versionParts(ver: string) {\n return (ver + \"\")\n .replace(/-/, \".\")\n .replace(/(\\.0)+[^.]*$/, \"\")\n .split(\".\");\n }\n\n function cmpVersion(a: string, b: string) {\n const aParts = versionParts(a);\n const bParts = versionParts(b);\n const len = Math.min(aParts.length, bParts.length);\n let cmp;\n\n for (let i = 0; i < len; i++) {\n cmp = parseInt(aParts[i], 10) - parseInt(bParts[i], 10);\n if (cmp !== 0) {\n return cmp;\n }\n }\n return aParts.length - bParts.length;\n }\n\n const diff = cmpVersion(a, b);\n\n if (op === \"==\") return diff === 0;\n else if (op === \">=\") return diff >= 0;\n else if (op === \">\") return diff > 0;\n else if (op === \"<=\") return diff <= 0;\n else if (op === \"<\") return diff < 0;\n else throw `Unknown operator: ${op}`;\n};\n\nfunction updateLabel(\n labelTxt: string | undefined,\n labelNode: JQuery\n): void {\n // Only update if label was specified in the update method\n if (typeof labelTxt === \"undefined\") return;\n if (labelNode.length !== 1) {\n throw new Error(\"labelNode must be of length 1\");\n }\n\n // Should the label be empty?\n const emptyLabel = Array.isArray(labelTxt) && labelTxt.length === 0;\n\n if (emptyLabel) {\n labelNode.addClass(\"shiny-label-null\");\n } else {\n labelNode.text(labelTxt);\n labelNode.removeClass(\"shiny-label-null\");\n }\n}\n\n// Compute the color property of an a tag, scoped within the element\nfunction getComputedLinkColor(el: HTMLElement): string {\n const a = document.createElement(\"a\");\n\n a.href = \"/\";\n const div = document.createElement(\"div\");\n\n div.style.setProperty(\"position\", \"absolute\", \"important\");\n div.style.setProperty(\"top\", \"-1000px\", \"important\");\n div.style.setProperty(\"left\", \"0\", \"important\");\n div.style.setProperty(\"width\", \"30px\", \"important\");\n div.style.setProperty(\"height\", \"10px\", \"important\");\n div.appendChild(a);\n el.appendChild(div);\n const linkColor = window.getComputedStyle(a).getPropertyValue(\"color\");\n\n el.removeChild(div);\n return linkColor;\n}\n\nfunction isBS3(): boolean {\n // @ts-expect-error; Check if `window.bootstrap` exists\n return !window.bootstrap;\n}\n\nfunction toLowerCase(str: T): Lowercase {\n return str.toLowerCase() as Lowercase;\n}\n\nexport {\n escapeHTML,\n randomId,\n strToBool,\n getStyle,\n padZeros,\n roundSignif,\n parseDate,\n formatDateUTC,\n makeResizeFilter,\n pixelRatio,\n getBoundingClientSizeBeforeZoom,\n scopeExprToFunc,\n asArray,\n mergeSort,\n $escape,\n mapValues,\n isnan,\n _equal,\n equal,\n compareVersion,\n updateLabel,\n getComputedLinkColor,\n hasOwnProperty,\n hasDefinedProperty,\n isBS3,\n toLowerCase,\n};\n", "function windowDevicePixelRatio(): number {\n return window.devicePixelRatio;\n}\n\nexport { windowDevicePixelRatio };\n", "import type { NotUndefined } from \"./extraTypes\";\n\n// Inspriation from https://fettblog.eu/typescript-hasownproperty/\n// But mixing with \"NonNullable key of Obj\" instead of \"key to unknown values\"\nfunction hasOwnProperty(\n obj: X,\n prop: Prop\n): obj is X & { [key in NonNullable]: X[key] } {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n}\n\n// Return true if the key exists on the object and the value is not undefined.\n//\n// This method is mainly used in input bindings' `receiveMessage` method.\n// Since we know that the values are sent by Shiny via `{jsonlite}`,\n// then we know that there are no `undefined` values. `null` is possible, but not `undefined`.\nfunction hasDefinedProperty<\n Prop extends keyof X,\n X extends { [key: string]: any }\n>(\n obj: X,\n prop: Prop\n): obj is X & { [key in NonNullable]: NotUndefined } {\n return (\n Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop] !== undefined\n );\n}\n\n// Return type for non-null value\nfunction ifUndefined, Y>(\n value: X,\n alternate: Y\n): NotUndefined;\n// Return type for null value\nfunction ifUndefined(value: X, alternate: Y): Y;\n// Logic\nfunction ifUndefined(value: X, alternate: Y): X | Y {\n if (value === undefined) return alternate;\n return value;\n}\n\nexport { hasOwnProperty, hasDefinedProperty, ifUndefined };\n", "import { mergeSort } from \"../utils\";\n\ninterface BindingBase {\n name: string;\n}\n\ninterface BindingObj {\n binding: Binding;\n priority: number;\n name?: string;\n}\n\nclass BindingRegistry {\n name!: string;\n bindings: Array> = [];\n bindingNames: { [key: string]: BindingObj } = {};\n\n register(binding: Binding, bindingName: string, priority = 0): void {\n const bindingObj = { binding, priority };\n\n this.bindings.unshift(bindingObj);\n if (bindingName) {\n this.bindingNames[bindingName] = bindingObj;\n binding.name = bindingName;\n }\n }\n\n setPriority(bindingName: string, priority: number): void {\n const bindingObj = this.bindingNames[bindingName];\n\n if (!bindingObj)\n throw \"Tried to set priority on unknown binding \" + bindingName;\n bindingObj.priority = priority || 0;\n }\n\n getPriority(bindingName: string): number | false {\n const bindingObj = this.bindingNames[bindingName];\n\n if (!bindingObj) return false;\n return bindingObj.priority;\n }\n\n getBindings(): Array> {\n // Sort the bindings. The ones with higher priority are consulted\n // first; ties are broken by most-recently-registered.\n return mergeSort(this.bindings, function (a, b) {\n return b.priority - a.priority;\n });\n }\n}\n\nexport { BindingRegistry };\n", "import type { RatePolicyModes } from \"../../inputPolicies/inputRateDecorator\";\nimport type { BindScope } from \"../../shiny/bind\";\n\nclass InputBinding {\n name!: string;\n\n // Returns a jQuery object or element array that contains the\n // descendants of scope that match this binding\n find(scope: BindScope): JQuery {\n throw \"Not implemented\";\n scope; // unused var\n }\n\n getId(el: HTMLElement): string {\n return el.getAttribute(\"data-input-id\") || el.id;\n }\n\n // Gives the input a type in case the server needs to know it\n // to deserialize the JSON correctly\n getType(el: HTMLElement): string | null {\n return null;\n el; // unused var\n }\n getValue(el: HTMLElement): any {\n throw \"Not implemented\";\n el; // unused var\n }\n\n // The callback method takes one argument, whose value is boolean. If true,\n // allow deferred (debounce or throttle) sending depending on the value of\n // getRatePolicy. If false, send value immediately. Default behavior is `false`\n subscribe(el: HTMLElement, callback: (value: boolean) => void): void {\n // empty\n el; // unused var\n callback; // unused var\n }\n unsubscribe(el: HTMLElement): void {\n // empty\n el; // unused var\n }\n\n // This is used for receiving messages that tell the input object to do\n // things, such as setting values (including min, max, and others).\n // 'data' should be an object with elements corresponding to value, min,\n // max, etc., as appropriate for the type of input object. It also should\n // trigger a change event.\n receiveMessage(el: HTMLElement, data: unknown): Promise | void {\n throw \"Not implemented\";\n el; // unused var\n data; // unused var\n }\n getState(el: HTMLElement): unknown {\n throw \"Not implemented\";\n el; // unused var\n }\n\n getRatePolicy(\n el: HTMLElement\n ): { policy: RatePolicyModes; delay: number } | null {\n return null;\n el; // unused var\n }\n\n // Some input objects need initialization before being bound. This is\n // called when the document is ready (for statically-added input objects),\n // and when new input objects are added to the document with\n // htmlOutputBinding.renderValue() (for dynamically-added input objects).\n // This is called before the input is bound.\n initialize(el: HTMLElement): void {\n //empty\n el;\n }\n\n // This is called after unbinding the output.\n dispose(el: HTMLElement): void {\n //empty\n el;\n }\n}\n\n//// NOTES FOR FUTURE DEV\n// Turn register systemin into something that is intialized for every instance.\n// \"Have a new instance for every item, not an instance that does work on every item\"\n//\n// * Keep register as is for historical purposes\n// make a new register function that would take a class\n// these class could be constructed at build time\n// store the constructed obj on the ele and retrieve\n\n// Then the classes could store their information within their local class, rather than on the element\n// VERY CLEAN!!!\n\n// to invoke methods, it would be something like `el.shinyClass.METHOD(x,y,z)`\n// * See https://github.com/rstudio/shinyvalidate/blob/c8becd99c01fac1bac03b50e2140f49fca39e7f4/srcjs/shinyvalidate.js#L157-L167\n// these methods would be added using a new method like `inputBindings.registerClass(ClassObj, name)`\n\n// things to watch out for:\n// * unbind, then rebind. Maybe we stash the local content.\n\n// Updates:\n// * Feel free to alter method names on classes. (And make them private)\n//// END NOTES FOR FUTURE DEV\n\nexport { InputBinding };\n", "import $ from \"jquery\";\nimport { hasDefinedProperty } from \"../../utils\";\nimport { InputBinding } from \"./inputBinding\";\n\ntype ActionButtonReceiveMessageData = {\n label?: string;\n icon?: string | [];\n disabled?: boolean;\n};\n\nclass ActionButtonInputBinding extends InputBinding {\n find(scope: HTMLElement): JQuery {\n return $(scope).find(\".action-button\");\n }\n getValue(el: HTMLElement): number {\n return $(el).data(\"val\") || 0;\n }\n setValue(el: HTMLElement, value: number): void {\n $(el).data(\"val\", value);\n }\n getType(el: HTMLElement): string {\n return \"shiny.action\";\n el;\n }\n subscribe(el: HTMLElement, callback: (x: boolean) => void): void {\n $(el).on(\n \"click.actionButtonInputBinding\",\n // e: Event\n function () {\n const $el = $(this);\n const val = $el.data(\"val\") || 0;\n\n $el.data(\"val\", val + 1);\n\n callback(false);\n }\n );\n }\n getState(el: HTMLElement): { value: number } {\n return { value: this.getValue(el) };\n }\n receiveMessage(el: HTMLElement, data: ActionButtonReceiveMessageData): void {\n const $el = $(el);\n\n if (hasDefinedProperty(data, \"label\") || hasDefinedProperty(data, \"icon\")) {\n // retrieve current label and icon\n let label: string = $el.text();\n let icon = \"\";\n\n // to check (and store) the previous icon, we look for a $el child\n // object that has an i tag, and some (any) class (this prevents\n // italicized text - which has an i tag but, usually, no class -\n // from being mistakenly selected)\n if ($el.find(\"i[class]\").length > 0) {\n const iconHtml = $el.find(\"i[class]\")[0];\n\n if (iconHtml === $el.children()[0]) {\n // another check for robustness\n icon = $(iconHtml).prop(\"outerHTML\");\n }\n }\n\n // update the requested properties\n if (hasDefinedProperty(data, \"label\")) {\n label = data.label;\n }\n if (hasDefinedProperty(data, \"icon\")) {\n // `data.icon` can be an [] if user gave `character(0)`.\n icon = Array.isArray(data.icon) ? \"\" : data.icon ?? \"\";\n }\n\n // produce new html\n $el.html(icon + \" \" + label);\n }\n\n if (hasDefinedProperty(data, \"disabled\")) {\n if (data.disabled) {\n $el.attr(\"disabled\", \"\");\n } else {\n $el.attr(\"disabled\", null);\n }\n }\n }\n\n unsubscribe(el: HTMLElement): void {\n $(el).off(\".actionButtonInputBinding\");\n }\n}\n\n// TODO-barret should this be put in the init methods?\n$(document).on(\"click\", \"a.action-button\", function (e) {\n e.preventDefault();\n});\n\nexport { ActionButtonInputBinding };\nexport type { ActionButtonReceiveMessageData };\n", "import $ from \"jquery\";\nimport { hasDefinedProperty } from \"../../utils\";\nimport { InputBinding } from \"./inputBinding\";\n\ntype CheckedHTMLElement = HTMLInputElement;\n\ntype CheckboxChecked = CheckedHTMLElement[\"checked\"];\ntype CheckboxReceiveMessageData = { value?: CheckboxChecked; label?: string };\n\nclass CheckboxInputBinding extends InputBinding {\n find(scope: HTMLElement): JQuery {\n // Inputs also have .shiny-input-checkbox class\n return $(scope).find('input[type=\"checkbox\"]');\n }\n getValue(el: CheckedHTMLElement): CheckboxChecked {\n return el.checked;\n }\n setValue(el: CheckedHTMLElement, value: CheckboxChecked): void {\n el.checked = value;\n }\n subscribe(el: HTMLElement, callback: (x: boolean) => void): void {\n $(el).on(\"change.checkboxInputBinding\", function () {\n callback(true);\n });\n }\n unsubscribe(el: HTMLElement): void {\n $(el).off(\".checkboxInputBinding\");\n }\n getState(el: CheckedHTMLElement): { label: string; value: CheckboxChecked } {\n return {\n label: $(el).parent().find(\"span\").text(),\n value: el.checked,\n };\n }\n receiveMessage(\n el: CheckedHTMLElement,\n data: CheckboxReceiveMessageData\n ): void {\n if (hasDefinedProperty(data, \"value\")) {\n el.checked = data.value;\n }\n\n // checkboxInput()'s label works different from other\n // input labels...the label container should always exist\n if (hasDefinedProperty(data, \"label\")) {\n $(el).parent().find(\"span\").text(data.label);\n }\n\n $(el).trigger(\"change\");\n }\n}\n\nexport { CheckboxInputBinding };\nexport type { CheckedHTMLElement, CheckboxReceiveMessageData };\n", "import $ from \"jquery\";\n\nimport { $escape, hasDefinedProperty, updateLabel } from \"../../utils\";\nimport type { CheckedHTMLElement } from \"./checkbox\";\nimport { InputBinding } from \"./inputBinding\";\n\ntype CheckboxGroupHTMLElement = CheckedHTMLElement;\ntype ValueLabelObject = {\n value: HTMLInputElement[\"value\"];\n label: string;\n};\ntype CheckboxGroupReceiveMessageData = {\n options?: string;\n value?: Parameters[1];\n label: string;\n};\n\ntype CheckboxGroupValue = CheckboxGroupHTMLElement[\"value\"];\n\n// Get the DOM element that contains the top-level label\nfunction getLabelNode(el: CheckboxGroupHTMLElement): JQuery {\n return $(el).find('label[for=\"' + $escape(el.id) + '\"]');\n}\n// Given an input DOM object, get the associated label. Handles labels\n// that wrap the input as well as labels associated with 'for' attribute.\nfunction getLabel(obj: HTMLElement): string | null {\n const parentNode = obj.parentNode as HTMLElement;\n\n // If \n if (parentNode.tagName === \"LABEL\") {\n return $(parentNode).find(\"span\").text().trim();\n }\n\n return null;\n}\n// Given an input DOM object, set the associated label. Handles labels\n// that wrap the input as well as labels associated with 'for' attribute.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction setLabel(obj: HTMLElement, value: string): null {\n const parentNode = obj.parentNode as HTMLElement;\n\n // If \n if (parentNode.tagName === \"LABEL\") {\n $(parentNode).find(\"span\").text(value);\n }\n\n return null;\n}\n\nclass CheckboxGroupInputBinding extends InputBinding {\n find(scope: HTMLElement): JQuery {\n return $(scope).find(\".shiny-input-checkboxgroup\");\n }\n\n getValue(el: CheckboxGroupHTMLElement): CheckboxGroupValue[] {\n // Select the checkbox objects that have name equal to the grouping div's id\n const $objs = $('input:checkbox[name=\"' + $escape(el.id) + '\"]:checked');\n const values = new Array($objs.length);\n\n for (let i = 0; i < $objs.length; i++) {\n values[i] = ($objs[i] as CheckboxGroupHTMLElement).value;\n }\n return values;\n }\n setValue(el: HTMLElement, value: string[] | string | null): void {\n // Null value should be treated as empty array\n value = value ?? [];\n\n // Clear all checkboxes\n $('input:checkbox[name=\"' + $escape(el.id) + '\"]').prop(\"checked\", false);\n\n // Accept array\n if (value instanceof Array) {\n for (let i = 0; i < value.length; i++) {\n $(\n 'input:checkbox[name=\"' +\n $escape(el.id) +\n '\"][value=\"' +\n $escape(value[i]) +\n '\"]'\n ).prop(\"checked\", true);\n }\n // Else assume it's a single value\n } else {\n $(\n 'input:checkbox[name=\"' +\n $escape(el.id) +\n '\"][value=\"' +\n $escape(value) +\n '\"]'\n ).prop(\"checked\", true);\n }\n }\n getState(el: CheckboxGroupHTMLElement): {\n label: string;\n value: ReturnType;\n options: ValueLabelObject[];\n } {\n const $objs = $(\n 'input:checkbox[name=\"' + $escape(el.id) + '\"]'\n ) as JQuery;\n\n // Store options in an array of objects, each with with value and label\n const options = new Array($objs.length);\n\n for (let i = 0; i < options.length; i++) {\n options[i] = { value: $objs[i].value, label: getLabel($objs[i]) };\n }\n\n return {\n label: getLabelNode(el).text(),\n value: this.getValue(el),\n options: options,\n };\n }\n receiveMessage(\n el: CheckboxGroupHTMLElement,\n data: CheckboxGroupReceiveMessageData\n ): void {\n const $el = $(el);\n\n // This will replace all the options\n if (hasDefinedProperty(data, \"options\")) {\n // Clear existing options and add each new one\n $el.find(\"div.shiny-options-group\").remove();\n // Backward compatibility: for HTML generated by shinybootstrap2 package\n $el.find(\"label.checkbox\").remove();\n $el.append(data.options);\n }\n\n if (hasDefinedProperty(data, \"value\")) {\n this.setValue(el, data.value);\n }\n\n updateLabel(data.label, getLabelNode(el));\n\n $(el).trigger(\"change\");\n }\n subscribe(\n el: CheckboxGroupHTMLElement,\n callback: (x: boolean) => void\n ): void {\n $(el).on(\"change.checkboxGroupInputBinding\", function () {\n callback(false);\n });\n }\n unsubscribe(el: CheckboxGroupHTMLElement): void {\n $(el).off(\".checkboxGroupInputBinding\");\n }\n}\n\nexport { CheckboxGroupInputBinding };\nexport type { CheckboxGroupReceiveMessageData };\n", "import $ from \"jquery\";\nimport {\n $escape,\n formatDateUTC,\n hasDefinedProperty,\n parseDate,\n updateLabel,\n} from \"../../utils\";\nimport type { NotUndefined } from \"../../utils/extraTypes\";\nimport { InputBinding } from \"./inputBinding\";\n\ndeclare global {\n interface JQuery {\n // Adjustment of https://github.com/DefinitelyTyped/DefinitelyTyped/blob/1626e0bac175121ec2e9f766a770e03a91843c31/types/bootstrap-datepicker/index.d.ts#L113-L114\n bsDatepicker(methodName: \"getUTCDate\"): Date;\n // Infinity is not allowed as a literal return type. Using `1e9999` as a placeholder that resolves to Infinity\n // https://github.com/microsoft/TypeScript/issues/32277\n // eslint-disable-next-line @typescript-eslint/no-loss-of-precision\n bsDatepicker(methodName: \"getStartDate\"): Date | -1e9999;\n // eslint-disable-next-line @typescript-eslint/no-loss-of-precision\n bsDatepicker(methodName: \"getEndDate\"): Date | 1e9999;\n bsDatepicker(methodName: string): void;\n bsDatepicker(methodName: string, params: Date | null): void;\n }\n}\n\ntype DateReceiveMessageData = {\n label: string;\n min?: Date | null;\n max?: Date | null;\n value?: Date | null;\n};\n\nclass DateInputBindingBase extends InputBinding {\n find(scope: HTMLElement): JQuery {\n return $(scope).find(\".shiny-date-input\");\n }\n getType(el: HTMLElement): string {\n return \"shiny.date\";\n el;\n }\n subscribe(el: HTMLElement, callback: (x: boolean) => void): void {\n // Don't update when in the middle of typing; listening on keyup or input\n // tends to send spurious values to the server, based on unpredictable\n // browser-dependant interpretation of partially-typed date strings.\n $(el).on(\n \"changeDate.dateInputBinding change.dateInputBinding\",\n // event: Event\n function () {\n // Send immediately when clicked\n // Or if typing, when enter pressed or focus lost\n callback(false);\n }\n );\n }\n unsubscribe(el: HTMLElement): void {\n $(el).off(\".dateInputBinding\");\n }\n\n getRatePolicy(): { policy: \"debounce\"; delay: 250 } {\n return {\n policy: \"debounce\",\n delay: 250,\n };\n }\n\n setValue(el: HTMLElement, data: unknown): void {\n throw \"not implemented\";\n el;\n data;\n }\n initialize(el: HTMLElement): void {\n const $input = $(el).find(\"input\");\n\n // The challenge with dates is that we want them to be at 00:00 in UTC so\n // that we can do comparisons with them. However, the Date object itself\n // does not carry timezone information, so we should call _floorDateTime()\n // on Dates as soon as possible so that we know we're always working with\n // consistent objects.\n\n let date = $input.data(\"initial-date\");\n // If initial_date is null, set to current date\n\n if (date === undefined || date === null) {\n // Get local date, but normalized to beginning of day in UTC.\n date = this._floorDateTime(this._dateAsUTC(new Date()));\n }\n\n this.setValue(el, date);\n\n // Set the start and end dates, from min-date and max-date. These always\n // use yyyy-mm-dd format, instead of bootstrap-datepicker's built-in\n // support for date-startdate and data-enddate, which use the current\n // date format.\n if ($input.data(\"min-date\") !== undefined) {\n this._setMin($input[0], $input.data(\"min-date\"));\n }\n if ($input.data(\"max-date\") !== undefined) {\n this._setMax($input[0], $input.data(\"max-date\"));\n }\n }\n protected _getLabelNode(el: HTMLElement): JQuery {\n return $(el).find('label[for=\"' + $escape(el.id) + '\"]');\n }\n // Given a format object from a date picker, return a string\n protected _formatToString(format: {\n parts: string[];\n separators: string[];\n }): string {\n // Format object has structure like:\n // { parts: ['mm', 'dd', 'yy'], separators: ['', '/', '/' ,''] }\n let str = \"\";\n\n let i;\n\n for (i = 0; i < format.parts.length; i++) {\n str += format.separators[i] + format.parts[i];\n }\n str += format.separators[i];\n return str;\n }\n // Given an unambiguous date string or a Date object, set the min (start) date.\n // null will unset. undefined will result in no change,\n protected _setMin(el: HTMLElement, date: Date | null): void {\n if (date === null) {\n $(el).bsDatepicker(\"setStartDate\", null);\n return;\n }\n\n const parsedDate = this._newDate(date);\n\n // If date parsing fails, do nothing\n if (parsedDate === null) return;\n\n // (Assign back to date as a Date object)\n date = parsedDate as Date;\n\n if (isNaN(date.valueOf())) return;\n // Workarounds for\n // https://github.com/rstudio/shiny/issues/2335\n const curValue = $(el).bsDatepicker(\"getUTCDate\");\n\n // Note that there's no `setUTCStartDate`, so we need to convert this Date.\n // It starts at 00:00 UTC, and we convert it to 00:00 in local time, which\n // is what's needed for `setStartDate`.\n $(el).bsDatepicker(\"setStartDate\", this._utcDateAsLocal(date));\n\n // If the new min is greater than the current date, unset the current date.\n if (date && curValue && date.getTime() > curValue.getTime()) {\n $(el).bsDatepicker(\"clearDates\");\n } else {\n // Setting the date needs to be done AFTER `setStartDate`, because the\n // datepicker has a bug where calling `setStartDate` will clear the date\n // internally (even though it will still be visible in the UI) when a\n // 2-digit year format is used.\n // https://github.com/eternicode/bootstrap-datepicker/issues/2010\n $(el).bsDatepicker(\"setUTCDate\", curValue);\n }\n }\n // Given an unambiguous date string or a Date object, set the max (end) date\n // null will unset.\n protected _setMax(el: HTMLElement, date: Date | null): void {\n if (date === null) {\n $(el).bsDatepicker(\"setEndDate\", null);\n return;\n }\n\n const parsedDate = this._newDate(date);\n\n // If date parsing fails, do nothing\n if (parsedDate === null) return;\n\n date = parsedDate as Date;\n\n if (isNaN(date.valueOf())) return;\n\n // Workaround for same issue as in _setMin.\n const curValue = $(el).bsDatepicker(\"getUTCDate\");\n\n $(el).bsDatepicker(\"setEndDate\", this._utcDateAsLocal(date));\n\n // If the new min is greater than the current date, unset the current date.\n if (date && curValue && date.getTime() < curValue.getTime()) {\n $(el).bsDatepicker(\"clearDates\");\n } else {\n $(el).bsDatepicker(\"setUTCDate\", curValue);\n }\n }\n // Given a date string of format yyyy-mm-dd, return a Date object with\n // that date at 12AM UTC.\n // If date is a Date object, return it unchanged.\n protected _newDate(date: Date | never | string): Date | null {\n if (date instanceof Date) return date;\n if (!date) return null;\n\n // Get Date object - this will be at 12AM in UTC, but may print\n // differently at the Javascript console.\n const d = parseDate(date);\n\n // If invalid date, return null\n if (isNaN(d.valueOf())) return null;\n\n return d;\n }\n // A Date can have any time during a day; this will return a new Date object\n // set to 00:00 in UTC.\n protected _floorDateTime(date: Date): Date {\n date = new Date(date.getTime());\n date.setUTCHours(0, 0, 0, 0);\n return date;\n }\n // Given a Date object, return a Date object which has the same \"clock time\"\n // in UTC. For example, if input date is 2013-02-01 23:00:00 GMT-0600 (CST),\n // output will be 2013-02-01 23:00:00 UTC. Note that the JS console may\n // print this in local time, as \"Sat Feb 02 2013 05:00:00 GMT-0600 (CST)\".\n protected _dateAsUTC(date: Date): Date {\n return new Date(date.getTime() - date.getTimezoneOffset() * 60000);\n }\n // The inverse of _dateAsUTC. This is needed to adjust time zones because\n // some bootstrap-datepicker methods only take local dates as input, and not\n // UTC.\n protected _utcDateAsLocal(date: Date): Date {\n return new Date(date.getTime() + date.getTimezoneOffset() * 60000);\n }\n}\n\nclass DateInputBinding extends DateInputBindingBase {\n // Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a\n // format like mm/dd/yyyy)\n getValue(el: HTMLElement): string {\n const date = $(el).find(\"input\").bsDatepicker(\"getUTCDate\");\n\n return formatDateUTC(date);\n }\n // value must be an unambiguous string like '2001-01-01', or a Date object.\n setValue(el: HTMLElement, value: Date | null): void {\n // R's NA, which is null here will remove current value\n if (value === null) {\n $(el).find(\"input\").val(\"\").bsDatepicker(\"update\");\n return;\n }\n\n const date = this._newDate(value);\n\n if (date === null) {\n return;\n }\n\n // If date is invalid, do nothing\n if (isNaN((date as Date).valueOf())) return;\n\n $(el).find(\"input\").bsDatepicker(\"setUTCDate\", date);\n }\n getState(el: HTMLElement): {\n label: string;\n value: string | null;\n valueString: string[] | number | string;\n min: string | null;\n max: string | null;\n language: string | null;\n weekstart: number;\n format: string;\n startview: DatepickerViewModes;\n } {\n const $el = $(el);\n const $input = $el.find(\"input\");\n\n let min = $input.data(\"datepicker\").startDate;\n let max = $input.data(\"datepicker\").endDate;\n\n // Stringify min and max. If min and max aren't set, they will be\n // -Infinity and Infinity; replace these with null.\n min = min === -Infinity ? null : formatDateUTC(min);\n max = max === Infinity ? null : formatDateUTC(max);\n\n // startViewMode is stored as a number; convert to string\n let startview = $input.data(\"datepicker\").startViewMode;\n\n if (startview === 2) startview = \"decade\";\n else if (startview === 1) startview = \"year\";\n else if (startview === 0) startview = \"month\";\n\n return {\n label: this._getLabelNode(el).text(),\n value: this.getValue(el),\n valueString: $input.val() as NotUndefined>,\n min: min,\n max: max,\n language: $input.data(\"datepicker\").language,\n weekstart: $input.data(\"datepicker\").weekStart,\n format: this._formatToString($input.data(\"datepicker\").format),\n startview: startview,\n };\n }\n receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void {\n const $input = $(el).find(\"input\");\n\n updateLabel(data.label, this._getLabelNode(el));\n\n if (hasDefinedProperty(data, \"min\")) this._setMin($input[0], data.min);\n\n if (hasDefinedProperty(data, \"max\")) this._setMax($input[0], data.max);\n\n // Must set value only after min and max have been set. If new value is\n // outside the bounds of the previous min/max, then the result will be a\n // blank input.\n if (hasDefinedProperty(data, \"value\")) this.setValue(el, data.value);\n\n $(el).trigger(\"change\");\n }\n}\n\nexport { DateInputBinding, DateInputBindingBase };\nexport type { DateReceiveMessageData };\n", "import $ from \"jquery\";\n\nimport {\n $escape,\n formatDateUTC,\n hasDefinedProperty,\n updateLabel,\n} from \"../../utils\";\nimport { DateInputBindingBase } from \"./date\";\n\ntype DateRangeReceiveMessageData = {\n label: string;\n min?: Date;\n max?: Date;\n value?: { start?: Date; end?: Date };\n};\n\nfunction getLabelNode(el: HTMLElement): JQuery {\n return $(el).find('label[for=\"' + $escape(el.id) + '\"]');\n}\nclass DateRangeInputBinding extends DateInputBindingBase {\n find(scope: HTMLElement): JQuery {\n return $(scope).find(\".shiny-date-range-input\");\n }\n // Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a\n // format like mm/dd/yyyy)\n getValue(el: HTMLElement): [string, string] {\n const $inputs = $(el).find(\"input\");\n const start = $inputs.eq(0).bsDatepicker(\"getUTCDate\");\n const end = $inputs.eq(1).bsDatepicker(\"getUTCDate\");\n\n return [formatDateUTC(start), formatDateUTC(end)];\n }\n // value must be an object, with optional fields `start` and `end`. These\n // should be unambiguous strings like '2001-01-01', or Date objects.\n setValue(el: HTMLElement, value: { start?: Date; end?: Date }): void {\n if (!(value instanceof Object)) {\n return;\n }\n\n // Get the start and end input objects\n const $inputs = $(el).find(\"input\");\n\n // If value is undefined, don't try to set\n // null will remove the current value\n if (value.start !== undefined) {\n if (value.start === null) {\n $inputs.eq(0).val(\"\").bsDatepicker(\"update\");\n } else {\n const start = this._newDate(value.start);\n\n $inputs.eq(0).bsDatepicker(\"setUTCDate\", start);\n }\n }\n if (value.end !== undefined) {\n if (value.end === null) {\n $inputs.eq(1).val(\"\").bsDatepicker(\"update\");\n } else {\n const end = this._newDate(value.end);\n\n $inputs.eq(1).bsDatepicker(\"setUTCDate\", end);\n }\n }\n }\n getState(el: HTMLElement): {\n label: string;\n value: [string, string];\n valueString: [string, string];\n min: ReturnType | null;\n max: ReturnType | null;\n weekstart: string;\n format: string;\n language: string;\n startview: string;\n } {\n const $el = $(el);\n const $inputs = $el.find(\"input\");\n const $startinput = $inputs.eq(0);\n const $endinput = $inputs.eq(1);\n\n // For many of the properties, assume start and end have the same values\n const min = $startinput.bsDatepicker(\"getStartDate\");\n const max = $startinput.bsDatepicker(\"getEndDate\");\n\n // Stringify min and max. If min and max aren't set, they will be\n // -Infinity and Infinity; replace these with null.\n const minStr = min === -Infinity ? null : formatDateUTC(min as Date);\n const maxStr = max === Infinity ? null : formatDateUTC(max as Date);\n\n // startViewMode is stored as a number; convert to string\n let startview = $startinput.data(\"datepicker\").startView;\n\n if (startview === 2) startview = \"decade\";\n else if (startview === 1) startview = \"year\";\n else if (startview === 0) startview = \"month\";\n\n return {\n label: getLabelNode(el).text(),\n value: this.getValue(el),\n valueString: [$startinput.val() as string, $endinput.val() as string],\n min: minStr,\n max: maxStr,\n weekstart: $startinput.data(\"datepicker\").weekStart,\n format: this._formatToString($startinput.data(\"datepicker\").format),\n language: $startinput.data(\"datepicker\").language,\n startview: startview,\n };\n }\n receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void {\n const $el = $(el);\n const $inputs = $el.find(\"input\");\n const $startinput = $inputs.eq(0);\n const $endinput = $inputs.eq(1);\n\n updateLabel(data.label, getLabelNode(el));\n\n if (hasDefinedProperty(data, \"min\")) {\n this._setMin($startinput[0], data.min);\n this._setMin($endinput[0], data.min);\n }\n\n if (hasDefinedProperty(data, \"max\")) {\n this._setMax($startinput[0], data.max);\n this._setMax($endinput[0], data.max);\n }\n\n // Must set value only after min and max have been set. If new value is\n // outside the bounds of the previous min/max, then the result will be a\n // blank input.\n if (hasDefinedProperty(data, \"value\")) {\n this.setValue(el, data.value);\n }\n\n $el.trigger(\"change\");\n }\n\n initialize(el: HTMLElement): void {\n const $el = $(el);\n const $inputs = $el.find(\"input\");\n const $startinput = $inputs.eq(0);\n const $endinput = $inputs.eq(1);\n\n let start = $startinput.data(\"initial-date\");\n let end = $endinput.data(\"initial-date\");\n\n // If empty/null, use local date, but as UTC\n if (start === undefined || start === null)\n start = this._dateAsUTC(new Date());\n\n if (end === undefined || end === null) end = this._dateAsUTC(new Date());\n\n this.setValue(el, { start: start, end: end });\n\n // // Set the start and end dates, from min-date and max-date. These always\n // // use yyyy-mm-dd format, instead of bootstrap-datepicker's built-in\n // // support for date-startdate and data-enddate, which use the current\n // // date format.\n this._setMin($startinput[0], $startinput.data(\"min-date\"));\n this._setMin($endinput[0], $startinput.data(\"min-date\"));\n this._setMax($startinput[0], $endinput.data(\"max-date\"));\n this._setMax($endinput[0], $endinput.data(\"max-date\"));\n }\n subscribe(el: HTMLElement, callback: (x: boolean) => void): void {\n // Don't update when in the middle of typing; listening on keyup or input\n // tends to send spurious values to the server, based on unpredictable\n // browser-dependant interpretation of partially-typed date strings.\n $(el).on(\n \"changeDate.dateRangeInputBinding change.dateRangeInputBinding\",\n // event: Event\n function () {\n // Send immediately when clicked\n // Or if typing, when enter pressed or focus lost\n callback(false);\n }\n );\n }\n unsubscribe(el: HTMLElement): void {\n $(el).off(\".dateRangeInputBinding\");\n }\n}\n\nexport { DateRangeInputBinding };\nexport type { DateRangeReceiveMessageData };\n", "import $ from \"jquery\";\nimport { FileUploader } from \"../../file/fileProcessor\";\nimport { shinyShinyApp } from \"../../shiny/initedMethods\";\nimport { InputBinding } from \"./inputBinding\";\n\nconst zoneActive = \"shiny-file-input-active\";\nconst zoneOver = \"shiny-file-input-over\";\n\nfunction zoneOf(el: HTMLElement | JQuery): JQuery {\n return $(el).closest(\"div.input-group\");\n}\n\n// This function makes it possible to attach listeners to the dragenter,\n// dragleave, and drop events of a single element with children. It's not\n// intuitive to do directly because outer elements fire \"dragleave\" events\n// both when the drag leaves the element and when the drag enters a child. To\n// make it easier, we maintain a count of the elements being dragged across\n// and trigger 3 new types of event:\n//\n// 1. draghover:enter - When a drag enters el and any of its children.\n// 2. draghover:leave - When the drag leaves el and all of its children.\n// 3. draghover:drop - When an item is dropped on el or any of its children.\nfunction enableDraghover(el: JQuery): JQuery {\n const $el = $(el);\n let childCounter = 0;\n\n /* eslint-disable @typescript-eslint/naming-convention */\n $el.on({\n \"dragenter.draghover\": (e) => {\n if (childCounter++ === 0) {\n $el.trigger(\"draghover:enter\", e);\n }\n },\n \"dragleave.draghover\": (e) => {\n if (--childCounter === 0) {\n $el.trigger(\"draghover:leave\", e);\n }\n if (childCounter < 0) {\n console.error(\"draghover childCounter is negative somehow\");\n }\n },\n \"dragover.draghover\": (e) => {\n e.preventDefault();\n },\n \"drop.draghover\": (e) => {\n childCounter = 0;\n $el.trigger(\"draghover:drop\", e);\n e.preventDefault();\n },\n });\n return $el;\n}\nfunction disableDraghover(el: JQuery): JQuery {\n return $(el).off(\".draghover\");\n}\nfunction enableDocumentEvents(): void {\n const $doc = $(\"html\");\n\n enableDraghover($doc).on({\n \"draghover:enter.draghover\":\n // e: Event\n () => {\n zoneOf($fileInputs).addClass(zoneActive);\n },\n \"draghover:leave.draghover\":\n // e: Event\n () => {\n zoneOf($fileInputs).removeClass(zoneActive);\n },\n \"draghover:drop.draghover\":\n // e: Event\n () => {\n zoneOf($fileInputs).removeClass(zoneOver).removeClass(zoneActive);\n },\n });\n}\nfunction disableDocumentEvents(): void {\n const $doc = $(\"html\");\n\n $doc.off(\".draghover\");\n disableDraghover($doc);\n}\nfunction canSetFiles(fileList: FileList): boolean {\n const testEl = document.createElement(\"input\");\n\n testEl.type = \"file\";\n try {\n testEl.files = fileList;\n } catch (e) {\n return false;\n }\n return true;\n}\nfunction handleDrop(e: JQuery.DragEventBase, el: HTMLInputElement): void {\n const files = e.originalEvent?.dataTransfer?.files,\n $el = $(el);\n\n if (files === undefined || files === null) {\n // 1. The FileList object isn't supported by this browser, and\n // there's nothing else we can try. (< IE 10)\n console.log(\n \"Dropping files is not supported on this browser. (no FileList)\"\n );\n } else if (!canSetFiles(files)) {\n // 2. The browser doesn't support assigning a type=file input's .files\n // property, but we do have a FileList to work with. (IE10+/Edge)\n $el.val(\"\");\n uploadDroppedFilesIE10Plus(el, files);\n } else {\n // 3. The browser supports FileList and input.files assignment.\n // (Chrome, Safari)\n $el.val(\"\");\n el.files = files;\n // Recent versions of Firefox (57+, or \"Quantum\" and beyond) don't seem to\n // automatically trigger a change event, so we trigger one manually here.\n // On browsers that do trigger change, this operation appears to be\n // idempotent, as el.files doesn't change between events.\n $el.trigger(\"change\");\n }\n}\n\n// NOTE On Safari, at least version 10.1.2, *if the developer console is open*,\n// setting the input's value will behave strangely because of a Safari bug. The\n// uploaded file's name will appear over the placeholder value, instead of\n// replacing it. The workaround is to restart Safari. When I (Alan Dipert) ran\n// into this bug Winston Chang helped me diagnose the exact problem, and Winston\n// then submitted a bug report to Apple.\nfunction setFileText($el: JQuery, files: FileList) {\n const $fileText = $el.closest(\"div.input-group\").find(\"input[type=text]\");\n\n if (files.length === 1) {\n $fileText.val(files[0].name);\n } else {\n $fileText.val(files.length + \" files\");\n }\n}\n\n// If previously selected files are uploading, abort that.\nfunction abortCurrentUpload($el: JQuery) {\n const uploader = $el.data(\"currentUploader\");\n\n if (uploader) uploader.abort();\n // Clear data-restore attribute if present.\n $el.removeAttr(\"data-restore\");\n}\n\nfunction uploadDroppedFilesIE10Plus(\n el: HTMLInputElement,\n files: FileList\n): void {\n const $el = $(el);\n\n abortCurrentUpload($el);\n\n // Set the label in the text box\n setFileText($el, files);\n\n // Start the new upload and put the uploader in 'currentUploader'.\n $el.data(\n \"currentUploader\",\n new FileUploader(shinyShinyApp(), fileInputBindingGetId(el), files, el)\n );\n}\n\nfunction uploadFiles(evt: JQuery.DragEvent): void {\n const $el = $(evt.target);\n\n abortCurrentUpload($el);\n\n const files = evt.target.files;\n const id = fileInputBindingGetId(evt.target);\n\n if (files.length === 0) return;\n\n // Set the label in the text box\n setFileText($el, files);\n\n // Start the new upload and put the uploader in 'currentUploader'.\n $el.data(\n \"currentUploader\",\n new FileUploader(shinyShinyApp(), id, files, evt.target)\n );\n}\n\n// Here we maintain a list of all the current file inputs. This is necessary\n// because we need to trigger events on them in order to respond to file drag\n// events. For example, they should all light up when a file is dragged on to\n// the page.\n// TODO-barret ; Should this be an internal class property?\nlet $fileInputs = $();\n\nfunction fileInputBindingGetId(this: any, el: HTMLInputElement): string {\n return InputBinding.prototype.getId.call(this, el) || el.name;\n}\n\nclass FileInputBinding extends InputBinding {\n find(scope: HTMLElement): JQuery {\n // Inputs also have .shiny-input-file class\n return $(scope).find('input[type=\"file\"]');\n }\n getId(el: HTMLInputElement): string {\n return fileInputBindingGetId(el);\n }\n getValue(el: HTMLElement): { name?: string } | null {\n // This returns a non-undefined value only when there's a 'data-restore'\n // attribute, which is set only when restoring Shiny state. If a file is\n // uploaded through the browser, 'data-restore' gets cleared.\n const data = $(el).attr(\"data-restore\");\n\n if (data) {\n const dataParsed = JSON.parse(data);\n\n // Set the label in the text box\n const $fileText = $(el)\n .closest(\"div.input-group\")\n .find(\"input[type=text]\");\n\n if (dataParsed.name.length === 1) {\n $fileText.val(dataParsed.name[0]);\n } else {\n $fileText.val(dataParsed.name.length + \" files\");\n }\n\n // Manually set up progress bar. A bit inelegant because it duplicates\n // code from FileUploader, but duplication is less bad than alternatives.\n const $progress = $(el).closest(\"div.form-group\").find(\".progress\");\n const $bar = $progress.find(\".progress-bar\");\n\n $progress.removeClass(\"active\");\n $bar.width(\"100%\");\n $bar.css(\"visibility\", \"visible\");\n\n return dataParsed;\n } else {\n return null;\n }\n }\n setValue(el: HTMLElement, value: void): void {\n // Not implemented\n el;\n value;\n }\n getType(el: HTMLElement): string {\n // This will be used only when restoring a file from a saved state.\n return \"shiny.file\";\n el;\n }\n\n subscribe(el: HTMLInputElement, callback: (x: boolean) => void): void {\n callback;\n\n $(el).on(\"change.fileInputBinding\", uploadFiles);\n // Here we try to set up the necessary events for Drag and Drop (\"DnD\").\n if ($fileInputs.length === 0) enableDocumentEvents();\n $fileInputs = $fileInputs.add(el);\n const $zone = zoneOf(el);\n\n enableDraghover($zone).on({\n \"draghover:enter.draghover\": (e) => {\n e;\n $zone.addClass(zoneOver);\n },\n \"draghover:leave.draghover\": (e) => {\n $zone.removeClass(zoneOver);\n // Prevent this event from bubbling to the document handler,\n // which would deactivate all zones.\n e.stopPropagation();\n },\n \"draghover:drop.draghover\": (e, dropEvent) => {\n e;\n handleDrop(dropEvent, el);\n },\n });\n }\n\n unsubscribe(el: HTMLElement): void {\n const $el = $(el),\n $zone = zoneOf(el);\n\n $zone.removeClass(zoneOver).removeClass(zoneActive);\n\n disableDraghover($zone);\n $el.off(\".fileInputBinding\");\n $zone.off(\".draghover\");\n\n // Remove el from list of inputs and (maybe) clean up global event handlers.\n $fileInputs = $fileInputs.not(el);\n if ($fileInputs.length === 0) disableDocumentEvents();\n }\n}\n\nexport { FileInputBinding };\n", "import $ from \"jquery\";\nimport { triggerFileInputChanged } from \"../events/inputChanged\";\nimport { getFileInputBinding } from \"../shiny/initedMethods\";\nimport type { ShinyApp } from \"../shiny/shinyapp\";\nimport { $escape } from \"../utils\";\n\ntype JobId = string;\ntype UploadUrl = string;\ntype UploadInitValue = { jobId: JobId; uploadUrl: UploadUrl };\ntype UploadEndValue = never;\n\n// Generic driver class for doing chunk-wise asynchronous processing of a\n// FileList object. Subclass/clone it and override the `on*` functions to\n// make it do something useful.\nclass FileProcessor {\n files: File[];\n fileIndex = -1;\n // Currently need to use small chunk size because R-Websockets can't\n // handle continuation frames\n aborted = false;\n completed = false;\n\n constructor(files: FileList, exec$run = true) {\n this.files = Array.from(files);\n\n // TODO: Register error/abort callbacks\n if (exec$run) {\n this.$run();\n }\n }\n\n // Begin callbacks. Subclassers/cloners may override any or all of these.\n onBegin(files: File[], cont: () => void): void {\n files;\n setTimeout(cont, 0);\n }\n onFile(file: File, cont: () => void): void {\n file;\n setTimeout(cont, 0);\n }\n onComplete(): void {\n return;\n }\n onAbort(): void {\n return;\n }\n // End callbacks\n\n // Aborts processing, unless it's already completed\n abort(): void {\n if (this.completed || this.aborted) return;\n\n this.aborted = true;\n this.onAbort();\n }\n\n // Returns a bound function that will call this.$run one time.\n $getRun(): () => void {\n let called = false;\n\n return () => {\n if (called) return;\n called = true;\n this.$run();\n };\n }\n\n // This function will be called multiple times to advance the process.\n // It relies on the state of the object's fields to know what to do next.\n $run(): void {\n if (this.aborted || this.completed) return;\n\n if (this.fileIndex < 0) {\n // Haven't started yet--begin\n this.fileIndex = 0;\n this.onBegin(this.files, this.$getRun());\n return;\n }\n\n if (this.fileIndex === this.files.length) {\n // Just ended\n this.completed = true;\n this.onComplete();\n return;\n }\n\n // If we got here, then we have a file to process, or we are\n // in the middle of processing a file, or have just finished\n // processing a file.\n\n const file = this.files[this.fileIndex++];\n\n this.onFile(file, this.$getRun());\n }\n}\n\nclass FileUploader extends FileProcessor {\n shinyapp: ShinyApp;\n id: string;\n el: HTMLElement;\n\n jobId!: JobId;\n uploadUrl!: UploadUrl;\n progressBytes!: number;\n totalBytes!: number;\n\n constructor(\n shinyapp: ShinyApp,\n id: string,\n files: FileList,\n el: HTMLElement\n ) {\n // Init super with files, do not execute `this.$run()` before setting variables\n super(files, false);\n this.shinyapp = shinyapp;\n this.id = id;\n this.el = el;\n this.$run();\n }\n\n makeRequest(\n method: \"uploadInit\",\n args: Array>,\n onSuccess: (value: UploadInitValue) => void,\n onFailure: Parameters[3],\n blobs: Parameters[4]\n ): void;\n makeRequest(\n method: \"uploadEnd\",\n args: [string, string],\n // UploadEndValue can not be used as the type will not conform\n onSuccess: (value: unknown) => void,\n onFailure: Parameters[3],\n blobs: Parameters[4]\n ): void;\n makeRequest(\n method: string,\n args: unknown[],\n onSuccess: Parameters[2],\n onFailure: Parameters[3],\n blobs: Parameters[4]\n ): void {\n this.shinyapp.makeRequest(method, args, onSuccess, onFailure, blobs);\n }\n onBegin(files: File[], cont: () => void): void {\n // Reset progress bar\n this.$setError(null);\n this.$setActive(true);\n this.$setVisible(true);\n this.onProgress(null, 0);\n\n this.totalBytes = 0;\n this.progressBytes = 0;\n $.each(files, (i, file) => {\n this.totalBytes += file.size;\n });\n\n const fileInfo = $.map(files, function (file: File) {\n return {\n name: file.name,\n size: file.size,\n type: file.type,\n };\n });\n\n this.makeRequest(\n \"uploadInit\",\n [fileInfo],\n (response) => {\n this.jobId = response.jobId;\n this.uploadUrl = response.uploadUrl;\n cont();\n },\n (error) => {\n this.onError(error);\n },\n undefined\n );\n }\n onFile(file: File, cont: () => void): void {\n this.onProgress(file, 0);\n\n /* eslint-disable-next-line @typescript-eslint/no-floating-promises */\n $.ajax(this.uploadUrl, {\n type: \"POST\",\n cache: false,\n xhr: () => {\n if (typeof $.ajaxSettings.xhr !== \"function\")\n throw \"jQuery's XHR is not a function\";\n\n const xhrVal = $.ajaxSettings.xhr();\n\n if (xhrVal.upload) {\n xhrVal.upload.onprogress = (e) => {\n if (e.lengthComputable) {\n this.onProgress(\n file,\n (this.progressBytes + e.loaded) / this.totalBytes\n );\n }\n };\n }\n return xhrVal;\n },\n data: file,\n contentType: \"application/octet-stream\",\n processData: false,\n success: () => {\n this.progressBytes += file.size;\n cont();\n },\n error: (jqXHR, textStatus, errorThrown) => {\n errorThrown;\n this.onError(jqXHR.responseText || textStatus);\n },\n });\n }\n onComplete(): void {\n const fileInfo = $.map(this.files, function (file: File, i) {\n return {\n name: file.name,\n size: file.size,\n type: file.type,\n };\n i;\n });\n\n // Trigger shiny:inputchanged. Unlike a normal shiny:inputchanged event,\n // it's not possible to modify the information before the values get\n // sent to the server.\n const evt = triggerFileInputChanged(\n this.id,\n fileInfo,\n getFileInputBinding(),\n this.el,\n \"shiny.fileupload\",\n document\n );\n\n this.makeRequest(\n \"uploadEnd\",\n [this.jobId, this.id],\n () => {\n this.$setActive(false);\n this.onProgress(null, 1);\n this.$bar().text(\"Upload complete\");\n // Reset the file input's value to \"\". This allows the same file to be\n // uploaded again. https://stackoverflow.com/a/22521275\n $(evt.el as HTMLElement).val(\"\");\n },\n (error) => {\n this.onError(error);\n },\n undefined\n );\n this.$bar().text(\"Finishing upload\");\n }\n onError(message: string): void {\n this.$setError(message || \"\");\n this.$setActive(false);\n }\n onAbort(): void {\n this.$setVisible(false);\n }\n onProgress(file: File | null, completed: number): void {\n this.$bar().width(Math.round(completed * 100) + \"%\");\n this.$bar().text(file ? file.name : \"\");\n }\n $container(): JQuery {\n return $(\"#\" + $escape(this.id) + \"_progress.shiny-file-input-progress\");\n }\n $bar(): JQuery {\n return $(\n \"#\" +\n $escape(this.id) +\n \"_progress.shiny-file-input-progress .progress-bar\"\n );\n }\n $setVisible(visible: boolean): void {\n this.$container().css(\"visibility\", visible ? \"visible\" : \"hidden\");\n }\n $setError(error: string | null): void {\n this.$bar().toggleClass(\"progress-bar-danger\", error !== null);\n if (error !== null) {\n this.onProgress(null, 1);\n this.$bar().text(error);\n }\n }\n $setActive(active: boolean): void {\n this.$container().toggleClass(\"active\", !!active);\n }\n}\n\nexport { FileUploader };\nexport type { UploadInitValue, UploadEndValue };\n", "import $ from \"jquery\";\nimport type { FileInputBinding } from \"../bindings/input/fileinput\";\nimport type { ShinyEventInputChanged } from \"./shinyEvents\";\n\nfunction triggerFileInputChanged(\n name: string,\n value: unknown,\n binding: FileInputBinding,\n el: HTMLElement,\n inputType: string,\n onEl: typeof document\n): ShinyEventInputChanged {\n const evt = $.Event(\"shiny:inputchanged\") as ShinyEventInputChanged;\n\n evt.name = name;\n evt.value = value;\n evt.binding = binding;\n evt.el = el;\n evt.inputType = inputType;\n\n $(onEl).trigger(evt);\n\n return evt;\n}\n\nexport { triggerFileInputChanged };\n", "import type { ShinyClass } from \".\";\nimport type { FileInputBinding } from \"../bindings/input/fileinput\";\nimport type { OutputBindingAdapter } from \"../bindings/outputAdapter\";\nimport type { EventPriority } from \"../inputPolicies\";\nimport type { BindScope } from \"./bind\";\nimport type { Handler, ShinyApp } from \"./shinyapp\";\n\nlet fullShinyObj: FullShinyDef;\n\n// TODO-future; It would be nice to have a way to export this type value instead of / in addition to `Shiny`\ntype FullShinyDef = Required<\n Pick<\n ShinyClass,\n | \"bindAll\"\n | \"forgetLastInputValue\"\n | \"initializeInputs\"\n | \"oncustommessage\"\n | \"setInputValue\"\n | \"shinyapp\"\n | \"unbindAll\"\n | \"user\"\n >\n> &\n ShinyClass;\n\nfunction setShinyObj(shiny: ShinyClass): void {\n fullShinyObj = shiny as FullShinyDef;\n}\n\nfunction validateShinyHasBeenSet(): FullShinyDef {\n if (typeof fullShinyObj === \"undefined\") {\n throw \"Shiny has not finish initialization yet. Please wait for the 'shiny-initialized' event.\";\n }\n return fullShinyObj;\n}\n\n//// 2021/03: TypeScript Conversion note\n// These methods are here due to the delayed initialization of `Shiny.shinyapp`. I\n// In theory, there could be multiple instances of `shinyapp`. In practice (and implementation), this is not possible and is a 1:1 coupling with `window.Shiny`.\n// To avoid calls to a large Shiny object, helper methods are created to wrap around calling the fully instantiated window.Shiny value.\n// TODO-barret; Why is `initShiny()` delayed? Is this to allow users to shim in some code? Why can't it be defined in the init method (maybe w/ an extra trigger call?)\nfunction shinySetInputValue(\n name: string,\n value: unknown,\n opts?: { priority?: EventPriority }\n): void {\n validateShinyHasBeenSet().setInputValue(name, value, opts);\n}\nfunction shinyShinyApp(): ShinyApp {\n return validateShinyHasBeenSet().shinyapp;\n}\nfunction setShinyUser(user: string): void {\n validateShinyHasBeenSet().user = user;\n}\nfunction shinyForgetLastInputValue(name: string): void {\n validateShinyHasBeenSet().forgetLastInputValue(name);\n}\nasync function shinyBindAll(scope: BindScope): Promise {\n await validateShinyHasBeenSet().bindAll(scope);\n}\nfunction shinyUnbindAll(scope: BindScope, includeSelf = false): void {\n validateShinyHasBeenSet().unbindAll(scope, includeSelf);\n}\nfunction shinyInitializeInputs(scope: BindScope): void {\n validateShinyHasBeenSet().initializeInputs(scope);\n}\n\nasync function shinyAppBindOutput(\n id: string,\n binding: OutputBindingAdapter\n): Promise {\n await shinyShinyApp().bindOutput(id, binding);\n}\n\nfunction shinyAppUnbindOutput(\n id: string,\n binding: OutputBindingAdapter\n): boolean {\n return shinyShinyApp().unbindOutput(id, binding);\n}\n\nfunction getShinyOnCustomMessage(): Handler | null {\n return validateShinyHasBeenSet().oncustommessage;\n}\n\nlet fileInputBinding: FileInputBinding;\n\nfunction getFileInputBinding(): FileInputBinding {\n return fileInputBinding;\n}\nfunction setFileInputBinding(fileInputBinding_: FileInputBinding): void {\n fileInputBinding = fileInputBinding_;\n}\n\nfunction getShinyCreateWebsocket(): (() => WebSocket) | void {\n return validateShinyHasBeenSet().createSocket;\n}\n\nexport {\n setShinyObj,\n shinySetInputValue,\n shinyShinyApp,\n setShinyUser,\n shinyForgetLastInputValue,\n shinyBindAll,\n shinyUnbindAll,\n shinyInitializeInputs,\n shinyAppBindOutput,\n shinyAppUnbindOutput,\n getShinyOnCustomMessage,\n getFileInputBinding,\n setFileInputBinding,\n getShinyCreateWebsocket,\n};\n", "import $ from \"jquery\";\nimport { $escape, hasDefinedProperty, updateLabel } from \"../../utils\";\nimport { TextInputBindingBase } from \"./text\";\n\ntype NumberHTMLElement = HTMLInputElement;\n\ntype NumberReceiveMessageData = {\n label: string;\n value?: string | null;\n min?: string | null;\n max?: string | null;\n step?: string | null;\n};\n\nfunction getLabelNode(el: NumberHTMLElement): JQuery {\n return $(el)\n .parent()\n .find('label[for=\"' + $escape(el.id) + '\"]');\n}\n\nclass NumberInputBinding extends TextInputBindingBase {\n find(scope: HTMLElement): JQuery {\n // Inputs also have .shiny-input-number class\n return $(scope).find('input[type=\"number\"]');\n }\n\n getValue(\n el: NumberHTMLElement\n ): string[] | number | string | null | undefined {\n const numberVal = $(el).val();\n\n if (typeof numberVal == \"string\") {\n if (/^\\s*$/.test(numberVal))\n // Return null if all whitespace\n return null;\n }\n\n // If valid Javascript number string, coerce to number\n const numberValue = Number(numberVal);\n\n if (!isNaN(numberValue)) {\n return numberValue;\n }\n\n return numberVal; // If other string like \"1e6\", send it unchanged\n }\n setValue(el: NumberHTMLElement, value: number): void {\n el.value = \"\" + value;\n }\n getType(el: NumberHTMLElement): string {\n return \"shiny.number\";\n el;\n }\n receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void {\n // Setting values to `\"\"` will remove the attribute value from the DOM element.\n // The attr key will still remain, but there is not value... ex: ``\n if (hasDefinedProperty(data, \"value\")) el.value = data.value ?? \"\";\n if (hasDefinedProperty(data, \"min\")) el.min = data.min ?? \"\";\n if (hasDefinedProperty(data, \"max\")) el.max = data.max ?? \"\";\n if (hasDefinedProperty(data, \"step\")) el.step = data.step ?? \"\";\n\n updateLabel(data.label, getLabelNode(el));\n\n $(el).trigger(\"change\");\n }\n\n getState(el: NumberHTMLElement): {\n label: string;\n value: ReturnType;\n min: number;\n max: number;\n step: number;\n } {\n return {\n label: getLabelNode(el).text(),\n value: this.getValue(el),\n min: Number(el.min),\n max: Number(el.max),\n step: Number(el.step),\n };\n }\n}\n\nexport { NumberInputBinding };\nexport type { NumberReceiveMessageData };\n", "import $ from \"jquery\";\nimport { $escape, hasDefinedProperty, updateLabel } from \"../../utils\";\n\nimport { InputBinding } from \"./inputBinding\";\n\n// interface TextHTMLElement extends NameValueHTMLElement {\n// placeholder: any;\n// }\n\ntype TextHTMLElement = HTMLInputElement;\ntype TextReceiveMessageData = {\n label: string;\n value?: TextHTMLElement[\"value\"];\n placeholder?: TextHTMLElement[\"placeholder\"];\n};\n\nfunction getLabelNode(el: HTMLElement): JQuery {\n return $(el)\n .parent()\n .find('label[for=\"' + $escape(el.id) + '\"]');\n}\n\nclass TextInputBindingBase extends InputBinding {\n find(scope: HTMLElement): JQuery {\n const $inputs = $(scope).find(\n 'input[type=\"text\"], input[type=\"search\"], input[type=\"url\"], input[type=\"email\"]'\n );\n // selectize.js 0.12.4 inserts a hidden text input with an\n // id that ends in '-selectized'. The .not() selector below\n // is to prevent textInputBinding from accidentally picking up\n // this hidden element as a shiny input (#2396)\n //\n // Inputs also now have .shiny-input-text class\n return $inputs.not('input[type=\"text\"][id$=\"-selectized\"]');\n }\n\n getId(el: TextHTMLElement): string {\n return super.getId(el) || el.name;\n // return InputBinding.prototype.getId.call(this, el) || el.name;\n }\n\n getValue(el: TextHTMLElement): unknown {\n throw \"not implemented\";\n el;\n }\n setValue(el: TextHTMLElement, value: unknown): void {\n throw \"not implemented\";\n el;\n value;\n }\n\n subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void {\n $(el).on(\n \"keyup.textInputBinding input.textInputBinding\",\n // event: Event\n function () {\n callback(true);\n }\n );\n $(el).on(\n \"change.textInputBinding\",\n // event: Event\n function () {\n callback(false);\n }\n );\n }\n unsubscribe(el: TextHTMLElement): void {\n $(el).off(\".textInputBinding\");\n }\n\n receiveMessage(el: TextHTMLElement, data: unknown): void {\n throw \"not implemented\";\n el;\n data;\n }\n\n getState(el: TextHTMLElement): unknown {\n throw \"not implemented\";\n el;\n }\n\n getRatePolicy(el: HTMLElement): { policy: \"debounce\"; delay: 250 } {\n return {\n policy: \"debounce\",\n delay: 250,\n };\n el;\n }\n}\n\nclass TextInputBinding extends TextInputBindingBase {\n setValue(el: TextHTMLElement, value: string): void {\n el.value = value;\n }\n\n getValue(el: TextHTMLElement): TextHTMLElement[\"value\"] {\n return el.value;\n }\n\n getState(el: TextHTMLElement): {\n label: string;\n value: string;\n placeholder: string;\n } {\n return {\n label: getLabelNode(el).text(),\n value: el.value,\n placeholder: el.placeholder,\n };\n }\n receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void {\n if (hasDefinedProperty(data, \"value\")) this.setValue(el, data.value);\n\n updateLabel(data.label, getLabelNode(el));\n\n if (hasDefinedProperty(data, \"placeholder\"))\n el.placeholder = data.placeholder;\n\n $(el).trigger(\"change\");\n }\n}\n\nexport { TextInputBinding, TextInputBindingBase };\nexport type { TextHTMLElement, TextReceiveMessageData };\n", "import $ from \"jquery\";\n\nimport { TextInputBinding } from \"./text\";\n\nclass PasswordInputBinding extends TextInputBinding {\n find(scope: HTMLElement): JQuery {\n // Inputs also have .shiny-input-password class\n return $(scope).find('input[type=\"password\"]');\n }\n\n getType(el: HTMLElement): string {\n return \"shiny.password\";\n el;\n }\n}\n\nexport { PasswordInputBinding };\n", "import $ from \"jquery\";\nimport { $escape, hasDefinedProperty, updateLabel } from \"../../utils\";\nimport { InputBinding } from \"./inputBinding\";\n\ntype RadioHTMLElement = HTMLInputElement;\n\ntype ValueLabelObject = {\n value: HTMLInputElement[\"value\"];\n label: string;\n};\n\ntype RadioReceiveMessageData = {\n value?: string | [];\n options?: ValueLabelObject[];\n label: string;\n};\n\n// Get the DOM element that contains the top-level label\nfunction getLabelNode(el: RadioHTMLElement): JQuery {\n return $(el)\n .parent()\n .find('label[for=\"' + $escape(el.id) + '\"]');\n}\n// Given an input DOM object, get the associated label. Handles labels\n// that wrap the input as well as labels associated with 'for' attribute.\nfunction getLabel(obj: HTMLElement): string | null {\n const parentNode = obj.parentNode as HTMLElement;\n\n // If \n if (parentNode.tagName === \"LABEL\") {\n return $(parentNode).find(\"span\").text().trim();\n }\n\n return null;\n}\n// Given an input DOM object, set the associated label. Handles labels\n// that wrap the input as well as labels associated with 'for' attribute.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction setLabel(obj: HTMLElement, value: string): null {\n const parentNode = obj.parentNode as HTMLElement;\n\n // If \n if (parentNode.tagName === \"LABEL\") {\n $(parentNode).find(\"span\").text(value);\n }\n\n return null;\n}\n\nclass RadioInputBinding extends InputBinding {\n find(scope: HTMLElement): JQuery {\n return $(scope).find(\".shiny-input-radiogroup\");\n }\n getValue(\n el: RadioHTMLElement\n ): string[] | number | string | null | undefined {\n // Select the radio objects that have name equal to the grouping div's id\n const checkedItems = $(\n 'input:radio[name=\"' + $escape(el.id) + '\"]:checked'\n );\n\n if (checkedItems.length === 0) {\n // If none are checked, the input will return null (it's the default on load,\n // but it wasn't emptied when calling updateRadioButtons with character(0)\n return null;\n }\n\n return checkedItems.val();\n }\n setValue(el: RadioHTMLElement, value: string | []): void {\n if (Array.isArray(value) && value.length === 0) {\n // Removing all checked item if the sent data is empty\n $('input:radio[name=\"' + $escape(el.id) + '\"]').prop(\"checked\", false);\n } else {\n $(\n 'input:radio[name=\"' +\n $escape(el.id) +\n '\"][value=\"' +\n $escape(value) +\n '\"]'\n ).prop(\"checked\", true);\n }\n }\n getState(el: RadioHTMLElement): {\n label: string;\n value: ReturnType;\n options: ValueLabelObject[];\n } {\n const $objs = $(\n 'input:radio[name=\"' + $escape(el.id) + '\"]'\n ) as JQuery;\n\n // Store options in an array of objects, each with with value and label\n const options = new Array($objs.length);\n\n for (let i = 0; i < options.length; i++) {\n options[i] = { value: $objs[i].value, label: getLabel($objs[i]) };\n }\n\n return {\n label: getLabelNode(el).text(),\n value: this.getValue(el),\n options: options,\n };\n }\n receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void {\n const $el = $(el);\n // This will replace all the options\n\n if (hasDefinedProperty(data, \"options\")) {\n // Clear existing options and add each new one\n $el.find(\"div.shiny-options-group\").remove();\n // Backward compatibility: for HTML generated by shinybootstrap2 package\n $el.find(\"label.radio\").remove();\n // @ts-expect-error; TODO-barret; IDK what this line is doing\n // TODO-barret; Should this line be setting attributes instead?\n // `data.options` is an array of `{value, label}` objects\n $el.append(data.options);\n }\n\n if (hasDefinedProperty(data, \"value\")) {\n this.setValue(el, data.value);\n }\n\n updateLabel(data.label, getLabelNode(el));\n\n $(el).trigger(\"change\");\n }\n subscribe(el: RadioHTMLElement, callback: (x: boolean) => void): void {\n $(el).on(\"change.radioInputBinding\", function () {\n callback(false);\n });\n }\n unsubscribe(el: RadioHTMLElement): void {\n $(el).off(\".radioInputBinding\");\n }\n}\n\nexport { RadioInputBinding };\nexport type { RadioReceiveMessageData };\n", "import $ from \"jquery\";\nimport { $escape, hasDefinedProperty, updateLabel } from \"../../utils\";\nimport { indirectEval } from \"../../utils/eval\";\nimport { InputBinding } from \"./inputBinding\";\n\ntype SelectHTMLElement = HTMLSelectElement & { nonempty: boolean };\n\ntype SelectInputReceiveMessageData = {\n label: string;\n options?: string;\n config?: string;\n url?: string;\n value?: string;\n};\n\ntype SelectizeOptions = Selectize.IOptions;\ntype SelectizeInfo = Selectize.IApi & {\n settings: SelectizeOptions;\n};\n\nfunction getLabelNode(el: SelectHTMLElement): JQuery {\n let escapedId = $escape(el.id);\n\n if (isSelectize(el)) {\n escapedId += \"-selectized\";\n }\n return $(el)\n .parent()\n .parent()\n .find('label[for=\"' + escapedId + '\"]');\n}\n// Return true if it's a selectize input, false if it's a regular select input.\n// eslint-disable-next-line camelcase\nfunction isSelectize(el: HTMLElement): boolean {\n const config = $(el)\n .parent()\n .find('script[data-for=\"' + $escape(el.id) + '\"]');\n\n return config.length > 0;\n}\n\nclass SelectInputBinding extends InputBinding {\n find(scope: HTMLElement): JQuery {\n // Inputs also have .shiny-input-select class\n return $(scope).find(\"select\");\n }\n getType(el: HTMLElement): string | null {\n const $el = $(el);\n\n if (!$el.hasClass(\"symbol\")) {\n // default character type\n return null;\n }\n if ($el.attr(\"multiple\") === \"multiple\") {\n return \"shiny.symbolList\";\n } else {\n return \"shiny.symbol\";\n }\n }\n getId(el: SelectHTMLElement): string {\n return InputBinding.prototype.getId.call(this, el) || el.name;\n }\n getValue(el: SelectHTMLElement): any {\n if (!isSelectize(el)) {\n return $(el).val();\n } else {\n const selectize = this._selectize(el);\n\n return selectize?.getValue();\n }\n }\n setValue(el: SelectHTMLElement, value: string): void {\n if (!isSelectize(el)) {\n $(el).val(value);\n } else {\n const selectize = this._selectize(el);\n\n selectize?.setValue(value);\n }\n }\n getState(el: SelectHTMLElement): {\n label: JQuery;\n value: ReturnType;\n options: Array<{ value: string; label: string }>;\n } {\n // Store options in an array of objects, each with with value and label\n const options: Array<{ value: string; label: string }> = new Array(\n el.length\n );\n\n for (let i = 0; i < el.length; i++) {\n options[i] = {\n // TODO-barret; Is this a safe assumption?; Are there no Option Groups?\n value: (el[i] as HTMLOptionElement).value,\n label: el[i].label,\n };\n }\n\n return {\n label: getLabelNode(el),\n value: this.getValue(el),\n options: options,\n };\n }\n receiveMessage(\n el: SelectHTMLElement,\n data: SelectInputReceiveMessageData\n ): void {\n const $el = $(el);\n\n // This will replace all the options\n if (hasDefinedProperty(data, \"options\")) {\n const selectize = this._selectize(el);\n\n // Must destroy selectize before appending new options, otherwise\n // selectize will restore the original select\n selectize?.destroy();\n // Clear existing options and add each new one\n $el.empty().append(data.options);\n this._selectize(el);\n }\n\n // re-initialize selectize\n if (hasDefinedProperty(data, \"config\")) {\n $el\n .parent()\n .find('script[data-for=\"' + $escape(el.id) + '\"]')\n .replaceWith(data.config);\n this._selectize(el, true);\n }\n\n // use server-side processing for selectize\n if (hasDefinedProperty(data, \"url\")) {\n type CallbackFn = Parameters<\n NonNullable\n >[1];\n const selectize = this._selectize(el) as ReturnType<\n SelectInputBinding[\"_selectize\"]\n > & {\n settings: {\n load: (query: string, callback: CallbackFn) => any;\n };\n };\n\n // Calling selectize.clear() first works around https://github.com/selectize/selectize.js/issues/2146\n // As of selectize.js >= v0.13.1, .clearOptions() clears the selection,\n // but does NOT remove the previously-selected options. So unless we call\n // .clear() first, the current selection(s) will remain as (deselected)\n // options. See #3966 #4142\n selectize.clear();\n selectize.clearOptions();\n let loaded = false;\n\n selectize.settings.load = function (query: string, callback: CallbackFn) {\n const settings = selectize.settings;\n\n /* eslint-disable-next-line @typescript-eslint/no-floating-promises */\n $.ajax({\n url: data.url,\n data: {\n query: query,\n field: JSON.stringify([settings.searchField]),\n value: settings.valueField,\n conju: settings.searchConjunction,\n maxop: settings.maxOptions,\n },\n type: \"GET\",\n error: function () {\n callback();\n },\n success: function (res) {\n // res = [{label: '1', value: '1', group: '1'}, ...]\n // success is called after options are added, but\n // groups need to be added manually below\n $.each(res, function (index, elem) {\n // Call selectize.addOptionGroup once for each optgroup; the\n // first argument is the group ID, the second is an object with\n // the group's label and value. We use the current settings of\n // the selectize object to decide the fieldnames of that obj.\n const optgroupId = elem[settings.optgroupField || \"optgroup\"];\n const optgroup: { [key: string]: string } = {};\n\n optgroup[settings.optgroupLabelField || \"label\"] = optgroupId;\n optgroup[settings.optgroupValueField || \"value\"] = optgroupId;\n selectize.addOptionGroup(optgroupId, optgroup);\n });\n callback(res);\n if (!loaded) {\n if (hasDefinedProperty(data, \"value\")) {\n selectize.setValue(data.value as any);\n } else if (settings.maxItems === 1) {\n // first item selected by default only for single-select\n selectize.setValue(res[0].value);\n }\n }\n loaded = true;\n },\n });\n };\n // perform an empty search after changing the `load` function\n selectize.load(function (callback) {\n selectize.settings.load.apply(selectize, [\"\", callback]);\n });\n } else if (hasDefinedProperty(data, \"value\")) {\n this.setValue(el, data.value);\n }\n\n updateLabel(data.label, getLabelNode(el));\n\n $(el).trigger(\"change\");\n }\n subscribe(el: SelectHTMLElement, callback: (x: boolean) => void): void {\n $(el).on(\n \"change.selectInputBinding\",\n // event: Event\n () => {\n // https://github.com/rstudio/shiny/issues/2162\n // Prevent spurious events that are gonna be squelched in\n // a second anyway by the onItemRemove down below\n if (el.nonempty && this.getValue(el) === \"\") {\n return;\n }\n callback(false);\n }\n );\n }\n unsubscribe(el: HTMLElement): void {\n $(el).off(\".selectInputBinding\");\n }\n initialize(el: SelectHTMLElement): void {\n this._selectize(el);\n }\n protected _selectize(\n el: SelectHTMLElement,\n update = false\n ): SelectizeInfo | undefined {\n // Apps like 008-html do not have the selectize js library\n // Safe-guard against missing the selectize js library\n if (!$.fn.selectize) return undefined;\n const $el = $(el);\n const config = $el\n .parent()\n .find('script[data-for=\"' + $escape(el.id) + '\"]');\n\n if (config.length === 0) return undefined;\n\n let options: SelectizeOptions & {\n labelField: \"label\";\n valueField: \"value\";\n searchField: [\"label\"];\n onItemRemove?: (value: string) => void;\n onDropdownClose?: () => void;\n } = $.extend(\n {\n labelField: \"label\",\n valueField: \"value\",\n searchField: [\"label\"],\n },\n JSON.parse(config.html())\n );\n\n // selectize created from selectInput()\n if (typeof config.data(\"nonempty\") !== \"undefined\") {\n el.nonempty = true;\n options = $.extend(options, {\n onItemRemove: function (this: SelectizeInfo, value: string) {\n if (this.getValue() === \"\")\n $(\"select#\" + $escape(el.id))\n .empty()\n .append(\n $(\"