tinky-text-input 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ [English](./README.md) | [简体中文](./README.zh-CN.md)
2
+
3
+ # tinky-text-input
4
+
5
+ [Tinky](https://github.com/ByteLandTechnology/tinky) 用のテキスト入力コンポーネント。
6
+
7
+ ## インストール
8
+
9
+ ```bash
10
+ npm install tinky-text-input
11
+ ```
12
+
13
+ ## 使い方
14
+
15
+ ```tsx
16
+ import React, { useState } from "react";
17
+ import { render, Text } from "tinky";
18
+ import { TextInput } from "tinky-text-input";
19
+
20
+ function Search() {
21
+ const [query, setQuery] = useState("");
22
+
23
+ return (
24
+ <Box>
25
+ <Box marginRight={1}>
26
+ <Text>検索クエリを入力:</Text>
27
+ </Box>
28
+
29
+ <TextInput value={query} onChange={setQuery} />
30
+ </Box>
31
+ );
32
+ }
33
+
34
+ render(<Search />);
35
+ ```
36
+
37
+ ## プロップス (Props)
38
+
39
+ ### placeholder
40
+
41
+ 型: `string`
42
+
43
+ `value` が空のときに表示するテキスト。
44
+
45
+ ### focus
46
+
47
+ 型: `boolean`\
48
+ デフォルト: `true`
49
+
50
+ ユーザーの入力をリッスンします。複数の入力コンポーネントが同時に存在し、入力を特定のコンポーネントに「ルーティング」する必要がある場合に便利です。
51
+
52
+ ### mask
53
+
54
+ 型: `string`
55
+
56
+ すべての文字を置換して値をマスクします。パスワード入力に便利です。
57
+
58
+ ### showCursor
59
+
60
+ 型: `boolean`\
61
+ デフォルト: `true`
62
+
63
+ カーソルを表示し、矢印キーでテキスト入力内を移動できるようにするかどうか。
64
+
65
+ ### highlightPastedText
66
+
67
+ 型: `boolean`\
68
+ デフォルト: `false`
69
+
70
+ 貼り付けられたテキストをハイライトします。
71
+
72
+ ### onSubmit
73
+
74
+ 型: `(value: string) => void`
75
+
76
+ `Enter` キーが押されたときに呼び出される関数。最初の引数は入力値です。
77
+
78
+ ### initialValue
79
+
80
+ 型: `string`\
81
+ デフォルト: `""`
82
+
83
+ 初期値(非制御コンポーネントのみ)。
84
+
85
+ ### value
86
+
87
+ 型: `string`
88
+
89
+ テキスト入力に表示する値(制御コンポーネントのみ)。
90
+
91
+ ### onChange
92
+
93
+ 型: `(value: string) => void`
94
+
95
+ 値が更新されたときに呼び出される関数(制御コンポーネントのみ)。
96
+
97
+ ## ライセンス
98
+
99
+ MIT
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ [简体中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
2
+
3
+ # tinky-text-input
4
+
5
+ A text input component for [Tinky](https://github.com/ByteLandTechnology/tinky).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install tinky-text-input
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import React, { useState } from "react";
17
+ import { render, Text } from "tinky";
18
+ import { TextInput } from "tinky-text-input";
19
+
20
+ function Search() {
21
+ const [query, setQuery] = useState("");
22
+
23
+ return (
24
+ <Box>
25
+ <Box marginRight={1}>
26
+ <Text>Enter your query:</Text>
27
+ </Box>
28
+
29
+ <TextInput value={query} onChange={setQuery} />
30
+ </Box>
31
+ );
32
+ }
33
+
34
+ render(<Search />);
35
+ ```
36
+
37
+ ## Props
38
+
39
+ ### placeholder
40
+
41
+ Type: `string`
42
+
43
+ Text to display when `value` is empty.
44
+
45
+ ### focus
46
+
47
+ Type: `boolean`\
48
+ Default: `true`
49
+
50
+ Listen to user's input. Useful in case there are multiple input components at the same time and input must be "routed" to a specific component.
51
+
52
+ ### mask
53
+
54
+ Type: `string`
55
+
56
+ Replace all chars and mask the value. Useful for password inputs.
57
+
58
+ ### showCursor
59
+
60
+ Type: `boolean`\
61
+ Default: `true`
62
+
63
+ Whether to show cursor and allow navigation inside text input with arrow keys.
64
+
65
+ ### highlightPastedText
66
+
67
+ Type: `boolean`\
68
+ Default: `false`
69
+
70
+ Highlight pasted text.
71
+
72
+ ### onSubmit
73
+
74
+ Type: `(value: string) => void`
75
+
76
+ Function to call when `Enter` is pressed, where first argument is a value of the input.
77
+
78
+ ### initialValue
79
+
80
+ Type: `string`\
81
+ Default: `""`
82
+
83
+ Initial value (only for uncontrolled component).
84
+
85
+ ### value
86
+
87
+ Type: `string`
88
+
89
+ Value to display in a text input (for controlled component).
90
+
91
+ ### onChange
92
+
93
+ Type: `(value: string) => void`
94
+
95
+ Function to call when value updates (for controlled component).
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,99 @@
1
+ [English](./README.md) | [日本語](./README.ja-JP.md)
2
+
3
+ # tinky-text-input
4
+
5
+ [Tinky](https://github.com/ByteLandTechnology/tinky) 的文本输入组件。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install tinky-text-input
11
+ ```
12
+
13
+ ## 使用方法
14
+
15
+ ```tsx
16
+ import React, { useState } from "react";
17
+ import { render, Text } from "tinky";
18
+ import { TextInput } from "tinky-text-input";
19
+
20
+ function Search() {
21
+ const [query, setQuery] = useState("");
22
+
23
+ return (
24
+ <Box>
25
+ <Box marginRight={1}>
26
+ <Text>请输入查询内容:</Text>
27
+ </Box>
28
+
29
+ <TextInput value={query} onChange={setQuery} />
30
+ </Box>
31
+ );
32
+ }
33
+
34
+ render(<Search />);
35
+ ```
36
+
37
+ ## 属性 (Props)
38
+
39
+ ### placeholder
40
+
41
+ 类型: `string`
42
+
43
+ 当 `value` 为空时显示的文本。
44
+
45
+ ### focus
46
+
47
+ 类型: `boolean`\
48
+ 默认值: `true`
49
+
50
+ 监听用户输入。如果有多个输入组件同时存在,并且需要将输入“路由”到特定组件,此属性非常有用。
51
+
52
+ ### mask
53
+
54
+ 类型: `string`
55
+
56
+ 替换所有字符并屏蔽值。适用于密码输入。
57
+
58
+ ### showCursor
59
+
60
+ 类型: `boolean`\
61
+ 默认值: `true`
62
+
63
+ 是否显示光标并允许使用方向键在文本输入框内导航。
64
+
65
+ ### highlightPastedText
66
+
67
+ 类型: `boolean`\
68
+ 默认值: `false`
69
+
70
+ 高亮显示粘贴的文本。
71
+
72
+ ### onSubmit
73
+
74
+ 类型: `(value: string) => void`
75
+
76
+ 当按下 `Enter` 键时调用的函数,第一个参数是输入的值。
77
+
78
+ ### initialValue
79
+
80
+ 类型: `string`\
81
+ 默认值: `""`
82
+
83
+ 初始值(仅适用于非受控组件)。
84
+
85
+ ### value
86
+
87
+ 类型: `string`
88
+
89
+ 文本输入框中显示的值(适用于受控组件)。
90
+
91
+ ### onChange
92
+
93
+ 类型: `(value: string) => void`
94
+
95
+ 当值更新时调用的函数(适用于受控组件)。
96
+
97
+ ## 许可证
98
+
99
+ MIT
@@ -0,0 +1,21 @@
1
+ import type { TextInputProps } from "./TextInput.js";
2
+ /**
3
+ * Props for the {@link ControlledTextInput} component.
4
+ */
5
+ export interface ControlledTextInputProps extends TextInputProps {
6
+ /**
7
+ * Value to display in a text input.
8
+ */
9
+ readonly value: string;
10
+ /**
11
+ * Function to call when value updates.
12
+ */
13
+ readonly onChange: (value: string) => void;
14
+ }
15
+ /**
16
+ * A controlled text input component that renders a text input field.
17
+ *
18
+ * @param props - The properties for the component.
19
+ * @returns The rendered text input component.
20
+ */
21
+ export declare function ControlledTextInput({ value: originalValue, placeholder, focus, mask, highlightPastedText, showCursor, onChange, onSubmit, }: ControlledTextInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Text } from "tinky";
4
+ import { useKeypress } from "tinky-keypress";
5
+ import ansis from "ansis";
6
+ /**
7
+ * A controlled text input component that renders a text input field.
8
+ *
9
+ * @param props - The properties for the component.
10
+ * @returns The rendered text input component.
11
+ */
12
+ export function ControlledTextInput({ value: originalValue, placeholder = "", focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit, }) {
13
+ const [state, setState] = useState({
14
+ cursorOffset: (originalValue || "").length,
15
+ cursorWidth: 0,
16
+ });
17
+ const { cursorOffset, cursorWidth } = state;
18
+ useEffect(() => {
19
+ setState((previousState) => {
20
+ if (!focus || !showCursor) {
21
+ return previousState;
22
+ }
23
+ const newValue = originalValue || "";
24
+ if (previousState.cursorOffset > newValue.length - 1) {
25
+ return {
26
+ cursorOffset: newValue.length,
27
+ cursorWidth: 0,
28
+ };
29
+ }
30
+ return previousState;
31
+ });
32
+ }, [originalValue, focus, showCursor]);
33
+ const cursorActualWidth = highlightPastedText ? cursorWidth : 0;
34
+ const value = mask ? mask.repeat(originalValue.length) : originalValue;
35
+ let renderedValue = value;
36
+ let renderedPlaceholder = placeholder ? ansis.gray(placeholder) : undefined;
37
+ // Fake mouse cursor, because it's too inconvenient to deal with actual cursor and ansi escapes
38
+ if (showCursor && focus) {
39
+ renderedPlaceholder =
40
+ placeholder.length > 0
41
+ ? ansis.inverse(placeholder[0]) + ansis.gray(placeholder.slice(1))
42
+ : ansis.inverse(" ");
43
+ renderedValue = value.length > 0 ? "" : ansis.inverse(" ");
44
+ let i = 0;
45
+ for (const char of value) {
46
+ renderedValue +=
47
+ i >= cursorOffset - cursorActualWidth && i <= cursorOffset
48
+ ? ansis.inverse(char)
49
+ : char;
50
+ i++;
51
+ }
52
+ if (value.length > 0 && cursorOffset === value.length) {
53
+ renderedValue += ansis.inverse(" ");
54
+ }
55
+ }
56
+ useKeypress((key) => {
57
+ if (key.name === "up" ||
58
+ key.name === "down" ||
59
+ (key.ctrl && key.name === "c") ||
60
+ key.name === "tab" ||
61
+ (key.shift && key.name === "tab")) {
62
+ return;
63
+ }
64
+ if (key.name === "return" || key.name === "enter") {
65
+ if (onSubmit) {
66
+ onSubmit(originalValue);
67
+ }
68
+ return;
69
+ }
70
+ let nextCursorOffset = cursorOffset;
71
+ let nextValue = originalValue;
72
+ let nextCursorWidth = 0;
73
+ const input = key.sequence;
74
+ if (key.name === "left") {
75
+ if (showCursor) {
76
+ nextCursorOffset--;
77
+ }
78
+ }
79
+ else if (key.name === "right") {
80
+ if (showCursor) {
81
+ nextCursorOffset++;
82
+ }
83
+ }
84
+ else if (key.name === "backspace" || key.name === "delete") {
85
+ if (cursorOffset > 0) {
86
+ nextValue =
87
+ originalValue.slice(0, cursorOffset - 1) +
88
+ originalValue.slice(cursorOffset, originalValue.length);
89
+ nextCursorOffset--;
90
+ }
91
+ }
92
+ else {
93
+ if (key.insertable && !key.ctrl && !key.meta && key.name !== "escape") {
94
+ nextValue =
95
+ originalValue.slice(0, cursorOffset) +
96
+ input +
97
+ originalValue.slice(cursorOffset, originalValue.length);
98
+ nextCursorOffset += input.length;
99
+ if (input.length > 1) {
100
+ nextCursorWidth = input.length;
101
+ }
102
+ }
103
+ }
104
+ if (cursorOffset < 0) {
105
+ nextCursorOffset = 0;
106
+ }
107
+ if (cursorOffset > originalValue.length) {
108
+ nextCursorOffset = originalValue.length;
109
+ }
110
+ setState({
111
+ cursorOffset: nextCursorOffset,
112
+ cursorWidth: nextCursorWidth,
113
+ });
114
+ if (nextValue !== originalValue) {
115
+ onChange(nextValue);
116
+ }
117
+ }, { isActive: focus });
118
+ return (_jsx(Text, { children: placeholder
119
+ ? value.length > 0
120
+ ? renderedValue
121
+ : renderedPlaceholder
122
+ : renderedValue }));
123
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Props for the {@link TextInput} component.
3
+ */
4
+ export interface TextInputProps {
5
+ /**
6
+ * Text to display when `value` is empty.
7
+ */
8
+ readonly placeholder?: string;
9
+ /**
10
+ * Listen to user's input. Useful in case there are multiple input components
11
+ * at the same time and input must be "routed" to a specific component.
12
+ *
13
+ * @default true
14
+ */
15
+ readonly focus?: boolean;
16
+ /**
17
+ * Replace all chars and mask the value. Useful for password inputs.
18
+ */
19
+ readonly mask?: string;
20
+ /**
21
+ * Whether to show cursor and allow navigation inside text input with arrow keys.
22
+ *
23
+ * @default true
24
+ */
25
+ readonly showCursor?: boolean;
26
+ /**
27
+ * Highlight pasted text.
28
+ *
29
+ * @default false
30
+ */
31
+ readonly highlightPastedText?: boolean;
32
+ /**
33
+ * Function to call when `Enter` is pressed, where first argument is a value of the input.
34
+ */
35
+ readonly onSubmit?: (value: string) => void;
36
+ /**
37
+ * Initial value.
38
+ *
39
+ * @default ""
40
+ */
41
+ readonly initialValue?: string;
42
+ }
43
+ /**
44
+ * An uncontrolled text input component that manages its own state.
45
+ *
46
+ * @param props - The properties for the component.
47
+ * @returns The rendered text input component.
48
+ */
49
+ export declare function TextInput({ initialValue, ...props }: TextInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ControlledTextInput } from "./ControlledTextInput.js";
4
+ /**
5
+ * An uncontrolled text input component that manages its own state.
6
+ *
7
+ * @param props - The properties for the component.
8
+ * @returns The rendered text input component.
9
+ */
10
+ export function TextInput({ initialValue = "", ...props }) {
11
+ const [value, setValue] = useState(initialValue);
12
+ return _jsx(ControlledTextInput, { ...props, value: value, onChange: setValue });
13
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { TextInput, type TextInputProps } from "./components/TextInput.js";
2
+ export { ControlledTextInput, type ControlledTextInputProps, } from "./components/ControlledTextInput.js";
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { TextInput } from "./components/TextInput.js";
2
+ export { ControlledTextInput, } from "./components/ControlledTextInput.js";
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "tinky-text-input",
3
+ "version": "0.1.0",
4
+ "description": "A text input component for Tinky",
5
+ "keywords": [
6
+ "tinky",
7
+ "text-input",
8
+ "component",
9
+ "cli",
10
+ "react"
11
+ ],
12
+ "homepage": "https://github.com/ByteLandTechnology/tinky-text-input#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ByteLandTechnology/tinky-text-input/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ByteLandTechnology/tinky-text-input.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": {
22
+ "name": "ByteLand Technology Limited"
23
+ },
24
+ "type": "module",
25
+ "main": "./lib/index.js",
26
+ "scripts": {
27
+ "test": "tsc",
28
+ "build": "tsc && npm run docs",
29
+ "lint": "eslint src tests",
30
+ "prepublish": "npm run build",
31
+ "prepare": "husky",
32
+ "docs": "typedoc --plugin typedoc-plugin-markdown --disableSources --out docs/api && prettier --write docs/api"
33
+ },
34
+ "files": [
35
+ "./bin/*",
36
+ "./lib/*"
37
+ ],
38
+ "typings": "./lib/index.d.ts",
39
+ "peerDependencies": {
40
+ "tinky-keypress": ">=1.2.0"
41
+ },
42
+ "devDependencies": {
43
+ "@commitlint/cli": "^20.3.0",
44
+ "@commitlint/config-conventional": "^20.3.0",
45
+ "@semantic-release/changelog": "^6.0.3",
46
+ "@semantic-release/commit-analyzer": "^13.0.1",
47
+ "@semantic-release/git": "^10.0.1",
48
+ "@semantic-release/github": "^12.0.2",
49
+ "@semantic-release/npm": "^13.1.3",
50
+ "@semantic-release/release-notes-generator": "^14.1.0",
51
+ "conventional-changelog-conventionalcommits": "^9.1.0",
52
+ "eslint": "^9.39.2",
53
+ "eslint-plugin-react": "^7.37.5",
54
+ "husky": "^9.1.7",
55
+ "jiti": "^2.6.1",
56
+ "lint-staged": "^16.2.7",
57
+ "prettier": "^3.7.4",
58
+ "semantic-release": "^25.0.2",
59
+ "typedoc": "^0.28.16",
60
+ "typedoc-plugin-markdown": "^4.9.0",
61
+ "typescript-eslint": "^8.53.0"
62
+ },
63
+ "commitlint": {
64
+ "extends": [
65
+ "@commitlint/config-conventional"
66
+ ]
67
+ },
68
+ "lint-staged": {
69
+ "*.{js,ts,jsx,tsx,json,md,yaml,yml}": "prettier --write"
70
+ }
71
+ }