@record-evolution/widget-form 1.0.25 → 1.0.27
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/dist/widget-form.d.ts +6 -0
- package/dist/widget-form.js +456 -405
- package/dist/widget-form.js.map +1 -1
- package/package.json +1 -1
- package/src/default-data.json +5 -1
- package/src/definition-schema.d.ts +41 -0
- package/src/definition-schema.json +76 -5
- package/src/widget-form.ts +105 -20
|
@@ -174,13 +174,61 @@
|
|
|
174
174
|
"title": "Pre-filled Value",
|
|
175
175
|
"description": "Pre-filled value for this field when the form loads. Can be a static value or bound to a data source.",
|
|
176
176
|
"type": "string",
|
|
177
|
+
"condition": {
|
|
178
|
+
"relativePath": "../type",
|
|
179
|
+
"showIfValueIn": ["dropdown", "textfield", "numberfield", "datetime"]
|
|
180
|
+
},
|
|
177
181
|
"order": 7
|
|
178
182
|
},
|
|
183
|
+
"preFilledValueMultiline": {
|
|
184
|
+
"title": "Pre-filled Value",
|
|
185
|
+
"description": "Pre-filled value for this field when the form loads. Can be a static value or bound to a data source.",
|
|
186
|
+
"type": "textarea",
|
|
187
|
+
"condition": {
|
|
188
|
+
"relativePath": "../type",
|
|
189
|
+
"showIfValueIn": ["textarea"]
|
|
190
|
+
},
|
|
191
|
+
"order": 8
|
|
192
|
+
},
|
|
193
|
+
"preFilledValueBoolean": {
|
|
194
|
+
"title": "Pre-filled Value",
|
|
195
|
+
"description": "Pre-filled checked state for this checkbox when the form loads. Can be a static value or bound to a data source.",
|
|
196
|
+
"type": "boolean",
|
|
197
|
+
"condition": {
|
|
198
|
+
"relativePath": "../type",
|
|
199
|
+
"showIfValueIn": ["checkbox"]
|
|
200
|
+
},
|
|
201
|
+
"order": 9
|
|
202
|
+
},
|
|
179
203
|
"defaultValue": {
|
|
180
204
|
"title": "Default Value",
|
|
181
205
|
"description": "Default value for this field when the form loads. If the user does not provide a value, this default will be used. Can be a static value or bound to a data source.",
|
|
182
206
|
"type": "string",
|
|
183
|
-
"
|
|
207
|
+
"condition": {
|
|
208
|
+
"relativePath": "../type",
|
|
209
|
+
"showIfValueIn": ["dropdown", "textfield", "numberfield", "datetime"]
|
|
210
|
+
},
|
|
211
|
+
"order": 10
|
|
212
|
+
},
|
|
213
|
+
"defaultValueMultiline": {
|
|
214
|
+
"title": "Default Value",
|
|
215
|
+
"description": "Default value for this field when the form loads. If the user does not provide a value, this default will be used. Can be a static value or bound to a data source.",
|
|
216
|
+
"type": "textarea",
|
|
217
|
+
"condition": {
|
|
218
|
+
"relativePath": "../type",
|
|
219
|
+
"showIfValueIn": ["textarea"]
|
|
220
|
+
},
|
|
221
|
+
"order": 11
|
|
222
|
+
},
|
|
223
|
+
"defaultValueBoolean": {
|
|
224
|
+
"title": "Default Value",
|
|
225
|
+
"description": "Default checked state for this checkbox when the form loads. If the user does not change it, this state will be used. Can be a static value or bound to a data source.",
|
|
226
|
+
"type": "boolean",
|
|
227
|
+
"condition": {
|
|
228
|
+
"relativePath": "../type",
|
|
229
|
+
"showIfValueIn": ["checkbox"]
|
|
230
|
+
},
|
|
231
|
+
"order": 12
|
|
184
232
|
},
|
|
185
233
|
"min": {
|
|
186
234
|
"title": "Minimum Value",
|
|
@@ -191,7 +239,7 @@
|
|
|
191
239
|
"relativePath": "../type",
|
|
192
240
|
"showIfValueIn": ["numberfield"]
|
|
193
241
|
},
|
|
194
|
-
"order":
|
|
242
|
+
"order": 13
|
|
195
243
|
},
|
|
196
244
|
"max": {
|
|
197
245
|
"title": "Maximum Value",
|
|
@@ -202,7 +250,7 @@
|
|
|
202
250
|
"relativePath": "../type",
|
|
203
251
|
"showIfValueIn": ["numberfield"]
|
|
204
252
|
},
|
|
205
|
-
"order":
|
|
253
|
+
"order": 14
|
|
206
254
|
},
|
|
207
255
|
"validation": {
|
|
208
256
|
"title": "Validation Regex",
|
|
@@ -213,7 +261,7 @@
|
|
|
213
261
|
"relativePath": "../type",
|
|
214
262
|
"showIfValueIn": ["textfield"]
|
|
215
263
|
},
|
|
216
|
-
"order":
|
|
264
|
+
"order": 15
|
|
217
265
|
},
|
|
218
266
|
"values": {
|
|
219
267
|
"title": "Dropdown Values",
|
|
@@ -242,7 +290,30 @@
|
|
|
242
290
|
}
|
|
243
291
|
}
|
|
244
292
|
},
|
|
245
|
-
"order":
|
|
293
|
+
"order": 16
|
|
294
|
+
},
|
|
295
|
+
"conditionalDisplay": {
|
|
296
|
+
"title": "Conditional Display",
|
|
297
|
+
"type": "object",
|
|
298
|
+
"description": "Optionally show this field only when another field in this form currently holds one of a set of values. Leave the controlling field empty to always show this field.",
|
|
299
|
+
"dataDrivenDisabled": true,
|
|
300
|
+
"order": 17,
|
|
301
|
+
"properties": {
|
|
302
|
+
"conditionField": {
|
|
303
|
+
"title": "Conditional On Field",
|
|
304
|
+
"type": "string",
|
|
305
|
+
"description": "The Label of another field in this form that controls whether this field is shown. Leave empty to always show this field. The match is against the other field's current value (for dropdowns, the option's stored value).",
|
|
306
|
+
"dataDrivenDisabled": true,
|
|
307
|
+
"order": 1
|
|
308
|
+
},
|
|
309
|
+
"conditionValues": {
|
|
310
|
+
"title": "Show If Value In",
|
|
311
|
+
"type": "string",
|
|
312
|
+
"description": "Comma-separated list of values. This field is shown only when the field named in 'Conditional On Field' currently holds one of these values. Example: pump, valve. For a checkbox controller use true or false. Has no effect unless 'Conditional On Field' is set.",
|
|
313
|
+
"dataDrivenDisabled": true,
|
|
314
|
+
"order": 2
|
|
315
|
+
}
|
|
316
|
+
}
|
|
246
317
|
}
|
|
247
318
|
}
|
|
248
319
|
}
|
package/src/widget-form.ts
CHANGED
|
@@ -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,24 +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 = field.preFilledValue ?? field.defaultValue ?? ''
|
|
121
|
-
} else if (field.type === 'checkbox') {
|
|
122
|
-
rawValue = formData.has(name) ? 'on' : 'off'
|
|
123
|
-
} else {
|
|
124
|
-
const entry = formData.get(name)
|
|
125
|
-
rawValue = entry === null || entry === '' ? (field.defaultValue ?? '') : entry
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
121
|
+
const targetColumn = {
|
|
129
122
|
swarm_app_databackend_key: field.targetColumn?.swarm_app_databackend_key,
|
|
130
123
|
table_name: field.targetColumn?.tablename,
|
|
131
|
-
column_name: field.targetColumn?.column
|
|
132
|
-
value: this.formatValue(rawValue, field.type ?? 'textfield')
|
|
124
|
+
column_name: field.targetColumn?.column
|
|
133
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') }
|
|
134
136
|
})
|
|
135
137
|
|
|
136
138
|
if (this.inputData?.deleteFlagColumn)
|
|
@@ -169,6 +171,31 @@ export class WidgetForm extends LitElement {
|
|
|
169
171
|
this.dialogOpen = false
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
// preFilledValue/defaultValue and their *Multiline/*Boolean twins are separate
|
|
175
|
+
// config keys (the editor shows a single-line, multi-line or checkbox input
|
|
176
|
+
// depending on the field type). Resolve by the current type — normalizing the
|
|
177
|
+
// boolean keys to 'true'/'false' strings for the existing downstream logic —
|
|
178
|
+
// and fall back to the single-line key so fields configured before the split
|
|
179
|
+
// keep their values. A value lingering in a hidden twin key after a type
|
|
180
|
+
// switch is ignored.
|
|
181
|
+
effectivePreFilledValue(field: Column): string | undefined {
|
|
182
|
+
if (field.type === 'textarea')
|
|
183
|
+
return (field.preFilledValueMultiline as string | undefined) ?? field.preFilledValue
|
|
184
|
+
if (field.type === 'checkbox')
|
|
185
|
+
return field.preFilledValueBoolean != null
|
|
186
|
+
? String(field.preFilledValueBoolean)
|
|
187
|
+
: field.preFilledValue
|
|
188
|
+
return field.preFilledValue
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
effectiveDefaultValue(field: Column): string | undefined {
|
|
192
|
+
if (field.type === 'textarea')
|
|
193
|
+
return (field.defaultValueMultiline as string | undefined) ?? field.defaultValue
|
|
194
|
+
if (field.type === 'checkbox')
|
|
195
|
+
return field.defaultValueBoolean != null ? String(field.defaultValueBoolean) : field.defaultValue
|
|
196
|
+
return field.defaultValue
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
formatValue(value: string, type: string): any {
|
|
173
200
|
switch (type) {
|
|
174
201
|
case 'numberfield': {
|
|
@@ -183,6 +210,56 @@ export class WidgetForm extends LitElement {
|
|
|
183
210
|
}
|
|
184
211
|
}
|
|
185
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
|
+
|
|
186
263
|
renderTextField(field: Column, i: number) {
|
|
187
264
|
return html`
|
|
188
265
|
<md-outlined-text-field
|
|
@@ -218,14 +295,16 @@ export class WidgetForm extends LitElement {
|
|
|
218
295
|
}
|
|
219
296
|
|
|
220
297
|
renderCheckbox(field: Column, i: number) {
|
|
298
|
+
const preFilledValue = this.effectivePreFilledValue(field)
|
|
299
|
+
const defaultValue = this.effectiveDefaultValue(field)
|
|
221
300
|
return html`
|
|
222
301
|
<div class="checkbox-container">
|
|
223
302
|
<md-checkbox
|
|
224
303
|
name="column-${i}"
|
|
225
304
|
aria-label=${field.label ?? ''}
|
|
226
|
-
?checked=${String(
|
|
305
|
+
?checked=${String(preFilledValue ?? defaultValue) === 'true'}
|
|
227
306
|
supporting-text=${field.description ?? ''}
|
|
228
|
-
?required=${field.required && !
|
|
307
|
+
?required=${field.required && !defaultValue && !preFilledValue}
|
|
229
308
|
></md-checkbox>
|
|
230
309
|
<label class="label"> ${field.label} </label>
|
|
231
310
|
</div>
|
|
@@ -233,15 +312,17 @@ export class WidgetForm extends LitElement {
|
|
|
233
312
|
}
|
|
234
313
|
|
|
235
314
|
renderTextArea(field: Column, i: number) {
|
|
315
|
+
const preFilledValue = this.effectivePreFilledValue(field)
|
|
316
|
+
const defaultValue = this.effectiveDefaultValue(field)
|
|
236
317
|
return html`
|
|
237
318
|
<md-outlined-text-field
|
|
238
319
|
.name="column-${i}"
|
|
239
320
|
.label="${field.label ?? ''}"
|
|
240
321
|
type="textarea"
|
|
241
|
-
.value="${
|
|
242
|
-
.placeholder="${
|
|
322
|
+
.value="${preFilledValue ?? ''}"
|
|
323
|
+
.placeholder="${defaultValue ?? ''}"
|
|
243
324
|
rows="3"
|
|
244
|
-
?required=${field.required && !
|
|
325
|
+
?required=${field.required && !defaultValue && !preFilledValue}
|
|
245
326
|
supporting-text=${field.description ?? ''}
|
|
246
327
|
></md-outlined-text-field>
|
|
247
328
|
`
|
|
@@ -293,6 +374,7 @@ export class WidgetForm extends LitElement {
|
|
|
293
374
|
|
|
294
375
|
resetForm() {
|
|
295
376
|
this.formKey++
|
|
377
|
+
this.fieldValues = {}
|
|
296
378
|
}
|
|
297
379
|
|
|
298
380
|
resolveRoute(item?: any): string | undefined {
|
|
@@ -600,12 +682,15 @@ export class WidgetForm extends LitElement {
|
|
|
600
682
|
method="dialog"
|
|
601
683
|
class="form-content"
|
|
602
684
|
@submit=${this.handleFormSubmit}
|
|
685
|
+
@input=${this.handleFieldChange}
|
|
686
|
+
@change=${this.handleFieldChange}
|
|
603
687
|
>
|
|
604
688
|
${repeat(
|
|
605
689
|
this.inputData?.formFields ?? [],
|
|
606
690
|
(field, i) => i,
|
|
607
691
|
(field, i) => {
|
|
608
692
|
if (field.hiddenField) return nothing
|
|
693
|
+
if (!this.isFieldVisible(field)) return nothing
|
|
609
694
|
switch (field.type) {
|
|
610
695
|
case 'textfield':
|
|
611
696
|
return this.renderTextField(field, i)
|