@nyaruka/temba-components 0.127.0 → 0.129.0

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 (109) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/demo/chart/horizontal-demo.html +81 -0
  3. package/demo/components/datepicker/example.html +63 -0
  4. package/demo/components/datepicker/range-picker-demo.html +161 -0
  5. package/demo/data/flows/sample-flow.json +127 -100
  6. package/demo/index.html +8 -0
  7. package/demo/static/css/prism.css +2 -0
  8. package/demo/static/js/prism-loader.js +12 -0
  9. package/demo/sticky-note-demo.html +152 -0
  10. package/demo/styles.css +71 -1
  11. package/dist/locales/es.js +5 -5
  12. package/dist/locales/es.js.map +1 -1
  13. package/dist/locales/fr.js +5 -5
  14. package/dist/locales/fr.js.map +1 -1
  15. package/dist/locales/locale-codes.js +11 -2
  16. package/dist/locales/locale-codes.js.map +1 -1
  17. package/dist/locales/pt.js +5 -5
  18. package/dist/locales/pt.js.map +1 -1
  19. package/dist/temba-components.js +509 -87
  20. package/dist/temba-components.js.map +1 -1
  21. package/out-tsc/src/chart/TembaChart.js +136 -62
  22. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  23. package/out-tsc/src/datepicker/DatePicker.js +11 -1
  24. package/out-tsc/src/datepicker/DatePicker.js.map +1 -1
  25. package/out-tsc/src/datepicker/RangePicker.js +595 -0
  26. package/out-tsc/src/datepicker/RangePicker.js.map +1 -0
  27. package/out-tsc/src/flow/Editor.js +210 -1
  28. package/out-tsc/src/flow/Editor.js.map +1 -1
  29. package/out-tsc/src/flow/EditorNode.js +98 -139
  30. package/out-tsc/src/flow/EditorNode.js.map +1 -1
  31. package/out-tsc/src/flow/StickyNote.js +272 -0
  32. package/out-tsc/src/flow/StickyNote.js.map +1 -0
  33. package/out-tsc/src/interfaces.js +1 -0
  34. package/out-tsc/src/interfaces.js.map +1 -1
  35. package/out-tsc/src/list/RunList.js +2 -1
  36. package/out-tsc/src/list/RunList.js.map +1 -1
  37. package/out-tsc/src/list/SortableList.js +9 -0
  38. package/out-tsc/src/list/SortableList.js.map +1 -1
  39. package/out-tsc/src/locales/es.js +5 -5
  40. package/out-tsc/src/locales/es.js.map +1 -1
  41. package/out-tsc/src/locales/fr.js +5 -5
  42. package/out-tsc/src/locales/fr.js.map +1 -1
  43. package/out-tsc/src/locales/locale-codes.js +11 -2
  44. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  45. package/out-tsc/src/locales/pt.js +5 -5
  46. package/out-tsc/src/locales/pt.js.map +1 -1
  47. package/out-tsc/src/store/AppState.js +33 -0
  48. package/out-tsc/src/store/AppState.js.map +1 -1
  49. package/out-tsc/src/vectoricon/index.js +2 -1
  50. package/out-tsc/src/vectoricon/index.js.map +1 -1
  51. package/out-tsc/temba-modules.js +5 -1
  52. package/out-tsc/temba-modules.js.map +1 -1
  53. package/out-tsc/test/temba-chart.test.js +36 -0
  54. package/out-tsc/test/temba-chart.test.js.map +1 -1
  55. package/out-tsc/test/temba-datepicker.test.js +1 -1
  56. package/out-tsc/test/temba-datepicker.test.js.map +1 -1
  57. package/out-tsc/test/temba-flow-editor-node.test.js +249 -5
  58. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  59. package/out-tsc/test/temba-range-picker.test.js +123 -0
  60. package/out-tsc/test/temba-range-picker.test.js.map +1 -0
  61. package/out-tsc/test/temba-select.test.js +10 -16
  62. package/out-tsc/test/temba-select.test.js.map +1 -1
  63. package/out-tsc/test/temba-webchat.test.js +4 -0
  64. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  65. package/out-tsc/test/utils.test.js +62 -0
  66. package/out-tsc/test/utils.test.js.map +1 -1
  67. package/package.json +1 -1
  68. package/screenshots/truth/datepicker/range-picker-all.png +0 -0
  69. package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
  70. package/screenshots/truth/datepicker/range-picker-default.png +0 -0
  71. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  72. package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
  73. package/screenshots/truth/datepicker/range-picker-min-max.png +0 -0
  74. package/screenshots/truth/datepicker/range-picker-week.png +0 -0
  75. package/screenshots/truth/datepicker/range-picker-year.png +0 -0
  76. package/screenshots/truth/sticky-note/blue.png +0 -0
  77. package/screenshots/truth/sticky-note/gray.png +0 -0
  78. package/screenshots/truth/sticky-note/green.png +0 -0
  79. package/screenshots/truth/sticky-note/pink.png +0 -0
  80. package/screenshots/truth/sticky-note/yellow.png +0 -0
  81. package/screenshots/truth/webchat/connected-state.png +0 -0
  82. package/src/chart/TembaChart.ts +144 -66
  83. package/src/datepicker/DatePicker.ts +9 -1
  84. package/src/datepicker/RangePicker.ts +602 -0
  85. package/src/flow/Editor.ts +252 -2
  86. package/src/flow/EditorNode.ts +98 -156
  87. package/src/flow/StickyNote.ts +284 -0
  88. package/src/interfaces.ts +2 -1
  89. package/src/list/RunList.ts +2 -1
  90. package/src/list/SortableList.ts +11 -0
  91. package/src/locales/es.ts +18 -13
  92. package/src/locales/fr.ts +18 -13
  93. package/src/locales/locale-codes.ts +11 -2
  94. package/src/locales/pt.ts +18 -13
  95. package/src/store/AppState.ts +51 -1
  96. package/src/store/flow-definition.d.ts +8 -0
  97. package/src/vectoricon/index.ts +2 -1
  98. package/static/svg/index.pdf +137 -0
  99. package/temba-modules.ts +5 -1
  100. package/test/temba-chart.test.ts +47 -0
  101. package/test/temba-datepicker.test.ts +1 -1
  102. package/test/temba-flow-editor-node.test.ts +322 -6
  103. package/test/temba-range-picker.test.ts +193 -0
  104. package/test/temba-select.test.ts +11 -19
  105. package/test/temba-webchat.test.ts +7 -0
  106. package/test/utils.test.ts +98 -0
  107. package/web-dev-server.config.mjs +30 -22
  108. package/web-test-runner.config.mjs +2 -0
  109. package/demo/datepicker/example.html +0 -69
