ng-primitives 0.78.0 → 0.80.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.
@@ -0,0 +1,593 @@
1
+ import * as i0 from '@angular/core';
2
+ import { HostListener, Directive, input, booleanAttribute, computed, output, signal, inject, ElementRef, DestroyRef, numberAttribute } from '@angular/core';
3
+ import { injectElementRef, explicitEffect, fromMutationObserver, fromResizeEvent } from 'ng-primitives/internal';
4
+ import { safeTakeUntilDestroyed } from 'ng-primitives/utils';
5
+ import { fromEvent, Subject } from 'rxjs';
6
+ import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
7
+ import { setupButton } from 'ng-primitives/button';
8
+
9
+ /**
10
+ * The state token for the PromptComposer primitive.
11
+ */
12
+ const NgpPromptComposerStateToken = createStateToken('PromptComposer');
13
+ /**
14
+ * Provides the PromptComposer state.
15
+ */
16
+ const providePromptComposerState = createStateProvider(NgpPromptComposerStateToken);
17
+ /**
18
+ * Injects the PromptComposer state.
19
+ */
20
+ const injectPromptComposerState = createStateInjector(NgpPromptComposerStateToken);
21
+ /**
22
+ * The PromptComposer state registration function.
23
+ */
24
+ const promptComposerState = createState(NgpPromptComposerStateToken);
25
+
26
+ /**
27
+ * The state token for the Thread primitive.
28
+ */
29
+ const NgpThreadStateToken = createStateToken('Thread');
30
+ /**
31
+ * Provides the Thread state.
32
+ */
33
+ const provideThreadState = createStateProvider(NgpThreadStateToken);
34
+ /**
35
+ * Injects the Thread state.
36
+ */
37
+ const injectThreadState = createStateInjector(NgpThreadStateToken);
38
+ /**
39
+ * The Thread state registration function.
40
+ */
41
+ const threadState = createState(NgpThreadStateToken);
42
+
43
+ /**
44
+ * The state token for the PromptComposerInput primitive.
45
+ */
46
+ const NgpPromptComposerInputStateToken = createStateToken('PromptComposerInput');
47
+ /**
48
+ * Provides the PromptComposerInput state.
49
+ */
50
+ const providePromptComposerInputState = createStateProvider(NgpPromptComposerInputStateToken);
51
+ /**
52
+ * Injects the PromptComposerInput state.
53
+ */
54
+ const injectPromptComposerInputState = createStateInjector(NgpPromptComposerInputStateToken);
55
+ /**
56
+ * The PromptComposerInput state registration function.
57
+ */
58
+ const promptComposerInputState = createState(NgpPromptComposerInputStateToken);
59
+
60
+ class NgpPromptComposerInput {
61
+ constructor() {
62
+ this.thread = injectThreadState();
63
+ this.composer = injectPromptComposerState();
64
+ this.element = injectElementRef();
65
+ /** The state of the prompt composer input. */
66
+ this.state = promptComposerInputState(this);
67
+ // set the initial state
68
+ this.composer().prompt.set(this.element.nativeElement.value);
69
+ // listen for requests to set the prompt
70
+ this.thread()
71
+ .requestPrompt.pipe(safeTakeUntilDestroyed())
72
+ .subscribe(value => {
73
+ // set the cursor to the end
74
+ this.composer().prompt.set(value);
75
+ this.element.nativeElement.setSelectionRange(value.length, value.length);
76
+ this.element.nativeElement.focus();
77
+ });
78
+ // listen for changes to the text content
79
+ fromEvent(this.element.nativeElement, 'input')
80
+ .pipe(safeTakeUntilDestroyed())
81
+ .subscribe(() => this.composer().prompt.set(this.element.nativeElement.value));
82
+ // any time the prompt changes, update the input value if needed
83
+ explicitEffect([this.composer().prompt], ([prompt]) => (this.element.nativeElement.value = prompt));
84
+ }
85
+ /**
86
+ * If the user presses Enter, the form will be submitted, unless they are holding Shift.
87
+ * This directive automatically handles that behavior.
88
+ */
89
+ onEnterKey(event) {
90
+ if (event.shiftKey) {
91
+ return;
92
+ }
93
+ event.preventDefault();
94
+ // if there is no text content, do nothing
95
+ if (this.element.nativeElement.value.trim().length === 0) {
96
+ return;
97
+ }
98
+ this.composer().submitPrompt();
99
+ }
100
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposerInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
101
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpPromptComposerInput, isStandalone: true, selector: "input[ngpPromptComposerInput], textarea[ngpPromptComposerInput]", host: { listeners: { "keydown.enter": "onEnterKey($event)" } }, providers: [providePromptComposerInputState()], exportAs: ["ngpPromptComposerInput"], ngImport: i0 }); }
102
+ }
103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposerInput, decorators: [{
104
+ type: Directive,
105
+ args: [{
106
+ selector: 'input[ngpPromptComposerInput], textarea[ngpPromptComposerInput]',
107
+ exportAs: 'ngpPromptComposerInput',
108
+ providers: [providePromptComposerInputState()],
109
+ }]
110
+ }], ctorParameters: () => [], propDecorators: { onEnterKey: [{
111
+ type: HostListener,
112
+ args: ['keydown.enter', ['$event']]
113
+ }] } });
114
+
115
+ /**
116
+ * The state token for the PromptComposerSubmit primitive.
117
+ */
118
+ const NgpPromptComposerSubmitStateToken = createStateToken('PromptComposerSubmit');
119
+ /**
120
+ * Provides the PromptComposerSubmit state.
121
+ */
122
+ const providePromptComposerSubmitState = createStateProvider(NgpPromptComposerSubmitStateToken);
123
+ /**
124
+ * Injects the PromptComposerSubmit state.
125
+ */
126
+ const injectPromptComposerSubmitState = createStateInjector(NgpPromptComposerSubmitStateToken);
127
+ /**
128
+ * The PromptComposerSubmit state registration function.
129
+ */
130
+ const promptComposerSubmitState = createState(NgpPromptComposerSubmitStateToken);
131
+
132
+ class NgpPromptComposerSubmit {
133
+ constructor() {
134
+ this.composer = injectPromptComposerState();
135
+ /** Whether the submit button should be disabled */
136
+ this.disabled = input(false, {
137
+ transform: booleanAttribute,
138
+ });
139
+ /** Whether dictation is currently active */
140
+ this.isDictating = computed(() => this.composer().isDictating());
141
+ /** The state of the prompt composer submit. */
142
+ this.state = promptComposerSubmitState(this);
143
+ setupButton({
144
+ disabled: computed(() => this.state.disabled() || this.composer().hasPrompt() === false),
145
+ });
146
+ }
147
+ onClick() {
148
+ this.composer().submitPrompt();
149
+ }
150
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposerSubmit, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
151
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpPromptComposerSubmit, isStandalone: true, selector: "button[ngpPromptComposerSubmit]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" }, properties: { "attr.data-prompt": "composer().hasPrompt() ? \"\" : null", "attr.data-dictating": "isDictating() ? \"\" : null", "attr.data-dictation-supported": "composer().dictationSupported ? \"\" : null" } }, providers: [providePromptComposerSubmitState()], exportAs: ["ngpPromptComposerSubmit"], ngImport: i0 }); }
152
+ }
153
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposerSubmit, decorators: [{
154
+ type: Directive,
155
+ args: [{
156
+ selector: 'button[ngpPromptComposerSubmit]',
157
+ exportAs: 'ngpPromptComposerSubmit',
158
+ providers: [providePromptComposerSubmitState()],
159
+ host: {
160
+ type: 'button',
161
+ '[attr.data-prompt]': 'composer().hasPrompt() ? "" : null',
162
+ '[attr.data-dictating]': 'isDictating() ? "" : null',
163
+ '[attr.data-dictation-supported]': 'composer().dictationSupported ? "" : null',
164
+ },
165
+ }]
166
+ }], ctorParameters: () => [], propDecorators: { onClick: [{
167
+ type: HostListener,
168
+ args: ['click']
169
+ }] } });
170
+
171
+ class NgpPromptComposer {
172
+ constructor() {
173
+ this.thread = injectThreadState();
174
+ /** Emits whenever the user submits the prompt. */
175
+ this.submit = output({ alias: 'ngpPromptComposerSubmit' });
176
+ /** @internal Store the current prompt text. */
177
+ this.prompt = signal('');
178
+ /** @internal Track whether the prompt is currently being dictated */
179
+ this.isDictating = signal(false);
180
+ /** @internal Determine whether the prompt input has content */
181
+ this.hasPrompt = computed(() => this.prompt().trim().length > 0);
182
+ /** Whether dictation is supported by the browser */
183
+ this.dictationSupported = !!(globalThis.SpeechRecognition || globalThis.webkitSpeechRecognition);
184
+ /** The state of the prompt composer. */
185
+ this.state = promptComposerState(this);
186
+ }
187
+ /**
188
+ * @internal
189
+ * Submits the current prompt if there is content, and clears the input.
190
+ */
191
+ submitPrompt() {
192
+ if (this.hasPrompt()) {
193
+ this.submit.emit(this.prompt());
194
+ this.prompt.set('');
195
+ this.thread().scrollToBottom('smooth');
196
+ }
197
+ }
198
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposer, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
199
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpPromptComposer, isStandalone: true, selector: "[ngpPromptComposer]", outputs: { submit: "ngpPromptComposerSubmit" }, host: { properties: { "attr.data-prompt": "hasPrompt() ? \"\" : null", "attr.data-dictating": "isDictating() ? \"\" : null", "attr.data-dictation-supported": "dictationSupported ? \"\" : null" } }, providers: [providePromptComposerState()], exportAs: ["ngpPromptComposer"], ngImport: i0 }); }
200
+ }
201
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposer, decorators: [{
202
+ type: Directive,
203
+ args: [{
204
+ selector: '[ngpPromptComposer]',
205
+ exportAs: 'ngpPromptComposer',
206
+ providers: [providePromptComposerState()],
207
+ host: {
208
+ '[attr.data-prompt]': 'hasPrompt() ? "" : null',
209
+ '[attr.data-dictating]': 'isDictating() ? "" : null',
210
+ '[attr.data-dictation-supported]': 'dictationSupported ? "" : null',
211
+ },
212
+ }]
213
+ }] });
214
+
215
+ /**
216
+ * The state token for the ThreadMessage primitive.
217
+ */
218
+ const NgpThreadMessageStateToken = createStateToken('ThreadMessage');
219
+ /**
220
+ * Provides the ThreadMessage state.
221
+ */
222
+ const provideThreadMessageState = createStateProvider(NgpThreadMessageStateToken);
223
+ /**
224
+ * Injects the ThreadMessage state.
225
+ */
226
+ const injectThreadMessageState = createStateInjector(NgpThreadMessageStateToken);
227
+ /**
228
+ * The ThreadMessage state registration function.
229
+ */
230
+ const threadMessageState = createState(NgpThreadMessageStateToken);
231
+
232
+ class NgpThreadMessage {
233
+ constructor() {
234
+ this.elementRef = inject((ElementRef));
235
+ this.destroyRef = inject(DestroyRef);
236
+ this.thread = injectThreadState();
237
+ /** The state of the thread message. */
238
+ this.state = threadMessageState(this);
239
+ // Watch for content changes (like streaming text) and maintain scroll position
240
+ fromMutationObserver(this.elementRef.nativeElement, {
241
+ childList: true, // Watch for new/removed child nodes
242
+ subtree: true, // Watch changes in all descendants
243
+ characterData: true, // Watch for text content changes in text nodes
244
+ attributes: false, // We don't care about attribute changes for content streaming
245
+ })
246
+ .pipe(safeTakeUntilDestroyed())
247
+ .subscribe(() => {
248
+ // if this is the last message, scroll to bottom
249
+ if (this.thread().isLastMessage(this)) {
250
+ this.thread().scrollToBottom('smooth');
251
+ }
252
+ });
253
+ this.thread().registerMessage(this);
254
+ this.destroyRef.onDestroy(() => this.thread().unregisterMessage(this));
255
+ }
256
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThreadMessage, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
257
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpThreadMessage, isStandalone: true, selector: "[ngpThreadMessage]", providers: [provideThreadMessageState()], exportAs: ["ngpThreadMessage"], ngImport: i0 }); }
258
+ }
259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThreadMessage, decorators: [{
260
+ type: Directive,
261
+ args: [{
262
+ selector: '[ngpThreadMessage]',
263
+ exportAs: 'ngpThreadMessage',
264
+ providers: [provideThreadMessageState()],
265
+ }]
266
+ }], ctorParameters: () => [] });
267
+
268
+ class NgpThread {
269
+ constructor() {
270
+ this.messages = [];
271
+ /** @internal emit event to trigger scrolling to bottom */
272
+ this.scrollRequest = new Subject();
273
+ /** @internal emit event to trigger setting the prompt */
274
+ this.requestPrompt = new Subject();
275
+ /** The state of the thread. */
276
+ this.state = threadState(this);
277
+ }
278
+ scrollToBottom(behavior) {
279
+ this.scrollRequest.next(behavior);
280
+ }
281
+ /** @internal Register a message with the thread */
282
+ registerMessage(message) {
283
+ this.messages.push(message);
284
+ }
285
+ /** @internal Unregister a message from the thread */
286
+ unregisterMessage(message) {
287
+ this.messages = this.messages.filter(m => m !== message);
288
+ }
289
+ /** @internal Determine if the given message is the last message in the thread */
290
+ isLastMessage(message) {
291
+ return this.messages.length > 0 && this.messages[this.messages.length - 1] === message;
292
+ }
293
+ /** @internal Set the prompt text in the associated prompt composer */
294
+ setPrompt(value) {
295
+ this.requestPrompt.next(value);
296
+ }
297
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThread, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
298
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpThread, isStandalone: true, selector: "[ngpThread]", providers: [provideThreadState()], exportAs: ["ngpThread"], ngImport: i0 }); }
299
+ }
300
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThread, decorators: [{
301
+ type: Directive,
302
+ args: [{
303
+ selector: '[ngpThread]',
304
+ exportAs: 'ngpThread',
305
+ providers: [provideThreadState()],
306
+ }]
307
+ }] });
308
+
309
+ /**
310
+ * The state token for the PromptComposerDictation primitive.
311
+ */
312
+ const NgpPromptComposerDictationStateToken = createStateToken('PromptComposerDictation');
313
+ /**
314
+ * Provides the PromptComposerDictation state.
315
+ */
316
+ const providePromptComposerDictationState = createStateProvider(NgpPromptComposerDictationStateToken);
317
+ /**
318
+ * Injects the PromptComposerDictation state.
319
+ */
320
+ const injectPromptComposerDictationState = createStateInjector(NgpPromptComposerDictationStateToken);
321
+ /**
322
+ * The PromptComposerDictation state registration function.
323
+ */
324
+ const promptComposerDictationState = createState(NgpPromptComposerDictationStateToken);
325
+
326
+ class NgpPromptComposerDictation {
327
+ constructor() {
328
+ this.composer = injectPromptComposerState();
329
+ this.recognition = null;
330
+ this.basePrompt = signal(''); // Store the prompt before dictation started
331
+ /** Whether the submit button should be disabled. */
332
+ this.disabled = input(false, {
333
+ transform: booleanAttribute,
334
+ });
335
+ /** Whether dictation is currently active */
336
+ this.isDictating = computed(() => this.composer().isDictating());
337
+ /** The state of the prompt composer. */
338
+ this.state = promptComposerDictationState(this);
339
+ setupButton({
340
+ disabled: computed(() => this.state.disabled() || this.composer().dictationSupported === false),
341
+ });
342
+ this.initializeSpeechRecognition();
343
+ }
344
+ ngOnDestroy() {
345
+ if (this.recognition) {
346
+ this.recognition.stop();
347
+ this.recognition = null;
348
+ }
349
+ }
350
+ onClick() {
351
+ if (!this.recognition) {
352
+ console.warn('Speech recognition is not supported in this browser');
353
+ return;
354
+ }
355
+ if (this.composer().isDictating()) {
356
+ this.stopDictation();
357
+ }
358
+ else {
359
+ this.startDictation();
360
+ }
361
+ }
362
+ onKeydown(event) {
363
+ if (event.key === 'Escape' && this.composer().isDictating()) {
364
+ event.preventDefault();
365
+ this.stopDictation();
366
+ }
367
+ }
368
+ initializeSpeechRecognition() {
369
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
370
+ if (!SpeechRecognition) {
371
+ return;
372
+ }
373
+ this.recognition = new SpeechRecognition();
374
+ this.recognition.continuous = true; // Enable continuous listening
375
+ this.recognition.interimResults = true; // Enable interim results for live updates
376
+ this.recognition.lang = 'en-US';
377
+ this.recognition.onstart = () => {
378
+ this.composer().isDictating.set(true);
379
+ // Store the current prompt as the base
380
+ this.basePrompt.set(this.composer().prompt());
381
+ };
382
+ this.recognition.onresult = (event) => {
383
+ let interimTranscript = '';
384
+ let finalTranscript = '';
385
+ // Process all results
386
+ for (let i = 0; i < event.results.length; i++) {
387
+ const transcript = event.results[i][0].transcript;
388
+ if (event.results[i].isFinal) {
389
+ finalTranscript += transcript;
390
+ }
391
+ else {
392
+ interimTranscript += transcript;
393
+ }
394
+ }
395
+ // Combine base prompt with final transcript and interim transcript
396
+ const baseText = this.basePrompt();
397
+ const separator = baseText ? ' ' : '';
398
+ const newPrompt = baseText + separator + finalTranscript + interimTranscript;
399
+ this.composer().prompt.set(newPrompt.trim());
400
+ };
401
+ this.recognition.onend = () => {
402
+ this.composer().isDictating.set(false);
403
+ };
404
+ this.recognition.onerror = (event) => {
405
+ console.error('Speech recognition error:', event.error);
406
+ this.composer().isDictating.set(false);
407
+ };
408
+ }
409
+ startDictation() {
410
+ if (this.recognition && !this.composer().isDictating()) {
411
+ this.recognition.start();
412
+ }
413
+ }
414
+ stopDictation() {
415
+ if (this.recognition && this.composer().isDictating()) {
416
+ this.recognition.stop();
417
+ }
418
+ }
419
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposerDictation, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
420
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpPromptComposerDictation, isStandalone: true, selector: "button[ngpPromptComposerDictation]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "onClick()", "document:keydown": "onKeydown($event)" }, properties: { "attr.data-dictating": "isDictating() ? \"\" : null", "attr.data-dictation-supported": "composer().dictationSupported ? \"\" : null", "attr.data-prompt": "composer().hasPrompt() ? \"\" : null" } }, providers: [providePromptComposerDictationState()], exportAs: ["ngpPromptComposerDictation"], ngImport: i0 }); }
421
+ }
422
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpPromptComposerDictation, decorators: [{
423
+ type: Directive,
424
+ args: [{
425
+ selector: 'button[ngpPromptComposerDictation]',
426
+ exportAs: 'ngpPromptComposerDictation',
427
+ providers: [providePromptComposerDictationState()],
428
+ host: {
429
+ type: 'button',
430
+ '[attr.data-dictating]': 'isDictating() ? "" : null',
431
+ '[attr.data-dictation-supported]': 'composer().dictationSupported ? "" : null',
432
+ '[attr.data-prompt]': 'composer().hasPrompt() ? "" : null',
433
+ },
434
+ }]
435
+ }], ctorParameters: () => [], propDecorators: { onClick: [{
436
+ type: HostListener,
437
+ args: ['click']
438
+ }], onKeydown: [{
439
+ type: HostListener,
440
+ args: ['document:keydown', ['$event']]
441
+ }] } });
442
+
443
+ /**
444
+ * The state token for the ThreadViewport primitive.
445
+ */
446
+ const NgpThreadViewportStateToken = createStateToken('ThreadViewport');
447
+ /**
448
+ * Provides the ThreadViewport state.
449
+ */
450
+ const provideThreadViewportState = createStateProvider(NgpThreadViewportStateToken);
451
+ /**
452
+ * Injects the ThreadViewport state.
453
+ */
454
+ const injectThreadViewportState = createStateInjector(NgpThreadViewportStateToken);
455
+ /**
456
+ * The ThreadViewport state registration function.
457
+ */
458
+ const threadViewportState = createState(NgpThreadViewportStateToken);
459
+
460
+ class NgpThreadViewport {
461
+ constructor() {
462
+ this.thread = injectThreadState();
463
+ this.elementRef = inject((ElementRef));
464
+ /**
465
+ * The distance in pixels from the bottom of the scrollable container that is considered "at the bottom".
466
+ * When the user scrolls within this threshold, the thread is treated as being at the bottom.
467
+ * This value is used to determine whether automatic scrolling to the bottom should occur,
468
+ * for example when new content is added or the container is resized.
469
+ *
470
+ * @default 70
471
+ */
472
+ this.threshold = input(70, {
473
+ alias: 'ngpThreadViewportThreshold',
474
+ transform: numberAttribute,
475
+ });
476
+ /**
477
+ * Whether the thread should automatically scroll to the bottom when new content is added.
478
+ */
479
+ this.autoScroll = input(true, {
480
+ alias: 'ngpThreadViewportAutoScroll',
481
+ transform: booleanAttribute,
482
+ });
483
+ /** Store the last known scroll position */
484
+ this.lastScrollTop = 0;
485
+ /** Determine if we are at the bottom of the scrollable container (within the threshold) */
486
+ this.isAtBottom = false;
487
+ /** The state of the thread viewport. */
488
+ this.state = threadViewportState(this);
489
+ // listen for scroll requests from the thread
490
+ this.thread()
491
+ .scrollRequest.pipe(safeTakeUntilDestroyed())
492
+ .subscribe(behavior => this.scrollToBottom(behavior));
493
+ fromResizeEvent(this.elementRef.nativeElement)
494
+ .pipe(safeTakeUntilDestroyed())
495
+ .subscribe(() => {
496
+ if (this.isAtBottom) {
497
+ this.scrollToBottom('instant');
498
+ }
499
+ this.onScroll();
500
+ });
501
+ }
502
+ /**
503
+ * Scroll the container to the bottom.
504
+ * @internal
505
+ */
506
+ scrollToBottom(behavior) {
507
+ if (!this.state.autoScroll()) {
508
+ return;
509
+ }
510
+ this.elementRef.nativeElement.scrollTo({
511
+ top: this.elementRef.nativeElement.scrollHeight,
512
+ behavior,
513
+ });
514
+ }
515
+ onScroll() {
516
+ const element = this.elementRef.nativeElement;
517
+ const isAtBottom = element.scrollHeight - element.scrollTop <= element.clientHeight;
518
+ if (isAtBottom || this.lastScrollTop >= element.scrollTop) {
519
+ this.isAtBottom = isAtBottom;
520
+ }
521
+ this.lastScrollTop = element.scrollTop;
522
+ }
523
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThreadViewport, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
524
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpThreadViewport, isStandalone: true, selector: "[ngpThreadViewport]", inputs: { threshold: { classPropertyName: "threshold", publicName: "ngpThreadViewportThreshold", isSignal: true, isRequired: false, transformFunction: null }, autoScroll: { classPropertyName: "autoScroll", publicName: "ngpThreadViewportAutoScroll", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "scroll": "onScroll()" } }, providers: [provideThreadViewportState()], exportAs: ["ngpThreadViewport"], ngImport: i0 }); }
525
+ }
526
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThreadViewport, decorators: [{
527
+ type: Directive,
528
+ args: [{
529
+ selector: '[ngpThreadViewport]',
530
+ exportAs: 'ngpThreadViewport',
531
+ providers: [provideThreadViewportState()],
532
+ }]
533
+ }], ctorParameters: () => [], propDecorators: { onScroll: [{
534
+ type: HostListener,
535
+ args: ['scroll']
536
+ }] } });
537
+
538
+ /**
539
+ * The state token for the ThreadSuggestion primitive.
540
+ */
541
+ const NgpThreadSuggestionStateToken = createStateToken('ThreadSuggestion');
542
+ /**
543
+ * Provides the ThreadSuggestion state.
544
+ */
545
+ const provideThreadSuggestionState = createStateProvider(NgpThreadSuggestionStateToken);
546
+ /**
547
+ * Injects the ThreadSuggestion state.
548
+ */
549
+ const injectThreadSuggestionState = createStateInjector(NgpThreadSuggestionStateToken);
550
+ /**
551
+ * The ThreadSuggestion state registration function.
552
+ */
553
+ const threadSuggestionState = createState(NgpThreadSuggestionStateToken);
554
+
555
+ class NgpThreadSuggestion {
556
+ constructor() {
557
+ this.thread = injectThreadState();
558
+ /** The suggested text to display in the input field. */
559
+ this.suggestion = input('', { alias: 'ngpThreadSuggestion' });
560
+ /** Whether the suggestion should populate the prompt when clicked. */
561
+ this.setPromptOnClick = input(true, {
562
+ alias: 'ngpThreadSuggestionSetPromptOnClick',
563
+ transform: booleanAttribute,
564
+ });
565
+ /** The state of the thread suggestion. */
566
+ this.state = threadSuggestionState(this);
567
+ }
568
+ submitSuggestion() {
569
+ if (this.state.setPromptOnClick() && this.state.suggestion().length > 0) {
570
+ this.thread().setPrompt(this.state.suggestion());
571
+ }
572
+ }
573
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThreadSuggestion, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
574
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpThreadSuggestion, isStandalone: true, selector: "button[ngpThreadSuggestion]", inputs: { suggestion: { classPropertyName: "suggestion", publicName: "ngpThreadSuggestion", isSignal: true, isRequired: false, transformFunction: null }, setPromptOnClick: { classPropertyName: "setPromptOnClick", publicName: "ngpThreadSuggestionSetPromptOnClick", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "submitSuggestion()" } }, providers: [provideThreadSuggestionState()], exportAs: ["ngpThreadSuggestion"], ngImport: i0 }); }
575
+ }
576
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpThreadSuggestion, decorators: [{
577
+ type: Directive,
578
+ args: [{
579
+ selector: 'button[ngpThreadSuggestion]',
580
+ exportAs: 'ngpThreadSuggestion',
581
+ providers: [provideThreadSuggestionState()],
582
+ }]
583
+ }], propDecorators: { submitSuggestion: [{
584
+ type: HostListener,
585
+ args: ['click']
586
+ }] } });
587
+
588
+ /**
589
+ * Generated bundle index. Do not edit.
590
+ */
591
+
592
+ export { NgpPromptComposer, NgpPromptComposerDictation, NgpPromptComposerInput, NgpPromptComposerSubmit, NgpThread, NgpThreadMessage, NgpThreadSuggestion, NgpThreadViewport, injectPromptComposerDictationState, injectPromptComposerInputState, injectPromptComposerState, injectPromptComposerSubmitState, injectThreadMessageState, injectThreadState, injectThreadSuggestionState, injectThreadViewportState, providePromptComposerDictationState, providePromptComposerInputState, providePromptComposerState, providePromptComposerSubmitState, provideThreadMessageState, provideThreadState, provideThreadSuggestionState, provideThreadViewportState };
593
+ //# sourceMappingURL=ng-primitives-ai.mjs.map