@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.
@@ -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
- "order": 8
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": 9
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": 10
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": 11
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": 12
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
  }
@@ -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 name = `column-${i}`
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(field.preFilledValue ?? field.defaultValue) === 'true'}
305
+ ?checked=${String(preFilledValue ?? defaultValue) === 'true'}
227
306
  supporting-text=${field.description ?? ''}
228
- ?required=${field.required && !field.defaultValue && !field.preFilledValue}
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="${field.preFilledValue ?? ''}"
242
- .placeholder="${field.defaultValue ?? ''}"
322
+ .value="${preFilledValue ?? ''}"
323
+ .placeholder="${defaultValue ?? ''}"
243
324
  rows="3"
244
- ?required=${field.required && !field.defaultValue && !field.preFilledValue}
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)