react-input-material 0.0.433 → 0.0.435
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/components/FileInput.js +1 -1
- package/components/FileInput.styles.css +3 -3
- package/components/GenericAnimate.d.ts +5 -3
- package/components/GenericAnimate.js +1 -1
- package/components/GenericInput.js +1 -1
- package/components/GenericInput.styles.css +1 -1
- package/components/Inputs.js +1 -1
- package/components/Inputs.styles.css +2 -2
- package/components/Interval.js +1 -1
- package/components/Interval.styles.css +2 -2
- package/components/RequireableCheckbox.styles.css +1 -1
- package/index.js +1 -1
- package/index.styles.css +6 -6
- package/package.json +4 -2
- package/type.d.ts +5 -0
- package/components/Dummy.tsx +0 -58
- package/components/FileInput.tsx +0 -1010
- package/components/GenericAnimate.tsx +0 -88
- package/components/GenericInput.tsx +0 -2875
- package/components/Inputs.tsx +0 -585
- package/components/Interval.tsx +0 -436
- package/components/RequireableCheckbox.tsx +0 -520
- package/components/WrapConfigurations.tsx +0 -127
- package/components/WrapStrict.tsx +0 -43
- package/components/WrapThemeProvider.tsx +0 -48
- package/components/WrapTooltip.tsx +0 -67
|
@@ -1,2875 +0,0 @@
|
|
|
1
|
-
// #!/usr/bin/env babel-node
|
|
2
|
-
// -*- coding: utf-8 -*-
|
|
3
|
-
/** @module GenericInput */
|
|
4
|
-
'use strict'
|
|
5
|
-
/* !
|
|
6
|
-
region header
|
|
7
|
-
[Project page](https://torben.website/react-material-input)
|
|
8
|
-
|
|
9
|
-
Copyright Torben Sickert (info["~at~"]torben.website) 16.12.2012
|
|
10
|
-
|
|
11
|
-
License
|
|
12
|
-
-------
|
|
13
|
-
|
|
14
|
-
This library written by Torben Sickert stand under a creative commons
|
|
15
|
-
naming 3.0 unported license.
|
|
16
|
-
See https://creativecommons.org/licenses/by/3.0/deed.de
|
|
17
|
-
endregion
|
|
18
|
-
*/
|
|
19
|
-
// region imports
|
|
20
|
-
import {Ace as CodeEditorNamespace} from 'ace-builds'
|
|
21
|
-
|
|
22
|
-
import Tools, {optionalRequire} from 'clientnode'
|
|
23
|
-
import {EvaluationResult, Mapping} from 'clientnode/type'
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
FocusEvent as ReactFocusEvent,
|
|
27
|
-
forwardRef,
|
|
28
|
-
ForwardedRef,
|
|
29
|
-
KeyboardEvent as ReactKeyboardEvent,
|
|
30
|
-
lazy,
|
|
31
|
-
memo as memorize,
|
|
32
|
-
MouseEvent as ReactMouseEvent,
|
|
33
|
-
MutableRefObject,
|
|
34
|
-
ReactElement,
|
|
35
|
-
ReactNode,
|
|
36
|
-
Suspense,
|
|
37
|
-
useEffect,
|
|
38
|
-
useImperativeHandle,
|
|
39
|
-
useRef,
|
|
40
|
-
useState
|
|
41
|
-
} from 'react'
|
|
42
|
-
import CodeEditorType, {IAceEditorProps as CodeEditorProps} from 'react-ace'
|
|
43
|
-
import {TransitionProps} from 'react-transition-group/Transition'
|
|
44
|
-
import UseAnimationsType from 'react-useanimations'
|
|
45
|
-
import LockAnimation from 'react-useanimations/lib/lock'
|
|
46
|
-
import PlusToXAnimation from 'react-useanimations/lib/plusToX'
|
|
47
|
-
|
|
48
|
-
import {Editor as RichTextEditor} from 'tinymce'
|
|
49
|
-
|
|
50
|
-
import {MDCMenuFoundation} from '@material/menu'
|
|
51
|
-
import {MDCSelectFoundation} from '@material/select'
|
|
52
|
-
import {MDCTextFieldFoundation} from '@material/textfield'
|
|
53
|
-
|
|
54
|
-
import {CircularProgress} from '@rmwc/circular-progress'
|
|
55
|
-
import {FormField} from '@rmwc/formfield'
|
|
56
|
-
import {Icon} from '@rmwc/icon'
|
|
57
|
-
import {IconButton} from '@rmwc/icon-button'
|
|
58
|
-
import {
|
|
59
|
-
Menu, MenuApi, MenuSurface, MenuSurfaceAnchor, MenuItem, MenuOnSelectEventT
|
|
60
|
-
} from '@rmwc/menu'
|
|
61
|
-
import {Select, SelectProps} from '@rmwc/select'
|
|
62
|
-
import {TextField, TextFieldProps} from '@rmwc/textfield'
|
|
63
|
-
import {Theme} from '@rmwc/theme'
|
|
64
|
-
import {IconOptions} from '@rmwc/types'
|
|
65
|
-
|
|
66
|
-
import {
|
|
67
|
-
Editor as RichTextEditorComponent, IAllProps as RichTextEditorProps
|
|
68
|
-
} from '@tinymce/tinymce-react'
|
|
69
|
-
import {
|
|
70
|
-
EventHandler as RichTextEventHandler
|
|
71
|
-
} from '@tinymce/tinymce-react/lib/cjs/main/ts/Events'
|
|
72
|
-
|
|
73
|
-
import Dummy from './Dummy'
|
|
74
|
-
import GenericAnimate from './GenericAnimate'
|
|
75
|
-
/*
|
|
76
|
-
"namedExport" version of css-loader:
|
|
77
|
-
|
|
78
|
-
import {
|
|
79
|
-
genericInputSuggestionsSuggestionClassName,
|
|
80
|
-
genericInputSuggestionsSuggestionMarkClassName,
|
|
81
|
-
genericInputClassName,
|
|
82
|
-
genericInputCustomClassName,
|
|
83
|
-
genericInputEditorLabelClassName,
|
|
84
|
-
genericInputSuggestionsClassName,
|
|
85
|
-
genericInputSuggestionsPendingClassName
|
|
86
|
-
} from './GenericInput.module'
|
|
87
|
-
*/
|
|
88
|
-
import cssClassNames from './GenericInput.module'
|
|
89
|
-
import WrapConfigurations from './WrapConfigurations'
|
|
90
|
-
import WrapTooltip from './WrapTooltip'
|
|
91
|
-
import {
|
|
92
|
-
deriveMissingPropertiesFromState as deriveMissingBasePropertiesFromState,
|
|
93
|
-
determineInitialValue,
|
|
94
|
-
determineInitialRepresentation,
|
|
95
|
-
determineValidationState as determineBaseValidationState,
|
|
96
|
-
formatValue,
|
|
97
|
-
getConsolidatedProperties as getBaseConsolidatedProperties,
|
|
98
|
-
getLabelAndValues,
|
|
99
|
-
getValueFromSelection,
|
|
100
|
-
mapPropertiesIntoModel,
|
|
101
|
-
normalizeSelection,
|
|
102
|
-
parseValue,
|
|
103
|
-
translateKnownSymbols,
|
|
104
|
-
triggerCallbackIfExists,
|
|
105
|
-
useMemorizedValue,
|
|
106
|
-
wrapStateSetter
|
|
107
|
-
} from '../helper'
|
|
108
|
-
import {
|
|
109
|
-
CursorState,
|
|
110
|
-
DataTransformSpecification,
|
|
111
|
-
defaultInputModelState as defaultModelState,
|
|
112
|
-
DefaultInputProperties as DefaultProperties,
|
|
113
|
-
defaultInputProperties as defaultProperties,
|
|
114
|
-
EditorState,
|
|
115
|
-
GenericEvent,
|
|
116
|
-
InputAdapter as Adapter,
|
|
117
|
-
InputAdapterWithReferences as AdapterWithReferences,
|
|
118
|
-
InputDataTransformation,
|
|
119
|
-
InputModelState as ModelState,
|
|
120
|
-
InputProperties as Properties,
|
|
121
|
-
inputPropertyTypes as propertyTypes,
|
|
122
|
-
InputProps as Props,
|
|
123
|
-
inputRenderProperties as renderProperties,
|
|
124
|
-
InputState as State,
|
|
125
|
-
InputModel as Model,
|
|
126
|
-
NativeInputType,
|
|
127
|
-
NormalizedSelection,
|
|
128
|
-
Renderable,
|
|
129
|
-
GenericInputComponent,
|
|
130
|
-
InputTablePosition as TablePosition,
|
|
131
|
-
InputValueState as ValueState,
|
|
132
|
-
TinyMCEOptions
|
|
133
|
-
} from '../type'
|
|
134
|
-
|
|
135
|
-
declare const TARGET_TECHNOLOGY:string
|
|
136
|
-
const isBrowser =
|
|
137
|
-
!(TARGET_TECHNOLOGY === 'node' || typeof window === undefined)
|
|
138
|
-
const UseAnimations:null|typeof Dummy|typeof UseAnimationsType =
|
|
139
|
-
isBrowser ? optionalRequire('react-useanimations') : null
|
|
140
|
-
const lockAnimation:null|typeof LockAnimation = isBrowser ?
|
|
141
|
-
optionalRequire('react-useanimations/lib/lock') :
|
|
142
|
-
null
|
|
143
|
-
const plusToXAnimation:null|typeof PlusToXAnimation = isBrowser ?
|
|
144
|
-
optionalRequire('react-useanimations/lib/plusToX') :
|
|
145
|
-
null
|
|
146
|
-
// endregion
|
|
147
|
-
const CSS_CLASS_NAMES:Mapping = cssClassNames as Mapping
|
|
148
|
-
// region code editor configuration
|
|
149
|
-
export const ACEEditorOptions = {
|
|
150
|
-
basePath: '/node_modules/ace-builds/src-noconflict/',
|
|
151
|
-
useWorker: false
|
|
152
|
-
}
|
|
153
|
-
const CodeEditor = lazy<typeof CodeEditorType>(
|
|
154
|
-
async ():Promise<{default:typeof CodeEditorType}> => {
|
|
155
|
-
const {config} = await import('ace-builds')
|
|
156
|
-
for (const [name, value] of Object.entries(ACEEditorOptions))
|
|
157
|
-
config.set(name, value)
|
|
158
|
-
|
|
159
|
-
return await import('react-ace')
|
|
160
|
-
}
|
|
161
|
-
)
|
|
162
|
-
// endregion
|
|
163
|
-
// region rich text editor configuration
|
|
164
|
-
declare const UTC_BUILD_TIMESTAMP:number|undefined
|
|
165
|
-
// NOTE: Could be set via module bundler environment variables.
|
|
166
|
-
const CURRENT_UTC_BUILD_TIMESTAMP =
|
|
167
|
-
typeof UTC_BUILD_TIMESTAMP === 'undefined' ? 1 : UTC_BUILD_TIMESTAMP
|
|
168
|
-
let richTextEditorLoadedOnce = false
|
|
169
|
-
const tinymceBasePath = '/node_modules/tinymce/'
|
|
170
|
-
export const TINYMCE_DEFAULT_OPTIONS:Partial<TinyMCEOptions> = {
|
|
171
|
-
/* eslint-disable camelcase */
|
|
172
|
-
// region paths
|
|
173
|
-
base_url: tinymceBasePath,
|
|
174
|
-
skin_url: `${tinymceBasePath}skins/ui/oxide`,
|
|
175
|
-
theme_url: `${tinymceBasePath}themes/silver/theme.min.js`,
|
|
176
|
-
// endregion
|
|
177
|
-
allow_conditional_comments: false,
|
|
178
|
-
allow_script_urls: false,
|
|
179
|
-
body_class: 'mdc-text-field__input',
|
|
180
|
-
branding: false,
|
|
181
|
-
cache_suffix: `?version=${CURRENT_UTC_BUILD_TIMESTAMP}`,
|
|
182
|
-
contextmenu: [],
|
|
183
|
-
document_base_url: '/',
|
|
184
|
-
element_format: 'xhtml',
|
|
185
|
-
entity_encoding: 'raw',
|
|
186
|
-
fix_list_elements: true,
|
|
187
|
-
hidden_input: false,
|
|
188
|
-
icon: 'material',
|
|
189
|
-
invalid_elements: 'em',
|
|
190
|
-
invalid_styles: 'color font-size line-height',
|
|
191
|
-
keep_styles: false,
|
|
192
|
-
menubar: false,
|
|
193
|
-
/* eslint-disable max-len */
|
|
194
|
-
plugins: [
|
|
195
|
-
'fullscreen',
|
|
196
|
-
'link',
|
|
197
|
-
'code',
|
|
198
|
-
'nonbreaking',
|
|
199
|
-
'searchreplace',
|
|
200
|
-
'visualblocks'
|
|
201
|
-
],
|
|
202
|
-
/* eslint-enable max-len */
|
|
203
|
-
relative_urls: false,
|
|
204
|
-
remove_script_host: false,
|
|
205
|
-
remove_trailing_brs: true,
|
|
206
|
-
schema: 'html5',
|
|
207
|
-
toolbar1: `
|
|
208
|
-
cut copy paste |
|
|
209
|
-
undo redo removeformat |
|
|
210
|
-
styleselect formatselect fontselect fontsizeselect |
|
|
211
|
-
searchreplace visualblocks fullscreen code
|
|
212
|
-
`.trim(),
|
|
213
|
-
toolbar2: `
|
|
214
|
-
alignleft aligncenter alignright alignjustify outdent indent |
|
|
215
|
-
link nonbreaking bullist numlist bold italic underline strikethrough
|
|
216
|
-
`.trim(),
|
|
217
|
-
trim: true
|
|
218
|
-
/* eslint-enable camelcase */
|
|
219
|
-
}
|
|
220
|
-
// endregion
|
|
221
|
-
// region static helper
|
|
222
|
-
/**
|
|
223
|
-
* Derives validation state from provided properties and state.
|
|
224
|
-
* @param properties - Current component properties.
|
|
225
|
-
* @param currentState - Current component state.
|
|
226
|
-
*
|
|
227
|
-
* @returns Whether component is in an aggregated valid or invalid state.
|
|
228
|
-
*/
|
|
229
|
-
export function determineValidationState<T>(
|
|
230
|
-
properties:DefaultProperties<T>, currentState:Partial<ModelState>
|
|
231
|
-
):boolean {
|
|
232
|
-
return determineBaseValidationState<
|
|
233
|
-
DefaultProperties<T>, Partial<ModelState>
|
|
234
|
-
>(
|
|
235
|
-
properties,
|
|
236
|
-
currentState,
|
|
237
|
-
{
|
|
238
|
-
invalidMaximum: ():boolean => (
|
|
239
|
-
typeof properties.model.maximum === 'number' &&
|
|
240
|
-
typeof properties.model.value === 'number' &&
|
|
241
|
-
!isNaN(properties.model.value) &&
|
|
242
|
-
properties.model.maximum >= 0 &&
|
|
243
|
-
properties.model.maximum < properties.model.value
|
|
244
|
-
),
|
|
245
|
-
invalidMinimum: ():boolean => (
|
|
246
|
-
typeof properties.model.minimum === 'number' &&
|
|
247
|
-
typeof properties.model.value === 'number' &&
|
|
248
|
-
!isNaN(properties.model.value) &&
|
|
249
|
-
properties.model.value < properties.model.minimum
|
|
250
|
-
),
|
|
251
|
-
|
|
252
|
-
invalidMaximumLength: ():boolean => (
|
|
253
|
-
typeof properties.model.maximumLength === 'number' &&
|
|
254
|
-
typeof properties.model.value === 'string' &&
|
|
255
|
-
properties.model.maximumLength >= 0 &&
|
|
256
|
-
properties.model.maximumLength < properties.model.value.length
|
|
257
|
-
),
|
|
258
|
-
invalidMinimumLength: ():boolean => (
|
|
259
|
-
typeof properties.model.minimumLength === 'number' &&
|
|
260
|
-
typeof properties.model.value === 'string' &&
|
|
261
|
-
properties.model.value.length < properties.model.minimumLength
|
|
262
|
-
),
|
|
263
|
-
|
|
264
|
-
invalidInvertedPattern: ():boolean => (
|
|
265
|
-
typeof properties.model.value === 'string' &&
|
|
266
|
-
([] as Array<null|RegExp|string>)
|
|
267
|
-
.concat(properties.model.invertedRegularExpressionPattern)
|
|
268
|
-
.some((expression:null|RegExp|string):boolean =>
|
|
269
|
-
typeof expression === 'string' &&
|
|
270
|
-
(new RegExp(expression)).test(
|
|
271
|
-
properties.model.value as unknown as string
|
|
272
|
-
) ||
|
|
273
|
-
expression !== null &&
|
|
274
|
-
typeof expression === 'object' &&
|
|
275
|
-
expression
|
|
276
|
-
.test(properties.model.value as unknown as string)
|
|
277
|
-
)
|
|
278
|
-
),
|
|
279
|
-
invalidPattern: ():boolean => (
|
|
280
|
-
typeof properties.model.value === 'string' &&
|
|
281
|
-
([] as Array<null|RegExp|string>)
|
|
282
|
-
.concat(properties.model.regularExpressionPattern)
|
|
283
|
-
.some((expression:null|RegExp|string):boolean =>
|
|
284
|
-
typeof expression === 'string' &&
|
|
285
|
-
!(new RegExp(expression)).test(
|
|
286
|
-
properties.model.value as unknown as string
|
|
287
|
-
) ||
|
|
288
|
-
expression !== null &&
|
|
289
|
-
typeof expression === 'object' &&
|
|
290
|
-
!expression
|
|
291
|
-
.test(properties.model.value as unknown as string)
|
|
292
|
-
)
|
|
293
|
-
)
|
|
294
|
-
}
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Avoid propagating the enter key event since this usually sends a form which
|
|
299
|
-
* is not intended when working in a text field.
|
|
300
|
-
* @param event - Keyboard event.
|
|
301
|
-
*
|
|
302
|
-
* @returns Nothing.
|
|
303
|
-
*/
|
|
304
|
-
export function preventEnterKeyPropagation(event:ReactKeyboardEvent):void {
|
|
305
|
-
if (Tools.keyCode.ENTER === event.keyCode)
|
|
306
|
-
event.stopPropagation()
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Indicates whether a provided query is matching currently provided
|
|
310
|
-
* suggestion.
|
|
311
|
-
* @param suggestion - Candidate to match again.
|
|
312
|
-
* @param query - Search query to check for matching.
|
|
313
|
-
*
|
|
314
|
-
* @returns Boolean result whether provided suggestion matches given query or
|
|
315
|
-
* not.
|
|
316
|
-
*/
|
|
317
|
-
export function suggestionMatches(
|
|
318
|
-
suggestion:string, query?:null|string
|
|
319
|
-
):boolean {
|
|
320
|
-
if (query) {
|
|
321
|
-
suggestion = suggestion.toLowerCase()
|
|
322
|
-
|
|
323
|
-
return query
|
|
324
|
-
.replace(/ +/g, ' ')
|
|
325
|
-
.toLowerCase()
|
|
326
|
-
.split(' ')
|
|
327
|
-
.every((part:string):boolean => suggestion.includes(part))
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return false
|
|
331
|
-
}
|
|
332
|
-
// endregion
|
|
333
|
-
/* eslint-disable jsdoc/require-description-complete-sentence */
|
|
334
|
-
/**
|
|
335
|
-
* Generic input wrapper component which automatically determines a useful
|
|
336
|
-
* input field depending on given model specification.
|
|
337
|
-
*
|
|
338
|
-
* Dataflow:
|
|
339
|
-
*
|
|
340
|
-
* 1. On-Render all states are merged with given properties into a normalized
|
|
341
|
-
* property object.
|
|
342
|
-
* 2. Properties, corresponding state values and sub node instances are saved
|
|
343
|
-
* into a "ref" object (to make them accessible from the outside e.g. for
|
|
344
|
-
* wrapper like web-components).
|
|
345
|
-
* 3. Event handler saves corresponding data modifications into state and
|
|
346
|
-
* normalized properties object.
|
|
347
|
-
* 4. All state changes except selection changes trigger an "onChange" event
|
|
348
|
-
* which delivers the consolidated properties object (with latest
|
|
349
|
-
* modifications included).
|
|
350
|
-
* @property static:displayName - Descriptive name for component to show in web
|
|
351
|
-
* developer tools.
|
|
352
|
-
*
|
|
353
|
-
* @param props - Given components properties.
|
|
354
|
-
* @param reference - Reference object to forward internal state.
|
|
355
|
-
*
|
|
356
|
-
* @returns React elements.
|
|
357
|
-
*/
|
|
358
|
-
export const GenericInputInner = function<Type = unknown>(
|
|
359
|
-
props:Props<Type>, reference?:ForwardedRef<Adapter<Type>>
|
|
360
|
-
):ReactElement {
|
|
361
|
-
/* eslint-enable jsdoc/require-description-complete-sentence */
|
|
362
|
-
// region live-cycle
|
|
363
|
-
/**
|
|
364
|
-
* Is triggered immediate after a re-rendering. Re-stores cursor selection
|
|
365
|
-
* state if editor has been switched.
|
|
366
|
-
* @returns Nothing.
|
|
367
|
-
*/
|
|
368
|
-
useEffect(():void => {
|
|
369
|
-
// region text-editor selection synchronisation
|
|
370
|
-
if (selectionIsUnstable || editorState.selectionIsUnstable)
|
|
371
|
-
if (properties.editorIsActive) {
|
|
372
|
-
/*
|
|
373
|
-
NOTE: If the corresponding editor are not loaded yet they
|
|
374
|
-
will set the selection state on initialisation as long as
|
|
375
|
-
"editorState.selectionIsUnstable" is set to "true".
|
|
376
|
-
*/
|
|
377
|
-
if (codeEditorReference.current?.editor?.selection) {
|
|
378
|
-
(codeEditorReference.current.editor.textInput as
|
|
379
|
-
unknown as
|
|
380
|
-
HTMLInputElement
|
|
381
|
-
).focus()
|
|
382
|
-
setCodeEditorSelectionState(codeEditorReference.current)
|
|
383
|
-
|
|
384
|
-
if (editorState.selectionIsUnstable)
|
|
385
|
-
setEditorState(
|
|
386
|
-
{...editorState, selectionIsUnstable: false}
|
|
387
|
-
)
|
|
388
|
-
} else if (richTextEditorInstance.current?.selection) {
|
|
389
|
-
richTextEditorInstance.current.focus(false)
|
|
390
|
-
setRichTextEditorSelectionState(
|
|
391
|
-
richTextEditorInstance.current
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
if (editorState.selectionIsUnstable)
|
|
395
|
-
setEditorState(
|
|
396
|
-
{...editorState, selectionIsUnstable: false}
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
} else if (inputReference.current) {
|
|
400
|
-
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
|
401
|
-
;(
|
|
402
|
-
inputReference.current as
|
|
403
|
-
HTMLInputElement|HTMLTextAreaElement
|
|
404
|
-
).setSelectionRange(
|
|
405
|
-
properties.cursor.start, properties.cursor.end
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
if (editorState.selectionIsUnstable)
|
|
409
|
-
setEditorState(
|
|
410
|
-
{...editorState, selectionIsUnstable: false}
|
|
411
|
-
)
|
|
412
|
-
}
|
|
413
|
-
// endregion
|
|
414
|
-
})
|
|
415
|
-
// endregion
|
|
416
|
-
// region context helper
|
|
417
|
-
/// region render helper
|
|
418
|
-
/**
|
|
419
|
-
* Applies icon preset configurations.
|
|
420
|
-
* @param options - Icon options to extend of known preset identified.
|
|
421
|
-
*
|
|
422
|
-
* @returns Given potential extended icon configuration.
|
|
423
|
-
*/
|
|
424
|
-
const applyIconPreset = (
|
|
425
|
-
options?:Properties['icon']
|
|
426
|
-
):IconOptions|string|undefined => {
|
|
427
|
-
if (options === 'clear_preset')
|
|
428
|
-
return {
|
|
429
|
-
icon: <GenericAnimate
|
|
430
|
-
in={!Tools.equals(properties.value, properties.default)}
|
|
431
|
-
>
|
|
432
|
-
{(
|
|
433
|
-
UseAnimations &&
|
|
434
|
-
!(UseAnimations as typeof Dummy).isDummy &&
|
|
435
|
-
plusToXAnimation
|
|
436
|
-
) ?
|
|
437
|
-
<UseAnimations
|
|
438
|
-
animation={plusToXAnimation} reverse={true}
|
|
439
|
-
/> :
|
|
440
|
-
<IconButton icon="clear"/>
|
|
441
|
-
}
|
|
442
|
-
</GenericAnimate>,
|
|
443
|
-
onClick: (event:ReactMouseEvent):void => {
|
|
444
|
-
event.preventDefault()
|
|
445
|
-
event.stopPropagation()
|
|
446
|
-
|
|
447
|
-
onChangeValue(parseValue<Type>(
|
|
448
|
-
properties,
|
|
449
|
-
properties.default as Type,
|
|
450
|
-
GenericInput.transformer
|
|
451
|
-
))
|
|
452
|
-
},
|
|
453
|
-
strategy: 'component',
|
|
454
|
-
tooltip: 'Clear input'
|
|
455
|
-
}
|
|
456
|
-
if (options === 'password_preset')
|
|
457
|
-
return useMemorizedValue(
|
|
458
|
-
{
|
|
459
|
-
icon: (
|
|
460
|
-
UseAnimations &&
|
|
461
|
-
!(UseAnimations as typeof Dummy).isDummy &&
|
|
462
|
-
lockAnimation
|
|
463
|
-
) ?
|
|
464
|
-
<UseAnimations
|
|
465
|
-
animation={lockAnimation}
|
|
466
|
-
reverse={!properties.hidden}
|
|
467
|
-
/> :
|
|
468
|
-
<IconButton
|
|
469
|
-
icon={properties.hidden ? 'lock_open' : 'lock'}
|
|
470
|
-
/>,
|
|
471
|
-
onClick: (event:ReactMouseEvent):void => {
|
|
472
|
-
event.preventDefault()
|
|
473
|
-
event.stopPropagation()
|
|
474
|
-
setHidden((value:boolean|undefined):boolean => {
|
|
475
|
-
if (value === undefined)
|
|
476
|
-
value = properties.hidden
|
|
477
|
-
properties.hidden = !value
|
|
478
|
-
|
|
479
|
-
onChange(event)
|
|
480
|
-
|
|
481
|
-
return properties.hidden
|
|
482
|
-
})
|
|
483
|
-
},
|
|
484
|
-
strategy: 'component',
|
|
485
|
-
tooltip:
|
|
486
|
-
`${(properties.hidden ? 'Show' : 'Hide')} password`
|
|
487
|
-
},
|
|
488
|
-
properties.hidden
|
|
489
|
-
)
|
|
490
|
-
return options
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Derives native input type from given input property configuration.
|
|
494
|
-
* @param properties - Input configuration to derive native input type
|
|
495
|
-
* from.
|
|
496
|
-
*
|
|
497
|
-
* @returns Determined input type.
|
|
498
|
-
*/
|
|
499
|
-
const determineNativeType = (
|
|
500
|
-
properties:Properties<Type>
|
|
501
|
-
):NativeInputType =>
|
|
502
|
-
(
|
|
503
|
-
properties.type === 'string' ?
|
|
504
|
-
properties.hidden ?
|
|
505
|
-
'password' :
|
|
506
|
-
'text' :
|
|
507
|
-
transformer[
|
|
508
|
-
properties.type as keyof InputDataTransformation
|
|
509
|
-
]?.type ?? properties.type
|
|
510
|
-
) as NativeInputType
|
|
511
|
-
/**
|
|
512
|
-
* Render help or error texts with current validation state color.
|
|
513
|
-
* @returns Determined renderable markup specification.
|
|
514
|
-
*/
|
|
515
|
-
const renderHelpText = ():ReactElement => <>
|
|
516
|
-
<GenericAnimate
|
|
517
|
-
in={
|
|
518
|
-
properties.selectableEditor &&
|
|
519
|
-
properties.type === 'string' &&
|
|
520
|
-
properties.editor !== 'plain'
|
|
521
|
-
}
|
|
522
|
-
>
|
|
523
|
-
<IconButton
|
|
524
|
-
icon={{
|
|
525
|
-
icon: properties.editorIsActive ?
|
|
526
|
-
'subject' :
|
|
527
|
-
properties.editor.startsWith('code') ?
|
|
528
|
-
'code' :
|
|
529
|
-
'text_format',
|
|
530
|
-
onClick: onChangeEditorIsActive
|
|
531
|
-
}}
|
|
532
|
-
/>
|
|
533
|
-
</GenericAnimate>
|
|
534
|
-
<GenericAnimate in={Boolean(properties.declaration)}>
|
|
535
|
-
<IconButton
|
|
536
|
-
icon={{
|
|
537
|
-
icon:
|
|
538
|
-
'more_' +
|
|
539
|
-
(properties.showDeclaration ? 'vert' : 'horiz'),
|
|
540
|
-
onClick: onChangeShowDeclaration
|
|
541
|
-
}}
|
|
542
|
-
/>
|
|
543
|
-
</GenericAnimate>
|
|
544
|
-
<GenericAnimate in={properties.showDeclaration}>
|
|
545
|
-
{properties.declaration}
|
|
546
|
-
</GenericAnimate>
|
|
547
|
-
<GenericAnimate in={
|
|
548
|
-
!properties.showDeclaration &&
|
|
549
|
-
properties.invalid &&
|
|
550
|
-
(
|
|
551
|
-
properties.showInitialValidationState ||
|
|
552
|
-
/*
|
|
553
|
-
Material inputs show their validation state at
|
|
554
|
-
least after a blur event so we synchronize error
|
|
555
|
-
message appearances.
|
|
556
|
-
*/
|
|
557
|
-
properties.visited
|
|
558
|
-
)
|
|
559
|
-
}>
|
|
560
|
-
<Theme use="error">{renderMessage(
|
|
561
|
-
properties.invalidMaximum &&
|
|
562
|
-
properties.maximumText ||
|
|
563
|
-
properties.invalidMaximumLength &&
|
|
564
|
-
properties.maximumLengthText ||
|
|
565
|
-
properties.invalidMinimum &&
|
|
566
|
-
properties.minimumText ||
|
|
567
|
-
properties.invalidMinimumLength &&
|
|
568
|
-
properties.minimumLengthText ||
|
|
569
|
-
properties.invalidInvertedPattern &&
|
|
570
|
-
properties.invertedPatternText ||
|
|
571
|
-
properties.invalidPattern &&
|
|
572
|
-
properties.patternText ||
|
|
573
|
-
properties.invalidRequired &&
|
|
574
|
-
properties.requiredText
|
|
575
|
-
)}</Theme>
|
|
576
|
-
</GenericAnimate>
|
|
577
|
-
</>
|
|
578
|
-
/**
|
|
579
|
-
* Renders given template string against all properties in current
|
|
580
|
-
* instance.
|
|
581
|
-
* @param template - Template to render.
|
|
582
|
-
*
|
|
583
|
-
* @returns Evaluated template or an empty string if something goes wrong.
|
|
584
|
-
*/
|
|
585
|
-
const renderMessage = (template?:unknown):string => {
|
|
586
|
-
if (typeof template === 'string') {
|
|
587
|
-
const evaluated:EvaluationResult = Tools.stringEvaluate(
|
|
588
|
-
`\`${template}\``,
|
|
589
|
-
{
|
|
590
|
-
formatValue: (value:Type):string =>
|
|
591
|
-
formatValue<Type>(properties, value, transformer),
|
|
592
|
-
...properties
|
|
593
|
-
}
|
|
594
|
-
)
|
|
595
|
-
|
|
596
|
-
if (evaluated.error) {
|
|
597
|
-
console.warn(
|
|
598
|
-
'Given message template could not be proceed:',
|
|
599
|
-
evaluated.error
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
return ''
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return evaluated.result
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
return ''
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Wraps given component with animation component if given condition holds.
|
|
612
|
-
* @param content - Component or string to wrap.
|
|
613
|
-
* @param propertiesOrInCondition - Animation properties or in condition
|
|
614
|
-
* only.
|
|
615
|
-
* @param condition - Show condition.
|
|
616
|
-
*
|
|
617
|
-
* @returns Wrapped component.
|
|
618
|
-
*/
|
|
619
|
-
const wrapAnimationConditionally = (
|
|
620
|
-
content:Renderable,
|
|
621
|
-
propertiesOrInCondition:(
|
|
622
|
-
boolean|Partial<TransitionProps<HTMLElement|undefined>>
|
|
623
|
-
) = {},
|
|
624
|
-
condition = true
|
|
625
|
-
):Renderable => {
|
|
626
|
-
if (typeof propertiesOrInCondition === 'boolean')
|
|
627
|
-
return condition ?
|
|
628
|
-
<GenericAnimate in={propertiesOrInCondition}>
|
|
629
|
-
{content}
|
|
630
|
-
</GenericAnimate> :
|
|
631
|
-
propertiesOrInCondition ? content : ''
|
|
632
|
-
|
|
633
|
-
return condition ?
|
|
634
|
-
<GenericAnimate {...propertiesOrInCondition}>
|
|
635
|
-
{content}
|
|
636
|
-
</GenericAnimate> :
|
|
637
|
-
propertiesOrInCondition.in ? content : ''
|
|
638
|
-
}
|
|
639
|
-
/**
|
|
640
|
-
* If given icon options has an additional tooltip configuration integrate
|
|
641
|
-
* a wrapping tooltip component into given configuration and remove initial
|
|
642
|
-
* tooltip configuration.
|
|
643
|
-
* @param options - Icon configuration potential extended a tooltip
|
|
644
|
-
* configuration.
|
|
645
|
-
*
|
|
646
|
-
* @returns Resolved icon configuration.
|
|
647
|
-
*/
|
|
648
|
-
const wrapIconWithTooltip = (
|
|
649
|
-
options?:Properties['icon']
|
|
650
|
-
):IconOptions|undefined => {
|
|
651
|
-
if (typeof options === 'object' && options?.tooltip) {
|
|
652
|
-
const tooltip:Properties['tooltip'] = options.tooltip
|
|
653
|
-
options = {...options}
|
|
654
|
-
delete options.tooltip
|
|
655
|
-
const nestedOptions:IconOptions = {...options}
|
|
656
|
-
options.strategy = 'component'
|
|
657
|
-
options.icon = <WrapTooltip options={tooltip}>
|
|
658
|
-
<Icon icon={nestedOptions} />
|
|
659
|
-
</WrapTooltip>
|
|
660
|
-
}
|
|
661
|
-
return options as IconOptions|undefined
|
|
662
|
-
}
|
|
663
|
-
/// endregion
|
|
664
|
-
/// region handle cursor selection state
|
|
665
|
-
//// region rich-text editor
|
|
666
|
-
/**
|
|
667
|
-
* Determines absolute offset in given markup.
|
|
668
|
-
* @param contentDomNode - Wrapping dom node where all content is
|
|
669
|
-
* contained.
|
|
670
|
-
* @param domNode - Dom node which contains given position.
|
|
671
|
-
* @param offset - Relative position within given node.
|
|
672
|
-
*
|
|
673
|
-
* @returns Determine absolute offset.
|
|
674
|
-
*/
|
|
675
|
-
const determineAbsoluteSymbolOffsetFromHTML = (
|
|
676
|
-
contentDomNode:Element, domNode:Element, offset:number
|
|
677
|
-
):number => {
|
|
678
|
-
if (!properties.value)
|
|
679
|
-
return 0
|
|
680
|
-
|
|
681
|
-
const indicatorKey = 'generic-input-selection-indicator'
|
|
682
|
-
const indicatorValue = '###'
|
|
683
|
-
const indicator = ` ${indicatorKey}="${indicatorValue}"`
|
|
684
|
-
|
|
685
|
-
domNode.setAttribute(indicatorKey, indicatorValue)
|
|
686
|
-
// NOTE: TinyMCE seems to add a newline after each paragraph.
|
|
687
|
-
const content:string = contentDomNode.innerHTML.replace(
|
|
688
|
-
/(<\/p>)/gi, '$1\n'
|
|
689
|
-
)
|
|
690
|
-
domNode.removeAttribute(indicatorKey)
|
|
691
|
-
|
|
692
|
-
const domNodeOffset:number = content.indexOf(indicator)
|
|
693
|
-
const startIndex:number = domNodeOffset + indicator.length
|
|
694
|
-
|
|
695
|
-
return (
|
|
696
|
-
offset +
|
|
697
|
-
content.indexOf('>', startIndex) +
|
|
698
|
-
1 -
|
|
699
|
-
indicator.length
|
|
700
|
-
)
|
|
701
|
-
}
|
|
702
|
-
//// endregion
|
|
703
|
-
//// region code editor
|
|
704
|
-
/**
|
|
705
|
-
* Determines absolute range from table oriented position.
|
|
706
|
-
* @param column - Symbol offset in given row.
|
|
707
|
-
* @param row - Offset row.
|
|
708
|
-
*
|
|
709
|
-
* @returns Determined offset.
|
|
710
|
-
*/
|
|
711
|
-
const determineAbsoluteSymbolOffsetFromTable = (
|
|
712
|
-
column:number, row:number
|
|
713
|
-
):number => {
|
|
714
|
-
if (typeof properties.value !== 'string' && !properties.value)
|
|
715
|
-
return 0
|
|
716
|
-
|
|
717
|
-
if (row > 0)
|
|
718
|
-
return column + (properties.value as unknown as string)
|
|
719
|
-
.split('\n')
|
|
720
|
-
.slice(0, row)
|
|
721
|
-
.map((line:string):number => 1 + line.length)
|
|
722
|
-
.reduce((sum:number, value:number):number => sum + value)
|
|
723
|
-
return column
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Converts absolute range into table oriented position.
|
|
727
|
-
* @param offset - Absolute position.
|
|
728
|
-
*
|
|
729
|
-
* @returns Position.
|
|
730
|
-
*/
|
|
731
|
-
const determineTablePosition = (offset:number):TablePosition => {
|
|
732
|
-
const result:TablePosition = {column: 0, row: 0}
|
|
733
|
-
|
|
734
|
-
if (typeof properties.value === 'string')
|
|
735
|
-
for (const line of properties.value.split('\n')) {
|
|
736
|
-
if (line.length < offset)
|
|
737
|
-
offset -= 1 + line.length
|
|
738
|
-
else {
|
|
739
|
-
result.column = offset
|
|
740
|
-
break
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
result.row += 1
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
return result
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Sets current cursor selection range in given code editor instance.
|
|
750
|
-
* @param instance - Code editor instance.
|
|
751
|
-
*
|
|
752
|
-
* @returns Nothing.
|
|
753
|
-
*/
|
|
754
|
-
const setCodeEditorSelectionState = (instance:CodeEditorType):void => {
|
|
755
|
-
const range:CodeEditorNamespace.Range =
|
|
756
|
-
instance.editor.selection.getRange()
|
|
757
|
-
const endPosition:TablePosition =
|
|
758
|
-
determineTablePosition(properties.cursor.end)
|
|
759
|
-
range.setEnd(endPosition.row, endPosition.column)
|
|
760
|
-
const startPosition:TablePosition =
|
|
761
|
-
determineTablePosition(properties.cursor.start)
|
|
762
|
-
range.setStart(startPosition.row, startPosition.column)
|
|
763
|
-
instance.editor.selection.setRange(range)
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Sets current cursor selection range in given rich text editor instance.
|
|
767
|
-
* @param instance - Code editor instance.
|
|
768
|
-
*
|
|
769
|
-
* @returns Nothing.
|
|
770
|
-
*/
|
|
771
|
-
const setRichTextEditorSelectionState = (instance:RichTextEditor):void => {
|
|
772
|
-
const indicator:{
|
|
773
|
-
end:string
|
|
774
|
-
start:string
|
|
775
|
-
} = {
|
|
776
|
-
end: '###generic-input-selection-indicator-end###',
|
|
777
|
-
start: '###generic-input-selection-indicator-start###'
|
|
778
|
-
}
|
|
779
|
-
const cursor:CursorState = {
|
|
780
|
-
end: properties.cursor.end + indicator.start.length,
|
|
781
|
-
start: properties.cursor.start
|
|
782
|
-
}
|
|
783
|
-
const keysSorted:Array<keyof typeof indicator> =
|
|
784
|
-
['start', 'end']
|
|
785
|
-
|
|
786
|
-
let value:string = properties.representation as string
|
|
787
|
-
for (const type of keysSorted)
|
|
788
|
-
value = (
|
|
789
|
-
value.substring(0, cursor[type]) +
|
|
790
|
-
indicator[type] +
|
|
791
|
-
value.substring(cursor[type])
|
|
792
|
-
)
|
|
793
|
-
instance.getBody().innerHTML = value
|
|
794
|
-
|
|
795
|
-
const walker:TreeWalker = document.createTreeWalker(
|
|
796
|
-
instance.getBody(), NodeFilter.SHOW_TEXT, null
|
|
797
|
-
)
|
|
798
|
-
|
|
799
|
-
const range:Range = instance.dom.createRng()
|
|
800
|
-
const result:{
|
|
801
|
-
end?:[Node, number]
|
|
802
|
-
start?:[Node, number]
|
|
803
|
-
} = {}
|
|
804
|
-
|
|
805
|
-
let node:Node|null
|
|
806
|
-
while (node = walker.nextNode())
|
|
807
|
-
for (const type of keysSorted)
|
|
808
|
-
if (node.nodeValue) {
|
|
809
|
-
const index:number =
|
|
810
|
-
node.nodeValue.indexOf(indicator[type])
|
|
811
|
-
if (index > -1) {
|
|
812
|
-
node.nodeValue = node.nodeValue.replace(
|
|
813
|
-
indicator[type], ''
|
|
814
|
-
)
|
|
815
|
-
|
|
816
|
-
result[type] = [node, index]
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
for (const type of keysSorted)
|
|
821
|
-
if (result[type])
|
|
822
|
-
range[
|
|
823
|
-
`set${Tools.stringCapitalize(type)}` as 'setEnd'|'setStart'
|
|
824
|
-
](...(result[type] as [Node, number]))
|
|
825
|
-
|
|
826
|
-
if (result.end && result.start)
|
|
827
|
-
instance.selection.setRng(range)
|
|
828
|
-
}
|
|
829
|
-
//// endregion
|
|
830
|
-
/**
|
|
831
|
-
* Saves current selection/cursor state in components state.
|
|
832
|
-
* @param event - Event which triggered selection change.
|
|
833
|
-
*
|
|
834
|
-
* @returns Nothing.
|
|
835
|
-
*/
|
|
836
|
-
const saveSelectionState = (event:GenericEvent):void => {
|
|
837
|
-
/*
|
|
838
|
-
NOTE: Known issues is that we do not get the absolute positions but
|
|
839
|
-
the one in current selected node.
|
|
840
|
-
*/
|
|
841
|
-
const codeEditorRange =
|
|
842
|
-
codeEditorReference.current?.editor?.selection?.getRange()
|
|
843
|
-
const richTextEditorRange =
|
|
844
|
-
richTextEditorInstance.current?.selection?.getRng()
|
|
845
|
-
const selectionEnd:null|number = (
|
|
846
|
-
inputReference.current as HTMLInputElement|HTMLTextAreaElement
|
|
847
|
-
)?.selectionEnd
|
|
848
|
-
const selectionStart:null|number = (
|
|
849
|
-
inputReference.current as HTMLInputElement|HTMLTextAreaElement
|
|
850
|
-
)?.selectionStart
|
|
851
|
-
if (codeEditorRange)
|
|
852
|
-
setCursor({
|
|
853
|
-
end: determineAbsoluteSymbolOffsetFromTable(
|
|
854
|
-
codeEditorRange.end.column,
|
|
855
|
-
typeof codeEditorRange.end.row === 'number' ?
|
|
856
|
-
codeEditorRange.end.row :
|
|
857
|
-
typeof properties.value === 'string' ?
|
|
858
|
-
properties.value.split('\n').length - 1 :
|
|
859
|
-
0
|
|
860
|
-
),
|
|
861
|
-
start: determineAbsoluteSymbolOffsetFromTable(
|
|
862
|
-
codeEditorRange.start.column,
|
|
863
|
-
typeof codeEditorRange.start.row === 'number' ?
|
|
864
|
-
codeEditorRange.start.row :
|
|
865
|
-
typeof properties.value === 'string' ?
|
|
866
|
-
properties.value.split('\n').length - 1 :
|
|
867
|
-
0
|
|
868
|
-
)
|
|
869
|
-
})
|
|
870
|
-
else if (richTextEditorRange)
|
|
871
|
-
setCursor({
|
|
872
|
-
end: determineAbsoluteSymbolOffsetFromHTML(
|
|
873
|
-
richTextEditorInstance.current!.getBody(),
|
|
874
|
-
richTextEditorInstance.current!.selection.getEnd(),
|
|
875
|
-
richTextEditorRange.endOffset
|
|
876
|
-
),
|
|
877
|
-
start: determineAbsoluteSymbolOffsetFromHTML(
|
|
878
|
-
richTextEditorInstance.current!.getBody(),
|
|
879
|
-
richTextEditorInstance.current!.selection.getStart(),
|
|
880
|
-
richTextEditorRange.startOffset
|
|
881
|
-
)
|
|
882
|
-
})
|
|
883
|
-
else if (
|
|
884
|
-
typeof selectionEnd === 'number' &&
|
|
885
|
-
typeof selectionStart === 'number'
|
|
886
|
-
) {
|
|
887
|
-
const add:0|1|-1 =
|
|
888
|
-
(event as unknown as KeyboardEvent)?.key?.length === 1 ?
|
|
889
|
-
1 :
|
|
890
|
-
(event as unknown as KeyboardEvent)?.key === 'Backspace' &&
|
|
891
|
-
(
|
|
892
|
-
(properties.representation as string).length >
|
|
893
|
-
selectionStart
|
|
894
|
-
) ?
|
|
895
|
-
-1 :
|
|
896
|
-
0
|
|
897
|
-
|
|
898
|
-
setCursor({end: selectionEnd + add, start: selectionStart + add})
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
/// endregion
|
|
902
|
-
/// region property aggregation
|
|
903
|
-
const deriveMissingPropertiesFromState = () => {
|
|
904
|
-
if (
|
|
905
|
-
givenProperties.cursor === null ||
|
|
906
|
-
typeof givenProperties.cursor !== 'object'
|
|
907
|
-
)
|
|
908
|
-
givenProperties.cursor = {} as CursorState
|
|
909
|
-
if (givenProperties.cursor.end === undefined)
|
|
910
|
-
givenProperties.cursor.end = cursor.end
|
|
911
|
-
if (givenProperties.cursor.start === undefined)
|
|
912
|
-
givenProperties.cursor.start = cursor.start
|
|
913
|
-
|
|
914
|
-
if (givenProperties.editorIsActive === undefined)
|
|
915
|
-
givenProperties.editorIsActive = editorState.editorIsActive
|
|
916
|
-
|
|
917
|
-
if (givenProperties.hidden === undefined)
|
|
918
|
-
givenProperties.hidden = hidden
|
|
919
|
-
|
|
920
|
-
/*
|
|
921
|
-
NOTE: This logic is important to re-determine representation when a
|
|
922
|
-
new value is provided via properties.
|
|
923
|
-
*/
|
|
924
|
-
if (givenProperties.representation === undefined)
|
|
925
|
-
givenProperties.representation = valueState.representation
|
|
926
|
-
|
|
927
|
-
if (givenProperties.showDeclaration === undefined)
|
|
928
|
-
givenProperties.showDeclaration = showDeclaration
|
|
929
|
-
|
|
930
|
-
deriveMissingBasePropertiesFromState<Props<Type>, ValueState<Type>>(
|
|
931
|
-
givenProperties, valueState
|
|
932
|
-
)
|
|
933
|
-
|
|
934
|
-
if (givenProperties.value === undefined) {
|
|
935
|
-
if (
|
|
936
|
-
givenProperties.representation === undefined &&
|
|
937
|
-
givenProperties.model!.value === undefined
|
|
938
|
-
)
|
|
939
|
-
givenProperties.representation = valueState.representation
|
|
940
|
-
} else if (
|
|
941
|
-
!representationControlled &&
|
|
942
|
-
givenProperties.value !== valueState.value
|
|
943
|
-
)
|
|
944
|
-
/*
|
|
945
|
-
NOTE: Set representation to "undefined" to trigger to derive
|
|
946
|
-
current representation from current value.
|
|
947
|
-
*/
|
|
948
|
-
givenProperties.representation = undefined
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Synchronizes property, state and model configuration:
|
|
952
|
-
* Properties overwrites default properties which overwrites default model
|
|
953
|
-
* properties.
|
|
954
|
-
* @param properties - Properties to merge.
|
|
955
|
-
*
|
|
956
|
-
* @returns Nothing.
|
|
957
|
-
*/
|
|
958
|
-
const mapPropertiesAndValidationStateIntoModel = (
|
|
959
|
-
properties:Props<Type>
|
|
960
|
-
):DefaultProperties<Type> => {
|
|
961
|
-
const result:DefaultProperties<Type> =
|
|
962
|
-
mapPropertiesIntoModel<Props<Type>, DefaultProperties<Type>>(
|
|
963
|
-
properties,
|
|
964
|
-
GenericInput.defaultProperties.model as unknown as Model<Type>
|
|
965
|
-
)
|
|
966
|
-
|
|
967
|
-
result.model.value = parseValue<Type>(
|
|
968
|
-
result, result.model.value as null|Type, transformer
|
|
969
|
-
)
|
|
970
|
-
|
|
971
|
-
determineValidationState<Type>(result, result.model.state)
|
|
972
|
-
|
|
973
|
-
return result
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* Calculate external properties (a set of all configurable properties).
|
|
977
|
-
* @param properties - Properties to merge.
|
|
978
|
-
*
|
|
979
|
-
* @returns External properties object.
|
|
980
|
-
*/
|
|
981
|
-
const getConsolidatedProperties = (
|
|
982
|
-
properties:Props<Type>
|
|
983
|
-
):Properties<Type> => {
|
|
984
|
-
const result:Properties<Type> =
|
|
985
|
-
getBaseConsolidatedProperties<Props<Type>, Properties<Type>>(
|
|
986
|
-
mapPropertiesAndValidationStateIntoModel(properties) as
|
|
987
|
-
Props<Type>
|
|
988
|
-
)
|
|
989
|
-
|
|
990
|
-
if (!result.selection && result.type === 'boolean')
|
|
991
|
-
// NOTE: Select-Fields restricts values to strings.
|
|
992
|
-
result.selection = [
|
|
993
|
-
{label: 'No', value: false as unknown as string},
|
|
994
|
-
{label: 'Yes', value: true as unknown as string}
|
|
995
|
-
]
|
|
996
|
-
|
|
997
|
-
// NOTE: If only an editor is specified it should be displayed.
|
|
998
|
-
if (!(result.selectableEditor || result.editor === 'plain'))
|
|
999
|
-
result.editorIsActive = true
|
|
1000
|
-
|
|
1001
|
-
if (typeof result.representation !== 'string') {
|
|
1002
|
-
result.representation = formatValue<Type>(
|
|
1003
|
-
result,
|
|
1004
|
-
result.value as null|Type,
|
|
1005
|
-
transformer,
|
|
1006
|
-
/*
|
|
1007
|
-
NOTE: Handle two cases:
|
|
1008
|
-
|
|
1009
|
-
1. Representation has to be determine initially
|
|
1010
|
-
(-> usually no focus).
|
|
1011
|
-
2. Representation was set from the outside
|
|
1012
|
-
(-> usually no focus).
|
|
1013
|
-
*/
|
|
1014
|
-
!result.focused
|
|
1015
|
-
)
|
|
1016
|
-
/*
|
|
1017
|
-
NOTE: We will try to restore last known selection state if
|
|
1018
|
-
representation has been modified.
|
|
1019
|
-
*/
|
|
1020
|
-
if (
|
|
1021
|
-
result.focused &&
|
|
1022
|
-
result.representation !== result.value as unknown as string &&
|
|
1023
|
-
['password', 'text'].includes(determineNativeType(result))
|
|
1024
|
-
)
|
|
1025
|
-
selectionIsUnstable = true
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
return result
|
|
1029
|
-
}
|
|
1030
|
-
/// endregion
|
|
1031
|
-
/// region reference setter
|
|
1032
|
-
/**
|
|
1033
|
-
* Set code editor references.
|
|
1034
|
-
* @param instance - Code editor instance.
|
|
1035
|
-
*
|
|
1036
|
-
* @returns Nothing.
|
|
1037
|
-
*/
|
|
1038
|
-
const setCodeEditorReference = (instance:CodeEditorType|null):void => {
|
|
1039
|
-
codeEditorReference.current = instance
|
|
1040
|
-
|
|
1041
|
-
if (codeEditorReference.current?.editor?.container?.querySelector(
|
|
1042
|
-
'textarea'
|
|
1043
|
-
))
|
|
1044
|
-
codeEditorInputReference.current =
|
|
1045
|
-
codeEditorReference.current.editor.container
|
|
1046
|
-
.querySelector('textarea')
|
|
1047
|
-
|
|
1048
|
-
if (
|
|
1049
|
-
codeEditorReference.current &&
|
|
1050
|
-
properties.editorIsActive &&
|
|
1051
|
-
editorState.selectionIsUnstable
|
|
1052
|
-
) {
|
|
1053
|
-
(codeEditorReference.current.editor.textInput as
|
|
1054
|
-
unknown as
|
|
1055
|
-
HTMLInputElement
|
|
1056
|
-
).focus()
|
|
1057
|
-
setCodeEditorSelectionState(codeEditorReference.current)
|
|
1058
|
-
setEditorState({...editorState, selectionIsUnstable: false})
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Set rich text editor references.
|
|
1063
|
-
* @param instance - Editor instance.
|
|
1064
|
-
*
|
|
1065
|
-
* @returns Nothing.
|
|
1066
|
-
*/
|
|
1067
|
-
const setRichTextEditorReference = (
|
|
1068
|
-
instance:null|RichTextEditorComponent
|
|
1069
|
-
):void => {
|
|
1070
|
-
richTextEditorReference.current = instance
|
|
1071
|
-
|
|
1072
|
-
/*
|
|
1073
|
-
Refer inner element here is possible but marked as private.
|
|
1074
|
-
|
|
1075
|
-
if (richTextEditorReference.current?.elementRef)
|
|
1076
|
-
richTextEditorInputReference.current =
|
|
1077
|
-
richTextEditorReference.current.elementRef
|
|
1078
|
-
*/
|
|
1079
|
-
}
|
|
1080
|
-
/// endregion
|
|
1081
|
-
// endregion
|
|
1082
|
-
// region event handler
|
|
1083
|
-
/**
|
|
1084
|
-
* Triggered on blur events.
|
|
1085
|
-
* @param event - Event object.
|
|
1086
|
-
*
|
|
1087
|
-
* @returns Nothing.
|
|
1088
|
-
*/
|
|
1089
|
-
const onBlur = (event:GenericEvent):void => setValueState((
|
|
1090
|
-
oldValueState:ValueState<Type, ModelState>
|
|
1091
|
-
):ValueState<Type, ModelState> => {
|
|
1092
|
-
setIsSuggestionOpen(false)
|
|
1093
|
-
|
|
1094
|
-
let changed = false
|
|
1095
|
-
let stateChanged = false
|
|
1096
|
-
|
|
1097
|
-
if (oldValueState.modelState.focused) {
|
|
1098
|
-
properties.focused = false
|
|
1099
|
-
changed = true
|
|
1100
|
-
stateChanged = true
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
if (!oldValueState.modelState.visited) {
|
|
1104
|
-
properties.visited = true
|
|
1105
|
-
changed = true
|
|
1106
|
-
stateChanged = true
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
if (!useSuggestions || properties.suggestSelection) {
|
|
1110
|
-
const candidate:null|Type = getValueFromSelection<Type>(
|
|
1111
|
-
properties.representation, normalizedSelection!
|
|
1112
|
-
)
|
|
1113
|
-
if (candidate === null) {
|
|
1114
|
-
properties.value = parseValue<Type>(
|
|
1115
|
-
properties, properties.value as null|Type, transformer
|
|
1116
|
-
)
|
|
1117
|
-
properties.representation = formatValue<Type>(
|
|
1118
|
-
properties, properties.value, transformer
|
|
1119
|
-
)
|
|
1120
|
-
} else
|
|
1121
|
-
properties.value = candidate
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
if (
|
|
1125
|
-
!Tools.equals(oldValueState.value, properties.value) ||
|
|
1126
|
-
oldValueState.representation !== properties.representation
|
|
1127
|
-
)
|
|
1128
|
-
changed = true
|
|
1129
|
-
|
|
1130
|
-
if (changed)
|
|
1131
|
-
onChange(event)
|
|
1132
|
-
|
|
1133
|
-
if (!Tools.equals(oldValueState.value, properties.value))
|
|
1134
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1135
|
-
properties,
|
|
1136
|
-
'changeValue',
|
|
1137
|
-
controlled,
|
|
1138
|
-
properties.value,
|
|
1139
|
-
event,
|
|
1140
|
-
properties
|
|
1141
|
-
)
|
|
1142
|
-
|
|
1143
|
-
if (stateChanged)
|
|
1144
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1145
|
-
properties,
|
|
1146
|
-
'changeState',
|
|
1147
|
-
controlled,
|
|
1148
|
-
properties.model.state,
|
|
1149
|
-
event,
|
|
1150
|
-
properties
|
|
1151
|
-
)
|
|
1152
|
-
|
|
1153
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1154
|
-
properties, 'blur', controlled, event, properties
|
|
1155
|
-
)
|
|
1156
|
-
|
|
1157
|
-
return changed ?
|
|
1158
|
-
{
|
|
1159
|
-
modelState: properties.model.state,
|
|
1160
|
-
representation: properties.representation,
|
|
1161
|
-
value: properties.value as null|Type
|
|
1162
|
-
} :
|
|
1163
|
-
oldValueState
|
|
1164
|
-
})
|
|
1165
|
-
/**
|
|
1166
|
-
* Triggered on any change events. Consolidates properties object and
|
|
1167
|
-
* triggers given on change callbacks.
|
|
1168
|
-
* @param event - Potential event object.
|
|
1169
|
-
*
|
|
1170
|
-
* @returns Nothing.
|
|
1171
|
-
*/
|
|
1172
|
-
const onChange = (event?:GenericEvent):void => {
|
|
1173
|
-
Tools.extend(
|
|
1174
|
-
true,
|
|
1175
|
-
properties,
|
|
1176
|
-
getConsolidatedProperties(
|
|
1177
|
-
/*
|
|
1178
|
-
Workaround since "Type" isn't identified as subset of
|
|
1179
|
-
"RecursivePartial<Type>" yet.
|
|
1180
|
-
*/
|
|
1181
|
-
properties as unknown as Props<Type>
|
|
1182
|
-
)
|
|
1183
|
-
)
|
|
1184
|
-
|
|
1185
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1186
|
-
properties, 'change', controlled, properties, event
|
|
1187
|
-
)
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Triggered when editor is active indicator should be changed.
|
|
1191
|
-
* @param event - Mouse event object.
|
|
1192
|
-
*
|
|
1193
|
-
* @returns Nothing.
|
|
1194
|
-
*/
|
|
1195
|
-
const onChangeEditorIsActive = (event?:ReactMouseEvent):void => {
|
|
1196
|
-
if (event) {
|
|
1197
|
-
event.preventDefault()
|
|
1198
|
-
event.stopPropagation()
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
setEditorState(({editorIsActive}):EditorState => {
|
|
1202
|
-
properties.editorIsActive = !editorIsActive
|
|
1203
|
-
|
|
1204
|
-
onChange(event)
|
|
1205
|
-
|
|
1206
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1207
|
-
properties,
|
|
1208
|
-
'changeEditorIsActive',
|
|
1209
|
-
controlled,
|
|
1210
|
-
properties.editorIsActive,
|
|
1211
|
-
event,
|
|
1212
|
-
properties
|
|
1213
|
-
)
|
|
1214
|
-
|
|
1215
|
-
return {
|
|
1216
|
-
editorIsActive: properties.editorIsActive,
|
|
1217
|
-
selectionIsUnstable: true
|
|
1218
|
-
}
|
|
1219
|
-
})
|
|
1220
|
-
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Triggered when show declaration indicator should be changed.
|
|
1223
|
-
* @param event - Potential event object.
|
|
1224
|
-
*
|
|
1225
|
-
* @returns Nothing.
|
|
1226
|
-
*/
|
|
1227
|
-
const onChangeShowDeclaration = (event?:ReactMouseEvent):void => {
|
|
1228
|
-
if (event) {
|
|
1229
|
-
event.preventDefault()
|
|
1230
|
-
event.stopPropagation()
|
|
1231
|
-
}
|
|
1232
|
-
setShowDeclaration((value:boolean):boolean => {
|
|
1233
|
-
properties.showDeclaration = !value
|
|
1234
|
-
|
|
1235
|
-
onChange(event)
|
|
1236
|
-
|
|
1237
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1238
|
-
properties,
|
|
1239
|
-
'changeShowDeclaration',
|
|
1240
|
-
controlled,
|
|
1241
|
-
properties.showDeclaration,
|
|
1242
|
-
event,
|
|
1243
|
-
properties
|
|
1244
|
-
)
|
|
1245
|
-
|
|
1246
|
-
return properties.showDeclaration
|
|
1247
|
-
})
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Triggered when ever the value changes.
|
|
1251
|
-
* Takes a given value or determines it from given event object and
|
|
1252
|
-
* generates new value state (internal value, representation and validation
|
|
1253
|
-
* states). Derived event handler will be triggered when internal state
|
|
1254
|
-
* has been consolidated.
|
|
1255
|
-
* @param eventOrValue - Event object or new value.
|
|
1256
|
-
* @param editorInstance - Potential editor instance if triggered from a
|
|
1257
|
-
* rich text or code editor.
|
|
1258
|
-
* @param selectedIndex - Indicates whether given event was triggered by a
|
|
1259
|
-
* selection.
|
|
1260
|
-
*
|
|
1261
|
-
* @returns Nothing.
|
|
1262
|
-
*/
|
|
1263
|
-
const onChangeValue = (
|
|
1264
|
-
eventOrValue:GenericEvent|null|Type,
|
|
1265
|
-
editorInstance?:RichTextEditor,
|
|
1266
|
-
selectedIndex = -1
|
|
1267
|
-
):void => {
|
|
1268
|
-
if (properties.disabled)
|
|
1269
|
-
return
|
|
1270
|
-
|
|
1271
|
-
setIsSuggestionOpen(true)
|
|
1272
|
-
|
|
1273
|
-
let event:GenericEvent|undefined
|
|
1274
|
-
if (eventOrValue !== null && typeof eventOrValue === 'object') {
|
|
1275
|
-
const target:HTMLInputElement|null|undefined =
|
|
1276
|
-
(eventOrValue as GenericEvent).target as HTMLInputElement ||
|
|
1277
|
-
(eventOrValue as GenericEvent).detail as HTMLInputElement
|
|
1278
|
-
if (target)
|
|
1279
|
-
/*
|
|
1280
|
-
NOTE: Enhanced select fields (menus) do not provide the
|
|
1281
|
-
selected value but index.
|
|
1282
|
-
*/
|
|
1283
|
-
if (typeof (
|
|
1284
|
-
target as unknown as {index:number}
|
|
1285
|
-
).index === 'number') {
|
|
1286
|
-
const index:number = (
|
|
1287
|
-
target as unknown as {index:number}
|
|
1288
|
-
).index - (properties.placeholder ? 1 : 0)
|
|
1289
|
-
properties.value =
|
|
1290
|
-
index >= 0 &&
|
|
1291
|
-
index < suggestionValues.length ?
|
|
1292
|
-
suggestionValues[index] as Type :
|
|
1293
|
-
null
|
|
1294
|
-
} else
|
|
1295
|
-
properties.value = typeof target.value === 'undefined' ?
|
|
1296
|
-
null :
|
|
1297
|
-
target.value as unknown as Type
|
|
1298
|
-
else
|
|
1299
|
-
properties.value = eventOrValue as null|Type
|
|
1300
|
-
} else
|
|
1301
|
-
properties.value = eventOrValue
|
|
1302
|
-
|
|
1303
|
-
const setHelper = ():void => setValueState((
|
|
1304
|
-
oldValueState:ValueState<Type, ModelState>
|
|
1305
|
-
):ValueState<Type, ModelState> => {
|
|
1306
|
-
if (
|
|
1307
|
-
!representationControlled &&
|
|
1308
|
-
oldValueState.representation === properties.representation &&
|
|
1309
|
-
/*
|
|
1310
|
-
NOTE: Unstable intermediate states have to be synced of a
|
|
1311
|
-
suggestion creator was pending.
|
|
1312
|
-
*/
|
|
1313
|
-
!properties.suggestionCreator &&
|
|
1314
|
-
selectedIndex === -1
|
|
1315
|
-
)
|
|
1316
|
-
/*
|
|
1317
|
-
NOTE: No representation update and no controlled value or
|
|
1318
|
-
representation:
|
|
1319
|
-
|
|
1320
|
-
-> No value update
|
|
1321
|
-
-> No state update
|
|
1322
|
-
-> Nothing to trigger
|
|
1323
|
-
*/
|
|
1324
|
-
return oldValueState
|
|
1325
|
-
|
|
1326
|
-
const valueState:ValueState<Type, ModelState> = {
|
|
1327
|
-
...oldValueState, representation: properties.representation
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
if (
|
|
1331
|
-
!controlled &&
|
|
1332
|
-
Tools.equals(oldValueState.value, properties.value)
|
|
1333
|
-
)
|
|
1334
|
-
/*
|
|
1335
|
-
NOTE: No value update and no controlled value:
|
|
1336
|
-
|
|
1337
|
-
-> No state update
|
|
1338
|
-
-> Nothing to trigger
|
|
1339
|
-
*/
|
|
1340
|
-
return valueState
|
|
1341
|
-
|
|
1342
|
-
valueState.value = properties.value as null|Type
|
|
1343
|
-
|
|
1344
|
-
let stateChanged = false
|
|
1345
|
-
|
|
1346
|
-
if (oldValueState.modelState.pristine) {
|
|
1347
|
-
properties.dirty = true
|
|
1348
|
-
properties.pristine = false
|
|
1349
|
-
stateChanged = true
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
onChange(event)
|
|
1353
|
-
|
|
1354
|
-
if (determineValidationState<Type>(
|
|
1355
|
-
properties as DefaultProperties<Type>, oldValueState.modelState
|
|
1356
|
-
))
|
|
1357
|
-
stateChanged = true
|
|
1358
|
-
|
|
1359
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1360
|
-
properties,
|
|
1361
|
-
'changeValue',
|
|
1362
|
-
controlled,
|
|
1363
|
-
properties.value,
|
|
1364
|
-
event,
|
|
1365
|
-
properties
|
|
1366
|
-
)
|
|
1367
|
-
|
|
1368
|
-
if (stateChanged) {
|
|
1369
|
-
valueState.modelState = properties.model.state
|
|
1370
|
-
|
|
1371
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1372
|
-
properties,
|
|
1373
|
-
'changeState',
|
|
1374
|
-
controlled,
|
|
1375
|
-
properties.model.state,
|
|
1376
|
-
event,
|
|
1377
|
-
properties
|
|
1378
|
-
)
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
if (useSelection || selectedIndex !== -1)
|
|
1382
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1383
|
-
properties,
|
|
1384
|
-
'select',
|
|
1385
|
-
controlled,
|
|
1386
|
-
event,
|
|
1387
|
-
properties
|
|
1388
|
-
)
|
|
1389
|
-
|
|
1390
|
-
return valueState
|
|
1391
|
-
})
|
|
1392
|
-
|
|
1393
|
-
properties.representation = selectedIndex !== -1 ?
|
|
1394
|
-
currentSuggestionLabels[selectedIndex] :
|
|
1395
|
-
typeof properties.value === 'string' ?
|
|
1396
|
-
properties.value :
|
|
1397
|
-
formatValue<Type>(properties, properties.value, transformer)
|
|
1398
|
-
|
|
1399
|
-
if (!useSuggestions) {
|
|
1400
|
-
properties.value =
|
|
1401
|
-
parseValue<Type>(properties, properties.value, transformer)
|
|
1402
|
-
|
|
1403
|
-
setHelper()
|
|
1404
|
-
} else if (properties.suggestionCreator) {
|
|
1405
|
-
const abortController:AbortController = new AbortController()
|
|
1406
|
-
|
|
1407
|
-
const onResultsRetrieved = (
|
|
1408
|
-
results:Properties['selection']
|
|
1409
|
-
):void => {
|
|
1410
|
-
if (abortController.signal.aborted)
|
|
1411
|
-
return
|
|
1412
|
-
|
|
1413
|
-
/*
|
|
1414
|
-
NOTE: A synchronous retrieved selection may has to stop a
|
|
1415
|
-
pending (slower) asynchronous request.
|
|
1416
|
-
*/
|
|
1417
|
-
setSelection((
|
|
1418
|
-
oldSelection:AbortController|Properties['selection']
|
|
1419
|
-
):Properties['selection'] => {
|
|
1420
|
-
if (
|
|
1421
|
-
oldSelection instanceof AbortController &&
|
|
1422
|
-
!oldSelection.signal.aborted
|
|
1423
|
-
)
|
|
1424
|
-
oldSelection.abort()
|
|
1425
|
-
|
|
1426
|
-
return results
|
|
1427
|
-
})
|
|
1428
|
-
|
|
1429
|
-
if (selectedIndex === -1) {
|
|
1430
|
-
const result:null|Type = getValueFromSelection<Type>(
|
|
1431
|
-
properties.representation, normalizeSelection(results)!
|
|
1432
|
-
)
|
|
1433
|
-
|
|
1434
|
-
if (result !== null || properties.searchSelection)
|
|
1435
|
-
properties.value = result
|
|
1436
|
-
else
|
|
1437
|
-
properties.value = parseValue<Type>(
|
|
1438
|
-
properties,
|
|
1439
|
-
properties.representation as unknown as null|Type,
|
|
1440
|
-
transformer
|
|
1441
|
-
)
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
setHelper()
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
/*
|
|
1448
|
-
Trigger asynchronous suggestions retrieving and delayed state
|
|
1449
|
-
consolidation.
|
|
1450
|
-
*/
|
|
1451
|
-
const result:(
|
|
1452
|
-
Properties['selection']|Promise<Properties['selection']>
|
|
1453
|
-
) = properties.suggestionCreator({
|
|
1454
|
-
abortController,
|
|
1455
|
-
properties,
|
|
1456
|
-
query: properties.representation as string
|
|
1457
|
-
})
|
|
1458
|
-
|
|
1459
|
-
if ((result as Promise<Properties['selection']>)?.then) {
|
|
1460
|
-
setSelection((
|
|
1461
|
-
oldSelection:AbortController|Properties['selection']
|
|
1462
|
-
):AbortController => {
|
|
1463
|
-
if (
|
|
1464
|
-
oldSelection instanceof AbortController &&
|
|
1465
|
-
!oldSelection.signal.aborted
|
|
1466
|
-
)
|
|
1467
|
-
oldSelection.abort()
|
|
1468
|
-
|
|
1469
|
-
return abortController
|
|
1470
|
-
})
|
|
1471
|
-
/*
|
|
1472
|
-
NOTE: Immediate sync current representation to maintain
|
|
1473
|
-
cursor state.
|
|
1474
|
-
*/
|
|
1475
|
-
setValueState((
|
|
1476
|
-
oldValueState:ValueState<Type, ModelState>
|
|
1477
|
-
):ValueState<Type, ModelState> => ({
|
|
1478
|
-
...oldValueState, representation: properties.representation
|
|
1479
|
-
}))
|
|
1480
|
-
|
|
1481
|
-
;(result as Promise<Properties['selection']>).then(
|
|
1482
|
-
onResultsRetrieved,
|
|
1483
|
-
/*
|
|
1484
|
-
NOTE: Avoid to through an exception when aborting the
|
|
1485
|
-
request intentionally.
|
|
1486
|
-
*/
|
|
1487
|
-
Tools.noop
|
|
1488
|
-
)
|
|
1489
|
-
} else
|
|
1490
|
-
onResultsRetrieved(result as Properties['selection'])
|
|
1491
|
-
} else {
|
|
1492
|
-
if (selectedIndex === -1) {
|
|
1493
|
-
/*
|
|
1494
|
-
Map value from given selections and trigger state
|
|
1495
|
-
consolidation.
|
|
1496
|
-
*/
|
|
1497
|
-
const result:null|Type = getValueFromSelection<Type>(
|
|
1498
|
-
properties.representation, normalizedSelection!
|
|
1499
|
-
)
|
|
1500
|
-
|
|
1501
|
-
if (result !== null || properties.searchSelection)
|
|
1502
|
-
properties.value = result
|
|
1503
|
-
else
|
|
1504
|
-
properties.value = parseValue<Type>(
|
|
1505
|
-
properties,
|
|
1506
|
-
properties.representation as unknown as null|Type,
|
|
1507
|
-
transformer
|
|
1508
|
-
)
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
setHelper()
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
/**
|
|
1515
|
-
* Triggered on click events.
|
|
1516
|
-
* @param event - Mouse event object.
|
|
1517
|
-
*
|
|
1518
|
-
* @returns Nothing.
|
|
1519
|
-
*/
|
|
1520
|
-
const onClick = (event:ReactMouseEvent):void => {
|
|
1521
|
-
onSelectionChange(event)
|
|
1522
|
-
|
|
1523
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1524
|
-
properties, 'click', controlled, event, properties
|
|
1525
|
-
)
|
|
1526
|
-
|
|
1527
|
-
onTouch(event)
|
|
1528
|
-
}
|
|
1529
|
-
/**
|
|
1530
|
-
* Triggered on focus events and opens suggestions.
|
|
1531
|
-
* @param event - Focus event object.
|
|
1532
|
-
*
|
|
1533
|
-
* @returns Nothing.
|
|
1534
|
-
*/
|
|
1535
|
-
const triggerOnFocusAndOpenSuggestions = (event:ReactFocusEvent):void => {
|
|
1536
|
-
setIsSuggestionOpen(true)
|
|
1537
|
-
|
|
1538
|
-
onFocus(event)
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Triggered on focus events.
|
|
1542
|
-
* @param event - Focus event object.
|
|
1543
|
-
*
|
|
1544
|
-
* @returns Nothing.
|
|
1545
|
-
*/
|
|
1546
|
-
const onFocus = (event:ReactFocusEvent):void => {
|
|
1547
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1548
|
-
properties, 'focus', controlled, event, properties
|
|
1549
|
-
)
|
|
1550
|
-
|
|
1551
|
-
onTouch(event)
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Triggered on down up events.
|
|
1555
|
-
* @param event - Key up event object.
|
|
1556
|
-
*
|
|
1557
|
-
* @returns Nothing.
|
|
1558
|
-
*/
|
|
1559
|
-
const onKeyDown = (event:ReactKeyboardEvent):void => {
|
|
1560
|
-
if (useSuggestions && Tools.keyCode.DOWN === event.keyCode)
|
|
1561
|
-
suggestionMenuAPIReference.current?.focusItemAtIndex(0)
|
|
1562
|
-
|
|
1563
|
-
/*
|
|
1564
|
-
NOTE: We do not want to forward keydown enter events coming from
|
|
1565
|
-
textareas.
|
|
1566
|
-
*/
|
|
1567
|
-
if (properties.type === 'string' && properties.editor !== 'plain')
|
|
1568
|
-
preventEnterKeyPropagation(event)
|
|
1569
|
-
|
|
1570
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1571
|
-
properties, 'keyDown', controlled, event, properties
|
|
1572
|
-
)
|
|
1573
|
-
}
|
|
1574
|
-
/**
|
|
1575
|
-
* Triggered on key up events.
|
|
1576
|
-
* @param event - Key up event object.
|
|
1577
|
-
*
|
|
1578
|
-
* @returns Nothing.
|
|
1579
|
-
*/
|
|
1580
|
-
const onKeyUp = (event:ReactKeyboardEvent):void => {
|
|
1581
|
-
// NOTE: Avoid breaking password-filler on non textarea fields!
|
|
1582
|
-
if (event.keyCode) {
|
|
1583
|
-
onSelectionChange(event)
|
|
1584
|
-
|
|
1585
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1586
|
-
properties, 'keyUp', controlled, event, properties
|
|
1587
|
-
)
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
/**
|
|
1591
|
-
* Triggered on selection change events.
|
|
1592
|
-
* @param event - Event which triggered selection change.
|
|
1593
|
-
*
|
|
1594
|
-
* @returns Nothing.
|
|
1595
|
-
*/
|
|
1596
|
-
const onSelectionChange = (event:GenericEvent):void => {
|
|
1597
|
-
saveSelectionState(event)
|
|
1598
|
-
|
|
1599
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1600
|
-
properties, 'selectionChange', controlled, event, properties
|
|
1601
|
-
)
|
|
1602
|
-
}
|
|
1603
|
-
/**
|
|
1604
|
-
* Triggers on start interacting with the input.
|
|
1605
|
-
* @param event - Event object which triggered interaction.
|
|
1606
|
-
*
|
|
1607
|
-
* @returns Nothing.
|
|
1608
|
-
*/
|
|
1609
|
-
const onTouch = (event:ReactFocusEvent|ReactMouseEvent):void =>
|
|
1610
|
-
setValueState((
|
|
1611
|
-
oldValueState:ValueState<Type, ModelState>
|
|
1612
|
-
):ValueState<Type, ModelState> => {
|
|
1613
|
-
let changedState = false
|
|
1614
|
-
|
|
1615
|
-
if (!oldValueState.modelState.focused) {
|
|
1616
|
-
properties.focused = true
|
|
1617
|
-
changedState = true
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
if (oldValueState.modelState.untouched) {
|
|
1621
|
-
properties.touched = true
|
|
1622
|
-
properties.untouched = false
|
|
1623
|
-
changedState = true
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
let result:ValueState<Type, ModelState> = oldValueState
|
|
1627
|
-
|
|
1628
|
-
if (changedState) {
|
|
1629
|
-
onChange(event)
|
|
1630
|
-
|
|
1631
|
-
result = {...oldValueState, modelState: properties.model.state}
|
|
1632
|
-
|
|
1633
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1634
|
-
properties,
|
|
1635
|
-
'changeState',
|
|
1636
|
-
controlled,
|
|
1637
|
-
properties.model.state,
|
|
1638
|
-
event,
|
|
1639
|
-
properties
|
|
1640
|
-
)
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
triggerCallbackIfExists<Properties<Type>>(
|
|
1644
|
-
properties, 'touch', controlled, event, properties
|
|
1645
|
-
)
|
|
1646
|
-
|
|
1647
|
-
return result
|
|
1648
|
-
})
|
|
1649
|
-
// endregion
|
|
1650
|
-
// region properties
|
|
1651
|
-
/// region references
|
|
1652
|
-
const codeEditorReference:MutableRefObject<CodeEditorType|null> =
|
|
1653
|
-
useRef<CodeEditorType>(null)
|
|
1654
|
-
const codeEditorInputReference:MutableRefObject<HTMLTextAreaElement|null> =
|
|
1655
|
-
useRef<HTMLTextAreaElement>(null)
|
|
1656
|
-
const foundationReference:MutableRefObject<
|
|
1657
|
-
MDCSelectFoundation|MDCTextFieldFoundation|null
|
|
1658
|
-
> = useRef<MDCSelectFoundation|MDCTextFieldFoundation>(null)
|
|
1659
|
-
const inputReference:MutableRefObject<
|
|
1660
|
-
HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|null
|
|
1661
|
-
> = useRef<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>(null)
|
|
1662
|
-
const richTextEditorInputReference:MutableRefObject<
|
|
1663
|
-
HTMLTextAreaElement|null
|
|
1664
|
-
> = useRef<HTMLTextAreaElement>(null)
|
|
1665
|
-
const richTextEditorInstance:MutableRefObject<null|RichTextEditor> =
|
|
1666
|
-
useRef<RichTextEditor>(null)
|
|
1667
|
-
const richTextEditorReference:MutableRefObject<
|
|
1668
|
-
null|RichTextEditorComponent
|
|
1669
|
-
> = useRef<RichTextEditorComponent>(null)
|
|
1670
|
-
const suggestionMenuAPIReference:MutableRefObject<MenuApi|null> =
|
|
1671
|
-
useRef<MenuApi>(null)
|
|
1672
|
-
const suggestionMenuFoundationReference:MutableRefObject<
|
|
1673
|
-
MDCMenuFoundation|null
|
|
1674
|
-
> = useRef<MDCMenuFoundation>(null)
|
|
1675
|
-
/// endregion
|
|
1676
|
-
const givenProps:Props<Type> = translateKnownSymbols(props)
|
|
1677
|
-
|
|
1678
|
-
const [cursor, setCursor] = useState<CursorState>({end: 0, start: 0})
|
|
1679
|
-
const [editorState, setEditorState] = useState<EditorState>({
|
|
1680
|
-
editorIsActive: false, selectionIsUnstable: false
|
|
1681
|
-
})
|
|
1682
|
-
const [hidden, setHidden] = useState<boolean|undefined>()
|
|
1683
|
-
const [isSuggestionOpen, setIsSuggestionOpen] = useState<boolean>(false)
|
|
1684
|
-
const [showDeclaration, setShowDeclaration] = useState<boolean>(false)
|
|
1685
|
-
|
|
1686
|
-
let initialValue:null|Type = determineInitialValue<Type>(
|
|
1687
|
-
givenProps,
|
|
1688
|
-
GenericInput.defaultProperties.model?.default as unknown as null|Type
|
|
1689
|
-
)
|
|
1690
|
-
if (initialValue instanceof Date)
|
|
1691
|
-
initialValue = (initialValue.getTime() / 1000) as unknown as Type
|
|
1692
|
-
/*
|
|
1693
|
-
NOTE: Extend default properties with given properties while letting
|
|
1694
|
-
default property object untouched for unchanged usage in other
|
|
1695
|
-
instances.
|
|
1696
|
-
*/
|
|
1697
|
-
const givenProperties:Props<Type> = Tools.extend<Props<Type>>(
|
|
1698
|
-
true,
|
|
1699
|
-
Tools.copy<Props<Type>>(GenericInput.defaultProperties as Props<Type>),
|
|
1700
|
-
givenProps
|
|
1701
|
-
)
|
|
1702
|
-
|
|
1703
|
-
const type:keyof InputDataTransformation =
|
|
1704
|
-
givenProperties.type as keyof InputDataTransformation ||
|
|
1705
|
-
givenProperties.model?.type ||
|
|
1706
|
-
'string'
|
|
1707
|
-
const transformer:InputDataTransformation =
|
|
1708
|
-
givenProperties.transformer ?
|
|
1709
|
-
{
|
|
1710
|
-
...GenericInput.transformer,
|
|
1711
|
-
[type]: Tools.extend<DataTransformSpecification<Type>>(
|
|
1712
|
-
true,
|
|
1713
|
-
Tools.copy<DataTransformSpecification<Type>>(
|
|
1714
|
-
GenericInput.transformer[type] as
|
|
1715
|
-
DataTransformSpecification<Type>
|
|
1716
|
-
) ||
|
|
1717
|
-
{},
|
|
1718
|
-
givenProperties.transformer as
|
|
1719
|
-
DataTransformSpecification<Type>
|
|
1720
|
-
)
|
|
1721
|
-
} :
|
|
1722
|
-
GenericInput.transformer
|
|
1723
|
-
|
|
1724
|
-
let [selection, setSelection] =
|
|
1725
|
-
useState<AbortController|Properties['selection']>()
|
|
1726
|
-
if (givenProperties.selection || givenProperties.model?.selection)
|
|
1727
|
-
selection =
|
|
1728
|
-
givenProperties.selection || givenProperties.model?.selection
|
|
1729
|
-
|
|
1730
|
-
const normalizedSelection:NormalizedSelection|undefined =
|
|
1731
|
-
selection instanceof AbortController ?
|
|
1732
|
-
[] :
|
|
1733
|
-
normalizeSelection(selection, givenProperties.labels)
|
|
1734
|
-
const [suggestionLabels, suggestionValues] =
|
|
1735
|
-
selection instanceof AbortController ?
|
|
1736
|
-
[[], []] :
|
|
1737
|
-
getLabelAndValues(normalizedSelection)
|
|
1738
|
-
|
|
1739
|
-
/*
|
|
1740
|
-
NOTE: This values have to share the same state item since they have to
|
|
1741
|
-
be updated in one event loop (set state callback).
|
|
1742
|
-
*/
|
|
1743
|
-
let [valueState, setValueState] = useState<ValueState<Type, ModelState>>(
|
|
1744
|
-
() => ({
|
|
1745
|
-
modelState: {...GenericInput.defaultModelState},
|
|
1746
|
-
representation: determineInitialRepresentation<Type>(
|
|
1747
|
-
givenProperties as DefaultProperties<Type>,
|
|
1748
|
-
GenericInput.defaultProperties as
|
|
1749
|
-
unknown as
|
|
1750
|
-
DefaultProperties<Type>,
|
|
1751
|
-
initialValue,
|
|
1752
|
-
transformer,
|
|
1753
|
-
normalizedSelection
|
|
1754
|
-
),
|
|
1755
|
-
value: initialValue
|
|
1756
|
-
})
|
|
1757
|
-
)
|
|
1758
|
-
|
|
1759
|
-
/*
|
|
1760
|
-
NOTE: Sometimes we need real given properties or derived (default
|
|
1761
|
-
extended) "given" properties.
|
|
1762
|
-
*/
|
|
1763
|
-
const controlled:boolean =
|
|
1764
|
-
!givenProperties.enforceUncontrolled &&
|
|
1765
|
-
(
|
|
1766
|
-
givenProps.model?.value !== undefined ||
|
|
1767
|
-
givenProps.value !== undefined
|
|
1768
|
-
) &&
|
|
1769
|
-
Boolean(givenProps.onChange || givenProps.onChangeValue)
|
|
1770
|
-
const representationControlled:boolean =
|
|
1771
|
-
controlled && givenProps.representation !== undefined
|
|
1772
|
-
let selectionIsUnstable = false
|
|
1773
|
-
|
|
1774
|
-
deriveMissingPropertiesFromState()
|
|
1775
|
-
|
|
1776
|
-
const properties:Properties<Type> =
|
|
1777
|
-
getConsolidatedProperties(givenProperties)
|
|
1778
|
-
|
|
1779
|
-
if (properties.hidden === undefined)
|
|
1780
|
-
properties.hidden = properties.name?.startsWith('password')
|
|
1781
|
-
// region synchronize properties into state where values are not controlled
|
|
1782
|
-
if (!Tools.equals(properties.cursor, cursor))
|
|
1783
|
-
setCursor(properties.cursor)
|
|
1784
|
-
if (properties.editorIsActive !== editorState.editorIsActive)
|
|
1785
|
-
setEditorState({
|
|
1786
|
-
...editorState, editorIsActive: properties.editorIsActive
|
|
1787
|
-
})
|
|
1788
|
-
if (properties.hidden !== hidden)
|
|
1789
|
-
setHidden(properties.hidden)
|
|
1790
|
-
if (properties.showDeclaration !== showDeclaration)
|
|
1791
|
-
setShowDeclaration(properties.showDeclaration)
|
|
1792
|
-
|
|
1793
|
-
const currentValueState:ValueState<Type, ModelState> = {
|
|
1794
|
-
modelState: properties.model.state,
|
|
1795
|
-
representation: properties.representation,
|
|
1796
|
-
value: properties.value!
|
|
1797
|
-
}
|
|
1798
|
-
/*
|
|
1799
|
-
NOTE: If value is controlled only trigger/save state changes when model
|
|
1800
|
-
state has changed.
|
|
1801
|
-
*/
|
|
1802
|
-
if (
|
|
1803
|
-
!controlled &&
|
|
1804
|
-
(
|
|
1805
|
-
!Tools.equals(properties.value, valueState.value) ||
|
|
1806
|
-
properties.representation !== valueState.representation
|
|
1807
|
-
) ||
|
|
1808
|
-
!Tools.equals(properties.model.state, valueState.modelState)
|
|
1809
|
-
)
|
|
1810
|
-
setValueState(currentValueState)
|
|
1811
|
-
if (controlled)
|
|
1812
|
-
setValueState = wrapStateSetter<ValueState<Type, ModelState>>(
|
|
1813
|
-
setValueState, currentValueState
|
|
1814
|
-
)
|
|
1815
|
-
// endregion
|
|
1816
|
-
// endregion
|
|
1817
|
-
// region export references
|
|
1818
|
-
useImperativeHandle(
|
|
1819
|
-
reference,
|
|
1820
|
-
():AdapterWithReferences<Type> => {
|
|
1821
|
-
const state:State<Type> =
|
|
1822
|
-
{modelState: properties.model.state} as State<Type>
|
|
1823
|
-
|
|
1824
|
-
for (const name of [
|
|
1825
|
-
'cursor', 'editorIsActive', 'hidden', 'showDeclaration'
|
|
1826
|
-
] as const)
|
|
1827
|
-
if (!Object.prototype.hasOwnProperty.call(givenProps, name))
|
|
1828
|
-
(state[name] as boolean|CursorState) = properties[name]
|
|
1829
|
-
|
|
1830
|
-
if (!representationControlled)
|
|
1831
|
-
state.representation = properties.representation
|
|
1832
|
-
if (!controlled)
|
|
1833
|
-
state.value = properties.value as null|Type
|
|
1834
|
-
|
|
1835
|
-
return {
|
|
1836
|
-
properties,
|
|
1837
|
-
references: {
|
|
1838
|
-
codeEditorReference,
|
|
1839
|
-
codeEditorInputReference,
|
|
1840
|
-
foundationReference,
|
|
1841
|
-
inputReference,
|
|
1842
|
-
richTextEditorInputReference,
|
|
1843
|
-
richTextEditorInstance,
|
|
1844
|
-
richTextEditorReference,
|
|
1845
|
-
suggestionMenuAPIReference,
|
|
1846
|
-
suggestionMenuFoundationReference
|
|
1847
|
-
},
|
|
1848
|
-
state
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
)
|
|
1852
|
-
// endregion
|
|
1853
|
-
// region render
|
|
1854
|
-
/// region intermediate render properties
|
|
1855
|
-
const genericProperties:Partial<
|
|
1856
|
-
CodeEditorProps|RichTextEditorProps|SelectProps|TextFieldProps
|
|
1857
|
-
> = {
|
|
1858
|
-
onBlur,
|
|
1859
|
-
onFocus: triggerOnFocusAndOpenSuggestions,
|
|
1860
|
-
placeholder: properties.placeholder
|
|
1861
|
-
}
|
|
1862
|
-
const materialProperties:SelectProps|TextFieldProps = {
|
|
1863
|
-
disabled: properties.disabled,
|
|
1864
|
-
helpText: {
|
|
1865
|
-
children: renderHelpText(),
|
|
1866
|
-
persistent: Boolean(properties.declaration)
|
|
1867
|
-
},
|
|
1868
|
-
invalid: properties.showInitialValidationState && properties.invalid,
|
|
1869
|
-
label: properties.description || properties.name,
|
|
1870
|
-
outlined: properties.outlined,
|
|
1871
|
-
required: properties.required
|
|
1872
|
-
}
|
|
1873
|
-
if (properties.icon)
|
|
1874
|
-
materialProperties.icon = wrapIconWithTooltip(
|
|
1875
|
-
applyIconPreset(properties.icon) as IconOptions
|
|
1876
|
-
) as IconOptions
|
|
1877
|
-
|
|
1878
|
-
const tinyMCEOptions:Partial<TinyMCEOptions> = {
|
|
1879
|
-
...TINYMCE_DEFAULT_OPTIONS,
|
|
1880
|
-
// eslint-disable-next-line camelcase
|
|
1881
|
-
content_style: properties.disabled ? 'body {opacity: .38}' : '',
|
|
1882
|
-
placeholder: properties.placeholder,
|
|
1883
|
-
readonly: Boolean(properties.disabled),
|
|
1884
|
-
setup: (instance:RichTextEditor):void => {
|
|
1885
|
-
richTextEditorInstance.current = instance
|
|
1886
|
-
richTextEditorInstance.current.on('init', ():void => {
|
|
1887
|
-
if (!richTextEditorInstance.current)
|
|
1888
|
-
return
|
|
1889
|
-
|
|
1890
|
-
richTextEditorLoadedOnce = true
|
|
1891
|
-
|
|
1892
|
-
if (
|
|
1893
|
-
properties.editorIsActive &&
|
|
1894
|
-
editorState.selectionIsUnstable
|
|
1895
|
-
) {
|
|
1896
|
-
richTextEditorInstance.current.focus(false)
|
|
1897
|
-
setRichTextEditorSelectionState(
|
|
1898
|
-
richTextEditorInstance.current
|
|
1899
|
-
)
|
|
1900
|
-
setEditorState(
|
|
1901
|
-
{...editorState, selectionIsUnstable: false}
|
|
1902
|
-
)
|
|
1903
|
-
}
|
|
1904
|
-
})
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
if (properties.editor.endsWith('raw)')) {
|
|
1908
|
-
tinyMCEOptions.toolbar1 =
|
|
1909
|
-
'cut copy paste | undo redo removeformat | code | fullscreen'
|
|
1910
|
-
delete tinyMCEOptions.toolbar2
|
|
1911
|
-
} else if (properties.editor.endsWith('simple)')) {
|
|
1912
|
-
tinyMCEOptions.toolbar1 =
|
|
1913
|
-
'cut copy paste | undo redo removeformat | bold italic ' +
|
|
1914
|
-
'underline strikethrough subscript superscript | fullscreen'
|
|
1915
|
-
delete tinyMCEOptions.toolbar2
|
|
1916
|
-
} else if (properties.editor.endsWith('normal)'))
|
|
1917
|
-
tinyMCEOptions.toolbar1 =
|
|
1918
|
-
'cut copy paste | undo redo removeformat | styleselect ' +
|
|
1919
|
-
'formatselect | searchreplace visualblocks fullscreen code'
|
|
1920
|
-
|
|
1921
|
-
const isAdvancedEditor:boolean = (
|
|
1922
|
-
!properties.selection &&
|
|
1923
|
-
properties.type === 'string' &&
|
|
1924
|
-
properties.editorIsActive &&
|
|
1925
|
-
(
|
|
1926
|
-
properties.editor.startsWith('code') ||
|
|
1927
|
-
properties.editor.startsWith('richtext(')
|
|
1928
|
-
)
|
|
1929
|
-
)
|
|
1930
|
-
|
|
1931
|
-
const currentRenderableSuggestions:Array<ReactElement> = []
|
|
1932
|
-
const currentSuggestionLabels:Array<ReactNode|string> = []
|
|
1933
|
-
const currentSuggestionValues:Array<unknown> = []
|
|
1934
|
-
const useSuggestions = Boolean(
|
|
1935
|
-
properties.suggestionCreator ||
|
|
1936
|
-
suggestionLabels.length &&
|
|
1937
|
-
(properties.searchSelection || properties.suggestSelection)
|
|
1938
|
-
)
|
|
1939
|
-
if (useSuggestions && suggestionLabels.length) {
|
|
1940
|
-
// NOTE: Create consistent property configuration.
|
|
1941
|
-
properties.suggestSelection = !properties.searchSelection
|
|
1942
|
-
|
|
1943
|
-
let index = 0
|
|
1944
|
-
for (const suggestion of suggestionLabels) {
|
|
1945
|
-
if (Tools.isFunction(properties.children)) {
|
|
1946
|
-
const result:null|ReactElement = properties.children({
|
|
1947
|
-
index,
|
|
1948
|
-
normalizedSelection: normalizedSelection!,
|
|
1949
|
-
properties,
|
|
1950
|
-
query: properties.representation as string,
|
|
1951
|
-
suggestion,
|
|
1952
|
-
value: suggestionValues[index] as Type
|
|
1953
|
-
})
|
|
1954
|
-
|
|
1955
|
-
if (result) {
|
|
1956
|
-
currentRenderableSuggestions.push(
|
|
1957
|
-
<MenuItem
|
|
1958
|
-
className={CSS_CLASS_NAMES[
|
|
1959
|
-
'generic-input__suggestions__suggestion'
|
|
1960
|
-
]}
|
|
1961
|
-
key={index}
|
|
1962
|
-
>
|
|
1963
|
-
{result}
|
|
1964
|
-
</MenuItem>
|
|
1965
|
-
)
|
|
1966
|
-
currentSuggestionLabels.push(suggestion)
|
|
1967
|
-
currentSuggestionValues.push(suggestionValues[index])
|
|
1968
|
-
}
|
|
1969
|
-
} else if (
|
|
1970
|
-
!properties.representation ||
|
|
1971
|
-
properties.suggestionCreator ||
|
|
1972
|
-
suggestionMatches(
|
|
1973
|
-
suggestion as string, properties.representation as string
|
|
1974
|
-
)
|
|
1975
|
-
) {
|
|
1976
|
-
currentRenderableSuggestions.push(
|
|
1977
|
-
<MenuItem
|
|
1978
|
-
className={CSS_CLASS_NAMES[
|
|
1979
|
-
'generic-input__suggestions__suggestion'
|
|
1980
|
-
]}
|
|
1981
|
-
key={index}
|
|
1982
|
-
>
|
|
1983
|
-
{(Tools.stringMark(
|
|
1984
|
-
suggestion,
|
|
1985
|
-
(
|
|
1986
|
-
properties.representation as string
|
|
1987
|
-
)?.split(' ') || '',
|
|
1988
|
-
(value:unknown):string =>
|
|
1989
|
-
`${value as string}`.toLowerCase(),
|
|
1990
|
-
(foundWord:string):ReactElement =>
|
|
1991
|
-
<span className={CSS_CLASS_NAMES[
|
|
1992
|
-
'generic-input__suggestions__suggestion' +
|
|
1993
|
-
'__mark'
|
|
1994
|
-
]}>
|
|
1995
|
-
{foundWord}
|
|
1996
|
-
</span>,
|
|
1997
|
-
null
|
|
1998
|
-
) as Array<ReactElement|string>).map((
|
|
1999
|
-
item:ReactElement|string, index:number
|
|
2000
|
-
):ReactElement =>
|
|
2001
|
-
<span key={index}>{item}</span>
|
|
2002
|
-
)}
|
|
2003
|
-
</MenuItem>
|
|
2004
|
-
)
|
|
2005
|
-
currentSuggestionLabels.push(suggestion)
|
|
2006
|
-
currentSuggestionValues.push(suggestionValues[index])
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
index += 1
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
const useSelection:boolean =
|
|
2013
|
-
(Boolean(normalizedSelection) || Boolean(properties.labels)) &&
|
|
2014
|
-
!useSuggestions
|
|
2015
|
-
/// endregion
|
|
2016
|
-
/// region determine type specific constraints
|
|
2017
|
-
const constraints:Mapping<unknown> = {}
|
|
2018
|
-
if (properties.type === 'number') {
|
|
2019
|
-
constraints.step = properties.step
|
|
2020
|
-
|
|
2021
|
-
if (properties.maximum !== Infinity)
|
|
2022
|
-
constraints.max = properties.maximum
|
|
2023
|
-
if (properties.minimum !== -Infinity)
|
|
2024
|
-
constraints.min = properties.minimum
|
|
2025
|
-
} else if (properties.type === 'string') {
|
|
2026
|
-
if (
|
|
2027
|
-
properties.maximumLength >= 0 &&
|
|
2028
|
-
properties.maximumLength !== Infinity
|
|
2029
|
-
)
|
|
2030
|
-
constraints.maxLength = properties.maximumLength
|
|
2031
|
-
if (properties.minimumLength > 0)
|
|
2032
|
-
constraints.minLength = properties.minimumLength
|
|
2033
|
-
|
|
2034
|
-
if (properties.editor !== 'plain')
|
|
2035
|
-
constraints.rows = properties.rows
|
|
2036
|
-
} else if ([
|
|
2037
|
-
'date', 'datetime-local', 'time', 'time-local'
|
|
2038
|
-
].includes(properties.type)) {
|
|
2039
|
-
constraints.step = properties.step
|
|
2040
|
-
|
|
2041
|
-
if (properties.maximum !== Infinity)
|
|
2042
|
-
constraints.max = formatValue<Type>(
|
|
2043
|
-
properties,
|
|
2044
|
-
properties.maximum as
|
|
2045
|
-
unknown as
|
|
2046
|
-
Type,
|
|
2047
|
-
transformer
|
|
2048
|
-
)
|
|
2049
|
-
if (properties.minimum !== -Infinity)
|
|
2050
|
-
constraints.min = formatValue<Type>(
|
|
2051
|
-
properties,
|
|
2052
|
-
properties.minimum as
|
|
2053
|
-
unknown as
|
|
2054
|
-
Type,
|
|
2055
|
-
transformer
|
|
2056
|
-
)
|
|
2057
|
-
}
|
|
2058
|
-
/// endregion
|
|
2059
|
-
/// region main markup
|
|
2060
|
-
return <WrapConfigurations
|
|
2061
|
-
strict={GenericInput.strict}
|
|
2062
|
-
themeConfiguration={properties.themeConfiguration}
|
|
2063
|
-
tooltip={properties.tooltip}
|
|
2064
|
-
>
|
|
2065
|
-
<div
|
|
2066
|
-
className={[CSS_CLASS_NAMES['generic-input']]
|
|
2067
|
-
.concat(
|
|
2068
|
-
isAdvancedEditor ?
|
|
2069
|
-
CSS_CLASS_NAMES['generic-input--custom'] :
|
|
2070
|
-
[],
|
|
2071
|
-
properties.className ?? []
|
|
2072
|
-
)
|
|
2073
|
-
.join(' ')
|
|
2074
|
-
}
|
|
2075
|
-
style={properties.styles}
|
|
2076
|
-
>
|
|
2077
|
-
{wrapAnimationConditionally(
|
|
2078
|
-
<Select
|
|
2079
|
-
{...genericProperties as SelectProps}
|
|
2080
|
-
{...materialProperties as SelectProps}
|
|
2081
|
-
enhanced
|
|
2082
|
-
foundationRef={foundationReference as
|
|
2083
|
-
MutableRefObject<MDCSelectFoundation|null>
|
|
2084
|
-
}
|
|
2085
|
-
inputRef={inputReference as
|
|
2086
|
-
unknown as
|
|
2087
|
-
(_reference:HTMLSelectElement|null) => void
|
|
2088
|
-
}
|
|
2089
|
-
onChange={onChangeValue}
|
|
2090
|
-
options={normalizedSelection as SelectProps['options']}
|
|
2091
|
-
rootProps={{
|
|
2092
|
-
name: properties.name,
|
|
2093
|
-
onClick: onClick,
|
|
2094
|
-
...properties.rootProps
|
|
2095
|
-
}}
|
|
2096
|
-
value={`${properties.value as unknown as string}`}
|
|
2097
|
-
{...properties.inputProperties as SelectProps}
|
|
2098
|
-
/>,
|
|
2099
|
-
useSelection
|
|
2100
|
-
)}
|
|
2101
|
-
{wrapAnimationConditionally(
|
|
2102
|
-
[
|
|
2103
|
-
<FormField
|
|
2104
|
-
className={['mdc-text-field']
|
|
2105
|
-
.concat(
|
|
2106
|
-
properties.disabled ?
|
|
2107
|
-
'mdc-text-field--disabled' :
|
|
2108
|
-
[],
|
|
2109
|
-
'mdc-text-field--textarea'
|
|
2110
|
-
)
|
|
2111
|
-
.join(' ')
|
|
2112
|
-
}
|
|
2113
|
-
onKeyDown={preventEnterKeyPropagation}
|
|
2114
|
-
key="advanced-editor-form-field"
|
|
2115
|
-
>
|
|
2116
|
-
<label>
|
|
2117
|
-
<span className={
|
|
2118
|
-
[CSS_CLASS_NAMES[
|
|
2119
|
-
'generic-input__editor__label'
|
|
2120
|
-
]]
|
|
2121
|
-
.concat(
|
|
2122
|
-
'mdc-floating-label',
|
|
2123
|
-
'mdc-floating-label--float-above'
|
|
2124
|
-
)
|
|
2125
|
-
.join(' ')
|
|
2126
|
-
}>
|
|
2127
|
-
<Theme use={
|
|
2128
|
-
properties.invalid &&
|
|
2129
|
-
(
|
|
2130
|
-
properties.showInitialValidationState ||
|
|
2131
|
-
properties.visited
|
|
2132
|
-
) ? 'error' : undefined
|
|
2133
|
-
}>
|
|
2134
|
-
{
|
|
2135
|
-
(
|
|
2136
|
-
properties.description ||
|
|
2137
|
-
properties.name
|
|
2138
|
-
) +
|
|
2139
|
-
(properties.required ? '*' : '')
|
|
2140
|
-
}
|
|
2141
|
-
</Theme>
|
|
2142
|
-
</span>
|
|
2143
|
-
{
|
|
2144
|
-
properties.editor.startsWith('code') ?
|
|
2145
|
-
<Suspense fallback={
|
|
2146
|
-
<CircularProgress size="large" />
|
|
2147
|
-
}>
|
|
2148
|
-
<CodeEditor
|
|
2149
|
-
{...genericProperties as
|
|
2150
|
-
CodeEditorProps
|
|
2151
|
-
}
|
|
2152
|
-
className="mdc-text-field__input"
|
|
2153
|
-
mode={(
|
|
2154
|
-
properties.editor.startsWith(
|
|
2155
|
-
'code('
|
|
2156
|
-
) &&
|
|
2157
|
-
properties.editor.endsWith(')')
|
|
2158
|
-
) ?
|
|
2159
|
-
properties.editor.substring(
|
|
2160
|
-
'code('.length,
|
|
2161
|
-
properties.editor.length -
|
|
2162
|
-
1
|
|
2163
|
-
) :
|
|
2164
|
-
'javascript'
|
|
2165
|
-
}
|
|
2166
|
-
name={properties.name}
|
|
2167
|
-
onChange={onChangeValue as
|
|
2168
|
-
unknown as
|
|
2169
|
-
(
|
|
2170
|
-
_value:string,
|
|
2171
|
-
_event?:unknown
|
|
2172
|
-
) => void
|
|
2173
|
-
}
|
|
2174
|
-
onCursorChange={onSelectionChange}
|
|
2175
|
-
onSelectionChange={
|
|
2176
|
-
onSelectionChange
|
|
2177
|
-
}
|
|
2178
|
-
ref={setCodeEditorReference}
|
|
2179
|
-
setOptions={{
|
|
2180
|
-
maxLines: properties.rows,
|
|
2181
|
-
minLines: properties.rows,
|
|
2182
|
-
readOnly: properties.disabled,
|
|
2183
|
-
tabSize: 4,
|
|
2184
|
-
useWorker: false
|
|
2185
|
-
}}
|
|
2186
|
-
theme="github"
|
|
2187
|
-
value={
|
|
2188
|
-
properties.representation as
|
|
2189
|
-
string
|
|
2190
|
-
}
|
|
2191
|
-
{...properties.inputProperties as
|
|
2192
|
-
CodeEditorProps
|
|
2193
|
-
}
|
|
2194
|
-
/>
|
|
2195
|
-
</Suspense> :
|
|
2196
|
-
<RichTextEditorComponent
|
|
2197
|
-
{...genericProperties as
|
|
2198
|
-
RichTextEditorProps
|
|
2199
|
-
}
|
|
2200
|
-
disabled={properties.disabled}
|
|
2201
|
-
init={tinyMCEOptions}
|
|
2202
|
-
onClick={onClick as
|
|
2203
|
-
unknown as
|
|
2204
|
-
RichTextEventHandler<MouseEvent>
|
|
2205
|
-
}
|
|
2206
|
-
onEditorChange={onChangeValue as
|
|
2207
|
-
unknown as
|
|
2208
|
-
RichTextEditorProps[
|
|
2209
|
-
'onEditorChange'
|
|
2210
|
-
]
|
|
2211
|
-
}
|
|
2212
|
-
onKeyUp={onKeyUp as
|
|
2213
|
-
unknown as
|
|
2214
|
-
RichTextEventHandler<KeyboardEvent>
|
|
2215
|
-
}
|
|
2216
|
-
ref={setRichTextEditorReference}
|
|
2217
|
-
textareaName={properties.name}
|
|
2218
|
-
tinymceScriptSrc={
|
|
2219
|
-
(
|
|
2220
|
-
TINYMCE_DEFAULT_OPTIONS
|
|
2221
|
-
.base_url as
|
|
2222
|
-
string
|
|
2223
|
-
) +
|
|
2224
|
-
'tinymce.min.js'
|
|
2225
|
-
}
|
|
2226
|
-
value={
|
|
2227
|
-
properties.representation as string
|
|
2228
|
-
}
|
|
2229
|
-
{...properties.inputProperties as
|
|
2230
|
-
RichTextEditorProps
|
|
2231
|
-
}
|
|
2232
|
-
/>
|
|
2233
|
-
}
|
|
2234
|
-
</label>
|
|
2235
|
-
</FormField>,
|
|
2236
|
-
<div
|
|
2237
|
-
className="mdc-text-field-helper-line"
|
|
2238
|
-
key="advanced-editor-helper-line"
|
|
2239
|
-
>
|
|
2240
|
-
<p className={
|
|
2241
|
-
'mdc-text-field-helper-text ' +
|
|
2242
|
-
'mdc-text-field-helper-text--persistent'
|
|
2243
|
-
}>
|
|
2244
|
-
{(
|
|
2245
|
-
materialProperties.helpText as
|
|
2246
|
-
{children:ReactElement}
|
|
2247
|
-
).children}
|
|
2248
|
-
</p>
|
|
2249
|
-
</div>
|
|
2250
|
-
],
|
|
2251
|
-
isAdvancedEditor,
|
|
2252
|
-
richTextEditorLoadedOnce ||
|
|
2253
|
-
properties.editor.startsWith('code')
|
|
2254
|
-
)}
|
|
2255
|
-
{wrapAnimationConditionally(
|
|
2256
|
-
<div>
|
|
2257
|
-
{useSuggestions ?
|
|
2258
|
-
<MenuSurfaceAnchor
|
|
2259
|
-
onKeyDown={preventEnterKeyPropagation}
|
|
2260
|
-
>
|
|
2261
|
-
{selection instanceof AbortController ?
|
|
2262
|
-
<MenuSurface
|
|
2263
|
-
anchorCorner="bottomLeft"
|
|
2264
|
-
className={
|
|
2265
|
-
CSS_CLASS_NAMES[
|
|
2266
|
-
'generic-input__suggestions'
|
|
2267
|
-
] +
|
|
2268
|
-
' ' +
|
|
2269
|
-
CSS_CLASS_NAMES[
|
|
2270
|
-
'generic-input__suggestions' +
|
|
2271
|
-
'--pending'
|
|
2272
|
-
]
|
|
2273
|
-
}
|
|
2274
|
-
open={true}
|
|
2275
|
-
>
|
|
2276
|
-
<CircularProgress size="large" />
|
|
2277
|
-
</MenuSurface> :
|
|
2278
|
-
<Menu
|
|
2279
|
-
anchorCorner="bottomLeft"
|
|
2280
|
-
apiRef={(instance:MenuApi|null):void => {
|
|
2281
|
-
suggestionMenuAPIReference.current =
|
|
2282
|
-
instance
|
|
2283
|
-
}}
|
|
2284
|
-
className={CSS_CLASS_NAMES[
|
|
2285
|
-
'generic-input__suggestions'
|
|
2286
|
-
]}
|
|
2287
|
-
focusOnOpen={false}
|
|
2288
|
-
foundationRef={
|
|
2289
|
-
suggestionMenuFoundationReference
|
|
2290
|
-
}
|
|
2291
|
-
onFocus={onFocus}
|
|
2292
|
-
onSelect={(
|
|
2293
|
-
event:MenuOnSelectEventT
|
|
2294
|
-
):void => {
|
|
2295
|
-
onChangeValue(
|
|
2296
|
-
currentSuggestionValues[
|
|
2297
|
-
event.detail.index
|
|
2298
|
-
] as Type,
|
|
2299
|
-
undefined,
|
|
2300
|
-
event.detail.index
|
|
2301
|
-
)
|
|
2302
|
-
setIsSuggestionOpen(false)
|
|
2303
|
-
}}
|
|
2304
|
-
open={
|
|
2305
|
-
Boolean(
|
|
2306
|
-
currentSuggestionLabels.length
|
|
2307
|
-
) &&
|
|
2308
|
-
isSuggestionOpen &&
|
|
2309
|
-
/*
|
|
2310
|
-
NOTE: If single possibility is
|
|
2311
|
-
already selected avoid showing this
|
|
2312
|
-
suggestion.
|
|
2313
|
-
*/
|
|
2314
|
-
!(
|
|
2315
|
-
currentSuggestionLabels.length ===
|
|
2316
|
-
1 &&
|
|
2317
|
-
currentSuggestionLabels[0] ===
|
|
2318
|
-
properties.representation
|
|
2319
|
-
)
|
|
2320
|
-
}
|
|
2321
|
-
>
|
|
2322
|
-
{currentRenderableSuggestions}
|
|
2323
|
-
</Menu>
|
|
2324
|
-
}
|
|
2325
|
-
</MenuSurfaceAnchor> :
|
|
2326
|
-
''
|
|
2327
|
-
}
|
|
2328
|
-
<TextField
|
|
2329
|
-
{...genericProperties as TextFieldProps}
|
|
2330
|
-
{...materialProperties as TextFieldProps}
|
|
2331
|
-
{...constraints}
|
|
2332
|
-
align={properties.align}
|
|
2333
|
-
characterCount={
|
|
2334
|
-
typeof properties.maximumLength === 'number' &&
|
|
2335
|
-
!isNaN(properties.maximumLength) &&
|
|
2336
|
-
properties.maximumLength >= 0
|
|
2337
|
-
}
|
|
2338
|
-
foundationRef={foundationReference as
|
|
2339
|
-
MutableRefObject<MDCTextFieldFoundation|null>
|
|
2340
|
-
}
|
|
2341
|
-
inputRef={inputReference as
|
|
2342
|
-
MutableRefObject<HTMLInputElement|null>
|
|
2343
|
-
}
|
|
2344
|
-
onChange={onChangeValue}
|
|
2345
|
-
ripple={properties.ripple}
|
|
2346
|
-
rootProps={{
|
|
2347
|
-
name: properties.name,
|
|
2348
|
-
onClick,
|
|
2349
|
-
onKeyDown,
|
|
2350
|
-
onKeyUp,
|
|
2351
|
-
...properties.rootProps
|
|
2352
|
-
}}
|
|
2353
|
-
textarea={
|
|
2354
|
-
properties.type === 'string' &&
|
|
2355
|
-
properties.editor !== 'plain'
|
|
2356
|
-
}
|
|
2357
|
-
trailingIcon={wrapIconWithTooltip(applyIconPreset(
|
|
2358
|
-
properties.trailingIcon
|
|
2359
|
-
))}
|
|
2360
|
-
type={determineNativeType(properties)}
|
|
2361
|
-
value={properties.representation as string}
|
|
2362
|
-
{...properties.inputProperties as TextFieldProps}
|
|
2363
|
-
/>
|
|
2364
|
-
</div>,
|
|
2365
|
-
!(isAdvancedEditor || useSelection),
|
|
2366
|
-
richTextEditorLoadedOnce ||
|
|
2367
|
-
properties.editor.startsWith('code')
|
|
2368
|
-
)}
|
|
2369
|
-
</div>
|
|
2370
|
-
</WrapConfigurations>
|
|
2371
|
-
/// endregion
|
|
2372
|
-
// endregion
|
|
2373
|
-
}
|
|
2374
|
-
// NOTE: This is useful in react dev tools.
|
|
2375
|
-
GenericInputInner.displayName = 'GenericInput'
|
|
2376
|
-
/**
|
|
2377
|
-
* Wrapping web component compatible react component.
|
|
2378
|
-
* @property static:defaultModelState - Initial model state.
|
|
2379
|
-
* @property static:defaultProperties - Initial property configuration.
|
|
2380
|
-
* @property static:locales - Defines input formatting locales.
|
|
2381
|
-
* @property static:propTypes - Triggers reacts runtime property value checks.
|
|
2382
|
-
* @property static:strict - Indicates whether we should wrap render output in
|
|
2383
|
-
* reacts strict component.
|
|
2384
|
-
* @property static:transformer - Generic input data transformation
|
|
2385
|
-
* specifications.
|
|
2386
|
-
* @property static:wrapped - Wrapped component.
|
|
2387
|
-
*
|
|
2388
|
-
* @param props - Given components properties.
|
|
2389
|
-
* @param reference - Reference object to forward internal state.
|
|
2390
|
-
*
|
|
2391
|
-
* @returns React elements.
|
|
2392
|
-
*/
|
|
2393
|
-
export const GenericInput:GenericInputComponent =
|
|
2394
|
-
memorize(forwardRef(GenericInputInner)) as unknown as GenericInputComponent
|
|
2395
|
-
// region static properties
|
|
2396
|
-
/// region web-component hints
|
|
2397
|
-
GenericInput.wrapped = GenericInputInner
|
|
2398
|
-
GenericInput.webComponentAdapterWrapped = 'react'
|
|
2399
|
-
/// endregion
|
|
2400
|
-
GenericInput.defaultModelState = defaultModelState
|
|
2401
|
-
/*
|
|
2402
|
-
NOTE: We set values to "undefined" to identify whether these values where
|
|
2403
|
-
provided via "props" and should shadow a state saved valued.
|
|
2404
|
-
*/
|
|
2405
|
-
GenericInput.defaultProperties = {
|
|
2406
|
-
...defaultProperties,
|
|
2407
|
-
cursor: undefined,
|
|
2408
|
-
model: {
|
|
2409
|
-
...defaultProperties.model,
|
|
2410
|
-
// Trigger initial determination.
|
|
2411
|
-
state: undefined as unknown as ModelState,
|
|
2412
|
-
value: undefined
|
|
2413
|
-
},
|
|
2414
|
-
representation: undefined,
|
|
2415
|
-
value: undefined
|
|
2416
|
-
}
|
|
2417
|
-
GenericInput.locales = Tools.locales
|
|
2418
|
-
GenericInput.propTypes = propertyTypes
|
|
2419
|
-
GenericInput.renderProperties = renderProperties
|
|
2420
|
-
GenericInput.strict = false
|
|
2421
|
-
GenericInput.transformer = {
|
|
2422
|
-
boolean: {
|
|
2423
|
-
parse: (value:number|string):boolean =>
|
|
2424
|
-
typeof value === 'boolean' ?
|
|
2425
|
-
value :
|
|
2426
|
-
new Map<number|string, boolean>([
|
|
2427
|
-
['false', false],
|
|
2428
|
-
['true', true],
|
|
2429
|
-
[0, false],
|
|
2430
|
-
[1, true]
|
|
2431
|
-
]).get(value) ??
|
|
2432
|
-
true,
|
|
2433
|
-
type: 'text'
|
|
2434
|
-
},
|
|
2435
|
-
currency: {
|
|
2436
|
-
format: {final: {
|
|
2437
|
-
options: {currency: 'USD'},
|
|
2438
|
-
transform: (
|
|
2439
|
-
value:number,
|
|
2440
|
-
configuration:DefaultProperties<number>,
|
|
2441
|
-
transformer:InputDataTransformation
|
|
2442
|
-
):string => {
|
|
2443
|
-
const currency:string =
|
|
2444
|
-
transformer.currency.format?.final.options?.currency as
|
|
2445
|
-
string ??
|
|
2446
|
-
'USD'
|
|
2447
|
-
|
|
2448
|
-
if (value === Infinity)
|
|
2449
|
-
return `Infinity ${currency}`
|
|
2450
|
-
|
|
2451
|
-
if (value === -Infinity)
|
|
2452
|
-
return `- Infinity ${currency}`
|
|
2453
|
-
|
|
2454
|
-
if (isNaN(value))
|
|
2455
|
-
return 'unknown'
|
|
2456
|
-
|
|
2457
|
-
return (new Intl.NumberFormat(
|
|
2458
|
-
GenericInput.locales,
|
|
2459
|
-
{
|
|
2460
|
-
style: 'currency',
|
|
2461
|
-
...transformer.currency.format?.final.options ?? {}
|
|
2462
|
-
}
|
|
2463
|
-
)).format(value)
|
|
2464
|
-
}
|
|
2465
|
-
}},
|
|
2466
|
-
parse: (
|
|
2467
|
-
value:string,
|
|
2468
|
-
configuration:DefaultProperties<number>,
|
|
2469
|
-
transformer:InputDataTransformation
|
|
2470
|
-
):number =>
|
|
2471
|
-
transformer.float.parse ?
|
|
2472
|
-
transformer.float.parse(value, configuration, transformer) :
|
|
2473
|
-
NaN,
|
|
2474
|
-
type: 'text'
|
|
2475
|
-
},
|
|
2476
|
-
|
|
2477
|
-
date: {
|
|
2478
|
-
format: {final: {transform: (
|
|
2479
|
-
value:Date|number|string,
|
|
2480
|
-
configuration:DefaultProperties<number>,
|
|
2481
|
-
transformer:InputDataTransformation
|
|
2482
|
-
):string => {
|
|
2483
|
-
if (typeof value !== 'number')
|
|
2484
|
-
if (transformer.date.parse)
|
|
2485
|
-
value = transformer.date.parse(
|
|
2486
|
-
value, configuration, transformer
|
|
2487
|
-
)
|
|
2488
|
-
else {
|
|
2489
|
-
const parsedDate:number = value instanceof Date ?
|
|
2490
|
-
value.getTime() / 1000 :
|
|
2491
|
-
Date.parse(value)
|
|
2492
|
-
if (isNaN(parsedDate)) {
|
|
2493
|
-
const parsedFloat:number = parseFloat(value as string)
|
|
2494
|
-
value = isNaN(parsedFloat) ? 0 : parsedFloat
|
|
2495
|
-
} else
|
|
2496
|
-
value = parsedDate / 1000
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
if (value === Infinity)
|
|
2500
|
-
return 'Infinitely far in the future'
|
|
2501
|
-
if (value === -Infinity)
|
|
2502
|
-
return 'Infinitely early in the past'
|
|
2503
|
-
if (!isFinite(value))
|
|
2504
|
-
return ''
|
|
2505
|
-
|
|
2506
|
-
const formattedValue:string =
|
|
2507
|
-
(new Date(Math.round(value * 1000))).toISOString()
|
|
2508
|
-
|
|
2509
|
-
return formattedValue.substring(0, formattedValue.indexOf('T'))
|
|
2510
|
-
}}},
|
|
2511
|
-
parse: (value:Date|number|string):number => {
|
|
2512
|
-
if (typeof value === 'number')
|
|
2513
|
-
return value
|
|
2514
|
-
|
|
2515
|
-
if (value instanceof Date)
|
|
2516
|
-
return value.getTime() / 1000
|
|
2517
|
-
|
|
2518
|
-
const parsedDate:number = Date.parse(value)
|
|
2519
|
-
if (isNaN(parsedDate)) {
|
|
2520
|
-
const parsedFloat:number = parseFloat(value)
|
|
2521
|
-
if (isNaN(parsedFloat))
|
|
2522
|
-
return 0
|
|
2523
|
-
|
|
2524
|
-
return parsedFloat
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
return parsedDate / 1000
|
|
2528
|
-
}
|
|
2529
|
-
},
|
|
2530
|
-
// TODO respect local to utc conversion.
|
|
2531
|
-
'datetime-local': {
|
|
2532
|
-
format: {final: {transform: (
|
|
2533
|
-
value:Date|number|string,
|
|
2534
|
-
configuration:DefaultProperties<number>,
|
|
2535
|
-
transformer:InputDataTransformation
|
|
2536
|
-
):string => {
|
|
2537
|
-
if (typeof value !== 'number')
|
|
2538
|
-
if (transformer['datetime-local'].parse)
|
|
2539
|
-
value = transformer['datetime-local'].parse(
|
|
2540
|
-
value, configuration, transformer
|
|
2541
|
-
)
|
|
2542
|
-
else {
|
|
2543
|
-
const parsedDate:number = value instanceof Date ?
|
|
2544
|
-
value.getTime() / 1000 :
|
|
2545
|
-
Date.parse(value)
|
|
2546
|
-
if (isNaN(parsedDate)) {
|
|
2547
|
-
const parsedFloat:number = parseFloat(value as string)
|
|
2548
|
-
value = isNaN(parsedFloat) ? 0 : parsedFloat
|
|
2549
|
-
} else
|
|
2550
|
-
value = parsedDate / 1000
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
if (value === Infinity)
|
|
2554
|
-
return 'Infinitely far in the future'
|
|
2555
|
-
if (value === -Infinity)
|
|
2556
|
-
return 'Infinitely early in the past'
|
|
2557
|
-
if (!isFinite(value))
|
|
2558
|
-
return ''
|
|
2559
|
-
|
|
2560
|
-
const formattedValue:string =
|
|
2561
|
-
(new Date(Math.round(value * 1000))).toISOString()
|
|
2562
|
-
|
|
2563
|
-
return formattedValue.substring(0, formattedValue.length - 1)
|
|
2564
|
-
}}},
|
|
2565
|
-
parse: (
|
|
2566
|
-
value:Date|number|string,
|
|
2567
|
-
configuration:DefaultProperties<number>,
|
|
2568
|
-
transformer:InputDataTransformation
|
|
2569
|
-
):number => {
|
|
2570
|
-
if (transformer.date.parse)
|
|
2571
|
-
return transformer.date.parse(
|
|
2572
|
-
value, configuration, transformer
|
|
2573
|
-
)
|
|
2574
|
-
|
|
2575
|
-
if (value instanceof Date)
|
|
2576
|
-
return value.getTime() / 1000
|
|
2577
|
-
|
|
2578
|
-
const parsedDate:number = Date.parse(value as string)
|
|
2579
|
-
if (isNaN(parsedDate)) {
|
|
2580
|
-
const parsedFloat:number = parseFloat(value as string)
|
|
2581
|
-
if (isNaN(parsedFloat))
|
|
2582
|
-
return 0
|
|
2583
|
-
|
|
2584
|
-
return parsedFloat
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
return parsedDate / 1000
|
|
2588
|
-
}
|
|
2589
|
-
},
|
|
2590
|
-
time: {
|
|
2591
|
-
format: {
|
|
2592
|
-
final: {transform: (
|
|
2593
|
-
value:Date|number|string,
|
|
2594
|
-
configuration:DefaultProperties<number>,
|
|
2595
|
-
transformer:InputDataTransformation
|
|
2596
|
-
):string => {
|
|
2597
|
-
if (typeof value !== 'number')
|
|
2598
|
-
if (transformer.time.parse)
|
|
2599
|
-
value = transformer.time.parse(
|
|
2600
|
-
value, configuration, transformer
|
|
2601
|
-
)
|
|
2602
|
-
else {
|
|
2603
|
-
const parsedDate:number = value instanceof Date ?
|
|
2604
|
-
value.getTime() / 1000 :
|
|
2605
|
-
Date.parse(value)
|
|
2606
|
-
if (isNaN(parsedDate)) {
|
|
2607
|
-
const parsedFloat:number =
|
|
2608
|
-
parseFloat(value as string)
|
|
2609
|
-
value = isNaN(parsedFloat) ? 0 : parsedFloat
|
|
2610
|
-
} else
|
|
2611
|
-
value = parsedDate / 1000
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
if (value === Infinity)
|
|
2615
|
-
return 'Infinitely far in the future'
|
|
2616
|
-
if (value === -Infinity)
|
|
2617
|
-
return 'Infinitely early in the past'
|
|
2618
|
-
if (!isFinite(value))
|
|
2619
|
-
return ''
|
|
2620
|
-
|
|
2621
|
-
let formattedValue:string =
|
|
2622
|
-
(new Date(Math.round(value * 1000))).toISOString()
|
|
2623
|
-
|
|
2624
|
-
formattedValue = formattedValue.substring(
|
|
2625
|
-
formattedValue.indexOf('T') + 1, formattedValue.length - 1
|
|
2626
|
-
)
|
|
2627
|
-
|
|
2628
|
-
if (
|
|
2629
|
-
configuration.step &&
|
|
2630
|
-
configuration.step >= 60 &&
|
|
2631
|
-
(configuration.step % 60) === 0
|
|
2632
|
-
)
|
|
2633
|
-
return formattedValue.substring(
|
|
2634
|
-
0, formattedValue.lastIndexOf(':')
|
|
2635
|
-
)
|
|
2636
|
-
|
|
2637
|
-
return formattedValue
|
|
2638
|
-
}}
|
|
2639
|
-
},
|
|
2640
|
-
parse: (value:Date|number|string):number => {
|
|
2641
|
-
if (typeof value === 'number')
|
|
2642
|
-
return value
|
|
2643
|
-
|
|
2644
|
-
if (value instanceof Date)
|
|
2645
|
-
return value.getTime() / 1000
|
|
2646
|
-
|
|
2647
|
-
const parsedDate:number = Date.parse(value)
|
|
2648
|
-
if (isNaN(parsedDate)) {
|
|
2649
|
-
const parsedFloat:number = parseFloat(value.replace(
|
|
2650
|
-
/^([0-9]{2}):([0-9]{2})(:([0-9]{2}(\.[0-9]+)?))?$/,
|
|
2651
|
-
(
|
|
2652
|
-
_match:string,
|
|
2653
|
-
hours:string,
|
|
2654
|
-
minutes:string,
|
|
2655
|
-
secondsSuffix?:string,
|
|
2656
|
-
seconds?:string,
|
|
2657
|
-
_millisecondsSuffix?:string
|
|
2658
|
-
):string =>
|
|
2659
|
-
String(
|
|
2660
|
-
parseInt(hours) *
|
|
2661
|
-
60 ** 2 +
|
|
2662
|
-
parseInt(minutes) *
|
|
2663
|
-
60 +
|
|
2664
|
-
(secondsSuffix ? parseFloat(seconds!) : 0)
|
|
2665
|
-
)
|
|
2666
|
-
))
|
|
2667
|
-
|
|
2668
|
-
if (isNaN(parsedFloat))
|
|
2669
|
-
return 0
|
|
2670
|
-
|
|
2671
|
-
return parsedFloat
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
return parsedDate / 1000
|
|
2675
|
-
}
|
|
2676
|
-
},
|
|
2677
|
-
/*
|
|
2678
|
-
NOTE: Daylight saving time should not make a difference since times
|
|
2679
|
-
will always be saved on zero unix timestamp where no daylight saving
|
|
2680
|
-
time rules existing.
|
|
2681
|
-
*/
|
|
2682
|
-
'time-local': {
|
|
2683
|
-
format: {
|
|
2684
|
-
final: {transform: (
|
|
2685
|
-
value:Date|number|string,
|
|
2686
|
-
configuration:DefaultProperties<number>,
|
|
2687
|
-
transformer:InputDataTransformation
|
|
2688
|
-
):string => {
|
|
2689
|
-
if (typeof value !== 'number')
|
|
2690
|
-
if (transformer['time-local'].parse)
|
|
2691
|
-
value = transformer['time-local'].parse(
|
|
2692
|
-
value, configuration, transformer
|
|
2693
|
-
)
|
|
2694
|
-
else {
|
|
2695
|
-
const parsedDate:number = value instanceof Date ?
|
|
2696
|
-
value.getTime() / 1000 :
|
|
2697
|
-
Date.parse(value)
|
|
2698
|
-
if (isNaN(parsedDate)) {
|
|
2699
|
-
const parsedFloat:number =
|
|
2700
|
-
parseFloat(value as string)
|
|
2701
|
-
value = isNaN(parsedFloat) ? 0 : parsedFloat
|
|
2702
|
-
} else
|
|
2703
|
-
value = parsedDate / 1000
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
if (value === Infinity)
|
|
2707
|
-
return 'Infinitely far in the future'
|
|
2708
|
-
if (value === -Infinity)
|
|
2709
|
-
return 'Infinitely early in the past'
|
|
2710
|
-
if (!isFinite(value))
|
|
2711
|
-
return ''
|
|
2712
|
-
|
|
2713
|
-
const dateTime = new Date(Math.round(value * 1000))
|
|
2714
|
-
const hours:number = dateTime.getHours()
|
|
2715
|
-
const minutes:number = dateTime.getMinutes()
|
|
2716
|
-
|
|
2717
|
-
const formattedValue:string = (
|
|
2718
|
-
`${hours < 10 ? '0' : ''}${String(hours)}:` +
|
|
2719
|
-
`${minutes < 10 ? '0' : ''}${String(minutes)}`
|
|
2720
|
-
)
|
|
2721
|
-
|
|
2722
|
-
if (!(
|
|
2723
|
-
configuration.step &&
|
|
2724
|
-
configuration.step >= 60 &&
|
|
2725
|
-
(configuration.step % 60) === 0
|
|
2726
|
-
)) {
|
|
2727
|
-
const seconds:number = dateTime.getSeconds()
|
|
2728
|
-
|
|
2729
|
-
return (
|
|
2730
|
-
`${formattedValue}:${(seconds < 10) ? '0' : ''}` +
|
|
2731
|
-
String(seconds)
|
|
2732
|
-
)
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
return formattedValue
|
|
2736
|
-
}}
|
|
2737
|
-
},
|
|
2738
|
-
parse: (value:Date|number|string):number => {
|
|
2739
|
-
if (typeof value === 'number')
|
|
2740
|
-
return value
|
|
2741
|
-
|
|
2742
|
-
if (value instanceof Date)
|
|
2743
|
-
return value.getTime() / 1000
|
|
2744
|
-
|
|
2745
|
-
const parsedDate:number = Date.parse(value)
|
|
2746
|
-
if (isNaN(parsedDate)) {
|
|
2747
|
-
const parsedFloat:number = parseFloat(value.replace(
|
|
2748
|
-
/^([0-9]{2}):([0-9]{2})(:([0-9]{2}(\.[0-9]+)?))?$/,
|
|
2749
|
-
(
|
|
2750
|
-
_match:string,
|
|
2751
|
-
hours:string,
|
|
2752
|
-
minutes:string,
|
|
2753
|
-
secondsSuffix?:string,
|
|
2754
|
-
seconds?:string,
|
|
2755
|
-
_millisecondsSuffix?:string
|
|
2756
|
-
):string => {
|
|
2757
|
-
const zeroDateTime = new Date(0)
|
|
2758
|
-
|
|
2759
|
-
zeroDateTime.setHours(parseInt(hours))
|
|
2760
|
-
zeroDateTime.setMinutes(parseInt(minutes))
|
|
2761
|
-
if (secondsSuffix)
|
|
2762
|
-
zeroDateTime.setSeconds(parseInt(seconds!))
|
|
2763
|
-
|
|
2764
|
-
return String(zeroDateTime.getTime() / 1000)
|
|
2765
|
-
}
|
|
2766
|
-
))
|
|
2767
|
-
|
|
2768
|
-
if (isNaN(parsedFloat))
|
|
2769
|
-
return 0
|
|
2770
|
-
|
|
2771
|
-
return parsedFloat
|
|
2772
|
-
}
|
|
2773
|
-
|
|
2774
|
-
return parsedDate / 1000
|
|
2775
|
-
},
|
|
2776
|
-
type: 'time'
|
|
2777
|
-
},
|
|
2778
|
-
|
|
2779
|
-
float: {
|
|
2780
|
-
format: {final: {transform: (
|
|
2781
|
-
value:number,
|
|
2782
|
-
configuration:DefaultProperties<number>,
|
|
2783
|
-
transformer:InputDataTransformation
|
|
2784
|
-
):string =>
|
|
2785
|
-
transformer.float.format ?
|
|
2786
|
-
value === Infinity ?
|
|
2787
|
-
'Infinity' :
|
|
2788
|
-
value === -Infinity ?
|
|
2789
|
-
'- Infinity' :
|
|
2790
|
-
(new Intl.NumberFormat(
|
|
2791
|
-
GenericInput.locales,
|
|
2792
|
-
transformer.float.format.final.options || {}
|
|
2793
|
-
)).format(value) :
|
|
2794
|
-
`${value}`
|
|
2795
|
-
}},
|
|
2796
|
-
parse: (
|
|
2797
|
-
value:number|string, configuration:DefaultProperties<number>
|
|
2798
|
-
):number => {
|
|
2799
|
-
if (typeof value === 'string')
|
|
2800
|
-
value = parseFloat(
|
|
2801
|
-
GenericInput.locales[0] === 'de-DE' ?
|
|
2802
|
-
value.replace(/\./g, '').replace(/,/g, '.') :
|
|
2803
|
-
value
|
|
2804
|
-
)
|
|
2805
|
-
|
|
2806
|
-
// Fix sign if possible.
|
|
2807
|
-
if (
|
|
2808
|
-
typeof value === 'number' &&
|
|
2809
|
-
(
|
|
2810
|
-
typeof configuration.minimum === 'number' &&
|
|
2811
|
-
configuration.minimum >= 0 &&
|
|
2812
|
-
value < 0 ||
|
|
2813
|
-
typeof configuration.maximum === 'number' &&
|
|
2814
|
-
configuration.maximum <= 0 &&
|
|
2815
|
-
value > 0
|
|
2816
|
-
)
|
|
2817
|
-
)
|
|
2818
|
-
value *= -1
|
|
2819
|
-
|
|
2820
|
-
return value
|
|
2821
|
-
},
|
|
2822
|
-
type: 'text'
|
|
2823
|
-
},
|
|
2824
|
-
integer: {
|
|
2825
|
-
format: {final: {transform: (
|
|
2826
|
-
value:number,
|
|
2827
|
-
configuration:DefaultProperties<number>,
|
|
2828
|
-
transformer:InputDataTransformation
|
|
2829
|
-
):string => (
|
|
2830
|
-
new Intl.NumberFormat(
|
|
2831
|
-
GenericInput.locales,
|
|
2832
|
-
{
|
|
2833
|
-
maximumFractionDigits: 0,
|
|
2834
|
-
...(transformer.integer.format?.final.options ?? {})
|
|
2835
|
-
}
|
|
2836
|
-
)).format(value)
|
|
2837
|
-
}},
|
|
2838
|
-
parse: (
|
|
2839
|
-
value:number|string, configuration:DefaultProperties<number>
|
|
2840
|
-
):number => {
|
|
2841
|
-
if (typeof value === 'string')
|
|
2842
|
-
value = parseInt(
|
|
2843
|
-
GenericInput.locales[0] === 'de-DE' ?
|
|
2844
|
-
value.replace(/[,.]/g, '') :
|
|
2845
|
-
value
|
|
2846
|
-
)
|
|
2847
|
-
|
|
2848
|
-
// Fix sign if possible.
|
|
2849
|
-
if (
|
|
2850
|
-
typeof value === 'number' &&
|
|
2851
|
-
(
|
|
2852
|
-
typeof configuration.minimum === 'number' &&
|
|
2853
|
-
configuration.minimum >= 0 &&
|
|
2854
|
-
value < 0 ||
|
|
2855
|
-
typeof configuration.maximum === 'number' &&
|
|
2856
|
-
configuration.maximum <= 0 &&
|
|
2857
|
-
value > 0
|
|
2858
|
-
)
|
|
2859
|
-
)
|
|
2860
|
-
value *= -1
|
|
2861
|
-
|
|
2862
|
-
return value
|
|
2863
|
-
},
|
|
2864
|
-
type: 'text'
|
|
2865
|
-
},
|
|
2866
|
-
number: {parse: (value:number|string):number =>
|
|
2867
|
-
typeof value === 'number' ? value : parseInt(value)
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
// endregion
|
|
2871
|
-
export default GenericInput
|
|
2872
|
-
// region vim modline
|
|
2873
|
-
// vim: set tabstop=4 shiftwidth=4 expandtab:
|
|
2874
|
-
// vim: foldmethod=marker foldmarker=region,endregion:
|
|
2875
|
-
// endregion
|