Compare commits

...

2 commits

Author SHA1 Message Date
2b0d8bc2c6 a bit more progress 2025-05-12 00:02:14 +02:00
a9ae4f9888 decent progress 2025-05-11 22:54:23 +02:00
15 changed files with 408 additions and 171 deletions

View file

@ -3,11 +3,11 @@ import { useEffect, useState } from 'react';
import './App.css' import './App.css'
import { Editor, initialEditorState, type EditorState } from './Editor' import { Editor, initialEditorState, type EditorState } from './Editor'
export function App() { export function App() {
const [state, setState] = useState<EditorState>(initialEditorState); const [state, setState] = useState<EditorState>(initialEditorState);
useEffect(() => { useEffect(() => {
window['APP_STATE'] = state;
// console.log("EDITOR STATE:", state); // console.log("EDITOR STATE:", state);
}, [state]); }, [state]);
@ -18,7 +18,12 @@ export function App() {
</header> </header>
<main> <main>
<Editor state={state} setState={setState}></Editor> <Editor
state={state}
setState={setState}
onResolve={() => {console.log("toplevel resolved")}}
onCancel={() => {console.log("toplevel canceled")}}
/>
</main> </main>
<footer> <footer>

View file

@ -5,7 +5,7 @@
} }
.functionName { .functionName {
text-align: center; /* text-align: center; */
} }
@ -16,43 +16,23 @@
border: solid 10px transparent; border: solid 10px transparent;
border-left-color: rgba(242, 253, 146); border-left-color: rgba(242, 253, 146);
margin-left: 0px; margin-left: 0px;
/* z-index: 1; */
} }
.inputParam { .inputParam {
height: 20px; /* height: 20px; */
margin-right: 20px; margin-right: 20px;
/* vertical-align: ; */
/* margin-left: 0; */
display: inline-block; display: inline-block;
/* border: solid 1px black; */
background-color: rgba(242, 253, 146); background-color: rgba(242, 253, 146);
/* border-radius: 10px; */
} }
.typeAnnot { .typeAnnot {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
} }
/* .outputParam:before {
content: "";
position: absolute;
width: 0px;
height: 0px;
margin-left: -28px;
margin-top: 0px;
border-style: solid;
border-color: orange;
border-width: 14px;
border-left-color: transparent;
} */
.outputParam { .outputParam {
text-align: left;
vertical-align: top; vertical-align: top;
/* margin-left: 28px; */ /* margin-left: 28px; */
padding: 0px; padding: 0px;

View file

@ -1,75 +1,104 @@
import { apply, getType, getInst } from "dope2";
import type { Dynamic, State2Props } from "./util/extra"; import type { Dynamic, State2Props } from "./util/extra";
import { Editor, type EditorState } from "./Editor"; import { Editor, type EditorState } from "./Editor";
import "./CallBlock.css"; import "./CallBlock.css";
import { useEffect } from "react";
import { Type } from "./Type";
import { Value } from "./Value";
import { focusPrevElement } from "./util/dom_trickery";
export interface CallBlockState { export interface CallBlockState {
kind: "call"; kind: "call";
env: any; env: any;
fn: EditorState; fn: EditorState;
input: EditorState; input: EditorState;
resolved: undefined | Dynamic;
rollback: EditorState;
} }
interface CallBlockProps extends State2Props<CallBlockState> { interface CallBlockProps extends State2Props<CallBlockState> {
onResolve: (d: Dynamic) => void; onResolve: (resolved: EditorState) => void;
} }
export function CallBlock({ state: {kind, env, fn, input}, setState, onResolve }: CallBlockProps) { export function CallBlock({ state: {kind, env, fn, input, resolved, rollback }, setState, onResolve }: CallBlockProps) {
const setResolved = (resolved?: Dynamic) => {
setState({kind, env, fn, input, resolved, rollback});
}
const makeTheCall = (input, fn) => {
console.log('makeTheCall...')
try {
const outputResolved = apply(input.resolved)(fn.resolved);
setResolved(outputResolved);
console.log("onResolve callblock..")
onResolve({
kind, env, fn, input, resolved: outputResolved, rollback
});
}
catch (e) {
console.log('makeTheCall:', e);
}
}
const setFn = (fn: EditorState) => { const setFn = (fn: EditorState) => {
setState({kind, env, fn, input}); setState({kind, env, fn, input, resolved, rollback});
} }
const setInput = (input: EditorState) => { const setInput = (input: EditorState) => {
setState({kind, env, fn, input}); setState({kind, env, fn, input, resolved, rollback});
} }
const onFnResolve = (fnState) => {
console.log('my fn resolved')
if (input.resolved) {
makeTheCall(input, fnState);
}
else {
// setFn(fnState);
setResolved(undefined);
onResolve({
kind, env, fn: fnState, input, resolved: undefined, rollback
});
}
}
const onInputResolve = (inputState) => {
console.log('my input resolved')
if (fn.resolved) {
makeTheCall(inputState, fn);
}
else {
// setInput(inputState);
setResolved(undefined);
onResolve({
kind, env, fn, input: inputState, resolved: undefined, rollback
});
}
}
const onFnCancel = () => {
}
const onInputCancel = () => {
// we become what we were before we became a CallBlock
if (rollback) {
setState(rollback);
focusPrevElement();
}
}
return <span className="functionBlock"> return <span className="functionBlock">
<div className="functionName"> <div className="functionName">
&#119891;&#119899;&nbsp; &#119891;&#119899;&nbsp;
<Editor state={fn} setState={setFn}/> <Editor state={fn} setState={setFn}
onResolve={onFnResolve} onCancel={onFnCancel}/>
</div> </div>
<div className="functionParams"> <div className="functionParams">
<div className="outputParam"> <div className="outputParam">
<div className="inputParam"> <div className="inputParam">
<Editor state={input} setState={setInput} /> <Editor state={input} setState={setInput} onResolve={onInputResolve} onCancel={onInputCancel} />
</div> </div>
result { resolved
? <Value dynamic={resolved} />
: <></> }
</div> </div>
</div> </div>
</span>; </span>;
} }
// function FunctionBlock({env, done, name, funDynamic}) {
// const functionType = getType(funDynamic);
// const inType = functionType.params[0](functionType);
// const [outDynamic, setOutDynamic] = useState<any>(null);
// const [input, setInput] = useState<any>(null);
// const gotInput = (name, inDynamic) => {
// setInput([name, inDynamic]);
// const outDynamic = apply(inDynamic)(funDynamic);
// setOutDynamic(outDynamic);
// // propagate result up
// done(outDynamic);
// };
// return <span className="functionBlock">
// <div className="functionName">
// &#119891;&#119899;&nbsp;
// {name}
// {/* <Type type={functionType}/> */}
// </div>
// <div className="functionParams">
// <div className="outputParam">
// <div className="inputParam">
// {
// (input === null)
// ? <InputBlock env={env} done={gotInput} type={inType} />
// : <DynamicBlock env={env} name={input[0]} dynamic={input[1]}/>
// }
// </div>
// {
// (input === null)
// ? <>&nbsp;</>
// : <DynamicBlock env={env} name="" dynamic={outDynamic} />
// }
// </div>
// </div>
// </span>;
// }

8
src/Editor.css Normal file
View file

@ -0,0 +1,8 @@
.typeSignature {
display: inline-block;
/* vertical-align:; */
}
.command {
width: 136px;
}

View file

@ -3,6 +3,11 @@ import { getSymbol, getType, module2Env, ModuleStd, symbolFunction } from "dope2
import { InputBlock, type InputBlockState } from "./InputBlock"; import { InputBlock, type InputBlockState } from "./InputBlock";
import { type Dynamic, type State2Props } from "./util/extra"; import { type Dynamic, type State2Props } from "./util/extra";
import { CallBlock, type CallBlockState } from "./CallBlock"; import { CallBlock, type CallBlockState } from "./CallBlock";
import { useEffect, useState } from "react";
import { Type } from "./Type";
import "./Editor.css"
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
interface LetInBlockState { interface LetInBlockState {
kind: "let"; kind: "let";
@ -10,12 +15,17 @@ interface LetInBlockState {
name: string; name: string;
value: EditorState; value: EditorState;
inner: EditorState; inner: EditorState;
resolved: undefined | Dynamic;
rollback?: EditorState;
} }
interface LambdaBlockState { interface LambdaBlockState {
kind: "lambda"; kind: "lambda";
env: any;
paramName: string; paramName: string;
expr: EditorState; expr: EditorState;
resolved: undefined | Dynamic;
rollback?: EditorState;
} }
export type EditorState = export type EditorState =
@ -29,72 +39,107 @@ export const initialEditorState: EditorState = {
env: module2Env(ModuleStd), env: module2Env(ModuleStd),
text: "", text: "",
resolved: undefined, resolved: undefined,
rollback: undefined,
}; };
type EditorProps = State2Props<EditorState>; interface EditorProps extends State2Props<EditorState> {
onResolve: (state: EditorState) => void;
onCancel: () => void;
}
const dontFilter = () => true; const dontFilter = () => true;
export function Editor({state, setState}: EditorProps) { function getCommands(type) {
let onResolve; const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'Tab'];
switch (state.kind) { if (getSymbol(type) === symbolFunction) {
case "input": commands.push('c');
onResolve = (resolved: InputBlockState) => {
console.log('resolved!', state, resolved);
if (resolved) {
const type = getType(resolved.resolved);
if (getSymbol(type) === symbolFunction) {
console.log('function!');
// we were InputBlock
// now we become FunctionBlock
setState({
kind: "call",
env: state.env,
fn: resolved,
input: initialEditorState,
})
}
}
// const type = getType(state.resolved);
// if (getSymbol(type) === symbolFunction) {
// console.log('function!');
// console.log('editor state:', state);
// }
}
return <InputBlock state={state} setState={setState} filter={dontFilter} onResolve={onResolve} />;
case "call":
onResolve = (d: Dynamic) => {}
return <CallBlock state={state} setState={setState} onResolve={onResolve} />;
case "let":
return <></>;
case "lambda":
return <></>;
} }
return commands;
} }
// function DynamicBlock({env, name, dynamic}) { export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
// const type = getType(dynamic); const [needCommand, setNeedCommand] = useState(false);
// if (getSymbol(type) === symbolFunction) { const onMyResolve = (editorState: EditorState) => {
// return <FunctionBlock env={env} name={name} funDynamic={dynamic} />; setState(editorState);
// } if (editorState.resolved) {
// else return <>{getInst(dynamic).toString()} :: <Type type={type}/></>; setNeedCommand(true);
// } }
else {
// function InputBlock({env, done, type}) { // unresolved
// const filterInputType = ([_, dynamic]) => { setNeedCommand(false);
// try { onResolve(editorState); // pass up the fact that we're unresolved
// unify(type, getType(dynamic)); }
// return true; }
// } catch (e) { // const onMyCancel
// if (!(e instanceof UnifyError)) { const onCommand = (e: React.KeyboardEvent) => {
// console.error(e); const type = getType(state.resolved);
// } const commands = getCommands(type);
// return false; if (!commands.includes(e.key)) {
// } return;
// } }
// return <> e.preventDefault();
// <span className="typeAnnot"><InputBlock env={env} done={done} filter={filterInputType} /></span> setNeedCommand(false);
// <span className="typeAnnot">:: <Type type={type}/></span> // u -> pass Up
// </>; if (e.key === "u" || e.key === "Enter" || e.key === "Tab") {
// } onResolve(state);
return;
}
// c -> Call
if (e.key === "c") {
// we become CallBlock
setState({
kind: "call",
env: state.env,
fn: state,
input: initialEditorState,
resolved: undefined,
rollback: state,
});
return;
}
// t -> Transform
if (e.key === "t") {
// we become CallBlock
setState({
kind: "call",
env: state.env,
fn: initialEditorState,
input: state,
resolved: undefined,
rollback: state,
});
return;
}
if (e.key === "Backspace" || e.key === "ArrowLeft") {
focusPrevElement();
return;
}
};
const renderBlock = () => {
switch (state.kind) {
case "input":
return <InputBlock state={state} setState={setState} filter={dontFilter} onResolve={onMyResolve} onCancel={onCancel} />;
case "call":
return <CallBlock state={state} setState={setState} onResolve={onMyResolve} />;
case "let":
return <></>;
case "lambda":
return <></>;
}
}
return <>
{renderBlock()}
{
(state.resolved)
? <div className="typeSignature">
:: <Type type={getType(state.resolved)} />
{ (needCommand)
? <input autoFocus={true} className="editable command" placeholder="<enter command>" onKeyDown={onCommand} value=""/>
: <></>
}
</div>
: <></>
}
</>;
}

