UNPKG

streamlit-component-lib

Version:

Support code for Streamlit Components

217 lines (216 loc) 8.73 kB
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; // Safari doesn't support the EventTarget class, so we use a shim. import { ArrowTable } from "./ArrowTable"; /** Messages from Component -> Streamlit */ var ComponentMessageType; (function (ComponentMessageType) { // A component sends this message when it's ready to receive messages // from Streamlit. Streamlit won't send any messages until it gets this. // Data: { apiVersion: number } ComponentMessageType["COMPONENT_READY"] = "streamlit:componentReady"; // The component has a new widget value. Send it back to Streamlit, which // will then re-run the app. // Data: { value: any } ComponentMessageType["SET_COMPONENT_VALUE"] = "streamlit:setComponentValue"; // The component has a new height for its iframe. // Data: { height: number } ComponentMessageType["SET_FRAME_HEIGHT"] = "streamlit:setFrameHeight"; })(ComponentMessageType || (ComponentMessageType = {})); /** * Streamlit communication API. * * Components can send data to Streamlit via the functions defined here, * and receive data from Streamlit via the `events` property. */ export var Streamlit = /** @class */ (function () { function Streamlit() { } /** * The Streamlit component API version we're targeting. * There's currently only 1! */ Streamlit.API_VERSION = 1; Streamlit.RENDER_EVENT = "streamlit:render"; /** Dispatches events received from Streamlit. */ Streamlit.events = new EventTarget(); Streamlit.registeredMessageListener = false; /** * Tell Streamlit that the component is ready to start receiving data. * Streamlit will defer emitting RENDER events until it receives the * COMPONENT_READY message. */ Streamlit.setComponentReady = function () { if (!Streamlit.registeredMessageListener) { // Register for message events if we haven't already window.addEventListener("message", Streamlit.onMessageEvent); Streamlit.registeredMessageListener = true; } Streamlit.sendBackMsg(ComponentMessageType.COMPONENT_READY, { apiVersion: Streamlit.API_VERSION }); }; /** * Report the component's height to Streamlit. * This should be called every time the component changes its DOM - that is, * when it's first loaded, and any time it updates. */ Streamlit.setFrameHeight = function (height) { if (height === undefined) { // `height` is optional. If undefined, it defaults to scrollHeight, // which is the entire height of the element minus its border, // scrollbar, and margin. height = document.body.scrollHeight; } if (height === Streamlit.lastFrameHeight) { // Don't bother updating if our height hasn't changed. return; } Streamlit.lastFrameHeight = height; Streamlit.sendBackMsg(ComponentMessageType.SET_FRAME_HEIGHT, { height: height }); }; /** * Set the component's value. This value will be returned to the Python * script, and the script will be re-run. * * For example: * * JavaScript: * Streamlit.setComponentValue("ahoy!") * * Python: * value = st.my_component(...) * st.write(value) # -> "ahoy!" * * The value must be an ArrowTable, a typed array, an ArrayBuffer, or be * serializable to JSON. */ Streamlit.setComponentValue = function (value) { var dataType; if (value instanceof ArrowTable) { dataType = "dataframe"; value = value.serialize(); } else if (isTypedArray(value)) { // All typed arrays get sent as Uint8Array, because that's what our // protobuf library uses for the "bytes" field type. dataType = "bytes"; value = new Uint8Array(value.buffer); } else if (value instanceof ArrayBuffer) { dataType = "bytes"; value = new Uint8Array(value); } else { dataType = "json"; } Streamlit.sendBackMsg(ComponentMessageType.SET_COMPONENT_VALUE, { value: value, dataType: dataType }); }; /** Receive a ForwardMsg from the Streamlit app */ Streamlit.onMessageEvent = function (event) { var type = event.data["type"]; switch (type) { case Streamlit.RENDER_EVENT: Streamlit.onRenderMessage(event.data); break; } }; /** * Handle an untyped Streamlit render event and redispatch it as a * StreamlitRenderEvent. */ Streamlit.onRenderMessage = function (data) { var args = data["args"]; if (args == null) { console.error("Got null args in onRenderMessage. This should never happen"); args = {}; } // Parse our dataframe arguments with arrow, and merge them into our args dict var dataframeArgs = data["dfs"] && data["dfs"].length > 0 ? Streamlit.argsDataframeToObject(data["dfs"]) : {}; args = __assign(__assign({}, args), dataframeArgs); var disabled = Boolean(data["disabled"]); var theme = data["theme"]; if (theme) { _injectTheme(theme); } // Dispatch a render event! var eventData = { disabled: disabled, args: args, theme: theme }; var event = new CustomEvent(Streamlit.RENDER_EVENT, { detail: eventData }); Streamlit.events.dispatchEvent(event); }; Streamlit.argsDataframeToObject = function (argsDataframe) { var argsDataframeArrow = argsDataframe.map(function (_a) { var key = _a.key, value = _a.value; return [key, Streamlit.toArrowTable(value)]; }); return Object.fromEntries(argsDataframeArrow); }; Streamlit.toArrowTable = function (df) { var _a; var data = (_a = df.data, _a.data), index = _a.index, columns = _a.columns, styler = _a.styler; return new ArrowTable(data, index, columns, styler); }; /** Post a message to the Streamlit app. */ Streamlit.sendBackMsg = function (type, data) { window.parent.postMessage(__assign({ isStreamlitMessage: true, type: type }, data), "*"); }; return Streamlit; }()); var _injectTheme = function (theme) { var style = document.createElement("style"); document.head.appendChild(style); style.innerHTML = "\n :root {\n --primary-color: ".concat(theme.primaryColor, ";\n --background-color: ").concat(theme.backgroundColor, ";\n --secondary-background-color: ").concat(theme.secondaryBackgroundColor, ";\n --text-color: ").concat(theme.textColor, ";\n --font: ").concat(theme.font, ";\n }\n\n body {\n background-color: var(--background-color);\n color: var(--text-color);\n }\n "); }; /** True if the value is a TypedArray. */ function isTypedArray(value) { var isBigIntArray = false; try { isBigIntArray = value instanceof BigInt64Array || value instanceof BigUint64Array; } catch (e) { // Ignore cause Safari does not support this // https://caniuse.com/mdn-javascript_builtins_bigint64array } return (value instanceof Int8Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int16Array || value instanceof Uint16Array || value instanceof Int32Array || value instanceof Uint32Array || value instanceof Float32Array || value instanceof Float64Array || isBigIntArray); }