@nyaruka/temba-components 0.156.18 → 0.157.1

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 (56) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/temba-components.js +2119 -1617
  3. package/dist/temba-components.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/display/Button.ts +102 -121
  6. package/src/display/Chat.ts +74 -9
  7. package/src/display/Dropdown.ts +11 -0
  8. package/src/display/Label.ts +154 -2
  9. package/src/display/LeafletMap.ts +4 -3
  10. package/src/display/Options.ts +71 -16
  11. package/src/display/TembaUser.ts +32 -8
  12. package/src/events/eventRenderers.ts +243 -95
  13. package/src/excellent/caret-utils.ts +0 -1
  14. package/src/flow/AutoTranslate.ts +2 -2
  15. package/src/flow/Editor.ts +4 -4
  16. package/src/flow/NodeEditor.ts +2 -2
  17. package/src/flow/NodeTypeSelector.ts +0 -5
  18. package/src/flow/RevisionsWindow.ts +1 -3
  19. package/src/flow/actions/set_contact_language.ts +5 -4
  20. package/src/flow/nodes/shared.ts +14 -0
  21. package/src/flow/nodes/split_by_llm_categorize.ts +28 -8
  22. package/src/flow/utils.ts +39 -60
  23. package/src/form/ArrayEditor.ts +9 -11
  24. package/src/form/Checkbox.ts +2 -2
  25. package/src/form/ColorPicker.ts +5 -3
  26. package/src/form/Compose.ts +1 -1
  27. package/src/form/FieldElement.ts +8 -8
  28. package/src/form/KeyValueEditor.ts +4 -4
  29. package/src/form/MessageEditor.ts +2 -3
  30. package/src/form/RangePicker.ts +17 -17
  31. package/src/form/TembaSlider.ts +10 -10
  32. package/src/form/TemplateEditor.ts +4 -4
  33. package/src/form/TextInput.ts +19 -1
  34. package/src/form/select/Omnibox.ts +21 -20
  35. package/src/form/select/Select.ts +382 -173
  36. package/src/form/select/WorkspaceSelect.ts +7 -1
  37. package/src/interfaces.ts +1 -0
  38. package/src/languages.ts +56 -0
  39. package/src/layout/Accordion.ts +2 -2
  40. package/src/layout/Dialog.ts +1 -3
  41. package/src/layout/Modax.ts +1 -1
  42. package/src/list/ContentMenu.ts +1 -2
  43. package/src/list/SortableList.ts +156 -0
  44. package/src/list/TembaMenu.ts +159 -113
  45. package/src/live/ContactBadges.ts +2 -1
  46. package/src/live/ContactChat.ts +62 -45
  47. package/src/live/ContactDetails.ts +3 -1
  48. package/src/live/ContactFieldEditor.ts +36 -31
  49. package/src/live/FieldManager.ts +4 -4
  50. package/src/store/AppState.ts +3 -21
  51. package/src/store/Store.ts +0 -29
  52. package/src/styles/designTokens.ts +158 -0
  53. package/src/styles/pillVariants.ts +147 -0
  54. package/static/css/temba-components.css +141 -36
  55. package/web-dev-server.config.mjs +0 -1
  56. package/web-test-runner.config.mjs +98 -1
@@ -14,8 +14,14 @@ export class WorkspaceSelect extends Select<WorkspaceOption> {
14
14
  return css`
15
15
  ${super.styles}
16
16
 
17
+ /* Workspace chooser is embedded in the account menu, not a
18
+ standalone form widget — suppress the focus border + halo
19
+ on both the select itself and the dropdown popup. */
17
20
  :host {
18
- border: 0px solid blue;
21
+ --temba-select-focus-border: transparent;
22
+ --temba-select-focus-halo: none;
23
+ --temba-options-focus-border: transparent;
24
+ --temba-options-focus-halo: none;
19
25
  }
20
26
  `;
21
27
  }