View file

@ -1,14 +1,32 @@
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@500&display=swap');
.suggest { .suggest {
margin-left: -3.5px;
margin-right: 5px;
color: #aaa; color: #aaa;
min-width: 30px;
font-size: 13pt;
font-family: "Inconsolata", monospace;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-variation-settings: "wdth" 100;
} }
.suggestions { .suggestions {
display: none;
}
.suggestions {
display: block;
text-align: left;
position: absolute; position: absolute;
/* display: inline-block; */ border: solid 1px dodgerblue;
/* top: 20px; */
border: solid 1px lightgrey;
cursor: pointer; cursor: pointer;
max-height: calc(100vh - 44px); max-height: calc(100vh - 44px);
overflow: scroll; overflow: scroll;
z-index: 10;
background-color: white;
} }
.selected { .selected {
background-color: dodgerblue; background-color: dodgerblue;
@ -17,6 +35,23 @@
.editable { .editable {
outline: 0px solid transparent; outline: 0px solid transparent;
display: inline-block; display: inline-block;
border: 0;
/* box-sizing: border-box; */
/* width: ; */
/* border: 1px black solid; */
/* border-style: dashed none dashed; */
margin-left: 4px;
margin-right: 0;
font-size: 13pt;
font-family: "Inconsolata", monospace;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-variation-settings: "wdth" 100;
background-color: transparent;
} }
.border-around-input { .border-around-input {
border: 1px solid black; border: 1px solid black;

View file

@ -7,36 +7,37 @@ import { Type } from "./Type";
import "./InputBlock.css"; import "./InputBlock.css";
import type { Dynamic, State2Props } from "./util/extra"; import type { Dynamic, State2Props } from "./util/extra";
import type { EditorState } from "./Editor";
import { ShowIf } from "./ShowIf";
export interface InputBlockState { export interface InputBlockState {
kind: "input"; kind: "input";
env: any; env: any;
text: string; text: string;
resolved: undefined | Dynamic; resolved: undefined | Dynamic;
rollback?: EditorState;
} }
interface InputBlockProps extends State2Props<InputBlockState> { interface InputBlockProps extends State2Props<InputBlockState> {
filter: (ls: any[]) => boolean; filter: (ls: any[]) => boolean;
onResolve: (state: InputBlockState) => void; onResolve: (state: InputBlockState) => void;
onCancel: () => void;
} }
export function InputBlock({ state: {kind, env, text, resolved}, setState, filter, onResolve }: InputBlockProps) { export function InputBlock({ state: {kind, env, text, resolved, rollback}, setState, filter, onResolve, onCancel }: InputBlockProps) {
const ref = useRef<any>(null); const ref = useRef<any>(null);
useEffect(() => { useEffect(() => {
ref.current?.focus(); ref.current?.focus();
if (ref.current) {
ref.current.textContent = text;
}
}, []); }, []);
const [i, setI] = useState(0); // selected suggestion const [i, setI] = useState(0); // selected suggestion
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
const setText = (text: string) => { const setText = (text: string) => {
setState({kind, env, text, resolved}); setState({kind, env, text, resolved, rollback});
} }
const setResolved = (resolved: Dynamic) => { const setResolved = (resolved: Dynamic) => {
setState({kind, env, text, resolved}); setState({kind, env, text, resolved, rollback});
} }
const singleSuggestion = trie.growPrefix(env.name2dyn)(text); const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
@ -53,28 +54,45 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte
useEffect(() => { useEffect(() => {
setI(0); // reset setI(0); // reset
const found = trie.get(env.name2dyn)(text); if (ref.current) {
setResolved(found); // may be undefined ref.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`;
}
}, [text]); }, [text]);
const onSelectSuggestion = ([name, dynamic]) => {
const onSelectSuggestion = ([name, _dynamic]) => { console.log(name);
// setText(name); // setText(name);
// ref.current.textContent = name; // ref.current.textContent = name;
// setRightMostCaretPosition(ref.current); // setRightMostCaretPosition(ref.current);
// setI(0); // setI(0);
// setResolved(dynamic);
console.log("onResolve inputblock..")
onResolve({ onResolve({
env,
kind: "input", kind: "input",
resolved: _dynamic, env,
text: name, text: name,
resolved: dynamic,
rollback,
}); });
}; };
const onInput = e => { const onInput = e => {
setText(e.target.textContent); setText(e.target.value);
if (resolved) {
onResolve({
kind: "input",
env,
text: e.target.value,
resolved: undefined,
rollback,
});
}
}; };
const getCaretPosition = () => {
return ref.current.selectionStart;
}
const onKeyDown = (e: React.KeyboardEvent) => { const onKeyDown = (e: React.KeyboardEvent) => {
const fns = { const fns = {
Tab: () => { Tab: () => {
@ -87,10 +105,14 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte
if (singleSuggestion.length > 0) { if (singleSuggestion.length > 0) {
const newText = text + singleSuggestion; const newText = text + singleSuggestion;
setText(newText); setText(newText);
ref.current.textContent = newText; // ref.current.textContent = newText;
setRightMostCaretPosition(ref.current); setRightMostCaretPosition(ref.current);
e.preventDefault(); e.preventDefault();
} }
else {
onSelectSuggestion(suggestions[i]);
e.preventDefault();
}
} }
}, },
ArrowDown: () => { ArrowDown: () => {
@ -117,33 +139,50 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte
onSelectSuggestion(suggestions[i]); onSelectSuggestion(suggestions[i]);
e.preventDefault(); e.preventDefault();
}, },
Backspace: () => {
if (text.length === 0) {
onCancel();
e.preventDefault();
}
}
}; };
fns[e.key]?.(); fns[e.key]?.();
}; };
return <span> return <span>
<span className="border-around-input"> <span className="">
<span className="editable" ref={ref} contentEditable="plaintext-only" onInput={onInput} onKeyDown={onKeyDown} <input ref={ref} placeholder="start typing..." className="editable" value={text} onInput={onInput} onKeyDown={onKeyDown} onFocus={() => setHaveFocus(true)} onBlur={() => setTimeout(() => setHaveFocus(false), 200)}/>
onFocus={() => setHaveFocus(true)} <span className="text-block suggest">{singleSuggestion}</span>
onBlur={() => { </span>
// hacky, but couldn't find another way: <ShowIf cond={haveFocus}>
// setTimeout(resetFocus, 0); <Suggestions
setHaveFocus(false); suggestions={suggestions}
} } style={{ height: 19 }}></span> onSelect={onSelectSuggestion}
{ i={i} setI={setI} />
(haveFocus) </ShowIf>
? <Suggestions suggestions={suggestions} onSelect={onSelectSuggestion} i={i} setI={setI} />
: <></>
}
<span className="text-block suggest">{singleSuggestion}</span>
</span>
</span>; </span>;
} }
function Suggestions({ suggestions, onSelect, i, setI }) { function Suggestions({ suggestions, onSelect, i, setI }) {
const onMouseEnter = j => () => {
setI(j);
};
const onMouseDown = j => () => {
setI(j);
onSelect(suggestions[i]);
};
return (suggestions.length > 0) ? return (suggestions.length > 0) ?
<div className="suggestions"> <div className={"suggestions"}>
{suggestions.map(([name, dynamic], j) => <div key={`${j}_${name}`} className={i === j ? "selected" : ""} onClick={() => setI(j)} onDoubleClick={() => onSelect(suggestions[i])}>{name} :: <Type type={getType(dynamic)} /></div>)} {suggestions.map(([name, dynamic], j) =>
<div
key={`${j}_${name}`}
className={i === j ? "selected" : ""}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
{name} :: <Type type={getType(dynamic)} />
</div>)
}
</div> </div>
: <></>; : <></>;
} }

9
src/ShowIf.tsx Normal file
View file

@ -0,0 +1,9 @@
// syntactic sugar
export function ShowIf({cond, children}) {
if (cond) {
return <>{children}</>;
}
else {
return <></>;
}
}

View file

@ -22,7 +22,7 @@ export function Type({type}) {
case symbolDictIterator: case symbolDictIterator:
return <BinaryType type={type} cssClass="dictType iteratorType" infix="*&rArr;" prefix="{" suffix="}"/>; return <BinaryType type={type} cssClass="dictType iteratorType" infix="*&rArr;" prefix="{" suffix="}"/>;
default: default:
return <div className="type">{getHumanReadableName(symbol)}</div> return <div className="type">{getHumanReadableName(symbol)}</div>
} }
} }

