@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.
@@ -253,17 +253,37 @@ class AIAutocompleteDropdownComponent {
253
253
  this.showPills = true;
254
254
  /** Fallback skeleton-bar widths used when loading with zero cached options. */
255
255
  this.fallbackSkeletonBarWidths = [159, 119, 164];
256
+ // Bridge for the @Input `showPills` into the reactive pipeline. Without this,
257
+ // changing the input after init wouldn't trigger a new vm emission until the
258
+ // next controller.dropdown$ tick — and we'd lose the showPills update entirely
259
+ // if the controller is idle. Updated from ngOnChanges below.
260
+ this.showPills$ = new BehaviorSubject(true);
256
261
  this.trackByWidth = (_, w) => w;
257
262
  }
258
- /**
259
- * View-model derived from the controller's `dropdown$`. Adds three booleans
260
- * (`showsPills`, `showsOptions`, `showsFallbackSkeleton`, `isVisible`) that
261
- * keep template-level logic to a property read per §19.6.
262
- */
263
- get vm$() {
264
- return this.controller.dropdown$.pipe(map((dd) => {
263
+ ngOnInit() {
264
+ // Seed showPills$ from the input's resolved value at init time.
265
+ this.showPills$.next(this.showPills);
266
+ this.initVm();
267
+ }
268
+ ngOnChanges(changes) {
269
+ if (changes["showPills"]) {
270
+ this.showPills$.next(this.showPills);
271
+ }
272
+ // Re-bind vm$ when the controller @Input is swapped (e.g. a multi-step form
273
+ // toggling between controllers). Without this, vm$ stays subscribed to the
274
+ // previous controller's dropdown$ — rendering stale options, or, if that
275
+ // controller was destroy()-ed, receiving a completion that collapses the
276
+ // dropdown permanently. Skip the first change: ngOnChanges fires before
277
+ // ngOnInit, which owns the initial creation.
278
+ if (changes["controller"] && !changes["controller"].firstChange && this.controller) {
279
+ this.initVm();
280
+ }
281
+ }
282
+ /** Build the view-model stream from the current controller + showPills$. */
283
+ initVm() {
284
+ this.vm$ = combineLatest([this.controller.dropdown$, this.showPills$]).pipe(map(([dd, showPills]) => {
265
285
  const options = dd.suggestions[0]?.options ?? [];
266
- const showsPills = this.showPills && (dd.pills.length > 0 || dd.isLoading);
286
+ const showsPills = showPills && (dd.pills.length > 0 || dd.isLoading);
267
287
  const showsOptions = options.length > 0;
268
288
  const showsFallbackSkeleton = dd.isLoading && !showsOptions;
269
289
  const isVisible = dd.isOpen && (showsOptions || showsPills || dd.isLoading);
@@ -294,10 +314,10 @@ class AIAutocompleteDropdownComponent {
294
314
  }
295
315
  }
296
316
  AIAutocompleteDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
297
- 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 });
317
+ 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 });
298
318
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, decorators: [{
299
319
  type: Component,
300
- 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"] }]
320
+ 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"] }]
301
321
  }], propDecorators: { controller: [{
302
322
  type: Input
303
323
  }], showPills: [{
@@ -366,17 +386,37 @@ class AIAutocompleteController {
366
386
  optionsPosition: opts.optionsPosition ?? "below",
367
387
  });
368
388
  this.rawState$ = new BehaviorSubject(this.core.getState());
369
- this.unsubscribeCore = this.core.subscribe((state) => {
389
+ // Push the store's *current* state, not the snapshot the listener was
390
+ // handed. The core notifies listeners synchronously, and some actions
391
+ // trigger reentrant `store.set` calls (e.g. typing a non-matching char
392
+ // during re-edit makes the core synchronously exit re-edit and kick off a
393
+ // fetch). Reentrancy makes the outer set's listener loop continue with a
394
+ // now-stale snapshot, so the listener can fire *out of order* — delivering
395
+ // an older state (still in re-edit) after a newer one (re-edit exited).
396
+ // Reading `getState()` at emission time collapses every notification onto
397
+ // the true latest state, so a stale `editingParam` can't resurrect and
398
+ // retrigger the editor's re-edit caret-park (which would yank the caret
399
+ // back to where re-editing started). distinctUntilChanged downstream drops
400
+ // the resulting duplicate emissions. React avoids this because it batches
401
+ // renders and its caret effects are dependency-gated; our subscription is
402
+ // synchronous, so we coalesce here instead.
403
+ this.unsubscribeCore = this.core.subscribe(() => {
370
404
  if (!this.isDestroyed)
371
- this.rawState$.next(state);
405
+ this.rawState$.next(this.core.getState());
372
406
  });
373
407
  // === Per-field observables ===
408
+ // Array-valued fields use shallow equality. The vanilla store spreads on
409
+ // every write (`store.set({ completedParams: [...] })`), so reference
410
+ // equality would re-fire downstream subscribers on every state tick even
411
+ // when the array's contents are unchanged. The other fields are
412
+ // primitives or stable object references, so default identity equality
413
+ // works fine.
374
414
  this.text$ = deriveField(this.rawState$, (s) => s.text);
375
- this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams);
376
- this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions);
377
- this.segments$ = deriveField(this.rawState$, (s) => s.segments);
415
+ this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams, arrEq);
416
+ this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions, arrEq);
417
+ this.segments$ = deriveField(this.rawState$, (s) => s.segments, arrEq);
378
418
  this.newParamId$ = deriveField(this.rawState$, (s) => s.newParamId);
379
- this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions);
419
+ this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions, arrEq);
380
420
  this.activeIndex$ = deriveField(this.rawState$, (s) => s.activeDropdownIndex);
381
421
  this.isLoading$ = deriveField(this.rawState$, computeUiLoading);
382
422
  this.isReady$ = deriveField(this.rawState$, (s) => s.isReady);
@@ -417,68 +457,109 @@ class AIAutocompleteController {
417
457
  return computeState(this.rawState$.value);
418
458
  }
419
459
  // === Action methods (pass-through to the core) ===
460
+ //
461
+ // Every action method begins with an `isDestroyed` guard so that callers
462
+ // who hold a stale reference after `destroy()` don't accidentally trigger
463
+ // work on a torn-down core. The vanilla core does not gate its own methods
464
+ // post-dispose — `reset()`, `handleTextChange()`, and friends would still
465
+ // schedule fetches or fire HTTP requests against an aborted controller,
466
+ // and the responses would have nowhere to surface (rawState$ is already
467
+ // complete). The guard makes the JSDoc-promised "no-op silently"
468
+ // semantics actually true.
420
469
  /** Clear all state, rotate `session_id`, and re-fetch. Call after submit. */
421
470
  reset() {
471
+ if (this.isDestroyed)
472
+ return;
422
473
  this.core.reset();
423
474
  }
424
475
  /** Pick a dropdown option (or replace the re-edited param if in re-edit mode). */
425
476
  selectOption(option) {
477
+ if (this.isDestroyed)
478
+ return;
426
479
  this.core.selectOption(option);
427
480
  }
428
481
  /** Promote the pill at `index` to active (front of the list). */
429
482
  setActivePill(index) {
483
+ if (this.isDestroyed)
484
+ return;
430
485
  this.core.setActivePill(index);
431
486
  }
432
487
  /** Drop the last completed param's "completed" status. Text stays as plain text. */
433
488
  removeLastParam() {
489
+ if (this.isDestroyed)
490
+ return;
434
491
  this.core.removeLastParam();
435
492
  }
436
493
  /** Clear the shimmer animation state for the most-recently-added param. */
437
494
  clearNewParamId() {
495
+ if (this.isDestroyed)
496
+ return;
438
497
  this.core.clearNewParamId();
439
498
  }
440
499
  /** Controlled-mode setter for editor text. */
441
500
  setValue(text) {
501
+ if (this.isDestroyed)
502
+ return;
442
503
  this.core.setValue(text);
443
504
  }
444
505
  /** Controlled-mode setter for completed params. */
445
506
  setCompletedParams(params) {
507
+ if (this.isDestroyed)
508
+ return;
446
509
  this.core.setCompletedParams(params);
447
510
  }
448
511
  /** Forward plain-text input from the consumer's `<textarea>` to the core. */
449
512
  handleTextChange(value) {
513
+ if (this.isDestroyed)
514
+ return;
450
515
  this.core.handleTextChange(value);
451
516
  }
452
517
  /** Forward keyboard events from the consumer's `<textarea>` to the core. */
453
518
  handleKeyDown(event) {
519
+ if (this.isDestroyed)
520
+ return;
454
521
  this.core.handleKeyDown(event);
455
522
  }
456
523
  /** Notify the core of focus state. */
457
524
  setFocused(focused) {
525
+ if (this.isDestroyed)
526
+ return;
458
527
  this.core.setFocused(focused);
459
528
  }
460
529
  /** Enter re-edit mode for the bold param with the given id. */
461
530
  startEditingParam(paramId) {
531
+ if (this.isDestroyed)
532
+ return;
462
533
  this.core.startEditingParam(paramId);
463
534
  }
464
535
  /** Exit re-edit mode without changing the bold param. */
465
536
  exitEditMode() {
537
+ if (this.isDestroyed)
538
+ return;
466
539
  this.core.exitEditMode();
467
540
  }
468
541
  /** Forward post-input caret position (extends the edit tail). */
469
542
  handleCaretAfterInput(offset) {
543
+ if (this.isDestroyed)
544
+ return;
470
545
  this.core.handleCaretAfterInput(offset);
471
546
  }
472
547
  /** Forward selection-only caret moves (may exit re-edit). */
473
548
  handleCaretMove(offset) {
549
+ if (this.isDestroyed)
550
+ return;
474
551
  this.core.handleCaretMove(offset);
475
552
  }
476
553
  /** Replace the bold param being re-edited. Returns true when applied. */
477
554
  replaceEditingRange(replacement) {
555
+ if (this.isDestroyed)
556
+ return false;
478
557
  return this.core.replaceEditingRange(replacement);
479
558
  }
480
559
  /** Set the highlighted option index (typically driven by hover). */
481
560
  setActiveDropdownIndex(index) {
561
+ if (this.isDestroyed)
562
+ return;
482
563
  this.core.setActiveDropdownIndex(index);
483
564
  }
484
565
  // === Lifecycle ===
@@ -490,6 +571,8 @@ class AIAutocompleteController {
490
571
  * (e.g. `text$` instead of `onChange`).
491
572
  */
492
573
  update(opts) {
574
+ if (this.isDestroyed)
575
+ return;
493
576
  this.core.update(opts);
494
577
  }
495
578
  /**
@@ -507,6 +590,24 @@ class AIAutocompleteController {
507
590
  }
508
591
  }
509
592
  // ===== Pure helpers — not part of the public surface =====
593
+ /**
594
+ * Shallow array equality. The vanilla store reassigns array fields on every
595
+ * write (`store.set({ completedParams: [...prev, x] })`), so reference
596
+ * equality alone would re-emit downstream even when the contents are
597
+ * identical. Used for `completedParams$`, `suggestions$`, `suggestionPills$`,
598
+ * and `segments$`.
599
+ */
600
+ function arrEq(a, b) {
601
+ if (a === b)
602
+ return true;
603
+ if (a.length !== b.length)
604
+ return false;
605
+ for (let i = 0; i < a.length; i++) {
606
+ if (a[i] !== b[i])
607
+ return false;
608
+ }
609
+ return true;
610
+ }
510
611
  function computeUiLoading(s) {
511
612
  return s.isLoading && !s.editingParam && !s.inSelectionAnimation;
512
613
  }
@@ -701,7 +802,9 @@ class AIAutocompleteEditorHostDirective {
701
802
  const anchor = sel.anchorNode;
702
803
  const startEl = anchor.nodeType === Node.ELEMENT_NODE ? anchor : anchor.parentElement;
703
804
  const strong = startEl?.closest('strong[data-seg="completed"][data-param-id]');
704
- const enclosing = strong?.dataset.paramId ?? null;
805
+ // `DOMStringMap` is an index signature; bracket notation is required
806
+ // by `noPropertyAccessFromIndexSignature`.
807
+ const enclosing = strong?.dataset["paramId"] ?? null;
705
808
  if (enclosing && enclosing !== this.editingParamId) {
706
809
  this.zone.run(() => this.controller.startEditingParam(enclosing));
707
810
  return;
@@ -801,13 +904,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
801
904
  type: Input
802
905
  }] } });
