@nyaruka/temba-components 0.129.9 → 0.129.10

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 (118) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/demo/test-colorpicker.html +30 -0
  3. package/dist/temba-components.js +867 -915
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/events.js.map +1 -1
  6. package/out-tsc/src/form/ArrayEditor.js +45 -56
  7. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  8. package/out-tsc/src/form/BaseListEditor.js +4 -3
  9. package/out-tsc/src/form/BaseListEditor.js.map +1 -1
  10. package/out-tsc/src/form/Checkbox.js +77 -24
  11. package/out-tsc/src/form/Checkbox.js.map +1 -1
  12. package/out-tsc/src/form/ColorPicker.js +28 -40
  13. package/out-tsc/src/form/ColorPicker.js.map +1 -1
  14. package/out-tsc/src/form/Completion.js +44 -53
  15. package/out-tsc/src/form/Completion.js.map +1 -1
  16. package/out-tsc/src/form/Compose.js +7 -8
  17. package/out-tsc/src/form/Compose.js.map +1 -1
  18. package/out-tsc/src/form/ContactSearch.js +3 -4
  19. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  20. package/out-tsc/src/form/DatePicker.js +29 -36
  21. package/out-tsc/src/form/DatePicker.js.map +1 -1
  22. package/out-tsc/src/form/{FormField.js → FieldElement.js} +78 -50
  23. package/out-tsc/src/form/FieldElement.js.map +1 -0
  24. package/out-tsc/src/form/FieldRenderer.js +2 -1
  25. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  26. package/out-tsc/src/form/ImagePicker.js +122 -126
  27. package/out-tsc/src/form/ImagePicker.js.map +1 -1
  28. package/out-tsc/src/form/KeyValueEditor.js +41 -37
  29. package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
  30. package/out-tsc/src/form/MessageEditor.js +55 -63
  31. package/out-tsc/src/form/MessageEditor.js.map +1 -1
  32. package/out-tsc/src/form/TembaSlider.js +3 -3
  33. package/out-tsc/src/form/TembaSlider.js.map +1 -1
  34. package/out-tsc/src/form/TemplateEditor.js +3 -3
  35. package/out-tsc/src/form/TemplateEditor.js.map +1 -1
  36. package/out-tsc/src/form/TextInput.js +22 -26
  37. package/out-tsc/src/form/TextInput.js.map +1 -1
  38. package/out-tsc/src/form/select/Select.js +9 -15
  39. package/out-tsc/src/form/select/Select.js.map +1 -1
  40. package/out-tsc/src/form/select/UserSelect.js +8 -9
  41. package/out-tsc/src/form/select/UserSelect.js.map +1 -1
  42. package/out-tsc/src/form/select/WorkspaceSelect.js +7 -8
  43. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -1
  44. package/out-tsc/src/live/ContactChat.js +32 -40
  45. package/out-tsc/src/live/ContactChat.js.map +1 -1
  46. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -1
  47. package/out-tsc/temba-modules.js +3 -2
  48. package/out-tsc/temba-modules.js.map +1 -1
  49. package/out-tsc/test/temba-checkbox.test.js +16 -0
  50. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  51. package/out-tsc/test/temba-integration-markdown.test.js +2 -4
  52. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  53. package/out-tsc/test/temba-slider.test.js +0 -1
  54. package/out-tsc/test/temba-slider.test.js.map +1 -1
  55. package/package.json +1 -1
  56. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  57. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  58. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  59. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  60. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  61. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  62. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  63. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  64. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  65. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  66. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  67. package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
  68. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  69. package/screenshots/truth/checkbox/checked.png +0 -0
  70. package/screenshots/truth/checkbox/default.png +0 -0
  71. package/screenshots/truth/colorpicker/default.png +0 -0
  72. package/screenshots/truth/colorpicker/focused.png +0 -0
  73. package/screenshots/truth/colorpicker/initialized.png +0 -0
  74. package/screenshots/truth/colorpicker/selected.png +0 -0
  75. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  76. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  77. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  78. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  79. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  80. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  81. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  82. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  83. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  84. package/screenshots/truth/run-list/basic.png +0 -0
  85. package/src/events.ts +5 -6
  86. package/src/form/ArrayEditor.ts +45 -57
  87. package/src/form/BaseListEditor.ts +4 -4
  88. package/src/form/Checkbox.ts +81 -24
  89. package/src/form/ColorPicker.ts +31 -43
  90. package/src/form/Completion.ts +49 -56
  91. package/src/form/Compose.ts +8 -8
  92. package/src/form/ContactSearch.ts +3 -4
  93. package/src/form/DatePicker.ts +32 -38
  94. package/src/form/{FormField.ts → FieldElement.ts} +105 -52
  95. package/src/form/FieldRenderer.ts +2 -1
  96. package/src/form/ImagePicker.ts +107 -110
  97. package/src/form/KeyValueEditor.ts +43 -39
  98. package/src/form/MessageEditor.ts +61 -67
  99. package/src/form/TembaSlider.ts +3 -3
  100. package/src/form/TemplateEditor.ts +3 -3
  101. package/src/form/TextInput.ts +25 -28
  102. package/src/form/select/Select.ts +12 -17
  103. package/src/form/select/UserSelect.ts +10 -11
  104. package/src/form/select/WorkspaceSelect.ts +9 -10
  105. package/src/live/ContactChat.ts +32 -41
  106. package/src/live/ContactFieldEditor.ts +2 -2
  107. package/temba-modules.ts +3 -2
  108. package/test/temba-checkbox.test.ts +26 -0
  109. package/test/temba-integration-markdown.test.ts +2 -4
  110. package/test/temba-slider.test.ts +0 -1
  111. package/test-assets/contacts/history.json +7 -20
  112. package/out-tsc/src/form/FormElement.js +0 -67
  113. package/out-tsc/src/form/FormElement.js.map +0 -1
  114. package/out-tsc/src/form/FormField.js.map +0 -1
  115. package/out-tsc/test/temba-formfield.test.js +0 -94
  116. package/out-tsc/test/temba-formfield.test.js.map +0 -1
  117. package/src/form/FormElement.ts +0 -69
  118. package/test/temba-formfield.test.ts +0 -121