9
src/Value.css Normal file
View file

@ -0,0 +1,9 @@
.value {
border: 1px solid black;
border-radius: 10px;
margin-left: 2px;
margin-right: 2px;
padding-left: 2px;
padding-right: 2px;
background-color: white;
}

60
src/Value.tsx Normal file
View file

@ -0,0 +1,60 @@
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight} from "dope2";
import "./Value.css";
export function Value({dynamic}) {
const type = getType(dynamic);
const inst = getInst(dynamic);
if (eqType(type)(Double)) {
return <ValueDouble val={inst}/>;
}
if (eqType(type)(Int)) {
return <ValueInt val={inst}/>;
}
const symbol = getSymbol(type);
switch (symbol) {
case symbolFunction:
return <ValueFunction/>;
// return <BinaryType type={type} cssClass="functionType" infix="&rarr;" prefix="" suffix=""/>;
// case symbolProduct:
// return <BinaryType type={type} cssClass="productType" infix="&#10799;" prefix="" suffix=""/>;
case symbolSum:
return <ValueSum val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>;
case symbolProduct:
return <ValueProduct val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>;
// case symbolDict:
// return <BinaryType type={type} cssClass="dictType" infix="&rArr;" prefix="{" suffix="}"/>;
// case symbolSet:
// return <UnaryType type={type} cssClass="setType" prefix="{" suffix="}" />;
case symbolList:
return <List val={inst} elemType={type.params[0](type)} />;
default:
return <>don't know how to show value</>;
}
}
function ValueDouble({val}) {
return <span className="value">{val.toString()}</span>;
}
function ValueInt({val}) {
return <span className="value">{val.toString()}</span>;
}
function ValueFunction() {
return <>&#119891;&#119899;&nbsp;</>;
}
// function Sum({val, elemType}) {
// return
// }
function List({val, elemType}) {
return <span className="listType">[{val.map((v, i) => <Value dynamic={{i:v, t:elemType}}/>)}]</span>;
}
function ValueSum({val, leftType, rightType}) {
return match(val)
(l => <>L <Value dynamic={{i:l, t:leftType}}/></>)
(r => <>R <Value dynamic={{i:r, t:rightType}}/></>);
}
function ValueProduct({val, leftType, rightType}) {
return <>(<Value dynamic={{i:getLeft(val), t:leftType}}/>,&nbsp;<Value dynamic={{i:getRight(val), t:rightType}} />)</>;
}