@@ -0,0 +1,602 @@
1
+ import { property } from 'lit/decorators.js';
2
+ import { RapidElement } from '../RapidElement';
3
+ import { DateTime } from 'luxon';
4
+ import { html, css } from 'lit';
5
+ import { DatePicker } from './DatePicker';
6
+ import { CustomEventType } from '../interfaces';
7
+
8
+ export class RangePicker extends RapidElement {
9
+ static styles = css`
10
+ .range-container {
11
+ display: flex;
12
+ gap: 0.5em;
13
+ align-items: center;
14
+ }
15
+ .date-display {
16
+ cursor: pointer;
17
+ padding: 0.2em 0.5em;
18
+ margin: 0.6em 0;
19
+ border-radius: 4px;
20
+ border: 1px solid transparent;
21
+ transition: border 0.2s;
22
+ }
23
+
24
+ .date-display:hover {
25
+ border: 1px solid var(--color-widget-border, #bbb);
26
+ background: var(--color-widget-hover, #f5f5f5);
27
+ }
28
+
29
+ input[type='date'] {
30
+ font-size: 1em;
31
+ padding: 0.2em 0.5em;
32
+ border-radius: 4px;
33
+ border: 1px solid #bbb;
34
+ }
35
+
36
+ .navigation-container {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 0.25em;
40
+ }
41
+
42
+ .nav-arrow {
43
+ background: #f5f5f5;
44
+ border: 1px solid #bbb;
45
+
46
+ border-radius: var(--curvature);
47
+ padding: 0em 0em;
48
+ cursor: pointer;
49
+ font-size: 0.6em;
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ width: 23px;
54
+ height: 23px;
55
+ transition: background 0.2s, border 0.2s, opacity 0.2s;
56
+ }
57
+
58
+ .nav-arrow:hover:not(:disabled) {
59
+ background: #e0eaff;
60
+ border-color: #3399ff;
61
+ }
62
+
63
+ .nav-arrow:disabled {
64
+ opacity: 0.5;
65
+ cursor: not-allowed;
66
+ background: #f9f9f9;
67
+ }
68
+
69
+ .nav-arrow.hidden {
70
+ visibility: hidden;
71
+ }
72
+
73
+ .button-group {
74
+ display: flex;
75
+ margin-left: 0em;
76
+ }
77
+ .range-btn {
78
+ background: #f5f5f5;
79
+ border: 1px solid #bbb;
80
+ border-radius: 0px;
81
+ margin-left: -1px;
82
+ padding: 0.2em 0.8em;
83
+ cursor: pointer;
84
+ font-size: 0.95em;
85
+ transition: background 0.2s, border 0.2s;
86
+ }
87
+
88
+ .button-group .range-btn:first-child {
89
+ border-radius: 4px 0 0 4px;
90
+ }
91
+
92
+ .button-group .range-btn:last-child {
93
+ border-radius: 0 4px 4px 0;
94
+ }
95
+
96
+ .range-btn.selected,
97
+ .range-btn:active {
98
+ background: #e0eaff;
99
+ border-color: #3399ff;
100
+ }
101
+ `;
102
+
103
+ @property({ type: String, attribute: 'start' })
104
+ startDate = '';
105
+
106
+ @property({ type: String, attribute: 'end' })
107
+ endDate = '';
108
+
109
+ @property({ type: Boolean })
110
+ editingStart = false;
111
+
112
+ @property({ type: Boolean })
113
+ editingEnd = false;
114
+
115
+ @property({ type: String })
116
+ selectedRange: 'W' | 'M' | 'Y' | 'ALL' | '' = '';
117
+
118
+ @property({ type: String, attribute: 'min' })
119
+ minDate = '2012-01-01';
120
+
121
+ @property({ type: String, attribute: 'max' })
122
+ maxDate = DateTime.now().toISODate();
123
+
124
+ private handleStartClick() {
125
+ this.editingStart = true;
126
+ }
127
+
128
+ private handleEndClick() {
129
+ this.editingEnd = true;
130
+ }
131
+
132
+ private setRange(type: 'W' | 'M' | 'Y' | 'ALL') {
133
+ const today = DateTime.now().toISODate();
134
+ let start = '';
135
+ if (type === 'W') {
136
+ start = DateTime.now().minus({ days: 6 }).toISODate();
137
+ } else if (type === 'M') {
138
+ start = DateTime.now().minus({ months: 1 }).plus({ days: 1 }).toISODate();
139
+ } else if (type === 'Y') {
140
+ start = DateTime.now().minus({ years: 1 }).plus({ days: 1 }).toISODate();
141
+ } else if (type === 'ALL') {
142
+ start = this.minDate || '2012-01-01';
143
+ }
144
+ this.startDate = start;
145
+ this.endDate = today;
146
+ this.selectedRange = type;
147
+ this.editingStart = false;
148
+ this.editingEnd = false;
149
+
150
+ this.fireCustomEvent(CustomEventType.DateRangeChanged, {
151
+ start: this.startDate,
152
+ end: this.endDate,
153
+ range: this.selectedRange
154
+ });
155
+ }
156
+
157
+ private setValidRange(type: 'start' | 'end', value: string) {
158
+ // Enforce min/max
159
+ let newValue = value;
160
+ if (newValue < this.minDate) newValue = this.minDate;
161
+ if (newValue > this.maxDate) newValue = this.maxDate;
162
+ const start = DateTime.fromISO(
163
+ type === 'start' ? newValue : this.startDate
164
+ );
165
+ const end = DateTime.fromISO(type === 'end' ? newValue : this.endDate);
166
+
167
+ if (!start.isValid || !end.isValid) return;
168
+ if (start > end) {
169
+ if (type === 'start') {
170
+ this.startDate = newValue;
171
+ this.endDate = start.toISODate();
172
+ } else {
173
+ this.endDate = newValue;
174
+ this.startDate = end.toISODate();
175
+ }
176
+ } else {
177
+ if (type === 'start') this.startDate = newValue;
178
+ else this.endDate = newValue;
179
+ }
180
+
181
+ this.fireCustomEvent(CustomEventType.DateRangeChanged, {
182
+ start: this.startDate,
183
+ end: this.endDate,
184
+ range: this.selectedRange
185
+ });
186
+ }
187
+
188
+ private canNavigatePrevious(): boolean {
189
+ if (this.selectedRange === 'ALL') return false;
190
+
191
+ const currentStart = DateTime.fromISO(this.startDate);
192
+ let previousStart: DateTime;
193
+
194
+ if (this.selectedRange === 'W') {
195
+ previousStart = currentStart.minus({ weeks: 1 });
196
+ } else if (this.selectedRange === 'M') {
197
+ previousStart = currentStart.minus({ months: 1 });
198
+ } else if (this.selectedRange === 'Y') {
199
+ previousStart = currentStart.minus({ years: 1 });
200
+ } else if (this.selectedRange === '') {
201
+ // Custom range - determine the interval and navigate by that amount
202
+ const interval = this.getCustomRangeInterval();
203
+ if (interval.type === 'days') {
204
+ previousStart = currentStart.minus({ days: interval.amount });
205
+ } else if (interval.type === 'months') {
206
+ previousStart = currentStart.minus({ months: interval.amount });
207
+ } else if (interval.type === 'years') {
208
+ previousStart = currentStart.minus({ years: interval.amount });
209
+ } else {
210
+ return false;
211
+ }
212
+ } else {
213
+ return false;
214
+ }
215
+
216
+ return previousStart.toISODate() >= this.minDate;
217
+ }
218
+
219
+ private canNavigateNext(): boolean {
220
+ if (this.selectedRange === 'ALL') return false;
221
+
222
+ const currentEnd = DateTime.fromISO(this.endDate);
223
+ let nextEnd: DateTime;
224
+
225
+ if (this.selectedRange === 'W') {
226
+ nextEnd = currentEnd.plus({ weeks: 1 });
227
+ } else if (this.selectedRange === 'M') {
228
+ nextEnd = currentEnd.plus({ months: 1 });
229
+ } else if (this.selectedRange === 'Y') {
230
+ nextEnd = currentEnd.plus({ years: 1 });
231
+ } else if (this.selectedRange === '') {
232
+ // Custom range - determine the interval and navigate by that amount
233
+ const interval = this.getCustomRangeInterval();
234
+ if (interval.type === 'days') {
235
+ nextEnd = currentEnd.plus({ days: interval.amount });
236
+ } else if (interval.type === 'months') {
237
+ nextEnd = currentEnd.plus({ months: interval.amount });
238
+ } else if (interval.type === 'years') {
239
+ nextEnd = currentEnd.plus({ years: interval.amount });
240
+ } else {
241
+ return false;
242
+ }
243
+ } else {
244
+ return false;
245
+ }
246
+
247
+ return nextEnd.toISODate() <= this.maxDate;
248
+ }
249
+
250
+ private getCustomRangeInterval(): {
251
+ type: 'days' | 'months' | 'years';
252
+ amount: number;
253
+ } {
254
+ const start = DateTime.fromISO(this.startDate);
255
+ const end = DateTime.fromISO(this.endDate);
256
+
257
+ if (!start.isValid || !end.isValid) {
258
+ return { type: 'days', amount: 1 };
259
+ }
260
+
261
+ // Check if it's a complete month (first day to last day of any month)
262
+ const isLastDayOfMonth = end.day === end.daysInMonth;
263
+ if (start.day === 1 && isLastDayOfMonth) {
264
+ // Single complete month
265
+ if (start.month === end.month && start.year === end.year) {
266
+ return { type: 'months', amount: 1 };
267
+ }
268
+
269
+ // Multiple complete months - check if we span complete months only
270
+ const startOfFirstMonth = start.startOf('month');
271
+ const endOfLastMonth = end.endOf('month');
272
+ const monthsDiff =
273
+ endOfLastMonth.diff(startOfFirstMonth, 'months').months + 1;
274
+
275
+ if (monthsDiff > 0 && Number.isInteger(monthsDiff)) {
276
+ return { type: 'months', amount: Math.round(monthsDiff) };
277
+ }
278
+ }
279
+
280
+ // Check if it's a full year
281
+ if (
282
+ start.month === 1 &&
283
+ start.day === 1 &&
284
+ end.month === 12 &&
285
+ end.day === 31
286
+ ) {
287
+ // Single complete year
288
+ if (start.year === end.year) {
289
+ return { type: 'years', amount: 1 };
290
+ }
291
+
292
+ // Multiple complete years
293
+ const yearsDiff = end.year - start.year + 1;
294
+ if (yearsDiff > 0) {
295
+ return { type: 'years', amount: yearsDiff };
296
+ }
297
+ }
298
+
299
+ // Default to days for any other custom range
300
+ const daysDiff = end.diff(start, 'days').days + 1; // +1 to include both start and end days
301
+ return { type: 'days', amount: Math.max(1, Math.round(daysDiff)) };
302
+ }
303
+
304
+ private navigatePrevious() {
305
+ if (!this.canNavigatePrevious()) return;
306
+
307
+ const currentStart = DateTime.fromISO(this.startDate);
308
+ const currentEnd = DateTime.fromISO(this.endDate);
309
+ let newStart: DateTime;
310
+ let newEnd: DateTime;
311
+
312
+ if (this.selectedRange === 'W') {
313
+ newStart = currentStart.minus({ weeks: 1 });
314
+ newEnd = currentEnd.minus({ weeks: 1 });
315
+ } else if (this.selectedRange === 'M') {
316
+ // Check if current M range is a complete month, if so maintain month boundaries
317
+ const interval = this.getCustomRangeInterval();
318
+ if (interval.type === 'months') {
319
+ newStart = currentStart.minus({ months: 1 }).startOf('month');
320
+ newEnd = newStart
321
+ .plus({ months: interval.amount })
322
+ .minus({ days: 1 })
323
+ .endOf('day');
324
+ } else {
325
+ newStart = currentStart.minus({ months: 1 });
326
+ newEnd = currentEnd.minus({ months: 1 });
327
+ }
328
+ } else if (this.selectedRange === 'Y') {
329
+ newStart = currentStart.minus({ years: 1 });
330
+ newEnd = currentEnd.minus({ years: 1 });
331
+ } else if (this.selectedRange === '') {
332
+ // Custom range - determine the interval and navigate by that amount
333
+ const interval = this.getCustomRangeInterval();
334
+ if (interval.type === 'days') {
335
+ newStart = currentStart.minus({ days: interval.amount });
336
+ newEnd = currentEnd.minus({ days: interval.amount });
337
+ } else if (interval.type === 'months') {
338
+ // For month navigation, maintain complete month boundaries
339
+ newStart = currentStart
340
+ .minus({ months: interval.amount })
341
+ .startOf('month');
342
+ newEnd = newStart
343
+ .plus({ months: interval.amount })
344
+ .minus({ days: 1 })
345
+ .endOf('day');
346
+ } else if (interval.type === 'years') {
347
+ newStart = currentStart.minus({ years: interval.amount });
348
+ newEnd = currentEnd.minus({ years: interval.amount });
349
+ } else {
350
+ return;
351
+ }
352
+ } else {
353
+ return;
354
+ }
355
+
356
+ // Enforce min/max bounds
357
+ const minDateTime = DateTime.fromISO(this.minDate);
358
+ const maxDateTime = DateTime.fromISO(this.maxDate);
359
+ const startDate =
360
+ newStart < minDateTime ? this.minDate : newStart.toISODate();
361
+ const endDate = newEnd > maxDateTime ? this.maxDate : newEnd.toISODate();
362
+
363
+ this.startDate = startDate;
364
+ this.endDate = endDate;
365
+
366
+ this.fireCustomEvent(CustomEventType.DateRangeChanged, {
367
+ start: this.startDate,
368
+ end: this.endDate,
369
+ range: this.selectedRange
370
+ });
371
+ }
372
+
373
+ private navigateNext() {
374
+ if (!this.canNavigateNext()) return;
375
+
376
+ const currentStart = DateTime.fromISO(this.startDate);
377
+ const currentEnd = DateTime.fromISO(this.endDate);
378
+ let newStart: DateTime;
379
+ let newEnd: DateTime;
380
+
381
+ if (this.selectedRange === 'W') {
382
+ newStart = currentStart.plus({ weeks: 1 });
383
+ newEnd = currentEnd.plus({ weeks: 1 });
384
+ } else if (this.selectedRange === 'M') {
385
+ // Check if current M range is a complete month, if so maintain month boundaries
386
+ const interval = this.getCustomRangeInterval();
387
+ if (interval.type === 'months') {
388
+ newStart = currentStart.plus({ months: 1 }).startOf('month');
389
+ newEnd = newStart
390
+ .plus({ months: interval.amount })
391
+ .minus({ days: 1 })
392
+ .endOf('day');
393
+ } else {
394
+ newStart = currentStart.plus({ months: 1 });
395
+ newEnd = currentEnd.plus({ months: 1 });
396
+ }
397
+ } else if (this.selectedRange === 'Y') {
398
+ newStart = currentStart.plus({ years: 1 });
399
+ newEnd = currentEnd.plus({ years: 1 });
400
+ } else if (this.selectedRange === '') {
401
+ // Custom range - determine the interval and navigate by that amount
402
+ const interval = this.getCustomRangeInterval();
403
+ if (interval.type === 'days') {
404
+ newStart = currentStart.plus({ days: interval.amount });
405
+ newEnd = currentEnd.plus({ days: interval.amount });
406
+ } else if (interval.type === 'months') {
407
+ // For month navigation, maintain complete month boundaries
408
+ newStart = currentStart
409
+ .plus({ months: interval.amount })
410
+ .startOf('month');
411
+ newEnd = newStart
412
+ .plus({ months: interval.amount })
413
+ .minus({ days: 1 })
414
+ .endOf('day');
415
+ } else if (interval.type === 'years') {
416
+ newStart = currentStart.plus({ years: interval.amount });
417
+ newEnd = currentEnd.plus({ years: interval.amount });
418
+ } else {
419
+ return;
420
+ }
421
+ } else {
422
+ return;
423
+ }
424
+
425
+ // Enforce min/max bounds
426
+ const minDateTime = DateTime.fromISO(this.minDate);
427
+ const maxDateTime = DateTime.fromISO(this.maxDate);
428
+ const startDate =
429
+ newStart < minDateTime ? this.minDate : newStart.toISODate();
430
+ const endDate = newEnd > maxDateTime ? this.maxDate : newEnd.toISODate();
431
+
432
+ this.startDate = startDate;
433
+ this.endDate = endDate;
434
+
435
+ this.fireCustomEvent(CustomEventType.DateRangeChanged, {
436
+ start: this.startDate,
437
+ end: this.endDate,
438
+ range: this.selectedRange
439
+ });
440
+ }
441
+
442
+ private getNavigationLabel(direction: 'previous' | 'next'): string {
443
+ const interval = this.getCustomRangeInterval();
444
+ const amount = interval.amount;
445
+ const unit =
446
+ interval.type === 'days'
447
+ ? amount === 1
448
+ ? 'day'
449
+ : 'days'
450
+ : interval.type === 'months'
451
+ ? amount === 1
452
+ ? 'month'
453
+ : 'months'
454
+ : amount === 1
455
+ ? 'year'
456
+ : 'years';
457
+
458
+ return `${
459
+ direction === 'previous' ? 'Previous' : 'Next'
460
+ } ${amount} ${unit}`;
461
+ }
462
+
463
+ updated(changed: Map<string, any>) {
464
+ super.updated(changed);
465
+
466
+ if (
467
+ (changed.has('startDate') && changed.has('endDate') && !this.startDate) ||
468
+ !this.endDate
469
+ ) {
470
+ this.setRange('M');
471
+ }
472
+
473
+ if (changed.has('editingStart') && this.editingStart) {
474
+ setTimeout(() => {
475
+ const startPicker: DatePicker = this.shadowRoot?.querySelector(
476
+ 'temba-datepicker.start-picker'
477
+ );
478
+
479
+ if (startPicker) {
480
+ startPicker.handleClicked();
481
+ }
482
+ }, 0);
483
+ }
484
+
485
+ if (changed.has('editingEnd') && this.editingEnd) {
486
+ setTimeout(() => {
487
+ const endPicker: DatePicker = this.shadowRoot?.querySelector(
488
+ 'temba-datepicker.end-picker'
489
+ );
490
+ if (endPicker) {
491
+ endPicker.handleClicked();
492
+ }
493
+ }, 0);
494
+ }
495
+ }
496
+
497
+ render() {
498
+ return html`
499
+ <div class="range-container">
500
+ ${this.editingStart
501
+ ? html`<temba-datepicker
502
+ class="start-picker"
503
+ .value=${this.startDate}
504
+ .min=${this.minDate}
505
+ .max=${this.maxDate}
506
+ @change=${(e: Event) => {
507
+ const value = (e.target as any).value;
508
+ this.setValidRange('start', value);
509
+ this.editingStart = false;
510
+ this.selectedRange = '';
511
+ }}
512
+ @blur=${() => (this.editingStart = false)}
513
+ ></temba-datepicker>`
514
+ : html`<span class="date-display" @click=${this.handleStartClick}
515
+ >${this.startDate || 'Start date'}</span
516
+ >`}
517
+ <span> - </span>
518
+ ${this.editingEnd
519
+ ? html`<temba-datepicker
520
+ .value=${this.endDate}
521
+ class="end-picker"
522
+ .min=${this.minDate}
523
+ .max=${this.maxDate}
524
+ @change=${(e: Event) => {
525
+ const value = (e.target as any).value;
526
+ this.setValidRange('end', value);
527
+ this.editingEnd = false;
528
+ this.selectedRange = '';
529
+ }}
530
+ @blur=${() => (this.editingEnd = false)}
531
+ ></temba-datepicker>`
532
+ : html`<span class="date-display" @click=${this.handleEndClick}
533
+ >${this.endDate || 'End date'}</span
534
+ >`}
535
+ <div class="navigation-container">
536
+ <button
537
+ class="nav-arrow ${this.selectedRange === 'ALL' ? 'hidden' : ''}"
538
+ ?disabled=${!this.canNavigatePrevious()}
539
+ @click=${this.navigatePrevious}
540
+ title="Previous ${this.selectedRange === 'W'
541
+ ? 'week'
542
+ : this.selectedRange === 'M'
543
+ ? 'month'
544
+ : this.selectedRange === 'Y'
545
+ ? 'year'
546
+ : this.selectedRange === ''
547
+ ? this.getNavigationLabel('previous')
548
+ : 'period'}"
549
+ >
550
+
551
+ </button>
552
+ <div class="button-group">
553
+ <button
554
+ class="range-btn ${this.selectedRange === 'W' ? 'selected' : ''}"
555
+ @click=${() => this.setRange('W')}
556
+ >
557
+ W
558
+ </button>
559
+ <button
560
+ class="range-btn ${this.selectedRange === 'M' ? 'selected' : ''}"
561
+ @click=${() => this.setRange('M')}
562
+ >
563
+ M
564
+ </button>
565
+ <button
566
+ class="range-btn ${this.selectedRange === 'Y' ? 'selected' : ''}"
567
+ @click=${() => this.setRange('Y')}
568
+ >
569
+ Y
570
+ </button>
571
+ <button
572
+ class="range-btn ${this.selectedRange === 'ALL'
573
+ ? 'selected'
574
+ : ''}"
575
+ @click=${() => this.setRange('ALL')}
576
+ >
577
+ All
578
+ </button>
579
+ </div>
580
+ <button
581
+ class="nav-arrow ${this.selectedRange === 'ALL' ? 'hidden' : ''}"
582
+ ?disabled=${!this.canNavigateNext()}
583
+ @click=${this.navigateNext}
584
+ title="Next ${this.selectedRange === 'W'
585
+ ? 'week'
586
+ : this.selectedRange === 'M'
587
+ ? 'month'
588
+ : this.selectedRange === 'Y'
589
+ ? 'year'
590
+ : this.selectedRange === ''
591
+ ? this.getNavigationLabel('next')
592
+ : 'period'}"
593
+ >
594
+
595
+ </button>
596
+ </div>
597
+ </div>
598
+ `;
599
+ }
600
+ }
601
+
602
+ customElements.define('temba-range-picker', RangePicker);