@tolle_/tolle-ui 18.2.29 → 18.2.30

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 (279) hide show
  1. package/esm2022/lib/accordion-item.component.mjs +5 -11
  2. package/esm2022/lib/accordion.component.mjs +4 -9
  3. package/esm2022/lib/alert-dialog-dynamic.component.mjs +5 -10
  4. package/esm2022/lib/alert-dialog.component.mjs +44 -59
  5. package/esm2022/lib/alert.component.mjs +5 -11
  6. package/esm2022/lib/aspect-ratio.component.mjs +119 -0
  7. package/esm2022/lib/attachment.component.mjs +334 -0
  8. package/esm2022/lib/avatar-fallback.component.mjs +3 -3
  9. package/esm2022/lib/avatar.component.mjs +4 -9
  10. package/esm2022/lib/badge.component.mjs +4 -9
  11. package/esm2022/lib/breadcrumb-item.component.mjs +3 -3
  12. package/esm2022/lib/breadcrumb-link.component.mjs +3 -3
  13. package/esm2022/lib/breadcrumb-separator.component.mjs +3 -3
  14. package/esm2022/lib/breadcrumb.component.mjs +3 -3
  15. package/esm2022/lib/bubble.component.mjs +301 -0
  16. package/esm2022/lib/button.component.mjs +5 -11
  17. package/esm2022/lib/calendar.component.mjs +5 -11
  18. package/esm2022/lib/card.component.mjs +14 -43
  19. package/esm2022/lib/carousel/carousel.component.mjs +5 -12
  20. package/esm2022/lib/chain-of-thought.component.mjs +380 -0
  21. package/esm2022/lib/chart-pie.component.mjs +386 -0
  22. package/esm2022/lib/chart.component.mjs +1146 -0
  23. package/esm2022/lib/chart.service.mjs +555 -0
  24. package/esm2022/lib/checkbox.component.mjs +5 -11
  25. package/esm2022/lib/checkpoint.component.mjs +134 -0
  26. package/esm2022/lib/collapsible.component.mjs +4 -11
  27. package/esm2022/lib/combobox.component.mjs +372 -0
  28. package/esm2022/lib/command-dialog.component.mjs +164 -0
  29. package/esm2022/lib/command.component.mjs +514 -0
  30. package/esm2022/lib/command.service.mjs +130 -0
  31. package/esm2022/lib/confirmation.component.mjs +219 -0
  32. package/esm2022/lib/context-menu-trigger.directive.mjs +36 -0
  33. package/esm2022/lib/context-menu.component.mjs +460 -0
  34. package/esm2022/lib/context-menu.service.mjs +121 -0
  35. package/esm2022/lib/context.component.mjs +377 -0
  36. package/esm2022/lib/conversation.component.mjs +442 -0
  37. package/esm2022/lib/country-selector.component.mjs +5 -11
  38. package/esm2022/lib/data-table.component.mjs +59 -12
  39. package/esm2022/lib/date-picker.component.mjs +251 -198
  40. package/esm2022/lib/date-range-picker.component.mjs +224 -116
  41. package/esm2022/lib/date-time-picker.component.mjs +506 -0
  42. package/esm2022/lib/direction.component.mjs +79 -0
  43. package/esm2022/lib/dropdown-item.component.mjs +3 -3
  44. package/esm2022/lib/dropdown-label.component.mjs +3 -3
  45. package/esm2022/lib/dropdown-menu.component.mjs +3 -3
  46. package/esm2022/lib/dropdown-separator.component.mjs +3 -3
  47. package/esm2022/lib/dropdown-trigger.directive.mjs +6 -4
  48. package/esm2022/lib/empty-state.component.mjs +4 -9
  49. package/esm2022/lib/field.component.mjs +394 -0
  50. package/esm2022/lib/hover-card.component.mjs +6 -16
  51. package/esm2022/lib/inline-citation.component.mjs +263 -0
  52. package/esm2022/lib/input-group.component.mjs +418 -0
  53. package/esm2022/lib/input.component.mjs +5 -11
  54. package/esm2022/lib/item.component.mjs +243 -0
  55. package/esm2022/lib/kbd.component.mjs +82 -0
  56. package/esm2022/lib/label.component.mjs +4 -9
  57. package/esm2022/lib/marker.component.mjs +135 -0
  58. package/esm2022/lib/masked-input.component.mjs +5 -11
  59. package/esm2022/lib/menubar.component.mjs +683 -0
  60. package/esm2022/lib/message-scroller.component.mjs +353 -0
  61. package/esm2022/lib/message-scroller.service.mjs +157 -0
  62. package/esm2022/lib/message.component.mjs +228 -0
  63. package/esm2022/lib/model-selector.component.mjs +310 -0
  64. package/esm2022/lib/multi-select.component.mjs +155 -72
  65. package/esm2022/lib/native-select.component.mjs +201 -0
  66. package/esm2022/lib/navigation-menu.component.mjs +642 -0
  67. package/esm2022/lib/otp-group.component.mjs +3 -3
  68. package/esm2022/lib/otp-slot.component.mjs +4 -9
  69. package/esm2022/lib/otp.component.mjs +5 -11
  70. package/esm2022/lib/pagination.component.mjs +7 -10
  71. package/esm2022/lib/plan.component.mjs +335 -0
  72. package/esm2022/lib/popover-content.component.mjs +4 -9
  73. package/esm2022/lib/popover.component.mjs +15 -13
  74. package/esm2022/lib/progress.component.mjs +4 -9
  75. package/esm2022/lib/prompt-input.component.mjs +526 -0
  76. package/esm2022/lib/queue.component.mjs +280 -0
  77. package/esm2022/lib/radio-group.component.mjs +6 -13
  78. package/esm2022/lib/radio-item.component.mjs +4 -9
  79. package/esm2022/lib/range-calendar.component.mjs +5 -11
  80. package/esm2022/lib/reasoning.component.mjs +335 -0
  81. package/esm2022/lib/resizable.component.mjs +219 -0
  82. package/esm2022/lib/segment.component.mjs +17 -7
  83. package/esm2022/lib/select-group.component.mjs +4 -8
  84. package/esm2022/lib/select-item.component.mjs +4 -9
  85. package/esm2022/lib/select-separator.component.mjs +4 -8
  86. package/esm2022/lib/select.component.mjs +133 -49
  87. package/esm2022/lib/separator.component.mjs +4 -9
  88. package/esm2022/lib/sheet.component.mjs +38 -56
  89. package/esm2022/lib/shimmer.component.mjs +84 -0
  90. package/esm2022/lib/sidebar.component.mjs +11 -1
  91. package/esm2022/lib/skeleton.component.mjs +4 -9
  92. package/esm2022/lib/sources.component.mjs +452 -0
  93. package/esm2022/lib/spinner.component.mjs +68 -0
  94. package/esm2022/lib/suggestion.component.mjs +207 -0
  95. package/esm2022/lib/switch.component.mjs +5 -11
  96. package/esm2022/lib/table.component.mjs +213 -0
  97. package/esm2022/lib/tabs.component.mjs +4 -11
  98. package/esm2022/lib/tag-input.component.mjs +5 -11
  99. package/esm2022/lib/task.component.mjs +359 -0
  100. package/esm2022/lib/textarea.component.mjs +5 -11
  101. package/esm2022/lib/theme.service.mjs +17 -6
  102. package/esm2022/lib/time-picker.component.mjs +923 -0
  103. package/esm2022/lib/toast.service.mjs +3 -2
  104. package/esm2022/lib/toaster.component.mjs +76 -39
  105. package/esm2022/lib/toggle-group.component.mjs +4 -9
  106. package/esm2022/lib/toggle.component.mjs +5 -11
  107. package/esm2022/lib/tool.component.mjs +462 -0
  108. package/esm2022/lib/typography.component.mjs +83 -0
  109. package/esm2022/lib/utils/color.mjs +140 -19
  110. package/esm2022/public-api.mjs +61 -1
  111. package/fesm2022/tolle-ui.mjs +16453 -1024
  112. package/fesm2022/tolle-ui.mjs.map +1 -1
  113. package/lib/alert-dialog.component.d.ts +10 -2
  114. package/lib/aspect-ratio.component.d.ts +20 -0
  115. package/lib/attachment.component.d.ts +101 -0
  116. package/lib/bubble.component.d.ts +112 -0
  117. package/lib/chain-of-thought.component.d.ts +166 -0
  118. package/lib/chart-pie.component.d.ts +108 -0
  119. package/lib/chart.component.d.ts +369 -0
  120. package/lib/chart.service.d.ts +222 -0
  121. package/lib/checkpoint.component.d.ts +51 -0
  122. package/lib/combobox.component.d.ts +90 -0
  123. package/lib/command-dialog.component.d.ts +53 -0
  124. package/lib/command.component.d.ts +189 -0
  125. package/lib/command.service.d.ts +67 -0
  126. package/lib/confirmation.component.d.ts +70 -0
  127. package/lib/context-menu-trigger.directive.d.ts +12 -0
  128. package/lib/context-menu.component.d.ts +41 -0
  129. package/lib/context-menu.service.d.ts +38 -0
  130. package/lib/context.component.d.ts +134 -0
  131. package/lib/conversation.component.d.ts +165 -0
  132. package/lib/data-table.component.d.ts +26 -1
  133. package/lib/date-picker.component.d.ts +72 -11
  134. package/lib/date-range-picker.component.d.ts +63 -6
  135. package/lib/date-time-picker.component.d.ts +119 -0
  136. package/lib/direction.component.d.ts +45 -0
  137. package/lib/field.component.d.ts +201 -0
  138. package/lib/inline-citation.component.d.ts +103 -0
  139. package/lib/input-group.component.d.ts +150 -0
  140. package/lib/item.component.d.ts +154 -0
  141. package/lib/kbd.component.d.ts +50 -0
  142. package/lib/marker.component.d.ts +58 -0
  143. package/lib/menubar.component.d.ts +247 -0
  144. package/lib/message-scroller.component.d.ts +159 -0
  145. package/lib/message-scroller.service.d.ts +73 -0
  146. package/lib/message.component.d.ts +128 -0
  147. package/lib/model-selector.component.d.ts +103 -0
  148. package/lib/multi-select.component.d.ts +66 -14
  149. package/lib/native-select.component.d.ts +57 -0
  150. package/lib/navigation-menu.component.d.ts +230 -0
  151. package/lib/plan.component.d.ts +120 -0
  152. package/lib/popover.component.d.ts +1 -0
  153. package/lib/prompt-input.component.d.ts +194 -0
  154. package/lib/queue.component.d.ts +106 -0
  155. package/lib/reasoning.component.d.ts +127 -0
  156. package/lib/resizable.component.d.ts +43 -0
  157. package/lib/segment.component.d.ts +13 -3
  158. package/lib/select.component.d.ts +37 -5
  159. package/lib/sheet.component.d.ts +3 -0
  160. package/lib/shimmer.component.d.ts +38 -0
  161. package/lib/sidebar.component.d.ts +2 -0
  162. package/lib/sources.component.d.ts +156 -0
  163. package/lib/spinner.component.d.ts +29 -0
  164. package/lib/suggestion.component.d.ts +96 -0
  165. package/lib/table.component.d.ts +106 -0
  166. package/lib/task.component.d.ts +151 -0
  167. package/lib/theme.service.d.ts +9 -0
  168. package/lib/time-picker.component.d.ts +243 -0
  169. package/lib/toast.service.d.ts +3 -1
  170. package/lib/toaster.component.d.ts +26 -9
  171. package/lib/tool.component.d.ts +158 -0
  172. package/lib/typography.component.d.ts +32 -0
  173. package/lib/utils/color.d.ts +19 -5
  174. package/package.json +1 -1
  175. package/public-api.d.ts +50 -0
  176. package/registry/docs-content.json +8480 -1542
  177. package/registry/llms-full.txt +2679 -319
  178. package/registry/llms.txt +43 -4
  179. package/registry/manifest.json +9090 -1742
  180. package/registry/r/accordion-item.json +1 -1
  181. package/registry/r/accordion.json +1 -1
  182. package/registry/r/alert-dialog-dynamic.json +1 -1
  183. package/registry/r/alert-dialog.json +1 -1
  184. package/registry/r/alert.json +1 -1
  185. package/registry/r/attachment.json +23 -0
  186. package/registry/r/avatar-fallback.json +1 -1
  187. package/registry/r/avatar.json +1 -1
  188. package/registry/r/badge.json +1 -1
  189. package/registry/r/breadcrumb-item.json +1 -1
  190. package/registry/r/breadcrumb-link.json +1 -1
  191. package/registry/r/breadcrumb-separator.json +1 -1
  192. package/registry/r/breadcrumb.json +1 -1
  193. package/registry/r/bubble.json +23 -0
  194. package/registry/r/button.json +1 -1
  195. package/registry/r/calendar.json +1 -1
  196. package/registry/r/card.json +1 -1
  197. package/registry/r/carousel.json +1 -1
  198. package/registry/r/chain-of-thought.json +23 -0
  199. package/registry/r/chart-pie.json +30 -0
  200. package/registry/r/chart.json +28 -0
  201. package/registry/r/checkbox.json +1 -1
  202. package/registry/r/checkpoint.json +23 -0
  203. package/registry/r/collapsible.json +1 -1
  204. package/registry/r/combobox.json +31 -0
  205. package/registry/r/command-dialog.json +21 -0
  206. package/registry/r/command.json +26 -0
  207. package/registry/r/confirmation.json +23 -0
  208. package/registry/r/context.json +26 -0
  209. package/registry/r/conversation.json +23 -0
  210. package/registry/r/country-selector.json +1 -1
  211. package/registry/r/data-table.json +1 -1
  212. package/registry/r/date-picker.json +4 -4
  213. package/registry/r/date-range-picker.json +3 -3
  214. package/registry/r/date-time-picker.json +28 -0
  215. package/registry/r/direction.json +16 -0
  216. package/registry/r/dropdown-item.json +1 -1
  217. package/registry/r/dropdown-label.json +1 -1
  218. package/registry/r/dropdown-menu.json +1 -1
  219. package/registry/r/dropdown-separator.json +1 -1
  220. package/registry/r/dropdown-trigger.json +1 -1
  221. package/registry/r/empty-state.json +1 -1
  222. package/registry/r/field.json +23 -0
  223. package/registry/r/hover-card.json +1 -1
  224. package/registry/r/inline-citation.json +26 -0
  225. package/registry/r/input-group.json +23 -0
  226. package/registry/r/input.json +1 -1
  227. package/registry/r/item.json +23 -0
  228. package/registry/r/kbd.json +23 -0
  229. package/registry/r/label.json +1 -1
  230. package/registry/r/marker.json +23 -0
  231. package/registry/r/masked-input.json +1 -1
  232. package/registry/r/menubar.json +24 -0
  233. package/registry/r/message-scroller.json +26 -0
  234. package/registry/r/message.json +23 -0
  235. package/registry/r/model-selector.json +32 -0
  236. package/registry/r/multi-select.json +2 -2
  237. package/registry/r/native-select.json +23 -0
  238. package/registry/r/navigation-menu.json +24 -0
  239. package/registry/r/otp-group.json +1 -1
  240. package/registry/r/otp-slot.json +1 -1
  241. package/registry/r/otp.json +1 -1
  242. package/registry/r/pagination.json +3 -2
  243. package/registry/r/plan.json +23 -0
  244. package/registry/r/popover-content.json +1 -1
  245. package/registry/r/popover.json +1 -1
  246. package/registry/r/progress.json +1 -1
  247. package/registry/r/prompt-input.json +23 -0
  248. package/registry/r/queue.json +23 -0
  249. package/registry/r/radio-group.json +1 -1
  250. package/registry/r/radio-item.json +1 -1
  251. package/registry/r/range-calendar.json +1 -1
  252. package/registry/r/reasoning.json +23 -0
  253. package/registry/r/segment.json +1 -1
  254. package/registry/r/select-group.json +1 -1
  255. package/registry/r/select-item.json +1 -1
  256. package/registry/r/select-separator.json +1 -1
  257. package/registry/r/select.json +4 -3
  258. package/registry/r/separator.json +1 -1
  259. package/registry/r/sheet.json +1 -1
  260. package/registry/r/shimmer.json +23 -0
  261. package/registry/r/sidebar.json +1 -1
  262. package/registry/r/skeleton.json +1 -1
  263. package/registry/r/sources.json +23 -0
  264. package/registry/r/spinner.json +23 -0
  265. package/registry/r/suggestion.json +23 -0
  266. package/registry/r/switch.json +1 -1
  267. package/registry/r/table.json +21 -0
  268. package/registry/r/tabs.json +1 -1
  269. package/registry/r/tag-input.json +1 -1
  270. package/registry/r/task.json +23 -0
  271. package/registry/r/textarea.json +1 -1
  272. package/registry/r/time-picker.json +24 -0
  273. package/registry/r/toaster.json +2 -2
  274. package/registry/r/toggle-group.json +1 -1
  275. package/registry/r/toggle.json +1 -1
  276. package/registry/r/tool.json +23 -0
  277. package/registry/r/typography.json +23 -0
  278. package/registry/registry.json +915 -22
  279. package/theme.css +109 -8
