diff --git a/js/repl/Repl.tsx b/js/repl/Repl.tsx index 9f78c5b836..d538204f78 100644 --- a/js/repl/Repl.tsx +++ b/js/repl/Repl.tsx @@ -6,7 +6,7 @@ import React, { type ChangeEvent } from "react"; import { prettySize, compareVersions } from "./Utils"; import ErrorBoundary from "./ErrorBoundary"; import CodeMirrorPanel from "./CodeMirrorPanel"; -import ReplOptions from "./ReplOptions"; +import ReplOptions, { IndeterminateCheckboxStatus } from "./ReplOptions"; import StorageService from "./StorageService"; import UriUtils from "./UriUtils"; import loadBundle from "./loadBundle"; @@ -581,10 +581,15 @@ class Repl extends React.Component { }, this._pluginsUpdatedSetStateCallback); }; - _onAssumptionsChange = (value: string, isChecked: boolean) => { + _onAssumptionsChange = ( + value: string, + status: IndeterminateCheckboxStatus + ) => { const { assumptions } = this.state.envConfig; - if (isChecked === true) assumptions[value] = isChecked; - else delete assumptions[value]; + if (status === IndeterminateCheckboxStatus.Indeterminate) + delete assumptions[value]; + else assumptions[value] = status === 1; + this.setState((state) => { return { envConfig: { ...state.envConfig, assumptions } }; }, this._pluginsUpdatedSetStateCallback); diff --git a/js/repl/ReplOptions.tsx b/js/repl/ReplOptions.tsx index 5673a4d368..dbfb8f4e66 100644 --- a/js/repl/ReplOptions.tsx +++ b/js/repl/ReplOptions.tsx @@ -84,7 +84,10 @@ type ToggleVersion = (event: ChangeEvent) => void; type ShowOfficialExternalPluginsChanged = (value: string) => void; type PluginSearch = (value: string) => void; type PluginChange = (plugin: any) => void; -type AssumptionsChange = (value: string, isChecked: boolean) => void; +type AssumptionsChange = ( + value: string, + status: IndeterminateCheckboxStatus +) => void; type Props = { babelVersion: string | undefined | null; @@ -182,6 +185,57 @@ const PresetOption = ({ ); }; +export const enum IndeterminateCheckboxStatus { + Indeterminate = -1, + Unchecked = 0, + Checked = 1, +} + +class IndeterminateCheckbox extends React.Component<{ + status: IndeterminateCheckboxStatus; + className: string; + onChange: (status: IndeterminateCheckboxStatus) => void; +}> { + state: { status: IndeterminateCheckboxStatus } = { status: -1 }; + ele: HTMLInputElement = null; + status: IndeterminateCheckboxStatus = -1; + componentDidMount() { + this.ele.indeterminate = true; + this.state.status = this.props.status; + } + + componentDidUpdate(prev) { + const status = this.state.status; + + if (status === IndeterminateCheckboxStatus.Indeterminate) { + this.ele.indeterminate = true; + } else { + this.ele.indeterminate = false; + this.ele.checked = status === IndeterminateCheckboxStatus.Checked; + } + + if (prev.status !== status) { + this.props.onChange(status); + } + } + + render() { + return ( + (this.ele = ele)} + onClick={(event) => { + event.nativeEvent.stopImmediatePropagation(); + this.setState({ + status: ++this.state.status > 1 ? -1 : this.state.status, + }); + }} + /> + ); + } +} + export default function ReplOptions(props: Props) { return (
@@ -726,8 +780,19 @@ class ExpandedContainer extends Component { {ASSUMPTIONS_OPTIONS.map((option) => { const isChecked = assumptions[option] === "undefined" - ? false + ? undefined : assumptions[option]; + + const button = ( + { + this._onAssumptionsCheck(option, status); + }} + /> + ); + return ( ); })} @@ -826,8 +884,8 @@ class ExpandedContainer extends Component { this.props.showOfficialExternalPluginsChanged(value); }; - _onAssumptionsCheck = (value, isChecked) => { - this.props.onAssumptionsChange(value, isChecked); + _onAssumptionsCheck = (value, status) => { + this.props.onAssumptionsChange(value, status); }; _pluginChanged = (e, plugin) => {