package/src/interfaces.ts CHANGED
@@ -59,6 +59,7 @@ export interface NamedUser extends User {
59
59
 
60
60
  export interface User {
61
61
  id?: number;
62
+ uuid?: string;
62
63
  first_name?: string;
63
64
  last_name?: string;
64
65
  name?: string;
@@ -0,0 +1,56 @@
1
+ const intlLanguageNames = new Intl.DisplayNames(['en'], {
2
+ type: 'language',
3
+ fallback: 'none'
4
+ });
5
+
6
+ const ADDITIONAL_LANGUAGE_NAMES: { [code: string]: string } = {
7
+ aab: 'Alumu-Tesu',
8
+ aac: 'Ari',
9
+ aas: 'Aasáx',
10
+ abp: 'Abellen Ayta',
11
+ acf: 'Saint Lucian Creole French',
12
+ aec: 'Saidi Arabic',
13
+ afb: 'Gulf Arabic',
14
+ apd: 'Sudanese Arabic',
15
+ ayl: 'Libyan Arabic',
16
+ blk: "Pa'o Karen",
17
+ bog: 'Bamako Sign Language',
18
+ bzs: 'Brazilian Sign Language',
19
+ csn: 'Colombian Sign Language',
20
+ dag: 'Dagbani',
21
+ ecs: 'Ecuadorian Sign Language',
22
+ frk: 'Frankish',
23
+ fsl: 'French Sign Language',
24
+ fuv: 'Nigerian Fulfulde',
25
+ gcr: 'Guianese Creole French',
26
+ gpe: 'Ghanaian Pidgin English',
27
+ gux: 'Gourmanchéma',
28
+ ise: 'Italian Sign Language',
29
+ ksw: "S'gaw Karen",
30
+ kun: 'Kunama',
31
+ kyu: 'Western Kayah',
32
+ laj: 'Lango',
33
+ nyj: 'Nyanga',
34
+ prd: 'Parsi-Dari',
35
+ prl: 'Peruvian Sign Language',
36
+ pst: 'Central Pashto',
37
+ rop: 'Kriol',
38
+ tdt: 'Tetun Dili',
39
+ toi: 'Tonga',
40
+ tuv: 'Turkana',
41
+ vsl: 'Venezuelan Sign Language'
42
+ };
43
+
44
+ export function getLanguageName(code: string): string {
45
+ if (!code) return '';
46
+ if (code === 'und') return 'Unknown';
47
+
48
+ try {
49
+ const name = intlLanguageNames.of(code);
50
+ if (name) return name;
51
+ } catch {
52
+ // fall through to additional lookup
53
+ }
54
+
55
+ return ADDITIONAL_LANGUAGE_NAMES[code] || code;
56
+ }
@@ -7,8 +7,8 @@ export class Accordion extends LitElement {
7
7
  return css`
8
8
  :host {
9
9
  display: block;
10
- border: 1px solid #e0e0e0;
11
- border-radius: 6px;
10
+ border: 1px solid var(--color-widget-border);
11
+ border-radius: var(--curvature-widget);
12
12
  overflow: hidden;
13
13
  }
14
14
  `;
@@ -181,8 +181,6 @@ export class Dialog extends ResizeElement {
181
181
 
182
182
  temba-button {
183
183
  margin-left: 10px;
184
- --button-y: 0.4em;
185
- --button-x: 1em;
186
184
  }
187
185
 
188
186
  .dialog-body temba-loading {
@@ -555,7 +553,7 @@ export class Dialog extends ResizeElement {
555
553
  ?destructive=${button.type == 'primary' && this.destructive}
556
554
  ?primary=${button.type == 'primary' && !this.destructive}
557
555
  ?secondary=${button.type == 'secondary'}
558
- ?submitting=${this.submitting}
556
+ ?submitting=${this.submitting && button.type == 'primary'}
559
557
  ?disabled=${this.disabled && !button.closes}
560
558
  index=${index}
561
559
  @click=${this.handleClick}
@@ -70,7 +70,7 @@ export class Modax extends RapidElement {
70
70
  color: tomato;
71
71
  padding: 10px;
72
72
  margin-bottom: 10px;
73
- border-radius: 6px;
73
+ border-radius: var(--curvature);
74
74
  }
75
75
 
76
76
  .step-ball {
@@ -39,8 +39,6 @@ export class ContentMenu extends RapidElement {
39
39
  z-index: 5000;
40
40
  }
41
41
  .container {
42
- --button-y: 0.4em;
43
- --button-x: 1em;
44
42
  display: flex;
45
43
  align-items: center;
46
44
  }
@@ -52,6 +50,7 @@ export class ContentMenu extends RapidElement {
52
50
 
53
51
  temba-button {
54
52
  margin-right: 0.5rem;
53
+ align-self: center;
55
54
  }
56
55
  .toggle {
57
56
  --icon-color: rgb(102, 102, 102);
@@ -252,9 +252,165 @@ export class SortableList extends RapidElement {
252
252
  // Copy form values for the root element and all descendants
253
253
  copyFormValues(element, clone);
254
254
 
255
+ // Inline computed styles onto the clone so it renders faithfully
256
+ // once detached from its original shadow-DOM scope. The ghost is
257
+ // appended to document.body (see startDrag), which means the
258
+ // original's shadow-root-scoped CSS rules no longer apply — chips
259
+ // would lose their flex layout, the X button would lose its shape,
260
+ // and the pill --icon-color would stop inheriting. Walk the cloned
261
+ // light DOM in parallel with the original and inline the layout
262
+ // properties + a curated set of DS custom properties.
263
+ this.inlineComputedStyles(element, clone);
264
+
255
265
  return clone;
256
266
  }
257
267
 
268
+ /** CSS properties copied from the original to the ghost. Covers
269
+ * layout (flex/sizing/spacing), visual (colors, borders, radii,
270
+ * shadows), and text (font, white-space, alignment) — enough to
271
+ * make a faithful free-floating clone without resolving the entire
272
+ * ~400 properties getComputedStyle returns.
273
+ *
274
+ * Important: `getComputedStyle` returns LONGHAND values only —
275
+ * asking for shorthand like `padding` / `margin` / `border` returns
276
+ * an empty string in most browsers. We list longhands explicitly so
277
+ * inlined chip padding / borders survive the move to document.body.
278
+ *
279
+ * Width/height ARE included: nested elements that depend on
280
+ * shadow-root class rules for sized boxes (e.g. select's
281
+ * `.remove-item { width: 16px; height: 16px }`) collapse to their
282
+ * content size without it, leaving a stray gap inside the chip. */
283
+ private static GHOST_COPY_PROPS = [
284
+ 'display',
285
+ 'flex-grow',
286
+ 'flex-shrink',
287
+ 'flex-basis',
288
+ 'flex-direction',
289
+ 'flex-wrap',
290
+ 'align-items',
291
+ 'align-self',
292
+ 'justify-content',
293
+ 'gap',
294
+ 'column-gap',
295
+ 'row-gap',
296
+ 'padding-top',
297
+ 'padding-right',
298
+ 'padding-bottom',
299
+ 'padding-left',
300
+ 'margin-top',
301
+ 'margin-right',
302
+ 'margin-bottom',
303
+ 'margin-left',
304
+ 'border-top-width',
305
+ 'border-right-width',
306
+ 'border-bottom-width',
307
+ 'border-left-width',
308
+ 'border-top-style',
309
+ 'border-right-style',
310
+ 'border-bottom-style',
311
+ 'border-left-style',
312
+ 'border-top-color',
313
+ 'border-right-color',
314
+ 'border-bottom-color',
315
+ 'border-left-color',
316
+ 'border-top-left-radius',
317
+ 'border-top-right-radius',
318
+ 'border-bottom-right-radius',
319
+ 'border-bottom-left-radius',
320
+ 'background-color',
321
+ 'background-image',
322
+ 'color',
323
+ 'font-family',
324
+ 'font-size',
325
+ 'font-weight',
326
+ 'font-style',
327
+ 'line-height',
328
+ 'letter-spacing',
329
+ 'text-align',
330
+ 'white-space',
331
+ 'overflow',
332
+ 'text-overflow',
333
+ 'box-shadow',
334
+ 'opacity',
335
+ 'width',
336
+ 'min-width',
337
+ 'max-width',
338
+ 'height',
339
+ 'min-height',
340
+ 'max-height',
341
+ 'box-sizing',
342
+ 'cursor',
343
+ 'user-select',
344
+ 'vertical-align'
345
+ ];
346
+
347
+ /** Design-system custom properties carried over to the ghost so
348
+ * nested custom elements (temba-icon's --icon-color, pill variants,
349
+ * etc.) read the correct values once detached from the original
350
+ * shadow-root scope. */
351
+ private static GHOST_COPY_CUSTOM_PROPS = [
352
+ '--icon-color',
353
+ '--color-widget-text',
354
+ '--color-text-help',
355
+ '--accent',
356
+ '--accent-100',
357
+ '--accent-200',
358
+ '--accent-700',
359
+ '--flow',
360
+ '--field',
361
+ '--channel',
362
+ '--text-1',
363
+ '--text-2',
364
+ '--sunken',
365
+ '--border',
366
+ '--border-strong',
367
+ '--font-family',
368
+ '--w-regular',
369
+ '--w-medium',
370
+ '--curvature',
371
+ '--curvature-widget'
372
+ ];
373
+
374
+ private inlineComputedStyles(original: Element, clone: Element): void {
375
+ if (!(original instanceof HTMLElement) || !(clone instanceof HTMLElement)) {
376
+ return;
377
+ }
378
+
379
+ const apply = (orig: HTMLElement, cln: HTMLElement) => {
380
+ const cs = window.getComputedStyle(orig);
381
+ let inline = '';
382
+ for (const p of SortableList.GHOST_COPY_PROPS) {
383
+ const v = cs.getPropertyValue(p);
384
+ if (v) inline += `${p}:${v};`;
385
+ }
386
+ for (const p of SortableList.GHOST_COPY_CUSTOM_PROPS) {
387
+ const v = cs.getPropertyValue(p);
388
+ if (v) inline += `${p}:${v};`;
389
+ }
390
+ // existing inline style wins by sitting AFTER the inlined
391
+ // computed values — preserves anything we just authored elsewhere
392
+ // (e.g. .option-name's explicit display:flex).
393
+ cln.setAttribute('style', inline + (cln.getAttribute('style') || ''));
394
+ };
395
+
396
+ const walk = (orig: Element, cln: Element) => {
397
+ if (orig instanceof HTMLElement && cln instanceof HTMLElement) {
398
+ apply(orig, cln);
399
+ }
400
+ // Descend into both light-DOM and custom-element subtrees: the
401
+ // latter's slotted/light children still need styles inlined, and
402
+ // its own shadow DOM rebuilds itself when the clone upgrades.
403
+ const oc = Array.from(orig.children);
404
+ const cc = Array.from(cln.children);
405
+ const n = Math.min(oc.length, cc.length);
406
+ for (let i = 0; i < n; i++) {
407
+ walk(oc[i], cc[i]);
408
+ }
409
+ };
410
+
411
+ walk(original, clone);
412
+ }
413
+
258
414
  public getIds() {
259
415
  return this.getSortableElements().map((ele) => ele.id);
260
416
  }