@magicx-eng/ai-autocomplete-angular 0.1.0 → 0.2.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.
@@ -4,8 +4,8 @@ import * as i0 from '@angular/core';
4
4
  import { EventEmitter, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, Output, inject, ChangeDetectorRef, NgZone, ViewChild, ElementRef, Renderer2, Directive, PLATFORM_ID, forwardRef, NgModule } from '@angular/core';
5
5
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
6
6
  import { AIAutocomplete, getCursorOffset, extractPlainText, renderEditableContent, plainTextLength, setCursorOffset, ModeController, buildQuery } from '@magicx-eng/ai-autocomplete-vanilla';
7
+ import { BehaviorSubject, combineLatest, map as map$1, distinctUntilChanged, shareReplay, Subject, takeUntil } from 'rxjs';
7
8
  import { map } from 'rxjs/operators';
8
- import { map as map$1, distinctUntilChanged, shareReplay, BehaviorSubject, Subject, takeUntil } from 'rxjs';
9
9
 
10
10
  /**
11
11
  * Internal pill list used by both the dropdown and Tier 1's inline pillbar.
@@ -256,18 +256,38 @@ class AIAutocompleteDropdownComponent {
256
256
  this.showPills = true;
257
257
  /** Fallback skeleton-bar widths used when loading with zero cached options. */
258
258
  this.fallbackSkeletonBarWidths = [159, 119, 164];
259
+ // Bridge for the @Input `showPills` into the reactive pipeline. Without this,
260
+ // changing the input after init wouldn't trigger a new vm emission until the
261
+ // next controller.dropdown$ tick — and we'd lose the showPills update entirely
262
+ // if the controller is idle. Updated from ngOnChanges below.
263
+ this.showPills$ = new BehaviorSubject(true);
259
264
  this.trackByWidth = (_, w) => w;
260
265
  }