@@ -0,0 +1,353 @@
1
+ import { Component, Directive, Input, Output, EventEmitter, ElementRef, NgZone, ChangeDetectionStrategy, ChangeDetectorRef, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { Subscription } from 'rxjs';
4
+ import { cn } from './utils/cn';
5
+ import { MessageScrollerService } from './message-scroller.service';
6
+ import * as i0 from "@angular/core";
7
+ import * as i1 from "@angular/common";
8
+ /**
9
+ * A scroll container built for conversations, where content streams in while
10
+ * the reader is still reading.
11
+ *
12
+ * A plain overflow-scroll div gets this wrong in both directions: it either
13
+ * pins to the bottom and yanks the page mid-sentence, or it never follows and
14
+ * the reader misses the reply. This handles the cases that are easy to get
15
+ * wrong — following the live edge only while the reader is already there,
16
+ * opening a saved thread at the newest turn rather than the absolute end, and
17
+ * holding position when older history loads in above.
18
+ *
19
+ * ```html
20
+ * <tolle-message-scroller startPosition="last-anchor">
21
+ * <tolle-message-scroller-viewport>
22
+ * <tolle-message-scroller-content>
23
+ * <div tolleMessageScrollerItem [scrollAnchor]="turn.isNewTurn" *ngFor="let turn of turns">
24
+ * …
25
+ * </div>
26
+ * </tolle-message-scroller-content>
27
+ * </tolle-message-scroller-viewport>
28
+ * <tolle-message-scroller-button />
29
+ * </tolle-message-scroller>
30
+ * ```
31
+ * @new
32
+ */
33
+ export class MessageScrollerComponent {
34
+ cdr = inject(ChangeDetectorRef);
35
+ /**
36
+ * Angular writes a bound `class` input through its styling path, which does
37
+ * not mark an OnPush component dirty — without this hook the component keeps
38
+ * rendering the class it was born with.
39
+ */
40
+ ngOnChanges() {
41
+ this.cdr.markForCheck();
42
+ }
43
+ /**
44
+ * Where to land when the transcript first renders. `last-anchor` opens at the
45
+ * newest turn so a long answer starts from its first line.
46
+ * @default 'last-anchor'
47
+ */
48
+ startPosition = 'last-anchor';
49
+ /**
50
+ * Pixels of the previous turn kept visible above an anchored turn, so the
51
+ * reader can see what the reply is responding to. @default 48
52
+ */
53
+ scrollPreviousItemPeek = 48;
54
+ /**
55
+ * Distance from the bottom, in px, still treated as the live edge.
56
+ * @default 32
57
+ */
58
+ threshold = 32;
59
+ /** Extra Tailwind classes merged onto the frame via `cn()` (last-wins). */
60
+ class = '';
61
+ /** Emitted when the reader arrives at, or leaves, the live edge. */
62
+ atBottomChange = new EventEmitter();
63
+ service = inject(MessageScrollerService);
64
+ sub = new Subscription();
65
+ cn = cn;
66
+ ngOnInit() {
67
+ this.service.threshold = this.threshold;
68
+ this.sub.add(this.service.atBottom$.subscribe((v) => this.atBottomChange.emit(v)));
69
+ }
70
+ ngOnDestroy() {
71
+ this.sub.unsubscribe();
72
+ }
73
+ /** Jumps to the newest content and resumes following. */
74
+ scrollToBottom(behavior = 'smooth') {
75
+ this.service.scrollToBottom(behavior);
76
+ }
77
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
78
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerComponent, isStandalone: true, selector: "tolle-message-scroller", inputs: { startPosition: "startPosition", scrollPreviousItemPeek: "scrollPreviousItemPeek", threshold: "threshold", class: "class" }, outputs: { atBottomChange: "atBottomChange" }, providers: [MessageScrollerService], usesOnChanges: true, ngImport: i0, template: `
79
+ <div [class]="cn('relative flex min-h-0 flex-1 flex-col overflow-hidden', class)">
80
+ <ng-content></ng-content>
81
+ </div>
82
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
83
+ }
84
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerComponent, decorators: [{
85
+ type: Component,
86
+ args: [{ selector: 'tolle-message-scroller', standalone: true, imports: [CommonModule], providers: [MessageScrollerService], changeDetection: ChangeDetectionStrategy.OnPush, template: `
87
+ <div [class]="cn('relative flex min-h-0 flex-1 flex-col overflow-hidden', class)">
88
+ <ng-content></ng-content>
89
+ </div>
90
+ `, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}\n"] }]
91
+ }], propDecorators: { startPosition: [{
92
+ type: Input
93
+ }], scrollPreviousItemPeek: [{
94
+ type: Input
95
+ }], threshold: [{
96
+ type: Input
97
+ }], class: [{
98
+ type: Input
99
+ }], atBottomChange: [{
100
+ type: Output
101
+ }] } });
102
+ /**
103
+ * The scrolling element. Watches its own size and its content's size so it can
104
+ * follow streamed text and hold position when history is prepended.
105
+ */
106
+ export class MessageScrollerViewportComponent {
107
+ cdr = inject(ChangeDetectorRef);
108
+ /**
109
+ * Angular writes a bound `class` input through its styling path, which does
110
+ * not mark an OnPush component dirty — without this hook the component keeps
111
+ * rendering the class it was born with.
112
+ */
113
+ ngOnChanges() {
114
+ this.cdr.markForCheck();
115
+ }
116
+ /** Extra Tailwind classes merged onto the viewport via `cn()` (last-wins). */
117
+ class = '';
118
+ service = inject(MessageScrollerService);
119
+ scroller = inject(MessageScrollerComponent);
120
+ host = inject(ElementRef);
121
+ zone = inject(NgZone);
122
+ viewportEl = null;
123
+ resizeObserver;
124
+ mutationObserver;
125
+ onScroll = () => this.service.measure();
126
+ /** scrollHeight captured before each DOM mutation, to detect prepends. */
127
+ lastScrollHeight = 0;
128
+ lastScrollTop = 0;
129
+ cn = cn;
130
+ ngAfterViewInit() {
131
+ const el = this.host.nativeElement.querySelector('div');
132
+ if (!el)
133
+ return;
134
+ this.viewportEl = el;
135
+ this.service.registerViewport(el);
136
+ // Scroll fires at frame rate; keeping the listener outside Angular means we
137
+ // only re-enter change detection when a tracked boolean actually flips.
138
+ this.zone.runOutsideAngular(() => {
139
+ el.addEventListener('scroll', this.onScroll, { passive: true });
140
+ if (typeof ResizeObserver !== 'undefined') {
141
+ this.resizeObserver = new ResizeObserver(() => this.service.handleContentGrowth());
142
+ this.resizeObserver.observe(el);
143
+ const content = el.firstElementChild;
144
+ if (content)
145
+ this.resizeObserver.observe(content);
146
+ }
147
+ if (typeof MutationObserver !== 'undefined') {
148
+ this.mutationObserver = new MutationObserver(() => this.afterMutation());
149
+ this.mutationObserver.observe(el, { childList: true, subtree: true, characterData: true });
150
+ }
151
+ });
152
+ this.lastScrollHeight = el.scrollHeight;
153
+ // Wait a frame so projected content has laid out before positioning.
154
+ setTimeout(() => {
155
+ this.service.applyStartPosition(this.scroller.startPosition, this.scroller.scrollPreviousItemPeek);
156
+ this.lastScrollHeight = el.scrollHeight;
157
+ });
158
+ }
159
+ ngOnDestroy() {
160
+ this.viewportEl?.removeEventListener('scroll', this.onScroll);
161
+ this.resizeObserver?.disconnect();
162
+ this.mutationObserver?.disconnect();
163
+ }
164
+ /**
165
+ * Distinguishes content appended below (stream) from content inserted above
166
+ * (history). Growth while the reader has NOT moved but the scrollable area
167
+ * above them has, means a prepend — restore their position rather than let
168
+ * the view lurch.
169
+ */
170
+ afterMutation() {
171
+ const el = this.viewportEl;
172
+ if (!el)
173
+ return;
174
+ const grew = el.scrollHeight - this.lastScrollHeight;
175
+ const readerMoved = el.scrollTop !== this.lastScrollTop;
176
+ if (grew > 0 && !readerMoved && !this.service.isFollowing && el.scrollTop > 0) {
177
+ this.service.preserveOnPrepend(this.lastScrollHeight);
178
+ }
179
+ else if (grew > 0) {
180
+ this.service.handleContentGrowth();
181
+ }
182
+ this.lastScrollHeight = el.scrollHeight;
183
+ this.lastScrollTop = el.scrollTop;
184
+ this.service.measure();
185
+ }
186
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerViewportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
187
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerViewportComponent, isStandalone: true, selector: "tolle-message-scroller-viewport", inputs: { class: "class" }, usesOnChanges: true, ngImport: i0, template: `
188
+ <div #viewport [class]="cn('min-h-0 flex-1 overflow-y-auto overscroll-contain', class)">
189
+ <ng-content></ng-content>
190
+ </div>
191
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
192
+ }
193
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerViewportComponent, decorators: [{
194
+ type: Component,
195
+ args: [{ selector: 'tolle-message-scroller-viewport', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
196
+ <div #viewport [class]="cn('min-h-0 flex-1 overflow-y-auto overscroll-contain', class)">
197
+ <ng-content></ng-content>
198
+ </div>
199
+ `, styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}\n"] }]
200
+ }], propDecorators: { class: [{
201
+ type: Input
202
+ }] } });
203
+ /** The transcript itself. Announced politely so streamed replies reach screen readers. */
204
+ export class MessageScrollerContentComponent {
205
+ cdr = inject(ChangeDetectorRef);
206
+ /**
207
+ * Angular writes a bound `class` input through its styling path, which does
208
+ * not mark an OnPush component dirty — without this hook the component keeps
209
+ * rendering the class it was born with.
210
+ */
211
+ ngOnChanges() {
212
+ this.cdr.markForCheck();
213
+ }
214
+ /** Extra Tailwind classes merged onto the transcript via `cn()` (last-wins). */
215
+ class = '';
216
+ cn = cn;
217
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
218
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerContentComponent, isStandalone: true, selector: "tolle-message-scroller-content", inputs: { class: "class" }, usesOnChanges: true, ngImport: i0, template: `
219
+ <div
220
+ role="log"
221
+ aria-live="polite"
222
+ aria-relevant="additions text"
223
+ [class]="cn('flex flex-col gap-4 p-4', class)"
224
+ >
225
+ <ng-content></ng-content>
226
+ </div>
227
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
228
+ }
229
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerContentComponent, decorators: [{
230
+ type: Component,
231
+ args: [{ selector: 'tolle-message-scroller-content', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
232
+ <div
233
+ role="log"
234
+ aria-live="polite"
235
+ aria-relevant="additions text"
236
+ [class]="cn('flex flex-col gap-4 p-4', class)"
237
+ >
238
+ <ng-content></ng-content>
239
+ </div>
240
+ `, styles: [":host{display:block}\n"] }]
241
+ }], propDecorators: { class: [{
242
+ type: Input
243
+ }] } });
244
+ /**
245
+ * Marks one row of the transcript. Set `scrollAnchor` on the row that begins a
246
+ * conversation turn so `startPosition="last-anchor"` can open there.
247
+ */
248
+ export class MessageScrollerItemDirective {
249
+ /** Marks this row as the start of a turn. @default false */
250
+ scrollAnchor = false;
251
+ service = inject(MessageScrollerService);
252
+ host = inject(ElementRef);
253
+ ngOnInit() {
254
+ if (this.scrollAnchor)
255
+ this.service.registerAnchor(this.host.nativeElement);
256
+ }
257
+ ngOnDestroy() {
258
+ this.service.unregisterAnchor(this.host.nativeElement);
259
+ }
260
+ /** Brings this row into view — use for jump-to-message. */
261
+ scrollIntoView(behavior = 'smooth') {
262
+ this.service.scrollToItem(this.host.nativeElement, behavior);
263
+ }
264
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
265
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerItemDirective, isStandalone: true, selector: "[tolleMessageScrollerItem]", inputs: { scrollAnchor: "scrollAnchor" }, host: { properties: { "attr.data-scroll-anchor": "scrollAnchor ? '' : null" } }, ngImport: i0 });
266
+ }
267
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerItemDirective, decorators: [{
268
+ type: Directive,
269
+ args: [{
270
+ selector: '[tolleMessageScrollerItem]',
271
+ standalone: true,
272
+ host: { '[attr.data-scroll-anchor]': `scrollAnchor ? '' : null` },
273
+ }]
274
+ }], propDecorators: { scrollAnchor: [{
275
+ type: Input
276
+ }] } });
277
+ /** Floating "jump to latest" control. Hidden while the reader is already at the edge. */
278
+ export class MessageScrollerButtonComponent {
279
+ /**
280
+ * Angular writes a bound `class` input through its styling path, which does
281
+ * not mark an OnPush component dirty — without this hook the component keeps
282
+ * rendering the class it was born with.
283
+ */
284
+ ngOnChanges() {
285
+ this.cdr.markForCheck();
286
+ }
287
+ /** Accessible name for the jump control. @default 'Jump to latest messages' */
288
+ ariaLabel = 'Jump to latest messages';
289
+ /** Extra Tailwind classes merged onto the button via `cn()` (last-wins). */
290
+ class = '';
291
+ service = inject(MessageScrollerService);
292
+ cdr = inject(ChangeDetectorRef);
293
+ sub = new Subscription();
294
+ atBottom = true;
295
+ cn = cn;
296
+ ngOnInit() {
297
+ this.sub.add(this.service.atBottom$.subscribe((v) => {
298
+ this.atBottom = v;
299
+ this.cdr.markForCheck();
300
+ }));
301
+ }
302
+ ngOnDestroy() {
303
+ this.sub.unsubscribe();
304
+ }
305
+ jump() {
306
+ this.service.scrollToBottom();
307
+ }
308
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
309
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerButtonComponent, isStandalone: true, selector: "tolle-message-scroller-button", inputs: { ariaLabel: "ariaLabel", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
310
+ <button
311
+ *ngIf="!atBottom"
312
+ type="button"
313
+ [attr.aria-label]="ariaLabel"
314
+ [class]="cn(
315
+ 'absolute bottom-4 left-1/2 z-10 flex -translate-x-1/2 items-center gap-1.5 rounded-full',
316
+ 'border border-border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md',
317
+ 'transition-colors hover:bg-accent hover:text-accent-foreground',
318
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
319
+ class
320
+ )"
321
+ (click)="jump()"
322
+ >
323
+ <i class="ri-arrow-down-line" aria-hidden="true"></i>
324
+ <ng-content></ng-content>
325
+ </button>
326
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
327
+ }
328
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerButtonComponent, decorators: [{
329
+ type: Component,
330
+ args: [{ selector: 'tolle-message-scroller-button', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
331
+ <button
332
+ *ngIf="!atBottom"
333
+ type="button"
334
+ [attr.aria-label]="ariaLabel"
335
+ [class]="cn(
336
+ 'absolute bottom-4 left-1/2 z-10 flex -translate-x-1/2 items-center gap-1.5 rounded-full',
337
+ 'border border-border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md',
338
+ 'transition-colors hover:bg-accent hover:text-accent-foreground',
339
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
340
+ class
341
+ )"
342
+ (click)="jump()"
343
+ >
344
+ <i class="ri-arrow-down-line" aria-hidden="true"></i>
345
+ <ng-content></ng-content>
346
+ </button>
347
+ `, styles: [":host{display:block}\n"] }]
348
+ }], propDecorators: { ariaLabel: [{
349
+ type: Input
350
+ }], class: [{
351
+ type: Input
352
+ }] } });
353
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZS1zY3JvbGxlci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy90b2xsZS9zcmMvbGliL21lc3NhZ2Utc2Nyb2xsZXIuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsdUJBQXVCLEVBQUUsaUJBQWlCLEVBQW9DLE1BQU0sRUFBYSxNQUFNLGVBQWUsQ0FBQztBQUN2TSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUNwQyxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ2hDLE9BQU8sRUFBRSxzQkFBc0IsRUFBdUIsTUFBTSw0QkFBNEIsQ0FBQzs7O0FBRXpGOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFpQkgsTUFBTSxPQUFPLHdCQUF3QjtJQUNsQixHQUFHLEdBQUcsTUFBTSxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFFakQ7Ozs7T0FJRztJQUNILFdBQVc7UUFDVCxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFRDs7OztPQUlHO0lBQ00sYUFBYSxHQUF3QixhQUFhLENBQUM7SUFDNUQ7OztPQUdHO0lBQ00sc0JBQXNCLEdBQUcsRUFBRSxDQUFDO0lBQ3JDOzs7T0FHRztJQUNNLFNBQVMsR0FBRyxFQUFFLENBQUM7SUFDeEIsMkVBQTJFO0lBQ2xFLEtBQUssR0FBRyxFQUFFLENBQUM7SUFFcEIsb0VBQW9FO0lBQzFELGNBQWMsR0FBRyxJQUFJLFlBQVksRUFBVyxDQUFDO0lBRXRDLE9BQU8sR0FBRyxNQUFNLENBQUMsc0JBQXNCLENBQUMsQ0FB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RSxDQUFDO0lBQ2hDLENBQUM7d0dBdENVLDhCQUE4Qjs0RkFBOUIsOEJBQThCLGtLQW5CL0I7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJULCtGQW5CUyxZQUFZOzs0RkFxQlgsOEJBQThCO2tCQXpCMUMsU0FBUzsrQkFDRSwrQkFBK0IsY0FFN0IsSUFBSSxXQUNQLENBQUMsWUFBWSxDQUFDLG1CQUNOLHVCQUF1QixDQUFDLE1BQU0sWUFDckM7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJUOzhCQWNRLFNBQVM7c0JBQWpCLEtBQUs7Z0JBRUcsS0FBSztzQkFBYixLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBEaXJlY3RpdmUsIElucHV0LCBPdXRwdXQsIEV2ZW50RW1pdHRlciwgRWxlbWVudFJlZiwgTmdab25lLCBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSwgQ2hhbmdlRGV0ZWN0b3JSZWYsIEFmdGVyVmlld0luaXQsIE9uSW5pdCwgT25EZXN0cm95LCBpbmplY3QsIE9uQ2hhbmdlcyB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IFN1YnNjcmlwdGlvbiB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgY24gfSBmcm9tICcuL3V0aWxzL2NuJztcbmltcG9ydCB7IE1lc3NhZ2VTY3JvbGxlclNlcnZpY2UsIFNjcm9sbFN0YXJ0UG9zaXRpb24gfSBmcm9tICcuL21lc3NhZ2Utc2Nyb2xsZXIuc2VydmljZSc7XG5cbi8qKlxuICogQSBzY3JvbGwgY29udGFpbmVyIGJ1aWx0IGZvciBjb252ZXJzYXRpb25zLCB3aGVyZSBjb250ZW50IHN0cmVhbXMgaW4gd2hpbGVcbiAqIHRoZSByZWFkZXIgaXMgc3RpbGwgcmVhZGluZy5cbiAqXG4gKiBBIHBsYWluIG92ZXJmbG93LXNjcm9sbCBkaXYgZ2V0cyB0aGlzIHdyb25nIGluIGJvdGggZGlyZWN0aW9uczogaXQgZWl0aGVyXG4gKiBwaW5zIHRvIHRoZSBib3R0b20gYW5kIHlhbmtzIHRoZSBwYWdlIG1pZC1zZW50ZW5jZSwgb3IgaXQgbmV2ZXIgZm9sbG93cyBhbmRcbiAqIHRoZSByZWFkZXIgbWlzc2VzIHRoZSByZXBseS4gVGhpcyBoYW5kbGVzIHRoZSBjYXNlcyB0aGF0IGFyZSBlYXN5IHRvIGdldFxuICogd3Jvbmcg4oCUIGZvbGxvd2luZyB0aGUgbGl2ZSBlZGdlIG9ubHkgd2hpbGUgdGhlIHJlYWRlciBpcyBhbHJlYWR5IHRoZXJlLFxuICogb3BlbmluZyBhIHNhdmVkIHRocmVhZCBhdCB0aGUgbmV3ZXN0IHR1cm4gcmF0aGVyIHRoYW4gdGhlIGFic29sdXRlIGVuZCwgYW5kXG4gKiBob2xkaW5nIHBvc2l0aW9uIHdoZW4gb2xkZXIgaGlzdG9yeSBsb2FkcyBpbiBhYm92ZS5cbiAqXG4gKiBgYGBodG1sXG4gKiA8dG9sbGUtbWVzc2FnZS1zY3JvbGxlciBzdGFydFBvc2l0aW9uPVwibGFzdC1hbmNob3JcIj5cbiAqICAgPHRvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItdmlld3BvcnQ+XG4gKiAgICAgPHRvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItY29udGVudD5cbiAqICAgICAgIDxkaXYgdG9sbGVNZXNzYWdlU2Nyb2xsZXJJdGVtIFtzY3JvbGxBbmNob3JdPVwidHVybi5pc05ld1R1cm5cIiAqbmdGb3I9XCJsZXQgdHVybiBvZiB0dXJuc1wiPlxuICogICAgICAgICDigKZcbiAqICAgICAgIDwvZGl2PlxuICogICAgIDwvdG9sbGUtbWVzc2FnZS1zY3JvbGxlci1jb250ZW50PlxuICogICA8L3RvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItdmlld3BvcnQ+XG4gKiAgIDx0b2xsZS1tZXNzYWdlLXNjcm9sbGVyLWJ1dHRvbiAvPlxuICogPC90b2xsZS1tZXNzYWdlLXNjcm9sbGVyPlxuICogYGBgXG4gKiBAbmV3XG4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ3RvbGxlLW1lc3NhZ2Utc2Nyb2xsZXInLFxuICAvLyBUaGUgaG9zdCBzaXRzIGJldHdlZW4gYSBjb25zdW1lci1zaXplZCBwYXJlbnQgYW5kIHRoZSB0ZW1wbGF0ZWQgZmxleFxuICAvLyBmcmFtZSBiZWxvdzsgZGlzcGxheTpibG9jayB3b3VsZCBkcm9wIHRoZSBwYXJlbnQncyBoZWlnaHQgYW5kIHRoZVxuICAvLyBmcmFtZSdzIGBmbGV4LTEgbWluLWgtMGAgd291bGQgaGF2ZSBub3RoaW5nIHRvIHNpemUgYWdhaW5zdC5cbiAgc3R5bGVzOiBbJzpob3N0IHsgZGlzcGxheTogZmxleDsgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsgaGVpZ2h0OiAxMDAlOyBtaW4taGVpZ2h0OiAwOyB9J10sXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtDb21tb25Nb2R1bGVdLFxuICBwcm92aWRlcnM6IFtNZXNzYWdlU2Nyb2xsZXJTZXJ2aWNlXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiBbY2xhc3NdPVwiY24oJ3JlbGF0aXZlIGZsZXggbWluLWgtMCBmbGV4LTEgZmxleC1jb2wgb3ZlcmZsb3ctaGlkZGVuJywgY2xhc3MpXCI+XG4gICAgICA8bmctY29udGVudD48L25nLWNvbnRlbnQ+XG4gICAgPC9kaXY+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlckNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcywgT25Jbml0LCBPbkRlc3Ryb3kge1xuICBwcml2YXRlIHJlYWRvbmx5IGNkciA9IGluamVjdChDaGFuZ2VEZXRlY3RvclJlZik7XG5cbiAgLyoqXG4gICAqIEFuZ3VsYXIgd3JpdGVzIGEgYm91bmQgYGNsYXNzYCBpbnB1dCB0aHJvdWdoIGl0cyBzdHlsaW5nIHBhdGgsIHdoaWNoIGRvZXNcbiAgICogbm90IG1hcmsgYW4gT25QdXNoIGNvbXBvbmVudCBkaXJ0eSDigJQgd2l0aG91dCB0aGlzIGhvb2sgdGhlIGNvbXBvbmVudCBrZWVwc1xuICAgKiByZW5kZXJpbmcgdGhlIGNsYXNzIGl0IHdhcyBib3JuIHdpdGguXG4gICAqL1xuICBuZ09uQ2hhbmdlcygpOiB2b2lkIHtcbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGVyZSB0byBsYW5kIHdoZW4gdGhlIHRyYW5zY3JpcHQgZmlyc3QgcmVuZGVycy4gYGxhc3QtYW5jaG9yYCBvcGVucyBhdCB0aGVcbiAgICogbmV3ZXN0IHR1cm4gc28gYSBsb25nIGFuc3dlciBzdGFydHMgZnJvbSBpdHMgZmlyc3QgbGluZS5cbiAgICogQGRlZmF1bHQgJ2xhc3QtYW5jaG9yJ1xuICAgKi9cbiAgQElucHV0KCkgc3RhcnRQb3NpdGlvbjogU2Nyb2xsU3RhcnRQb3NpdGlvbiA9ICdsYXN0LWFuY2hvcic7XG4gIC8qKlxuICAgKiBQaXhlbHMgb2YgdGhlIHByZXZpb3VzIHR1cm4ga2VwdCB2aXNpYmxlIGFib3ZlIGFuIGFuY2hvcmVkIHR1cm4sIHNvIHRoZVxuICAgKiByZWFkZXIgY2FuIHNlZSB3aGF0IHRoZSByZXBseSBpcyByZXNwb25kaW5nIHRvLiBAZGVmYXVsdCA0OFxuICAgKi9cbiAgQElucHV0KCkgc2Nyb2xsUHJldmlvdXNJdGVtUGVlayA9IDQ4O1xuICAvKipcbiAgICogRGlzdGFuY2UgZnJvbSB0aGUgYm90dG9tLCBpbiBweCwgc3RpbGwgdHJlYXRlZCBhcyB0aGUgbGl2ZSBlZGdlLlxuICAgKiBAZGVmYXVsdCAzMlxuICAgKi9cbiAgQElucHV0KCkgdGhyZXNob2xkID0gMzI7XG4gIC8qKiBFeHRyYSBUYWlsd2luZCBjbGFzc2VzIG1lcmdlZCBvbnRvIHRoZSBmcmFtZSB2aWEgYGNuKClgIChsYXN0LXdpbnMpLiAqL1xuICBASW5wdXQoKSBjbGFzcyA9ICcnO1xuXG4gIC8qKiBFbWl0dGVkIHdoZW4gdGhlIHJlYWRlciBhcnJpdmVzIGF0LCBvciBsZWF2ZXMsIHRoZSBsaXZlIGVkZ2UuICovXG4gIEBPdXRwdXQoKSBhdEJvdHRvbUNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8Ym9vbGVhbj4oKTtcblxuICBwcml2YXRlIHJlYWRvbmx5IHNlcnZpY2UgPSBpbmplY3QoTWVzc2FnZVNjcm9sbGVyU2VydmljZSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgc3ViID0gbmV3IFN1YnNjcmlwdGlvbigpO1xuICBwcm90ZWN0ZWQgY24gPSBjbjtcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2UudGhyZXNob2xkID0gdGhpcy50aHJlc2hvbGQ7XG4gICAgdGhpcy5zdWIuYWRkKHRoaXMuc2VydmljZS5hdEJvdHRvbSQuc3Vic2NyaWJlKCh2KSA9PiB0aGlzLmF0Qm90dG9tQ2hhbmdlLmVtaXQodikpKTtcbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuc3ViLnVuc3Vic2NyaWJlKCk7XG4gIH1cblxuICAvKiogSnVtcHMgdG8gdGhlIG5ld2VzdCBjb250ZW50IGFuZCByZXN1bWVzIGZvbGxvd2luZy4gKi9cbiAgc2Nyb2xsVG9Cb3R0b20oYmVoYXZpb3I6IFNjcm9sbEJlaGF2aW9yID0gJ3Ntb290aCcpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2Uuc2Nyb2xsVG9Cb3R0b20oYmVoYXZpb3IpO1xuICB9XG59XG5cbi8qKlxuICogVGhlIHNjcm9sbGluZyBlbGVtZW50LiBXYXRjaGVzIGl0cyBvd24gc2l6ZSBhbmQgaXRzIGNvbnRlbnQncyBzaXplIHNvIGl0IGNhblxuICogZm9sbG93IHN0cmVhbWVkIHRleHQgYW5kIGhvbGQgcG9zaXRpb24gd2hlbiBoaXN0b3J5IGlzIHByZXBlbmRlZC5cbiAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAndG9sbGUtbWVzc2FnZS1zY3JvbGxlci12aWV3cG9ydCcsXG4gIC8vIFNhbWUgcmVhc29uaW5nIGFzIHRvbGxlLW1lc3NhZ2Utc2Nyb2xsZXI6IHRoaXMgaG9zdCBpcyB0aGUgZmxleCBpdGVtIHRoZVxuICAvLyBzY3JvbGxhYmxlIGlubmVyIGRpdiBzaXplcyBhZ2FpbnN0LCBzbyBpdCBtdXN0IGpvaW4gdGhlIGZsZXggY2hhaW4gaXRzZWxmLlxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBmbGV4OyBmbGV4LWRpcmVjdGlvbjogY29sdW1uOyBmbGV4OiAxIDEgYXV0bzsgbWluLWhlaWdodDogMDsgfSddLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiAjdmlld3BvcnQgW2NsYXNzXT1cImNuKCdtaW4taC0wIGZsZXgtMSBvdmVyZmxvdy15LWF1dG8gb3ZlcnNjcm9sbC1jb250YWluJywgY2xhc3MpXCI+XG4gICAgICA8bmctY29udGVudD48L25nLWNvbnRlbnQ+XG4gICAgPC9kaXY+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlclZpZXdwb3J0Q29tcG9uZW50IGltcGxlbWVudHMgT25DaGFuZ2VzLCBBZnRlclZpZXdJbml0LCBPbkRlc3Ryb3kge1xuICBwcml2YXRlIHJlYWRvbmx5IGNkciA9IGluamVjdChDaGFuZ2VEZXRlY3RvclJlZik7XG5cbiAgLyoqXG4gICAqIEFuZ3VsYXIgd3JpdGVzIGEgYm91bmQgYGNsYXNzYCBpbnB1dCB0aHJvdWdoIGl0cyBzdHlsaW5nIHBhdGgsIHdoaWNoIGRvZXNcbiAgICogbm90IG1hcmsgYW4gT25QdXNoIGNvbXBvbmVudCBkaXJ0eSDigJQgd2l0aG91dCB0aGlzIGhvb2sgdGhlIGNvbXBvbmVudCBrZWVwc1xuICAgKiByZW5kZXJpbmcgdGhlIGNsYXNzIGl0IHdhcyBib3JuIHdpdGguXG4gICAqL1xuICBuZ09uQ2hhbmdlcygpOiB2b2lkIHtcbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIC8qKiBFeHRyYSBUYWlsd2luZCBjbGFzc2VzIG1lcmdlZCBvbnRvIHRoZSB2aWV3cG9ydCB2aWEgYGNuKClgIChsYXN0LXdpbnMpLiAqL1xuICBASW5wdXQoKSBjbGFzcyA9ICcnO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgc2VydmljZSA9IGluamVjdChNZXNzYWdlU2Nyb2xsZXJTZXJ2aWNlKTtcbiAgcHJpdmF0ZSByZWFkb25seSBzY3JvbGxlciA9IGluamVjdChNZXNzYWdlU2Nyb2xsZXJDb21wb25lbnQpO1xuICBwcml2YXRlIHJlYWRvbmx5IGhvc3QgPSBpbmplY3Q8RWxlbWVudFJlZjxIVE1MRWxlbWVudD4+KEVsZW1lbnRSZWYpO1xuICBwcml2YXRlIHJlYWRvbmx5IHpvbmUgPSBpbmplY3QoTmdab25lKTtcblxuICBwcml2YXRlIHZpZXdwb3J0RWw6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgcmVzaXplT2JzZXJ2ZXI/OiBSZXNpemVPYnNlcnZlcjtcbiAgcHJpdmF0ZSBtdXRhdGlvbk9ic2VydmVyPzogTXV0YXRpb25PYnNlcnZlcjtcbiAgcHJpdmF0ZSBvblNjcm9sbCA9ICgpID0+IHRoaXMuc2VydmljZS5tZWFzdXJlKCk7XG4gIC8qKiBzY3JvbGxIZWlnaHQgY2FwdHVyZWQgYmVmb3JlIGVhY2ggRE9NIG11dGF0aW9uLCB0byBkZXRlY3QgcHJlcGVuZHMuICovXG4gIHByaXZhdGUgbGFzdFNjcm9sbEhlaWdodCA9IDA7XG4gIHByaXZhdGUgbGFzdFNjcm9sbFRvcCA9IDA7XG4gIHByb3RlY3RlZCBjbiA9IGNuO1xuXG4gIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IHRoaXMuaG9zdC5uYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3I8SFRNTEVsZW1lbnQ+KCdkaXYnKTtcbiAgICBpZiAoIWVsKSByZXR1cm47XG4gICAgdGhpcy52aWV3cG9ydEVsID0gZWw7XG4gICAgdGhpcy5zZXJ2aWNlLnJlZ2lzdGVyVmlld3BvcnQoZWwpO1xuXG4gICAgLy8gU2Nyb2xsIGZpcmVzIGF0IGZyYW1lIHJhdGU7IGtlZXBpbmcgdGhlIGxpc3RlbmVyIG91dHNpZGUgQW5ndWxhciBtZWFucyB3ZVxuICAgIC8vIG9ubHkgcmUtZW50ZXIgY2hhbmdlIGRldGVjdGlvbiB3aGVuIGEgdHJhY2tlZCBib29sZWFuIGFjdHVhbGx5IGZsaXBzLlxuICAgIHRoaXMuem9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKSA9PiB7XG4gICAgICBlbC5hZGRFdmVudExpc3RlbmVyKCdzY3JvbGwnLCB0aGlzLm9uU2Nyb2xsLCB7IHBhc3NpdmU6IHRydWUgfSk7XG5cbiAgICAgIGlmICh0eXBlb2YgUmVzaXplT2JzZXJ2ZXIgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIgPSBuZXcgUmVzaXplT2JzZXJ2ZXIoKCkgPT4gdGhpcy5zZXJ2aWNlLmhhbmRsZUNvbnRlbnRHcm93dGgoKSk7XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZShlbCk7XG4gICAgICAgIGNvbnN0IGNvbnRlbnQgPSBlbC5maXJzdEVsZW1lbnRDaGlsZDtcbiAgICAgICAgaWYgKGNvbnRlbnQpIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZShjb250ZW50KTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGVvZiBNdXRhdGlvbk9ic2VydmVyICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICB0aGlzLm11dGF0aW9uT2JzZXJ2ZXIgPSBuZXcgTXV0YXRpb25PYnNlcnZlcigoKSA9PiB0aGlzLmFmdGVyTXV0YXRpb24oKSk7XG4gICAgICAgIHRoaXMubXV0YXRpb25PYnNlcnZlci5vYnNlcnZlKGVsLCB7IGNoaWxkTGlzdDogdHJ1ZSwgc3VidHJlZTogdHJ1ZSwgY2hhcmFjdGVyRGF0YTogdHJ1ZSB9KTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHRoaXMubGFzdFNjcm9sbEhlaWdodCA9IGVsLnNjcm9sbEhlaWdodDtcblxuICAgIC8vIFdhaXQgYSBmcmFtZSBzbyBwcm9qZWN0ZWQgY29udGVudCBoYXMgbGFpZCBvdXQgYmVmb3JlIHBvc2l0aW9uaW5nLlxuICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgdGhpcy5zZXJ2aWNlLmFwcGx5U3RhcnRQb3NpdGlvbihcbiAgICAgICAgdGhpcy5zY3JvbGxlci5zdGFydFBvc2l0aW9uLFxuICAgICAgICB0aGlzLnNjcm9sbGVyLnNjcm9sbFByZXZpb3VzSXRlbVBlZWtcbiAgICAgICk7XG4gICAgICB0aGlzLmxhc3RTY3JvbGxIZWlnaHQgPSBlbC5zY3JvbGxIZWlnaHQ7XG4gICAgfSk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnZpZXdwb3J0RWw/LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRoaXMub25TY3JvbGwpO1xuICAgIHRoaXMucmVzaXplT2JzZXJ2ZXI/LmRpc2Nvbm5lY3QoKTtcbiAgICB0aGlzLm11dGF0aW9uT2JzZXJ2ZXI/LmRpc2Nvbm5lY3QoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEaXN0aW5ndWlzaGVzIGNvbnRlbnQgYXBwZW5kZWQgYmVsb3cgKHN0cmVhbSkgZnJvbSBjb250ZW50IGluc2VydGVkIGFib3ZlXG4gICAqIChoaXN0b3J5KS4gR3Jvd3RoIHdoaWxlIHRoZSByZWFkZXIgaGFzIE5PVCBtb3ZlZCBidXQgdGhlIHNjcm9sbGFibGUgYXJlYVxuICAgKiBhYm92ZSB0aGVtIGhhcywgbWVhbnMgYSBwcmVwZW5kIOKAlCByZXN0b3JlIHRoZWlyIHBvc2l0aW9uIHJhdGhlciB0aGFuIGxldFxuICAgKiB0aGUgdmlldyBsdXJjaC5cbiAgICovXG4gIHByaXZhdGUgYWZ0ZXJNdXRhdGlvbigpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IHRoaXMudmlld3BvcnRFbDtcbiAgICBpZiAoIWVsKSByZXR1cm47XG5cbiAgICBjb25zdCBncmV3ID0gZWwuc2Nyb2xsSGVpZ2h0IC0gdGhpcy5sYXN0U2Nyb2xsSGVpZ2h0O1xuICAgIGNvbnN0IHJlYWRlck1vdmVkID0gZWwuc2Nyb2xsVG9wICE9PSB0aGlzLmxhc3RTY3JvbGxUb3A7XG5cbiAgICBpZiAoZ3JldyA+IDAgJiYgIXJlYWRlck1vdmVkICYmICF0aGlzLnNlcnZpY2UuaXNGb2xsb3dpbmcgJiYgZWwuc2Nyb2xsVG9wID4gMCkge1xuICAgICAgdGhpcy5zZXJ2aWNlLnByZXNlcnZlT25QcmVwZW5kKHRoaXMubGFzdFNjcm9sbEhlaWdodCk7XG4gICAgfSBlbHNlIGlmIChncmV3ID4gMCkge1xuICAgICAgdGhpcy5zZXJ2aWNlLmhhbmRsZUNvbnRlbnRHcm93dGgoKTtcbiAgICB9XG5cbiAgICB0aGlzLmxhc3RTY3JvbGxIZWlnaHQgPSBlbC5zY3JvbGxIZWlnaHQ7XG4gICAgdGhpcy5sYXN0U2Nyb2xsVG9wID0gZWwuc2Nyb2xsVG9wO1xuICAgIHRoaXMuc2VydmljZS5tZWFzdXJlKCk7XG4gIH1cbn1cblxuLyoqIFRoZSB0cmFuc2NyaXB0IGl0c2VsZi4gQW5ub3VuY2VkIHBvbGl0ZWx5IHNvIHN0cmVhbWVkIHJlcGxpZXMgcmVhY2ggc2NyZWVuIHJlYWRlcnMuICovXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICd0b2xsZS1tZXNzYWdlLXNjcm9sbGVyLWNvbnRlbnQnLFxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBibG9jazsgfSddLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdlxuICAgICAgcm9sZT1cImxvZ1wiXG4gICAgICBhcmlhLWxpdmU9XCJwb2xpdGVcIlxuICAgICAgYXJpYS1yZWxldmFudD1cImFkZGl0aW9ucyB0ZXh0XCJcbiAgICAgIFtjbGFzc109XCJjbignZmxleCBmbGV4LWNvbCBnYXAtNCBwLTQnLCBjbGFzcylcIlxuICAgID5cbiAgICAgIDxuZy1jb250ZW50PjwvbmctY29udGVudD5cbiAgICA8L2Rpdj5cbiAgYCxcbn0pXG5leHBvcnQgY2xhc3MgTWVzc2FnZVNjcm9sbGVyQ29udGVudENvbXBvbmVudCAgaW1wbGVtZW50cyBPbkNoYW5nZXN7XG4gIHByaXZhdGUgcmVhZG9ubHkgY2RyID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcblxuICAvKipcbiAgICogQW5ndWxhciB3cml0ZXMgYSBib3VuZCBgY2xhc3NgIGlucHV0IHRocm91Z2ggaXRzIHN0eWxpbmcgcGF0aCwgd2hpY2ggZG9lc1xuICAgKiBub3QgbWFyayBhbiBPblB1c2ggY29tcG9uZW50IGRpcnR5IOKAlCB3aXRob3V0IHRoaXMgaG9vayB0aGUgY29tcG9uZW50IGtlZXBzXG4gICAqIHJlbmRlcmluZyB0aGUgY2xhc3MgaXQgd2FzIGJvcm4gd2l0aC5cbiAgICovXG4gIG5nT25DaGFuZ2VzKCk6IHZvaWQge1xuICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICB9XG5cbiAgLyoqIEV4dHJhIFRhaWx3aW5kIGNsYXNzZXMgbWVyZ2VkIG9udG8gdGhlIHRyYW5zY3JpcHQgdmlhIGBjbigpYCAobGFzdC13aW5zKS4gKi9cbiAgQElucHV0KCkgY2xhc3MgPSAnJztcbiAgcHJvdGVjdGVkIGNuID0gY247XG59XG5cbi8qKlxuICogTWFya3Mgb25lIHJvdyBvZiB0aGUgdHJhbnNjcmlwdC4gU2V0IGBzY3JvbGxBbmNob3JgIG9uIHRoZSByb3cgdGhhdCBiZWdpbnMgYVxuICogY29udmVyc2F0aW9uIHR1cm4gc28gYHN0YXJ0UG9zaXRpb249XCJsYXN0LWFuY2hvclwiYCBjYW4gb3BlbiB0aGVyZS5cbiAqL1xuQERpcmVjdGl2ZSh7XG4gIHNlbGVjdG9yOiAnW3RvbGxlTWVzc2FnZVNjcm9sbGVySXRlbV0nLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBob3N0OiB7ICdbYXR0ci5kYXRhLXNjcm9sbC1hbmNob3JdJzogYHNjcm9sbEFuY2hvciA/ICcnIDogbnVsbGAgfSxcbn0pXG5leHBvcnQgY2xhc3MgTWVzc2FnZVNjcm9sbGVySXRlbURpcmVjdGl2ZSBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95IHtcbiAgLyoqIE1hcmtzIHRoaXMgcm93IGFzIHRoZSBzdGFydCBvZiBhIHR1cm4uIEBkZWZhdWx0IGZhbHNlICovXG4gIEBJbnB1dCgpIHNjcm9sbEFuY2hvciA9IGZhbHNlO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgc2VydmljZSA9IGluamVjdChNZXNzYWdlU2Nyb2xsZXJTZXJ2aWNlKTtcbiAgcHJpdmF0ZSByZWFkb25seSBob3N0ID0gaW5qZWN0PEVsZW1lbnRSZWY8SFRNTEVsZW1lbnQ+PihFbGVtZW50UmVmKTtcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5zY3JvbGxBbmNob3IpIHRoaXMuc2VydmljZS5yZWdpc3RlckFuY2hvcih0aGlzLmhvc3QubmF0aXZlRWxlbWVudCk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2UudW5yZWdpc3RlckFuY2hvcih0aGlzLmhvc3QubmF0aXZlRWxlbWVudCk7XG4gIH1cblxuICAvKiogQnJpbmdzIHRoaXMgcm93IGludG8gdmlldyDigJQgdXNlIGZvciBqdW1wLXRvLW1lc3NhZ2UuICovXG4gIHNjcm9sbEludG9WaWV3KGJlaGF2aW9yOiBTY3JvbGxCZWhhdmlvciA9ICdzbW9vdGgnKTogdm9pZCB7XG4gICAgdGhpcy5zZXJ2aWNlLnNjcm9sbFRvSXRlbSh0aGlzLmhvc3QubmF0aXZlRWxlbWVudCwgYmVoYXZpb3IpO1xuICB9XG59XG5cbi8qKiBGbG9hdGluZyBcImp1bXAgdG8gbGF0ZXN0XCIgY29udHJvbC4gSGlkZGVuIHdoaWxlIHRoZSByZWFkZXIgaXMgYWxyZWFkeSBhdCB0aGUgZWRnZS4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ3RvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItYnV0dG9uJyxcbiAgc3R5bGVzOiBbJzpob3N0IHsgZGlzcGxheTogYmxvY2s7IH0nXSxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW0NvbW1vbk1vZHVsZV0sXG4gIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxidXR0b25cbiAgICAgICpuZ0lmPVwiIWF0Qm90dG9tXCJcbiAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgW2F0dHIuYXJpYS1sYWJlbF09XCJhcmlhTGFiZWxcIlxuICAgICAgW2NsYXNzXT1cImNuKFxuICAgICAgICAnYWJzb2x1dGUgYm90dG9tLTQgbGVmdC0xLzIgei0xMCBmbGV4IC10cmFuc2xhdGUteC0xLzIgaXRlbXMtY2VudGVyIGdhcC0xLjUgcm91bmRlZC1mdWxsJyxcbiAgICAgICAgJ2JvcmRlciBib3JkZXItYm9yZGVyIGJnLXBvcG92ZXIgcHgtMyBweS0xLjUgdGV4dC14cyBmb250LW1lZGl1bSB0ZXh0LXBvcG92ZXItZm9yZWdyb3VuZCBzaGFkb3ctbWQnLFxuICAgICAgICAndHJhbnNpdGlvbi1jb2xvcnMgaG92ZXI6YmctYWNjZW50IGhvdmVyOnRleHQtYWNjZW50LWZvcmVncm91bmQnLFxuICAgICAgICAnZm9jdXMtdmlzaWJsZTpvdXRsaW5lLW5vbmUgZm9jdXMtdmlzaWJsZTpyaW5nLTIgZm9jdXMtdmlzaWJsZTpyaW5nLXJpbmcgZm9jdXMtdmlzaWJsZTpyaW5nLW9mZnNldC0yJyxcbiAgICAgICAgY2xhc3NcbiAgICAgIClcIlxuICAgICAgKGNsaWNrKT1cImp1bXAoKVwiXG4gICAgPlxuICAgICAgPGkgY2xhc3M9XCJyaS1hcnJvdy1kb3duLWxpbmVcIiBhcmlhLWhpZGRlbj1cInRydWVcIj48L2k+XG4gICAgICA8bmctY29udGVudD48L25nLWNvbnRlbnQ+XG4gICAgPC9idXR0b24+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlckJ1dHRvbkNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcywgT25Jbml0LCBPbkRlc3Ryb3kge1xuXG4gIC8qKlxuICAgKiBBbmd1bGFyIHdyaXRlcyBhIGJvdW5kIGBjbGFzc2AgaW5wdXQgdGhyb3VnaCBpdHMgc3R5bGluZyBwYXRoLCB3aGljaCBkb2VzXG4gICAqIG5vdCBtYXJrIGFuIE9uUHVzaCBjb21wb25lbnQgZGlydHkg4oCUIHdpdGhvdXQgdGhpcyBob29rIHRoZSBjb21wb25lbnQga2VlcHNcbiAgICogcmVuZGVyaW5nIHRoZSBjbGFzcyBpdCB3YXMgYm9ybiB3aXRoLlxuICAgKi9cbiAgbmdPbkNoYW5nZXMoKTogdm9pZCB7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gIH1cblxuICAvKiogQWNjZXNzaWJsZSBuYW1lIGZvciB0aGUganVtcCBjb250cm9sLiBAZGVmYXVsdCAnSnVtcCB0byBsYXRlc3QgbWVzc2FnZXMnICovXG4gIEBJbnB1dCgpIGFyaWFMYWJlbCA9ICdKdW1wIHRvIGxhdGVzdCBtZXNzYWdlcyc7XG4gIC8qKiBFeHRyYSBUYWlsd2luZCBjbGFzc2VzIG1lcmdlZCBvbnRvIHRoZSBidXR0b24gdmlhIGBjbigpYCAobGFzdC13aW5zKS4gKi9cbiAgQElucHV0KCkgY2xhc3MgPSAnJztcblxuICBwcml2YXRlIHJlYWRvbmx5IHNlcnZpY2UgPSBpbmplY3QoTWVzc2FnZVNjcm9sbGVyU2VydmljZSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgY2RyID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcbiAgcHJpdmF0ZSByZWFkb25seSBzdWIgPSBuZXcgU3Vic2NyaXB0aW9uKCk7XG5cbiAgYXRCb3R0b20gPSB0cnVlO1xuICBwcm90ZWN0ZWQgY24gPSBjbjtcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLnN1Yi5hZGQoXG4gICAgICB0aGlzLnNlcnZpY2UuYXRCb3R0b20kLnN1YnNjcmliZSgodikgPT4ge1xuICAgICAgICB0aGlzLmF0Qm90dG9tID0gdjtcbiAgICAgICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gICAgICB9KVxuICAgICk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnN1Yi51bnN1YnNjcmliZSgpO1xuICB9XG5cbiAganVtcCgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2Uuc2Nyb2xsVG9Cb3R0b20oKTtcbiAgfVxufVxuIl19
@@ -0,0 +1,157 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { BehaviorSubject } from 'rxjs';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Scroll state for one `tolle-message-scroller`, shared with its viewport,
6
+ * items and buttons.
7
+ *
8
+ * The governing rule is: **never move the reader against their intent.** Every
9
+ * automatic scroll here is gated on the reader already being at the live edge.
10
+ * The moment they scroll up, auto-scroll switches off and stays off until they
11
+ * come back down or explicitly jump.
12
+ *
13
+ * Positions are read from and written to the DOM directly rather than mirrored
14
+ * into component state — scroll events fire far too often to run change
15
+ * detection on, and the values would be stale by the time they landed.
16
+ */
17
+ export class MessageScrollerService {
18
+ /** The scrollable element, set by the viewport once it renders. */
19
+ viewport = null;
20
+ /** Rows registered as the start of a conversation turn, in DOM order. */
21
+ anchors = [];
22
+ atBottomSubject = new BehaviorSubject(true);
23
+ /** True while the reader is at (or within `threshold` of) the live edge. */
24
+ atBottom$ = this.atBottomSubject.asObservable();
25
+ followingSubject = new BehaviorSubject(true);
26
+ /**
27
+ * True while new content should pull the view along. Set false the instant
28
+ * the reader scrolls away from the bottom.
29
+ */
30
+ following$ = this.followingSubject.asObservable();
31
+ /** Distance from the bottom, in px, still considered "at the live edge". */
32
+ threshold = 32;
33
+ get atBottom() {
34
+ return this.atBottomSubject.value;
35
+ }
36
+ get isFollowing() {
37
+ return this.followingSubject.value;
38
+ }
39
+ registerViewport(el) {
40
+ this.viewport = el;
41
+ }
42
+ registerAnchor(el) {
43
+ if (!this.anchors.includes(el))
44
+ this.anchors.push(el);
45
+ }
46
+ unregisterAnchor(el) {
47
+ const i = this.anchors.indexOf(el);
48
+ if (i > -1)
49
+ this.anchors.splice(i, 1);
50
+ }
51
+ /**
52
+ * Recomputes whether we're at the live edge. Called from the viewport's
53
+ * scroll handler, which runs outside Angular — this only pushes through the
54
+ * subjects when the boolean actually flips, so we re-enter change detection
55
+ * once per state change instead of once per scroll event.
56
+ */
57
+ measure() {
58
+ const el = this.viewport;
59
+ if (!el)
60
+ return;
61
+ const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
62
+ const atBottom = distance <= this.threshold;
63
+ if (atBottom !== this.atBottomSubject.value)
64
+ this.atBottomSubject.next(atBottom);
65
+ // Leaving the bottom stops following; returning resumes it. This is the
66
+ // whole "don't fight the reader" contract in two lines.
67
+ if (atBottom !== this.followingSubject.value)
68
+ this.followingSubject.next(atBottom);
69
+ }
70
+ /** Jumps to the live edge and resumes following. */
71
+ scrollToBottom(behavior = 'smooth') {
72
+ const el = this.viewport;
73
+ if (!el)
74
+ return;
75
+ el.scrollTo({ top: el.scrollHeight, behavior });
76
+ if (!this.followingSubject.value)
77
+ this.followingSubject.next(true);
78
+ if (!this.atBottomSubject.value)
79
+ this.atBottomSubject.next(true);
80
+ }
81
+ /** Scrolls a specific row into view, e.g. jump-to-message from a search result. */
82
+ scrollToItem(el, behavior = 'smooth') {
83
+ el.scrollIntoView({ behavior, block: 'start' });
84
+ }
85
+ /**
86
+ * Positions the transcript on open. `last-anchor` puts the newest turn at the
87
+ * top of the viewport instead of dumping the reader at the absolute end, so a
88
+ * long streamed answer starts from its beginning — with `peek` px of the
89
+ * previous turn left visible for context.
90
+ */
91
+ applyStartPosition(position, peek) {
92
+ const el = this.viewport;
93
+ if (!el)
94
+ return;
95
+ if (position === 'top') {
96
+ el.scrollTop = 0;
97
+ this.measure();
98
+ return;
99
+ }
100
+ if (position === 'last-anchor') {
101
+ const anchor = this.lastAnchorInDomOrder();
102
+ if (anchor) {
103
+ el.scrollTop = Math.max(0, anchor.offsetTop - el.offsetTop - peek);
104
+ this.measure();
105
+ return;
106
+ }
107
+ // No turns marked — fall through to the bottom.
108
+ }
109
+ el.scrollTop = el.scrollHeight;
110
+ this.measure();
111
+ }
112
+ /**
113
+ * Content grew. Follows only if the reader was already at the edge; uses
114
+ * 'auto' so streamed tokens don't queue a smooth animation per chunk.
115
+ */
116
+ handleContentGrowth() {
117
+ if (!this.followingSubject.value) {
118
+ // Not following, but the scrollbar just changed size — re-measure so the
119
+ // jump button's visibility stays honest.
120
+ this.measure();
121
+ return;
122
+ }
123
+ const el = this.viewport;
124
+ if (!el)
125
+ return;
126
+ el.scrollTop = el.scrollHeight;
127
+ }
128
+ /**
129
+ * History was prepended above the current view. Restores the reader's exact
130
+ * position by re-adding the height the new content introduced — otherwise the
131
+ * transcript visibly lurches downward as older messages load in.
132
+ */
133
+ preserveOnPrepend(previousScrollHeight) {
134
+ const el = this.viewport;
135
+ if (!el)
136
+ return;
137
+ const delta = el.scrollHeight - previousScrollHeight;
138
+ if (delta > 0)
139
+ el.scrollTop += delta;
140
+ }
141
+ /** Current scrollHeight, captured before a prepend so it can be compared after. */
142
+ snapshotScrollHeight() {
143
+ return this.viewport?.scrollHeight ?? 0;
144
+ }
145
+ /** The last registered anchor in document order (registration order can vary). */
146
+ lastAnchorInDomOrder() {
147
+ if (!this.anchors.length)
148
+ return undefined;
149
+ return [...this.anchors].sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1)[this.anchors.length - 1];
150
+ }
151
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
152
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerService });
153
+ }
154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerService, decorators: [{
155
+ type: Injectable
156
+ }] });
157
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZS1zY3JvbGxlci5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvdG9sbGUvc3JjL2xpYi9tZXNzYWdlLXNjcm9sbGVyLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sTUFBTSxDQUFDOztBQUt2Qzs7Ozs7Ozs7Ozs7O0dBWUc7QUFFSCxNQUFNLE9BQU8sc0JBQXNCO0lBQ2pDLG1FQUFtRTtJQUMzRCxRQUFRLEdBQXVCLElBQUksQ0FBQztJQUU1Qyx5RUFBeUU7SUFDakUsT0FBTyxHQUFrQixFQUFFLENBQUM7SUFFbkIsZUFBZSxHQUFHLElBQUksZUFBZSxDQUFVLElBQUksQ0FBQyxDQUFDO0lBQ3RFLDRFQUE0RTtJQUNuRSxTQUFTLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxZQUFZLEVBQUUsQ0FBQztJQUV4QyxnQkFBZ0IsR0FBRyxJQUFJLGVBQWUsQ0FBVSxJQUFJLENBQUMsQ0FBQztJQUN2RTs7O09BR0c7SUFDTSxVQUFVLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFlBQVksRUFBRSxDQUFDO0lBRTNELDRFQUE0RTtJQUM1RSxTQUFTLEdBQUcsRUFBRSxDQUFDO0lBRWYsSUFBSSxRQUFRO1FBQ1YsT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQztJQUNwQyxDQUFDO0lBRUQsSUFBSSxXQUFXO1FBQ2IsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDO0lBQ3JDLENBQUM7SUFFRCxnQkFBZ0IsQ0FBQyxFQUFlO1FBQzlCLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRCxjQUFjLENBQUMsRUFBZTtRQUM1QixJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENB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QTJCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQ3pFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDN0IsQ0FBQzt3R0FqSlUsc0JBQXNCOzRHQUF0QixzQkFBc0I7OzRGQUF0QixzQkFBc0I7a0JBRGxDLFVBQVUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBCZWhhdmlvclN1YmplY3QgfSBmcm9tICdyeGpzJztcblxuLyoqIFdoZXJlIGEgZnJlc2hseS1tb3VudGVkIHNjcm9sbGVyIHNob3VsZCBzdGFydC4gKi9cbmV4cG9ydCB0eXBlIFNjcm9sbFN0YXJ0UG9zaXRpb24gPSAndG9wJyB8ICdib3R0b20nIHwgJ2xhc3QtYW5jaG9yJztcblxuLyoqXG4gKiBTY3JvbGwgc3RhdGUgZm9yIG9uZSBgdG9sbGUtbWVzc2FnZS1zY3JvbGxlcmAsIHNoYXJlZCB3aXRoIGl0cyB2aWV3cG9ydCxcbiAqIGl0ZW1zIGFuZCBidXR0b25zLlxuICpcbiAqIFRoZSBnb3Zlcm5pbmcgcnVsZSBpczogKipuZXZlciBtb3ZlIHRoZSByZWFkZXIgYWdhaW5zdCB0aGVpciBpbnRlbnQuKiogRXZlcnlcbiAqIGF1dG9tYXRpYyBzY3JvbGwgaGVyZSBpcyBnYXRlZCBvbiB0aGUgcmVhZGVyIGFscmVhZHkgYmVpbmcgYXQgdGhlIGxpdmUgZWRnZS5cbiAqIFRoZSBtb21lbnQgdGhleSBzY3JvbGwgdXAsIGF1dG8tc2Nyb2xsIHN3aXRjaGVzIG9mZiBhbmQgc3RheXMgb2ZmIHVudGlsIHRoZXlcbiAqIGNvbWUgYmFjayBkb3duIG9yIGV4cGxpY2l0bHkganVtcC5cbiAqXG4gKiBQb3NpdGlvbnMgYXJlIHJlYWQgZnJvbSBhbmQgd3JpdHRlbiB0byB0aGUgRE9NIGRpcmVjdGx5IHJhdGhlciB0aGFuIG1pcnJvcmVkXG4gKiBpbnRvIGNvbXBvbmVudCBzdGF0ZSDigJQgc2Nyb2xsIGV2ZW50cyBmaXJlIGZhciB0b28gb2Z0ZW4gdG8gcnVuIGNoYW5nZVxuICogZGV0ZWN0aW9uIG9uLCBhbmQgdGhlIHZhbHVlcyB3b3VsZCBiZSBzdGFsZSBieSB0aGUgdGltZSB0aGV5IGxhbmRlZC5cbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlclNlcnZpY2Uge1xuICAvKiogVGhlIHNjcm9sbGFibGUgZWxlbWVudCwgc2V0IGJ5IHRoZSB2aWV3cG9ydCBvbmNlIGl0IHJlbmRlcnMuICovXG4gIHByaXZhdGUgdmlld3BvcnQ6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XG5cbiAgLyoqIFJvd3MgcmVnaXN0ZXJlZCBhcyB0aGUgc3RhcnQgb2YgYSBjb252ZXJzYXRpb24gdHVybiwgaW4gRE9NIG9yZGVyLiAqL1xuICBwcml2YXRlIGFuY2hvcnM6IEhUTUxFbGVtZW50W10gPSBbXTtcblxuICBwcml2YXRlIHJlYWRvbmx5IGF0Qm90dG9tU3ViamVjdCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8Ym9vbGVhbj4odHJ1ZSk7XG4gIC8qKiBUcnVlIHdoaWxlIHRoZSByZWFkZXIgaXMgYXQgKG9yIHdpdGhpbiBgdGhyZXNob2xkYCBvZikgdGhlIGxpdmUgZWRnZS4gKi9cbiAgcmVhZG9ubHkgYXRCb3R0b20kID0gdGhpcy5hdEJvdHRvbVN1YmplY3QuYXNPYnNlcnZhYmxlKCk7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBmb2xsb3dpbmdTdWJqZWN0ID0gbmV3IEJlaGF2aW9yU3ViamVjdDxib29sZWFuPih0cnVlKTtcbiAgLyoqXG4gICAqIFRydWUgd2hpbGUgbmV3IGNvbnRlbnQgc2hvdWxkIHB1bGwgdGhlIHZpZXcgYWxvbmcuIFNldCBmYWxzZSB0aGUgaW5zdGFudFxuICAgKiB0aGUgcmVhZGVyIHNjcm9sbHMgYXdheSBmcm9tIHRoZSBib3R0b20uXG4gICAqL1xuICByZWFkb25seSBmb2xsb3dpbmckID0gdGhpcy5mb2xsb3dpbmdTdWJqZWN0LmFzT2JzZXJ2YWJsZSgpO1xuXG4gIC8qKiBEaXN0YW5jZSBmcm9tIHRoZSBib3R0b20sIGluIHB4LCBzdGlsbCBjb25zaWRlcmVkIFwiYXQgdGhlIGxpdmUgZWRnZVwiLiAqL1xuICB0aHJlc2hvbGQgPSAzMjtcblxuICBnZXQgYXRCb3R0b20oKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuYXRCb3R0b21TdWJqZWN0LnZhbHVlO1xuICB9XG5cbiAgZ2V0IGlzRm9sbG93aW5nKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmZvbGxvd2luZ1N1YmplY3QudmFsdWU7XG4gIH1cblxuICByZWdpc3RlclZpZXdwb3J0KGVsOiBIVE1MRWxlbWVudCk6IHZvaWQge1xuICAgIHRoaXMudmlld3BvcnQgPSBlbDtcbiAgfVxuXG4gIHJlZ2lzdGVyQW5jaG9yKGVsOiBIVE1MRWxlbWVudCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5hbmNob3JzLmluY2x1ZGVzKGVsKSkgdGhpcy5hbmNob3JzLnB1c2goZWwpO1xuICB9XG5cbiAgdW5yZWdpc3RlckFuY2hvcihlbDogSFRNTEVsZW1lbnQpOiB2b2lkIHtcbiAgICBjb25zdCBpID0gdGhpcy5hbmNob3JzLmluZGV4T2YoZWwpO1xuICAgIGlmIChpID4gLTEpIHRoaXMuYW5jaG9ycy5zcGxpY2UoaSwgMSk7XG4gIH1cblxuICAvKipcbiAgICogUmVjb21wdXRlcyB3aGV0aGVyIHdlJ3JlIGF0IHRoZSBsaXZlIGVkZ2UuIENhbGxlZCBmcm9tIHRoZSB2aWV3cG9ydCdzXG4gICAqIHNjcm9sbCBoYW5kbGVyLCB3aGljaCBydW5zIG91dHNpZGUgQW5ndWxhciDigJQgdGhpcyBvbmx5IHB1c2hlcyB0aHJvdWdoIHRoZVxuICAgKiBzdWJqZWN0cyB3aGVuIHRoZSBib29sZWFuIGFjdHVhbGx5IGZsaXBzLCBzbyB3ZSByZS1lbnRlciBjaGFuZ2UgZGV0ZWN0aW9uXG4gICAqIG9uY2UgcGVyIHN0YXRlIGNoYW5nZSBpbnN0ZWFkIG9mIG9uY2UgcGVyIHNjcm9sbCBldmVudC5cbiAgICovXG4gIG1lYXN1cmUoKTogdm9pZCB7XG4gICAgY29uc3QgZWwgPSB0aGlzLnZpZXdwb3J0O1xuICAgIGlmICghZWwpIHJldHVybjtcblxuICAgIGNvbnN0IGRpc3RhbmNlID0gZWwuc2Nyb2xsSGVpZ2h0IC0gZWwuc2Nyb2xsVG9wIC0gZWwuY2xpZW50SGVpZ2h0O1xuICAgIGNvbnN0IGF0Qm90dG9tID0gZGlzdGFuY2UgPD0gdGhpcy50aHJlc2hvbGQ7XG5cbiAgICBpZiAoYXRCb3R0b20gIT09IHRoaXMuYXRCb3R0b21TdWJqZWN0LnZhbHVlKSB0aGlzLmF0Qm90dG9tU3ViamVjdC5uZXh0KGF0Qm90dG9tKTtcblxuICAgIC8vIExlYXZpbmcgdGhlIGJvdHRvbSBzdG9wcyBmb2xsb3dpbmc7IHJldHVybmluZyByZXN1bWVzIGl0LiBUaGlzIGlzIHRoZVxuICAgIC8vIHdob2xlIFwiZG9uJ3QgZmlnaHQgdGhlIHJlYWRlclwiIGNvbnRyYWN0IGluIHR3byBsaW5lcy5cbiAgICBpZiAoYXRCb3R0b20gIT09IHRoaXMuZm9sbG93aW5nU3ViamVjdC52YWx1ZSkgdGhpcy5mb2xsb3dpbmdTdWJqZWN0Lm5leHQoYXRCb3R0b20pO1xuICB9XG5cbiAgLyoqIEp1bXBzIHRvIHRoZSBsaXZlIGVkZ2UgYW5kIHJlc3VtZXMgZm9sbG93aW5nLiAqL1xuICBzY3JvbGxUb0JvdHRvbShiZWhhdmlvcjogU2Nyb2xsQmVoYXZpb3IgPSAnc21vb3RoJyk6IHZvaWQge1xuICAgIGNvbnN0IGVsID0gdGhpcy52aWV3cG9ydDtcbiAgICBpZiAoIWVsKSByZXR1cm47XG4gICAgZWwuc2Nyb2xsVG8oeyB0b3A6IGVsLnNjcm9sbEhlaWdodCwgYmVoYXZpb3IgfSk7XG4gICAgaWYgKCF0aGlzLmZvbGxvd2luZ1N1YmplY3QudmFsdWUpIHRoaXMuZm9sbG93aW5nU3ViamVjdC5uZXh0KHRydWUpO1xuICAgIGlmICghdGhpcy5hdEJvdHRvbVN1YmplY3QudmFsdWUpIHRoaXMuYXRCb3R0b21TdWJqZWN0Lm5leHQodHJ1ZSk7XG4gIH1cblxuICAvKiogU2Nyb2xscyBhIHNwZWNpZmljIHJvdyBpbnRvIHZpZXcsIGUuZy4ganVtcC10by1tZXNzYWdlIGZyb20gYSBzZWFyY2ggcmVzdWx0LiAqL1xuICBzY3JvbGxUb0l0ZW0oZWw6IEhUTUxFbGVtZW50LCBiZWhhdmlvcjogU2Nyb2xsQmVoYXZpb3IgPSAnc21vb3RoJyk6IHZvaWQge1xuICAgIGVsLnNjcm9sbEludG9WaWV3KHsgYmVoYXZpb3IsIGJsb2NrOiAnc3RhcnQnIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFBvc2l0aW9ucyB0aGUgdHJhbnNjcmlwdCBvbiBvcGVuLiBgbGFzdC1hbmNob3JgIHB1dHMgdGhlIG5ld2VzdCB0dXJuIGF0IHRoZVxuICAgKiB0b3Agb2YgdGhlIHZpZXdwb3J0IGluc3RlYWQgb2YgZHVtcGluZyB0aGUgcmVhZGVyIGF0IHRoZSBhYnNvbHV0ZSBlbmQsIHNvIGFcbiAgICogbG9uZyBzdHJlYW1lZCBhbnN3ZXIgc3RhcnRzIGZyb20gaXRzIGJlZ2lubmluZyDigJQgd2l0aCBgcGVla2AgcHggb2YgdGhlXG4gICAqIHByZXZpb3VzIHR1cm4gbGVmdCB2aXNpYmxlIGZvciBjb250ZXh0LlxuICAgKi9cbiAgYXBwbHlTdGFydFBvc2l0aW9uKHBvc2l0aW9uOiBTY3JvbGxTdGFydFBvc2l0aW9uLCBwZWVrOiBudW1iZXIpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IHRoaXMudmlld3BvcnQ7XG4gICAgaWYgKCFlbCkgcmV0dXJuO1xuXG4gICAgaWYgKHBvc2l0aW9uID09PSAndG9wJykge1xuICAgICAgZWwuc2Nyb2xsVG9wID0gMDtcbiAgICAgIHRoaXMubWVhc3VyZSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChwb3NpdGlvbiA9PT0gJ2xhc3QtYW5jaG9yJykge1xuICAgICAgY29uc3QgYW5jaG9yID0gdGhpcy5sYXN0QW5jaG9ySW5Eb21PcmRlcigpO1xuICAgICAgaWYgKGFuY2hvcikge1xuICAgICAgICBlbC5zY3JvbGxUb3AgPSBNYXRoLm1heCgwLCBhbmNob3Iub2Zmc2V0VG9wIC0gZWwub2Zmc2V0VG9wIC0gcGVlayk7XG4gICAgICAgIHRoaXMubWVhc3VyZSgpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICAvLyBObyB0dXJucyBtYXJrZWQg4oCUIGZhbGwgdGhyb3VnaCB0byB0aGUgYm90dG9tLlxuICAgIH1cblxuICAgIGVsLnNjcm9sbFRvcCA9IGVsLnNjcm9sbEhlaWdodDtcbiAgICB0aGlzLm1lYXN1cmUoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb250ZW50IGdyZXcuIEZvbGxvd3Mgb25seSBpZiB0aGUgcmVhZGVyIHdhcyBhbHJlYWR5IGF0IHRoZSBlZGdlOyB1c2VzXG4gICAqICdhdXRvJyBzbyBzdHJlYW1lZCB0b2tlbnMgZG9uJ3QgcXVldWUgYSBzbW9vdGggYW5pbWF0aW9uIHBlciBjaHVuay5cbiAgICovXG4gIGhhbmRsZUNvbnRlbnRHcm93dGgoKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmZvbGxvd2luZ1N1YmplY3QudmFsdWUpIHtcbiAgICAgIC8vIE5vdCBmb2xsb3dpbmcsIGJ1dCB0aGUgc2Nyb2xsYmFyIGp1c3QgY2hhbmdlZCBzaXplIOKAlCByZS1tZWFzdXJlIHNvIHRoZVxuICAgICAgLy8ganVtcCBidXR0b24ncyB2aXNpYmlsaXR5IHN0YXlzIGhvbmVzdC5cbiAgICAgIHRoaXMubWVhc3VyZSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBlbCA9IHRoaXMudmlld3BvcnQ7XG4gICAgaWYgKCFlbCkgcmV0dXJuO1xuICAgIGVsLnNjcm9sbFRvcCA9IGVsLnNjcm9sbEhlaWdodDtcbiAgfVxuXG4gIC8qKlxuICAgKiBIaXN0b3J5IHdhcyBwcmVwZW5kZWQgYWJvdmUgdGhlIGN1cnJlbnQgdmlldy4gUmVzdG9yZXMgdGhlIHJlYWRlcidzIGV4YWN0XG4gICAqIHBvc2l0aW9uIGJ5IHJlLWFkZGluZyB0aGUgaGVpZ2h0IHRoZSBuZXcgY29udGVudCBpbnRyb2R1Y2VkIOKAlCBvdGhlcndpc2UgdGhlXG4gICAqIHRyYW5zY3JpcHQgdmlzaWJseSBsdXJjaGVzIGRvd253YXJkIGFzIG9sZGVyIG1lc3NhZ2VzIGxvYWQgaW4uXG4gICAqL1xuICBwcmVzZXJ2ZU9uUHJlcGVuZChwcmV2aW91c1Njcm9sbEhlaWdodDogbnVtYmVyKTogdm9pZCB7XG4gICAgY29uc3QgZWwgPSB0aGlzLnZpZXdwb3J0O1xuICAgIGlmICghZWwpIHJldHVybjtcbiAgICBjb25zdCBkZWx0YSA9IGVsLnNjcm9sbEhlaWdodCAtIHByZXZpb3VzU2Nyb2xsSGVpZ2h0O1xuICAgIGlmIChkZWx0YSA+IDApIGVsLnNjcm9sbFRvcCArPSBkZWx0YTtcbiAgfVxuXG4gIC8qKiBDdXJyZW50IHNjcm9sbEhlaWdodCwgY2FwdHVyZWQgYmVmb3JlIGEgcHJlcGVuZCBzbyBpdCBjYW4gYmUgY29tcGFyZWQgYWZ0ZXIuICovXG4gIHNuYXBzaG90U2Nyb2xsSGVpZ2h0KCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMudmlld3BvcnQ/LnNjcm9sbEhlaWdodCA/PyAwO1xuICB9XG5cbiAgLyoqIFRoZSBsYXN0IHJlZ2lzdGVyZWQgYW5jaG9yIGluIGRvY3VtZW50IG9yZGVyIChyZWdpc3RyYXRpb24gb3JkZXIgY2FuIHZhcnkpLiAqL1xuICBwcml2YXRlIGxhc3RBbmNob3JJbkRvbU9yZGVyKCk6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkIHtcbiAgICBpZiAoIXRoaXMuYW5jaG9ycy5sZW5ndGgpIHJldHVybiB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIFsuLi50aGlzLmFuY2hvcnNdLnNvcnQoKGEsIGIpID0+XG4gICAgICBhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKGIpICYgTm9kZS5ET0NVTUVOVF9QT1NJVElPTl9GT0xMT1dJTkcgPyAtMSA6IDFcbiAgICApW3RoaXMuYW5jaG9ycy5sZW5ndGggLSAxXTtcbiAgfVxufVxuIl19