803
906
 
804
- function resolveInitialMode(mode) {
805
- if (mode !== "auto")
806
- return mode;
807
- if (typeof window === "undefined")
808
- return "dark";
809
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
810
- }
811
907
  /**
812
908
  * Tier 1 component — `<ai-autocomplete>`. The Angular equivalent of React's
813
909
  * `<AIAutocomplete />`. Owns the contentEditable editor, inline pills, mode
@@ -835,19 +931,26 @@ class AIAutocompleteComponent {
835
931
  this.completedParamsChange = new EventEmitter();
836
932
  this.focused = new EventEmitter();
837
933
  this.blurred = new EventEmitter();
934
+ /**
935
+ * Concrete light/dark value bound to `[attr.data-mode]`. Seeded in ngOnInit
936
+ * so the attribute is present during SSR (where ModeController, gated behind
937
+ * isPlatformBrowser in ngAfterViewInit, never runs). On the browser the
938
+ * ModeController's resolve callback keeps this in sync — so its runtime
939
+ * prefers-color-scheme updates and this binding always agree (no clobber).
940
+ */
838
941
  this.resolvedMode = "dark";
839
942
  this.onChangeFn = noop$1;
840
943
  this.onTouchedFn = noop$1;
841
944
  // §16.9: prefer `inject()` over constructor-parameter DI for components.
