@aparte/angular 0.24.0 → 0.25.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.
@@ -11,7 +11,7 @@
11
11
  * a plain host listener when you need `stopPropagation`.
12
12
  */
13
13
  import { ElementRef, EventEmitter } from '@angular/core';
14
- import type { AparteActionClickEventDetail, AparteActionEventDetail, AparteAttachmentPreviewEventDetail, AparteBranchNavigateEventDetail, AparteCompactEventDetail, AparteComposerChangeEventDetail, AparteContextThresholdEventDetail, AparteConversationArchiveDetail, AparteConversationDeleteDetail, AparteConversationPinDetail, AparteConversationRenameDetail, AparteConversationSelectDetail, AparteEditEventDetail, AparteFeedbackEventDetail, AparteLinkClickEventDetail, AparteMessageInfoEventDetail, AparteOptgroupToggleEventDetail, ApartePathChangedEventDetail, AparteRetryEventDetail, AparteScrollRailJumpDetail, AparteSegmentUpdateEventDetail, AparteSelectChangeDetail, AparteSendEventDetail, AparteSidebarToggleDetail, AparteSplitResizeDetail, AparteSuggestionEventDetail } from '@aparte/core';
14
+ import type { AparteActionClickEventDetail, AparteActionEventDetail, AparteAttachmentPreviewEventDetail, AparteBranchNavigateEventDetail, AparteCompactEventDetail, AparteComposerChangeEventDetail, AparteContextThresholdEventDetail, AparteConversationArchiveDetail, AparteConversationDeleteDetail, AparteConversationListItem, AparteConversationPinDetail, AparteConversationRenameDetail, AparteConversationSelectDetail, AparteEditEventDetail, AparteFeedbackEventDetail, AparteLinkClickEventDetail, AparteMessageInfoEventDetail, AparteOptgroupToggleEventDetail, ApartePathChangedEventDetail, AparteRetryEventDetail, AparteScrollRailJumpDetail, AparteSegmentUpdateEventDetail, AparteSelectChangeDetail, AparteSendEventDetail, AparteSidebarToggleDetail, AparteSplitResizeDetail, AparteSuggestionEventDetail } from '@aparte/core';
15
15
  import * as i0 from "@angular/core";
16
16
  /**
17
17
  * Writes one declared attribute onto the host element.
@@ -275,6 +275,8 @@ export declare class AparteConversationListDirective extends AparteElementBase {
275
275
  set loading(v: boolean);
276
276
  /** Let the list finish the gesture: rename, pin, unpin, archive, unarchive and delete are performed on the conversation manager registered with `setConversationManager()`, instead of leaving you six listeners to write. The event still fires first, and `preventDefault()` on it takes the gesture back. Off by default, and selecting stays yours either way. */
277
277
  set manage(v: boolean);
278
+ /** Set the list of conversations to display. Triggers a re-render. */
279
+ set conversations(v: AparteConversationListItem[] | undefined);
278
280
  /** A row's title was activated; the host loads that conversation. */
279
281
  readonly conversationSelect: EventEmitter<AparteConversationSelectDetail>;
280
282
  /** A rename was committed with a new, non-empty title. Cancelable: `preventDefault()` takes the gesture back from `manage`. Angular: this Output emits the detail, so preventDefault() is not on it — bind the DOM event for that: (aparte-conversation-rename)="$event.preventDefault()". */