@@ -1,7 +1,7 @@
1
1
  import { TemplateResult, css, html } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { ifDefined } from 'lit-html/directives/if-defined.js';
4
- import { FormElement } from './FormElement';
4
+ import { FieldElement } from './FieldElement';
5
5
  import { Completion } from './Completion';
6
6
  import { MediaPicker } from './MediaPicker';
7
7
  import { Attachment } from '../interfaces';
@@ -12,9 +12,10 @@ import { Icon } from '../Icons';
12
12
  * MessageEditor is a composed component that combines temba-completion and temba-media-picker
13
13
  * for editing messages with text completion and file attachments
14
14
  */
15
- export class MessageEditor extends FormElement {
15
+ export class MessageEditor extends FieldElement {
16
16
  static get styles() {
17
17
  return css`
18
+ ${super.styles}
18
19
  :host {
19
20
  display: block;
20
21
  }
@@ -130,9 +131,6 @@ export class MessageEditor extends FormElement {
130
131
  @property({ type: String })
131
132
  name = '';
132
133
 
133
- @property({ type: String })
134
- value = '';
135
-
136
134
  @property({ type: String })
137
135
  placeholder = '';
138
136
 
@@ -373,77 +371,73 @@ export class MessageEditor extends FormElement {
373
371
  }
374
372
 
375
373
  public render(): TemplateResult {
374
+ return this.renderField();
375
+ }
376
+
377
+ protected renderWidget(): TemplateResult {
376
378
  const hasAttachments = this.hasStaticAttachments();
377
379
 
378
380
  return html`
379
- <temba-field
380
- name=${this.name}
381
- .label=${this.label}
382
- .helpText=${this.helpText}
383
- .errors=${this.errors}
384
- .widgetOnly=${this.widgetOnly}
381
+ <div
382
+ class=${getClasses({
383
+ 'message-editor-container': true,
384
+ highlight: this.pendingDrop,
385
+ 'has-attachments': hasAttachments
386
+ })}
387
+ @dragenter=${this.handleDragEnter}
388
+ @dragover=${this.handleDragOver}
389
+ @dragleave=${this.handleDragLeave}
390
+ @drop=${this.handleDrop}
385
391
  >
386
- <div
387
- class=${getClasses({
388
- 'message-editor-container': true,
389
- highlight: this.pendingDrop,
390
- 'has-attachments': hasAttachments
391
- })}
392
- @dragenter=${this.handleDragEnter}
393
- @dragover=${this.handleDragOver}
394
- @dragleave=${this.handleDragLeave}
395
- @drop=${this.handleDrop}
396
- >
397
- <div class="completion-wrapper">
398
- <temba-completion
399
- name=${this.name}
400
- .value=${this.value}
401
- placeholder=${this.placeholder}
402
- ?textarea=${this.textarea}
403
- ?autogrow=${this.autogrow}
404
- ?session=${this.session}
405
- ?submitOnEnter=${this.submitOnEnter}
406
- ?gsm=${this.gsm}
407
- ?disableCompletion=${this.disableCompletion}
408
- maxlength=${ifDefined(this.maxLength)}
409
- counter=${ifDefined(this.counter)}
410
- minHeight=${ifDefined(this.minHeight)}
411
- widgetOnly
412
- @change=${this.handleCompletionChange}
413
- ></temba-completion>
414
- </div>
415
-
416
- <div class="media-wrapper ">
417
- <temba-media-picker
392
+ <div class="completion-wrapper">
393
+ <temba-completion
394
+ name=${this.name}
395
+ .value=${this.value}
396
+ placeholder=${this.placeholder}
397
+ ?textarea=${this.textarea}
398
+ ?autogrow=${this.autogrow}
399
+ ?session=${this.session}
400
+ ?submitOnEnter=${this.submitOnEnter}
401
+ ?gsm=${this.gsm}
402
+ ?disableCompletion=${this.disableCompletion}
403
+ maxlength=${ifDefined(this.maxLength)}
404
+ counter=${ifDefined(this.counter)}
405
+ minHeight=${ifDefined(this.minHeight)}
406
+ widgetOnly
407
+ @change=${this.handleCompletionChange}
408
+ ></temba-completion>
409
+ </div>
410
+
411
+ <div class="media-wrapper ">
412
+ <temba-media-picker
413
+ .accept=${this.accept}
414
+ .max=${this.maxAttachments}
415
+ .endpoint=${this.endpoint}
416
+ @change=${this.handleMediaChange}
417
+ ignoreDrops
418
+ ></temba-media-picker>
419
+ </div>
420
+ <temba-icon
421
+ class="attachment-icon"
422
+ name=${Icon.attachment}
423
+ size="1.2"
424
+ @click=${this.handleAttachmentIconClick}
425
+ ></temba-icon>
426
+
427
+ <div class="drop-overlay"></div>
428
+
429
+ <!-- Hidden media picker for handling uploads when no attachments are shown -->
430
+ ${!hasAttachments
431
+ ? html`<temba-media-picker
432
+ style="display: none;"
418
433
  .accept=${this.accept}
419
434
  .max=${this.maxAttachments}
420
435
  .endpoint=${this.endpoint}
421
436
  @change=${this.handleMediaChange}
422
437
  ignoreDrops
423
- ></temba-media-picker>
424
- </div>
425
- <temba-icon
426
- class="attachment-icon"
427
- name=${Icon.attachment}
428
- size="1.2"
429
- @click=${this.handleAttachmentIconClick}
430
- ></temba-icon>
431
-
432
- <div class="drop-overlay"></div>
433
-
434
- <!-- Hidden media picker for handling uploads when no attachments are shown -->
435
- ${!hasAttachments
436
- ? html`<temba-media-picker
437
- style="display: none;"
438
- .accept=${this.accept}
439
- .max=${this.maxAttachments}
440
- .endpoint=${this.endpoint}
441
- @change=${this.handleMediaChange}
442
- ignoreDrops
443
- ></temba-media-picker>`
444
- : ''}
445
- </div>
446
- </temba-field>
438
+ ></temba-media-picker>`
439
+ : ''}
440
+ </div>
447
441
  `;
448
442
  }
449
443
  }
@@ -1,10 +1,10 @@
1
1
  import { css, html, TemplateResult } from 'lit';
2
2
  import { styleMap } from 'lit-html/directives/style-map.js';
3
3
  import { property } from 'lit/decorators.js';
4
- import { FormElement } from './FormElement';
4
+ import { FieldElement } from './FieldElement';
5
5
  import { getClasses } from '../utils';
6
6
 
7
- export class TembaSlider extends FormElement {
7
+ export class TembaSlider extends FieldElement {
8
8
  static get styles() {
9
9
  return css`
10
10
  :host {
@@ -142,7 +142,7 @@ export class TembaSlider extends FormElement {
142
142
  this.requestUpdate();
143
143
  }
144
144
 
145
- public render(): TemplateResult {
145
+ public renderWidget(): TemplateResult {
146
146
  return html` <div class="${getClasses({ grabbed: this.grabbed })}">
147
147
  <div
148
148
  style=${styleMap({ left: this.circleX + 'px' })}
@@ -1,9 +1,9 @@
1
1
  import { property } from 'lit/decorators.js';
2
- import { FormElement } from './FormElement';
3
2
  import { TemplateResult, html, css, LitElement } from 'lit';
4
3
  import { CustomEventType } from '../interfaces';
5
4
  import { MediaPicker } from './MediaPicker';
6
5
  import { getClasses } from '../utils';
6
+ import { FieldElement } from './FieldElement';
7
7
 
8
8
  interface Component {
9
9
  name: string;
@@ -28,7 +28,7 @@ interface Template {
28
28
  base_translation: Translation;
29
29
  }
30
30
 
31
- export class TemplateEditor extends FormElement {
31
+ export class TemplateEditor extends FieldElement {
32
32
  static shadowRootOptions = {
33
33
  ...LitElement.shadowRootOptions,
34
34
  delegatesFocus: true
@@ -457,7 +457,7 @@ export class TemplateEditor extends FormElement {
457
457
  </div>`;
458
458
  }
459
459
 
460
- public render(): TemplateResult {
460
+ public renderWidget(): TemplateResult {
461
461
  let content = null;
462
462
  if (this.translation) {
463
463
  content = this.renderComponents(this.translation.components);
@@ -2,7 +2,7 @@ import { TemplateResult, html, css } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { ifDefined } from 'lit-html/directives/if-defined.js';
4
4
  import { styleMap } from 'lit-html/directives/style-map.js';
5
- import { FormElement } from './FormElement';
5
+ import { FieldElement } from './FieldElement';
6
6
  import { Modax } from '../layout/Modax';
7
7
  import { sanitizeUnintendedUnicode } from '../utils';
8
8
  import { CharCount } from '../display/CharCount';
@@ -13,9 +13,10 @@ export enum InputType {
13
13
  Number = 'number'
14
14
  }
15
15
 
16
- export class TextInput extends FormElement {
16
+ export class TextInput extends FieldElement {
17
17
  static get styles() {
18
18
  return css`
19
+ ${super.styles}
19
20
  .input-container {
20
21
  border-radius: var(--curvature-widget);
21
22
  cursor: var(--input-cursor);
@@ -368,9 +369,15 @@ export class TextInput extends FormElement {
368
369
 
369
370
  // TODO make this a formelement and have contactsearch set the root
370
371
  public render(): TemplateResult {
371
- const containerStyle = {
372
- height: `${this.textarea ? '100%' : 'auto'}`
373
- };
372
+ return this.renderField();
373
+ }
374
+
375
+ protected renderWidget() {
376
+ const containerStyle: any = {};
377
+ if (this.counter) {
378
+ containerStyle['--counter-background'] =
379
+ 'var(--color-widget-border, transparent)';
380
+ }
374
381
 
375
382
  const clear =
376
383
  this.clearable && this.inputElement && this.inputElement.value
@@ -474,31 +481,21 @@ export class TextInput extends FormElement {
474
481
  }
475
482
 
476
483
  return html`
477
- <temba-field
478
- name=${this.name}
479
- .label="${this.label}"
480
- .helpText="${this.helpText}"
481
- .errors=${this.errors}
482
- .widgetOnly=${this.widgetOnly}
483
- .hideLabel=${this.hideLabel}
484
- .disabled=${this.disabled}
484
+ <div
485
+ class="input-container"
486
+ style=${styleMap(containerStyle)}
487
+ @click=${this.handleContainerClick}
485
488
  >
486
- <div
487
- class="input-container"
488
- style=${styleMap(containerStyle)}
489
- @click=${this.handleContainerClick}
489
+ <slot name="prefix"></slot>
490
+
491
+ ${input} ${clear}
492
+ <slot name="type" class="type-icon"
493
+ >${this.type === InputType.Number
494
+ ? html`<temba-icon name="number"></temba-icon>`
495
+ : null}</slot
490
496
  >
491
- <slot name="prefix"></slot>
492
-
493
- ${input} ${clear}
494
- <slot name="type" class="type-icon"
495
- >${this.type === InputType.Number
496
- ? html`<temba-icon name="number"></temba-icon>`
497
- : null}</slot
498
- >
499
- <slot></slot>
500
- </div>
501
- </temba-field>
497
+ <slot></slot>
498
+ </div>
502
499
  `;
503
500
  }
504
501
  }
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-function */
2
- import { TemplateResult, html, css, CSSResult, CSSResultArray } from 'lit';
2
+ import { TemplateResult, html, css } from 'lit';
3
3
  import { property, state } from 'lit/decorators.js';
4
4
  import {
5
5
  getUrl,
@@ -11,7 +11,7 @@ import {
11
11
  import '../../display/Options';
12
12
  import '../../list/SortableList';
13
13
  import { EventHandler } from '../../RapidElement';
14
- import { FormElement } from '../../form/FormElement';
14
+ import { FieldElement } from '../../form/FieldElement';
15
15
 
16
16
  import { lru } from 'tiny-lru';
17
17
  import { CompletionOption, CustomEventType, Position } from '../../interfaces';
@@ -35,11 +35,13 @@ export interface SelectOption {
35
35
  arbitrary?: boolean;
36
36
  }
37
37
 
38
- export class Select<T extends SelectOption> extends FormElement {
38
+ export class Select<T extends SelectOption> extends FieldElement {
39
39
  private hiddenInputs: HTMLInputElement[] = [];
40
40
 
41
- static get styles(): CSSResult | CSSResultArray {
41
+ static get styles() {
42
42
  return css`
43
+ ${super.styles}
44
+
43
45
  :host {
44
46
  --transition-speed: 0;
45
47
  font-family: var(--font-family);
@@ -1660,7 +1662,7 @@ export class Select<T extends SelectOption> extends FormElement {
1660
1662
  }
1661
1663
  }
1662
1664
 
1663
- public render(): TemplateResult {
1665
+ protected renderWidget(): TemplateResult {
1664
1666
  const placeholder = this.values.length === 0 ? this.placeholder : '';
1665
1667
 
1666
1668
  // Single unified placeholder - shows when empty and (not focused OR not searchable)
@@ -1845,16 +1847,6 @@ export class Select<T extends SelectOption> extends FormElement {
1845
1847
  ${placeholderElement}`}`;
1846
1848
 
1847
1849
  return html`
1848
-
1849
- <temba-field
1850
- name=${this.name}
1851
- .label=${this.label}
1852
- .helpText=${this.helpText}
1853
- .errors=${this.errors}
1854
- .widgetOnly=${this.widgetOnly}
1855
- .hideErrors=${this.hideErrors}
1856
- ?disabled=${this.disabled}
1857
- >
1858
1850
  <slot></slot>
1859
1851
  <div class="wrapper-bg">
1860
1852
 
@@ -1948,7 +1940,10 @@ export class Select<T extends SelectOption> extends FormElement {
1948
1940
  : null
1949
1941
  }
1950
1942
  </temba-options>
1951
- </temba-field>
1952
- `;
1943
+ `;
1944
+ }
1945
+
1946
+ public render(): TemplateResult {
1947
+ return this.renderField();
1953
1948
  }
1954
1949
  }
@@ -1,4 +1,4 @@
1
- import { css, CSSResultArray, html, TemplateResult } from 'lit';
1
+ import { css, html, TemplateResult } from 'lit';
2
2
  import { Select, SelectOption } from './Select';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { getFullName } from '../../display/TembaUser';
@@ -10,16 +10,15 @@ export interface UserOption extends SelectOption {
10
10
  }
11
11
 
12
12
  export class UserSelect extends Select<UserOption> {
13
- static get styles(): CSSResultArray {
14
- return [
15
- super.styles,
16
- css`
17
- :host {
18
- width: 150px;
19
- display: block;
20
- }
21
- `
22
- ];
13
+ static get styles() {
14
+ return css`
15
+ ${super.styles}
16
+
17
+ :host {
18
+ width: 150px;
19
+ display: block;
20
+ }
21
+ `;
23
22
  }
24
23
 
25
24
  @property({ type: String })
@@ -1,4 +1,4 @@
1
- import { css, CSSResultArray, html, TemplateResult } from 'lit';
1
+ import { css, html, TemplateResult } from 'lit';
2
2
  import { Select, SelectOption } from './Select';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { getScrollParent } from '../../utils';
@@ -10,15 +10,14 @@ export interface WorkspaceOption extends SelectOption {
10
10
  }
11
11
 
12
12
  export class WorkspaceSelect extends Select<WorkspaceOption> {
13
- static get styles(): CSSResultArray {
14
- return [
15
- super.styles,
16
- css`
17
- :host {
18
- border: 0px solid blue;
19
- }
20
- `
21
- ];
13
+ static get styles() {
14
+ return css`
15
+ ${super.styles}
16
+
17
+ :host {
18
+ border: 0px solid blue;
19
+ }
20
+ `;
22
21
  }
23
22
 
24
23
  @property({ type: String })
@@ -22,11 +22,11 @@ import {
22
22
  AirtimeTransferredEvent,
23
23
  CallEvent,
24
24
  ChannelEvent,
25
+ ChatStartedEvent,
25
26
  ContactEvent,
26
27
  ContactGroupsEvent,
27
28
  ContactHistoryPage,
28
29
  ContactLanguageChangedEvent,
29
- FlowEvent,
30
30
  MsgEvent,
31
31
  NameChangedEvent,
32
32
  OptInEvent,
@@ -51,36 +51,35 @@ export const BODY_SNIPPET_LENGTH = 250;
51
51
  */
52
52
 
53
53
  export enum Events {
54
- MESSAGE_CREATED = 'msg_created',
55
- MESSAGE_RECEIVED = 'msg_received',
54
+ AIRTIME_TRANSFERRED = 'airtime_transferred',
56
55
  BROADCAST_CREATED = 'broadcast_created',
57
- IVR_CREATED = 'ivr_created',
58
56
  CALL_CREATED = 'call_created',
57
+ CALL_MISSED = 'call_missed',
59
58
  CALL_RECEIVED = 'call_received',
59
+ CHAT_STARTED = 'chat_started',
60
60
  CONTACT_FIELD_CHANGED = 'contact_field_changed',
61
61
  CONTACT_GROUPS_CHANGED = 'contact_groups_changed',
62
+ CONTACT_LANGUAGE_CHANGED = 'contact_language_changed',
62
63
  CONTACT_NAME_CHANGED = 'contact_name_changed',
63
64
  CONTACT_URNS_CHANGED = 'contact_urns_changed',
64
- CHANNEL_EVENT = 'channel_event',
65
- CONTACT_LANGUAGE_CHANGED = 'contact_language_changed',
66
- AIRTIME_TRANSFERRED = 'airtime_transferred',
65
+ IVR_CREATED = 'ivr_created',
66
+ MSG_CREATED = 'msg_created',
67
+ MSG_RECEIVED = 'msg_received',
67
68
  NOTE_CREATED = 'note_created',
69
+ OPTIN_REQUESTED = 'optin_requested',
70
+ OPTIN_STARTED = 'optin_started',
71
+ OPTIN_STOPPED = 'optin_stopped',
72
+ RUN_ENDED = 'run_ended',
73
+ RUN_STARTED = 'run_started',
68
74
  TICKET_ASSIGNED = 'ticket_assigned',
69
- TICKET_NOTE_ADDED = 'ticket_note_added',
70
75
  TICKET_CLOSED = 'ticket_closed',
76
+ TICKET_NOTE_ADDED = 'ticket_note_added',
71
77
  TICKET_OPENED = 'ticket_opened',
72
78
  TICKET_REOPENED = 'ticket_reopened',
73
79
  TICKET_TOPIC_CHANGED = 'ticket_topic_changed',
74
- OPTIN_STARTED = 'optin_started',
75
- OPTIN_STOPPED = 'optin_stopped',
76
- OPTIN_REQUESTED = 'optin_requested',
77
- RUN_STARTED = 'run_started',
78
- RUN_ENDED = 'run_ended',
79
80
 
80
81
  // deprecated
81
- CALL_STARTED = 'call_started',
82
- FLOW_ENTERED = 'flow_entered',
83
- FLOW_EXITED = 'flow_exited'
82
+ CHANNEL_EVENT = 'channel_event'
84
83
  }
85
84
 
86
85
  const renderInfoList = (singular: string, plural: string, items: any[]) => {
@@ -123,18 +122,6 @@ const renderChannelEvent = (event: ChannelEvent): string => {
123
122
  }
124
123
  };
125
124
 
126
- const renderFlowEvent = (event: FlowEvent): string => {
127
- let verb = 'Interrupted';
128
- if (event.status !== 'I') {
129
- if (event.type === Events.FLOW_ENTERED) {
130
- verb = 'Started';
131
- } else {
132
- verb = 'Completed';
133
- }
134
- }
135
- return `${verb} [**${event.flow.name}**](/flow/editor/${event.flow.uuid}/)`;
136
- };
137
-
138
125
  const renderRunEvent = (event: RunEvent): string => {
139
126
  let verb = 'Started';
140
127
  if (event.type === Events.RUN_ENDED) {
@@ -150,6 +137,14 @@ const renderRunEvent = (event: RunEvent): string => {
150
137
  return `${verb} [**${event.flow.name}**](/flow/editor/${event.flow.uuid}/)`;
151
138
  };
152
139
 
140
+ const renderChatStartedEvent = (event: ChatStartedEvent): string => {
141
+ if (event.params) {
142
+ return `Chat referral`;
143
+ } else {
144
+ return `Chat started`;
145
+ }
146
+ };
147
+
153
148
  const renderUpdateEvent = (event: UpdateFieldEvent): string => {
154
149
  return event.value
155
150
  ? `Updated **${event.field.name}** to **${event.value.text}**`
@@ -227,9 +222,11 @@ export const renderContactLanguageChangedEvent = (
227
222
 
228
223
  export const renderCallEvent = (event: CallEvent): string => {
229
224
  if (event.type === Events.CALL_CREATED) {
230
- return `Call Started`;
225
+ return `Call started`;
226
+ } else if (event.type === Events.CALL_MISSED) {
227
+ return `Call missed`;
231
228
  } else if (event.type === Events.CALL_RECEIVED) {
232
- return `Call Answered`;
229
+ return `Call answered`;
233
230
  }
234
231
  };
235
232
 
@@ -665,13 +662,6 @@ export class ContactChat extends ContactStoreElement {
665
662
  text: `Topic changed to **${(event as TicketEvent).topic.name}**`
666
663
  };
667
664
  break;
668
- case Events.FLOW_ENTERED:
669
- case Events.FLOW_EXITED:
670
- message = {
671
- type: MessageType.Inline,
672
- text: renderFlowEvent(event as FlowEvent)
673
- };
674
- break;
675
665
  case Events.RUN_STARTED:
676
666
  case Events.RUN_ENDED:
677
667
  message = {
@@ -710,22 +700,23 @@ export class ContactChat extends ContactStoreElement {
710
700
  };
711
701
  break;
712
702
  case Events.CALL_CREATED:
703
+ case Events.CALL_MISSED:
713
704
  case Events.CALL_RECEIVED:
714
705
  message = {
715
706
  type: MessageType.Inline,
716
707
  text: renderCallEvent(event as CallEvent)
717
708
  };
718
709
  break;
719
- case Events.CALL_STARTED: // deprecated
710
+ case Events.CHANNEL_EVENT:
720
711
  message = {
721
712
  type: MessageType.Inline,
722
- text: `Started Call`
713
+ text: renderChannelEvent(event as ChannelEvent)
723
714
  };
724
715
  break;
725
- case Events.CHANNEL_EVENT:
716
+ case Events.CHAT_STARTED:
726
717
  message = {
727
718
  type: MessageType.Inline,
728
- text: renderChannelEvent(event as ChannelEvent)
719
+ text: renderChatStartedEvent(event as ChatStartedEvent)
729
720
  };
730
721
  break;
731
722
  case Events.CONTACT_LANGUAGE_CHANGED:
@@ -1,12 +1,12 @@
1
1
  import { css, html, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
- import { FormElement } from '../form/FormElement';
4
3
  import { CustomEventType } from '../interfaces';
5
4
  import { RapidElement } from '../RapidElement';
6
5
  import { InputType, TextInput } from '../form/TextInput';
7
6
  import { Icon } from '../Icons';
8
7
  import { getClasses, WebResponse } from '../utils';
9
8
  import { Select } from '../form/select/Select';
9
+ import { FieldElement } from '../form/FieldElement';
10
10
 
11
11
  enum Status {
12
12
  Success = 'success',
@@ -380,7 +380,7 @@ export class ContactFieldEditor extends RapidElement {
380
380
  public handleSubmit() {
381
381
  const input = this.shadowRoot.querySelector(
382
382
  'temba-textinput, temba-datepicker'
383
- ) as FormElement;
383
+ ) as FieldElement;
384
384
 
385
385
  if (input.value !== this.value) {
386
386
  this.dirty = true;
package/temba-modules.ts CHANGED
@@ -6,7 +6,7 @@ import { Completion } from './src/form/Completion';
6
6
  import { Modax } from './src/layout/Modax';
7
7
  import { Dialog } from './src/layout/Dialog';
8
8
  import { Button } from './src/display/Button';
9
- import { FormField } from './src/form/FormField';
9
+ import { FieldElement } from './src/form/FieldElement';
10
10
  import { Loading } from './src/display/Loading';
11
11
  import { CharCount } from './src/display/CharCount';
12
12
  import { Options } from './src/display/Options';
@@ -103,7 +103,8 @@ addCustomElement('temba-field-manager', FieldManager);
103
103
  addCustomElement('temba-urn', ContactUrn);
104
104
  addCustomElement('temba-content-menu', ContentMenu);
105
105
 
106
- addCustomElement('temba-field', FormField);
106
+ // Note: FieldElement is a base class and not directly instantiated as a custom element
107
+ export { FieldElement };
107
108
  addCustomElement('temba-dialog', Dialog);
108
109
  addCustomElement('temba-modax', Modax);
109
110
  addCustomElement('temba-charcount', CharCount);
@@ -165,4 +165,30 @@ describe('temba-checkbox', () => {
165
165
  data = new FormData(form);
166
166
  expect(data.get('my-cb')).to.equal('5');
167
167
  });
168
+
169
+ it('aligns help text with label when both are present', async () => {
170
+ const el: Checkbox = await fixture(html`
171
+ <temba-checkbox
172
+ label="Checkbox with help"
173
+ help_text="This help text should align with the label text"
174
+ >
175
+ </temba-checkbox>
176
+ `);
177
+
178
+ expect(el.label).to.equal('Checkbox with help');
179
+ expect(el.helpText).to.equal(
180
+ 'This help text should align with the label text'
181
+ );
182
+
183
+ // Verify help text element exists and has proper alignment styles
184
+ const helpTextEl = el.shadowRoot.querySelector(
185
+ '.checkbox-help-text'
186
+ ) as HTMLElement;
187
+ expect(helpTextEl).to.not.be.null;
188
+ expect(helpTextEl.textContent.trim()).to.equal(
189
+ 'This help text should align with the label text'
190
+ );
191
+
192
+ await assertScreenshot('checkbox/checkbox-with-help-text', getClip(el));
193
+ });
168
194
  });
@@ -16,10 +16,8 @@ describe('FormElement markdown integration', () => {
16
16
 
17
17
  await checkbox.updateComplete;
18
18
 
19
- // Check that errors are rendered with markdown
20
- const errorElements = checkbox.shadowRoot
21
- .querySelectorAll('temba-field')[0]
22
- .shadowRoot.querySelectorAll('.alert-error');
19
+ // Check that errors are rendered with markdown directly in checkbox shadow root
20
+ const errorElements = checkbox.shadowRoot.querySelectorAll('.alert-error');
23
21
  expect(errorElements.length).to.equal(2);
24
22
 
25
23
  // First error should have bold text and link