View file

@ -1,3 +1,18 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
body { body {
margin: 0; margin: 0;
font-family: "Roboto", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-variation-settings:
"wdth" 100;
}
:focus-within:not(body) {
/* outline: 2px solid black; */
/* background-color: aqua; */
} }

View file

@ -33,13 +33,13 @@ export function setRightMostCaretPosition(elem) {
} }
export function focusNextElement() { export function focusNextElement() {
const editable = Array.from<any>(document.querySelectorAll('[contenteditable]')); const editable = Array.from<any>(document.querySelectorAll('input'));
const index = editable.indexOf(document.activeElement); const index = editable.indexOf(document.activeElement);
editable[index+1]?.focus(); editable[index+1]?.focus();
} }
export function focusPrevElement() { export function focusPrevElement() {
const editable = Array.from<any>(document.querySelectorAll('[contenteditable]')); const editable = Array.from<any>(document.querySelectorAll('input'));
const index = editable.indexOf(document.activeElement); const index = editable.indexOf(document.activeElement);
const prevElem = editable[index-1] const prevElem = editable[index-1]
if (prevElem) { if (prevElem) {

View file

@ -1,3 +1,5 @@
import type { EditorState } from "../Editor";
export interface Dynamic { export interface Dynamic {
i: any; i: any;
t: any; t: any;
@ -6,5 +8,6 @@ export interface Dynamic {
export interface State2Props<T> { export interface State2Props<T> {
state: T; state: T;
// setState: (callback: (state: T) => T) => void; // setState: (callback: (state: T) => T) => void;
setState: (state: T) => void; // setState: (state: T) => void;
setState: (state: EditorState) => void;
} }

View file

@ -20,8 +20,8 @@
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": false,
"noUnusedParameters": true, "noUnusedParameters": false,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true