@lumen-design/input 0.0.2
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/dist/Input.svelte +177 -0
- package/dist/Input.svelte.d.ts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '@lumen-design/icon'
|
|
3
|
+
import { Eye, EyeOff, XCircle } from 'lucide'
|
|
4
|
+
import type { InputProps } from './types'
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
value = $bindable(''),
|
|
8
|
+
autofilled = $bindable(false),
|
|
9
|
+
type = 'text',
|
|
10
|
+
size = 'medium',
|
|
11
|
+
placeholder = '',
|
|
12
|
+
disabled = false,
|
|
13
|
+
clearable = false,
|
|
14
|
+
readonly = false,
|
|
15
|
+
showPassword = false,
|
|
16
|
+
maxlength,
|
|
17
|
+
name,
|
|
18
|
+
id,
|
|
19
|
+
autocomplete,
|
|
20
|
+
prefix,
|
|
21
|
+
suffix,
|
|
22
|
+
oninput,
|
|
23
|
+
onchange,
|
|
24
|
+
onfocus,
|
|
25
|
+
onblur,
|
|
26
|
+
onkeydown,
|
|
27
|
+
onkeyup,
|
|
28
|
+
onclear,
|
|
29
|
+
class: cls = '',
|
|
30
|
+
...attrs
|
|
31
|
+
}: InputProps = $props()
|
|
32
|
+
|
|
33
|
+
let inputRef: HTMLInputElement | null = $state(null)
|
|
34
|
+
let focused = $state(false)
|
|
35
|
+
let showClear = $derived(clearable && value && !disabled && !readonly && focused)
|
|
36
|
+
|
|
37
|
+
let passwordVisible = $state(false)
|
|
38
|
+
const showPwdToggle = $derived(showPassword && type === 'password' && !disabled)
|
|
39
|
+
const resolvedType = $derived(showPwdToggle ? (passwordVisible ? 'text' : 'password') : type)
|
|
40
|
+
|
|
41
|
+
const suffixCount = $derived((suffix ? 1 : 0) + (showClear ? 1 : 0) + (showPwdToggle ? 1 : 0))
|
|
42
|
+
|
|
43
|
+
const classes = $derived(
|
|
44
|
+
`lm-input lm-input--${size}${disabled ? ' is-disabled' : ''}${focused ? ' is-focused' : ''}${prefix ? ' has-prefix' : ''}${suffixCount >= 1 ? ' has-suffix' : ''}${suffixCount >= 2 ? ' has-suffix-2' : ''}${suffixCount >= 3 ? ' has-suffix-3' : ''}${cls ? ` ${cls}` : ''}`
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const handleInput = (e: Event & { currentTarget: HTMLInputElement }): void => {
|
|
48
|
+
value = e.currentTarget.value
|
|
49
|
+
oninput?.(e)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleChange = (e: Event & { currentTarget: HTMLInputElement }): void => {
|
|
53
|
+
value = e.currentTarget.value; onchange?.(e)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleFocus = (e: FocusEvent & { currentTarget: HTMLInputElement }): void => {
|
|
57
|
+
updateAutofillFromNode(e.currentTarget)
|
|
58
|
+
focused = true
|
|
59
|
+
onfocus?.(e)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handleAnimationStart = (e: AnimationEvent & { currentTarget: HTMLInputElement }): void => {
|
|
63
|
+
if (e.animationName === 'lm-input-autofill') {
|
|
64
|
+
updateAutofillFromNode(e.currentTarget)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const handleBlur = (e: FocusEvent & { currentTarget: HTMLInputElement }): void => {
|
|
69
|
+
focused = false
|
|
70
|
+
onblur?.(e)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handleClear = (): void => {
|
|
74
|
+
value = ''
|
|
75
|
+
onclear?.()
|
|
76
|
+
inputRef?.focus()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleTogglePassword = (): void => {
|
|
80
|
+
passwordVisible = !passwordVisible
|
|
81
|
+
inputRef?.focus()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const updateAutofillFromNode = (node: HTMLInputElement): void => {
|
|
85
|
+
try {
|
|
86
|
+
autofilled = node.matches(':-webkit-autofill')
|
|
87
|
+
if (autofilled) {
|
|
88
|
+
const domValue = node.value ?? ''
|
|
89
|
+
if (domValue && domValue !== value) value = domValue
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
autofilled = false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const autofillProbe = (node: HTMLInputElement) => {
|
|
97
|
+
updateAutofillFromNode(node)
|
|
98
|
+
const raf = requestAnimationFrame(() => updateAutofillFromNode(node))
|
|
99
|
+
const timer = setTimeout(() => updateAutofillFromNode(node), 200)
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
destroy() {
|
|
103
|
+
cancelAnimationFrame(raf)
|
|
104
|
+
clearTimeout(timer)
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<div class={classes}>
|
|
111
|
+
{#if prefix}
|
|
112
|
+
<span class="lm-input__prefix">
|
|
113
|
+
{@render prefix()}
|
|
114
|
+
</span>
|
|
115
|
+
{/if}
|
|
116
|
+
|
|
117
|
+
<input
|
|
118
|
+
bind:this={inputRef}
|
|
119
|
+
use:autofillProbe
|
|
120
|
+
{...attrs}
|
|
121
|
+
id={id}
|
|
122
|
+
name={name}
|
|
123
|
+
autocomplete={autocomplete as any}
|
|
124
|
+
type={resolvedType}
|
|
125
|
+
class="lm-input__inner"
|
|
126
|
+
{placeholder}
|
|
127
|
+
{disabled}
|
|
128
|
+
{readonly}
|
|
129
|
+
{maxlength}
|
|
130
|
+
bind:value={value}
|
|
131
|
+
oninput={handleInput}
|
|
132
|
+
onchange={handleChange}
|
|
133
|
+
onfocus={handleFocus}
|
|
134
|
+
onanimationstart={handleAnimationStart}
|
|
135
|
+
onblur={handleBlur}
|
|
136
|
+
{onkeydown}
|
|
137
|
+
{onkeyup}
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
{#if suffix || showClear || showPwdToggle}
|
|
141
|
+
<span class="lm-input__suffix">
|
|
142
|
+
{#if showPwdToggle}
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
class="lm-input__password"
|
|
146
|
+
onmousedown={(e: MouseEvent) => {
|
|
147
|
+
e.preventDefault()
|
|
148
|
+
handleTogglePassword()
|
|
149
|
+
}}
|
|
150
|
+
tabindex="-1"
|
|
151
|
+
aria-label={passwordVisible ? '隐藏密码' : '显示密码'}
|
|
152
|
+
>
|
|
153
|
+
<Icon icon={passwordVisible ? EyeOff : Eye} size={16} />
|
|
154
|
+
</button>
|
|
155
|
+
{/if}
|
|
156
|
+
|
|
157
|
+
{#if showClear}
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
class="lm-input__clear"
|
|
161
|
+
onmousedown={(e: MouseEvent) => {
|
|
162
|
+
e.preventDefault()
|
|
163
|
+
handleClear()
|
|
164
|
+
}}
|
|
165
|
+
tabindex="-1"
|
|
166
|
+
aria-label="Clear input"
|
|
167
|
+
>
|
|
168
|
+
<Icon icon={XCircle} size={16} />
|
|
169
|
+
</button>
|
|
170
|
+
{/if}
|
|
171
|
+
|
|
172
|
+
{#if suffix}
|
|
173
|
+
{@render suffix()}
|
|
174
|
+
{/if}
|
|
175
|
+
</span>
|
|
176
|
+
{/if}
|
|
177
|
+
</div>
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/** Input 类型 */
|
|
3
|
+
export type InputType = 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
|
|
4
|
+
/** Input 尺寸 */
|
|
5
|
+
export type InputSize = 'small' | 'medium' | 'large';
|
|
6
|
+
/** Input 属性 */
|
|
7
|
+
export interface InputProps {
|
|
8
|
+
/** 绑定值 */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** 输入框类型 */
|
|
11
|
+
type?: InputType;
|
|
12
|
+
/** 尺寸 */
|
|
13
|
+
size?: InputSize;
|
|
14
|
+
/** 占位符 */
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** 原生 name(对浏览器自动填充很关键) */
|
|
17
|
+
name?: string;
|
|
18
|
+
/** 原生 id */
|
|
19
|
+
id?: string;
|
|
20
|
+
/** 原生 autocomplete */
|
|
21
|
+
autocomplete?: string;
|
|
22
|
+
/** 原生 autocapitalize */
|
|
23
|
+
autocapitalize?: 'off' | 'on' | 'characters' | 'none' | 'sentences' | 'words' | null;
|
|
24
|
+
/** 原生 spellcheck */
|
|
25
|
+
spellcheck?: boolean;
|
|
26
|
+
/** 无可视 label 时的可访问性标签(透传到 input) */
|
|
27
|
+
'aria-label'?: string;
|
|
28
|
+
/** 是否禁用 */
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
/** 是否可清空 */
|
|
31
|
+
clearable?: boolean;
|
|
32
|
+
/** 是否只读 */
|
|
33
|
+
readonly?: boolean;
|
|
34
|
+
/** 密码可见切换(仅 type=password 时生效) */
|
|
35
|
+
showPassword?: boolean;
|
|
36
|
+
/** 最大长度 */
|
|
37
|
+
maxlength?: number;
|
|
38
|
+
/** 前缀内容 */
|
|
39
|
+
prefix?: Snippet;
|
|
40
|
+
/** 后缀内容 */
|
|
41
|
+
suffix?: Snippet;
|
|
42
|
+
/** 输入事件 */
|
|
43
|
+
oninput?: (e: Event & {
|
|
44
|
+
currentTarget: HTMLInputElement;
|
|
45
|
+
}) => void;
|
|
46
|
+
/** 变化事件 */
|
|
47
|
+
onchange?: (e: Event & {
|
|
48
|
+
currentTarget: HTMLInputElement;
|
|
49
|
+
}) => void;
|
|
50
|
+
/** 聚焦事件 */
|
|
51
|
+
onfocus?: (e: FocusEvent & {
|
|
52
|
+
currentTarget: HTMLInputElement;
|
|
53
|
+
}) => void;
|
|
54
|
+
/** 失焦事件 */
|
|
55
|
+
onblur?: (e: FocusEvent & {
|
|
56
|
+
currentTarget: HTMLInputElement;
|
|
57
|
+
}) => void;
|
|
58
|
+
/** 键盘按下事件 */
|
|
59
|
+
onkeydown?: (e: KeyboardEvent & {
|
|
60
|
+
currentTarget: HTMLInputElement;
|
|
61
|
+
}) => void;
|
|
62
|
+
/** 键盘抬起事件 */
|
|
63
|
+
onkeyup?: (e: KeyboardEvent & {
|
|
64
|
+
currentTarget: HTMLInputElement;
|
|
65
|
+
}) => void;
|
|
66
|
+
/** 清空事件 */
|
|
67
|
+
onclear?: () => void;
|
|
68
|
+
/** 自定义类名 */
|
|
69
|
+
class?: string;
|
|
70
|
+
/** 是否被浏览器自动填充(只读,由组件内部检测 :-webkit-autofill 设置) */
|
|
71
|
+
autofilled?: boolean;
|
|
72
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumen-design/input",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Input component for Lumen UI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./dist/index.js",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "svelte-package -i src -o dist --types",
|
|
21
|
+
"postbuild": "cp -f src/*.d.ts dist/ 2>/dev/null || true",
|
|
22
|
+
"build:watch": "svelte-package -i src -o dist --types -w"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@sveltejs/package": "^2.5.7",
|
|
26
|
+
"svelte": "5.48.2"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"svelte": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"lucide": "^0.563.0"
|
|
33
|
+
}
|
|
34
|
+
}
|