@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.
- package/README.md +1 -0
- package/esm2020/lib/ai-autocomplete/ai-autocomplete.component.mjs +72 -28
- package/esm2020/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.mjs +33 -12
- package/esm2020/lib/controller/ai-autocomplete.controller.mjs +88 -7
- package/esm2020/lib/input/ai-autocomplete-input.directive.mjs +85 -6
- package/esm2020/lib/internal/editor-host/editor-host.directive.mjs +4 -2
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs +280 -51
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs +276 -50
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts +10 -2
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts.map +1 -1
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts +27 -12
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts.map +1 -1
- package/lib/controller/ai-autocomplete.controller.d.ts.map +1 -1
- package/lib/input/ai-autocomplete-input.directive.d.ts +15 -0
- package/lib/input/ai-autocomplete-input.directive.d.ts.map +1 -1
- package/lib/internal/editor-host/editor-host.directive.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
888
|
-
|
|
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
|
|
1027
|
+
if (changes["value"] && this.value !== undefined) {
|
|
907
1028
|
this.controller.setValue(this.value);
|
|
908
1029
|
}
|
|
909
|
-
if (changes
|
|
1030
|
+
if (changes["completedParams"] && this.completedParams !== undefined) {
|
|
910
1031
|
this.controller.setCompletedParams(this.completedParams);
|
|
911
1032
|
}
|
|
912
1033
|
// Reactive options
|
|
913
|
-
if (changes
|
|
914
|
-
changes
|
|
915
|
-
changes
|
|
916
|
-
changes
|
|
917
|
-
changes
|
|
918
|
-
changes
|
|
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
|
|
929
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
|
|
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
|
},
|