/
labeledinputview.ts
242 lines (209 loc) · 6.12 KB
/
labeledinputview.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/labeledinput/labeledinputview
*/
import View from '../view.js';
import LabelView from '../label/labelview.js';
import type { default as InputView, InputViewInputEvent } from '../input/inputview.js';
import { uid, type Locale } from '@ckeditor/ckeditor5-utils';
import '../../theme/components/labeledinput/labeledinput.css';
/**
* The labeled input view class.
*/
export default class LabeledInputView extends View {
/**
* The label view.
*/
public readonly labelView: LabelView;
/**
* The input view.
*/
public readonly inputView: InputView;
/**
* The status view for the {@link #inputView}. It displays {@link #errorText} and
* {@link #infoText}.
*/
public readonly statusView: View;
/**
* The text of the label.
*
* @observable
*/
declare public label: string | undefined;
/**
* The value of the input.
*
* @observable
*/
declare public value: string | undefined;
/**
* Controls whether the component is in read-only mode.
*
* @observable
*/
declare public isReadOnly: boolean;
/**
* The validation error text. When set, it will be displayed
* next to the {@link #inputView} as a typical validation error message.
* Set it to `null` to hide the message.
*
* **Note:** Setting this property to anything but `null` will automatically
* make the {@link module:ui/inputtext/inputtextview~InputTextView#hasError `hasError`}
* of the {@link #inputView} `true`.
*
* **Note:** Typing in the {@link #inputView} which fires the
* {@link module:ui/inputtext/inputtextview~InputTextView#event:input `input` event}
* resets this property back to `null`, indicating that the input field can be re–validated.
*
* @observable
*/
declare public errorText: string | null;
/**
* The additional information text displayed next to the {@link #inputView} which can
* be used to inform the user about the purpose of the input, provide help or hints.
*
* Set it to `null` to hide the message.
*
* **Note:** This text will be displayed in the same place as {@link #errorText} but the
* latter always takes precedence: if the {@link #errorText} is set, it replaces
* {@link #errorText} for as long as the value of the input is invalid.
*
* @observable
*/
declare public infoText: string | null;
/**
* The combined status text made of {@link #errorText} and {@link #infoText}.
* Note that when present, {@link #errorText} always takes precedence in the
* status.
*
* @see #errorText
* @see #infoText
* @see #statusView
* @private
* @observable
*/
declare public _statusText: string | null;
/**
* Creates an instance of the labeled input view class.
*
* @param locale The locale instance.
* @param InputView Constructor of the input view.
*/
constructor(
locale: Locale | undefined,
InputView: new ( locale: Locale | undefined, statusUid: string ) => InputView
) {
super( locale );
const inputUid = `ck-input-${ uid() }`;
const statusUid = `ck-status-${ uid() }`;
this.set( 'label', undefined );
this.set( 'value', undefined );
this.set( 'isReadOnly', false );
this.set( 'errorText', null );
this.set( 'infoText', null );
this.labelView = this._createLabelView( inputUid );
this.inputView = this._createInputView( InputView, inputUid, statusUid );
this.statusView = this._createStatusView( statusUid );
this.bind( '_statusText' ).to(
this, 'errorText',
this, 'infoText',
( errorText, infoText ) => errorText || infoText
);
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-labeled-input',
bind.if( 'isReadOnly', 'ck-disabled' )
]
},
children: [
this.labelView,
this.inputView,
this.statusView
]
} );
}
/**
* Creates label view class instance and bind with view.
*
* @param id Unique id to set as labelView#for attribute.
*/
private _createLabelView( id: string ) {
const labelView = new LabelView( this.locale );
labelView.for = id;
labelView.bind( 'text' ).to( this, 'label' );
return labelView;
}
/**
* Creates input view class instance and bind with view.
*
* @param InputView Input view constructor.
* @param inputUid Unique id to set as inputView#id attribute.
* @param statusUid Unique id of the status for the input's `aria-describedby` attribute.
*/
private _createInputView(
InputView: new ( locale: Locale | undefined, statusUid: string ) => InputView,
inputUid: string,
statusUid: string
) {
const inputView = new InputView( this.locale, statusUid );
inputView.id = inputUid;
inputView.ariaDescribedById = statusUid;
inputView.bind( 'value' ).to( this );
inputView.bind( 'isReadOnly' ).to( this );
inputView.bind( 'hasError' ).to( this, 'errorText', value => !!value );
inputView.on<InputViewInputEvent>( 'input', () => {
// UX: Make the error text disappear and disable the error indicator as the user
// starts fixing the errors.
this.errorText = null;
} );
return inputView;
}
/**
* Creates the status view instance. It displays {@link #errorText} and {@link #infoText}
* next to the {@link #inputView}. See {@link #_statusText}.
*
* @param statusUid Unique id of the status, shared with the input's `aria-describedby` attribute.
*/
private _createStatusView( statusUid: string ) {
const statusView = new View( this.locale );
const bind = this.bindTemplate;
statusView.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-labeled-input__status',
bind.if( 'errorText', 'ck-labeled-input__status_error' ),
bind.if( '_statusText', 'ck-hidden', value => !value )
],
id: statusUid,
role: bind.if( 'errorText', 'alert' )
},
children: [
{
text: bind.to( '_statusText' )
}
]
} );
return statusView;
}
/**
* Moves the focus to the input and selects the value.
*/
public select(): void {
this.inputView.select();
}
/**
* Focuses the input.
*/
public focus(): void {
this.inputView.focus();
}
}