mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-12-20 13:37:49 +08:00
feature: tui onFocusChanged event
feature: add focus event for tui reconstruct: tui props and accesscontrol parse logic
This commit is contained in:
@@ -243,6 +243,18 @@ const AccessControlTUI = () => {
|
|||||||
/**
|
/**
|
||||||
* Basic Configuration Tab
|
* Basic Configuration Tab
|
||||||
*/
|
*/
|
||||||
|
const [getDetectInterval, setDetectInterval] = createSignal(
|
||||||
|
config().detectInterval.toString(),
|
||||||
|
);
|
||||||
|
const [getWatchInterval, setWatchInterval] = createSignal(
|
||||||
|
config().watchInterval.toString(),
|
||||||
|
);
|
||||||
|
const [getNoticeTimes, setNoticeTimes] = createSignal(
|
||||||
|
config().noticeTimes.toString(),
|
||||||
|
);
|
||||||
|
const [getDetectRange, setDetectRange] = createSignal(
|
||||||
|
config().detectRange.toString(),
|
||||||
|
);
|
||||||
const BasicTab = () => {
|
const BasicTab = () => {
|
||||||
return div(
|
return div(
|
||||||
{ class: "flex flex-col" },
|
{ class: "flex flex-col" },
|
||||||
@@ -251,10 +263,12 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Detect Interval (ms):"),
|
label({}, "Detect Interval (ms):"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => config().detectInterval?.toString() ?? "",
|
value: () => getDetectInterval(),
|
||||||
onInput: (value) => {
|
onInput: (value) => setDetectInterval(value),
|
||||||
const num = validateNumber(value);
|
onFocusChanged: () => {
|
||||||
|
const num = validateNumber(getDetectInterval());
|
||||||
if (num !== null) setConfig("detectInterval", num);
|
if (num !== null) setConfig("detectInterval", num);
|
||||||
|
else setDetectInterval(config().detectInterval.toString());
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -263,10 +277,12 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Watch Interval (ms):"),
|
label({}, "Watch Interval (ms):"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => config().watchInterval?.toString() ?? "",
|
value: () => getWatchInterval(),
|
||||||
onInput: (value) => {
|
onInput: (value) => setWatchInterval(value),
|
||||||
const num = validateNumber(value);
|
onFocusChanged: () => {
|
||||||
|
const num = validateNumber(getWatchInterval());
|
||||||
if (num !== null) setConfig("watchInterval", num);
|
if (num !== null) setConfig("watchInterval", num);
|
||||||
|
else setWatchInterval(config().watchInterval.toString());
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -275,10 +291,12 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Notice Times:"),
|
label({}, "Notice Times:"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => config().noticeTimes?.toString() ?? "",
|
value: () => getNoticeTimes(),
|
||||||
onInput: (value) => {
|
onInput: (value) => setNoticeTimes(value),
|
||||||
const num = validateNumber(value);
|
onFocusChanged: () => {
|
||||||
|
const num = validateNumber(getNoticeTimes());
|
||||||
if (num !== null) setConfig("noticeTimes", num);
|
if (num !== null) setConfig("noticeTimes", num);
|
||||||
|
else setNoticeTimes(config().noticeTimes.toString());
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -287,10 +305,12 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Detect Range:"),
|
label({}, "Detect Range:"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => config().detectRange?.toString() ?? "",
|
value: () => getDetectRange(),
|
||||||
onInput: (value) => {
|
onInput: (value) => setDetectRange(value),
|
||||||
const num = validateNumber(value);
|
onFocusChanged: () => {
|
||||||
|
const num = validateNumber(getDetectRange());
|
||||||
if (num !== null) setConfig("detectRange", num);
|
if (num !== null) setConfig("detectRange", num);
|
||||||
|
else setDetectRange(config().detectRange.toString());
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -436,6 +456,13 @@ const AccessControlTUI = () => {
|
|||||||
) => {
|
) => {
|
||||||
return () => {
|
return () => {
|
||||||
const toastConfig = config()[toastType];
|
const toastConfig = config()[toastType];
|
||||||
|
const [getTempToastConfig, setTempToastConfig] = createSignal({
|
||||||
|
title: textutils.serialiseJSON(toastConfig.title),
|
||||||
|
msg: textutils.serialiseJSON(toastConfig.msg),
|
||||||
|
prefix: toastConfig.prefix ?? "",
|
||||||
|
brackets: toastConfig.brackets ?? "",
|
||||||
|
bracketColor: toastConfig.bracketColor ?? "",
|
||||||
|
});
|
||||||
|
|
||||||
return div(
|
return div(
|
||||||
{ class: "flex flex-col w-full" },
|
{ class: "flex flex-col w-full" },
|
||||||
@@ -443,20 +470,34 @@ const AccessControlTUI = () => {
|
|||||||
input({
|
input({
|
||||||
class: "w-full",
|
class: "w-full",
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => textutils.serialiseJSON(toastConfig?.title) ?? "",
|
value: () => getTempToastConfig().title,
|
||||||
onInput: (value) => {
|
onInput: (value) =>
|
||||||
|
setTempToastConfig({
|
||||||
|
...getTempToastConfig(),
|
||||||
|
title: value,
|
||||||
|
}),
|
||||||
|
onFocusChanged: () => {
|
||||||
|
const currentToastConfig = config()[toastType];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = textutils.unserialiseJSON(value);
|
const parsed = textutils.unserialiseJSON(
|
||||||
if (parsed != undefined && typeof parsed === "object") {
|
getTempToastConfig().title,
|
||||||
const currentConfig = config();
|
) as MinecraftTextComponent;
|
||||||
const currentToast = currentConfig[toastType];
|
if (
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
parsed.text !== undefined &&
|
||||||
|
parsed.color !== undefined
|
||||||
|
) {
|
||||||
setConfig(toastType, {
|
setConfig(toastType, {
|
||||||
...currentToast,
|
...currentToastConfig,
|
||||||
title: parsed as MinecraftTextComponent,
|
title: parsed,
|
||||||
});
|
});
|
||||||
}
|
} else throw new Error("Invalid JSON");
|
||||||
} catch {
|
} catch {
|
||||||
// Invalid JSON, ignore
|
setTempToastConfig({
|
||||||
|
...getTempToastConfig(),
|
||||||
|
title: textutils.serialiseJSON(currentToastConfig.title),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -465,19 +506,31 @@ const AccessControlTUI = () => {
|
|||||||
input({
|
input({
|
||||||
class: "w-full",
|
class: "w-full",
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => textutils.serialiseJSON(toastConfig?.msg) ?? "",
|
value: () => getTempToastConfig().msg,
|
||||||
onInput: (value) => {
|
onInput: (value) =>
|
||||||
|
setTempToastConfig({ ...getTempToastConfig(), msg: value }),
|
||||||
|
onFocusChanged: () => {
|
||||||
|
const currentToastConfig = config()[toastType];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = textutils.unserialiseJSON(value);
|
const parsed = textutils.unserialiseJSON(
|
||||||
if (parsed != undefined && typeof parsed === "object") {
|
getTempToastConfig().msg,
|
||||||
const currentConfig = config();
|
) as MinecraftTextComponent;
|
||||||
const currentToast = currentConfig[toastType];
|
if (
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
parsed.text !== undefined &&
|
||||||
|
parsed.color !== undefined
|
||||||
|
) {
|
||||||
setConfig(toastType, {
|
setConfig(toastType, {
|
||||||
...currentToast,
|
...currentToastConfig,
|
||||||
msg: parsed as MinecraftTextComponent,
|
msg: parsed,
|
||||||
});
|
});
|
||||||
}
|
} else throw new Error("Invalid JSON");
|
||||||
} catch {
|
} catch {
|
||||||
|
setTempToastConfig({
|
||||||
|
...getTempToastConfig(),
|
||||||
|
msg: textutils.serialiseJSON(currentToastConfig.msg),
|
||||||
|
});
|
||||||
// Invalid JSON, ignore
|
// Invalid JSON, ignore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -488,11 +541,15 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Prefix:"),
|
label({}, "Prefix:"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => toastConfig?.prefix ?? "",
|
value: () => getTempToastConfig().prefix,
|
||||||
onInput: (value) => {
|
onInput: (value) =>
|
||||||
const currentConfig = config();
|
setTempToastConfig({ ...getTempToastConfig(), prefix: value }),
|
||||||
const currentToast = currentConfig[toastType];
|
onFocusChanged: () => {
|
||||||
setConfig(toastType, { ...currentToast, prefix: value });
|
const currentToastConfig = config()[toastType];
|
||||||
|
setConfig(toastType, {
|
||||||
|
...currentToastConfig,
|
||||||
|
prefix: getTempToastConfig().prefix,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -502,11 +559,15 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Brackets:"),
|
label({}, "Brackets:"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => toastConfig?.brackets ?? "",
|
value: () => getTempToastConfig().brackets,
|
||||||
onInput: (value) => {
|
onInput: (value) =>
|
||||||
const currentConfig = config();
|
setTempToastConfig({ ...getTempToastConfig(), brackets: value }),
|
||||||
const currentToast = currentConfig[toastType];
|
onFocusChanged: () => {
|
||||||
setConfig(toastType, { ...currentToast, brackets: value });
|
const currentToastConfig = config()[toastType];
|
||||||
|
setConfig(toastType, {
|
||||||
|
...currentToastConfig,
|
||||||
|
brackets: getTempToastConfig().brackets,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -516,11 +577,18 @@ const AccessControlTUI = () => {
|
|||||||
label({}, "Bracket Color:"),
|
label({}, "Bracket Color:"),
|
||||||
input({
|
input({
|
||||||
type: "text",
|
type: "text",
|
||||||
value: () => toastConfig?.bracketColor ?? "",
|
value: () => getTempToastConfig().bracketColor,
|
||||||
onInput: (value) => {
|
onInput: (value) =>
|
||||||
const currentConfig = config();
|
setTempToastConfig({
|
||||||
const currentToast = currentConfig[toastType];
|
...getTempToastConfig(),
|
||||||
setConfig(toastType, { ...currentToast, bracketColor: value });
|
bracketColor: value,
|
||||||
|
}),
|
||||||
|
onFocusChanged: () => {
|
||||||
|
const currentToastConfig = config()[toastType];
|
||||||
|
setConfig(toastType, {
|
||||||
|
...currentToastConfig,
|
||||||
|
bracketColor: getTempToastConfig().bracketColor,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
* Represents a node in the UI tree
|
* Represents a node in the UI tree
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Accessor } from "./reactivity";
|
import { ButtonProps, DivProps, InputProps, LabelProps } from "./components";
|
||||||
|
import { Accessor, Setter } from "./reactivity";
|
||||||
|
import { ScrollContainerProps } from "./scrollContainer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout properties for flexbox layout
|
* Layout properties for flexbox layout
|
||||||
@@ -34,7 +36,7 @@ export interface StyleProps {
|
|||||||
/**
|
/**
|
||||||
* Scroll properties for scroll containers
|
* Scroll properties for scroll containers
|
||||||
*/
|
*/
|
||||||
export interface ScrollProps {
|
export interface ScrollProps extends BaseProps {
|
||||||
/** Current horizontal scroll position */
|
/** Current horizontal scroll position */
|
||||||
scrollX: number;
|
scrollX: number;
|
||||||
/** Current vertical scroll position */
|
/** Current vertical scroll position */
|
||||||
@@ -69,6 +71,9 @@ export interface ComputedLayout {
|
|||||||
export interface BaseProps {
|
export interface BaseProps {
|
||||||
/** CSS-like class names for layout (e.g., "flex flex-col") */
|
/** CSS-like class names for layout (e.g., "flex flex-col") */
|
||||||
class?: string;
|
class?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
onFocusChanged?: Setter<boolean> | ((value: boolean) => void);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,6 +95,14 @@ export type UIObjectType =
|
|||||||
| "fragment"
|
| "fragment"
|
||||||
| "scroll-container";
|
| "scroll-container";
|
||||||
|
|
||||||
|
export type UIObjectProps =
|
||||||
|
| DivProps
|
||||||
|
| LabelProps
|
||||||
|
| InputProps
|
||||||
|
| ButtonProps
|
||||||
|
| ScrollProps
|
||||||
|
| ScrollContainerProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UIObject represents a node in the UI tree
|
* UIObject represents a node in the UI tree
|
||||||
* It can be a component, text, or a control flow element
|
* It can be a component, text, or a control flow element
|
||||||
@@ -99,7 +112,7 @@ export class UIObject {
|
|||||||
type: UIObjectType;
|
type: UIObjectType;
|
||||||
|
|
||||||
/** Props passed to the component */
|
/** Props passed to the component */
|
||||||
props: Record<string, unknown>;
|
props: UIObjectProps;
|
||||||
|
|
||||||
/** Children UI objects */
|
/** Children UI objects */
|
||||||
children: UIObject[];
|
children: UIObject[];
|
||||||
@@ -136,7 +149,7 @@ export class UIObject {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
type: UIObjectType,
|
type: UIObjectType,
|
||||||
props: Record<string, unknown> = {},
|
props: UIObjectProps = {},
|
||||||
children: UIObject[] = [],
|
children: UIObject[] = [],
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -155,7 +168,7 @@ export class UIObject {
|
|||||||
this.extractHandlers();
|
this.extractHandlers();
|
||||||
|
|
||||||
// Initialize cursor position for text inputs
|
// Initialize cursor position for text inputs
|
||||||
if (type === "input" && props.type !== "checkbox") {
|
if (type === "input" && (props as InputProps).type !== "checkbox") {
|
||||||
this.cursorPos = 0;
|
this.cursorPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,9 +181,9 @@ export class UIObject {
|
|||||||
maxScrollY: 0,
|
maxScrollY: 0,
|
||||||
contentWidth: 0,
|
contentWidth: 0,
|
||||||
contentHeight: 0,
|
contentHeight: 0,
|
||||||
showScrollbar: props.showScrollbar !== false,
|
showScrollbar: (props as ScrollProps).showScrollbar !== false,
|
||||||
viewportWidth: (props.width as number) ?? 10,
|
viewportWidth: props.width ?? 10,
|
||||||
viewportHeight: (props.height as number) ?? 10,
|
viewportHeight: props.height ?? 10,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +221,7 @@ export class UIObject {
|
|||||||
* Parse CSS-like class string into layout and style properties
|
* Parse CSS-like class string into layout and style properties
|
||||||
*/
|
*/
|
||||||
private parseClassNames(): void {
|
private parseClassNames(): void {
|
||||||
const className = this.props.class as string | undefined;
|
const className = this.props.class;
|
||||||
if (className === undefined) return;
|
if (className === undefined) return;
|
||||||
|
|
||||||
const classes = className.split(" ").filter((c) => c.length > 0);
|
const classes = className.split(" ").filter((c) => c.length > 0);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { calculateLayout } from "./layout";
|
|||||||
import { render as renderTree, clearScreen } from "./renderer";
|
import { render as renderTree, clearScreen } from "./renderer";
|
||||||
import { CCLog, HOUR } from "../ccLog";
|
import { CCLog, HOUR } from "../ccLog";
|
||||||
import { setLogger } from "./context";
|
import { setLogger } from "./context";
|
||||||
|
import { InputProps } from "./components";
|
||||||
|
import { Setter } from "./reactivity";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application class
|
* Main application class
|
||||||
@@ -145,7 +147,7 @@ export class Application {
|
|||||||
if (
|
if (
|
||||||
this.focusedNode !== undefined &&
|
this.focusedNode !== undefined &&
|
||||||
this.focusedNode.type === "input" &&
|
this.focusedNode.type === "input" &&
|
||||||
this.focusedNode.props.type !== "checkbox"
|
(this.focusedNode.props as InputProps).type !== "checkbox"
|
||||||
) {
|
) {
|
||||||
this.needsRender = true;
|
this.needsRender = true;
|
||||||
}
|
}
|
||||||
@@ -213,11 +215,13 @@ export class Application {
|
|||||||
this.needsRender = true;
|
this.needsRender = true;
|
||||||
}
|
}
|
||||||
} else if (this.focusedNode.type === "input") {
|
} else if (this.focusedNode.type === "input") {
|
||||||
const type = this.focusedNode.props.type as string | undefined;
|
const type = (this.focusedNode.props as InputProps).type as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
if (type === "checkbox") {
|
if (type === "checkbox") {
|
||||||
// Toggle checkbox
|
// Toggle checkbox
|
||||||
const onChangeProp = this.focusedNode.props.onChange;
|
const onChangeProp = (this.focusedNode.props as InputProps).onChange;
|
||||||
const checkedProp = this.focusedNode.props.checked;
|
const checkedProp = (this.focusedNode.props as InputProps).checked;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof onChangeProp === "function" &&
|
typeof onChangeProp === "function" &&
|
||||||
@@ -234,7 +238,9 @@ export class Application {
|
|||||||
this.focusedNode.type === "input"
|
this.focusedNode.type === "input"
|
||||||
) {
|
) {
|
||||||
// Handle text input key events
|
// Handle text input key events
|
||||||
const type = this.focusedNode.props.type as string | undefined;
|
const type = (this.focusedNode.props as InputProps).type as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
if (type !== "checkbox") {
|
if (type !== "checkbox") {
|
||||||
this.handleTextInputKey(key);
|
this.handleTextInputKey(key);
|
||||||
}
|
}
|
||||||
@@ -247,8 +253,8 @@ export class Application {
|
|||||||
private handleTextInputKey(key: number): void {
|
private handleTextInputKey(key: number): void {
|
||||||
if (this.focusedNode === undefined) return;
|
if (this.focusedNode === undefined) return;
|
||||||
|
|
||||||
const valueProp = this.focusedNode.props.value;
|
const valueProp = (this.focusedNode.props as InputProps).value;
|
||||||
const onInputProp = this.focusedNode.props.onInput;
|
const onInputProp = (this.focusedNode.props as InputProps).onInput;
|
||||||
|
|
||||||
if (typeof valueProp !== "function" || typeof onInputProp !== "function") {
|
if (typeof valueProp !== "function" || typeof onInputProp !== "function") {
|
||||||
return;
|
return;
|
||||||
@@ -292,11 +298,11 @@ export class Application {
|
|||||||
*/
|
*/
|
||||||
private handleCharEvent(char: string): void {
|
private handleCharEvent(char: string): void {
|
||||||
if (this.focusedNode !== undefined && this.focusedNode.type === "input") {
|
if (this.focusedNode !== undefined && this.focusedNode.type === "input") {
|
||||||
const type = this.focusedNode.props.type as string | undefined;
|
const type = (this.focusedNode.props as InputProps).type;
|
||||||
if (type !== "checkbox") {
|
if (type !== "checkbox") {
|
||||||
// Insert character at cursor position
|
// Insert character at cursor position
|
||||||
const onInputProp = this.focusedNode.props.onInput;
|
const onInputProp = (this.focusedNode.props as InputProps).onInput;
|
||||||
const valueProp = this.focusedNode.props.value;
|
const valueProp = (this.focusedNode.props as InputProps).value;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof onInputProp === "function" &&
|
typeof onInputProp === "function" &&
|
||||||
@@ -331,11 +337,26 @@ export class Application {
|
|||||||
string.format("handleMouseClick: Found node of type %s.", clicked.type),
|
string.format("handleMouseClick: Found node of type %s.", clicked.type),
|
||||||
);
|
);
|
||||||
// Set focus
|
// Set focus
|
||||||
|
if (
|
||||||
|
this.focusedNode !== undefined &&
|
||||||
|
typeof this.focusedNode.props.onFocusChanged === "function"
|
||||||
|
) {
|
||||||
|
const onFocusChanged = this.focusedNode.props
|
||||||
|
.onFocusChanged as Setter<boolean>;
|
||||||
|
onFocusChanged(false);
|
||||||
|
}
|
||||||
this.focusedNode = clicked;
|
this.focusedNode = clicked;
|
||||||
|
if (typeof clicked.props.onFocusChanged === "function") {
|
||||||
|
const onFocusChanged = clicked.props.onFocusChanged as Setter<boolean>;
|
||||||
|
onFocusChanged(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize cursor position for text inputs on focus
|
// Initialize cursor position for text inputs on focus
|
||||||
if (clicked.type === "input" && clicked.props.type !== "checkbox") {
|
if (
|
||||||
const valueProp = clicked.props.value;
|
clicked.type === "input" &&
|
||||||
|
(clicked.props as InputProps).type !== "checkbox"
|
||||||
|
) {
|
||||||
|
const valueProp = (clicked.props as InputProps).value;
|
||||||
if (typeof valueProp === "function") {
|
if (typeof valueProp === "function") {
|
||||||
const currentValue = (valueProp as () => string)();
|
const currentValue = (valueProp as () => string)();
|
||||||
clicked.cursorPos = currentValue.length;
|
clicked.cursorPos = currentValue.length;
|
||||||
@@ -354,10 +375,10 @@ export class Application {
|
|||||||
this.needsRender = true;
|
this.needsRender = true;
|
||||||
}
|
}
|
||||||
} else if (clicked.type === "input") {
|
} else if (clicked.type === "input") {
|
||||||
const type = clicked.props.type as string | undefined;
|
const type = (clicked.props as InputProps).type as string | undefined;
|
||||||
if (type === "checkbox") {
|
if (type === "checkbox") {
|
||||||
const onChangeProp = clicked.props.onChange;
|
const onChangeProp = (clicked.props as InputProps).onChange;
|
||||||
const checkedProp = clicked.props.checked;
|
const checkedProp = (clicked.props as InputProps).checked;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof onChangeProp === "function" &&
|
typeof onChangeProp === "function" &&
|
||||||
@@ -424,6 +445,14 @@ export class Application {
|
|||||||
|
|
||||||
const interactive = this.collectInteractive(this.root);
|
const interactive = this.collectInteractive(this.root);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.focusedNode !== undefined &&
|
||||||
|
typeof this.focusedNode.props.onFocusChanged === "function"
|
||||||
|
) {
|
||||||
|
const onFocusChanged = this.focusedNode.props
|
||||||
|
.onFocusChanged as Setter<boolean>;
|
||||||
|
onFocusChanged(false);
|
||||||
|
}
|
||||||
if (interactive.length === 0) {
|
if (interactive.length === 0) {
|
||||||
this.focusedNode = undefined;
|
this.focusedNode = undefined;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { concatSentence } from "../common";
|
|||||||
/**
|
/**
|
||||||
* Props for div component
|
* Props for div component
|
||||||
*/
|
*/
|
||||||
export type DivProps = BaseProps & Record<string, unknown>;
|
export type DivProps = BaseProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for label component
|
* Props for label component
|
||||||
@@ -20,7 +20,7 @@ export type DivProps = BaseProps & Record<string, unknown>;
|
|||||||
export type LabelProps = BaseProps & {
|
export type LabelProps = BaseProps & {
|
||||||
/** Whether to automatically wrap long text. Defaults to false. */
|
/** Whether to automatically wrap long text. Defaults to false. */
|
||||||
wordWrap?: boolean;
|
wordWrap?: boolean;
|
||||||
} & Record<string, unknown>;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for button component
|
* Props for button component
|
||||||
@@ -28,7 +28,7 @@ export type LabelProps = BaseProps & {
|
|||||||
export type ButtonProps = BaseProps & {
|
export type ButtonProps = BaseProps & {
|
||||||
/** Click handler */
|
/** Click handler */
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
} & Record<string, unknown>;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for input component
|
* Props for input component
|
||||||
@@ -46,7 +46,7 @@ export type InputProps = BaseProps & {
|
|||||||
onChange?: Setter<boolean> | ((checked: boolean) => void);
|
onChange?: Setter<boolean> | ((checked: boolean) => void);
|
||||||
/** Placeholder text */
|
/** Placeholder text */
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
} & Record<string, unknown>;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for form component
|
* Props for form component
|
||||||
@@ -54,7 +54,7 @@ export type InputProps = BaseProps & {
|
|||||||
export type FormProps = BaseProps & {
|
export type FormProps = BaseProps & {
|
||||||
/** Submit handler */
|
/** Submit handler */
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
} & Record<string, unknown>;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic container component for layout
|
* Generic container component for layout
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Calculates positions and sizes for UI elements based on flexbox rules
|
* Calculates positions and sizes for UI elements based on flexbox rules
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { InputProps } from "./components";
|
||||||
import { UIObject } from "./UIObject";
|
import { UIObject } from "./UIObject";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,7 +110,7 @@ function measureNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "input": {
|
case "input": {
|
||||||
const type = node.props.type as string | undefined;
|
const type = (node.props as InputProps).type as string | undefined;
|
||||||
if (type === "checkbox") {
|
if (type === "checkbox") {
|
||||||
const naturalWidth = 3; // [X] or [ ]
|
const naturalWidth = 3; // [X] or [ ]
|
||||||
const naturalHeight = 1;
|
const naturalHeight = 1;
|
||||||
@@ -119,7 +120,7 @@ function measureNode(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Text input - use a default width or from props
|
// Text input - use a default width or from props
|
||||||
const defaultWidth = (node.props.width as number | undefined) ?? 20;
|
const defaultWidth = node.props.width ?? 20;
|
||||||
const naturalHeight = 1;
|
const naturalHeight = 1;
|
||||||
return {
|
return {
|
||||||
width: measuredWidth ?? defaultWidth,
|
width: measuredWidth ?? defaultWidth,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { UIObject } from "./UIObject";
|
import { UIObject } from "./UIObject";
|
||||||
import { Accessor } from "./reactivity";
|
import { InputProps } from "./components";
|
||||||
import { isScrollContainer } from "./scrollContainer";
|
import { isScrollContainer } from "./scrollContainer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,14 +189,14 @@ function drawNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "input": {
|
case "input": {
|
||||||
const type = node.props.type as string | undefined;
|
const type = (node.props as InputProps).type as string | undefined;
|
||||||
|
|
||||||
if (type === "checkbox") {
|
if (type === "checkbox") {
|
||||||
// Draw checkbox
|
// Draw checkbox
|
||||||
let isChecked = false;
|
let isChecked = false;
|
||||||
const checkedProp = node.props.checked;
|
const checkedProp = (node.props as InputProps).checked;
|
||||||
if (typeof checkedProp === "function") {
|
if (typeof checkedProp === "function") {
|
||||||
isChecked = (checkedProp as Accessor<boolean>)();
|
isChecked = checkedProp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focused) {
|
if (focused) {
|
||||||
@@ -212,12 +212,11 @@ function drawNode(
|
|||||||
} else {
|
} else {
|
||||||
// Draw text input
|
// Draw text input
|
||||||
let displayText = "";
|
let displayText = "";
|
||||||
const valueProp = node.props.value;
|
const valueProp = (node.props as InputProps).value;
|
||||||
if (typeof valueProp === "function") {
|
if (typeof valueProp === "function") {
|
||||||
displayText = (valueProp as Accessor<string>)();
|
displayText = valueProp();
|
||||||
}
|
}
|
||||||
|
const placeholder = (node.props as InputProps).placeholder;
|
||||||
const placeholder = node.props.placeholder as string | undefined;
|
|
||||||
const cursorPos = node.cursorPos ?? 0;
|
const cursorPos = node.cursorPos ?? 0;
|
||||||
let currentTextColor = textColor;
|
let currentTextColor = textColor;
|
||||||
let showPlaceholder = false;
|
let showPlaceholder = false;
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ const Counter = () => {
|
|||||||
div(
|
div(
|
||||||
{ class: "flex flex-row" },
|
{ class: "flex flex-row" },
|
||||||
button({ onClick: () => setCount(count() - 1), class: "text-red" }, "-"),
|
button({ onClick: () => setCount(count() - 1), class: "text-red" }, "-"),
|
||||||
button({ onClick: () => setCount(count() + 1), class: "text-green" }, "+"),
|
button(
|
||||||
|
{ onClick: () => setCount(count() + 1), class: "text-green" },
|
||||||
|
"+",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -82,9 +85,9 @@ const TodosApp = () => {
|
|||||||
}),
|
}),
|
||||||
label(
|
label(
|
||||||
{
|
{
|
||||||
class: todo.completed ? "ml-1 text-gray" : "ml-1 text-white"
|
class: todo.completed ? "ml-1 text-gray" : "ml-1 text-white",
|
||||||
},
|
},
|
||||||
() => todo.title
|
() => todo.title,
|
||||||
),
|
),
|
||||||
button(
|
button(
|
||||||
{
|
{
|
||||||
@@ -318,13 +321,13 @@ const App = () => {
|
|||||||
when: () => tabIndex() === 3,
|
when: () => tabIndex() === 3,
|
||||||
fallback: MultiScrollExample(),
|
fallback: MultiScrollExample(),
|
||||||
},
|
},
|
||||||
StaticScrollExample()
|
StaticScrollExample(),
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
SimpleScrollExample()
|
SimpleScrollExample(),
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
TodosApp()
|
TodosApp(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Counter(),
|
Counter(),
|
||||||
|
|||||||
Reference in New Issue
Block a user