@openremote/or-mwc-components 1.8.0 → 1.8.1

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,1876 +0,0 @@
1
- import {css, html, LitElement, PropertyValues, TemplateResult, unsafeCSS} from "lit";
2
- import {customElement, property, state} from "lit/decorators.js";
3
- import {Ref, ref, createRef} from "lit/directives/ref.js";
4
- import {classMap} from "lit/directives/class-map.js";
5
- import {ifDefined} from "lit/directives/if-defined.js";
6
- import {when} from 'lit/directives/when.js';
7
- import {until} from 'lit/directives/until.js';
8
- import {MDCTextField} from "@material/textfield";
9
- import {MDCComponent} from "@material/base";
10
- import {MDCRipple} from "@material/ripple";
11
- import {MDCCheckbox} from "@material/checkbox";
12
- import {MDCSwitch} from "@material/switch";
13
- import {MDCSlider, MDCSliderChangeEventDetail} from "@material/slider";
14
- import {MDCSelect, MDCSelectEvent} from "@material/select";
15
- import {MDCList, MDCListActionEvent} from "@material/list";
16
-
17
- import {MDCFormField, MDCFormFieldInput} from "@material/form-field";
18
- import {MDCIconButtonToggle, MDCIconButtonToggleEventDetail} from "@material/icon-button";
19
- import {DefaultColor4, DefaultColor5, DefaultColor8, Util} from "@openremote/core";
20
- import "@openremote/or-icon";
21
- import {OrIcon} from "@openremote/or-icon";
22
- import {
23
- AssetDescriptor,
24
- Attribute,
25
- AttributeDescriptor,
26
- MetaHolder,
27
- NameHolder,
28
- NameValueHolder,
29
- ValueConstraint,
30
- ValueConstraintAllowedValues,
31
- ValueConstraintFuture,
32
- ValueConstraintFutureOrPresent,
33
- ValueConstraintMax,
34
- ValueConstraintMin,
35
- ValueConstraintNotBlank,
36
- ValueConstraintNotEmpty,
37
- ValueConstraintNotNull,
38
- ValueConstraintPast,
39
- ValueConstraintPastOrPresent,
40
- ValueConstraintPattern,
41
- ValueConstraintSize,
42
- ValueDescriptor,
43
- ValueDescriptorHolder,
44
- ValueFormat,
45
- ValueFormatStyleRepresentation,
46
- ValueHolder,
47
- WellknownMetaItems,
48
- WellknownValueTypes
49
- } from "@openremote/model";
50
- import {getItemTemplate, getListTemplate, ListItem, ListType} from "./or-mwc-list";
51
- import { i18next } from "@openremote/or-translate";
52
- import { styleMap } from "lit/directives/style-map.js";
53
-
54
- // TODO: Add webpack/rollup to build so consumers aren't forced to use the same tooling
55
- const buttonStyle = require("@material/button/dist/mdc.button.css");
56
- const buttonFabStyle = require("@material/fab/dist/mdc.fab.css");
57
- const iconButtonStyle = require("@material/icon-button/dist/mdc.icon-button.css");
58
- const textfieldStyle = require("@material/textfield/dist/mdc.textfield.css");
59
- const rippleStyle = require("@material/ripple/dist/mdc.ripple.css");
60
- const lineRippleStyle = require("@material/line-ripple/dist/mdc.line-ripple.css");
61
- const floatingLabelStyle = require("@material/floating-label/dist/mdc.floating-label.css");
62
- const formFieldStyle = require("@material/form-field/dist/mdc.form-field.css");
63
- const checkboxStyle = require("@material/checkbox/dist/mdc.checkbox.css");
64
- const radioStyle = require("@material/radio/dist/mdc.radio.css");
65
- const switchStyle = require("@material/switch/dist/mdc.switch.css");
66
- const selectStyle = require("@material/select/dist/mdc.select.css");
67
- const listStyle = require("@material/list/dist/mdc.list.css");
68
- const menuSurfaceStyle = require("@material/menu-surface/dist/mdc.menu-surface.css");
69
- const menuStyle = require("@material/menu/dist/mdc.menu.css");
70
- const sliderStyle = require("@material/slider/dist/mdc.slider.css");
71
-
72
- export class OrInputChangedEvent extends CustomEvent<OrInputChangedEventDetail> {
73
-
74
- public static readonly NAME = "or-mwc-input-changed";
75
-
76
- constructor(value?: any, previousValue?: any, enterPressed?: boolean) {
77
- super(OrInputChangedEvent.NAME, {
78
- detail: {
79
- value: value,
80
- previousValue: previousValue,
81
- enterPressed: enterPressed
82
- },
83
- bubbles: true,
84
- composed: true
85
- });
86
- }
87
- }
88
-
89
- export interface OrInputChangedEventDetail {
90
- value?: any;
91
- previousValue?: any;
92
- enterPressed?: boolean;
93
- }
94
-
95
- declare global {
96
- export interface HTMLElementEventMap {
97
- [OrInputChangedEvent.NAME]: OrInputChangedEvent;
98
- }
99
- }
100
-
101
- export enum InputType {
102
- BUTTON = "button",
103
- BUTTON_TOGGLE = "button-toggle",
104
- BUTTON_MOMENTARY = "button-momentary",
105
- CHECKBOX = "checkbox",
106
- CHECKBOX_LIST = "checkbox-list",
107
- COLOUR = "color",
108
- DATE = "date",
109
- DATETIME = "datetime-local",
110
- EMAIL = "email",
111
- JSON = "json",
112
- JSON_OBJECT = "json-object",
113
- MONTH = "month",
114
- NUMBER = "number",
115
- BIG_INT = "big-int",
116
- PASSWORD = "password",
117
- RADIO = "radio",
118
- SWITCH = "switch",
119
- RANGE = "range",
120
- TELEPHONE = "tel",
121
- TEXT = "text",
122
- TEXTAREA = "textarea",
123
- TIME = "time",
124
- URL = "url",
125
- WEEK = "week",
126
- SELECT = "select",
127
- LIST = "list",
128
- CRON = "cron",
129
- DURATION = "duration",
130
- DURATION_TIME = "duration-time",
131
- DURATION_PERIOD = "duration-period"
132
- }
133
-
134
- export interface ValueInputProviderOptions {
135
- label?: string;
136
- required?: boolean;
137
- readonly?: boolean;
138
- disabled?: boolean;
139
- compact?: boolean;
140
- rounded?: boolean;
141
- outlined?: boolean;
142
- comfortable?: boolean;
143
- resizeVertical?: boolean;
144
- inputType?: InputType;
145
- }
146
-
147
- export interface ValueInputProvider {
148
- templateFunction: ValueInputTemplateFunction;
149
- supportsHelperText: boolean;
150
- supportsLabel: boolean;
151
- supportsSendButton: boolean;
152
- validator?: () => boolean;
153
- }
154
-
155
- export type ValueInputTemplateFunction = ((value: any, focused: boolean, loading: boolean, sending: boolean, error: boolean, helperText: string | undefined) => TemplateResult | PromiseLike<TemplateResult>) | undefined;
156
-
157
- export type ValueInputProviderGenerator = (assetDescriptor: AssetDescriptor | string, valueHolder: NameHolder & ValueHolder<any> | undefined, valueHolderDescriptor: ValueDescriptorHolder | undefined, valueDescriptor: ValueDescriptor, valueChangeNotifier: (value: OrInputChangedEventDetail | undefined) => void, options: ValueInputProviderOptions) => ValueInputProvider;
158
-
159
- function inputTypeSupportsButton(inputType: InputType): boolean {
160
- return inputType === InputType.NUMBER
161
- || inputType === InputType.BIG_INT
162
- || inputType === InputType.TELEPHONE
163
- || inputType === InputType.TEXT
164
- || inputType === InputType.PASSWORD
165
- || inputType === InputType.DATE
166
- || inputType === InputType.DATETIME
167
- || inputType === InputType.EMAIL
168
- || inputType === InputType.JSON
169
- || inputType === InputType.JSON_OBJECT
170
- || inputType === InputType.MONTH
171
- || inputType === InputType.TEXTAREA
172
- || inputType === InputType.TIME
173
- || inputType === InputType.URL
174
- || inputType === InputType.WEEK;
175
- }
176
-
177
- function inputTypeSupportsHelperText(inputType: InputType) {
178
- return inputTypeSupportsButton(inputType) || inputType === InputType.SELECT;
179
- }
180
-
181
- function inputTypeSupportsLabel(inputType: InputType) {
182
- return inputTypeSupportsHelperText(inputType) || inputType === InputType.CHECKBOX || inputType === InputType.BUTTON_MOMENTARY;
183
- }
184
-
185
- export const getValueHolderInputTemplateProvider: ValueInputProviderGenerator = (assetDescriptor, valueHolder, valueHolderDescriptor, valueDescriptor, valueChangeNotifier, options) => {
186
-
187
- let inputType: InputType | undefined = options.inputType;
188
- let step: number | undefined;
189
- let pattern: string | undefined;
190
- let min: any;
191
- let max: any;
192
- let multiple: any;
193
- let required: boolean | undefined;
194
- let selectOptions: [string, string][] | undefined;
195
- let valueConverter: (v: any) => any | undefined;
196
- const styles = {} as any;
197
-
198
- const assetType = typeof assetDescriptor === "string" ? assetDescriptor : assetDescriptor.name;
199
- const constraints: ValueConstraint[] = (valueHolder && ((valueHolder as MetaHolder).meta) || (valueDescriptor && (valueDescriptor as MetaHolder).meta) ? Util.getAttributeValueConstraints(valueHolder as Attribute<any>, valueHolderDescriptor as AttributeDescriptor, assetType) : Util.getMetaValueConstraints(valueHolder as NameValueHolder<any>, valueHolderDescriptor as AttributeDescriptor, assetType)) || [];
200
- const format: ValueFormat | undefined = (valueHolder && ((valueHolder as MetaHolder).meta) || (valueDescriptor && (valueDescriptor as MetaHolder).meta) ? Util.getAttributeValueFormat(valueHolder as Attribute<any>, valueHolderDescriptor as AttributeDescriptor, assetType) : Util.getMetaValueFormat(valueHolder as Attribute<any>, valueHolderDescriptor as AttributeDescriptor, assetType));
201
-
202
- // Determine input type
203
- if (!inputType) {
204
- switch (valueDescriptor.name) {
205
- case WellknownValueTypes.TEXT:
206
- case WellknownValueTypes.EMAIL:
207
- case WellknownValueTypes.UUID:
208
- case WellknownValueTypes.ASSETID:
209
- case WellknownValueTypes.HOSTORIPADDRESS:
210
- case WellknownValueTypes.IPADDRESS:
211
- inputType = Util.getMetaValue(WellknownMetaItems.MULTILINE, valueHolder, valueHolderDescriptor) === true ? InputType.TEXTAREA : InputType.TEXT;
212
- break;
213
- case WellknownValueTypes.BOOLEAN:
214
- if (format && format.asNumber) {
215
- inputType = InputType.NUMBER;
216
- step = 1;
217
- min = 0;
218
- max = 1;
219
- valueConverter = (v) => !!v;
220
- break;
221
- }
222
- if (format && (format.asOnOff || format.asOpenClosed)) {
223
- inputType = InputType.SWITCH;
224
- } else {
225
- inputType = InputType.CHECKBOX;
226
- }
227
-
228
- if (format && format.asMomentary || (Util.getMetaValue(WellknownMetaItems.MOMENTARY, valueHolder, valueHolderDescriptor) === true) ) {
229
- inputType = InputType.BUTTON_MOMENTARY;
230
- }
231
- break;
232
- case WellknownValueTypes.BIGNUMBER:
233
- case WellknownValueTypes.NUMBER:
234
- case WellknownValueTypes.POSITIVEINTEGER:
235
- case WellknownValueTypes.POSITIVENUMBER:
236
- case WellknownValueTypes.LONG:
237
- case WellknownValueTypes.INTEGER:
238
- case WellknownValueTypes.BYTE:
239
- case WellknownValueTypes.INTEGERBYTE:
240
- case WellknownValueTypes.DIRECTION:
241
- case WellknownValueTypes.TCPIPPORTNUMBER:
242
- if (valueDescriptor.name === WellknownValueTypes.BYTE || valueDescriptor.name === WellknownValueTypes.INTEGERBYTE) {
243
- min = 0;
244
- max = 255;
245
- step = 1;
246
- } else if (valueDescriptor.name === WellknownValueTypes.INTEGER || valueDescriptor.name === WellknownValueTypes.LONG) {
247
- step = 1;
248
- }
249
- if (format && format.asDate) {
250
- inputType = InputType.DATETIME;
251
- } else if (format && format.asBoolean) {
252
- inputType = InputType.CHECKBOX;
253
- valueConverter = (v) => v ? 1 : 0;
254
- } else if (format && format.asSlider) {
255
- inputType = InputType.RANGE;
256
- } else {
257
- inputType = InputType.NUMBER;
258
- }
259
- break;
260
- case WellknownValueTypes.BIGINTEGER:
261
- inputType = InputType.BIG_INT;
262
- step = 1;
263
- break;
264
- case WellknownValueTypes.COLOURRGB:
265
- inputType = InputType.COLOUR;
266
- break;
267
- case WellknownValueTypes.DATEANDTIME:
268
- case WellknownValueTypes.TIMESTAMP:
269
- case WellknownValueTypes.TIMESTAMPISO8601:
270
- inputType = InputType.DATETIME;
271
- break;
272
- case WellknownValueTypes.CRONEXPRESSION:
273
- inputType = InputType.CRON;
274
- break;
275
- case WellknownValueTypes.TIMEDURATIONISO8601:
276
- inputType = InputType.DURATION_TIME;
277
- break;
278
- case WellknownValueTypes.PERIODDURATIONISO8601:
279
- inputType = InputType.DURATION_PERIOD;
280
- break;
281
- case WellknownValueTypes.TIMEANDPERIODDURATIONISO8601:
282
- inputType = InputType.DURATION;
283
- break;
284
- case WellknownValueTypes.JSONOBJECT:
285
- inputType = InputType.JSON_OBJECT;
286
- break;
287
- }
288
-
289
- if (valueDescriptor.arrayDimensions && valueDescriptor.arrayDimensions > 0) {
290
- inputType = InputType.JSON;
291
- }
292
- }
293
-
294
- if (!inputType) {
295
- switch (valueDescriptor.jsonType) {
296
- case "number":
297
- case "bigint":
298
- inputType = InputType.NUMBER;
299
- break;
300
- case "boolean":
301
- inputType = InputType.CHECKBOX;
302
- break;
303
- case "string":
304
- inputType = InputType.TEXT;
305
- break;
306
- case "date":
307
- inputType = InputType.DATETIME;
308
- break;
309
- }
310
- }
311
-
312
- if (!inputType) {
313
- inputType = InputType.JSON;
314
- }
315
-
316
- // Apply any constraints
317
- const sizeConstraint = constraints && constraints.find(c => c.type === "size") as ValueConstraintSize;
318
- const patternConstraint = constraints && constraints.find(c => c.type === "pattern") as ValueConstraintPattern;
319
- const minConstraint = constraints && constraints.find(c => c.type === "min") as ValueConstraintMin;
320
- const maxConstraint = constraints && constraints.find(c => c.type === "max") as ValueConstraintMax;
321
- const allowedValuesConstraint = constraints && constraints.find(c => c.type === "allowedValues") as ValueConstraintAllowedValues;
322
- const pastConstraint = constraints && constraints.find(c => c.type === "past") as ValueConstraintPast;
323
- const pastOrPresentConstraint = constraints && constraints.find(c => c.type === "pastOrPresent") as ValueConstraintPastOrPresent;
324
- const futureConstraint = constraints && constraints.find(c => c.type === "future") as ValueConstraintFuture;
325
- const futureOrPresentConstraint = constraints && constraints.find(c => c.type === "futureOrPresent") as ValueConstraintFutureOrPresent;
326
- const notEmptyConstraint = constraints && constraints.find(c => c.type === "notEmpty") as ValueConstraintNotEmpty;
327
- const notBlankConstraint = constraints && constraints.find(c => c.type === "notBlank") as ValueConstraintNotBlank;
328
- const notNullConstraint = constraints && constraints.find(c => c.type === "notNull") as ValueConstraintNotNull;
329
-
330
- if (sizeConstraint) {
331
- min = sizeConstraint.min;
332
- max = sizeConstraint.max;
333
- }
334
- if (sizeConstraint) {
335
- min = sizeConstraint.min;
336
- max = sizeConstraint.max;
337
- }
338
- if (minConstraint) {
339
- min = minConstraint.min;
340
- }
341
- if (maxConstraint) {
342
- max = maxConstraint.max;
343
- }
344
- if (patternConstraint) {
345
- pattern = patternConstraint.regexp;
346
- }
347
- if (notNullConstraint) {
348
- required = true;
349
- }
350
- if (notBlankConstraint && !pattern) {
351
- pattern = "\\S+";
352
- } else if (notEmptyConstraint && !pattern) {
353
- pattern = ".+";
354
- }
355
- if (allowedValuesConstraint && allowedValuesConstraint.allowedValues) {
356
- const allowedLabels = allowedValuesConstraint.allowedValueNames && allowedValuesConstraint.allowedValueNames.length === allowedValuesConstraint.allowedValues.length ? allowedValuesConstraint.allowedValueNames : undefined;
357
- selectOptions = allowedValuesConstraint.allowedValues.map((v, i) => {
358
- let label = allowedLabels ? allowedLabels[i] : "" + v;
359
- label = Util.getAllowedValueLabel(label)!;
360
- return [v, label || "" + v];
361
- });
362
- inputType = InputType.SELECT;
363
-
364
- if (valueDescriptor.arrayDimensions && valueDescriptor.arrayDimensions > 0) {
365
- multiple = true;
366
- }
367
- }
368
-
369
- if (inputType === InputType.DATETIME) {
370
- if (pastConstraint || pastOrPresentConstraint) {
371
- min = undefined;
372
- max = new Date();
373
- } else if (futureConstraint || futureOrPresentConstraint) {
374
- min = new Date();
375
- max = undefined;
376
- }
377
-
378
- // Refine the input type based on formatting
379
- if (format) {
380
- if (format.timeStyle && !format.dateStyle) {
381
- inputType = InputType.TIME;
382
- } else if (format.dateStyle && !format.timeStyle) {
383
- inputType = InputType.DATE;
384
- }
385
- }
386
- }
387
-
388
- if (inputType === InputType.NUMBER && format && format.resolution) {
389
- step = format.resolution;
390
- }
391
-
392
- if (inputType === InputType.COLOUR) {
393
- styles.marginLeft = "24px"
394
- }
395
-
396
- const supportsHelperText = inputTypeSupportsHelperText(inputType);
397
- const supportsLabel = inputTypeSupportsLabel(inputType);
398
- const supportsSendButton = inputTypeSupportsButton(inputType);
399
- const readonly = options.readonly;
400
- required = required || options.required;
401
- const comfortable = options.comfortable;
402
- const resizeVertical = options.resizeVertical;
403
- const inputRef: Ref<OrMwcInput> = createRef();
404
-
405
- const templateFunction: ValueInputTemplateFunction = (value, focused, loading, sending, error, helperText) => {
406
-
407
- const disabled = options.disabled || loading || sending;
408
- const label = supportsLabel ? options.label : undefined;
409
-
410
- return html`<or-mwc-input ${ref(inputRef)} id="input" style="${styleMap(styles)}" .type="${inputType}" .label="${label}" .value="${value}" .pattern="${pattern}"
411
- .min="${min}" .max="${max}" .format="${format}" .focused="${focused}" .required="${required}" .multiple="${multiple}"
412
- .options="${selectOptions}" .comfortable="${comfortable}" .readonly="${readonly}" .disabled="${disabled}" .step="${step}"
413
- .helperText="${helperText}" .helperPersistent="${true}" .resizeVertical="${resizeVertical}"
414
- .rounded="${options.rounded}"
415
- .outlined="${options.outlined}"
416
- @or-mwc-input-changed="${(e: OrInputChangedEvent) => {
417
- e.stopPropagation();
418
- e.detail.value = valueConverter ? valueConverter(e.detail.value) : e.detail.value;
419
- valueChangeNotifier(e.detail);
420
- }}"></or-mwc-input>`
421
- };
422
-
423
- return {
424
- templateFunction: templateFunction,
425
- supportsHelperText: supportsHelperText,
426
- supportsSendButton: supportsSendButton,
427
- supportsLabel: supportsLabel,
428
- validator: () => {
429
- if (!inputRef.value) {
430
- return false;
431
- }
432
- return inputRef.value.checkValidity();
433
- }
434
- };
435
- }
436
-
437
- // language=CSS
438
- const style = css`
439
-
440
- :host {
441
- display: inline-block;
442
- --internal-or-mwc-input-color: var(--or-mwc-input-color, var(--or-app-color4, ${unsafeCSS(DefaultColor4)}));
443
- --internal-or-mwc-input-text-color: var(--or-mwc-input-text-color, var(--or-app-color8, ${unsafeCSS(DefaultColor8)}));
444
-
445
- --mdc-theme-primary: var(--internal-or-mwc-input-color);
446
- --mdc-theme-on-primary: var(--internal-or-mwc-input-text-color);
447
- --mdc-theme-secondary: var(--internal-or-mwc-input-color);
448
- }
449
-
450
- :host([hidden]) {
451
- display: none;
452
- }
453
-
454
- :host([type=select]) {
455
- height: 56px;
456
- }
457
-
458
- #wrapper {
459
- display: flex;
460
- align-items: center;
461
- min-height: 48px;
462
- height: 100%;
463
- }
464
-
465
- #wrapper > label {
466
- white-space: nowrap;
467
- margin-right: 20px;
468
- }
469
-
470
- #component {
471
- max-width: 100%;
472
- }
473
-
474
- .mdc-text-field {
475
- flex: 1 1 0;
476
- }
477
-
478
- .mdc-list {
479
- flex: 1;
480
- overflow: auto;
481
- }
482
-
483
- .mdc-select__anchor {
484
- max-width: 100%;
485
- width: 100%;
486
- }
487
-
488
- .mdc-checkbox-list input {
489
- display: none;
490
- }
491
-
492
- .mdc-checkbox-list label {
493
- display: block;
494
- border-radius: 50%;
495
- text-align: center;
496
- width: 32px;
497
- line-height: 32px;
498
- height: 32px;
499
- cursor: pointer;
500
- background-color: var(--or-app-color2);
501
- font-size: 13px;
502
- }
503
-
504
- input::-webkit-calendar-picker-indicator {
505
- margin: 0;
506
- }
507
-
508
- .mdc-checkbox-list .mdc-checkbox {
509
- padding: 0;
510
- height: 32px;
511
- width: 32px;
512
- }
513
- .mdc-radio-container {
514
- display: flex;
515
- flex-direction: column;
516
- }
517
- .mdc-text-field.mdc-text-field--invalid:not(.mdc-text-field--disabled) + .mdc-text-field-helper-line .mdc-text-field-helper-text {
518
- color: var(--mdc-theme-error, #b00020)
519
- }
520
-
521
- .mdc-checkbox-list input:checked + label {
522
- color: var(--or-app-color2);
523
- background-color: var(--mdc-theme-primary);
524
- }
525
-
526
- .mdc-button--rounded,
527
- .or-mwc-input--rounded {
528
- border-radius: 24px !important;
529
- --mdc-shape-small: 32px;
530
- }
531
-
532
- #select-searchable {
533
- background-color: transparent;
534
- border: 1px solid var(--or-app-color5, ${unsafeCSS(DefaultColor5)});
535
- margin: 8px;
536
- width: calc(100% - 16px);
537
- border-radius: 4px;
538
- padding: 4px 16px;
539
- flex: 0 0 auto;
540
- align-items: center;
541
- height: auto;
542
- }
543
-
544
- .mdc-text-field__input::-webkit-calendar-picker-indicator {
545
- display: block;
546
- }
547
-
548
- ::-webkit-clear-button {display: none;}
549
- ::-webkit-inner-spin-button { display: none; }
550
- ::-webkit-datetime-edit { padding: 0em; }
551
- ::-webkit-datetime-edit-text { padding: 0; }
552
-
553
- .mdc-text-field--focused:not(.mdc-text-field--disabled) .mdc-floating-label {
554
- color: var(--mdc-theme-primary);
555
- }
556
- .mdc-text-field--focused .mdc-text-field__input:required ~ .mdc-floating-label::after,
557
- .mdc-text-field--focused .mdc-text-field__input:required ~ .mdc-notched-outline .mdc-floating-label::after {
558
- color: var(--mdc-theme-primary);
559
- }
560
-
561
- .mdc-text-field__input.resize-vertical {
562
- resize: vertical;
563
- }
564
-
565
- .mdc-text-field, .mdc-text-field-helper-line {
566
- width: 100%;
567
- }
568
-
569
- .mdc-text-field.dense-comfortable, .mdc-select.dense-comfortable {
570
- height: 48px;
571
- }
572
-
573
- .mdc-text-field.dense-compact {
574
- height: 36px;
575
- }
576
-
577
- .mdc-select:not(.mdc-list) {
578
- white-space: nowrap;
579
- display: flex;
580
- flex-direction: column;
581
- }
582
-
583
- .mdc-select:not(.mdc-select--disabled).mdc-select--focused .mdc-floating-label {
584
- color: var(--mdc-theme-primary);
585
- }
586
-
587
- .mdc-select-helper-text {
588
- white-space: normal;
589
- color: rgba(0, 0, 0, 0.6);
590
- }
591
-
592
- .mdc-icon-button {
593
- padding: 0;
594
- color: var(--internal-or-mwc-input-color);
595
- }
596
-
597
- /* Give slider min width like select etc. */
598
- .mdc-slider {
599
- min-width: 200px;
600
- flex: 1;
601
- }
602
-
603
- .mdc-switch {
604
- margin: 0 24px;
605
- }
606
-
607
- .mdc-switch--full-width {
608
- margin-left: auto;
609
- }
610
- .mdc-button--fullwidth {
611
- width: 100%;
612
- }
613
- #field {
614
- height: 100%;
615
- }
616
-
617
- .mdc-select__menu .mdc-list .mdc-list-item.mdc-list-item--selected or-icon {
618
- --or-icon-fill: var(--or-app-color4);
619
- }
620
-
621
- .mdc-menu__searchable {
622
- overflow: hidden;
623
- }
624
- .mdc-menu__searchable.mdc-menu-surface--open {
625
- display: flex;
626
- flex-direction: column-reverse;
627
- }
628
- .mdc-menu__searchable.mdc-menu-surface--is-open-below {
629
- flex-direction: column;
630
- }
631
-
632
- /* Prevent mouse events being fired from inside the or-icon shadowDOM */
633
- .mdc-list-item__graphic > or-icon {
634
- pointer-events: none;
635
- }
636
- `;
637
-
638
- @customElement("or-mwc-input")
639
- export class OrMwcInput extends LitElement {
640
-
641
- static get styles() {
642
- return [
643
- css`${unsafeCSS(iconButtonStyle)}`,
644
- css`${unsafeCSS(buttonStyle)}`,
645
- css`${unsafeCSS(buttonFabStyle)}`,
646
- css`${unsafeCSS(textfieldStyle)}`,
647
- css`${unsafeCSS(rippleStyle)}`,
648
- css`${unsafeCSS(lineRippleStyle)}`,
649
- css`${unsafeCSS(floatingLabelStyle)}`,
650
- css`${unsafeCSS(formFieldStyle)}`,
651
- css`${unsafeCSS(checkboxStyle)}`,
652
- css`${unsafeCSS(radioStyle)}`,
653
- css`${unsafeCSS(switchStyle)}`,
654
- css`${unsafeCSS(selectStyle)}`,
655
- css`${unsafeCSS(listStyle)}`,
656
- css`${unsafeCSS(menuStyle)}`,
657
- css`${unsafeCSS(menuSurfaceStyle)}`,
658
- css`${unsafeCSS(sliderStyle)}`,
659
- style
660
- ];
661
- }
662
-
663
- @property({type: Boolean})
664
- public focused?: boolean;
665
-
666
- @property()
667
- public value?: any;
668
-
669
- @property({type: String})
670
- public type?: InputType;
671
-
672
- @property({type: String})
673
- public name?: String;
674
-
675
- @property({type: Boolean})
676
- public readonly: boolean = false;
677
-
678
- @property({type: Boolean})
679
- public required: boolean = false;
680
-
681
- @property()
682
- public max?: any;
683
-
684
- @property()
685
- public min?: any;
686
-
687
- @property({type: Number})
688
- public step?: number;
689
-
690
- @property({type: Boolean})
691
- public checked: boolean = false;
692
-
693
- @property({type: Boolean})
694
- public indeterminate: boolean = false;
695
-
696
- @property({type: Number})
697
- public maxLength?: number;
698
-
699
- @property({type: Number})
700
- public minLength?: number;
701
-
702
- @property({type: Number})
703
- public rows?: number;
704
-
705
- @property({type: Number})
706
- public cols?: number;
707
-
708
- @property({type: Boolean})
709
- public multiple: boolean = false;
710
-
711
- @property({type: String, attribute: true, reflect: false})
712
- public pattern?: string;
713
-
714
- @property({type: String})
715
- public placeHolder?: string;
716
-
717
- @property({type: Array})
718
- public options?: any[] | any;
719
-
720
- @property({type: Boolean})
721
- public autoSelect?: boolean;
722
-
723
- @property({type: Object})
724
- public searchProvider?: (search?: string) => Promise<[any, string][]>
725
-
726
- @property({type: String})
727
- public searchLabel = "search"
728
-
729
- /* STYLING PROPERTIES BELOW */
730
-
731
- @property({type: String})
732
- public icon?: string;
733
-
734
- @property({type: String})
735
- public iconColor?: string;
736
-
737
- @property({type: String})
738
- public iconOn?: string;
739
-
740
- @property({type: String})
741
- public iconTrailing?: string;
742
-
743
- @property({type: Boolean})
744
- public compact: boolean = false;
745
-
746
- @property({type: Boolean})
747
- public comfortable: boolean = false;
748
-
749
- /* BUTTON STYLES START */
750
-
751
- @property({type: Boolean})
752
- public raised: boolean = false;
753
-
754
- @property({type: Boolean})
755
- public action: boolean = false;
756
-
757
- @property({type: Boolean})
758
- public unElevated: boolean = false;
759
-
760
- @property({type: Boolean})
761
- public outlined: boolean = false;
762
-
763
- @property({type: Boolean})
764
- public rounded: boolean = false;
765
-
766
- @property({type: Object})
767
- public format?: ValueFormat;
768
-
769
- @property({type: Boolean})
770
- public disableSliderNumberInput: boolean = false;
771
-
772
- /* BUTTON STYLES END */
773
-
774
- /* TEXT INPUT STYLES START */
775
-
776
- @property({type: Boolean})
777
- public fullWidth: boolean = false;
778
-
779
- @property({type: String})
780
- public helperText?: string;
781
-
782
- @property({type: Boolean})
783
- public helperPersistent: boolean = false;
784
-
785
- @property({type: String, attribute: true})
786
- public validationMessage?: string;
787
-
788
- @property({type: Boolean})
789
- public autoValidate = false;
790
-
791
- @property({type: Boolean})
792
- public charCounter: boolean = false;
793
-
794
- @property({type: String})
795
- public label?: string;
796
-
797
- @property({type: Boolean})
798
- public disabled: boolean = false;
799
-
800
- @property({type: Boolean})
801
- public continuous: boolean = false;
802
-
803
- @property({type: Boolean})
804
- public resizeVertical: boolean = false;
805
-
806
- /**
807
- * Always censure text fields (like a password), and do not allow toggling
808
- */
809
- @property({type: Boolean})
810
- public censored: boolean = false;
811
-
812
- /**
813
- * Toggles visibility state of the password InputType (true = shown, false = hidden)
814
- */
815
- @property({type: Boolean, reflect: true})
816
- public advertised: boolean = false;
817
-
818
- public get nativeValue(): any {
819
- if (this._mdcComponent) {
820
- return (this._mdcComponent as any).value;
821
- }
822
- }
823
-
824
- /* TEXT INPUT STYLES END */
825
-
826
- protected _mdcComponent?: MDCComponent<any>;
827
- protected _mdcComponent2?: MDCComponent<any>;
828
- protected _selectedIndex = -1;
829
- protected _menuObserver?: IntersectionObserver;
830
- protected _tempValue: any;
831
- @state()
832
- protected isUiValid = true;
833
- @state()
834
- public searchableValue?: string;
835
- @state()
836
- protected errorMessage?: string;
837
-
838
- disconnectedCallback(): void {
839
- super.disconnectedCallback();
840
- if (this._mdcComponent) {
841
- this._mdcComponent.destroy();
842
- this._mdcComponent = undefined;
843
- this._menuObserver?.disconnect()
844
- }
845
- if (this._mdcComponent2) {
846
- this._mdcComponent2.destroy();
847
- this._mdcComponent2 = undefined;
848
- this._menuObserver?.disconnect();
849
- }
850
- }
851
-
852
- protected shouldUpdate(_changedProperties: PropertyValues) {
853
- if(_changedProperties.has("indeterminate")) {
854
- if(this._mdcComponent && this.type === InputType.CHECKBOX){
855
- (this._mdcComponent as any).indeterminate = this.indeterminate;
856
- }
857
- }
858
-
859
- if (_changedProperties.has("disabled")) {
860
- if (this._mdcComponent) {
861
- (this._mdcComponent as any).disabled = this.disabled;
862
- }
863
- if (this.type === InputType.RANGE && this._mdcComponent2) {
864
- (this._mdcComponent2 as any).disabled = this.disabled;
865
- }
866
- }
867
-
868
- if (_changedProperties.has("readonly")) {
869
- if (this._mdcComponent) {
870
- (this._mdcComponent as any).readonly = this.readonly;
871
- }
872
- if (this.type === InputType.RANGE && this._mdcComponent2) {
873
- (this._mdcComponent2 as any).readonly = this.readonly;
874
- }
875
- }
876
-
877
- if (!this.type && this.value) {
878
- if (this.value instanceof Date) {
879
- this.type = InputType.DATETIME;
880
- } else if (typeof(this.value) === "boolean") {
881
- this.type = InputType.CHECKBOX;
882
- } else if (typeof(this.value) === "number") {
883
- this.type = InputType.NUMBER;
884
- } else if (typeof(this.value) === "string") {
885
- this.type = InputType.TEXT;
886
- } else {
887
- this.type = InputType.JSON;
888
- }
889
- }
890
- return true;
891
- }
892
-
893
- public focus() {
894
- if (this.type === InputType.RANGE && this._mdcComponent2) {
895
- (this._mdcComponent2 as any).focus();
896
- } else if (this._mdcComponent && typeof (this._mdcComponent as any).focus === "function") {
897
- (this._mdcComponent as any).focus();
898
- }
899
- }
900
-
901
-
902
- protected render() {
903
-
904
- if (this.type) {
905
-
906
- const showLabel = !this.fullWidth && this.label;
907
- let outlined = !this.fullWidth && this.outlined;
908
- let hasHelper = !!this.helperText;
909
- const showValidationMessage = !this.isUiValid && (!!this.errorMessage || !!this.validationMessage);
910
- const helperClasses = {
911
- "mdc-text-field-helper-text--persistent": !showValidationMessage && this.helperPersistent,
912
- "mdc-text-field-helper-text--validation-msg": showValidationMessage,
913
- };
914
- const hasValue = (this.value !== null && this.value !== undefined) || this.value === false;
915
- let labelTemplate = showLabel ? html`<span class="mdc-floating-label ${hasValue ? "mdc-floating-label--float-above" : ""}" id="label">${this.label}</span>` : undefined;
916
-
917
- switch (this.type) {
918
- case InputType.RADIO:
919
- const optsRadio = this.resolveOptions(this.options);
920
- this._selectedIndex = -1;
921
- return html`
922
- <div class="mdc-radio-container">
923
- ${optsRadio ? optsRadio.map(([optValue, optDisplay], index) => {
924
- if (this.value === optValue) {
925
- this._selectedIndex = index;
926
- }
927
- return html`
928
- <div id="field" class="mdc-form-field">
929
- <div class="mdc-radio">
930
- <input type="radio"
931
- id="elem-${optValue}"
932
- name="${ifDefined(this.name)}"
933
- value="${optValue}"
934
- ?checked="${this.value && this.value.includes(optValue)}"
935
- ?required="${this.required}"
936
- ?disabled="${this.disabled || this.readonly}"
937
- @change="${(e: Event) => this.onValueChange((e.target as HTMLInputElement), optValue)}"
938
- class="mdc-radio__native-control"/>
939
- <div class="mdc-radio__background">
940
- <div class="mdc-radio__outer-circle"></div>
941
- <div class="mdc-radio__inner-circle"></div>
942
- </div>
943
- <div class="mdc-radio__ripple"></div>
944
- </div>
945
- <label for="elem-${optValue}"><or-translate value="${optDisplay}"></or-translate></label>
946
- </div>
947
-
948
- `;
949
- }) : ``}
950
- </div>
951
- `;
952
- case InputType.SWITCH:
953
- const classesSwitch = {
954
- "mdc-switch--disabled": this.disabled || this.readonly,
955
- "mdc-switch--full-width": this.fullWidth,
956
- "mdc-switch--checked": this.value,
957
- };
958
-
959
- return html`
960
- <span id="wrapper">
961
- ${this.label ? html`<label for="elem" class="${this.disabled ? "mdc-switch--disabled" : ""}">${this.label}</label>` : ``}
962
- <div id="component" class="mdc-switch ${classMap(classesSwitch)}">
963
- <div class="mdc-switch__track"></div>
964
- <div class="mdc-switch__thumb-underlay">
965
- <div class="mdc-switch__thumb">
966
- <input type="checkbox" id="elem" class="mdc-switch__native-control"
967
- ?checked="${this.value}"
968
- ?required="${this.required}"
969
- ?disabled="${this.disabled || this.readonly}"
970
- @change="${(e: Event) => this.onValueChange((e.target as HTMLInputElement), (e.target as HTMLInputElement).checked)}"
971
- role="switch">
972
- </div>
973
- </div>
974
- </div>
975
- </span>
976
- `;
977
- case InputType.LIST:
978
- const classesList = {
979
- "mdc-select--outlined": outlined,
980
- "mdc-select--disabled": this.disabled,
981
- "mdc-select--required": this.required,
982
- "mdc-select--dense": false, // this.dense,
983
- "mdc-select--no-label": !this.label,
984
- "mdc-select--with-leading-icon": !!this.icon
985
- };
986
-
987
- const optsList = this.resolveOptions(this.options);
988
- this._selectedIndex = -1;
989
- return html`
990
- <div id="component" class="mdc-list mdc-select ${classMap(classesList)}" @MDCList:action="${(e: MDCListActionEvent) => this.onValueChange(undefined, e.detail.index === -1 ? undefined : Array.isArray(this.options![e.detail.index]) ? this.options![e.detail.index][0] : this.options![e.detail.index])}">
991
- <ul class="mdc-list">
992
- ${optsList ? optsList.map(([optValue, optDisplay], index) => {
993
- if (this.value === optValue) {
994
- this._selectedIndex = index;
995
- }
996
- // todo: it's not actually putting the mdc-list-item--selected class on even when this.value === optValue...
997
- return html`<li class="${classMap({"mdc-list-item": true, "mdc-list-item--selected": this.value === optValue})}" role="option" data-value="${optValue}"><or-translate value="${optDisplay}"></or-translate></li>`;
998
- }) : ``}
999
- </ul>
1000
- </div>
1001
- `;
1002
- case InputType.SELECT:
1003
- const classes = {
1004
- "mdc-select--outlined": outlined,
1005
- "mdc-select--filled": !outlined,
1006
- "mdc-select--disabled": this.disabled || this.readonly,
1007
- "mdc-select--required": this.required,
1008
- "mdc-select--dense": false, // this.dense,
1009
- "dense-comfortable": this.comfortable,
1010
- "mdc-select--no-label": !this.label,
1011
- "mdc-select--with-leading-icon": !!this.icon,
1012
- "or-mwc-input--rounded": this.rounded
1013
-
1014
- };
1015
-
1016
- let opts: [any, string][] | Promise<[any, string][]>;
1017
- if(this.searchProvider != undefined) {
1018
- opts = this.searchProvider(this.searchableValue);
1019
- } else {
1020
- opts = this.resolveOptions(this.options)!;
1021
- }
1022
- const itemClickHandler: (ev: MouseEvent, item: ListItem) => void = (ev, item) => {
1023
- const value = item.value;
1024
-
1025
- if (this.multiple) {
1026
- ev.stopPropagation();
1027
- const inputValue = this._tempValue ?? (Array.isArray(this.value) ? [...this.value] : this.value !== undefined ? [this.value] : []);
1028
-
1029
- const index = inputValue.findIndex((v: any) => v === value);
1030
- if (index >= 0) {
1031
- inputValue.splice(index, 1);
1032
- } else {
1033
- inputValue.push(value);
1034
- }
1035
- const listItemEl = (ev.composedPath()[0] as HTMLElement).closest("li") as HTMLElement,
1036
- iconEl = listItemEl.getElementsByTagName("or-icon")[0] as OrIcon;
1037
- if (listItemEl) {
1038
- if (index >= 0) {
1039
- listItemEl.classList.remove("mdc-list-item--selected");
1040
- } else {
1041
- listItemEl.classList.add("mdc-list-item--selected");
1042
- }
1043
- }
1044
- if (iconEl) {
1045
- iconEl.icon = index >= 0 ? "checkbox-blank-outline" : "checkbox-marked";
1046
- }
1047
- this._tempValue = inputValue;
1048
- }
1049
-
1050
- // A narrowed down list with search, or a different asynchronous approach does not always trigger @MDCSelect:change,
1051
- // so using itemClickHandler instead to let it trigger anyway.
1052
- else if(this.searchProvider != undefined || !Array.isArray(opts)) {
1053
- this.onValueChange(undefined, item.value);
1054
- }
1055
-
1056
- };
1057
-
1058
- const menuCloseHandler = () => {
1059
- const v = (this._tempValue ?? this.value);
1060
- window.setTimeout(() => {
1061
- if (this._mdcComponent) {
1062
- // Hack to stop label moving down when there is a value set
1063
- (this._mdcComponent as any).foundation.adapter.floatLabel(v && (!Array.isArray(v) || v.length > 0));
1064
- }
1065
- });
1066
-
1067
- if (!this._tempValue) {
1068
- return;
1069
- }
1070
- const val = [...this._tempValue];
1071
- this._tempValue = undefined;
1072
- this.onValueChange(undefined, val);
1073
- };
1074
-
1075
- const listTemplate = (options: [any, string][]) => {
1076
- if(this.searchProvider != undefined && (!options || options.length == 0)) {
1077
- return html`<span class="mdc-text-field-helper-line" style="margin: 8px 8px 8px 0;">${i18next.t('noResults')}</span>`
1078
- } else {
1079
- return getListTemplate(
1080
- this.multiple ? ListType.MULTI_TICK : ListType.SELECT,
1081
- html`${options?.map(([optValue, optDisplay], index) => {
1082
- return getItemTemplate(
1083
- {
1084
- text: optDisplay,
1085
- value: optValue
1086
- },
1087
- index,
1088
- Array.isArray(this.value) ? this.value as any[] : this.value ? [this.value as any] : [],
1089
- this.multiple ? ListType.MULTI_TICK : ListType.SELECT,
1090
- false,
1091
- itemClickHandler
1092
- );
1093
- })}`,
1094
- false,
1095
- undefined
1096
- );
1097
- }
1098
- }
1099
-
1100
- return html`
1101
- <div id="component"
1102
- class="mdc-select ${classMap(classes)}"
1103
- @MDCSelect:change="${async (e: MDCSelectEvent) => {
1104
- const options: [any, string][] = (Array.isArray(opts) ? opts : await opts);
1105
- this.onValueChange(undefined, e.detail.index === -1 ? undefined : Array.isArray(options[e.detail.index]) ? options[e.detail.index][0] : options[e.detail.index]);
1106
- }}">
1107
- <div class="mdc-select__anchor" role="button"
1108
- aria-haspopup="listbox"
1109
- aria-expanded="false"
1110
- aria-disabled="${""+(this.disabled || this.readonly)}"
1111
- aria-labelledby="label selected-text">
1112
- ${!outlined ? html`<span class="mdc-select__ripple"></span>` : undefined}
1113
- ${outlined ? this.renderOutlined(labelTemplate) : labelTemplate}
1114
- <span class="mdc-select__selected-text-container">
1115
- <span id="selected-text" class="mdc-select__selected-text"></span>
1116
- </span>
1117
- <span class="mdc-select__dropdown-icon">
1118
- <svg
1119
- class="mdc-select__dropdown-icon-graphic"
1120
- viewBox="7 10 10 5">
1121
- <polygon
1122
- class="mdc-select__dropdown-icon-inactive"
1123
- stroke="none"
1124
- fill-rule="evenodd"
1125
- points="7 10 12 15 17 10">
1126
- </polygon>
1127
- <polygon
1128
- class="mdc-select__dropdown-icon-active"
1129
- stroke="none"
1130
- fill-rule="evenodd"
1131
- points="7 15 12 10 17 15">
1132
- </polygon>
1133
- </svg>
1134
- </span>
1135
- ${!outlined ? html`<div class="mdc-line-ripple"></div>` : ``}
1136
- </div>
1137
- <div id="mdc-select-menu" class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fixed ${this.searchProvider != undefined ? 'mdc-menu__searchable' : undefined}" @MDCMenuSurface:closed="${menuCloseHandler}">
1138
- ${when(this.searchProvider != undefined, () => html`
1139
- <label id="select-searchable" class="mdc-text-field mdc-text-field--filled">
1140
- <span class="mdc-floating-label" style="color: rgba(0, 0, 0, 0.6); text-transform: capitalize; visibility: ${this.searchableValue ? 'hidden' : 'visible'}" id="my-label-id">
1141
- <or-translate .value="${this.searchLabel}"></or-translate>
1142
- </span>
1143
- <input class="mdc-text-field__input" type="text"
1144
- @keyup="${(e: KeyboardEvent) => this.searchableValue = (e.target as HTMLInputElement).value}"
1145
- />
1146
- </label>
1147
- `)}
1148
- ${when(Array.isArray(opts), () => {
1149
- return listTemplate(opts as [any, string][]);
1150
- }, () => {
1151
- return until(new Promise(async (resolve) => {
1152
- resolve(listTemplate(await opts));
1153
- }), html`<span class="mdc-text-field-helper-line" style="margin: 8px 8px 8px 0;">${i18next.t('loading')}</span>`)
1154
- })}
1155
- </div>
1156
- ${hasHelper || showValidationMessage ? html`
1157
- <p id="component-helper-text" class="mdc-select-helper-text ${classMap(helperClasses)}" aria-hidden="true">
1158
- ${showValidationMessage ? this.errorMessage || this.validationMessage : this.helperText}
1159
- </p>` : ``}
1160
- </div>
1161
- `;
1162
- case InputType.BUTTON_TOGGLE:
1163
- return html`
1164
- <button id="component" class="mdc-icon-button ${this.value ? "mdc-icon-button--on" : ""}"
1165
- ?readonly="${this.readonly}"
1166
- ?disabled="${this.disabled}"
1167
- @MDCIconButtonToggle:change="${(evt: CustomEvent<MDCIconButtonToggleEventDetail>) => this.onValueChange(undefined, evt.detail.isOn)}">
1168
- ${this.icon ? html`<or-icon class="mdc-icon-button__icon" aria-hidden="true" icon="${this.icon}"></or-icon>` : ``}
1169
- ${this.iconOn ? html`<or-icon class="mdc-icon-button__icon mdc-icon-button__icon--on" aria-hidden="true" icon="${this.iconOn}"></or-icon>` : ``}
1170
- </button>
1171
- `;
1172
- case InputType.BUTTON:
1173
- case InputType.BUTTON_MOMENTARY: {
1174
-
1175
- const onMouseDown = (ev: MouseEvent) => {
1176
- if (this.disabled) {
1177
- ev.stopPropagation();
1178
- }
1179
-
1180
- if (isMomentary) this.dispatchEvent(new OrInputChangedEvent(true, null))
1181
- };
1182
- const onMouseUp = (ev: MouseEvent) => {
1183
- if (this.disabled) {
1184
- ev.stopPropagation();
1185
- }
1186
-
1187
- if (isMomentary) this.dispatchEvent(new OrInputChangedEvent(false, true))
1188
- };
1189
- const onClick = (ev: MouseEvent) => {
1190
- if (this.disabled) {
1191
- ev.stopPropagation();
1192
- }
1193
-
1194
- if (!isMomentary) this.dispatchEvent(new OrInputChangedEvent(true, null))
1195
- };
1196
-
1197
- const isMomentary = this.type === InputType.BUTTON_MOMENTARY;
1198
- const isIconButton = !this.action && !this.label;
1199
- // If no action, label or icons are given, show as a circle.
1200
- if (isIconButton && !this.iconTrailing && !this.icon ) {
1201
- this.icon = "circle"
1202
- }
1203
-
1204
- const classes = {
1205
- "mdc-icon-button": isIconButton,
1206
- "mdc-fab": !isIconButton && this.action,
1207
- "mdc-fab--extended": !isIconButton && this.action && !!this.label,
1208
- "mdc-fab--mini": !isIconButton && this.action && (this.compact || this.comfortable),
1209
- "mdc-button": !isIconButton && !this.action,
1210
- "mdc-button--raised": !isIconButton && !this.action && this.raised,
1211
- "mdc-button--unelevated": !isIconButton && !this.action && this.unElevated,
1212
- "mdc-button--outlined": !isIconButton && !this.action && (this.outlined || isMomentary),
1213
- "mdc-button--rounded": !isIconButton && !this.action && this.rounded,
1214
- "mdc-button--fullwidth": this.fullWidth,
1215
- };
1216
- return html`
1217
- <button id="component" class="${classMap(classes)}"
1218
- ?readonly="${this.readonly}"
1219
- ?disabled="${this.disabled}"
1220
- @click="${(ev: MouseEvent) => onClick(ev)}"
1221
- @mousedown="${(ev: MouseEvent) => onMouseDown(ev)}" @mouseup="${(ev: MouseEvent) => onMouseUp(ev)}">
1222
- ${!isIconButton ? html`<div class="mdc-button__ripple"></div>` : ``}
1223
- ${this.icon ? html`<or-icon class="${isIconButton ? "" : this.action ? "mdc-fab__icon" : "mdc-button__icon"}" aria-hidden="true" icon="${this.icon}"></or-icon>` : ``}
1224
- ${this.label ? html`<span class="${this.action ? "mdc-fab__label" : "mdc-button__label"}"><or-translate .value="${this.label}"></or-translate></span>` : ``}
1225
- ${!isIconButton && this.iconTrailing ? html`<or-icon class="${this.action ? "mdc-fab__icon" : "mdc-button__icon"}" aria-hidden="true" icon="${this.iconTrailing}"></or-icon>` : ``}
1226
- </button>
1227
- `;
1228
- }
1229
- case InputType.CHECKBOX_LIST:
1230
- if (!Array.isArray(this.value)) {
1231
- if (this.value === null || this.value === undefined) {
1232
- this.value = [];
1233
- } else {
1234
- this.value = [this.value];
1235
- }
1236
- }
1237
- const optsCheckboxList = this.resolveOptions(this.options);
1238
- this._selectedIndex = -1;
1239
- return html`
1240
- <div class="mdc-checkbox-list">
1241
- ${optsCheckboxList ? optsCheckboxList.map(([optValue, optDisplay], index) => {
1242
- if (this.value === optValue) {
1243
- this._selectedIndex = index;
1244
- }
1245
- return html`
1246
- <div id="field" class="mdc-form-field">
1247
- <div id="component" class="mdc-checkbox">
1248
- <input type="checkbox"
1249
- ?checked="${this.value && this.value.includes(optValue)}"
1250
- ?required="${this.required}"
1251
- name="${optValue}"
1252
- ?disabled="${this.disabled || this.readonly}"
1253
- @change="${(e: Event) => {
1254
- let val: any[] = this.value;
1255
- if ((e.target as HTMLInputElement).checked) {
1256
- if (!val.includes(optValue)) {
1257
- val = [optValue,...val];
1258
- }
1259
- } else {
1260
- val = val.filter((v: any) => v !== optValue);
1261
- }
1262
- this.onValueChange((e.target as HTMLInputElement), val);
1263
- }}"
1264
- class="mdc-checkbox__native-control" id="elem-${optValue}"/>
1265
-
1266
- <label for="elem-${optValue}"><or-translate value="${optDisplay}"></or-translate></label>
1267
-
1268
- </div>
1269
- </div>
1270
-
1271
- `;
1272
- }) : ``}
1273
- </div>
1274
- `;
1275
- case InputType.CHECKBOX:
1276
- let classList = {
1277
- "mdc-checkbox": true,
1278
- "mdc-checkbox--disabled": this.disabled || this.readonly
1279
- };
1280
- return html`
1281
- <div id="field" class="mdc-form-field">
1282
- <div id="component" class="${classMap(classList)}">
1283
- <input type="checkbox"
1284
- id="elem"
1285
- data-indeterminate="${this.indeterminate}"
1286
- ?checked="${this.value}"
1287
- ?required="${this.required}"
1288
- ?disabled="${this.disabled || this.readonly}"
1289
- @change="${(e: Event) => this.onValueChange((e.target as HTMLInputElement), (e.target as HTMLInputElement).checked)}"
1290
- class="mdc-checkbox__native-control" />
1291
- <div class="mdc-checkbox__background">
1292
- <svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
1293
- <path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"></path>
1294
- </svg>
1295
- <div class="mdc-checkbox__mixedmark"></div>
1296
- </div>
1297
- <div class="mdc-checkbox__ripple"></div>
1298
- </div>
1299
- <label class="mdc-checkbox-circle" for="elem">${this.label}</label>
1300
- </div>
1301
- `;
1302
- case InputType.COLOUR:
1303
- return html`
1304
- <div id="component" style="width: 100%; display: inline-flex; align-items: center; padding: 8px 0;">
1305
- <input type="color" id="elem" style="border: none; height: 31px; width: 31px; padding: 1px 3px; min-height: 22px; min-width: 30px;cursor: pointer" value="${this.value}"
1306
- ?disabled="${this.disabled || this.readonly}"
1307
- ?required="${this.required}"
1308
- @change="${(e: any) => this.onValueChange((e.target as HTMLInputElement), (e.target as HTMLInputElement).value)}"
1309
- />
1310
- <label style="margin-left: 10px; cursor: pointer" for="elem">${this.label}</label>
1311
- </div>
1312
- `
1313
- case InputType.NUMBER:
1314
- case InputType.RANGE:
1315
- case InputType.DATE:
1316
- case InputType.DATETIME:
1317
- case InputType.TIME:
1318
- case InputType.MONTH:
1319
- case InputType.WEEK:
1320
- case InputType.EMAIL:
1321
- case InputType.PASSWORD:
1322
- case InputType.TELEPHONE:
1323
- case InputType.URL:
1324
- case InputType.TEXT:
1325
- case InputType.TEXTAREA:
1326
- case InputType.JSON:
1327
- case InputType.JSON_OBJECT: {
1328
- // The following HTML input types require the values as specially formatted strings
1329
- let valMinMax: [any, any, any] = [this.value === undefined || this.value === null ? undefined : this.value, this.min, this.max];
1330
-
1331
- if (valMinMax.some((v) => typeof (v) !== "string")) {
1332
-
1333
- if (this.type === InputType.JSON || this.type === InputType.JSON_OBJECT) {
1334
- if (valMinMax[0] !== undefined) {
1335
- if (typeof valMinMax[0] !== "string" || valMinMax[0] === null) {
1336
- try {
1337
- valMinMax[0] = JSON.stringify(valMinMax[0], null, 2);
1338
- } catch (e) {
1339
- console.warn("Failed to parse JSON expression for input control");
1340
- valMinMax[0] = "";
1341
- }
1342
- }
1343
- } else {
1344
- valMinMax[0] = "";
1345
- }
1346
- } else {
1347
-
1348
- const format = this.format ? {...this.format} : {};
1349
-
1350
- switch (this.type) {
1351
- case InputType.TIME:
1352
- format.asDate = true;
1353
- format.hour12 = false;
1354
- format.timeStyle = this.step && this.step < 60 ? ValueFormatStyleRepresentation.MEDIUM : ValueFormatStyleRepresentation.SHORT;
1355
- break;
1356
- case InputType.DATE:
1357
- format.asDate = true;
1358
- format.momentJsFormat = "YYYY-MM-DD";
1359
- break;
1360
- case InputType.WEEK:
1361
- format.asDate = true;
1362
- format.momentJsFormat = "YYYY-[W]WW";
1363
- break;
1364
- case InputType.MONTH:
1365
- format.asDate = true;
1366
- format.momentJsFormat = "YYYY-MM";
1367
- break;
1368
- case InputType.DATETIME:
1369
- format.asDate = true;
1370
- format.momentJsFormat = "YYYY-MM-DDTHH:mm";
1371
- break;
1372
- case InputType.NUMBER:
1373
- format.maximumFractionDigits ??= 20; // default according to Web documentation
1374
- break;
1375
- }
1376
-
1377
- // Numbers/dates must be in english locale without commas etc.
1378
- format.useGrouping = false;
1379
- valMinMax = valMinMax.map((val) => val !== undefined ? Util.getValueAsString(val, () => format, "en-GB") : undefined) as [any,any,any];
1380
- }
1381
- }
1382
-
1383
- let inputElem: TemplateResult | undefined;
1384
- let label = this.label;
1385
- let type = this.type;
1386
- let componentId = "component";
1387
-
1388
- if (this.type === InputType.RANGE) {
1389
- // Change vars so number input can be included alongside the slider
1390
- label = undefined;
1391
- outlined = false;
1392
- hasHelper = false;
1393
- type = InputType.NUMBER;
1394
- componentId = "number";
1395
- }
1396
-
1397
- if (!(this.type === InputType.RANGE && this.disableSliderNumberInput)) {
1398
-
1399
- // Handle password toggling logic
1400
- if(this.censored) type = InputType.PASSWORD;
1401
- if(this.type === InputType.PASSWORD && this.advertised) type = InputType.TEXT;
1402
-
1403
- const classes = {
1404
- "mdc-text-field": true,
1405
- "mdc-text-field--invalid": !this.valid,
1406
- "mdc-text-field--filled": !outlined,
1407
- "mdc-text-field--outlined": outlined,
1408
- "mdc-text-field--textarea": type === InputType.TEXTAREA || type === InputType.JSON || type === InputType.JSON_OBJECT,
1409
- "mdc-text-field--disabled": this.disabled,
1410
- "mdc-text-field--fullwidth": this.fullWidth && !outlined,
1411
- "dense-comfortable": this.comfortable && !(type === InputType.TEXTAREA || type === InputType.JSON || type === InputType.JSON_OBJECT),
1412
- "dense-compact": !this.comfortable && this.compact,
1413
- "mdc-text-field--label-floating": hasValue,
1414
- "mdc-text-field--no-label": !this.label,
1415
- "mdc-text-field--with-leading-icon": !!this.icon,
1416
- "mdc-text-field--with-trailing-icon": !!this.iconTrailing,
1417
- "or-mwc-input--rounded": this.rounded
1418
- };
1419
-
1420
- inputElem = type === InputType.TEXTAREA || type === InputType.JSON || type === InputType.JSON_OBJECT
1421
- ? html`
1422
- <textarea id="elem" class="mdc-text-field__input ${this.resizeVertical ? "resize-vertical" : ""}" ?required="${this.required}"
1423
- ?readonly="${this.readonly}" ?disabled="${this.disabled}" minlength="${ifDefined(this.minLength)}"
1424
- maxlength="${ifDefined(this.maxLength)}" rows="${this.rows ? this.rows : 5}"
1425
- cols="${ifDefined(this.cols)}" aria-label="${ifDefined(label)}"
1426
- aria-labelledby="${ifDefined(label ? "label" : undefined)}"
1427
- @change="${(e: Event) => this.onValueChange((e.target as HTMLTextAreaElement), (e.target as HTMLTextAreaElement).value)}">${valMinMax[0] ? valMinMax[0] : ""}</textarea>`
1428
- : html`
1429
- <input type="${type}" id="elem" aria-labelledby="${ifDefined(label ? "label" : undefined)}"
1430
- class="mdc-text-field__input" ?required="${this.required}" ?readonly="${this.readonly}"
1431
- ?disabled="${this.disabled}" min="${ifDefined(valMinMax[1])}" max="${ifDefined(valMinMax[2])}"
1432
- step="${this.step ? this.step : "any"}" minlength="${ifDefined(this.minLength)}" pattern="${ifDefined(this.pattern)}"
1433
- maxlength="${ifDefined(this.maxLength)}" placeholder="${ifDefined(this.placeHolder)}"
1434
- .value="${valMinMax[0] !== null && valMinMax[0] !== undefined ? valMinMax[0] : ""}"
1435
- @keydown="${(e: KeyboardEvent) => {
1436
- if ((e.code === "Enter" || e.code === "NumpadEnter")) {
1437
- this.onValueChange((e.target as HTMLInputElement), (e.target as HTMLInputElement).value, true);
1438
- }}}"
1439
- @blur="${(e: Event) => {if ((e.target as HTMLInputElement).value === "") this.reportValidity()}}"
1440
- @change="${(e: Event) => this.onValueChange((e.target as HTMLInputElement), (e.target as HTMLInputElement).value)}" />`;
1441
-
1442
- inputElem = html`
1443
- <label id="${componentId}" class="${classMap(classes)}">
1444
- ${this.icon ? html`<or-icon class="mdc-text-field__icon mdc-text-field__icon--leading" style="color: ${this.iconColor ? "#" + this.iconColor : "unset"}" aria-hidden="true" icon="${this.icon}"></or-icon>` : ``}
1445
- ${outlined ? `` : html`<span class="mdc-text-field__ripple"></span>`}
1446
- ${inputElem}
1447
- ${outlined ? this.renderOutlined(labelTemplate) : labelTemplate}
1448
- ${outlined ? `` : html`<span class="mdc-line-ripple"></span>`}
1449
- ${this.type === InputType.PASSWORD && !this.censored ? html`<or-icon class="mdc-text-field__icon mdc-text-field__icon--trailing" aria-hidden="true" icon=${this.advertised ? 'eye' : 'eye-off'} style="pointer-events: auto;" @click=${() => this.advertised = !this.advertised}></or-icon>` : ``}
1450
- ${this.iconTrailing ? html`<or-icon class="mdc-text-field__icon mdc-text-field__icon--trailing" aria-hidden="true" icon="${this.iconTrailing}"></or-icon>` : ``}
1451
- </label>
1452
- ${hasHelper || showValidationMessage ? html`
1453
- <div class="mdc-text-field-helper-line">
1454
- <div class="mdc-text-field-helper-text ${classMap(helperClasses)}">${showValidationMessage ? this.errorMessage || this.validationMessage : this.helperText}</div>
1455
- ${this.charCounter && !this.readonly ? html`<div class="mdc-text-field-character-counter"></div>` : ``}
1456
- </div>
1457
- ` : ``}
1458
- `;
1459
- }
1460
-
1461
- if (this.type === InputType.RANGE) {
1462
-
1463
- const classes = {
1464
- "mdc-slider": true,
1465
- "mdc-slider--range": this.continuous,
1466
- "mdc-slider--discreet": !this.continuous,
1467
- "mdc-slider--disabled": this.disabled || this.readonly
1468
- };
1469
-
1470
- inputElem = html`
1471
- <span id="wrapper">
1472
- ${this.label ? html`<label for="component" class="${this.disabled ? "mdc-switch--disabled" : ""}">${this.label}</label>` : ``}
1473
- <div id="component" class="${classMap(classes)}" @MDCSlider:change="${(ev:CustomEvent<MDCSliderChangeEventDetail>) => this.onValueChange(undefined, ev.detail.value)}">
1474
- <input id="elem" class="mdc-slider__input" type="range" min="${ifDefined(valMinMax[1])}" max="${ifDefined(valMinMax[2])}" value="${valMinMax[0] || valMinMax[1] || 0}" name="slider" step="${this.step || 1}" ?readonly="${this.readonly}" ?disabled="${this.disabled}" aria-label="${ifDefined(this.label)}" />
1475
- <div class="mdc-slider__track">
1476
- <div class="mdc-slider__track--inactive"></div>
1477
- <div class="mdc-slider__track--active">
1478
- <div class="mdc-slider__track--active_fill"></div>
1479
- </div>
1480
- </div>
1481
- <div class="mdc-slider__thumb">
1482
- ${!this.continuous ? html`<div class="mdc-slider__value-indicator-container" aria-hidden="true">
1483
- <div class="mdc-slider__value-indicator">
1484
- <span class="mdc-slider__value-indicator-text">
1485
- 50
1486
- </span>
1487
- </div>
1488
- </div>` : ``}
1489
- <div class="mdc-slider__thumb-knob"></div>
1490
- </div>
1491
- </div>
1492
- ${inputElem ? html`<div style="min-width: 70px; width: 70px;">${inputElem}</div>` : ``}
1493
- </span>
1494
- `;
1495
- }
1496
-
1497
- return inputElem;
1498
- }
1499
- }
1500
- }
1501
-
1502
- return html`<span>INPUT TYPE NOT IMPLEMENTED</span>`;
1503
- }
1504
-
1505
- protected _getFormat(): ValueFormat | undefined {
1506
- if (this.format) {
1507
- return this.format;
1508
- }
1509
- }
1510
-
1511
- update(_changedProperties: PropertyValues) {
1512
- if (_changedProperties.has('autoValidate') && this._mdcComponent) {
1513
- const comp = this._mdcComponent as any;
1514
- if (comp.foundation && comp.foundation.setValidateOnValueChange) {
1515
- comp.foundation.setValidateOnValueChange(this.autoValidate);
1516
- }
1517
- }
1518
-
1519
- super.update(_changedProperties);
1520
- }
1521
-
1522
- firstUpdated(_changedProperties: PropertyValues) {
1523
- super.firstUpdated(_changedProperties);
1524
-
1525
- if (this.autoValidate) {
1526
- this.reportValidity();
1527
- }
1528
- }
1529
-
1530
- protected updated(_changedProperties: PropertyValues): void {
1531
- super.updated(_changedProperties);
1532
-
1533
- if (_changedProperties.has("type")) {
1534
- const component = this.shadowRoot!.getElementById("component");
1535
- if (this._mdcComponent) {
1536
- this._mdcComponent.destroy();
1537
- this._mdcComponent = undefined;
1538
- }
1539
- if (this._mdcComponent2) {
1540
- this._mdcComponent2.destroy();
1541
- this._mdcComponent2 = undefined;
1542
- }
1543
-
1544
- if (component && this.type) {
1545
- switch (this.type) {
1546
- case InputType.LIST:
1547
- const mdcList = new MDCList(component);
1548
- this._mdcComponent = mdcList;
1549
- mdcList.selectedIndex = this._selectedIndex;
1550
- break;
1551
- case InputType.SELECT:
1552
- const mdcSelect = new MDCSelect(component);
1553
- this._mdcComponent = mdcSelect;
1554
-
1555
- const hasValue = (this.value !== null && this.value !== undefined);
1556
- if (!hasValue) {
1557
- mdcSelect.selectedIndex = -1; // Without this first option will be shown as selected
1558
- }
1559
-
1560
- if (this.multiple) {
1561
- // To make multiple select work then override the adapter getSelectedIndex
1562
- (this._mdcComponent as any).foundation.adapter.getSelectedIndex = () => {
1563
- // Return first item index
1564
- if (!Array.isArray(this.value) || (this.value as []).length === 0) {
1565
- return -1;
1566
- }
1567
- const firstSelected = (this.value as any[])[0];
1568
- const items = (this._mdcComponent as any).foundation.adapter.getMenuItemValues();
1569
- return items.indexOf(firstSelected);
1570
- };
1571
- }
1572
-
1573
- mdcSelect.useDefaultValidation = !this.multiple;
1574
- mdcSelect.valid = !this.required || (!this.multiple && mdcSelect.valid) || (this.multiple && Array.isArray(this.value) && (this.value as []).length > 0);
1575
-
1576
- const selectedText = this.getSelectedTextValue();
1577
- (this._mdcComponent as any).foundation.adapter.setSelectedText(selectedText);
1578
- (this._mdcComponent as any).foundation.adapter.floatLabel(!!selectedText);
1579
-
1580
- // Set width of fixed select menu to match the component width
1581
- // Using an observer to prevent forced reflow / DOM measurements; prevents blocking the thread
1582
- if(!this._menuObserver) {
1583
- this._menuObserver = new IntersectionObserver((entries, observer) => {
1584
- if((entries[0].target as HTMLElement).style.minWidth != (entries[0].target.parentElement?.clientWidth + "px")) {
1585
- (entries[0].target as HTMLElement).style.minWidth = entries[0].target.parentElement?.clientWidth + "px";
1586
- }
1587
- })
1588
- this._menuObserver.observe(this.shadowRoot!.getElementById("mdc-select-menu")!);
1589
- }
1590
-
1591
- // This overrides the standard mdc menu body click capture handler as it doesn't work with webcomponents
1592
- const searchable: boolean = (this.searchProvider !== undefined);
1593
- const multi: boolean = this.multiple;
1594
- (mdcSelect as any).menu.menuSurface_.foundation.handleBodyClick = function (evt: MouseEvent) {
1595
- const el = evt.composedPath()[0]; // Use composed path not evt target to work with webcomponents
1596
- if (this.adapter.isElementInContainer(el)) {
1597
- if(!searchable) {
1598
- return; // Normal select menu closes automatically, so abort
1599
- }
1600
- // if searchable, we manually close the menu when clicking a list item.
1601
- // However, if something else than a list item (for example the search field) is clicked, it should not close, so abort.
1602
- else if (el instanceof Element && !el.className.includes('mdc-list-item')) {
1603
- return;
1604
- }
1605
- else if (multi) {
1606
- return;
1607
- }
1608
- }
1609
- (mdcSelect as any).menu.menuSurface_.close();
1610
- };
1611
-
1612
- break;
1613
- case InputType.RADIO:
1614
- case InputType.CHECKBOX_LIST:
1615
- case InputType.COLOUR:
1616
- break;
1617
- case InputType.BUTTON:
1618
- case InputType.BUTTON_MOMENTARY:
1619
- const isIconButton = !this.action && !this.label;
1620
- const ripple = new MDCRipple(component);
1621
- if (isIconButton) {
1622
- ripple.unbounded = true;
1623
- }
1624
- this._mdcComponent = ripple;
1625
- break;
1626
- case InputType.BUTTON_TOGGLE:
1627
- this._mdcComponent = new MDCIconButtonToggle(component);
1628
- break;
1629
- case InputType.CHECKBOX:
1630
- this._mdcComponent = new MDCCheckbox(component);
1631
- const field = this.shadowRoot!.getElementById("field");
1632
- if (field) {
1633
- const mdcField = new MDCFormField(field);
1634
- mdcField.input = this._mdcComponent as unknown as MDCFormFieldInput;
1635
- this._mdcComponent2 = mdcField;
1636
- }
1637
- break;
1638
- case InputType.SWITCH:
1639
- this._mdcComponent = new MDCSwitch(component);
1640
- break;
1641
- case InputType.RANGE:
1642
- this._mdcComponent = new MDCSlider(component);
1643
- const numberComponent = this.shadowRoot!.getElementById("number");
1644
- if (numberComponent) {
1645
- const numberField = new MDCTextField(numberComponent);
1646
- numberField.useNativeValidation = false;
1647
- this._mdcComponent2 = numberField;
1648
- }
1649
- break;
1650
- default:
1651
- const textField = new MDCTextField(component);
1652
- textField.useNativeValidation = false;
1653
- this._mdcComponent = textField;
1654
- break;
1655
- }
1656
-
1657
- if (this._mdcComponent && this.focused && typeof((this._mdcComponent as any).focus) === "function") {
1658
- (this._mdcComponent as any).focus();
1659
- }
1660
- }
1661
- } else {
1662
- // some components need to be kept in sync with the DOM
1663
- if (this.type === InputType.SELECT && this._mdcComponent) {
1664
- if (_changedProperties.has("options")) {
1665
- (this._mdcComponent as MDCSelect).layoutOptions(); // has big impact on performance when the MDCSelect list is large.
1666
- }
1667
- (this._mdcComponent as MDCSelect).disabled = !!(this.disabled || this.readonly);
1668
- (this._mdcComponent as MDCSelect).useDefaultValidation = !this.multiple;
1669
- (this._mdcComponent as MDCSelect).valid = !this.required || (!this.multiple && (this._mdcComponent as MDCSelect).valid) || (this.multiple && Array.isArray(this.value) && (this.value as []).length > 0);
1670
- const selectedText = this.getSelectedTextValue();
1671
- (this._mdcComponent as any).foundation.adapter.setSelectedText(selectedText);
1672
- (this._mdcComponent as any).foundation.adapter.floatLabel(!!selectedText);
1673
- } else if (this.type === InputType.RANGE && this._mdcComponent) {
1674
- const slider = this._mdcComponent as MDCSlider;
1675
- slider.setDisabled(this.disabled || this.readonly);
1676
- // slider.getDefaultFoundation(). getDefaultFoundation()..getMax() = this.min;
1677
- // slider.max = this.max;
1678
- slider.setValue(this.value);
1679
- } else if (this.type === InputType.SWITCH && this._mdcComponent) {
1680
- const swtch = this._mdcComponent as MDCSwitch;
1681
- swtch.checked = this.value;
1682
- } else if (this.type === InputType.CHECKBOX && this._mdcComponent) {
1683
- const checkbox = this._mdcComponent as MDCCheckbox;
1684
- checkbox.checked = !!this.value;
1685
- checkbox.disabled = !!(this.disabled || this.readonly);
1686
- }
1687
-
1688
- if (this._mdcComponent) {
1689
- (this._mdcComponent as any).required = !!this.required;
1690
- }
1691
- }
1692
-
1693
- if(_changedProperties.has("label")) {
1694
- (this._mdcComponent as any)?.layout?.(); // Adjusts the dimensions and positions for all sub-elements.
1695
- }
1696
-
1697
- if (this.autoValidate) {
1698
- this.reportValidity();
1699
- }
1700
- }
1701
-
1702
- protected renderOutlined(labelTemplate: TemplateResult | undefined) {
1703
- return html`
1704
- <span class="mdc-notched-outline">
1705
- <span class="mdc-notched-outline__leading"></span>
1706
- ${labelTemplate ? html`
1707
- <span class="mdc-notched-outline__notch">
1708
- ${labelTemplate}
1709
- </span>
1710
- ` : ``}
1711
- <span class="mdc-notched-outline__trailing"></span>
1712
- </span>
1713
- `;
1714
- }
1715
-
1716
- public setCustomValidity(msg: string | undefined) {
1717
- this.errorMessage = msg;
1718
- const elem = this.shadowRoot!.getElementById("elem") as HTMLElement;
1719
- if (elem && (elem as any).setCustomValidity) {
1720
- (elem as any).setCustomValidity(msg ?? "");
1721
- }
1722
- this.reportValidity();
1723
- }
1724
-
1725
- public checkValidity(): boolean {
1726
- const elem = this.shadowRoot!.getElementById("elem") as any;
1727
- let valid = true;
1728
- if (elem && elem.validity) {
1729
- const nativeValidity = elem.validity as ValidityState;
1730
- valid = nativeValidity.valid;
1731
- }
1732
-
1733
- if (valid && (this.type === InputType.JSON || this.type === InputType.JSON_OBJECT)) {
1734
- // JSON needs special validation - if no text value but this.value then parsing failed
1735
- if (this.value !== undefined && this.value !== null && (this._mdcComponent as MDCTextField).value === "") {
1736
- valid = false;
1737
- }
1738
- }
1739
-
1740
- return valid;
1741
- }
1742
-
1743
- public reportValidity(): boolean {
1744
- const isValid = this.checkValidity();
1745
- this.isUiValid = isValid;
1746
-
1747
- if (this._mdcComponent) {
1748
- (this._mdcComponent as any).valid = isValid;
1749
- }
1750
-
1751
- return isValid;
1752
- }
1753
-
1754
- protected onValueChange(elem: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | undefined, newValue: any | undefined, enterPressed?: boolean) {
1755
- let previousValue = this.value;
1756
- let errorMsg: string | undefined;
1757
-
1758
- if (newValue === "undefined") {
1759
- previousValue = null;
1760
- newValue = undefined;
1761
- }
1762
-
1763
- if (newValue === "null") {
1764
- previousValue = undefined;
1765
- newValue = null;
1766
- }
1767
-
1768
- if (typeof(newValue) === "string") {
1769
- switch (this.type) {
1770
- case InputType.CHECKBOX:
1771
- case InputType.SWITCH:
1772
- newValue = newValue === "on";
1773
- break;
1774
- case InputType.JSON:
1775
- case InputType.JSON_OBJECT:
1776
- case InputType.NUMBER:
1777
- case InputType.RANGE:
1778
- if (newValue === "") {
1779
- newValue = null;
1780
- } else {
1781
- try {
1782
- newValue = JSON.parse(newValue);
1783
- if (this.type === InputType.JSON_OBJECT && (typeof newValue !== 'object' || Array.isArray(newValue))) {
1784
- newValue = this.value;
1785
- errorMsg = i18next.t("validation.invalidJSON");
1786
- }
1787
- } catch (e) {
1788
- newValue = this.value;
1789
- errorMsg = this.type === InputType.JSON || this.type == InputType.JSON_OBJECT ? i18next.t("validation.invalidJSON") : i18next.t("validation.invalidNumber");
1790
- }
1791
- }
1792
- break;
1793
- case InputType.DATETIME:
1794
- if (newValue === "") {
1795
- newValue = null;
1796
- } else {
1797
- try {
1798
- newValue = Date.parse(newValue);
1799
- } catch (e) {
1800
- newValue = this.value;
1801
- errorMsg = i18next.t("validation.invalidDate");
1802
- }
1803
- }
1804
- break;
1805
- }
1806
- }
1807
- this.value = newValue;
1808
- this.setCustomValidity(errorMsg);
1809
- this.reportValidity();
1810
-
1811
- if (this.type !== InputType.CHECKBOX_LIST && newValue !== previousValue) {
1812
- if (this.type === InputType.RANGE) {
1813
- (this._mdcComponent as MDCSlider).setValue(newValue);
1814
- if (this._mdcComponent2) {
1815
- (this._mdcComponent2 as MDCTextField).value = newValue;
1816
- }
1817
- }
1818
- this.dispatchEvent(new OrInputChangedEvent(this.value, previousValue, enterPressed));
1819
- }
1820
-
1821
- // Reset search if value has been selected
1822
- if(this.searchProvider != undefined && this.type === InputType.SELECT) {
1823
- const searchableElement = this.shadowRoot?.getElementById('select-searchable')?.children[1];
1824
- if(searchableElement) {
1825
- this.searchableValue = undefined;
1826
- (searchableElement as HTMLInputElement).value = "";
1827
- }
1828
- }
1829
-
1830
- if (this.type === InputType.CHECKBOX_LIST && !Util.objectsEqual(newValue, previousValue, true)) {
1831
- this.dispatchEvent(new OrInputChangedEvent(newValue, previousValue, enterPressed));
1832
- }
1833
- }
1834
-
1835
- public get valid(): boolean {
1836
- const elem = this.shadowRoot!.getElementById("elem") as any;
1837
- if (elem && elem.checkValidity) {
1838
- return elem.checkValidity();
1839
- }
1840
- return true;
1841
- }
1842
-
1843
- public get currentValue(): any {
1844
- const elem = this.shadowRoot!.getElementById("elem") as any;
1845
- if (elem && elem.value) {
1846
- return elem.value;
1847
- }
1848
- }
1849
-
1850
- protected resolveOptions(options: any[] | undefined): [any, string][] | undefined {
1851
- let resolved: [any, string][] | undefined;
1852
-
1853
- if (options && options.length > 0) {
1854
- resolved = options.map(opt => {
1855
- if (Array.isArray(opt)) {
1856
- return opt as [any, string];
1857
- } else {
1858
- const optStr = "" + opt;
1859
- return [opt, i18next.t(optStr, {defaultValue: Util.camelCaseToSentenceCase(optStr)})]
1860
- }
1861
- });
1862
- }
1863
-
1864
- return resolved;
1865
- }
1866
-
1867
- protected getSelectedTextValue(options?: [string, string][] | undefined): string {
1868
- const value = this.value;
1869
- const values = Array.isArray(value) ? value as string[] : value != null ? [value as string] : undefined;
1870
- if (!values) {
1871
- return "";
1872
- }
1873
- const opts = options || this.resolveOptions(this.options);
1874
- return !opts || !values ? "" : values.map(v => opts.find(([optValue, optDisplay], index) => v === optValue)).map((opt) => opt ? opt[1] : "").join(", ");
1875
- }
1876
- }