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.
@@ -1,1010 +0,0 @@
1
- // #!/usr/bin/env babel-node
2
- // -*- coding: utf-8 -*-
3
- /** @module FileInput */
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 {blobToBase64String, dataURLToBlob} from 'blob-util'
21
- import Tools from 'clientnode'
22
- import {Mapping} from 'clientnode/type'
23
- import {
24
- FocusEvent as ReactFocusEvent,
25
- ForwardedRef,
26
- forwardRef,
27
- memo as memorize,
28
- MouseEvent as ReactMouseEvent,
29
- MutableRefObject,
30
- ReactElement,
31
- SyntheticEvent,
32
- useEffect,
33
- useImperativeHandle,
34
- useRef,
35
- useState
36
- } from 'react'
37
- import {
38
- Card,
39
- CardActionButton,
40
- CardActionButtons,
41
- CardActions,
42
- CardMedia,
43
- CardPrimaryAction
44
- } from '@rmwc/card'
45
- import {CircularProgress} from '@rmwc/circular-progress'
46
- import {Theme} from '@rmwc/theme'
47
- import {Typography} from '@rmwc/typography'
48
-
49
- /*
50
- "namedExport" version of css-loader:
51
-
52
- import {
53
- fileInputClassName,
54
- fileInputDownloadClassName,
55
- fileInputIframeWrapperPaddingClassName,
56
- fileInputNativeClassName,
57
- fileInputTextRepresentationClassName
58
- } from './FileInput.module'
59
- */
60
- import cssClassNames from './FileInput.module'
61
- import GenericInput from './GenericInput'
62
- import {WrapConfigurations} from './WrapConfigurations'
63
- import {
64
- deriveMissingPropertiesFromState,
65
- determineInitialValue,
66
- determineValidationState as determineBaseValidationState,
67
- getConsolidatedProperties as getBaseConsolidatedProperties,
68
- mapPropertiesIntoModel,
69
- translateKnownSymbols,
70
- triggerCallbackIfExists,
71
- wrapStateSetter
72
- } from '../helper'
73
- import {
74
- defaultFileInputModelState as defaultModelState,
75
- DefaultFileInputProperties as DefaultProperties,
76
- defaultFileInputProperties as defaultProperties,
77
- defaultFileNameInputProperties,
78
- FileInputAdapter as Adapter,
79
- FileInputModelState as ModelState,
80
- FileInputProperties as Properties,
81
- FileInputProps as Props,
82
- FileValue,
83
- FileInputValueState as ValueState,
84
- fileInputPropertyTypes as propertyTypes,
85
- InputAdapter,
86
- InputProperties,
87
- InputProps,
88
- FileRepresentationType as RepresentationType,
89
- FileInputComponent
90
- } from '../type'
91
- // endregion
92
- // region constants
93
- const CSS_CLASS_NAMES:Mapping = cssClassNames as Mapping
94
- /*
95
- NOTE: Caused by a bug transpiling regular expression which ignores needed
96
- escape sequences for "/" when using the nativ regular expression type.
97
- */
98
- const IMAGE_CONTENT_TYPE_REGULAR_EXPRESSION = new RegExp(
99
- '^image\\/(?:p?jpe?g|png|svg(?:\\+xml)?|vnd\\.microsoft\\.icon|gif|tiff|' +
100
- 'webp|vnd\\.wap\\.wbmp|x-(?:icon|jng|ms-bmp))$',
101
- 'i'
102
- )
103
- const TEXT_CONTENT_TYPE_REGULAR_EXPRESSION = new RegExp(
104
- '^(?:application\\/xml)|(?:text\\/(?:plain|x-ndpb[wy]html|(?:x-)?csv' +
105
- '|x?html?|xml))$',
106
- 'i'
107
- )
108
- const REPRESENTABLE_TEXT_CONTENT_TYPE_REGULAR_EXPRESSION =
109
- // Plain version:
110
- /^text\/plain$/i
111
- // Rendered version:
112
- // /^(application\/xml)|(text\/(plain|x?html?|xml))$/i
113
- const VIDEO_CONTENT_TYPE_REGULAR_EXPRESSION = new RegExp(
114
- '^video\\/(?:(?:x-)?(?:x-)?webm|3gpp|mp2t|mp4|mpeg|quicktime|(?:x-)?flv' +
115
- '|(?:x-)?m4v|(?:x-)mng|x-ms-as|x-ms-wmv|x-msvideo)' +
116
- '|(?:application\\/(?:x-)?shockwave-flash)$',
117
- 'i'
118
- )
119
- // endregion
120
- // region helper
121
- export const preserveStaticFileBaseNameInputGenerator:Properties[
122
- 'generateFileNameInputProperties'
123
- ] = (
124
- prototype:InputProps<string>, {name, value: {name: fileName}}
125
- ):InputProps<string> => ({
126
- ...prototype,
127
- disabled: true,
128
- value:
129
- name +
130
- (fileName?.includes('.') ?
131
- fileName.substring(fileName.lastIndexOf('.')) :
132
- ''
133
- )
134
- })
135
- /**
136
- * Determines which type of file we have to present.
137
- * @param contentType - File type to derive representation type from.
138
- * @returns Nothing.
139
- */
140
- export const determineRepresentationType = (
141
- contentType:string
142
- ):RepresentationType => {
143
- contentType = contentType.replace(/; *charset=.+$/, '')
144
-
145
- if (TEXT_CONTENT_TYPE_REGULAR_EXPRESSION.test(contentType)) {
146
- if (REPRESENTABLE_TEXT_CONTENT_TYPE_REGULAR_EXPRESSION.test(
147
- contentType
148
- ))
149
- return 'renderableText'
150
-
151
- return 'text'
152
- }
153
-
154
- if (IMAGE_CONTENT_TYPE_REGULAR_EXPRESSION.test(contentType))
155
- return 'image'
156
-
157
- if (VIDEO_CONTENT_TYPE_REGULAR_EXPRESSION.test(contentType))
158
- return 'video'
159
-
160
- return 'binary'
161
- }
162
- export const determineValidationState = <P extends DefaultProperties>(
163
- properties:P, invalidName:boolean, currentState:ModelState
164
- ):boolean => determineBaseValidationState<P>(
165
- properties,
166
- currentState,
167
- {
168
- invalidMaximumSize: ():boolean => (
169
- typeof properties.model.maximumSize === 'number' &&
170
- properties.model.maximumSize <
171
- (properties.model.value?.blob?.size || 0)
172
- ),
173
- invalidMinimumSize: ():boolean => (
174
- typeof properties.model.minimumSize === 'number' &&
175
- properties.model.minimumSize >
176
- (properties.model.value?.blob?.size || 0)
177
- ),
178
- invalidName: ():boolean => invalidName,
179
- invalidContentTypePattern: ():boolean => (
180
- typeof properties.model.value?.blob?.type === 'string' &&
181
- ([] as Array<null|RegExp|string>)
182
- .concat(properties.model.contentTypeRegularExpressionPattern)
183
- .some((expression:null|RegExp|string):boolean =>
184
- typeof expression === 'string' &&
185
- !(new RegExp(expression))
186
- .test(properties.model.value!.blob!.type!) ||
187
- expression !== null &&
188
- typeof expression === 'object' &&
189
- !expression.test(properties.model.value!.blob!.type!)
190
- )
191
- ),
192
- invalidInvertedContentTypePattern: ():boolean => (
193
- typeof properties.model.value?.blob?.type === 'string' &&
194
- ([] as Array<null|RegExp|string>)
195
- .concat(
196
- properties.model
197
- .invertedContentTypeRegularExpressionPattern
198
- )
199
- .some((expression:null|RegExp|string):boolean =>
200
- typeof expression === 'string' &&
201
- (new RegExp(expression))
202
- .test(properties.model.value!.blob!.type!) ||
203
- expression !== null &&
204
- typeof expression === 'object' &&
205
- expression.test(properties.model.value!.blob!.type!)
206
- )
207
- )
208
- }
209
- )
210
- export const readBinaryDataIntoText = (
211
- blob:Blob, encoding = 'utf-8'
212
- ):Promise<string> =>
213
- new Promise<string>((
214
- resolve:(_value:string) => void, reject:(_reason:Error) => void
215
- ):void => {
216
- const fileReader:FileReader = new FileReader()
217
-
218
- fileReader.onload = (event:Event):void => {
219
- let content:string =
220
- (event.target as unknown as {result:string}).result
221
- // Remove preceding BOM.
222
- if (
223
- content.length &&
224
- encoding.endsWith('-sig') &&
225
- content.charCodeAt(0) === 0xFEFF
226
- )
227
- content = content.slice(1)
228
- // Normalize line endings to unix format.
229
- content = content.replace(/\r\n/g, '\n')
230
- resolve(content)
231
- }
232
-
233
- fileReader.onabort = ():void => reject(new Error('abort'))
234
- fileReader.onerror = ():void => reject(new Error())
235
-
236
- fileReader.readAsText(
237
- blob,
238
- encoding.endsWith('-sig') ?
239
- encoding.substring(0, encoding.length - '-sig'.length) :
240
- encoding
241
- )
242
- })
243
- // endregion
244
- /* eslint-disable jsdoc/require-description-complete-sentence */
245
- /**
246
- * Validateable checkbox wrapper component.
247
- * @property static:displayName - Descriptive name for component to show in web
248
- * developer tools.
249
- *
250
- * Dataflow:
251
- *
252
- * 1. On-Render all states are merged with given properties into a normalized
253
- * property object.
254
- * 2. Properties, corresponding state values and sub node instances are saved
255
- * into a "ref" object (to make them accessible from the outside e.g. for
256
- * wrapper like web-components).
257
- * 3. Event handler saves corresponding data modifications into state and
258
- * normalized properties object.
259
- * 4. All state changes except selection changes trigger an "onChange" event
260
- * which delivers the consolidated properties object (with latest
261
- * modifications included).
262
- *
263
- * @property static:displayName - Descriptive name for component to show in web
264
- * developer tools.
265
- *
266
- * @param props - Given components properties.
267
- * @param reference - Reference object to forward internal state.
268
- *
269
- * @returns React elements.
270
- */
271
- export const FileInputInner = function(
272
- props:Props, reference?:ForwardedRef<Adapter>
273
- ):ReactElement {
274
- // region property aggregation
275
- /**
276
- * Calculate external properties (a set of all configurable properties).
277
- * @param properties - Properties to merge.
278
- *
279
- * @returns External properties object.
280
- */
281
- const getConsolidatedProperties = (properties:Props):Properties => {
282
- const result:DefaultProperties =
283
- mapPropertiesIntoModel<Props, DefaultProperties>(
284
- properties, FileInput.defaultProperties.model
285
- )
286
-
287
- determineValidationState(
288
- result,
289
- // TODO not available
290
- false
291
- /*
292
- nameInputReference.current?.properties.invalid ??
293
- result.fileNameInputProperties.invalid ??
294
- result.model.fileName.invalid ??
295
- result.model!.state.invalidName
296
- */,
297
- result.model.state
298
- )
299
-
300
- return getBaseConsolidatedProperties<Props, Properties>(result)
301
- }
302
- // endregion
303
- // region event handler
304
- /**
305
- * Triggered on blur events.
306
- * @param event - Event object.
307
- *
308
- * @returns Nothing.
309
- */
310
- const onBlur = (event:SyntheticEvent):void => setValueState((
311
- oldValueState:ValueState
312
- ):ValueState => {
313
- let changed = false
314
-
315
- if (oldValueState.modelState.focused) {
316
- properties.focused = false
317
- changed = true
318
- }
319
-
320
- if (!oldValueState.modelState.visited) {
321
- properties.visited = true
322
- changed = true
323
- }
324
-
325
- if (changed) {
326
- onChange(event)
327
-
328
- triggerCallbackIfExists<Properties>(
329
- properties,
330
- 'changeState',
331
- controlled,
332
- properties.model.state,
333
- event,
334
- properties
335
- )
336
- }
337
-
338
- triggerCallbackIfExists<Properties>(
339
- properties, 'blur', controlled, event, properties
340
- )
341
-
342
- return changed ?
343
- {...oldValueState, modelState: properties.model.state} :
344
- oldValueState
345
- })
346
- /**
347
- * Triggered on any change events. Consolidates properties object and
348
- * triggers given on change callbacks.
349
- * @param event - Potential event object.
350
- *
351
- * @returns Nothing.
352
- */
353
- const onChange = (event?:SyntheticEvent):void => {
354
- if (nameInputReference.current?.properties)
355
- properties.model.fileName =
356
- nameInputReference.current.properties.model
357
-
358
- const consolidatedProperties:Properties = getConsolidatedProperties(
359
- /*
360
- Workaround since "Type" isn't identified as subset of
361
- "RecursivePartial<Type>" yet.
362
- */
363
- properties as unknown as Props
364
- )
365
- // NOTE: Avoid recursive merging of deprecated value properties.
366
- delete properties.model.value
367
- delete properties.value
368
- // NOTE: Avoid trying to write into a readonly object.
369
- properties.styles = Tools.copy(properties.styles)
370
-
371
- Tools.extend(true, properties, consolidatedProperties)
372
-
373
- triggerCallbackIfExists<Properties>(
374
- properties, 'change', controlled, properties, event
375
- )
376
- }
377
- /**
378
- * Triggered when ever the value changes.
379
- * @param eventSourceOrName - Event object or new value.
380
- * @param event - Optional event object (if not provided as first
381
- * argument).
382
- * @param inputProperties - Current properties state.
383
- * @param attachBlobProperty - Indicates whether additional data is added
384
- * through post processed data properties.
385
- *
386
- * @returns Nothing.
387
- */
388
- const onChangeValue = (
389
- eventSourceOrName:FileValue|null|string|SyntheticEvent,
390
- event?:SyntheticEvent|undefined,
391
- inputProperties?:InputProperties<string>|undefined,
392
- attachBlobProperty = false
393
- ):void => {
394
- if (!(properties.model.mutable && properties.model.writable))
395
- return
396
-
397
- if (
398
- eventSourceOrName &&
399
- fileInputReference.current &&
400
- (eventSourceOrName as SyntheticEvent).target ===
401
- fileInputReference.current
402
- ) {
403
- event = eventSourceOrName as SyntheticEvent
404
-
405
- if ((event.target as unknown as {files:FileList}).files?.length) {
406
- const blob:File =
407
- (event.target as unknown as {files:FileList}).files[0]
408
-
409
- properties.value = {blob, name: blob.name}
410
-
411
- properties.value.name =
412
- properties.generateFileNameInputProperties(
413
- {
414
- disabled: properties.disabled,
415
- value: blob.name,
416
- ...defaultFileNameInputProperties,
417
- model: properties.model.fileName,
418
- onChangeValue,
419
- default: properties.value.name
420
- },
421
- properties as
422
- Omit<Properties, 'value'> &
423
- {value:FileValue & {name:string}}
424
- )?.value ||
425
- blob.name
426
- } else
427
- return
428
- }
429
-
430
- setValueState((oldValueState:ValueState):ValueState => {
431
- if (eventSourceOrName === null)
432
- properties.value = eventSourceOrName
433
- else if (typeof eventSourceOrName === 'string')
434
- /*
435
- NOTE: A name can only be changed if a blob is available
436
- beforehand.
437
- */
438
- properties.value = {
439
- ...oldValueState.value, name: eventSourceOrName
440
- }
441
- else if (
442
- typeof (eventSourceOrName as FileValue).source === 'string' ||
443
- typeof (eventSourceOrName as FileValue).url === 'string'
444
- )
445
- if (attachBlobProperty)
446
- properties.value = {
447
- ...oldValueState.value, ...eventSourceOrName
448
- }
449
- else
450
- properties.value = eventSourceOrName as FileValue
451
-
452
- if (Tools.equals(oldValueState.value, properties.value))
453
- return oldValueState
454
-
455
- let stateChanged = false
456
-
457
- const result:ValueState = {
458
- ...oldValueState, value: properties.value as FileValue|null
459
- }
460
-
461
- if (oldValueState.modelState.pristine) {
462
- properties.dirty = true
463
- properties.pristine = false
464
- stateChanged = true
465
- }
466
-
467
- onChange(event)
468
-
469
- if (determineValidationState(
470
- properties as unknown as DefaultProperties,
471
- nameInputReference.current?.properties?.invalid || false,
472
- oldValueState.modelState
473
- ))
474
- stateChanged = true
475
-
476
- triggerCallbackIfExists<Properties>(
477
- properties,
478
- 'changeValue',
479
- controlled,
480
- properties.value,
481
- event,
482
- properties
483
- )
484
-
485
- if (stateChanged) {
486
- result.modelState = properties.model.state
487
-
488
- triggerCallbackIfExists<Properties>(
489
- properties,
490
- 'changeState',
491
- controlled,
492
- properties.model.state,
493
- event,
494
- properties
495
- )
496
- }
497
-
498
-
499
- if (attachBlobProperty)
500
- result.attachBlobProperty = true
501
-
502
- return result
503
- })
504
- }
505
- /**
506
- * Triggered on click events.
507
- * @param event - Mouse event object.
508
- *
509
- * @returns Nothing.
510
- */
511
- const onClick = (event:ReactMouseEvent):void => {
512
- triggerCallbackIfExists<Properties>(
513
- properties, 'click', controlled, event, properties
514
- )
515
-
516
- onTouch(event)
517
- }
518
- /**
519
- * Triggered on focus events.
520
- * @param event - Focus event object.
521
- *
522
- * @returns Nothing.
523
- */
524
- const onFocus = (event:ReactFocusEvent):void => {
525
- triggerCallbackIfExists<Properties>(
526
- properties, 'focus', controlled, event, properties
527
- )
528
-
529
- onTouch(event)
530
- }
531
- /**
532
- * Triggers on start interacting with the input.
533
- * @param event - Event object which triggered interaction.
534
- *
535
- * @returns Nothing.
536
- */
537
- const onTouch = (event:ReactFocusEvent|ReactMouseEvent):void =>
538
- setValueState((oldValueState:ValueState):ValueState => {
539
- let changedState = false
540
-
541
- if (!oldValueState.modelState.focused) {
542
- properties.focused = true
543
- changedState = true
544
- }
545
-
546
- if (oldValueState.modelState.untouched) {
547
- properties.touched = true
548
- properties.untouched = false
549
- changedState = true
550
- }
551
-
552
- let result:ValueState = oldValueState
553
-
554
- if (changedState) {
555
- onChange(event)
556
-
557
- result = {...oldValueState, modelState: properties.model.state}
558
-
559
- triggerCallbackIfExists<Properties>(
560
- properties,
561
- 'changeState',
562
- controlled,
563
- properties.model.state,
564
- event,
565
- properties
566
- )
567
- }
568
-
569
- triggerCallbackIfExists<Properties>(
570
- properties, 'touch', controlled, event, properties
571
- )
572
-
573
- return result
574
- })
575
- // endregion
576
- // region properties
577
- /// region references
578
- const deleteButtonReference:MutableRefObject<HTMLButtonElement|null> =
579
- useRef<HTMLButtonElement>(null)
580
- const downloadLinkReference:MutableRefObject<HTMLAnchorElement|null> =
581
- useRef<HTMLAnchorElement>(null)
582
- const fileInputReference:MutableRefObject<HTMLInputElement|null> =
583
- useRef<HTMLInputElement>(null)
584
- const nameInputReference:MutableRefObject<InputAdapter<string>|null> =
585
- useRef<InputAdapter<string>>(null)
586
- const uploadButtonReference:MutableRefObject<HTMLDivElement|null> =
587
- useRef<HTMLDivElement>(null)
588
- /// endregion
589
- const givenProps:Props = translateKnownSymbols(props)
590
-
591
- const initialValue:FileValue|null = determineInitialValue<FileValue>(
592
- givenProps, FileInput.defaultProperties.model.default
593
- )
594
- /*
595
- NOTE: Extend default properties with given properties while letting
596
- default property object untouched for unchanged usage in other
597
- instances.
598
- */
599
- const givenProperties:Props = Tools.extend(
600
- true, Tools.copy(FileInput.defaultProperties), givenProps
601
- )
602
- /*
603
- NOTE: This values have to share the same state item since they have to
604
- be updated in one event loop (set state callback).
605
- */
606
- let [valueState, setValueState] = useState<ValueState>({
607
- attachBlobProperty: false,
608
- modelState: {...FileInput.defaultModelState},
609
- value: initialValue
610
- })
611
-
612
- const controlled:boolean =
613
- !givenProperties.enforceUncontrolled &&
614
- (
615
- givenProps.model?.value !== undefined ||
616
- givenProps.value !== undefined
617
- ) &&
618
- Boolean(givenProps.onChange || givenProps.onChangeValue)
619
-
620
- deriveMissingPropertiesFromState(givenProperties, valueState)
621
-
622
- const properties:Properties = getConsolidatedProperties(givenProperties)
623
-
624
- /*
625
- NOTE: We have to merge asynchronous determined missing value properties
626
- to avoid endless rendering loops when a value is provided via
627
- properties.
628
- */
629
- if (valueState.attachBlobProperty && valueState.value)
630
- properties.value =
631
- Tools.extend<FileValue>(true, valueState.value, properties.value!)
632
-
633
- /// region synchronize uncontrolled properties into state
634
- const currentValueState:ValueState = {
635
- attachBlobProperty: false,
636
- modelState: properties.model.state,
637
- value: properties.value as FileValue|null
638
- }
639
- /*
640
- NOTE: If value is controlled only trigger/save state changes when model
641
- state has changed.
642
- */
643
- if (
644
- valueState.attachBlobProperty ||
645
- !(controlled || Tools.equals(properties.value, valueState.value)) ||
646
- !Tools.equals(properties.model.state, valueState.modelState)
647
- )
648
- setValueState(currentValueState)
649
- if (controlled)
650
- setValueState =
651
- wrapStateSetter<ValueState>(setValueState, currentValueState)
652
- /// endregion
653
- useImperativeHandle(
654
- reference,
655
- ():Adapter & {
656
- references:{
657
- deleteButtonReference:MutableRefObject<HTMLButtonElement|null>,
658
- downloadLinkReference:MutableRefObject<HTMLAnchorElement|null>,
659
- fileInputReference:MutableRefObject<HTMLInputElement|null>,
660
- nameInputReference:MutableRefObject<InputAdapter<string>|null>,
661
- uploadButtonReference:MutableRefObject<HTMLDivElement|null>
662
- }
663
- } => ({
664
- properties,
665
- references: {
666
- deleteButtonReference,
667
- downloadLinkReference,
668
- fileInputReference,
669
- nameInputReference,
670
- uploadButtonReference
671
- },
672
- state: {
673
- modelState: properties.model.state,
674
- ...(controlled ? {} : {value: properties.value})
675
- }
676
- })
677
- )
678
- // endregion
679
- useEffect(():void => {
680
- (async ():Promise<void> => {
681
- let valueChanged:null|Partial<FileValue> = null
682
- if (
683
- properties.value?.blob &&
684
- properties.value.blob instanceof Blob &&
685
- !properties.value.source
686
- )
687
- valueChanged = {
688
- source: TEXT_CONTENT_TYPE_REGULAR_EXPRESSION.test(
689
- properties.value.blob.type
690
- ) ?
691
- await readBinaryDataIntoText(
692
- properties.value.blob
693
- ) :
694
- typeof Blob === 'undefined' ?
695
- (properties.value.toString as
696
- unknown as
697
- (_encoding:string) => string
698
- )('base64') :
699
- await blobToBase64String(properties.value.blob)
700
- }
701
-
702
- if (properties.value?.source) {
703
- if (!properties.value.blob)
704
- if (properties.value.url?.startsWith('data:'))
705
- valueChanged = {
706
- blob: dataURLToBlob(properties.value.source)
707
- }
708
- else if (properties.sourceToBlobOptions)
709
- valueChanged = {
710
- blob: new Blob(
711
- [properties.value.source],
712
- properties.sourceToBlobOptions
713
- )
714
- }
715
-
716
- if (!properties.value.url && properties.value.blob?.type) {
717
- const source = TEXT_CONTENT_TYPE_REGULAR_EXPRESSION.test(
718
- properties.value.blob.type
719
- ) ?
720
- btoa(properties.value.source) :
721
- properties.value.source
722
-
723
- valueChanged = {
724
- url:
725
- `data:${properties.value.blob.type};base64,` +
726
- source
727
- }
728
- }
729
- }
730
-
731
- if (valueChanged)
732
- onChangeValue(valueChanged, undefined, undefined, true)
733
- })()
734
- .catch(console.error)
735
- })
736
- // region render
737
- const representationType:RepresentationType =
738
- properties.value?.blob?.type ?
739
- determineRepresentationType(properties.value.blob.type) :
740
- 'binary'
741
- const invalid:boolean = (
742
- properties.invalid &&
743
- (
744
- properties.showInitialValidationState ||
745
- /*
746
- Material inputs show their validation state at least after a
747
- blur event so we synchronize error appearances.
748
- */
749
- properties.visited
750
- )
751
- )
752
-
753
- return <WrapConfigurations
754
- strict={FileInput.strict}
755
- themeConfiguration={properties.themeConfiguration}
756
- tooltip={properties.tooltip}
757
- >
758
- <Card
759
- className={
760
- [CSS_CLASS_NAMES['file-input']]
761
- .concat(properties.className ?? [])
762
- .join(' ')
763
- }
764
- onBlur={onBlur}
765
- onClick={onClick}
766
- onFocus={onFocus}
767
- style={properties.styles}
768
- >
769
- <CardPrimaryAction>
770
- {properties.value?.url ?
771
- representationType === 'image' ?
772
- <CardMedia
773
- {...properties.media}
774
- style={{
775
- backgroundImage: `url(${properties.value.url})`
776
- }}
777
- /> :
778
- representationType === 'video' ?
779
- <video autoPlay loop muted>
780
- <source
781
- src={properties.value.url}
782
- type={properties.value.blob!.type}
783
- />
784
- </video> :
785
- representationType === 'renderableText' ?
786
- <div className={
787
- [CSS_CLASS_NAMES[
788
- 'file-input__iframe-wrapper'
789
- ]]
790
- .concat(
791
- ['text/html', 'text/plain']
792
- .includes(
793
- properties.value.blob!
794
- .type!
795
- ) ?
796
- CSS_CLASS_NAMES[
797
- 'file-input__iframe-' +
798
- 'wrapper--padding'
799
- ] :
800
- []
801
- )
802
- .join(' ')
803
- }>
804
- <iframe
805
- frameBorder="no"
806
- scrolling="no"
807
- src={properties.value.url}
808
- />
809
- </div> :
810
- properties.value?.source &&
811
- representationType === 'text' ?
812
- <pre
813
- className={CSS_CLASS_NAMES[
814
- 'file-input__text-representation'
815
- ]}
816
- >
817
- {properties.value.source}
818
- </pre> :
819
- '' :
820
- properties.value?.blob &&
821
- properties.value.blob instanceof Blob ?
822
- // NOTE: Only blobs have to red asynchronously.
823
- <CircularProgress size="large" /> :
824
- ''
825
- }
826
- <div>
827
- <Typography tag="h2" use="headline6">
828
- {invalid ?
829
- <Theme use="error">{
830
- properties.description ?
831
- properties.description :
832
- properties.name
833
- }</Theme> :
834
- properties.description ?
835
- properties.description :
836
- properties.name
837
- }
838
- </Typography>
839
- {properties.declaration ?
840
- <Typography
841
- style={{marginTop: '-1rem'}}
842
- tag="h3"
843
- theme="textSecondaryOnBackground"
844
- use="subtitle2"
845
- >
846
- {invalid ?
847
- <Theme use="error">
848
- {properties.declaration}
849
- </Theme> :
850
- properties.declaration
851
- }
852
- </Typography> :
853
- ''
854
- }
855
- {properties.value ?
856
- <GenericInput
857
- ref={nameInputReference}
858
- {...properties.generateFileNameInputProperties(
859
- {
860
- disabled: properties.disabled,
861
- value: properties.value?.name,
862
- ...defaultFileNameInputProperties,
863
- model: properties.model.fileName,
864
- onChangeValue: onChangeValue,
865
- default: properties.value.name
866
- },
867
- properties as
868
- Omit<Properties, 'value'> &
869
- {value:FileValue & {name:string}}
870
- )}
871
- /> :
872
- ''
873
- }
874
- {properties.children ?
875
- properties.children({
876
- declaration: properties.declaration,
877
- invalid,
878
- properties,
879
- value: properties.value
880
- }) :
881
- ''
882
- }
883
- </div>
884
- {/* TODO use "accept" attribute for better validation. */}
885
- <input
886
- disabled={properties.disabled}
887
- className={CSS_CLASS_NAMES['file-input__native']}
888
- id={properties.id || properties.name}
889
- name={properties.name}
890
- onChange={onChangeValue}
891
- ref={fileInputReference}
892
- type="file"
893
- />
894
- </CardPrimaryAction>
895
- {!properties.disabled || properties.value ?
896
- <CardActions>
897
- <CardActionButtons>
898
- {!properties.disabled ?
899
- <CardActionButton
900
- onClick={():void =>
901
- fileInputReference.current?.click()
902
- }
903
- ref={uploadButtonReference}
904
- ripple={properties.ripple}
905
- >
906
- {properties.value ?
907
- properties.editButton :
908
- properties.newButton
909
- }
910
- </CardActionButton> :
911
- ''
912
- }
913
- {properties.value ?
914
- <>
915
- {!properties.disabled ?
916
- <CardActionButton
917
- onClick={
918
- ():void => onChangeValue(null)
919
- }
920
- ref={deleteButtonReference}
921
- ripple={properties.ripple}
922
- >
923
- {properties.deleteButton}
924
- </CardActionButton> :
925
- ''
926
- }
927
- {properties.value.url ?
928
- <CardActionButton
929
- onClick={():void =>
930
- downloadLinkReference
931
- .current?.click()
932
- }
933
- ripple={properties.ripple}
934
- >
935
- <a
936
- className={CSS_CLASS_NAMES[
937
- 'file-input__download'
938
- ]}
939
- download={properties.value.name}
940
- href={properties.value.url}
941
- ref={downloadLinkReference}
942
- target="_blank"
943
- {...(properties.value.blob?.type ?
944
- {type:
945
- properties.value.blob.type
946
- } :
947
- {}
948
- )}
949
- >{properties.downloadButton}</a>
950
- </CardActionButton> :
951
- ''
952
- }
953
- </> :
954
- ''
955
- }
956
- </CardActionButtons>
957
- </CardActions> :
958
- ''
959
- }
960
- </Card>
961
- </WrapConfigurations>
962
- // endregion
963
- }
964
- /* eslint-enable jsdoc/require-description-complete-sentence */
965
- // NOTE: This is useful in react dev tools.
966
- FileInputInner.displayName = 'FileInput'
967
- /**
968
- * Wrapping web component compatible react component.
969
- * @property static:defaultModelState - Initial model state.
970
- * @property static:defaultProperties - Initial property configuration.
971
- * @property static:propTypes - Triggers reacts runtime property value checks.
972
- * @property static:strict - Indicates whether we should wrap render output in
973
- * reacts strict component.
974
- * @property static:wrapped - Wrapped component.
975
- *
976
- * @param props - Given components properties.
977
- * @param reference - Reference object to forward internal state.
978
- *
979
- * @returns React elements.
980
- */
981
- export const FileInput:FileInputComponent =
982
- memorize(forwardRef(FileInputInner)) as unknown as FileInputComponent
983
- // region static properties
984
- /// region web-component hints
985
- FileInput.wrapped = FileInputInner
986
- FileInput.webComponentAdapterWrapped = 'react'
987
- /// endregion
988
- FileInput.defaultModelState = defaultModelState
989
- /*
990
- NOTE: We set values to "undefined" to identify whether these values where
991
- provided via "props" and should shadow a state saved valued.
992
- */
993
- FileInput.defaultProperties = {
994
- ...defaultProperties,
995
- model: {
996
- ...defaultProperties.model,
997
- // Trigger initial determination.
998
- state: undefined as unknown as ModelState,
999
- value: undefined
1000
- },
1001
- value: undefined
1002
- }
1003
- FileInput.propTypes = propertyTypes
1004
- FileInput.strict = false
1005
- // endregion
1006
- export default FileInput
1007
- // region vim modline
1008
- // vim: set tabstop=4 shiftwidth=4 expandtab:
1009
- // vim: foldmethod=marker foldmarker=region,endregion:
1010
- // endregion