@@ -297,7 +299,7 @@ export declare class AparteConversationListDirective extends AparteElementBase {
297
299
  protected onConversationUnarchive(e: CustomEvent<AparteConversationArchiveDetail>): void;
298
300
  protected onConversationDelete(e: CustomEvent<AparteConversationDeleteDetail>): void;
299
301
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteConversationListDirective, never>;
300
- static ɵdir: i0.ɵɵDirectiveDeclaration<AparteConversationListDirective, "aparte-conversation-list", never, { "activeId": { "alias": "activeId"; "required": false; }; "flat": { "alias": "flat"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "manage": { "alias": "manage"; "required": false; }; }, { "conversationSelect": "conversationSelect"; "conversationRename": "conversationRename"; "conversationPin": "conversationPin"; "conversationUnpin": "conversationUnpin"; "conversationArchive": "conversationArchive"; "conversationUnarchive": "conversationUnarchive"; "conversationDelete": "conversationDelete"; }, never, never, true, never>;
302
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteConversationListDirective, "aparte-conversation-list", never, { "activeId": { "alias": "activeId"; "required": false; }; "flat": { "alias": "flat"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "manage": { "alias": "manage"; "required": false; }; "conversations": { "alias": "conversations"; "required": false; }; }, { "conversationSelect": "conversationSelect"; "conversationRename": "conversationRename"; "conversationPin": "conversationPin"; "conversationUnpin": "conversationUnpin"; "conversationArchive": "conversationArchive"; "conversationUnarchive": "conversationUnarchive"; "conversationDelete": "conversationDelete"; }, never, never, true, never>;
301
303
  static ngAcceptInputType_flat: unknown;
302
304
  static ngAcceptInputType_loading: unknown;
303
305
  static ngAcceptInputType_manage: unknown;
package/dist/index.d.ts CHANGED
@@ -5,10 +5,12 @@
5
5
  export { provideAparte, APARTE_CONFIG_TOKEN } from './lib/provide-aparte';
6
6
  export type { ProvideAparteOptions, ApartePluginLoader } from './lib/provide-aparte';
7
7
  export { AparteChatComponent } from './lib/aparte-chat.component';
8
+ export { AparteBubbleDirective } from './lib/aparte-bubble.directive';
9
+ export { AparteToolUIDirective, AparteSegmentUIDirective } from './lib/aparte-tool-ui.directive';
8
10
  export { AparteUiComponent } from './lib/aparte-ui.component';
9
11
  export type { AparteUiHandle, AparteUiProps } from './lib/aparte-ui.component';
10
12
  export * from './generated/element.directives';
11
13
  export { AparteAiService, APARTE_CLIENT_OPTIONS } from './lib/aparte-ai.service';
12
14
  export { ConversationManagerService } from './lib/conversation-manager.service';
13
- export type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, AparteChatImperativeApi, } from '@aparte/core';
15
+ export type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, AparteChatImperativeApi, AparteToolCallView, } from '@aparte/core';
14
16
  export { aparteGlobalConfig } from '@aparte/core';
@@ -0,0 +1,27 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import type { AparteMessage } from '@aparte/core';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Marks the template `<aparte-chat>` draws each message with, in place of
6
+ * `<aparte-chat-bubble>`: write it as content of the chat, the message as `$implicit`.
7
+ *
8
+ * ```html
9
+ * <aparte-chat [messages]="messages">
10
+ * <ng-template aparteBubble let-message>
11
+ * <app-bubble [message]="message" />
12
+ * </ng-template>
13
+ * </aparte-chat>
14
+ * ```
15
+ *
16
+ * The same thing as passing a `TemplateRef` to `[bubbleTemplate]`, written where Angular
17
+ * developers write a per-item template. When both are given, `[bubbleTemplate]` wins.
18
+ * The built-in action bar (retry, edit, the version picker) belongs to the native bubble;
19
+ * a template of yours owns whatever it wires.
20
+ */
21
+ export declare class AparteBubbleDirective {
22
+ readonly template: TemplateRef<{
23
+ $implicit: AparteMessage;
24
+ }>;
25
+ static ɵfac: i0.ɵɵFactoryDeclaration<AparteBubbleDirective, never>;
26
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteBubbleDirective, "ng-template[aparteBubble]", never, {}, {}, never, never, true, never>;
27
+ }
@@ -1,10 +1,11 @@
1
- import { ElementRef, OnDestroy, QueryList, AfterViewInit, TemplateRef } from '@angular/core';
1
+ import { AfterViewInit, ElementRef, OnDestroy, QueryList, TemplateRef } from '@angular/core';
2
2
  import { Observable } from 'rxjs';
3
3
  import type { AparteMessage, AparteSegment, AparteSendEventDetail, AparteConfig, AparteActionEventDetail, AparteChatImperativeApi } from '@aparte/core';
4
4
  import { isAwaitingReply } from '@aparte/core';
5
+ import { AparteBubbleDirective } from './aparte-bubble.directive';
5
6
  import * as i0 from "@angular/core";
6
7
  /**
7
- * AparteChatComponent — Angular 19 Wrapper
8
+ * AparteChatComponent — the Angular wrapper (19.2 to 21, zone or zoneless)
8
9
  *
9
10
  * Standalone component wrapping aparté Web Components with Angular Signals.
10
11
  * The streaming / branch-navigation / host-method orchestration (orphan-stream
@@ -15,25 +16,35 @@ import * as i0 from "@angular/core";
15
16
  */
16
17
  export declare class AparteChatComponent implements AfterViewInit, OnDestroy, AparteChatImperativeApi {
17
18
  constructor();
19
+ ngAfterViewInit(): void;
18
20
  private elementRef;
21
+ private readonly _injector;
22
+ private readonly _isBrowser;
19
23
  /**
20
24
  * Template bridge to core's shared `isAwaitingReply` (an Angular template can't
21
25
  * call an imported function). One rule for the four wrappers and core itself,
22
26
  * so "is this bubble in flight" can't drift between them.
23
27
  */
24
28
  readonly awaitingReply: typeof isAwaitingReply;
25
- /** Messages to display */
26
- set messagesInput(val: AparteMessage[]);
29
+ /** Messages to display. Two-way: `[(messages)]`, with `(messagesChange)` reporting the chat's own changes. */
30
+ readonly messagesInput: import("@angular/core").InputSignal<AparteMessage[]>;
31
+ /** Every list the host reported — read only by the render-cache rule below. */
32
+ private readonly _emitted;
33
+ /**
34
+ * Under `[(messages)]` each report comes back through the input — possibly an EARLIER one,
35
+ * after the chat appended without reporting (an optimistic append, or core's append right
36
+ * after a flush) — and an echo is not the parent's word: it keeps the local list. The host
37
+ * tells them apart (`takeEcho`), the rule React, Vue and Svelte apply, so a list the parent
38
+ * restores later (a reopened conversation, an undo) is applied. Before the host exists
39
+ * nothing was reported, so the input is taken as it is.
40
+ */
27
41
  readonly messages: import("@angular/core").WritableSignal<AparteMessage[]>;
28
42
  /** Input placeholder text */
29
- set placeholderInput(val: string);
30
- readonly placeholder: import("@angular/core").WritableSignal<string>;
43
+ readonly placeholder: import("@angular/core").InputSignal<string>;
31
44
  /** Whether the input is disabled */
32
- set disabledInput(val: boolean);
33
- readonly disabled: import("@angular/core").WritableSignal<boolean>;
45
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
34
46
  /** When false, Shift+Enter submits and a bare Enter inserts a newline. */
35
- set submitOnEnterInput(val: boolean);
36
- readonly submitOnEnter: import("@angular/core").WritableSignal<boolean>;
47
+ readonly submitOnEnter: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
37
48
  /**
38
49
  * Opt in to the "centered composer when empty" layout: the composer sits
39
50
  * vertically centered with the `[slot='empty-state']` content above it while
@@ -41,21 +52,27 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
41
52
  * Off by default — additive (adds the `--auto-center` modifier + a
42
53
  * `data-aparte-empty` attribute the shipped `aparte.css` recipe keys off).
43
54
  */
44
- set centerWhenEmptyInput(val: boolean);
45
- readonly centerWhenEmpty: import("@angular/core").WritableSignal<boolean>;
55
+ readonly centerWhenEmpty: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
46
56
  /**
47
57
  * The conversation is on its way: the viewport draws two skeleton turns, the empty
48
58
  * state stays off and the composer is disabled until it lands. The conversation
49
59
  * controller sets this itself while it fetches through your adapter's `loadFull()`;
50
60
  * the input is for a wait of your own. Off by default.
51
61
  */
52
- set loadingInput(val: boolean);
53
- readonly loading: import("@angular/core").WritableSignal<boolean>;
62
+ readonly loading: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
54
63
  /** The controller's wait (a conversation fetched through the adapter). */
55
64
  readonly hostLoading: import("@angular/core").WritableSignal<boolean>;
56
65
  readonly waiting: import("@angular/core").Signal<boolean>;
57
66
  /** Bumped on every config change, so the two locale strings below follow a language switch under OnPush. */
58
67
  private readonly _localeTick;
68
+ /**
69
+ * True until the host is built — on the server always, and in the browser only while Angular
70
+ * hydrates the server's HTML: the host element is then the one the server sent, still carrying
71
+ * the attribute. A client-only render holds nothing, so a component projected into the empty
72
+ * state (built in an @if, after the host binding) talks to live elements in its own
73
+ * ngAfterViewInit, which runs before this component's.
74
+ */
75
+ protected readonly hydrating: import("@angular/core").WritableSignal<boolean>;
59
76
  private _unsubscribeLocale?;
60
77
  get loadingText(): string;
61
78
  /**
@@ -70,11 +87,9 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
70
87
  * selector on Angular alone. This input is that parity. Additive: the recipe's
71
88
  * own classes stay.
72
89
  */
73
- set containerClassInput(val: string);
74
- readonly containerClass: import("@angular/core").WritableSignal<string>;
90
+ readonly containerClass: import("@angular/core").InputSignalWithTransform<string, string>;
75
91
  /** Inline style for the same div — a string or a `{ prop: value }` map. */
76
- set containerStyleInput(val: string | Record<string, string> | null);
77
- readonly containerStyle: import("@angular/core").WritableSignal<string | Record<string, string>>;
92
+ readonly containerStyle: import("@angular/core").InputSignalWithTransform<string | Record<string, string>, string | Record<string, string>>;
78
93
  /**
79
94
  * Overlay the composer on the transcript (the ChatGPT anatomy): the scroll
80
95
  * surface spans the whole column, the scrollbar runs edge to edge, and the
@@ -83,8 +98,7 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
83
98
  * core's recipe and the viewport's measurement both key on the attribute
84
99
  * there. Read when the viewport mounts.
85
100
  */
86
- set overlayComposerInput(val: boolean);
87
- readonly overlayComposer: import("@angular/core").WritableSignal<boolean>;
101
+ readonly overlayComposer: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
88
102
  /**
89
103
  * Add the file picker + chips strip to the default composer shell. **Off by
90
104
  * default**, because the capability needs a host that consumes the files: an
@@ -94,8 +108,7 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
94
108
  * project a `[slot='composer']` (drop `<aparte-composer-add-attachment>` and
95
109
  * `<aparte-composer-attachments>` in it yourself).
96
110
  */
97
- set attachmentsInput(val: boolean);
98
- readonly attachments: import("@angular/core").WritableSignal<boolean>;
111
+ readonly attachments: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
99
112
  /**
100
113
  * Render the `<aparte-elicitation>` presenter inside the host — on by default, as
101
114
  * it is in `<aparte-chat>`: the built-in approval gate and `requestUserInput()`
@@ -103,46 +116,47 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
103
116
  * when you mount a presenter of your own (`setElicitationPresenter`) or place the
104
117
  * element yourself.
105
118
  */
106
- set elicitationInput(val: boolean);
107
- readonly elicitation: import("@angular/core").WritableSignal<boolean>;
108
- /** Whether the assistant is currently typing/streaming */
109
- set isTypingInput(val: boolean);
119
+ readonly elicitation: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
120
+ /** Whether the assistant is currently typing/streaming. The host turns it off on the first token. */
121
+ readonly isTypingInput: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
110
122
  readonly isTyping: import("@angular/core").WritableSignal<boolean>;
111
123
  /** Text to show in the typing status */
112
- set typingTextInput(val: string | undefined);
113
- readonly typingText: import("@angular/core").WritableSignal<string>;
124
+ readonly typingText: import("@angular/core").InputSignal<string>;
114
125
  /** The status line's word: the input, else the locale's, like the rest of the chat. */
115
126
  get typingLabel(): string;
116
- /** Duration in ms to freeze spacer recalculation after a conversation swap. */
117
- layoutTransitionMs: number;
127
+ /** Duration in ms to freeze spacer recalculation after a conversation swap. Read when the host is created. */
128
+ readonly layoutTransitionMs: import("@angular/core").InputSignalWithTransform<number, unknown>;
118
129
  /**
119
130
  * Active conversation id. When provided, the host attaches an
120
131
  * `AparteConversationController` that loads/persists messages via the
121
132
  * `AparteConversationManager` registered in `aparteGlobalConfig`. Setting a different id
122
133
  * mid-stream aborts the previous request; `null` deselects.
123
134
  */
124
- set conversationIdInput(val: string | null | undefined);
135
+ readonly conversationIdInput: import("@angular/core").InputSignal<string>;
125
136
  private _conversationId;
126
137
  /**
127
138
  * Instance {@link AparteConfig} for this chat. When set, aparté components
128
139
  * inside resolve THIS config instead of `aparteGlobalConfig`, so
129
140
  * several independently-configured chats can coexist on one page. Omit for the
130
- * global config. Read once in `ngAfterViewInit` when the host is created.
141
+ * global config. Read once, when the host is created after the first render.
131
142
  */
132
- config?: AparteConfig;
143
+ readonly config: import("@angular/core").InputSignal<AparteConfig>;
133
144
  /**
134
145
  * Render your OWN element per message in place of `<aparte-chat-bubble>`.
135
- * Opt-in — provide a `<ng-template let-message>` and pass its ref. Driven by
136
- * the reactive `messages()` signal, so it updates live during streaming.
137
- * The built-in action bar (retry/edit/branch) belongs to the native bubble;
138
- * a custom bubble owns whatever it wires.
146
+ * Opt-in — provide a `<ng-template let-message>` and pass its ref, or write the
147
+ * template as content with {@link AparteBubbleDirective} (`<ng-template aparteBubble
148
+ * let-message>`); this input wins when both are given. Driven by the reactive
149
+ * `messages()` signal, so it updates live during streaming. The built-in action bar
150
+ * (retry/edit/branch) belongs to the native bubble; a custom bubble owns whatever it wires.
139
151
  * @example
140
152
  * <aparte-chat [bubbleTemplate]="tpl" />
141
153
  * <ng-template #tpl let-message>{{ message.content }}</ng-template>
142
154
  */
143
- bubbleTemplate?: TemplateRef<{
155
+ readonly bubbleTemplate: import("@angular/core").InputSignal<TemplateRef<{
144
156
  $implicit: AparteMessage;
145
- }>;
157
+ }>>;
158
+ /** The `<ng-template aparteBubble>` written as content, if any. */
159
+ readonly bubbleDirective: import("@angular/core").Signal<AparteBubbleDirective>;
146
160
  /**
147
161
  * User submitted a message from the composer. It is **appended to the thread
148
162
  * automatically** (optimistic UI) before this fires — do NOT add it again in
@@ -173,13 +187,15 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
173
187
  readonly isStreaming: import("@angular/core").Signal<boolean>;
174
188
  /** rAF id used to coalesce rapid signal updates during streaming */
175
189
  private _syncRafId;
176
- /** The framework-agnostic chat-host orchestrator (created in ngAfterViewInit). */
190
+ /** The framework-agnostic chat-host orchestrator (created after the first render, in the browser). */
177
191
  private _host?;
178
192
  private _unbindHost?;
179
193
  /** QueryList.changes subscription — released in ngOnDestroy so SPA route
180
194
  * churn doesn't leak a live subscription per mount. */
181
195
  private _bubbleRefsSub?;
182
- ngAfterViewInit(): void;
196
+ /** Set by ngOnDestroy: an afterNextRender that lands after a destroy must not build a host. */
197
+ private _destroyed;
198
+ private _createHost;
183
199
  private readonly _onAction;
184
200
  ngOnDestroy(): void;
185
201
  /** Append a message optimistically — `{ historical: true }` for one restored from your own store. */
@@ -248,14 +264,5 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
248
264
  /** Handle aparte-send event from the composer Web Component. */
249
265
  onAparteSend(event: Event): void;
250
266
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteChatComponent, never>;
251
- static ɵcmp: i0.ɵɵComponentDeclaration<AparteChatComponent, "aparte-chat", never, { "messagesInput": { "alias": "messages"; "required": false; }; "placeholderInput": { "alias": "placeholder"; "required": false; }; "disabledInput": { "alias": "disabled"; "required": false; }; "submitOnEnterInput": { "alias": "submitOnEnter"; "required": false; }; "centerWhenEmptyInput": { "alias": "centerWhenEmpty"; "required": false; }; "loadingInput": { "alias": "loading"; "required": false; }; "containerClassInput": { "alias": "containerClass"; "required": false; }; "containerStyleInput": { "alias": "containerStyle"; "required": false; }; "overlayComposerInput": { "alias": "overlayComposer"; "required": false; }; "attachmentsInput": { "alias": "attachments"; "required": false; }; "elicitationInput": { "alias": "elicitation"; "required": false; }; "isTypingInput": { "alias": "isTyping"; "required": false; }; "typingTextInput": { "alias": "typingText"; "required": false; }; "layoutTransitionMs": { "alias": "layoutTransitionMs"; "required": false; }; "conversationIdInput": { "alias": "conversationId"; "required": false; }; "config": { "alias": "config"; "required": false; }; "bubbleTemplate": { "alias": "bubbleTemplate"; "required": false; }; }, { "messageSent": "messageSent"; "action": "action"; "messagesChange": "messagesChange"; "messageAppended": "messageAppended"; "typingChange": "typingChange"; "conversationCreated": "conversationCreated"; }, never, ["[slot='empty-state']", "[slot='above-composer']", "[slot='toolbar']", "[slot='composer']"], true, never>;
252
- static ngAcceptInputType_disabledInput: unknown;
253
- static ngAcceptInputType_submitOnEnterInput: unknown;
254
- static ngAcceptInputType_centerWhenEmptyInput: unknown;
255
- static ngAcceptInputType_loadingInput: unknown;
256
- static ngAcceptInputType_overlayComposerInput: unknown;
257
- static ngAcceptInputType_attachmentsInput: unknown;
258
- static ngAcceptInputType_elicitationInput: unknown;
259
- static ngAcceptInputType_isTypingInput: unknown;
260
- static ngAcceptInputType_layoutTransitionMs: unknown;
267
+ static ɵcmp: i0.ɵɵComponentDeclaration<AparteChatComponent, "aparte-chat", never, { "messagesInput": { "alias": "messages"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "submitOnEnter": { "alias": "submitOnEnter"; "required": false; "isSignal": true; }; "centerWhenEmpty": { "alias": "centerWhenEmpty"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "containerClass": { "alias": "containerClass"; "required": false; "isSignal": true; }; "containerStyle": { "alias": "containerStyle"; "required": false; "isSignal": true; }; "overlayComposer": { "alias": "overlayComposer"; "required": false; "isSignal": true; }; "attachments": { "alias": "attachments"; "required": false; "isSignal": true; }; "elicitation": { "alias": "elicitation"; "required": false; "isSignal": true; }; "isTypingInput": { "alias": "isTyping"; "required": false; "isSignal": true; }; "typingText": { "alias": "typingText"; "required": false; "isSignal": true; }; "layoutTransitionMs": { "alias": "layoutTransitionMs"; "required": false; "isSignal": true; }; "conversationIdInput": { "alias": "conversationId"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "bubbleTemplate": { "alias": "bubbleTemplate"; "required": false; "isSignal": true; }; }, { "messageSent": "messageSent"; "action": "action"; "messagesChange": "messagesChange"; "messageAppended": "messageAppended"; "typingChange": "typingChange"; "conversationCreated": "conversationCreated"; }, ["bubbleDirective"], ["[slot='empty-state']", "[slot='above-composer']", "[slot='toolbar']", "[slot='composer']"], true, never>;
261
268
  }
@@ -0,0 +1,63 @@
1
+ import { type AparteConfig, type AparteSegmentBase, type AparteToolCallView, type AparteToolMatcher } from '@aparte/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Draws a tool call with your own template, for as long as the template is in the page.
5
+ *
6
+ * ```html
7
+ * <ng-template aparteToolUI="get_weather" let-tool>
8
+ * <app-weather-card [tool]="tool" />
9
+ * </ng-template>
10
+ * ```
11
+ *
12
+ * `let-tool` is an `AparteToolCallView`; its `input` is the model's arguments, `unknown` until
13
+ * the card narrows it — give the card the view (`input.required<AparteToolCallView>()`) and type
14
+ * its arguments there.
15
+ *
16
+ * Every `get_weather` call is drawn with the template, the call's view as `$implicit`, and
17
+ * given back to core's row when the template leaves. The view is created where you declare
18
+ * the template, so the components in it inject what your app provides there. Underneath it is
19
+ * core's `overrideToolRenderer` + `defineMountedToolRenderer`; nothing is declared in a server
20
+ * render, and the cards are drawn in the browser.
21
+ */
22
+ export declare class AparteToolUIDirective {
23
+ /** The tool this draws: a name, a `RegExp`, or `'*'` for every tool. */
24
+ readonly name: import("@angular/core").InputSignal<AparteToolMatcher>;
25
+ /** The config to declare it on. Defaults to the global one. */
26
+ readonly config: import("@angular/core").InputSignal<AparteConfig>;
27
+ private readonly template;
28
+ private readonly container;
29
+ constructor();
30
+ /** Types `let-tool` in a strict template. */
31
+ static ngTemplateContextGuard(_dir: AparteToolUIDirective, _ctx: unknown): _ctx is {
32
+ $implicit: AparteToolCallView;
33
+ };
34
+ static ɵfac: i0.ɵɵFactoryDeclaration<AparteToolUIDirective, never>;
35
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteToolUIDirective, "ng-template[aparteToolUI]", never, { "name": { "alias": "aparteToolUI"; "required": true; "isSignal": true; }; "config": { "alias": "aparteToolUIConfig"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
36
+ }
37
+ /**
38
+ * Draws a segment type with your own template — one your stream emits, or a built-in such as
39
+ * `code` — the segment as `$implicit`. Taking the last declaration off a built-in type gives it
40
+ * back to core's renderer.
41
+ *
42
+ * ```html
43
+ * <ng-template aparteSegmentUI="chart" let-segment>
44
+ * <app-chart [segment]="segment" />
45
+ * </ng-template>
46
+ * ```
47
+ *
48
+ * `let-segment` is the base segment with its own fields: the component it is handed to types it.
49
+ */
50
+ export declare class AparteSegmentUIDirective {
51
+ /** The segment `type` this draws. */
52
+ readonly type: import("@angular/core").InputSignal<string>;
53
+ /** The config to declare it on. Defaults to the global one. */
54
+ readonly config: import("@angular/core").InputSignal<AparteConfig>;
55
+ private readonly template;
56
+ private readonly container;
57
+ constructor();
58
+ static ngTemplateContextGuard(_dir: AparteSegmentUIDirective, _ctx: unknown): _ctx is {
59
+ $implicit: AparteSegmentBase & Record<string, unknown>;
60
+ };
61
+ static ɵfac: i0.ɵɵFactoryDeclaration<AparteSegmentUIDirective, never>;
62
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteSegmentUIDirective, "ng-template[aparteSegmentUI]", never, { "type": { "alias": "aparteSegmentUI"; "required": true; "isSignal": true; }; "config": { "alias": "aparteSegmentUIConfig"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
63
+ }
@@ -76,6 +76,13 @@ export declare class AparteUiComponent implements AfterViewInit, OnChanges, OnDe
76
76
  readonly elementEvent: import("@angular/core").OutputEmitterRef<CustomEvent<any>>;
77
77
  /** The dynamically created Web Component element */
78
78
  private element;
79
+ /**
80
+ * On a server the element is not built: it would be serialized into the HTML without its
81
+ * props or listeners, hydration would keep it, and the browser would append its own beside
82
+ * it — two counters, two pickers. React, Vue and Svelte build theirs in an effect or onMount,
83
+ * which a server never runs.
84
+ */
85
+ private readonly isBrowser;
79
86
  /** Cleanup functions for event listeners */
80
87
  private eventCleanups;
81
88
  /** Joined key of the bound event set, so a fresh inline `[events]` array
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@aparte/angular",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "homepage": "https://apartejs.dev/frameworks/angular/",
5
- "description": "Angular 19 wrapper for aparté — an ergonomic <aparte-chat> component plus services over the framework-agnostic web components.",
5
+ "description": "Angular wrapper for aparté (Angular 19.2 to 21, zone or zoneless) — an ergonomic <aparte-chat> component plus services over the framework-agnostic web components.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "main": "./dist/fesm2022/aparte-angular.mjs",
@@ -28,25 +28,26 @@
28
28
  "node": ">=18"
29
29
  },
30
30
  "peerDependencies": {
31
- "@angular/common": "^19.2.0",
32
- "@angular/core": "^19.2.0",
33
- "@aparte/core": ">=0.24.0 <1.0.0",
31
+ "@angular/common": "^19.2.0 || ^20.0.0 || ^21.0.0",
32
+ "@angular/core": "^19.2.0 || ^20.0.0 || ^21.0.0",
33
+ "@aparte/core": ">=0.25.0 <1.0.0",
34
34
  "rxjs": "^7.8.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@analogjs/vitest-angular": "^2.2.2",
38
37
  "@angular/common": "^19.2.0",
39
38
  "@angular/compiler": "^19.2.0",
40
39
  "@angular/compiler-cli": "^19.2.0",
41
40
  "@angular/core": "^19.2.0",
42
41
  "@angular/platform-browser": "^19.2.0",
42
+ "@angular/platform-server": "^19.2.0",
43
43
  "jsdom": "^22.1.0",
44
44
  "ng-packagr": "^19.2.0",
45
45
  "rxjs": "^7.8.2",
46
46
  "typescript": "^5.4.0",
47
47
  "vite": "^6.0.0",
48
48
  "zone.js": "~0.15.0",
49
- "@aparte/core": "0.24.0"
49
+ "@aparte/core": "0.25.0",
50
+ "@aparte/provider-scenario": "0.25.0"
50
51
  },
51
52
  "keywords": [
52
53
  "angular",