261
- /**
262
- * View-model derived from the controller's `dropdown$`. Adds three booleans
263
- * (`showsPills`, `showsOptions`, `showsFallbackSkeleton`, `isVisible`) that
264
- * keep template-level logic to a property read per §19.6.
265
- */
266
- get vm$() {
267
- return this.controller.dropdown$.pipe(map((dd) => {
266
+ ngOnInit() {
267
+ // Seed showPills$ from the input's resolved value at init time.
268
+ this.showPills$.next(this.showPills);
269
+ this.initVm();
270
+ }
271
+ ngOnChanges(changes) {
272
+ if (changes["showPills"]) {
273
+ this.showPills$.next(this.showPills);
274
+ }
275
+ // Re-bind vm$ when the controller @Input is swapped (e.g. a multi-step form
276
+ // toggling between controllers). Without this, vm$ stays subscribed to the
277
+ // previous controller's dropdown$ — rendering stale options, or, if that
278
+ // controller was destroy()-ed, receiving a completion that collapses the
279
+ // dropdown permanently. Skip the first change: ngOnChanges fires before
280
+ // ngOnInit, which owns the initial creation.
281
+ if (changes["controller"] && !changes["controller"].firstChange && this.controller) {
282
+ this.initVm();
283
+ }
284
+ }
285
+ /** Build the view-model stream from the current controller + showPills$. */
286
+ initVm() {
287
+ this.vm$ = combineLatest([this.controller.dropdown$, this.showPills$]).pipe(map(([dd, showPills]) => {
268
288
  var _a, _b;
269
289
  const options = (_b = (_a = dd.suggestions[0]) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : [];
270
- const showsPills = this.showPills && (dd.pills.length > 0 || dd.isLoading);
290
+ const showsPills = showPills && (dd.pills.length > 0 || dd.isLoading);
271
291
  const showsOptions = options.length > 0;
272
292
  const showsFallbackSkeleton = dd.isLoading && !showsOptions;
273
293
  const isVisible = dd.isOpen && (showsOptions || showsPills || dd.isLoading);
@@ -298,10 +318,10 @@ class AIAutocompleteDropdownComponent {
298
318
  }
299
319
  }
300
320
  AIAutocompleteDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
301
- AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n class=\"magicx-aia-dropdown\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }, { kind: "component", type: SuggestionGridComponent, selector: "aia-suggestion-grid", inputs: ["options", "activeIndex", "listboxId", "loading"], outputs: ["select", "highlight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
321
+ AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }, { kind: "component", type: SuggestionGridComponent, selector: "aia-suggestion-grid", inputs: ["options", "activeIndex", "listboxId", "loading"], outputs: ["select", "highlight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
302
322
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, decorators: [{
303
323
  type: Component,
304
- args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [CommonModule, PillListComponent, SuggestionGridComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n class=\"magicx-aia-dropdown\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"] }]
324
+ args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [CommonModule, PillListComponent, SuggestionGridComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"] }]
305
325
  }], propDecorators: { controller: [{
306
326
  type: Input
307
327
  }], showPills: [{
@@ -367,17 +387,37 @@ class AIAutocompleteController {
367
387
  // arrow opens the dropdown from the input.
368
388
  optionsPosition: (_b = opts.optionsPosition) !== null && _b !== void 0 ? _b : "below" }));
369
389
  this.rawState$ = new BehaviorSubject(this.core.getState());
370
- this.unsubscribeCore = this.core.subscribe((state) => {
390
+ // Push the store's *current* state, not the snapshot the listener was
391
+ // handed. The core notifies listeners synchronously, and some actions
392
+ // trigger reentrant `store.set` calls (e.g. typing a non-matching char
393
+ // during re-edit makes the core synchronously exit re-edit and kick off a
394
+ // fetch). Reentrancy makes the outer set's listener loop continue with a
395
+ // now-stale snapshot, so the listener can fire *out of order* — delivering
396
+ // an older state (still in re-edit) after a newer one (re-edit exited).
397
+ // Reading `getState()` at emission time collapses every notification onto
398
+ // the true latest state, so a stale `editingParam` can't resurrect and
399
+ // retrigger the editor's re-edit caret-park (which would yank the caret
400
+ // back to where re-editing started). distinctUntilChanged downstream drops
401
+ // the resulting duplicate emissions. React avoids this because it batches
402
+ // renders and its caret effects are dependency-gated; our subscription is
403
+ // synchronous, so we coalesce here instead.
404
+ this.unsubscribeCore = this.core.subscribe(() => {
371
405
  if (!this.isDestroyed)
372
- this.rawState$.next(state);
406
+ this.rawState$.next(this.core.getState());
373
407
  });
374
408
  // === Per-field observables ===
409
+ // Array-valued fields use shallow equality. The vanilla store spreads on
410
+ // every write (`store.set({ completedParams: [...] })`), so reference
411
+ // equality would re-fire downstream subscribers on every state tick even
412
+ // when the array's contents are unchanged. The other fields are
413
+ // primitives or stable object references, so default identity equality
414
+ // works fine.
375
415
  this.text$ = deriveField(this.rawState$, (s) => s.text);
376
- this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams);
377
- this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions);
378
- this.segments$ = deriveField(this.rawState$, (s) => s.segments);
416
+ this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams, arrEq);
417
+ this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions, arrEq);
418
+ this.segments$ = deriveField(this.rawState$, (s) => s.segments, arrEq);
379
419
  this.newParamId$ = deriveField(this.rawState$, (s) => s.newParamId);
380
- this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions);
420
+ this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions, arrEq);
381
421
  this.activeIndex$ = deriveField(this.rawState$, (s) => s.activeDropdownIndex);
382
422
  this.isLoading$ = deriveField(this.rawState$, computeUiLoading);
383
423
  this.isReady$ = deriveField(this.rawState$, (s) => s.isReady);
@@ -418,68 +458,109 @@ class AIAutocompleteController {
418
458
  return computeState(this.rawState$.value);
419
459
  }
420
460
  // === Action methods (pass-through to the core) ===
461
+ //
462
+ // Every action method begins with an `isDestroyed` guard so that callers
463
+ // who hold a stale reference after `destroy()` don't accidentally trigger
464
+ // work on a torn-down core. The vanilla core does not gate its own methods
465
+ // post-dispose — `reset()`, `handleTextChange()`, and friends would still
466
+ // schedule fetches or fire HTTP requests against an aborted controller,
467
+ // and the responses would have nowhere to surface (rawState$ is already
468
+ // complete). The guard makes the JSDoc-promised "no-op silently"
469
+ // semantics actually true.
421
470
  /** Clear all state, rotate `session_id`, and re-fetch. Call after submit. */
422
471
  reset() {
472
+ if (this.isDestroyed)
473
+ return;
423
474
  this.core.reset();
424
475
  }
425
476
  /** Pick a dropdown option (or replace the re-edited param if in re-edit mode). */
426
477
  selectOption(option) {
478
+ if (this.isDestroyed)
479
+ return;
427
480
  this.core.selectOption(option);
428
481
  }
429
482
  /** Promote the pill at `index` to active (front of the list). */
430
483
  setActivePill(index) {
484
+ if (this.isDestroyed)
485
+ return;
431
486
  this.core.setActivePill(index);
432
487
  }
433
488
  /** Drop the last completed param's "completed" status. Text stays as plain text. */
434
489
  removeLastParam() {
490
+ if (this.isDestroyed)
491
+ return;
435
492
  this.core.removeLastParam();
436
493
  }
437
494
  /** Clear the shimmer animation state for the most-recently-added param. */
438
495
  clearNewParamId() {
496
+ if (this.isDestroyed)
497
+ return;
439
498
  this.core.clearNewParamId();
440
499
  }
441
500
  /** Controlled-mode setter for editor text. */
442
501
  setValue(text) {
502
+ if (this.isDestroyed)
503
+ return;
443
504
  this.core.setValue(text);
444
505
  }
445
506
  /** Controlled-mode setter for completed params. */
446
507
  setCompletedParams(params) {
508
+ if (this.isDestroyed)
509
+ return;
447
510
  this.core.setCompletedParams(params);
448
511
  }
449
512
  /** Forward plain-text input from the consumer's `<textarea>` to the core. */
450
513
  handleTextChange(value) {
514
+ if (this.isDestroyed)
515
+ return;
451
516
  this.core.handleTextChange(value);
452
517
  }
453
518
  /** Forward keyboard events from the consumer's `<textarea>` to the core. */
454
519
  handleKeyDown(event) {
520
+ if (this.isDestroyed)
521
+ return;
455
522
  this.core.handleKeyDown(event);
456
523
  }
457
524
  /** Notify the core of focus state. */
458
525
  setFocused(focused) {
526
+ if (this.isDestroyed)
527
+ return;
459
528
  this.core.setFocused(focused);
460
529
  }
461
530
  /** Enter re-edit mode for the bold param with the given id. */
462
531
  startEditingParam(paramId) {
532
+ if (this.isDestroyed)
533
+ return;
463
534
  this.core.startEditingParam(paramId);
464
535
  }
465
536
  /** Exit re-edit mode without changing the bold param. */
466
537
  exitEditMode() {
538
+ if (this.isDestroyed)
539
+ return;
467
540
  this.core.exitEditMode();
468
541
  }
469
542
  /** Forward post-input caret position (extends the edit tail). */
470
543
  handleCaretAfterInput(offset) {
544
+ if (this.isDestroyed)
545
+ return;
471
546
  this.core.handleCaretAfterInput(offset);
472
547
  }
473
548
  /** Forward selection-only caret moves (may exit re-edit). */
474
549
  handleCaretMove(offset) {
550
+ if (this.isDestroyed)
551
+ return;
475
552
  this.core.handleCaretMove(offset);
476
553
  }
477
554
  /** Replace the bold param being re-edited. Returns true when applied. */
478
555
  replaceEditingRange(replacement) {
556
+ if (this.isDestroyed)
557
+ return false;
479
558
  return this.core.replaceEditingRange(replacement);
480
559
  }
481
560
  /** Set the highlighted option index (typically driven by hover). */
482
561
  setActiveDropdownIndex(index) {
562
+ if (this.isDestroyed)
563
+ return;
483
564
  this.core.setActiveDropdownIndex(index);
484
565
  }
485
566
  // === Lifecycle ===
@@ -491,6 +572,8 @@ class AIAutocompleteController {
491
572
  * (e.g. `text$` instead of `onChange`).
492
573
  */
493
574
  update(opts) {
575
+ if (this.isDestroyed)
576
+ return;
494
577
  this.core.update(opts);
495
578
  }
496
579
  /**
@@ -508,6 +591,24 @@ class AIAutocompleteController {
508
591
  }
509
592
  }
510
593
  // ===== Pure helpers — not part of the public surface =====
594
+ /**
595
+ * Shallow array equality. The vanilla store reassigns array fields on every
596
+ * write (`store.set({ completedParams: [...prev, x] })`), so reference
597
+ * equality alone would re-emit downstream even when the contents are
598
+ * identical. Used for `completedParams$`, `suggestions$`, `suggestionPills$`,
599
+ * and `segments$`.
600
+ */
601
+ function arrEq(a, b) {
602
+ if (a === b)
603
+ return true;
604
+ if (a.length !== b.length)
605
+ return false;
606
+ for (let i = 0; i < a.length; i++) {
607
+ if (a[i] !== b[i])
608
+ return false;
609
+ }
610
+ return true;
611
+ }
511
612
  function computeUiLoading(s) {
512
613
  return s.isLoading && !s.editingParam && !s.inSelectionAnimation;
513
614
  }
@@ -704,7 +805,9 @@ class AIAutocompleteEditorHostDirective {
704
805
  const anchor = sel.anchorNode;
705
806
  const startEl = anchor.nodeType === Node.ELEMENT_NODE ? anchor : anchor.parentElement;
706
807
  const strong = startEl === null || startEl === void 0 ? void 0 : startEl.closest('strong[data-seg="completed"][data-param-id]');
707
- const enclosing = (_a = strong === null || strong === void 0 ? void 0 : strong.dataset.paramId) !== null && _a !== void 0 ? _a : null;
808
+ // `DOMStringMap` is an index signature; bracket notation is required
809
+ // by `noPropertyAccessFromIndexSignature`.
810
+ const enclosing = (_a = strong === null || strong === void 0 ? void 0 : strong.dataset["paramId"]) !== null && _a !== void 0 ? _a : null;
708
811
  if (enclosing && enclosing !== this.editingParamId) {
709
812
  this.zone.run(() => this.controller.startEditingParam(enclosing));
710
813
  return;
@@ -806,13 +909,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
806
909
  type: Input
807
910
  }] } });
808
911
 
809
- function resolveInitialMode(mode) {
810
- if (mode !== "auto")
811
- return mode;
812
- if (typeof window === "undefined")
813
- return "dark";
814
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
815
- }
816
912
  /**
817
913
  * Tier 1 component — `<ai-autocomplete>`. The Angular equivalent of React's
818
914
  * `<AIAutocomplete />`. Owns the contentEditable editor, inline pills, mode
@@ -840,19 +936,26 @@ class AIAutocompleteComponent {
840
936
  this.completedParamsChange = new EventEmitter();
841
937
  this.focused = new EventEmitter();
842
938
  this.blurred = new EventEmitter();
939
+ /**
940
+ * Concrete light/dark value bound to `[attr.data-mode]`. Seeded in ngOnInit
941
+ * so the attribute is present during SSR (where ModeController, gated behind
942
+ * isPlatformBrowser in ngAfterViewInit, never runs). On the browser the
943
+ * ModeController's resolve callback keeps this in sync — so its runtime
944
+ * prefers-color-scheme updates and this binding always agree (no clobber).
945
+ */
843
946
  this.resolvedMode = "dark";
844
947
  this.onChangeFn = noop$1;
845
948
  this.onTouchedFn = noop$1;
846
949
  // §16.9: prefer `inject()` over constructor-parameter DI for components.
847
950
  this.platformId = inject(PLATFORM_ID);
848
- this.el = inject(ElementRef);
849
- this.cdr = inject(ChangeDetectorRef);
850
951
  this.zone = inject(NgZone);
952
+ this.cdr = inject(ChangeDetectorRef);
851
953
  }
852
954
  ngOnInit() {
955
+ // Seed before the browser guard so SSR renders a concrete data-mode.
956
+ this.resolvedMode = resolveInitialMode(this.mode);
853
957
  if (!isPlatformBrowser(this.platformId))
854
958
  return;
855
- this.resolvedMode = resolveInitialMode(this.mode);
856
959
  this.controller = new AIAutocompleteController({
857
960
  apiConfig: this.apiConfig,
858
961
  optionOverrides: this.optionOverrides,
@@ -886,12 +989,26 @@ class AIAutocompleteComponent {
886
989
  setCursorOffset(editor, offset);
887
990
  },
888
991
  });
992
+ if (this.pendingWriteValue !== undefined) {
993
+ this.controller.setValue(this.pendingWriteValue);
994
+ this.pendingWriteValue = undefined;
995
+ }
889
996
  }
890
997
  ngAfterViewInit() {
891
998
  if (!isPlatformBrowser(this.platformId))
892
999
  return;
893
- const container = this.el.nativeElement;
894
- this.modeController = new ModeController(container, this.mode);
1000
+ // ModeController writes `data-mode` to the element it's given. The CSS
1001
+ // selectors are scoped to `.magicx-aia[data-mode="..."]` — which lives on
1002
+ // the inner `<div class="magicx-aia magicx-aia-container" #container>`,
1003
+ // NOT the host `<ai-autocomplete>` element. The resolve callback mirrors
1004
+ // the value into `resolvedMode` so the `[attr.data-mode]` binding stays in
1005
+ // lockstep with ModeController — the binding handles SSR, ModeController
1006
+ // handles runtime prefers-color-scheme switches, and because they always
1007
+ // agree neither clobbers the other.
1008
+ this.modeController = new ModeController(this.containerRef.nativeElement, this.mode, (resolved) => {
1009
+ this.resolvedMode = resolved;
1010
+ this.cdr.markForCheck();
1011
+ });
895
1012
  // Pill-wrap detection — when the pill list wraps below the editor's last
896
1013
  // line, drop its 8px left margin via a data attribute the CSS keys off.
897
1014
  // Mirrors the React component's useLayoutEffect.
@@ -910,20 +1027,24 @@ class AIAutocompleteComponent {
910
1027
  var _a;
911
1028
  if (!this.controller)
912
1029
  return;
1030
+ // `SimpleChanges` is `{ [key: string]: SimpleChange }`, so per
1031
+ // `noPropertyAccessFromIndexSignature` each key lookup uses bracket
1032
+ // notation — this keeps the strictness contract that the rest of the
1033
+ // workspace enforces.
913
1034
  // Controlled-mode propagation
914
- if (changes.value && this.value !== undefined) {
1035
+ if (changes["value"] && this.value !== undefined) {
915
1036
  this.controller.setValue(this.value);
916
1037
  }
917
- if (changes.completedParams && this.completedParams !== undefined) {
1038
+ if (changes["completedParams"] && this.completedParams !== undefined) {
918
1039
  this.controller.setCompletedParams(this.completedParams);
919
1040
  }
920
1041
  // Reactive options
921
- if (changes.apiConfig ||
922
- changes.optionOverrides ||
923
- changes.dropdownTrigger ||
924
- changes.optionsPosition ||
925
- changes.closeDropdownOnBlur ||
926
- changes.showNonTappableOptions) {
1042
+ if (changes["apiConfig"] ||
1043
+ changes["optionOverrides"] ||
1044
+ changes["dropdownTrigger"] ||
1045
+ changes["optionsPosition"] ||
1046
+ changes["closeDropdownOnBlur"] ||
1047
+ changes["showNonTappableOptions"]) {
927
1048
  this.controller.update({
928
1049
  apiConfig: this.apiConfig,
929
1050
  optionOverrides: this.optionOverrides,
@@ -933,9 +1054,12 @@ class AIAutocompleteComponent {
933
1054
  showNonTappableOptions: this.showNonTappableOptions,
934
1055
  });
935
1056
  }
936
- if (changes.mode) {
937
- (_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(this.mode);
1057
+ if (changes["mode"]) {
1058
+ // Keep the binding correct even when ModeController doesn't exist yet
1059
+ // (SSR / pre-ngAfterViewInit). On the browser, setMode triggers the
1060
+ // resolve callback which sets the same value.
938
1061
  this.resolvedMode = resolveInitialMode(this.mode);
1062
+ (_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(this.mode);
939
1063
  }
940
1064
  }
941
1065
  ngOnDestroy() {
@@ -964,8 +1088,8 @@ class AIAutocompleteComponent {
964
1088
  setMode(mode) {
965
1089
  var _a;
966
1090
  this.mode = mode;
967
- (_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
968
1091
  this.resolvedMode = resolveInitialMode(mode);
1092
+ (_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
969
1093
  this.cdr.markForCheck();
970
1094
  }
971
1095
  // === Template helpers ===
@@ -1019,9 +1143,16 @@ class AIAutocompleteComponent {
1019
1143
  }
1020
1144
  // === ControlValueAccessor ===
1021
1145
  writeValue(value) {
1022
- if (!this.controller)
1146
+ const next = value !== null && value !== void 0 ? value : "";
1147
+ if (!this.controller) {
1148
+ // Reactive Forms can call writeValue() before ngOnInit creates the
1149
+ // controller (dynamic form construction, or a FormControl that already
1150
+ // had a value at directive-creation time). Buffer the value; ngOnInit
1151
+ // flushes it as soon as the controller exists.
1152
+ this.pendingWriteValue = next;
1023
1153
  return;
1024
- this.controller.setValue(value !== null && value !== void 0 ? value : "");
1154
+ }
1155
+ this.controller.setValue(next);
1025
1156
  }
1026
1157
  registerOnChange(fn) {
1027
1158
  this.onChangeFn = fn;
@@ -1103,7 +1234,7 @@ AIAutocompleteComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0"
1103
1234
  useExisting: forwardRef(() => AIAutocompleteComponent),
1104
1235
  multi: true,
1105
1236
  },
1106
- ], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: AIAutocompleteDropdownComponent, selector: "ai-autocomplete-dropdown", inputs: ["controller", "showPills", "className"] }, { kind: "directive", type: AIAutocompleteEditorHostDirective, selector: "[aiaEditorHost]", inputs: ["aiaEditorHost", "autoFocus"] }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1237
+ ], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: AIAutocompleteDropdownComponent, selector: "ai-autocomplete-dropdown", inputs: ["controller", "showPills", "className"] }, { kind: "directive", type: AIAutocompleteEditorHostDirective, selector: "[aiaEditorHost]", inputs: ["aiaEditorHost", "autoFocus"] }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1107
1238
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, decorators: [{
1108
1239
  type: Component,
1109
1240
  args: [{ selector: "ai-autocomplete", standalone: true, imports: [
@@ -1118,7 +1249,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1118
1249
  useExisting: forwardRef(() => AIAutocompleteComponent),
1119
1250
  multi: true,
1120
1251
  },
1121
- ], template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"] }]
1252
+ ], template: "<div\n #container\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"] }]
1122
1253
  }], propDecorators: { apiConfig: [{
1123
1254
  type: Input
1124
1255
  }], optionOverrides: [{
@@ -1161,6 +1292,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1161
1292
  type: Output
1162
1293
  }], blurred: [{
1163
1294
  type: Output
1295
+ }], containerRef: [{
1296
+ type: ViewChild,
1297
+ args: ["container", { static: true }]
1164
1298
  }], editorRef: [{
1165
1299
  type: ViewChild,
1166
1300
  args: ["editor", { static: false }]
@@ -1171,6 +1305,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1171
1305
  function noop$1() {
1172
1306
  // Default no-op for CVA callbacks.
1173
1307
  }
1308
+ /**
1309
+ * Resolve a concrete light/dark value from the configured mode. Used to seed
1310
+ * the `data-mode` attribute synchronously (including during SSR, where
1311
+ * ModeController never runs). On the server `window` is undefined → defaults
1312
+ * to "dark" (matches the vanilla/React behavior).
1313
+ */
1314
+ function resolveInitialMode(mode) {
1315
+ if (mode !== "auto")
1316
+ return mode;
1317
+ if (typeof window === "undefined")
1318
+ return "dark";
1319
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1320
+ }
1174
1321
 
1175
1322
  /**
1176
1323
  * Tier 2 input binder. Attach to a `<textarea>` or `<input>` and pass an
@@ -1204,11 +1351,23 @@ class AIAutocompleteInputDirective {
1204
1351
  this.destroy$ = new Subject();
1205
1352
  this.onChangeFn = noop;
1206
1353
  this.onTouchedFn = noop;
1354
+ // Timestamp of the last `input` event. A `keyup`/`select` that fires within
1355
+ // this window is the tail of a keystroke, not a deliberate caret move, so we
1356
+ // skip the caret-move handler there (mirrors the editor host's selectionchange
1357
+ // guard). performance.now() is monotonic; 0 means "no input yet".
1358
+ this.lastInputAt = 0;
1359
+ // Tracks the most-recently-promoted param so we only reposition the caret
1360
+ // once per option selection (mirrors the editor host's post-promote refocus).
1361
+ this.lastSeenParamId = "";
1207
1362
  }
1208
1363
  ngOnInit() {
1209
1364
  if (!this.controller) {
1210
1365
  throw new Error('[aiaInput] requires a controller — use `[aiaInput]="myController"`.');
1211
1366
  }
1367
+ if (this.pendingWriteValue !== undefined) {
1368
+ this.controller.setValue(this.pendingWriteValue);
1369
+ this.pendingWriteValue = undefined;
1370
+ }
1212
1371
  const el = this.el.nativeElement;
1213
1372
  this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
1214
1373
  // Sync editor text → DOM. Skips the write when values already match, so
@@ -1244,6 +1403,36 @@ class AIAutocompleteInputDirective {
1244
1403
  this.renderer.removeAttribute(el, "aria-activedescendant");
1245
1404
  }
1246
1405
  });
1406
+ // After an option selection promotes a param, the core appends its text and
1407
+ // stamps `caretOffset` at the end of the inserted text. The text$ subscriber
1408
+ // above writes the new value but *restores the pre-write caret* (correct for
1409
+ // typing echoes, wrong here), so we move the caret to the promoted position.
1410
+ // Keyed on newParamId so it fires once per selection, not on every tick.
1411
+ // Registered after text$ so the new value is in the DOM before we seek.
1412
+ this.controller.newParamId$.pipe(takeUntil(this.destroy$)).subscribe((id) => {
1413
+ var _a;
1414
+ const current = id !== null && id !== void 0 ? id : "";
1415
+ if (current && current !== this.lastSeenParamId) {
1416
+ this.lastSeenParamId = current;
1417
+ const offset = (_a = this.controller.getState().caretOffset) !== null && _a !== void 0 ? _a : el.value.length;
1418
+ const clamped = Math.min(Math.max(offset, 0), el.value.length);
1419
+ // Only grab focus if we don't already have it — don't yank focus back
1420
+ // from wherever the user moved it after selecting (e.g. a host-app
1421
+ // button). setSelectionRange still positions the caret regardless.
1422
+ if (typeof document !== "undefined" && document.activeElement !== el) {
1423
+ el.focus();
1424
+ }
1425
+ try {
1426
+ el.setSelectionRange(clamped, clamped);
1427
+ }
1428
+ catch (_b) {
1429
+ // Some input types (e.g. "number") don't support setSelectionRange.
1430
+ }
1431
+ }
1432
+ else if (!current) {
1433
+ this.lastSeenParamId = "";
1434
+ }
1435
+ });
1247
1436
  }
1248
1437
  ngOnDestroy() {
1249
1438
  this.destroy$.next();
@@ -1257,6 +1446,12 @@ class AIAutocompleteInputDirective {
1257
1446
  * we don't disrupt multi-keystroke input methods (Pinyin, Hangul, etc.).
1258
1447
  */
1259
1448
  onInput(event) {
1449
+ var _a;
1450
+ // Stamp the input time before anything else. The autocapitalize path below
1451
+ // calls setSelectionRange, which can synchronously fire a `select` event →
1452
+ // onCaretMove; stamping first ensures that event falls inside the guard
1453
+ // window and is treated as part of this keystroke, not a caret move.
1454
+ this.lastInputAt = performance.now();
1260
1455
  const target = event.target;
1261
1456
  const raw = target.value;
1262
1457
  const isComposing = event.isComposing === true;
@@ -1271,17 +1466,43 @@ class AIAutocompleteInputDirective {
1271
1466
  try {
1272
1467
  target.setSelectionRange(start, end);
1273
1468
  }
1274
- catch (_a) {
1469
+ catch (_b) {
1275
1470
  // See note in ngOnInit's text$ subscriber.
1276
1471
  }
1277
1472
  }
1278
1473
  }
1279
1474
  this.controller.handleTextChange(next);
1475
+ // Sync the real caret into the core. The dropdown's auto-trigger gate only
1476
+ // opens when the caret is at the end of the text; without this, the core's
1477
+ // `caretOffset` stays pinned at whatever a prior promote/select set it to
1478
+ // and goes stale as the user keeps typing, wedging the dropdown closed.
1479
+ // selectionStart is null on some input types (number/email/color); treat
1480
+ // that as "caret at end" rather than feeding null into the core.
1481
+ this.controller.handleCaretAfterInput((_a = target.selectionStart) !== null && _a !== void 0 ? _a : target.value.length);
1280
1482
  this.onChangeFn(next);
1281
1483
  }
1282
1484
  onKeyDown(event) {
1283
1485
  this.controller.handleKeyDown(event);
1284
1486
  }
1487
+ /**
1488
+ * Caret moved by something other than typing — arrow keys, Home/End, or a
1489
+ * mouse click/drag (keyup + mouseup). Forward the new offset so the core can
1490
+ * re-evaluate the caret-at-end dropdown gate (and exit re-edit if the caret
1491
+ * left the region). We deliberately do NOT listen for the `select` event:
1492
+ * programmatic `setSelectionRange` calls (the text$ writeback, the post-select
1493
+ * caret reposition) fire it in jsdom/some browsers, which would re-enter here
1494
+ * and clobber `caretOffset`. `keyup`/`mouseup` only fire for real user input.
1495
+ * Skipped right after an `input` event, where the caret is already handled by
1496
+ * `handleCaretAfterInput` above.
1497
+ */
1498
+ onCaretMove() {
1499
+ var _a;
1500
+ if (performance.now() - this.lastInputAt < 50)
1501
+ return;
1502
+ const el = this.el.nativeElement;
1503
+ // selectionStart is null on some input types — treat as caret at end.
1504
+ this.controller.handleCaretMove((_a = el.selectionStart) !== null && _a !== void 0 ? _a : el.value.length);
1505
+ }
1285
1506
  onFocus() {
1286
1507
  this.controller.setFocused(true);
1287
1508
  }
@@ -1293,11 +1514,17 @@ class AIAutocompleteInputDirective {
1293
1514
  writeValue(value) {
1294
1515
  const next = value !== null && value !== void 0 ? value : "";
1295
1516
  this.renderer.setProperty(this.el.nativeElement, "value", next);
1296
- // The controller may be undefined transiently if Angular calls
1297
- // writeValue() before ngOnInit (rare but possible during dynamic form
1298
- // construction). Guard so we don't throw in that window.
1299
- if (this.controller)
1517
+ // If Angular calls writeValue() before ngOnInit (possible during dynamic
1518
+ // form construction), queue the value and let ngOnInit flush it. We
1519
+ // can't push it into the controller now because either the controller
1520
+ // binding hasn't resolved yet, or text$'s initial subscription would
1521
+ // overwrite it moments later.
1522
+ if (this.controller) {
1300
1523
  this.controller.setValue(next);
1524
+ }
1525
+ else {
1526
+ this.pendingWriteValue = next;
1527
+ }
1301
1528
  }
1302
1529
  registerOnChange(fn) {
1303
1530
  this.onChangeFn = fn;
@@ -1310,7 +1537,7 @@ class AIAutocompleteInputDirective {
1310
1537
  }
1311
1538
  }
1312
1539
  AIAutocompleteInputDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1313
- AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
1540
+ AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "keyup": "onCaretMove()", "mouseup": "onCaretMove()", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
1314
1541
  {
1315
1542
  provide: NG_VALUE_ACCESSOR,
1316
1543
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
@@ -1328,6 +1555,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1328
1555
  "aria-autocomplete": "list",
1329
1556
  "(input)": "onInput($event)",
1330
1557
  "(keydown)": "onKeyDown($event)",
1558
+ "(keyup)": "onCaretMove()",
1559
+ "(mouseup)": "onCaretMove()",
1331
1560
  "(focus)": "onFocus()",
1332
1561
  "(blur)": "onBlur()",
1333
1562
  },