842
945
  this.platformId = inject(PLATFORM_ID);
843
- this.el = inject(ElementRef);
844
- this.cdr = inject(ChangeDetectorRef);
845
946
  this.zone = inject(NgZone);
947
+ this.cdr = inject(ChangeDetectorRef);
846
948
  }
847
949
  ngOnInit() {
950
+ // Seed before the browser guard so SSR renders a concrete data-mode.
951
+ this.resolvedMode = resolveInitialMode(this.mode);
848
952
  if (!isPlatformBrowser(this.platformId))
849
953
  return;
850
- this.resolvedMode = resolveInitialMode(this.mode);
851
954
  this.controller = new AIAutocompleteController({
852
955
  apiConfig: this.apiConfig,
853
956
  optionOverrides: this.optionOverrides,
@@ -880,12 +983,26 @@ class AIAutocompleteComponent {
880
983
  setCursorOffset(editor, offset);
881
984
  },
882
985
  });
986
+ if (this.pendingWriteValue !== undefined) {
987
+ this.controller.setValue(this.pendingWriteValue);
988
+ this.pendingWriteValue = undefined;
989
+ }
883
990
  }
884
991
  ngAfterViewInit() {
885
992
  if (!isPlatformBrowser(this.platformId))
886
993
  return;
887
- const container = this.el.nativeElement;
888
- this.modeController = new ModeController(container, this.mode);
994
+ // ModeController writes `data-mode` to the element it's given. The CSS
995
+ // selectors are scoped to `.magicx-aia[data-mode="..."]` — which lives on
996
+ // the inner `<div class="magicx-aia magicx-aia-container" #container>`,
997
+ // NOT the host `<ai-autocomplete>` element. The resolve callback mirrors
998
+ // the value into `resolvedMode` so the `[attr.data-mode]` binding stays in
999
+ // lockstep with ModeController — the binding handles SSR, ModeController
1000
+ // handles runtime prefers-color-scheme switches, and because they always
1001
+ // agree neither clobbers the other.
1002
+ this.modeController = new ModeController(this.containerRef.nativeElement, this.mode, (resolved) => {
1003
+ this.resolvedMode = resolved;
1004
+ this.cdr.markForCheck();
1005
+ });
889
1006
  // Pill-wrap detection — when the pill list wraps below the editor's last
890
1007
  // line, drop its 8px left margin via a data attribute the CSS keys off.
891
1008
  // Mirrors the React component's useLayoutEffect.
@@ -902,20 +1019,24 @@ class AIAutocompleteComponent {
902
1019
  ngOnChanges(changes) {
903
1020
  if (!this.controller)
904
1021
  return;
1022
+ // `SimpleChanges` is `{ [key: string]: SimpleChange }`, so per
1023
+ // `noPropertyAccessFromIndexSignature` each key lookup uses bracket
1024
+ // notation — this keeps the strictness contract that the rest of the
1025
+ // workspace enforces.
905
1026
  // Controlled-mode propagation
906
- if (changes.value && this.value !== undefined) {
1027
+ if (changes["value"] && this.value !== undefined) {
907
1028
  this.controller.setValue(this.value);
908
1029
  }
909
- if (changes.completedParams && this.completedParams !== undefined) {
1030
+ if (changes["completedParams"] && this.completedParams !== undefined) {
910
1031
  this.controller.setCompletedParams(this.completedParams);
911
1032
  }
912
1033
  // Reactive options
913
- if (changes.apiConfig ||
914
- changes.optionOverrides ||
915
- changes.dropdownTrigger ||
916
- changes.optionsPosition ||
917
- changes.closeDropdownOnBlur ||
918
- changes.showNonTappableOptions) {
1034
+ if (changes["apiConfig"] ||
1035
+ changes["optionOverrides"] ||
1036
+ changes["dropdownTrigger"] ||
1037
+ changes["optionsPosition"] ||
1038
+ changes["closeDropdownOnBlur"] ||
1039
+ changes["showNonTappableOptions"]) {
919
1040
  this.controller.update({
920
1041
  apiConfig: this.apiConfig,
921
1042
  optionOverrides: this.optionOverrides,
@@ -925,9 +1046,12 @@ class AIAutocompleteComponent {
925
1046
  showNonTappableOptions: this.showNonTappableOptions,
926
1047
  });
927
1048
  }
928
- if (changes.mode) {
929
- this.modeController?.setMode(this.mode);
1049
+ if (changes["mode"]) {
1050
+ // Keep the binding correct even when ModeController doesn't exist yet
1051
+ // (SSR / pre-ngAfterViewInit). On the browser, setMode triggers the
1052
+ // resolve callback which sets the same value.
930
1053
  this.resolvedMode = resolveInitialMode(this.mode);
1054
+ this.modeController?.setMode(this.mode);
931
1055
  }
932
1056
  }
933
1057
  ngOnDestroy() {
@@ -951,8 +1075,8 @@ class AIAutocompleteComponent {
951
1075
  /** Switch color mode at runtime. */
952
1076
  setMode(mode) {
953
1077
  this.mode = mode;
954
- this.modeController?.setMode(mode);
955
1078
  this.resolvedMode = resolveInitialMode(mode);
1079
+ this.modeController?.setMode(mode);
956
1080
  this.cdr.markForCheck();
957
1081
  }
958
1082
  // === Template helpers ===
@@ -1005,9 +1129,16 @@ class AIAutocompleteComponent {
1005
1129
  }
1006
1130
  // === ControlValueAccessor ===
1007
1131
  writeValue(value) {
1008
- if (!this.controller)
1132
+ const next = value ?? "";
1133
+ if (!this.controller) {
1134
+ // Reactive Forms can call writeValue() before ngOnInit creates the
1135
+ // controller (dynamic form construction, or a FormControl that already
1136
+ // had a value at directive-creation time). Buffer the value; ngOnInit
1137
+ // flushes it as soon as the controller exists.
1138
+ this.pendingWriteValue = next;
1009
1139
  return;
1010
- this.controller.setValue(value ?? "");
1140
+ }
1141
+ this.controller.setValue(next);
1011
1142
  }
1012
1143
  registerOnChange(fn) {
1013
1144
  this.onChangeFn = fn;
@@ -1087,7 +1218,7 @@ AIAutocompleteComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0"
1087
1218
  useExisting: forwardRef(() => AIAutocompleteComponent),
1088
1219
  multi: true,
1089
1220
  },
1090
- ], 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 });
1221
+ ], 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 });
1091
1222
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, decorators: [{
1092
1223
  type: Component,
1093
1224
  args: [{ selector: "ai-autocomplete", standalone: true, imports: [
@@ -1102,7 +1233,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1102
1233
  useExisting: forwardRef(() => AIAutocompleteComponent),
1103
1234
  multi: true,
1104
1235
  },
1105
- ], 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"] }]
1236
+ ], 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"] }]
1106
1237
  }], propDecorators: { apiConfig: [{
1107
1238
  type: Input
1108
1239
  }], optionOverrides: [{
@@ -1145,6 +1276,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1145
1276
  type: Output
1146
1277
  }], blurred: [{
1147
1278
  type: Output
1279
+ }], containerRef: [{
1280
+ type: ViewChild,
1281
+ args: ["container", { static: true }]
1148
1282
  }], editorRef: [{
1149
1283
  type: ViewChild,
1150
1284
  args: ["editor", { static: false }]
@@ -1155,6 +1289,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1155
1289
  function noop$1() {
1156
1290
  // Default no-op for CVA callbacks.
1157
1291
  }
1292
+ /**
1293
+ * Resolve a concrete light/dark value from the configured mode. Used to seed
1294
+ * the `data-mode` attribute synchronously (including during SSR, where
1295
+ * ModeController never runs). On the server `window` is undefined → defaults
1296
+ * to "dark" (matches the vanilla/React behavior).
1297
+ */
1298
+ function resolveInitialMode(mode) {
1299
+ if (mode !== "auto")
1300
+ return mode;
1301
+ if (typeof window === "undefined")
1302
+ return "dark";
1303
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1304
+ }
1158
1305
 
1159
1306
  /**
1160
1307
  * Tier 2 input binder. Attach to a `<textarea>` or `<input>` and pass an
@@ -1188,11 +1335,23 @@ class AIAutocompleteInputDirective {
1188
1335
  this.destroy$ = new Subject();
1189
1336
  this.onChangeFn = noop;
1190
1337
  this.onTouchedFn = noop;
1338
+ // Timestamp of the last `input` event. A `keyup`/`select` that fires within
1339
+ // this window is the tail of a keystroke, not a deliberate caret move, so we
1340
+ // skip the caret-move handler there (mirrors the editor host's selectionchange
1341
+ // guard). performance.now() is monotonic; 0 means "no input yet".
1342
+ this.lastInputAt = 0;
1343
+ // Tracks the most-recently-promoted param so we only reposition the caret
1344
+ // once per option selection (mirrors the editor host's post-promote refocus).
1345
+ this.lastSeenParamId = "";
1191
1346
  }
1192
1347
  ngOnInit() {
1193
1348
  if (!this.controller) {
1194
1349
  throw new Error('[aiaInput] requires a controller — use `[aiaInput]="myController"`.');
1195
1350
  }
1351
+ if (this.pendingWriteValue !== undefined) {
1352
+ this.controller.setValue(this.pendingWriteValue);
1353
+ this.pendingWriteValue = undefined;
1354
+ }
1196
1355
  const el = this.el.nativeElement;
1197
1356
  this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
1198
1357
  // Sync editor text → DOM. Skips the write when values already match, so
@@ -1228,6 +1387,35 @@ class AIAutocompleteInputDirective {
1228
1387
  this.renderer.removeAttribute(el, "aria-activedescendant");
1229
1388
  }
1230
1389
  });
1390
+ // After an option selection promotes a param, the core appends its text and
1391
+ // stamps `caretOffset` at the end of the inserted text. The text$ subscriber
1392
+ // above writes the new value but *restores the pre-write caret* (correct for
1393
+ // typing echoes, wrong here), so we move the caret to the promoted position.
1394
+ // Keyed on newParamId so it fires once per selection, not on every tick.
1395
+ // Registered after text$ so the new value is in the DOM before we seek.
1396
+ this.controller.newParamId$.pipe(takeUntil(this.destroy$)).subscribe((id) => {
1397
+ const current = id ?? "";
1398
+ if (current && current !== this.lastSeenParamId) {
1399
+ this.lastSeenParamId = current;
1400
+ const offset = this.controller.getState().caretOffset ?? el.value.length;
1401
+ const clamped = Math.min(Math.max(offset, 0), el.value.length);
1402
+ // Only grab focus if we don't already have it — don't yank focus back
1403
+ // from wherever the user moved it after selecting (e.g. a host-app
1404
+ // button). setSelectionRange still positions the caret regardless.
1405
+ if (typeof document !== "undefined" && document.activeElement !== el) {
1406
+ el.focus();
1407
+ }
1408
+ try {
1409
+ el.setSelectionRange(clamped, clamped);
1410
+ }
1411
+ catch {
1412
+ // Some input types (e.g. "number") don't support setSelectionRange.
1413
+ }
1414
+ }
1415
+ else if (!current) {
1416
+ this.lastSeenParamId = "";
1417
+ }
1418
+ });
1231
1419
  }
1232
1420
  ngOnDestroy() {
1233
1421
  this.destroy$.next();
@@ -1241,6 +1429,11 @@ class AIAutocompleteInputDirective {
1241
1429
  * we don't disrupt multi-keystroke input methods (Pinyin, Hangul, etc.).
1242
1430
  */
1243
1431
  onInput(event) {
1432
+ // Stamp the input time before anything else. The autocapitalize path below
1433
+ // calls setSelectionRange, which can synchronously fire a `select` event →
1434
+ // onCaretMove; stamping first ensures that event falls inside the guard
1435
+ // window and is treated as part of this keystroke, not a caret move.
1436
+ this.lastInputAt = performance.now();
1244
1437
  const target = event.target;
1245
1438
  const raw = target.value;
1246
1439
  const isComposing = event.isComposing === true;
@@ -1261,11 +1454,36 @@ class AIAutocompleteInputDirective {
1261
1454
  }
1262
1455
  }
1263
1456
  this.controller.handleTextChange(next);
1457
+ // Sync the real caret into the core. The dropdown's auto-trigger gate only
1458
+ // opens when the caret is at the end of the text; without this, the core's
1459
+ // `caretOffset` stays pinned at whatever a prior promote/select set it to
1460
+ // and goes stale as the user keeps typing, wedging the dropdown closed.
1461
+ // selectionStart is null on some input types (number/email/color); treat
1462
+ // that as "caret at end" rather than feeding null into the core.
1463
+ this.controller.handleCaretAfterInput(target.selectionStart ?? target.value.length);
1264
1464
  this.onChangeFn(next);
1265
1465
  }
1266
1466
  onKeyDown(event) {
1267
1467
  this.controller.handleKeyDown(event);
1268
1468
  }
1469
+ /**
1470
+ * Caret moved by something other than typing — arrow keys, Home/End, or a
1471
+ * mouse click/drag (keyup + mouseup). Forward the new offset so the core can
1472
+ * re-evaluate the caret-at-end dropdown gate (and exit re-edit if the caret
1473
+ * left the region). We deliberately do NOT listen for the `select` event:
1474
+ * programmatic `setSelectionRange` calls (the text$ writeback, the post-select
1475
+ * caret reposition) fire it in jsdom/some browsers, which would re-enter here
1476
+ * and clobber `caretOffset`. `keyup`/`mouseup` only fire for real user input.
1477
+ * Skipped right after an `input` event, where the caret is already handled by
1478
+ * `handleCaretAfterInput` above.
1479
+ */
1480
+ onCaretMove() {
1481
+ if (performance.now() - this.lastInputAt < 50)
1482
+ return;
1483
+ const el = this.el.nativeElement;
1484
+ // selectionStart is null on some input types — treat as caret at end.
1485
+ this.controller.handleCaretMove(el.selectionStart ?? el.value.length);
1486
+ }
1269
1487
  onFocus() {
1270
1488
  this.controller.setFocused(true);
1271
1489
  }
@@ -1277,11 +1495,17 @@ class AIAutocompleteInputDirective {
1277
1495
  writeValue(value) {
1278
1496
  const next = value ?? "";
1279
1497
  this.renderer.setProperty(this.el.nativeElement, "value", next);
1280
- // The controller may be undefined transiently if Angular calls
1281
- // writeValue() before ngOnInit (rare but possible during dynamic form
1282
- // construction). Guard so we don't throw in that window.
1283
- if (this.controller)
1498
+ // If Angular calls writeValue() before ngOnInit (possible during dynamic
1499
+ // form construction), queue the value and let ngOnInit flush it. We
1500
+ // can't push it into the controller now because either the controller
1501
+ // binding hasn't resolved yet, or text$'s initial subscription would
1502
+ // overwrite it moments later.
1503
+ if (this.controller) {
1284
1504
  this.controller.setValue(next);
1505
+ }
1506
+ else {
1507
+ this.pendingWriteValue = next;
1508
+ }
1285
1509
  }
1286
1510
  registerOnChange(fn) {
1287
1511
  this.onChangeFn = fn;
@@ -1294,7 +1518,7 @@ class AIAutocompleteInputDirective {
1294
1518
  }
1295
1519
  }
1296
1520
  AIAutocompleteInputDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1297
- 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: [
1521
+ 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: [
1298
1522
  {
1299
1523
  provide: NG_VALUE_ACCESSOR,
1300
1524
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
@@ -1312,6 +1536,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1312
1536
  "aria-autocomplete": "list",
1313
1537
  "(input)": "onInput($event)",
1314
1538
  "(keydown)": "onKeyDown($event)",
1539
+ "(keyup)": "onCaretMove()",
1540
+ "(mouseup)": "onCaretMove()",
1315
1541
  "(focus)": "onFocus()",
1316
1542
  "(blur)": "onBlur()",
1317
1543
  },