1919 * @property {string[]? } autocompleteOptions
2020 * @property {string? } icon
2121 * @property {boolean? } clearable
22- * @property {boolean ? } disabled
22+ * @property {('value' | 'always') ? } clearableCondition
2323 * @property {function(string, InputState)? } onChange
24+ * @property {boolean? } disabled
25+ * @property {function(string, InputState)? } onClear
2426 * @property {number? } width
2527 * @property {number? } height
2628 * @property {string? } style
@@ -39,7 +41,11 @@ import { Portal } from './portal.js';
3941const { div, input, label, i, small } = van . tags ;
4042const defaultHeight = 32 ;
4143const iconSize = 22 ;
42- const clearIconSize = 20 ;
44+ const addonIconSize = 20 ;
45+ const passwordFieldTypeSwitch = {
46+ password : 'text' ,
47+ text : 'password' ,
48+ } ;
4349
4450const Input = ( /** @type Properties */ props ) => {
4551 loadStylesheet ( 'input' , stylesheet ) ;
@@ -54,6 +60,9 @@ const Input = (/** @type Properties */ props) => {
5460 return errors . val [ 0 ] ?? '' ;
5561 } ) ;
5662
63+ const originalInputType = van . derive ( ( ) => getValue ( props . type ) ?? 'text' ) ;
64+ const inputType = van . state ( originalInputType . rawVal ) ;
65+
5766 const onChange = props . onChange ?. val ?? props . onChange ;
5867 if ( onChange ) {
5968 onChange ( value . val , { errors : errors . val , valid : errors . val . length <= 0 } ) ;
@@ -65,6 +74,8 @@ const Input = (/** @type Properties */ props) => {
6574 }
6675 } ) ;
6776
77+ const onClear = props . onClear ?. val ?? props . onClear ?? ( ( ) => value . val = '' ) ;
78+
6879 const autocompleteOpened = van . state ( false ) ;
6980 const autocompleteOptions = van . derive ( ( ) => {
7081 const filtered = getValue ( props . autocompleteOptions ) ?. filter ( option => option . toLowerCase ( ) . includes ( value . val . toLowerCase ( ) ) ) ;
@@ -97,23 +108,49 @@ const Input = (/** @type Properties */ props) => {
97108 ) ,
98109 ( ) => getValue ( props . icon ) ? i (
99110 {
100- class : 'material-symbols-rounded tg-input--icon' ,
111+ class : 'material-symbols-rounded tg-input--icon text-secondary ' ,
101112 style : `bottom: ${ ( ( getValue ( props . height ) || defaultHeight ) - iconSize ) / 2 } px` ,
102113 } ,
103114 props . icon ,
104115 ) : '' ,
105- ( ) => getValue ( props . clearable ) ? i (
106- {
107- class : ( ) => `material-symbols-rounded tg-input--clear clickable ${ value . val ? '' : 'hidden' } ` ,
108- style : `bottom: ${ ( ( getValue ( props . height ) || defaultHeight ) - clearIconSize ) / 2 } px` ,
109- onclick : ( ) => value . val = '' ,
110- } ,
111- 'clear' ,
112- ) : '' ,
116+ ( ) => {
117+ const clearableCondition = getValue ( props . clearableCondition ) ?? 'value' ;
118+ const showClearable = getValue ( props . clearable ) && (
119+ clearableCondition === 'always'
120+ || ( clearableCondition === 'value' && value . val )
121+ ) ;
122+
123+ return div (
124+ { class : 'flex-row' } ,
125+ originalInputType . val === 'password' && value . val
126+ ? i (
127+ {
128+ class : 'material-symbols-rounded tg-input--visibility clickable text-secondary' ,
129+ style : `bottom: ${ ( ( getValue ( props . height ) || defaultHeight ) - addonIconSize ) / 2 } px` ,
130+ onclick : ( ) => inputType . val = passwordFieldTypeSwitch [ inputType . val ] ,
131+ } ,
132+ inputType . val === 'password' ? 'visibility' : 'visibility_off' ,
133+ )
134+ : '' ,
135+ showClearable
136+ ? i (
137+ {
138+ class : ( ) => `material-symbols-rounded tg-input--clear text-secondary clickable` ,
139+ style : `bottom: ${ ( ( getValue ( props . height ) || defaultHeight ) - addonIconSize ) / 2 } px` ,
140+ onclick : onClear ,
141+ } ,
142+ 'clear' ,
143+ )
144+ : '' ,
145+ ) ;
146+ } ,
113147
114148 div (
115149 {
116- class : ( ) => `flex-row tg-input--field ${ getValue ( props . disabled ) ? 'tg-input--disabled' : '' } ` ,
150+ class : ( ) => {
151+ const sufixIconCount = Number ( value . val && originalInputType . val === 'password' ) + Number ( value . val && getValue ( props . clearable ) ) ;
152+ return `flex-row tg-input--field ${ getValue ( props . disabled ) ? 'tg-input--disabled' : '' } sufix-padding-${ sufixIconCount } ` ;
153+ } ,
117154 style : ( ) => `height: ${ getValue ( props . height ) || defaultHeight } px;` ,
118155 } ,
119156 props . prefix
@@ -125,7 +162,7 @@ const Input = (/** @type Properties */ props) => {
125162 input ( {
126163 value,
127164 name : props . name ?? '' ,
128- type : props . type ?? 'text' ,
165+ type : inputType ,
129166 disabled : props . disabled ,
130167 placeholder : ( ) => getValue ( props . placeholder ) ?? '' ,
131168 oninput : debounce ( ( /** @type Event */ event ) => value . val = event . target . value , 300 ) ,
@@ -178,14 +215,23 @@ stylesheet.replace(`
178215 padding-left: 28px;
179216}
180217
181- .tg-input--clear {
218+ .tg-input--clear,
219+ .tg-input--visibility {
182220 position: absolute;
183- right: 4px;
184- font-size: ${ clearIconSize } px;
221+ font-size: ${ addonIconSize } px;
222+ right: 8px;
223+ }
224+
225+ .tg-input--visibility + .tg-input--clear {
226+ right: ${ addonIconSize + 16 } px;
227+ }
228+
229+ .tg-input--field.sufix-padding-1 {
230+ padding-right: ${ addonIconSize + 8 } px;
185231}
186232
187- .tg-input--clear ~ .tg-input--field {
188- padding-right: 24px ;
233+ .tg-input--field.sufix-padding-2 {
234+ padding-right: ${ addonIconSize * 2 + 8 * 2 } px; ;
189235}
190236
191237.tg-input--field {
0 commit comments