@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.
@@ -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 { InputData } from './definition-schema.js'
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<InputData['formFields'], undefined>[number]
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?: 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 name = `column-${i}`
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)