@nyaruka/temba-components 0.129.8 → 0.129.9

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.
Files changed (203) hide show
  1. package/CHANGELOG.md +27 -3
  2. package/demo/data/flows/sample-flow.json +186 -96
  3. package/dist/temba-components.js +414 -351
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/events.js.map +1 -1
  6. package/out-tsc/src/excellent/helpers.js +2 -2
  7. package/out-tsc/src/excellent/helpers.js.map +1 -1
  8. package/out-tsc/src/flow/CanvasNode.js +25 -7
  9. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  10. package/out-tsc/src/flow/Editor.js +11 -1
  11. package/out-tsc/src/flow/Editor.js.map +1 -1
  12. package/out-tsc/src/flow/NodeEditor.js +133 -290
  13. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  14. package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
  15. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  16. package/out-tsc/src/flow/actions/call_llm.js +56 -3
  17. package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
  18. package/out-tsc/src/flow/actions/call_webhook.js +1 -1
  19. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
  20. package/out-tsc/src/flow/actions/open_ticket.js +65 -3
  21. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
  22. package/out-tsc/src/flow/actions/set_run_result.js +75 -0
  23. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  24. package/out-tsc/src/flow/config.js +4 -0
  25. package/out-tsc/src/flow/config.js.map +1 -1
  26. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
  27. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
  28. package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
  29. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
  30. package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
  31. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  32. package/out-tsc/src/flow/types.js +0 -65
  33. package/out-tsc/src/flow/types.js.map +1 -1
  34. package/out-tsc/src/form/ArrayEditor.js +18 -61
  35. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  36. package/out-tsc/src/form/FieldRenderer.js +305 -0
  37. package/out-tsc/src/form/FieldRenderer.js.map +1 -0
  38. package/out-tsc/src/form/FormField.js +3 -3
  39. package/out-tsc/src/form/FormField.js.map +1 -1
  40. package/out-tsc/src/form/TextInput.js +1 -1
  41. package/out-tsc/src/form/TextInput.js.map +1 -1
  42. package/out-tsc/src/form/select/Select.js +48 -20
  43. package/out-tsc/src/form/select/Select.js.map +1 -1
  44. package/out-tsc/src/live/ContactChat.js +39 -13
  45. package/out-tsc/src/live/ContactChat.js.map +1 -1
  46. package/out-tsc/src/markdown.js +13 -11
  47. package/out-tsc/src/markdown.js.map +1 -1
  48. package/out-tsc/test/ActionHelper.js +2 -0
  49. package/out-tsc/test/ActionHelper.js.map +1 -1
  50. package/out-tsc/test/NodeHelper.js +148 -0
  51. package/out-tsc/test/NodeHelper.js.map +1 -0
  52. package/out-tsc/test/actions/call_llm.test.js +103 -0
  53. package/out-tsc/test/actions/call_llm.test.js.map +1 -0
  54. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
  55. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
  56. package/out-tsc/test/nodes/split_by_random.test.js +150 -0
  57. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
  58. package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
  59. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
  60. package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
  61. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
  62. package/out-tsc/test/temba-add-input-labels.test.js +70 -0
  63. package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
  64. package/out-tsc/test/temba-field-renderer.test.js +296 -0
  65. package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
  66. package/out-tsc/test/temba-markdown.test.js +1 -1
  67. package/out-tsc/test/temba-markdown.test.js.map +1 -1
  68. package/out-tsc/test/temba-node-editor.test.js +400 -0
  69. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  70. package/out-tsc/test/temba-select.test.js +6 -3
  71. package/out-tsc/test/temba-select.test.js.map +1 -1
  72. package/out-tsc/test/temba-webchat.test.js +1 -1
  73. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  74. package/package.json +1 -1
  75. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  76. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  77. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  78. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  79. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  80. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  81. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  82. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  83. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  84. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  85. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  86. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  87. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  88. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  89. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  90. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  91. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  92. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  93. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  94. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  95. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  96. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  97. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  98. package/screenshots/truth/editor/router.png +0 -0
  99. package/screenshots/truth/editor/send_msg.png +0 -0
  100. package/screenshots/truth/editor/set_contact_language.png +0 -0
  101. package/screenshots/truth/editor/set_contact_name.png +0 -0
  102. package/screenshots/truth/editor/set_run_result.png +0 -0
  103. package/screenshots/truth/editor/wait.png +0 -0
  104. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  105. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  106. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  107. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  108. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  109. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  110. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  111. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  112. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  113. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  114. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  115. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  116. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  117. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  118. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  119. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  120. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  121. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  122. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  123. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  124. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  125. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  126. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  127. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  128. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  129. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  130. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  131. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  132. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  133. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  134. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  135. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  136. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  141. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  142. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  143. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  144. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  145. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  146. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  147. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  148. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  149. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  150. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  151. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  152. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  153. package/screenshots/truth/omnibox/selected.png +0 -0
  154. package/screenshots/truth/select/functions.png +0 -0
  155. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  156. package/screenshots/truth/select/search-enabled.png +0 -0
  157. package/src/events.ts +8 -1
  158. package/src/excellent/helpers.ts +2 -2
  159. package/src/flow/CanvasNode.ts +22 -1
  160. package/src/flow/Editor.ts +12 -1
  161. package/src/flow/NodeEditor.ts +186 -374
  162. package/src/flow/actions/add_input_labels.ts +45 -0
  163. package/src/flow/actions/call_llm.ts +57 -3
  164. package/src/flow/actions/call_webhook.ts +1 -1
  165. package/src/flow/actions/open_ticket.ts +74 -3
  166. package/src/flow/actions/set_run_result.ts +83 -0
  167. package/src/flow/config.ts +4 -0
  168. package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
  169. package/src/flow/nodes/split_by_ticket.ts +19 -0
  170. package/src/flow/nodes/wait_for_response.ts +28 -1
  171. package/src/flow/types.ts +26 -127
  172. package/src/form/ArrayEditor.ts +34 -82
  173. package/src/form/FieldRenderer.ts +465 -0
  174. package/src/form/FormField.ts +3 -3
  175. package/src/form/TextInput.ts +1 -1
  176. package/src/form/select/Select.ts +51 -20
  177. package/src/live/ContactChat.ts +39 -15
  178. package/src/markdown.ts +19 -11
  179. package/src/store/flow-definition.d.ts +5 -2
  180. package/static/api/labels.json +31 -0
  181. package/static/api/topics.json +24 -9
  182. package/static/api/users.json +35 -16
  183. package/static/css/temba-components.css +3 -3
  184. package/stress-test.js +18 -13
  185. package/test/ActionHelper.ts +2 -0
  186. package/test/NodeHelper.ts +184 -0
  187. package/test/actions/call_llm.test.ts +137 -0
  188. package/test/nodes/README.md +78 -0
  189. package/test/nodes/split_by_llm_categorize.test.ts +698 -0
  190. package/test/nodes/split_by_random.test.ts +177 -0
  191. package/test/nodes/wait_for_digits.test.ts +176 -0
  192. package/test/nodes/wait_for_response.test.ts +206 -0
  193. package/test/temba-add-input-labels.test.ts +87 -0
  194. package/test/temba-field-renderer.test.ts +482 -0
  195. package/test/temba-markdown.test.ts +1 -1
  196. package/test/temba-node-editor.test.ts +496 -0
  197. package/test/temba-select.test.ts +6 -6
  198. package/test/temba-webchat.test.ts +1 -1
  199. package/test-assets/select/llms.json +18 -0
  200. package/web-dev-mock.mjs +96 -6
  201. package/web-dev-server.config.mjs +29 -7
  202. package/test/temba-flow-editor.test.ts.backup +0 -563
  203. package/test/temba-utils-index.test.ts.backup +0 -1737
