@seed-design/lynx-react 0.3.1 โ 0.4.1
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.
- package/lib/components/Field/Field.d.ts +47 -0
- package/lib/components/Field/Field.d.ts.map +1 -0
- package/lib/components/Field/Field.jsx +121 -0
- package/lib/components/Field/Field.namespace.d.ts +2 -0
- package/lib/components/Field/Field.namespace.d.ts.map +1 -0
- package/lib/components/Field/Field.namespace.js +16 -0
- package/lib/components/Field/context.d.ts +19 -0
- package/lib/components/Field/context.d.ts.map +1 -0
- package/lib/components/Field/context.js +10 -0
- package/lib/components/Field/index.d.ts +3 -0
- package/lib/components/Field/index.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts +42 -0
- package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.jsx +140 -0
- package/lib/components/KeyboardAvoidingScrollView/context.d.ts +17 -0
- package/lib/components/KeyboardAvoidingScrollView/context.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/context.js +12 -0
- package/lib/components/KeyboardAvoidingScrollView/engine.d.ts +50 -0
- package/lib/components/KeyboardAvoidingScrollView/engine.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/engine.js +259 -0
- package/lib/components/KeyboardAvoidingScrollView/geometry.d.ts +50 -0
- package/lib/components/KeyboardAvoidingScrollView/geometry.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/geometry.js +90 -0
- package/lib/components/KeyboardAvoidingScrollView/index.d.ts +2 -0
- package/lib/components/KeyboardAvoidingScrollView/index.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.d.ts +11 -0
- package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.js +43 -0
- package/lib/components/KeyboardAvoidingScrollView/native-driver.d.ts +34 -0
- package/lib/components/KeyboardAvoidingScrollView/native-driver.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/native-driver.js +133 -0
- package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
- package/lib/components/ProgressCircle/ProgressCircle.jsx +36 -5
- package/lib/components/Stack/Stack.d.ts.map +1 -1
- package/lib/components/Stack/Stack.jsx +10 -1
- package/lib/components/TagGroup/TagGroup.d.ts +2 -1
- package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
- package/lib/components/TagGroup/TagGroup.jsx +2 -1
- package/lib/components/TextField/TextField.d.ts +131 -0
- package/lib/components/TextField/TextField.d.ts.map +1 -0
- package/lib/components/TextField/TextField.jsx +434 -0
- package/lib/components/TextField/TextField.namespace.d.ts +2 -0
- package/lib/components/TextField/TextField.namespace.d.ts.map +1 -0
- package/lib/components/TextField/TextField.namespace.js +14 -0
- package/lib/components/TextField/context.d.ts +22 -0
- package/lib/components/TextField/context.d.ts.map +1 -0
- package/lib/components/TextField/context.js +7 -0
- package/lib/components/TextField/index.d.ts +4 -0
- package/lib/components/TextField/index.d.ts.map +1 -0
- package/lib/components/TextField/useTextFieldWithGraphemes.d.ts +24 -0
- package/lib/components/TextField/useTextFieldWithGraphemes.d.ts.map +1 -0
- package/lib/components/TextField/useTextFieldWithGraphemes.js +47 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/index.js +7 -1
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/_grapheme_data.js +8 -0
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/_incb_data.js +14 -0
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/core.js +68 -0
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/grapheme.js +175 -0
- package/package.json +7 -5
- package/src/components/Field/Field.namespace.ts +19 -0
- package/src/components/Field/Field.test.tsx +81 -0
- package/src/components/Field/Field.tsx +252 -0
- package/src/components/Field/context.ts +26 -0
- package/src/components/Field/index.ts +21 -0
- package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.test.tsx +267 -0
- package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.tsx +299 -0
- package/src/components/KeyboardAvoidingScrollView/context.ts +24 -0
- package/src/components/KeyboardAvoidingScrollView/engine.test.ts +451 -0
- package/src/components/KeyboardAvoidingScrollView/engine.ts +417 -0
- package/src/components/KeyboardAvoidingScrollView/geometry.test.ts +315 -0
- package/src/components/KeyboardAvoidingScrollView/geometry.ts +194 -0
- package/src/components/KeyboardAvoidingScrollView/index.ts +5 -0
- package/src/components/KeyboardAvoidingScrollView/keyboard-event-source.test.ts +122 -0
- package/src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts +75 -0
- package/src/components/KeyboardAvoidingScrollView/native-driver.test.ts +223 -0
- package/src/components/KeyboardAvoidingScrollView/native-driver.ts +248 -0
- package/src/components/ProgressCircle/ProgressCircle.test.tsx +58 -0
- package/src/components/ProgressCircle/ProgressCircle.tsx +42 -5
- package/src/components/Stack/Stack.test.tsx +67 -0
- package/src/components/Stack/Stack.tsx +15 -1
- package/src/components/TagGroup/TagGroup.test.tsx +73 -0
- package/src/components/TagGroup/TagGroup.tsx +4 -2
- package/src/components/TextField/TextField.namespace.ts +16 -0
- package/src/components/TextField/TextField.test.tsx +1052 -0
- package/src/components/TextField/TextField.tsx +954 -0
- package/src/components/TextField/context.ts +33 -0
- package/src/components/TextField/index.ts +23 -0
- package/src/components/TextField/useTextFieldWithGraphemes.test.tsx +80 -0
- package/src/components/TextField/useTextFieldWithGraphemes.ts +78 -0
- package/src/components/index.ts +3 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "@lynx-js/react";
|
|
2
|
+
import type { NodesRef } from "@lynx-js/types";
|
|
3
|
+
|
|
4
|
+
/** iOS์ Android์ 32๋นํธ `maxlength` setter์์ ์ฌ์ฉํ๋ ๋ฌด์ ํ ์ผํฐ๋. */
|
|
5
|
+
export const NATIVE_TEXT_MAX_LENGTH_UNLIMITED = 2_147_483_647;
|
|
6
|
+
|
|
7
|
+
export interface TextFieldContextValue {
|
|
8
|
+
rootRef: React.RefObject<NodesRef | null>;
|
|
9
|
+
value: string;
|
|
10
|
+
/** @internal controlled ์
๋ ฅ์ native value ์กฐ์ ์ฌ๋ถ๋ฅผ ํ๋ณํ๋ค. */
|
|
11
|
+
controlled: boolean;
|
|
12
|
+
nativeInsertionMaxLength?: number;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
invalid: boolean;
|
|
15
|
+
readOnly: boolean;
|
|
16
|
+
required: boolean;
|
|
17
|
+
name?: string;
|
|
18
|
+
focused: boolean;
|
|
19
|
+
setValue: (value: string) => void;
|
|
20
|
+
setFocused: (focused: boolean) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TextFieldContext = React.createContext<TextFieldContextValue | null>(null);
|
|
24
|
+
|
|
25
|
+
export function useTextFieldContext(): TextFieldContextValue {
|
|
26
|
+
const context = React.useContext(TextFieldContext);
|
|
27
|
+
|
|
28
|
+
if (!context) {
|
|
29
|
+
throw new Error("TextField compound components must be rendered inside <TextField.Root>.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
TextFieldPrefixIcon,
|
|
3
|
+
TextFieldPrefixText,
|
|
4
|
+
TextFieldRoot,
|
|
5
|
+
TextFieldSuffixIcon,
|
|
6
|
+
TextFieldSuffixText,
|
|
7
|
+
TextFieldInput,
|
|
8
|
+
TextFieldTextarea,
|
|
9
|
+
type TextFieldPrefixIconProps,
|
|
10
|
+
type TextFieldPrefixTextProps,
|
|
11
|
+
type TextFieldRootProps,
|
|
12
|
+
type TextFieldSuffixIconProps,
|
|
13
|
+
type TextFieldSuffixTextProps,
|
|
14
|
+
type TextFieldInputProps,
|
|
15
|
+
type TextFieldTextareaProps,
|
|
16
|
+
} from "./TextField";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
useTextFieldWithGraphemes,
|
|
20
|
+
type UseTextFieldWithGraphemesParams,
|
|
21
|
+
} from "./useTextFieldWithGraphemes";
|
|
22
|
+
|
|
23
|
+
export * as TextField from "./TextField.namespace";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { act, renderHook } from "@lynx-js/react/testing-library";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { NATIVE_TEXT_MAX_LENGTH_UNLIMITED } from "./context";
|
|
5
|
+
import { useTextFieldWithGraphemes } from "./useTextFieldWithGraphemes";
|
|
6
|
+
|
|
7
|
+
describe("useTextFieldWithGraphemes", () => {
|
|
8
|
+
it("counts extended grapheme clusters", () => {
|
|
9
|
+
const { result } = renderHook(() =>
|
|
10
|
+
useTextFieldWithGraphemes({ defaultValue: "๐จโ๐ฉโ๐งโ๐ฆ๐ณ๏ธโ๐eฬ" }),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(result.current.graphemes).toEqual(["๐จโ๐ฉโ๐งโ๐ฆ", "๐ณ๏ธโ๐", "eฬ"]);
|
|
14
|
+
expect(result.current.counterProps).toEqual({ current: 3, max: 0 });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("reports the original and grapheme-sliced values", () => {
|
|
18
|
+
const onValueChange = vi.fn();
|
|
19
|
+
const { result } = renderHook(() =>
|
|
20
|
+
useTextFieldWithGraphemes({ maxGraphemeCount: 2, onValueChange }),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
act(() => {
|
|
24
|
+
result.current.textFieldRootProps.onValueChange("A๐จโ๐ฉโ๐งโ๐ฆB");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(onValueChange).toHaveBeenCalledWith({
|
|
28
|
+
value: "A๐จโ๐ฉโ๐งโ๐ฆB",
|
|
29
|
+
graphemes: ["A", "๐จโ๐ฉโ๐งโ๐ฆ", "B"],
|
|
30
|
+
slicedValue: "A๐จโ๐ฉโ๐งโ๐ฆ",
|
|
31
|
+
slicedGraphemes: ["A", "๐จโ๐ฉโ๐งโ๐ฆ"],
|
|
32
|
+
});
|
|
33
|
+
expect(result.current.counterProps).toEqual({ current: 3, max: 2 });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("keeps a controlled value fixed", () => {
|
|
37
|
+
const onValueChange = vi.fn();
|
|
38
|
+
const { result } = renderHook(() =>
|
|
39
|
+
useTextFieldWithGraphemes({ value: "๊ณ ์ ", onValueChange }),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
act(() => {
|
|
43
|
+
result.current.textFieldRootProps.onValueChange("๋ณ๊ฒฝ");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(result.current.textFieldRootProps.value).toBe("๊ณ ์ ");
|
|
47
|
+
expect(onValueChange).toHaveBeenCalledOnce();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("leaves native insertion maxlength unmanaged without a grapheme limit", () => {
|
|
51
|
+
const { result } = renderHook(() => useTextFieldWithGraphemes({ defaultValue: "์ ํ ์์" }));
|
|
52
|
+
|
|
53
|
+
expect(result.current.textFieldRootProps.nativeInsertionMaxLength).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("provides a UTF-16 native insertion cap only at the grapheme limit", () => {
|
|
57
|
+
const family = "๐จโ๐ฉโ๐งโ๐ฆ";
|
|
58
|
+
const { result, rerender } = renderHook(
|
|
59
|
+
({ value }: { value: string }) => useTextFieldWithGraphemes({ value, maxGraphemeCount: 2 }),
|
|
60
|
+
{ initialProps: { value: family } },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(result.current.textFieldRootProps.nativeInsertionMaxLength).toBe(
|
|
64
|
+
NATIVE_TEXT_MAX_LENGTH_UNLIMITED,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const valueAtLimit = `${family}A`;
|
|
68
|
+
rerender({ value: valueAtLimit });
|
|
69
|
+
|
|
70
|
+
expect(result.current.graphemes).toEqual([family, "A"]);
|
|
71
|
+
expect(result.current.textFieldRootProps.nativeInsertionMaxLength).toBe(valueAtLimit.length);
|
|
72
|
+
expect(result.current.textFieldRootProps.nativeInsertionMaxLength).toBeGreaterThan(2);
|
|
73
|
+
|
|
74
|
+
rerender({ value: "A" });
|
|
75
|
+
|
|
76
|
+
expect(result.current.textFieldRootProps.nativeInsertionMaxLength).toBe(
|
|
77
|
+
NATIVE_TEXT_MAX_LENGTH_UNLIMITED,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from "@lynx-js/react";
|
|
2
|
+
import { splitGraphemes } from "unicode-segmenter/grapheme";
|
|
3
|
+
|
|
4
|
+
import { NATIVE_TEXT_MAX_LENGTH_UNLIMITED } from "./context";
|
|
5
|
+
|
|
6
|
+
export interface UseTextFieldWithGraphemesParams {
|
|
7
|
+
maxGraphemeCount?: number;
|
|
8
|
+
value?: string;
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
onValueChange?: (values: {
|
|
11
|
+
value: string;
|
|
12
|
+
graphemes: string[];
|
|
13
|
+
slicedValue: string;
|
|
14
|
+
slicedGraphemes: string[];
|
|
15
|
+
}) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let cachedValue = "";
|
|
19
|
+
let cachedGraphemes: string[] = [];
|
|
20
|
+
|
|
21
|
+
function getGraphemes(value: string): string[] {
|
|
22
|
+
if (value === cachedValue) return cachedGraphemes;
|
|
23
|
+
|
|
24
|
+
cachedValue = value;
|
|
25
|
+
cachedGraphemes = Array.from(splitGraphemes(value));
|
|
26
|
+
return cachedGraphemes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useTextFieldWithGraphemes({
|
|
30
|
+
maxGraphemeCount,
|
|
31
|
+
value: controlledValue,
|
|
32
|
+
defaultValue = "",
|
|
33
|
+
onValueChange,
|
|
34
|
+
}: UseTextFieldWithGraphemesParams) {
|
|
35
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
36
|
+
const isControlled = controlledValue !== undefined;
|
|
37
|
+
const value = isControlled ? controlledValue : uncontrolledValue;
|
|
38
|
+
const graphemes = React.useMemo(() => getGraphemes(value), [value]);
|
|
39
|
+
|
|
40
|
+
const handleValueChange = React.useCallback(
|
|
41
|
+
(nextValue: string) => {
|
|
42
|
+
"background only";
|
|
43
|
+
|
|
44
|
+
const nextGraphemes = getGraphemes(nextValue);
|
|
45
|
+
const slicedGraphemes =
|
|
46
|
+
maxGraphemeCount === undefined ? nextGraphemes : nextGraphemes.slice(0, maxGraphemeCount);
|
|
47
|
+
|
|
48
|
+
if (!isControlled) {
|
|
49
|
+
setUncontrolledValue(nextValue);
|
|
50
|
+
}
|
|
51
|
+
onValueChange?.({
|
|
52
|
+
value: nextValue,
|
|
53
|
+
graphemes: nextGraphemes,
|
|
54
|
+
slicedValue: slicedGraphemes.join(""),
|
|
55
|
+
slicedGraphemes,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
[isControlled, maxGraphemeCount, onValueChange],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
textFieldRootProps: {
|
|
63
|
+
value,
|
|
64
|
+
onValueChange: handleValueChange,
|
|
65
|
+
nativeInsertionMaxLength:
|
|
66
|
+
maxGraphemeCount === undefined
|
|
67
|
+
? undefined
|
|
68
|
+
: graphemes.length >= maxGraphemeCount
|
|
69
|
+
? value.length
|
|
70
|
+
: NATIVE_TEXT_MAX_LENGTH_UNLIMITED,
|
|
71
|
+
},
|
|
72
|
+
counterProps: {
|
|
73
|
+
current: graphemes.length,
|
|
74
|
+
max: maxGraphemeCount ?? 0,
|
|
75
|
+
},
|
|
76
|
+
graphemes,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -4,10 +4,13 @@ export * from "./Badge";
|
|
|
4
4
|
export * from "./Box";
|
|
5
5
|
export * from "./BottomSheet";
|
|
6
6
|
export * from "./Checkbox";
|
|
7
|
+
export * from "./Field";
|
|
7
8
|
export * from "./Icon";
|
|
9
|
+
export * from "./KeyboardAvoidingScrollView";
|
|
8
10
|
export * from "./ProgressCircle";
|
|
9
11
|
export * from "./RadioGroup";
|
|
10
12
|
export * from "./Stack";
|
|
11
13
|
export * from "./Switch";
|
|
12
14
|
export * from "./TagGroup";
|
|
13
15
|
export * from "./Text";
|
|
16
|
+
export * from "./TextField";
|