@record-evolution/widget-form 1.0.26 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/widget-form.d.ts +7 -3
- package/dist/widget-form.js +410 -374
- package/dist/widget-form.js.map +1 -1
- package/package.json +1 -1
- package/src/default-data.json +5 -2
- package/src/definition-schema.d.ts +18 -1
- package/src/definition-schema.json +38 -1
- package/src/widget-form.ts +74 -19
package/src/widget-form.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { html, css, LitElement, PropertyValues, nothing } from 'lit'
|
|
|
2
2
|
import { repeat } from 'lit/directives/repeat.js'
|
|
3
3
|
import { keyed } from 'lit/directives/keyed.js'
|
|
4
4
|
import { property, state, customElement, query } from 'lit/decorators.js'
|
|
5
|
-
import {
|
|
5
|
+
import { FormConfiguration } from './definition-schema.js'
|
|
6
6
|
|
|
7
7
|
import '@material/web/fab/fab.js'
|
|
8
8
|
import '@material/web/icon/icon.js'
|
|
@@ -18,7 +18,7 @@ import '@material/web/select/select-option.js'
|
|
|
18
18
|
|
|
19
19
|
import type { MdDialog } from '@material/web/dialog/dialog.js'
|
|
20
20
|
|
|
21
|
-
type Column = Exclude<
|
|
21
|
+
type Column = Exclude<FormConfiguration['formFields'], undefined>[number]
|
|
22
22
|
type Theme = {
|
|
23
23
|
theme_name: string
|
|
24
24
|
theme_object: any
|
|
@@ -26,7 +26,7 @@ type Theme = {
|
|
|
26
26
|
@customElement('widget-form-versionplaceholder')
|
|
27
27
|
export class WidgetForm extends LitElement {
|
|
28
28
|
@property({ type: Object })
|
|
29
|
-
inputData?:
|
|
29
|
+
inputData?: FormConfiguration
|
|
30
30
|
|
|
31
31
|
@property({ type: Object })
|
|
32
32
|
theme?: Theme
|
|
@@ -43,6 +43,11 @@ export class WidgetForm extends LitElement {
|
|
|
43
43
|
|
|
44
44
|
@state() private formKey = 0
|
|
45
45
|
|
|
46
|
+
// Current value of every field, keyed by field label. Rebuilt on each
|
|
47
|
+
// input/change so conditional-display rules (which reference a controlling
|
|
48
|
+
// field by its label) re-evaluate live as the user fills the form.
|
|
49
|
+
@state() private fieldValues: Record<string, string> = {}
|
|
50
|
+
|
|
46
51
|
version: string = 'versionplaceholder'
|
|
47
52
|
|
|
48
53
|
update(changedProperties: Map<string, any>) {
|
|
@@ -113,25 +118,21 @@ export class WidgetForm extends LitElement {
|
|
|
113
118
|
const form = event.target as HTMLFormElement
|
|
114
119
|
const formData = new FormData(form)
|
|
115
120
|
const submitData = this.inputData?.formFields?.map((field, i) => {
|
|
116
|
-
const
|
|
117
|
-
let rawValue: any
|
|
118
|
-
|
|
119
|
-
if (field.hiddenField) {
|
|
120
|
-
rawValue = this.effectivePreFilledValue(field) ?? this.effectiveDefaultValue(field) ?? ''
|
|
121
|
-
} else if (field.type === 'checkbox') {
|
|
122
|
-
rawValue = formData.has(name) ? 'on' : 'off'
|
|
123
|
-
} else {
|
|
124
|
-
const entry = formData.get(name)
|
|
125
|
-
rawValue =
|
|
126
|
-
entry === null || entry === '' ? (this.effectiveDefaultValue(field) ?? '') : entry
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
121
|
+
const targetColumn = {
|
|
130
122
|
swarm_app_databackend_key: field.targetColumn?.swarm_app_databackend_key,
|
|
131
123
|
table_name: field.targetColumn?.tablename,
|
|
132
|
-
column_name: field.targetColumn?.column
|
|
133
|
-
value: this.formatValue(rawValue, field.type ?? 'textfield')
|
|
124
|
+
column_name: field.targetColumn?.column
|
|
134
125
|
}
|
|
126
|
+
|
|
127
|
+
// A field hidden by an unmet conditional-display rule is "not
|
|
128
|
+
// applicable": submit null and skip value/default resolution. Its
|
|
129
|
+
// required rule never fired because the element was absent from the DOM.
|
|
130
|
+
if (!field.hiddenField && !this.isFieldVisible(field)) {
|
|
131
|
+
return { ...targetColumn, value: null }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const rawValue = this.currentRawValue(field, i, formData)
|
|
135
|
+
return { ...targetColumn, value: this.formatValue(rawValue, field.type ?? 'textfield') }
|
|
135
136
|
})
|
|
136
137
|
|
|
137
138
|
if (this.inputData?.deleteFlagColumn)
|
|
@@ -209,6 +210,56 @@ export class WidgetForm extends LitElement {
|
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
// Resolve a field's current raw (string) value, applying the same
|
|
214
|
+
// empty-falls-back-to-default rule used at submit so condition evaluation and
|
|
215
|
+
// submission agree. Checkbox is normalized to 'true'/'false' (matching the
|
|
216
|
+
// literals designers use in a condition list); a field absent from the DOM
|
|
217
|
+
// (hidden by its own condition) yields its effective default value.
|
|
218
|
+
currentRawValue(field: Column, i: number, formData: FormData): string {
|
|
219
|
+
const name = `column-${i}`
|
|
220
|
+
if (field.hiddenField)
|
|
221
|
+
return this.effectivePreFilledValue(field) ?? this.effectiveDefaultValue(field) ?? ''
|
|
222
|
+
if (field.type === 'checkbox') return formData.has(name) ? 'true' : 'false'
|
|
223
|
+
const entry = formData.get(name)
|
|
224
|
+
return entry === null || entry === '' ? (this.effectiveDefaultValue(field) ?? '') : String(entry)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Rebuild the label→value map on every input/change so conditional-display
|
|
228
|
+
// rules re-evaluate live. A single delegated listener on the <form> catches
|
|
229
|
+
// all field types (Material components bubble input/change within this root).
|
|
230
|
+
handleFieldChange(event: Event) {
|
|
231
|
+
const form = event.currentTarget as HTMLFormElement
|
|
232
|
+
if (!form) return
|
|
233
|
+
const formData = new FormData(form)
|
|
234
|
+
const values: Record<string, string> = {}
|
|
235
|
+
this.inputData?.formFields?.forEach((field, i) => {
|
|
236
|
+
if (field.label) values[field.label] = this.currentRawValue(field, i, formData)
|
|
237
|
+
})
|
|
238
|
+
this.fieldValues = values
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A field is visible unless it declares a conditional-display rule whose
|
|
242
|
+
// controlling field (referenced by label) does not currently hold one of the
|
|
243
|
+
// listed values. Fail-open on misconfiguration (unknown field / empty list)
|
|
244
|
+
// so a half-configured rule never silently hides a field and its data.
|
|
245
|
+
isFieldVisible(field: Column): boolean {
|
|
246
|
+
const ref = field.conditionalDisplay?.conditionField?.trim()
|
|
247
|
+
if (!ref) return true
|
|
248
|
+
const allowed = (field.conditionalDisplay?.conditionValues ?? '')
|
|
249
|
+
.split(',')
|
|
250
|
+
.map((v) => v.trim())
|
|
251
|
+
.filter((v) => v !== '')
|
|
252
|
+
if (allowed.length === 0) return true
|
|
253
|
+
const controller = this.inputData?.formFields?.find((f) => f.label === ref)
|
|
254
|
+
if (!controller) return true
|
|
255
|
+
const current =
|
|
256
|
+
this.fieldValues[ref] ??
|
|
257
|
+
this.effectivePreFilledValue(controller) ??
|
|
258
|
+
this.effectiveDefaultValue(controller) ??
|
|
259
|
+
''
|
|
260
|
+
return allowed.includes(String(current))
|
|
261
|
+
}
|
|
262
|
+
|
|
212
263
|
renderTextField(field: Column, i: number) {
|
|
213
264
|
return html`
|
|
214
265
|
<md-outlined-text-field
|
|
@@ -323,6 +374,7 @@ export class WidgetForm extends LitElement {
|
|
|
323
374
|
|
|
324
375
|
resetForm() {
|
|
325
376
|
this.formKey++
|
|
377
|
+
this.fieldValues = {}
|
|
326
378
|
}
|
|
327
379
|
|
|
328
380
|
resolveRoute(item?: any): string | undefined {
|
|
@@ -630,12 +682,15 @@ export class WidgetForm extends LitElement {
|
|
|
630
682
|
method="dialog"
|
|
631
683
|
class="form-content"
|
|
632
684
|
@submit=${this.handleFormSubmit}
|
|
685
|
+
@input=${this.handleFieldChange}
|
|
686
|
+
@change=${this.handleFieldChange}
|
|
633
687
|
>
|
|
634
688
|
${repeat(
|
|
635
689
|
this.inputData?.formFields ?? [],
|
|
636
690
|
(field, i) => i,
|
|
637
691
|
(field, i) => {
|
|
638
692
|
if (field.hiddenField) return nothing
|
|
693
|
+
if (!this.isFieldVisible(field)) return nothing
|
|
639
694
|
switch (field.type) {
|
|
640
695
|
case 'textfield':
|
|
641
696
|
return this.renderTextField(field, i)
|