package/src/flow/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TemplateResult } from 'lit-html';
2
- import { Action } from '../store/flow-definition';
2
+ import { Action, Node } from '../store/flow-definition';
3
3
 
4
4
  export interface ValidationResult {
5
5
  valid: boolean;
@@ -65,39 +65,16 @@ export interface SliderAttributes {
65
65
  range?: boolean;
66
66
  }
67
67
 
68
- // Widget configuration using discriminated union for type safety
69
- export type WidgetConfig =
70
- | { type: 'temba-textinput'; attributes?: TextInputAttributes }
71
- | { type: 'temba-completion'; attributes?: CompletionAttributes }
72
- | { type: 'temba-select'; attributes?: SelectAttributes }
73
- | { type: 'temba-checkbox'; attributes?: CheckboxAttributes }
74
- | { type: 'temba-slider'; attributes?: SliderAttributes }
75
- | { type: string; attributes?: { [key: string]: any } }; // Generic fallback
68
+ export interface FormData extends Record<string, any> {}
76
69
 
77
- // Property configuration with the clean structure you want
78
- export interface PropertyConfig {
79
- // Form field metadata
80
- label?: string;
81
- helpText?: string;
82
- required?: boolean;
83
- maxLength?: number;
84
- minLength?: number;
85
- pattern?: string;
86
-
87
- // Widget configuration
88
- widget: WidgetConfig;
89
-
90
- // Conditional behavior based on other field values
91
- conditions?: {
92
- // When to show this field
93
- visible?: (formData: any) => boolean;
94
-
95
- // When this field is disabled
96
- disabled?: (formData: any) => boolean;
97
- };
70
+ export interface FormConfig {
71
+ form?: Record<string, FieldConfig>;
72
+ layout?: LayoutItem[];
73
+ sanitize?: (formData: FormData) => void;
74
+ validate?: (formData: FormData) => ValidationResult;
98
75
  }
99
76
 
100
- export interface NodeConfig {
77
+ export interface NodeConfig extends FormConfig {
101
78
  type: string;
102
79
  name?: string;
103
80
  color?: string;
@@ -108,22 +85,28 @@ export interface NodeConfig {
108
85
  operand?: string;
109
86
  configurable?: boolean; // can the rules be configured in the UI
110
87
  rules?: {
111
- type: 'has_number_between' | 'has_string' | 'has_value' | 'has_not_value';
88
+ type:
89
+ | 'has_number_between'
90
+ | 'has_string'
91
+ | 'has_value'
92
+ | 'has_not_value'
93
+ | 'has_text';
112
94
  arguments: string[];
113
95
  categoryName: string;
114
96
  }[];
115
97
  };
116
- properties?: { [key: string]: PropertyConfig };
117
- toFormData?: (node: any) => any;
118
- fromFormData?: (formData: any, originalNode: any) => any;
98
+
99
+ toFormData?: (node: Node) => FormData;
100
+ fromFormData?: (formData: FormData, originalNode: Node) => Node;
101
+ render?: (node: Node) => TemplateResult;
119
102
  }
120
103
 
121
104
  // New field configuration system for generic form generation
122
105
  export interface BaseFieldConfig {
123
106
  label?: string;
124
107
  required?: boolean;
125
- evaluated?: boolean; // if this field supports expression evaluation
126
- dependsOn?: string[]; // fields this field depends on
108
+ evaluated?: boolean;
109
+ dependsOn?: string[];
127
110
  computeValue?: (
128
111
  values: Record<string, any>,
129
112
  currentValue: any,
@@ -137,7 +120,7 @@ export interface BaseFieldConfig {
137
120
  helpText?: string;
138
121
 
139
122
  // Layout properties
140
- maxWidth?: string; // CSS max-width value (e.g., '200px', '50%', '10rem')
123
+ maxWidth?: string;
141
124
 
142
125
  // Conditional rendering
143
126
  conditions?: {
@@ -160,7 +143,7 @@ export interface TextareaFieldConfig extends BaseFieldConfig {
160
143
 
161
144
  export interface SelectFieldConfig extends BaseFieldConfig {
162
145
  type: 'select';
163
- options: string[] | { value: string; label: string }[];
146
+ options?: string[] | { value: string; label: string }[];
164
147
  multi?: boolean;
165
148
  clearable?: boolean;
166
149
  searchable?: boolean;
@@ -171,7 +154,10 @@ export interface SelectFieldConfig extends BaseFieldConfig {
171
154
  nameKey?: string;
172
155
  endpoint?: string;
173
156
  emails?: boolean;
157
+ getName?: (item: any) => string;
174
158
  flavor?: 'small' | 'large';
159
+ createArbitraryOption?: (input: string, options: any[]) => any;
160
+ allowCreate?: boolean;
175
161
  }
176
162
 
177
163
  export interface KeyValueFieldConfig extends BaseFieldConfig {
@@ -256,7 +242,7 @@ export type LayoutItem =
256
242
  | GroupLayoutConfig
257
243
  | string; // string is shorthand for field
258
244
 
259
- export interface ActionConfig {
245
+ export interface ActionConfig extends FormConfig {
260
246
  name: string;
261
247
  color: string;
262
248
  evaluated?: string[];
@@ -265,13 +251,8 @@ export interface ActionConfig {
265
251
  form?: Record<string, FieldConfig>;
266
252
  layout?: LayoutItem[]; // optional layout configuration - array of layout items
267
253
 
268
- // Action editor configuration (legacy)
269
- // Form-level transformations
270
- sanitize?: (formData: any) => any;
271
254
  toFormData?: (action: Action) => any;
272
255
  fromFormData?: (formData: any) => Action;
273
-
274
- validate?: (action: Action) => ValidationResult;
275
256
  }
276
257
 
277
258
  export const COLORS = {
@@ -287,85 +268,3 @@ export const COLORS = {
287
268
  add: '#309c42',
288
269
  remove: '#e74c3c'
289
270
  };
290
-
291
- // Default property type mappings
292
- export function getDefaultComponent(value: any): WidgetConfig['type'] {
293
- if (typeof value === 'boolean') {
294
- return 'temba-checkbox';
295
- }
296
- if (typeof value === 'number') {
297
- return 'temba-textinput';
298
- }
299
- if (Array.isArray(value)) {
300
- return 'temba-select'; // For arrays, use multi-select
301
- }
302
- // Default to text input for strings and unknown types
303
- return 'temba-textinput';
304
- }
305
-
306
- // Get component properties for default mappings with proper typing
307
- export function getDefaultComponentProps(value: any): PropertyConfig {
308
- if (typeof value === 'boolean') {
309
- return {
310
- widget: { type: 'temba-checkbox' }
311
- };
312
- }
313
- if (typeof value === 'number') {
314
- return {
315
- widget: {
316
- type: 'temba-textinput',
317
- attributes: { type: 'number' }
318
- }
319
- };
320
- }
321
- if (Array.isArray(value)) {
322
- if (value.length > 0 && typeof value[0] === 'string') {
323
- return {
324
- widget: {
325
- type: 'temba-select',
326
- attributes: { multi: true, tags: true }
327
- }
328
- };
329
- }
330
- return {
331
- widget: {
332
- type: 'temba-select',
333
- attributes: { multi: true }
334
- }
335
- };
336
- }
337
- return {
338
- widget: { type: 'temba-textinput' }
339
- };
340
- }
341
-
342
- // Type guard functions for working with WidgetConfig
343
- export function isTextInputWidget(
344
- config: WidgetConfig
345
- ): config is { type: 'temba-textinput'; attributes?: TextInputAttributes } {
346
- return config.type === 'temba-textinput';
347
- }
348
-
349
- export function isCompletionWidget(
350
- config: WidgetConfig
351
- ): config is { type: 'temba-completion'; attributes?: CompletionAttributes } {
352
- return config.type === 'temba-completion';
353
- }
354
-
355
- export function isSelectWidget(
356
- config: WidgetConfig
357
- ): config is { type: 'temba-select'; attributes?: SelectAttributes } {
358
- return config.type === 'temba-select';
359
- }
360
-
361
- export function isCheckboxWidget(
362
- config: WidgetConfig
363
- ): config is { type: 'temba-checkbox'; attributes?: CheckboxAttributes } {
364
- return config.type === 'temba-checkbox';
365
- }
366
-
367
- export function isSliderWidget(
368
- config: WidgetConfig
369
- ): config is { type: 'slider'; attributes?: SliderAttributes } {
370
- return config.type === 'temba-slider';
371
- }
@@ -2,6 +2,7 @@ import { html, css, TemplateResult } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { FieldConfig, SelectFieldConfig } from '../flow/types';
4
4
  import { BaseListEditor, ListItem } from './BaseListEditor';
5
+ import { FieldRenderer } from './FieldRenderer';
5
6
 
6
7
  @customElement('temba-array-editor')
7
8
  export class TembaArrayEditor extends BaseListEditor<ListItem> {
@@ -132,87 +133,42 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
132
133
  ): TemplateResult {
133
134
  const computedValue = this.computeFieldValue(itemIndex, fieldName, config);
134
135
 
135
- switch (config.type) {
136
- case 'text':
137
- return html`<temba-textinput
138
- .value=${computedValue || ''}
139
- .placeholder=${config.placeholder}
140
- @change=${(e: any) =>
141
- this.handleFieldChange(itemIndex, fieldName, e.target.value)}
142
- ></temba-textinput>`;
143
-
144
- case 'textarea':
145
- return html`<temba-textinput
146
- .value=${computedValue || ''}
147
- .placeholder=${config.placeholder}
148
- textarea
149
- .rows=${config.rows || 3}
150
- @change=${(e: any) =>
151
- this.handleFieldChange(itemIndex, fieldName, e.target.value)}
152
- ></temba-textinput>`;
153
-
154
- case 'select': {
155
- const selectConfig = config as SelectFieldConfig;
156
- const fieldValue = this.computeFieldValue(itemIndex, fieldName, config);
157
-
158
- return html`<temba-select
159
- class="form-control"
160
- ?clearable="${selectConfig.clearable || false}"
161
- ?searchable="${selectConfig.searchable || false}"
162
- ?tags="${selectConfig.tags || false}"
163
- ?multi="${selectConfig.multi || false}"
164
- ?emails="${selectConfig.emails || false}"
165
- placeholder="${selectConfig.placeholder || ''}"
166
- maxItems="${selectConfig.maxItems || 0}"
167
- valueKey="${selectConfig.valueKey || 'value'}"
168
- nameKey="${selectConfig.nameKey || 'name'}"
169
- endpoint="${selectConfig.endpoint || ''}"
170
- value="${fieldValue || ''}"
171
- flavor="small"
172
- @change="${(e: Event) => {
173
- const target = e.target as any;
174
- let value: any;
175
-
176
- // For temba-select, extract the correct value
177
- if (target.tagName === 'TEMBA-SELECT') {
178
- if (target.multi || target.emails || target.tags) {
179
- value = target.values || [];
180
- } else {
181
- // Single select: extract value from first selected option
182
- const values = target.values || [];
183
- value =
184
- values.length > 0 && values[0]
185
- ? values[0].value !== undefined
186
- ? values[0].value
187
- : values[0]
188
- : '';
189
- }
136
+ // Use FieldRenderer for consistent field rendering
137
+ return FieldRenderer.renderField(fieldName, config, computedValue, {
138
+ showLabel: false, // ArrayEditor doesn't show labels for individual fields
139
+ flavor: 'small', // ArrayEditor uses small flavor
140
+ extraClasses: 'form-control',
141
+ onChange: (e: Event) => {
142
+ let value: any;
143
+ const target = e.target as any;
144
+
145
+ // Handle different field types and their change events
146
+ if (config.type === 'select') {
147
+ // For temba-select, extract the correct value
148
+ if (target.tagName === 'TEMBA-SELECT') {
149
+ if (target.multi || target.emails || target.tags) {
150
+ value = target.values || [];
190
151
  } else {
191
- value = target.value;
152
+ // Single select: extract value from first selected option
153
+ const values = target.values || [];
154
+ value =
155
+ values.length > 0 && values[0]
156
+ ? values[0].value !== undefined
157
+ ? values[0].value
158
+ : values[0]
159
+ : '';
192
160
  }
193
-
194
- this.handleFieldChange(itemIndex, fieldName, value);
195
- }}"
196
- >
197
- ${selectConfig.options?.map((option: any) => {
198
- if (typeof option === 'string') {
199
- return html`<temba-option
200
- name="${option}"
201
- value="${option}"
202
- ></temba-option>`;
203
- } else {
204
- return html`<temba-option
205
- name="${option.label || option.name}"
206
- value="${option.value}"
207
- ></temba-option>`;
208
- }
209
- })}
210
- </temba-select>`;
161
+ } else {
162
+ value = target.value;
163
+ }
164
+ } else {
165
+ // For other field types, use the target value directly
166
+ value = target.value;
167
+ }
168
+
169
+ this.handleFieldChange(itemIndex, fieldName, value);
211
170
  }
212
-
213
- default:
214
- return html`<span>Unsupported field type: ${config.type}</span>`;
215
- }
171
+ });
216
172
  }
217
173
 
218
174
  renderItem(item: ListItem, index: number): TemplateResult {
@@ -283,10 +239,6 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
283
239
  flex: 1;
284
240
  }
285
241
 
286
- .field:first-child {
287
- flex: 0 0 140px; /* Fixed width for type dropdown */
288
- }
289
-
290
242
  .field label {
291
243
  display: block;
292
244
  margin-bottom: 4px;