@magicx-eng/ai-autocomplete-angular 0.1.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 +582 -0
- package/esm2020/lib/ai-autocomplete/ai-autocomplete.component.mjs +365 -0
- package/esm2020/lib/ai-autocomplete/index.mjs +2 -0
- package/esm2020/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.mjs +81 -0
- package/esm2020/lib/ai-autocomplete-dropdown/index.mjs +2 -0
- package/esm2020/lib/ai-autocomplete.module.mjs +26 -0
- package/esm2020/lib/controller/ai-autocomplete.controller.mjs +226 -0
- package/esm2020/lib/controller/index.mjs +2 -0
- package/esm2020/lib/controller/observables.mjs +24 -0
- package/esm2020/lib/input/ai-autocomplete-input.directive.mjs +180 -0
- package/esm2020/lib/input/index.mjs +2 -0
- package/esm2020/lib/internal/editor-host/editor-host.directive.mjs +253 -0
- package/esm2020/lib/internal/pill-list/pill-list.component.mjs +65 -0
- package/esm2020/lib/internal/suggestion-grid/suggestion-grid.component.mjs +97 -0
- package/esm2020/lib/internal/suggestion-item/suggestion-item.component.mjs +76 -0
- package/esm2020/lib/shared/types.mjs +2 -0
- package/esm2020/magicx-eng-ai-autocomplete-angular.mjs +5 -0
- package/esm2020/public-api.mjs +18 -0
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs +1386 -0
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs.map +1 -0
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs +1370 -0
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs.map +1 -0
- package/index.d.ts +6 -0
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts +113 -0
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts.map +1 -0
- package/lib/ai-autocomplete/index.d.ts +2 -0
- package/lib/ai-autocomplete/index.d.ts.map +1 -0
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts +51 -0
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts.map +1 -0
- package/lib/ai-autocomplete-dropdown/index.d.ts +2 -0
- package/lib/ai-autocomplete-dropdown/index.d.ts.map +1 -0
- package/lib/ai-autocomplete.module.d.ts +18 -0
- package/lib/ai-autocomplete.module.d.ts.map +1 -0
- package/lib/controller/ai-autocomplete.controller.d.ts +131 -0
- package/lib/controller/ai-autocomplete.controller.d.ts.map +1 -0
- package/lib/controller/index.d.ts +2 -0
- package/lib/controller/index.d.ts.map +1 -0
- package/lib/controller/observables.d.ts +22 -0
- package/lib/controller/observables.d.ts.map +1 -0
- package/lib/input/ai-autocomplete-input.directive.d.ts +60 -0
- package/lib/input/ai-autocomplete-input.directive.d.ts.map +1 -0
- package/lib/input/index.d.ts +2 -0
- package/lib/input/index.d.ts.map +1 -0
- package/lib/internal/editor-host/editor-host.directive.d.ts +46 -0
- package/lib/internal/editor-host/editor-host.directive.d.ts.map +1 -0
- package/lib/internal/pill-list/pill-list.component.d.ts +35 -0
- package/lib/internal/pill-list/pill-list.component.d.ts.map +1 -0
- package/lib/internal/suggestion-grid/suggestion-grid.component.d.ts +37 -0
- package/lib/internal/suggestion-grid/suggestion-grid.component.d.ts.map +1 -0
- package/lib/internal/suggestion-item/suggestion-item.component.d.ts +33 -0
- package/lib/internal/suggestion-item/suggestion-item.component.d.ts.map +1 -0
- package/lib/shared/types.d.ts +125 -0
- package/lib/shared/types.d.ts.map +1 -0
- package/magicx-eng-ai-autocomplete-angular.d.ts.map +1 -0
- package/package.json +46 -0
- package/public-api.d.ts +15 -0
- package/public-api.d.ts.map +1 -0
|
@@ -0,0 +1,1386 @@
|
|
|
1
|
+
import * as i1 from '@angular/common';
|
|
2
|
+
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { EventEmitter, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, Output, inject, ChangeDetectorRef, NgZone, ViewChild, ElementRef, Renderer2, Directive, PLATFORM_ID, forwardRef, NgModule } from '@angular/core';
|
|
5
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
|
+
import { AIAutocomplete, getCursorOffset, extractPlainText, renderEditableContent, plainTextLength, setCursorOffset, ModeController, buildQuery } from '@magicx-eng/ai-autocomplete-vanilla';
|
|
7
|
+
import { map } from 'rxjs/operators';
|
|
8
|
+
import { map as map$1, distinctUntilChanged, shareReplay, BehaviorSubject, Subject, takeUntil } from 'rxjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Internal pill list used by both the dropdown and Tier 1's inline pillbar.
|
|
12
|
+
* Mirrors `packages/react/src/components/PillList.tsx` one-for-one — including
|
|
13
|
+
* the per-index opacity ramp (0.4 / 0.3 / 0.15) and the skeleton-loading
|
|
14
|
+
* fallback that preserves layout while the next fetch is in flight.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
class PillListComponent {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.pills = [];
|
|
21
|
+
this.activePillIndex = 0;
|
|
22
|
+
this.rounded = false;
|
|
23
|
+
/**
|
|
24
|
+
* When true, pills become non-interactive shimmering skeletons. Real pills
|
|
25
|
+
* (with their text) are rendered when provided so widths/positions match
|
|
26
|
+
* the previous state; with no pills, fixed-width fallback placeholders are
|
|
27
|
+
* rendered instead.
|
|
28
|
+
*/
|
|
29
|
+
this.loading = false;
|
|
30
|
+
this.selectPill = new EventEmitter();
|
|
31
|
+
/** Fallback widths when loading with zero cached pills (matches React). */
|
|
32
|
+
this.fallbackSkeletonWidths = [125, 69];
|
|
33
|
+
this.trackByType = (_, pill) => pill.type;
|
|
34
|
+
this.trackByWidth = (_, w) => w;
|
|
35
|
+
}
|
|
36
|
+
/** Per-index opacity ramp — mirrors PillList.tsx `getPillOpacity`. */
|
|
37
|
+
pillOpacity(index) {
|
|
38
|
+
if (index === 0)
|
|
39
|
+
return 0.4;
|
|
40
|
+
if (index === 1)
|
|
41
|
+
return 0.3;
|
|
42
|
+
return 0.15;
|
|
43
|
+
}
|
|
44
|
+
onPillClick(index) {
|
|
45
|
+
if (this.loading)
|
|
46
|
+
return;
|
|
47
|
+
this.selectPill.emit(index);
|
|
48
|
+
}
|
|
49
|
+
onPillMouseDown(event) {
|
|
50
|
+
// Prevent the editor from losing focus when a pill is tapped.
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
PillListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: PillListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
55
|
+
PillListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: PillListComponent, isStandalone: true, selector: "aia-pill-list", inputs: { pills: "pills", activePillIndex: "activePillIndex", rounded: "rounded", loading: "loading" }, outputs: { selectPill: "selectPill" }, ngImport: i0, template: "<ng-container *ngIf=\"loading && pills.length === 0; else realPills\">\n <span class=\"aia-pill-list\" data-aia-pill-list-loading=\"\">\n <span\n *ngFor=\"let w of fallbackSkeletonWidths; trackBy: trackByWidth; let i = index\"\n data-aia-pill-skeleton=\"\"\n class=\"aia-pill aia-pill-skeleton\"\n [class.aia-pill-rounded]=\"rounded\"\n [style.width.px]=\"w\"\n [style.opacity]=\"pillOpacity(i)\"\n ></span>\n </span>\n</ng-container>\n\n<ng-template #realPills>\n <span\n class=\"aia-pill-list\"\n [attr.data-aia-pill-list-loading]=\"loading ? '' : null\"\n >\n <button\n *ngFor=\"let pill of pills; trackBy: trackByType; let i = index\"\n type=\"button\"\n data-aia-pill=\"\"\n [attr.data-aia-loading]=\"loading ? '' : null\"\n tabindex=\"-1\"\n class=\"aia-pill\"\n [class.aia-pill-rounded]=\"rounded\"\n [class.aia-pill-active]=\"i === activePillIndex && !loading\"\n [class.aia-pill-skeleton]=\"loading\"\n [style.opacity]=\"pillOpacity(i)\"\n [disabled]=\"loading\"\n (mousedown)=\"onPillMouseDown($event)\"\n (click)=\"onPillClick(i)\"\n >\n {{ pill.text }}\n </button>\n </span>\n</ng-template>\n", styles: [".aia-pill-list{position:relative;z-index:1;pointer-events:auto;display:inline-flex;gap:5px;align-items:center;vertical-align:middle}.aia-pill{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:13px;border:none;border-radius:999px;background:rgba(49,50,85,.25);background:color-mix(in srgb,var(--aia-pill-bg, var(--aia-color-background-supportive, #313255)) 25%,transparent);color:var(--aia-pill-color, var(--aia-color-text-muted, #c1c4cb));font-family:inherit;font-size:var(--aia-pill-font-size, 19px);line-height:30px;cursor:pointer;white-space:nowrap;animation:aiaPillFadeIn .4s cubic-bezier(.4,0,.2,1) forwards;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)}.aia-pill-rounded{border-radius:999px}.aia-pill-skeleton{pointer-events:none;cursor:default;color:transparent;animation:aiaPillSkeletonPulse 1.4s ease-in-out infinite}.aia-pill:hover{filter:brightness(1.2)}.aia-pill-active{outline:1px solid #5a5b8a}@keyframes aiaPillFadeIn{0%{opacity:0}}@keyframes aiaPillSkeletonPulse{0%,to{filter:brightness(1)}50%{filter:brightness(.55)}}\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
56
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: PillListComponent, decorators: [{
|
|
57
|
+
type: Component,
|
|
58
|
+
args: [{ selector: "aia-pill-list", standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-container *ngIf=\"loading && pills.length === 0; else realPills\">\n <span class=\"aia-pill-list\" data-aia-pill-list-loading=\"\">\n <span\n *ngFor=\"let w of fallbackSkeletonWidths; trackBy: trackByWidth; let i = index\"\n data-aia-pill-skeleton=\"\"\n class=\"aia-pill aia-pill-skeleton\"\n [class.aia-pill-rounded]=\"rounded\"\n [style.width.px]=\"w\"\n [style.opacity]=\"pillOpacity(i)\"\n ></span>\n </span>\n</ng-container>\n\n<ng-template #realPills>\n <span\n class=\"aia-pill-list\"\n [attr.data-aia-pill-list-loading]=\"loading ? '' : null\"\n >\n <button\n *ngFor=\"let pill of pills; trackBy: trackByType; let i = index\"\n type=\"button\"\n data-aia-pill=\"\"\n [attr.data-aia-loading]=\"loading ? '' : null\"\n tabindex=\"-1\"\n class=\"aia-pill\"\n [class.aia-pill-rounded]=\"rounded\"\n [class.aia-pill-active]=\"i === activePillIndex && !loading\"\n [class.aia-pill-skeleton]=\"loading\"\n [style.opacity]=\"pillOpacity(i)\"\n [disabled]=\"loading\"\n (mousedown)=\"onPillMouseDown($event)\"\n (click)=\"onPillClick(i)\"\n >\n {{ pill.text }}\n </button>\n </span>\n</ng-template>\n", styles: [".aia-pill-list{position:relative;z-index:1;pointer-events:auto;display:inline-flex;gap:5px;align-items:center;vertical-align:middle}.aia-pill{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:13px;border:none;border-radius:999px;background:rgba(49,50,85,.25);background:color-mix(in srgb,var(--aia-pill-bg, var(--aia-color-background-supportive, #313255)) 25%,transparent);color:var(--aia-pill-color, var(--aia-color-text-muted, #c1c4cb));font-family:inherit;font-size:var(--aia-pill-font-size, 19px);line-height:30px;cursor:pointer;white-space:nowrap;animation:aiaPillFadeIn .4s cubic-bezier(.4,0,.2,1) forwards;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)}.aia-pill-rounded{border-radius:999px}.aia-pill-skeleton{pointer-events:none;cursor:default;color:transparent;animation:aiaPillSkeletonPulse 1.4s ease-in-out infinite}.aia-pill:hover{filter:brightness(1.2)}.aia-pill-active{outline:1px solid #5a5b8a}@keyframes aiaPillFadeIn{0%{opacity:0}}@keyframes aiaPillSkeletonPulse{0%,to{filter:brightness(1)}50%{filter:brightness(.55)}}\n"] }]
|
|
59
|
+
}], propDecorators: { pills: [{
|
|
60
|
+
type: Input
|
|
61
|
+
}], activePillIndex: [{
|
|
62
|
+
type: Input
|
|
63
|
+
}], rounded: [{
|
|
64
|
+
type: Input
|
|
65
|
+
}], loading: [{
|
|
66
|
+
type: Input
|
|
67
|
+
}], selectPill: [{
|
|
68
|
+
type: Output
|
|
69
|
+
}] } });
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Internal single-option cell used inside the suggestion grid. Mirrors
|
|
73
|
+
* `packages/react/src/components/SuggestionItem.tsx`. Handles:
|
|
74
|
+
*
|
|
75
|
+
* - Tappable vs non-tappable cursor + hover styling
|
|
76
|
+
* - Pressed-state streak animation (500ms, single-flight)
|
|
77
|
+
* - Keyboard activation (Enter/Space)
|
|
78
|
+
* - ARIA `role="option"` + `aria-selected` for the listbox pattern
|
|
79
|
+
* - Loading skeleton when `loading=true`
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
class SuggestionItemComponent {
|
|
84
|
+
constructor() {
|
|
85
|
+
this.isHighlighted = false;
|
|
86
|
+
this.id = "";
|
|
87
|
+
this.loading = false;
|
|
88
|
+
this.select = new EventEmitter();
|
|
89
|
+
this.highlight = new EventEmitter();
|
|
90
|
+
this.pressed = false;
|
|
91
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
92
|
+
}
|
|
93
|
+
ngOnDestroy() {
|
|
94
|
+
if (this.pressedTimer !== undefined)
|
|
95
|
+
clearTimeout(this.pressedTimer);
|
|
96
|
+
}
|
|
97
|
+
onSelect() {
|
|
98
|
+
if (this.loading || !this.option.is_tappable || this.pressed)
|
|
99
|
+
return;
|
|
100
|
+
this.pressed = true;
|
|
101
|
+
this.cdr.markForCheck();
|
|
102
|
+
this.select.emit(this.option);
|
|
103
|
+
if (this.pressedTimer !== undefined)
|
|
104
|
+
clearTimeout(this.pressedTimer);
|
|
105
|
+
this.pressedTimer = setTimeout(() => {
|
|
106
|
+
this.pressed = false;
|
|
107
|
+
this.cdr.markForCheck();
|
|
108
|
+
}, 500);
|
|
109
|
+
}
|
|
110
|
+
onMouseEnter() {
|
|
111
|
+
if (this.loading || !this.option.is_tappable)
|
|
112
|
+
return;
|
|
113
|
+
this.highlight.emit();
|
|
114
|
+
}
|
|
115
|
+
onKeyDown(event) {
|
|
116
|
+
if (this.loading || !this.option.is_tappable)
|
|
117
|
+
return;
|
|
118
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
this.onSelect();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
SuggestionItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
125
|
+
SuggestionItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: SuggestionItemComponent, isStandalone: true, selector: "aia-suggestion-item", inputs: { option: "option", isHighlighted: "isHighlighted", id: "id", loading: "loading" }, outputs: { select: "select", highlight: "highlight" }, ngImport: i0, template: "<div\n [id]=\"id\"\n role=\"option\"\n data-aia-option=\"\"\n [attr.data-aia-loading]=\"loading ? '' : null\"\n [attr.aria-selected]=\"isHighlighted\"\n class=\"aia-suggestion-item\"\n [class.aia-suggestion-item-highlighted]=\"isHighlighted && !loading\"\n [class.aia-suggestion-item-tappable]=\"option.is_tappable\"\n [class.aia-suggestion-item-non-tappable]=\"!option.is_tappable\"\n [class.aia-suggestion-item-pressed]=\"pressed\"\n [attr.tabindex]=\"loading || !option.is_tappable ? -1 : 0\"\n (click)=\"onSelect()\"\n (keydown)=\"onKeyDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n <div class=\"aia-suggestion-streaks\"></div>\n <div class=\"aia-suggestion-streaks-vert\"></div>\n <span class=\"aia-suggestion-content\">\n <span class=\"aia-suggestion-text\">\n <ng-container *ngIf=\"option.icon; else noIcon\"\n >{{ option.icon }} {{ option.text }}</ng-container\n >\n <ng-template #noIcon>{{ option.text }}</ng-template>\n </span>\n <span *ngIf=\"option.tag\" class=\"aia-suggestion-tag\">{{ option.tag }}</span>\n </span>\n</div>\n", styles: [".aia-suggestion-item{position:relative;overflow:visible;display:flex;align-items:flex-start;font-family:inherit;font-size:var(--aia-option-font-size, 19px);font-weight:250;line-height:24px;color:var(--aia-option-color, var(--aia-color-text-muted, #c1c4cb));white-space:normal;word-break:break-word;border-radius:12px;padding:13px;animation:aiaSuggestionFadeIn .5s cubic-bezier(.4,0,.2,1) forwards}@keyframes aiaSuggestionFadeIn{0%{opacity:0}}.aia-suggestion-content{position:relative;z-index:2}.aia-suggestion-item-tappable{cursor:pointer}.aia-suggestion-item-tappable:hover{color:var(--aia-option-color-selected, var(--aia-color-text-default, #fff))}.aia-suggestion-item-non-tappable{cursor:default}.aia-suggestion-item-highlighted{color:var(--aia-option-color-selected, var(--aia-color-text-default, #fff));background:var(--aia-option-bg, transparent)}.aia-suggestion-tag{font-size:11px;margin-left:6px;opacity:.5}.aia-suggestion-item-pressed{opacity:.8;color:var(--aia-color-text-default, #fff);background:rgba(var(--aia-streak-rgb, 255, 255, 255),.06);animation:aiaGlassFade .5s ease forwards,aiaTapDown .5s ease forwards}@keyframes aiaTapDown{0%{transform:scale(1)}30%{transform:scale(.97)}to{transform:scale(1)}}@keyframes aiaGlassFade{0%{background:var(--aia-streak-glass-bg, rgba(255, 255, 255, .1))}to{background:transparent}}.aia-suggestion-streaks{position:absolute;inset:0;z-index:1;pointer-events:none;border-radius:inherit;overflow:hidden}.aia-suggestion-streaks:before{content:\"\";position:absolute;bottom:-3px;left:60%;width:0;height:6px;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.5) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.2) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.2);filter:blur(1px);border-radius:50%}.aia-suggestion-streaks:after{content:\"\";position:absolute;top:-3px;right:60%;width:0;height:6px;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.5) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.2) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.2);filter:blur(1px);border-radius:50%}.aia-suggestion-streaks-vert{position:absolute;inset:0;z-index:1;pointer-events:none;border-radius:inherit;overflow:hidden}.aia-suggestion-streaks-vert:before{content:\"\";position:absolute;bottom:0;right:-3px;width:6px;height:0;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.4) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.15) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.15);filter:blur(1px);border-radius:50%}.aia-suggestion-streaks-vert:after{content:\"\";position:absolute;top:0;left:-3px;width:6px;height:0;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.4) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.15) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.15);filter:blur(1px);border-radius:50%}.aia-suggestion-item-pressed .aia-suggestion-streaks:before{animation:aiaStreakHorizRight .5s cubic-bezier(.4,0,.2,1) forwards}.aia-suggestion-item-pressed .aia-suggestion-streaks:after{animation:aiaStreakHorizLeft .5s cubic-bezier(.4,0,.2,1) forwards}.aia-suggestion-item-pressed .aia-suggestion-streaks-vert:before{animation:aiaStreakVertUp .3s cubic-bezier(.3,0,.2,1) .2s forwards}.aia-suggestion-item-pressed .aia-suggestion-streaks-vert:after{animation:aiaStreakVertDown .3s cubic-bezier(.3,0,.2,1) .2s forwards}@keyframes aiaStreakHorizRight{0%{width:0;height:4px;opacity:0;filter:blur(1px);box-shadow:0 0 8px 3px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}15%{height:4px;opacity:1;filter:blur(1px);box-shadow:0 0 10px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}80%{width:50%;height:10px;opacity:.8;filter:blur(3px);box-shadow:0 0 16px 6px rgba(var(--aia-streak-rgb, 255, 255, 255),.1)}to{width:50%;height:12px;opacity:0;filter:blur(5px);box-shadow:0 0 20px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.03)}}@keyframes aiaStreakHorizLeft{0%{width:0;height:4px;opacity:0;filter:blur(1px);box-shadow:0 0 8px 3px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}15%{height:4px;opacity:1;filter:blur(1px);box-shadow:0 0 10px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}80%{width:50%;height:10px;opacity:.8;filter:blur(3px);box-shadow:0 0 16px 6px rgba(var(--aia-streak-rgb, 255, 255, 255),.1)}to{width:50%;height:12px;opacity:0;filter:blur(5px);box-shadow:0 0 20px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.03)}}@keyframes aiaStreakVertUp{0%{height:0;width:6px;opacity:.9;filter:blur(1.8px);box-shadow:0 0 12px 5px rgba(var(--aia-streak-rgb, 255, 255, 255),.25)}75%{height:100%;width:10px;opacity:.4;filter:blur(3px);box-shadow:0 0 18px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.06)}to{height:100%;width:14px;opacity:0;filter:blur(5px);box-shadow:0 0 24px 10px rgba(var(--aia-streak-rgb, 255, 255, 255),.02)}}@keyframes aiaStreakVertDown{0%{height:0;width:6px;opacity:.9;filter:blur(1.8px);box-shadow:0 0 12px 5px rgba(var(--aia-streak-rgb, 255, 255, 255),.25)}75%{height:100%;width:10px;opacity:.4;filter:blur(3px);box-shadow:0 0 18px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.06)}to{height:100%;width:14px;opacity:0;filter:blur(5px);box-shadow:0 0 24px 10px rgba(var(--aia-streak-rgb, 255, 255, 255),.02)}}.aia-suggestion-item[data-aia-loading]{cursor:default;animation:aiaSuggestionSkeletonPulse 1.4s ease-in-out infinite}.aia-suggestion-item[data-aia-loading] .aia-suggestion-text{color:transparent;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));border-radius:999px;-webkit-box-decoration-break:clone;box-decoration-break:clone}.aia-suggestion-item[data-aia-loading] .aia-suggestion-tag{display:none}@keyframes aiaSuggestionSkeletonPulse{0%,to{filter:brightness(1)}50%{filter:brightness(.55)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionItemComponent, decorators: [{
|
|
127
|
+
type: Component,
|
|
128
|
+
args: [{ selector: "aia-suggestion-item", standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n [id]=\"id\"\n role=\"option\"\n data-aia-option=\"\"\n [attr.data-aia-loading]=\"loading ? '' : null\"\n [attr.aria-selected]=\"isHighlighted\"\n class=\"aia-suggestion-item\"\n [class.aia-suggestion-item-highlighted]=\"isHighlighted && !loading\"\n [class.aia-suggestion-item-tappable]=\"option.is_tappable\"\n [class.aia-suggestion-item-non-tappable]=\"!option.is_tappable\"\n [class.aia-suggestion-item-pressed]=\"pressed\"\n [attr.tabindex]=\"loading || !option.is_tappable ? -1 : 0\"\n (click)=\"onSelect()\"\n (keydown)=\"onKeyDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n <div class=\"aia-suggestion-streaks\"></div>\n <div class=\"aia-suggestion-streaks-vert\"></div>\n <span class=\"aia-suggestion-content\">\n <span class=\"aia-suggestion-text\">\n <ng-container *ngIf=\"option.icon; else noIcon\"\n >{{ option.icon }} {{ option.text }}</ng-container\n >\n <ng-template #noIcon>{{ option.text }}</ng-template>\n </span>\n <span *ngIf=\"option.tag\" class=\"aia-suggestion-tag\">{{ option.tag }}</span>\n </span>\n</div>\n", styles: [".aia-suggestion-item{position:relative;overflow:visible;display:flex;align-items:flex-start;font-family:inherit;font-size:var(--aia-option-font-size, 19px);font-weight:250;line-height:24px;color:var(--aia-option-color, var(--aia-color-text-muted, #c1c4cb));white-space:normal;word-break:break-word;border-radius:12px;padding:13px;animation:aiaSuggestionFadeIn .5s cubic-bezier(.4,0,.2,1) forwards}@keyframes aiaSuggestionFadeIn{0%{opacity:0}}.aia-suggestion-content{position:relative;z-index:2}.aia-suggestion-item-tappable{cursor:pointer}.aia-suggestion-item-tappable:hover{color:var(--aia-option-color-selected, var(--aia-color-text-default, #fff))}.aia-suggestion-item-non-tappable{cursor:default}.aia-suggestion-item-highlighted{color:var(--aia-option-color-selected, var(--aia-color-text-default, #fff));background:var(--aia-option-bg, transparent)}.aia-suggestion-tag{font-size:11px;margin-left:6px;opacity:.5}.aia-suggestion-item-pressed{opacity:.8;color:var(--aia-color-text-default, #fff);background:rgba(var(--aia-streak-rgb, 255, 255, 255),.06);animation:aiaGlassFade .5s ease forwards,aiaTapDown .5s ease forwards}@keyframes aiaTapDown{0%{transform:scale(1)}30%{transform:scale(.97)}to{transform:scale(1)}}@keyframes aiaGlassFade{0%{background:var(--aia-streak-glass-bg, rgba(255, 255, 255, .1))}to{background:transparent}}.aia-suggestion-streaks{position:absolute;inset:0;z-index:1;pointer-events:none;border-radius:inherit;overflow:hidden}.aia-suggestion-streaks:before{content:\"\";position:absolute;bottom:-3px;left:60%;width:0;height:6px;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.5) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.2) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.2);filter:blur(1px);border-radius:50%}.aia-suggestion-streaks:after{content:\"\";position:absolute;top:-3px;right:60%;width:0;height:6px;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.5) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.2) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.2);filter:blur(1px);border-radius:50%}.aia-suggestion-streaks-vert{position:absolute;inset:0;z-index:1;pointer-events:none;border-radius:inherit;overflow:hidden}.aia-suggestion-streaks-vert:before{content:\"\";position:absolute;bottom:0;right:-3px;width:6px;height:0;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.4) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.15) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.15);filter:blur(1px);border-radius:50%}.aia-suggestion-streaks-vert:after{content:\"\";position:absolute;top:0;left:-3px;width:6px;height:0;opacity:0;background:radial-gradient(ellipse at center,rgba(var(--aia-streak-rgb, 255, 255, 255),.4) 0%,rgba(var(--aia-streak-rgb, 255, 255, 255),.15) 40%,transparent 70%);box-shadow:0 0 12px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.15);filter:blur(1px);border-radius:50%}.aia-suggestion-item-pressed .aia-suggestion-streaks:before{animation:aiaStreakHorizRight .5s cubic-bezier(.4,0,.2,1) forwards}.aia-suggestion-item-pressed .aia-suggestion-streaks:after{animation:aiaStreakHorizLeft .5s cubic-bezier(.4,0,.2,1) forwards}.aia-suggestion-item-pressed .aia-suggestion-streaks-vert:before{animation:aiaStreakVertUp .3s cubic-bezier(.3,0,.2,1) .2s forwards}.aia-suggestion-item-pressed .aia-suggestion-streaks-vert:after{animation:aiaStreakVertDown .3s cubic-bezier(.3,0,.2,1) .2s forwards}@keyframes aiaStreakHorizRight{0%{width:0;height:4px;opacity:0;filter:blur(1px);box-shadow:0 0 8px 3px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}15%{height:4px;opacity:1;filter:blur(1px);box-shadow:0 0 10px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}80%{width:50%;height:10px;opacity:.8;filter:blur(3px);box-shadow:0 0 16px 6px rgba(var(--aia-streak-rgb, 255, 255, 255),.1)}to{width:50%;height:12px;opacity:0;filter:blur(5px);box-shadow:0 0 20px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.03)}}@keyframes aiaStreakHorizLeft{0%{width:0;height:4px;opacity:0;filter:blur(1px);box-shadow:0 0 8px 3px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}15%{height:4px;opacity:1;filter:blur(1px);box-shadow:0 0 10px 4px rgba(var(--aia-streak-rgb, 255, 255, 255),.3)}80%{width:50%;height:10px;opacity:.8;filter:blur(3px);box-shadow:0 0 16px 6px rgba(var(--aia-streak-rgb, 255, 255, 255),.1)}to{width:50%;height:12px;opacity:0;filter:blur(5px);box-shadow:0 0 20px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.03)}}@keyframes aiaStreakVertUp{0%{height:0;width:6px;opacity:.9;filter:blur(1.8px);box-shadow:0 0 12px 5px rgba(var(--aia-streak-rgb, 255, 255, 255),.25)}75%{height:100%;width:10px;opacity:.4;filter:blur(3px);box-shadow:0 0 18px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.06)}to{height:100%;width:14px;opacity:0;filter:blur(5px);box-shadow:0 0 24px 10px rgba(var(--aia-streak-rgb, 255, 255, 255),.02)}}@keyframes aiaStreakVertDown{0%{height:0;width:6px;opacity:.9;filter:blur(1.8px);box-shadow:0 0 12px 5px rgba(var(--aia-streak-rgb, 255, 255, 255),.25)}75%{height:100%;width:10px;opacity:.4;filter:blur(3px);box-shadow:0 0 18px 8px rgba(var(--aia-streak-rgb, 255, 255, 255),.06)}to{height:100%;width:14px;opacity:0;filter:blur(5px);box-shadow:0 0 24px 10px rgba(var(--aia-streak-rgb, 255, 255, 255),.02)}}.aia-suggestion-item[data-aia-loading]{cursor:default;animation:aiaSuggestionSkeletonPulse 1.4s ease-in-out infinite}.aia-suggestion-item[data-aia-loading] .aia-suggestion-text{color:transparent;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));border-radius:999px;-webkit-box-decoration-break:clone;box-decoration-break:clone}.aia-suggestion-item[data-aia-loading] .aia-suggestion-tag{display:none}@keyframes aiaSuggestionSkeletonPulse{0%,to{filter:brightness(1)}50%{filter:brightness(.55)}}\n"] }]
|
|
129
|
+
}], propDecorators: { option: [{
|
|
130
|
+
type: Input
|
|
131
|
+
}], isHighlighted: [{
|
|
132
|
+
type: Input
|
|
133
|
+
}], id: [{
|
|
134
|
+
type: Input
|
|
135
|
+
}], loading: [{
|
|
136
|
+
type: Input
|
|
137
|
+
}], select: [{
|
|
138
|
+
type: Output
|
|
139
|
+
}], highlight: [{
|
|
140
|
+
type: Output
|
|
141
|
+
}] } });
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Internal scrollable grid of suggestion options. Mirrors
|
|
145
|
+
* `packages/react/src/components/SuggestionGrid.tsx` — including the bottom
|
|
146
|
+
* fade overlay that appears when there's vertical overflow, driven by a
|
|
147
|
+
* ResizeObserver + scroll listener. ResizeObserver runs outside Angular's
|
|
148
|
+
* zone (§16.1) so high-frequency layout events don't trip change detection.
|
|
149
|
+
*
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
class SuggestionGridComponent {
|
|
153
|
+
constructor() {
|
|
154
|
+
this.options = [];
|
|
155
|
+
this.activeIndex = -1;
|
|
156
|
+
this.listboxId = "";
|
|
157
|
+
this.loading = false;
|
|
158
|
+
this.select = new EventEmitter();
|
|
159
|
+
this.highlight = new EventEmitter();
|
|
160
|
+
this.hasBottomOverflow = false;
|
|
161
|
+
this.zone = inject(NgZone);
|
|
162
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
163
|
+
this.trackByOptionText = (_, opt) => opt.text;
|
|
164
|
+
}
|
|
165
|
+
ngAfterViewInit() {
|
|
166
|
+
this.bindOverflowTracking();
|
|
167
|
+
this.measureOverflow();
|
|
168
|
+
}
|
|
169
|
+
ngOnChanges() {
|
|
170
|
+
// Synchronously re-measure when options change — matches React's
|
|
171
|
+
// useLayoutEffect on [options]. ResizeObserver alone misses this case
|
|
172
|
+
// because the observed box doesn't grow when the grid hits max-height.
|
|
173
|
+
queueMicrotask(() => this.measureOverflow());
|
|
174
|
+
}
|
|
175
|
+
ngOnDestroy() {
|
|
176
|
+
var _a;
|
|
177
|
+
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
178
|
+
if (this.scrollListener && this.gridRef) {
|
|
179
|
+
this.gridRef.nativeElement.removeEventListener("scroll", this.scrollListener);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
onSelectItem(option) {
|
|
183
|
+
this.select.emit(option);
|
|
184
|
+
}
|
|
185
|
+
onHighlightItem(index) {
|
|
186
|
+
this.highlight.emit(index);
|
|
187
|
+
}
|
|
188
|
+
bindOverflowTracking() {
|
|
189
|
+
var _a;
|
|
190
|
+
const grid = (_a = this.gridRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
191
|
+
if (!grid)
|
|
192
|
+
return;
|
|
193
|
+
this.zone.runOutsideAngular(() => {
|
|
194
|
+
this.scrollListener = () => this.measureOverflow();
|
|
195
|
+
grid.addEventListener("scroll", this.scrollListener, { passive: true });
|
|
196
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
197
|
+
this.resizeObserver = new ResizeObserver(() => this.measureOverflow());
|
|
198
|
+
this.resizeObserver.observe(grid);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
measureOverflow() {
|
|
203
|
+
var _a;
|
|
204
|
+
const grid = (_a = this.gridRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
205
|
+
if (!grid)
|
|
206
|
+
return;
|
|
207
|
+
const next = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;
|
|
208
|
+
if (next !== this.hasBottomOverflow) {
|
|
209
|
+
this.zone.run(() => {
|
|
210
|
+
this.hasBottomOverflow = next;
|
|
211
|
+
this.cdr.markForCheck();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
SuggestionGridComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
217
|
+
SuggestionGridComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: SuggestionGridComponent, isStandalone: true, selector: "aia-suggestion-grid", inputs: { options: "options", activeIndex: "activeIndex", listboxId: "listboxId", loading: "loading" }, outputs: { select: "select", highlight: "highlight" }, viewQueries: [{ propertyName: "gridRef", first: true, predicate: ["grid"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"aia-suggestion-grid-scroll\"\n [attr.data-fade]=\"hasBottomOverflow ? '' : null\"\n>\n <div #grid class=\"aia-suggestion-grid\">\n <aia-suggestion-item\n *ngFor=\"let option of options; trackBy: trackByOptionText; let i = index\"\n [option]=\"option\"\n [isHighlighted]=\"i === activeIndex\"\n [id]=\"listboxId + '-option-' + i\"\n [loading]=\"loading\"\n (select)=\"onSelectItem($event)\"\n (highlight)=\"onHighlightItem(i)\"\n >\n </aia-suggestion-item>\n </div>\n</div>\n", styles: [".aia-suggestion-grid-scroll{position:relative}.aia-suggestion-grid-scroll:after{content:\"\";position:absolute;left:0;right:0;bottom:0;height:50%;pointer-events:none;opacity:0;transition:opacity .15s ease-out;backdrop-filter:blur(12px);mask-image:linear-gradient(to bottom,transparent,white)}.aia-suggestion-grid-scroll[data-fade]:after{opacity:1}.aia-suggestion-grid{display:grid;grid-template-columns:minmax(0,250px);grid-auto-rows:min-content;align-content:start;max-width:100cqi;padding:8px;max-height:200px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3)) transparent}@container (min-width: 516px){.aia-suggestion-grid{grid-template-columns:repeat(2,minmax(0,250px));justify-content:start}}.aia-suggestion-grid::-webkit-scrollbar{width:6px}.aia-suggestion-grid::-webkit-scrollbar-track{background:transparent}.aia-suggestion-grid::-webkit-scrollbar-thumb{background:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3));border-radius:3px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: SuggestionItemComponent, selector: "aia-suggestion-item", inputs: ["option", "isHighlighted", "id", "loading"], outputs: ["select", "highlight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
218
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionGridComponent, decorators: [{
|
|
219
|
+
type: Component,
|
|
220
|
+
args: [{ selector: "aia-suggestion-grid", standalone: true, imports: [CommonModule, SuggestionItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n class=\"aia-suggestion-grid-scroll\"\n [attr.data-fade]=\"hasBottomOverflow ? '' : null\"\n>\n <div #grid class=\"aia-suggestion-grid\">\n <aia-suggestion-item\n *ngFor=\"let option of options; trackBy: trackByOptionText; let i = index\"\n [option]=\"option\"\n [isHighlighted]=\"i === activeIndex\"\n [id]=\"listboxId + '-option-' + i\"\n [loading]=\"loading\"\n (select)=\"onSelectItem($event)\"\n (highlight)=\"onHighlightItem(i)\"\n >\n </aia-suggestion-item>\n </div>\n</div>\n", styles: [".aia-suggestion-grid-scroll{position:relative}.aia-suggestion-grid-scroll:after{content:\"\";position:absolute;left:0;right:0;bottom:0;height:50%;pointer-events:none;opacity:0;transition:opacity .15s ease-out;backdrop-filter:blur(12px);mask-image:linear-gradient(to bottom,transparent,white)}.aia-suggestion-grid-scroll[data-fade]:after{opacity:1}.aia-suggestion-grid{display:grid;grid-template-columns:minmax(0,250px);grid-auto-rows:min-content;align-content:start;max-width:100cqi;padding:8px;max-height:200px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3)) transparent}@container (min-width: 516px){.aia-suggestion-grid{grid-template-columns:repeat(2,minmax(0,250px));justify-content:start}}.aia-suggestion-grid::-webkit-scrollbar{width:6px}.aia-suggestion-grid::-webkit-scrollbar-track{background:transparent}.aia-suggestion-grid::-webkit-scrollbar-thumb{background:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3));border-radius:3px}\n"] }]
|
|
221
|
+
}], propDecorators: { options: [{
|
|
222
|
+
type: Input
|
|
223
|
+
}], activeIndex: [{
|
|
224
|
+
type: Input
|
|
225
|
+
}], listboxId: [{
|
|
226
|
+
type: Input
|
|
227
|
+
}], loading: [{
|
|
228
|
+
type: Input
|
|
229
|
+
}], select: [{
|
|
230
|
+
type: Output
|
|
231
|
+
}], highlight: [{
|
|
232
|
+
type: Output
|
|
233
|
+
}], gridRef: [{
|
|
234
|
+
type: ViewChild,
|
|
235
|
+
args: ["grid", { static: false }]
|
|
236
|
+
}] } });
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Tier 2 dropdown component — the Angular equivalent of React's
|
|
240
|
+
* `<AIAutocompleteDropdown />`. Renders the pillbar, options grid, and
|
|
241
|
+
* loading skeleton based on the supplied controller's `dropdown$` snapshot.
|
|
242
|
+
*
|
|
243
|
+
* ```html
|
|
244
|
+
* <ai-autocomplete-dropdown [controller]="ac"></ai-autocomplete-dropdown>
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* All actions (pill click, option select, option highlight) are forwarded to
|
|
248
|
+
* the controller. Consumers who need to observe these events can subscribe
|
|
249
|
+
* to the controller's per-field observables.
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
253
|
+
class AIAutocompleteDropdownComponent {
|
|
254
|
+
constructor() {
|
|
255
|
+
/** Whether to render the pillbar above the options grid. Default: true. */
|
|
256
|
+
this.showPills = true;
|
|
257
|
+
/** Fallback skeleton-bar widths used when loading with zero cached options. */
|
|
258
|
+
this.fallbackSkeletonBarWidths = [159, 119, 164];
|
|
259
|
+
this.trackByWidth = (_, w) => w;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* View-model derived from the controller's `dropdown$`. Adds three booleans
|
|
263
|
+
* (`showsPills`, `showsOptions`, `showsFallbackSkeleton`, `isVisible`) that
|
|
264
|
+
* keep template-level logic to a property read per §19.6.
|
|
265
|
+
*/
|
|
266
|
+
get vm$() {
|
|
267
|
+
return this.controller.dropdown$.pipe(map((dd) => {
|
|
268
|
+
var _a, _b;
|
|
269
|
+
const options = (_b = (_a = dd.suggestions[0]) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : [];
|
|
270
|
+
const showsPills = this.showPills && (dd.pills.length > 0 || dd.isLoading);
|
|
271
|
+
const showsOptions = options.length > 0;
|
|
272
|
+
const showsFallbackSkeleton = dd.isLoading && !showsOptions;
|
|
273
|
+
const isVisible = dd.isOpen && (showsOptions || showsPills || dd.isLoading);
|
|
274
|
+
return {
|
|
275
|
+
pills: dd.pills,
|
|
276
|
+
options,
|
|
277
|
+
activeIndex: dd.activeIndex,
|
|
278
|
+
isLoading: dd.isLoading,
|
|
279
|
+
showsPills,
|
|
280
|
+
showsOptions,
|
|
281
|
+
showsFallbackSkeleton,
|
|
282
|
+
isVisible,
|
|
283
|
+
};
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
onPillClick(index) {
|
|
287
|
+
this.controller.setActivePill(index);
|
|
288
|
+
}
|
|
289
|
+
onSelect(option) {
|
|
290
|
+
this.controller.selectOption(option);
|
|
291
|
+
}
|
|
292
|
+
onHighlight(index) {
|
|
293
|
+
this.controller.setActiveDropdownIndex(index);
|
|
294
|
+
}
|
|
295
|
+
preventBlur(event) {
|
|
296
|
+
// Prevent the editor from losing focus when interacting with the dropdown.
|
|
297
|
+
event.preventDefault();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
AIAutocompleteDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
301
|
+
AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n class=\"magicx-aia-dropdown\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }, { kind: "component", type: SuggestionGridComponent, selector: "aia-suggestion-grid", inputs: ["options", "activeIndex", "listboxId", "loading"], outputs: ["select", "highlight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
302
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, decorators: [{
|
|
303
|
+
type: Component,
|
|
304
|
+
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [CommonModule, PillListComponent, SuggestionGridComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n class=\"magicx-aia-dropdown\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"] }]
|
|
305
|
+
}], propDecorators: { controller: [{
|
|
306
|
+
type: Input
|
|
307
|
+
}], showPills: [{
|
|
308
|
+
type: Input
|
|
309
|
+
}], className: [{
|
|
310
|
+
type: Input
|
|
311
|
+
}] } });
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Per-field observable derivation factory. Enforces the §19.4 invariant:
|
|
315
|
+
*
|
|
316
|
+
* - `distinctUntilChanged` — prevents re-emission storms when the upstream
|
|
317
|
+
* state object ticks but the field a consumer cares about hasn't changed.
|
|
318
|
+
* Without this, every keystroke would re-render every async-pipe binding
|
|
319
|
+
* in the consumer's template even when nothing changed for that binding.
|
|
320
|
+
* - `shareReplay({ bufferSize: 1, refCount: true })` — guarantees that
|
|
321
|
+
* multiple subscribers (multiple `async` pipes in a template, e.g.
|
|
322
|
+
* `*ngIf="isLoading$ | async"` and `[disabled]="isLoading$ | async"`)
|
|
323
|
+
* share a single upstream subscription. `refCount: true` is non-obvious
|
|
324
|
+
* but essential: without it, the inner subscription survives after the
|
|
325
|
+
* last subscriber unmounts, leaking the chain back to `state$`.
|
|
326
|
+
*
|
|
327
|
+
* Every public observable on {@link AIAutocompleteController} must go through
|
|
328
|
+
* this factory — no exceptions.
|
|
329
|
+
*
|
|
330
|
+
* @internal
|
|
331
|
+
*/
|
|
332
|
+
function deriveField(source$, selector, equalityFn) {
|
|
333
|
+
return source$.pipe(map$1(selector), distinctUntilChanged(equalityFn), shareReplay({ bufferSize: 1, refCount: true }));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Tier 2 logic surface for `@magicx-eng/ai-autocomplete-angular` — the Angular
|
|
338
|
+
* equivalent of React's `useAIAutocomplete()` hook. Wraps a headless
|
|
339
|
+
* `@magicx-eng/ai-autocomplete-vanilla` core and exposes its state as RxJS
|
|
340
|
+
* observables plus thin action methods.
|
|
341
|
+
*
|
|
342
|
+
* Per TDB §3.3, the controller is consumer-managed — the consumer creates it
|
|
343
|
+
* with `new`, holds the reference, and **must call `destroy()`** in their
|
|
344
|
+
* own `ngOnDestroy`. The Tier 1 `<ai-autocomplete>` component does this
|
|
345
|
+
* automatically; Tier 2 consumers do not get auto-cleanup.
|
|
346
|
+
*
|
|
347
|
+
* @public
|
|
348
|
+
*/
|
|
349
|
+
class AIAutocompleteController {
|
|
350
|
+
/**
|
|
351
|
+
* Throws if invoked outside a browser environment. For SSR / Angular
|
|
352
|
+
* Universal, gate construction with `isPlatformBrowser` — see TDB §16.3.
|
|
353
|
+
*/
|
|
354
|
+
constructor(opts = {}) {
|
|
355
|
+
var _a, _b;
|
|
356
|
+
this.isDestroyed = false;
|
|
357
|
+
if (typeof document === "undefined") {
|
|
358
|
+
throw new Error("AIAutocompleteController requires a browser environment. " +
|
|
359
|
+
"For SSR / Angular Universal, gate construction with isPlatformBrowser.");
|
|
360
|
+
}
|
|
361
|
+
// Headless mode never renders to the host, so a throwaway div is fine —
|
|
362
|
+
// it's required by the core's signature for symmetry with full mode.
|
|
363
|
+
const host = document.createElement("div");
|
|
364
|
+
this.core = new AIAutocomplete(host, Object.assign(Object.assign({}, opts), { renderMode: "headless", source: (_a = opts.source) !== null && _a !== void 0 ? _a : "headless-sdk",
|
|
365
|
+
// Headless mode never renders the dropdown DOM, but the keyboard
|
|
366
|
+
// controller still reads `optionsPosition` to decide which vertical
|
|
367
|
+
// arrow opens the dropdown from the input.
|
|
368
|
+
optionsPosition: (_b = opts.optionsPosition) !== null && _b !== void 0 ? _b : "below" }));
|
|
369
|
+
this.rawState$ = new BehaviorSubject(this.core.getState());
|
|
370
|
+
this.unsubscribeCore = this.core.subscribe((state) => {
|
|
371
|
+
if (!this.isDestroyed)
|
|
372
|
+
this.rawState$.next(state);
|
|
373
|
+
});
|
|
374
|
+
// === Per-field observables ===
|
|
375
|
+
this.text$ = deriveField(this.rawState$, (s) => s.text);
|
|
376
|
+
this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams);
|
|
377
|
+
this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions);
|
|
378
|
+
this.segments$ = deriveField(this.rawState$, (s) => s.segments);
|
|
379
|
+
this.newParamId$ = deriveField(this.rawState$, (s) => s.newParamId);
|
|
380
|
+
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions);
|
|
381
|
+
this.activeIndex$ = deriveField(this.rawState$, (s) => s.activeDropdownIndex);
|
|
382
|
+
this.isLoading$ = deriveField(this.rawState$, computeUiLoading);
|
|
383
|
+
this.isReady$ = deriveField(this.rawState$, (s) => s.isReady);
|
|
384
|
+
this.isFocused$ = deriveField(this.rawState$, (s) => s.isFocused);
|
|
385
|
+
this.isDropdownOpen$ = deriveField(this.rawState$, (s) => s.isDropdownOpen);
|
|
386
|
+
this.placeholderText$ = deriveField(this.rawState$, (s) => s.placeholderText);
|
|
387
|
+
this.error$ = deriveField(this.rawState$, (s) => s.error);
|
|
388
|
+
this.editingParam$ = deriveField(this.rawState$, (s) => s.editingParam);
|
|
389
|
+
this.editingAnchor$ = deriveField(this.rawState$, (s) => s.editingAnchor);
|
|
390
|
+
this.caretOffset$ = deriveField(this.rawState$, (s) => s.caretOffset);
|
|
391
|
+
// === Composite observables ===
|
|
392
|
+
// Project directly off `rawState$` with a single map() so each store
|
|
393
|
+
// tick produces exactly ONE coherent state$ emission. Using
|
|
394
|
+
// `combineLatest` over the per-field observables here would fire one
|
|
395
|
+
// emission per inner-source tick (up to N per store update), and the
|
|
396
|
+
// editor-host directive's post-promote refocus would run against an
|
|
397
|
+
// intermediate state where `newParamId` was set but `segments` /
|
|
398
|
+
// `caretOffset` still held their old values — leaving the caret stuck
|
|
399
|
+
// at the pre-click position.
|
|
400
|
+
this.state$ = this.rawState$.pipe(map$1(computeState), shareReplay({ bufferSize: 1, refCount: true }));
|
|
401
|
+
this.dropdown$ = deriveField(this.rawState$, computeDropdown);
|
|
402
|
+
}
|
|
403
|
+
// === Read-only accessors ===
|
|
404
|
+
/** Stable ARIA listbox ID for the dropdown. Pass to `<textarea aria-controls="...">`. */
|
|
405
|
+
get listboxId() {
|
|
406
|
+
return this.core.listboxId;
|
|
407
|
+
}
|
|
408
|
+
/** Whether `destroy()` has been called. After destruction all action methods no-op. */
|
|
409
|
+
get destroyed() {
|
|
410
|
+
return this.isDestroyed;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Synchronous snapshot of the controller's current public state. Mirrors
|
|
414
|
+
* the latest emission of `state$`. Useful in callbacks where you can't
|
|
415
|
+
* `subscribe` (e.g. inside an event handler).
|
|
416
|
+
*/
|
|
417
|
+
getState() {
|
|
418
|
+
return computeState(this.rawState$.value);
|
|
419
|
+
}
|
|
420
|
+
// === Action methods (pass-through to the core) ===
|
|
421
|
+
/** Clear all state, rotate `session_id`, and re-fetch. Call after submit. */
|
|
422
|
+
reset() {
|
|
423
|
+
this.core.reset();
|
|
424
|
+
}
|
|
425
|
+
/** Pick a dropdown option (or replace the re-edited param if in re-edit mode). */
|
|
426
|
+
selectOption(option) {
|
|
427
|
+
this.core.selectOption(option);
|
|
428
|
+
}
|
|
429
|
+
/** Promote the pill at `index` to active (front of the list). */
|
|
430
|
+
setActivePill(index) {
|
|
431
|
+
this.core.setActivePill(index);
|
|
432
|
+
}
|
|
433
|
+
/** Drop the last completed param's "completed" status. Text stays as plain text. */
|
|
434
|
+
removeLastParam() {
|
|
435
|
+
this.core.removeLastParam();
|
|
436
|
+
}
|
|
437
|
+
/** Clear the shimmer animation state for the most-recently-added param. */
|
|
438
|
+
clearNewParamId() {
|
|
439
|
+
this.core.clearNewParamId();
|
|
440
|
+
}
|
|
441
|
+
/** Controlled-mode setter for editor text. */
|
|
442
|
+
setValue(text) {
|
|
443
|
+
this.core.setValue(text);
|
|
444
|
+
}
|
|
445
|
+
/** Controlled-mode setter for completed params. */
|
|
446
|
+
setCompletedParams(params) {
|
|
447
|
+
this.core.setCompletedParams(params);
|
|
448
|
+
}
|
|
449
|
+
/** Forward plain-text input from the consumer's `<textarea>` to the core. */
|
|
450
|
+
handleTextChange(value) {
|
|
451
|
+
this.core.handleTextChange(value);
|
|
452
|
+
}
|
|
453
|
+
/** Forward keyboard events from the consumer's `<textarea>` to the core. */
|
|
454
|
+
handleKeyDown(event) {
|
|
455
|
+
this.core.handleKeyDown(event);
|
|
456
|
+
}
|
|
457
|
+
/** Notify the core of focus state. */
|
|
458
|
+
setFocused(focused) {
|
|
459
|
+
this.core.setFocused(focused);
|
|
460
|
+
}
|
|
461
|
+
/** Enter re-edit mode for the bold param with the given id. */
|
|
462
|
+
startEditingParam(paramId) {
|
|
463
|
+
this.core.startEditingParam(paramId);
|
|
464
|
+
}
|
|
465
|
+
/** Exit re-edit mode without changing the bold param. */
|
|
466
|
+
exitEditMode() {
|
|
467
|
+
this.core.exitEditMode();
|
|
468
|
+
}
|
|
469
|
+
/** Forward post-input caret position (extends the edit tail). */
|
|
470
|
+
handleCaretAfterInput(offset) {
|
|
471
|
+
this.core.handleCaretAfterInput(offset);
|
|
472
|
+
}
|
|
473
|
+
/** Forward selection-only caret moves (may exit re-edit). */
|
|
474
|
+
handleCaretMove(offset) {
|
|
475
|
+
this.core.handleCaretMove(offset);
|
|
476
|
+
}
|
|
477
|
+
/** Replace the bold param being re-edited. Returns true when applied. */
|
|
478
|
+
replaceEditingRange(replacement) {
|
|
479
|
+
return this.core.replaceEditingRange(replacement);
|
|
480
|
+
}
|
|
481
|
+
/** Set the highlighted option index (typically driven by hover). */
|
|
482
|
+
setActiveDropdownIndex(index) {
|
|
483
|
+
this.core.setActiveDropdownIndex(index);
|
|
484
|
+
}
|
|
485
|
+
// === Lifecycle ===
|
|
486
|
+
/**
|
|
487
|
+
* Apply runtime changes to a subset of options. Event callbacks
|
|
488
|
+
* (`onSubmit`, `onError`, etc.) cannot be swapped this way — they are
|
|
489
|
+
* registered once at construction (matches the React hook's design). For
|
|
490
|
+
* dynamic event reactivity, subscribe to the corresponding observable
|
|
491
|
+
* (e.g. `text$` instead of `onChange`).
|
|
492
|
+
*/
|
|
493
|
+
update(opts) {
|
|
494
|
+
this.core.update(opts);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Release all resources — unsubscribes from the core, destroys the core,
|
|
498
|
+
* and completes the internal state subject. Safe to call multiple times.
|
|
499
|
+
* After destruction, action methods no-op silently.
|
|
500
|
+
*/
|
|
501
|
+
destroy() {
|
|
502
|
+
if (this.isDestroyed)
|
|
503
|
+
return;
|
|
504
|
+
this.isDestroyed = true;
|
|
505
|
+
this.unsubscribeCore();
|
|
506
|
+
this.core.destroy();
|
|
507
|
+
this.rawState$.complete();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// ===== Pure helpers — not part of the public surface =====
|
|
511
|
+
function computeUiLoading(s) {
|
|
512
|
+
return s.isLoading && !s.editingParam && !s.inSelectionAnimation;
|
|
513
|
+
}
|
|
514
|
+
function computeState(s) {
|
|
515
|
+
return {
|
|
516
|
+
text: s.text,
|
|
517
|
+
completedParams: s.completedParams,
|
|
518
|
+
suggestionPills: s.actionableSuggestions,
|
|
519
|
+
segments: s.segments,
|
|
520
|
+
newParamId: s.newParamId,
|
|
521
|
+
suggestions: s.suggestions,
|
|
522
|
+
activeIndex: s.activeDropdownIndex,
|
|
523
|
+
isLoading: computeUiLoading(s),
|
|
524
|
+
isReady: s.isReady,
|
|
525
|
+
isFocused: s.isFocused,
|
|
526
|
+
isDropdownOpen: s.isDropdownOpen,
|
|
527
|
+
placeholderText: s.placeholderText,
|
|
528
|
+
error: s.error,
|
|
529
|
+
editingParam: s.editingParam,
|
|
530
|
+
editingAnchor: s.editingAnchor,
|
|
531
|
+
caretOffset: s.caretOffset,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function computeDropdown(s) {
|
|
535
|
+
const editingParam = s.editingParam;
|
|
536
|
+
const activeSuggestion = s.actionableSuggestions[0];
|
|
537
|
+
const dropdownPill = editingParam
|
|
538
|
+
? {
|
|
539
|
+
type: editingParam.suggestionType,
|
|
540
|
+
text: editingParam.suggestionPlaceholder,
|
|
541
|
+
required: true,
|
|
542
|
+
options: editingParam.options,
|
|
543
|
+
}
|
|
544
|
+
: null;
|
|
545
|
+
const dropdownActivePill = dropdownPill !== null && dropdownPill !== void 0 ? dropdownPill : activeSuggestion;
|
|
546
|
+
const dropdownPills = dropdownPill ? [dropdownPill] : s.actionableSuggestions;
|
|
547
|
+
return {
|
|
548
|
+
suggestions: dropdownActivePill ? [Object.assign(Object.assign({}, dropdownActivePill), { options: s.filteredOptions })] : [],
|
|
549
|
+
activeIndex: s.activeDropdownIndex,
|
|
550
|
+
isOpen: s.isDropdownOpen,
|
|
551
|
+
pills: dropdownPills,
|
|
552
|
+
isLoading: computeUiLoading(s),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Module-level cache for the one-time plaintext-only feature detection.
|
|
558
|
+
* Mirrors `supportsPlaintextOnly` in useContentEditableEditor.ts.
|
|
559
|
+
*/
|
|
560
|
+
let plaintextOnlyCache;
|
|
561
|
+
function supportsPlaintextOnly() {
|
|
562
|
+
if (plaintextOnlyCache !== undefined)
|
|
563
|
+
return plaintextOnlyCache;
|
|
564
|
+
if (typeof document === "undefined")
|
|
565
|
+
return false;
|
|
566
|
+
const probe = document.createElement("div");
|
|
567
|
+
probe.setAttribute("contenteditable", "plaintext-only");
|
|
568
|
+
plaintextOnlyCache = probe.contentEditable === "plaintext-only";
|
|
569
|
+
return plaintextOnlyCache;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Internal directive that owns every concern specific to running the
|
|
573
|
+
* autocomplete on top of a contentEditable host: composition state, caret
|
|
574
|
+
* tracking, selectionchange handling, `beforeinput` interception, paste
|
|
575
|
+
* sanitization, plaintext-only feature detection, segment rendering,
|
|
576
|
+
* post-promote refocus, and re-edit caret park.
|
|
577
|
+
*
|
|
578
|
+
* Mirrors `packages/react/src/hooks/useContentEditableEditor.ts` one-for-one.
|
|
579
|
+
* Used only by the Tier 1 `<ai-autocomplete>` component — never exported.
|
|
580
|
+
*
|
|
581
|
+
* @internal
|
|
582
|
+
*/
|
|
583
|
+
class AIAutocompleteEditorHostDirective {
|
|
584
|
+
constructor() {
|
|
585
|
+
this.autoFocus = true;
|
|
586
|
+
this.el = inject(ElementRef);
|
|
587
|
+
this.renderer = inject(Renderer2);
|
|
588
|
+
this.zone = inject(NgZone);
|
|
589
|
+
this.destroy$ = new Subject();
|
|
590
|
+
this.composing = false;
|
|
591
|
+
this.lastInputAt = 0;
|
|
592
|
+
this.lastSeenParamId = "";
|
|
593
|
+
this.lastEditingId = "";
|
|
594
|
+
this.editingParamId = null;
|
|
595
|
+
}
|
|
596
|
+
ngOnInit() {
|
|
597
|
+
if (!this.controller) {
|
|
598
|
+
throw new Error("[aiaEditorHost] requires a controller");
|
|
599
|
+
}
|
|
600
|
+
const el = this.el.nativeElement;
|
|
601
|
+
const ceMode = supportsPlaintextOnly() ? "plaintext-only" : "true";
|
|
602
|
+
this.renderer.setAttribute(el, "contenteditable", ceMode);
|
|
603
|
+
this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
|
|
604
|
+
this.renderer.setAttribute(el, "suppress-content-editable-warning", "true");
|
|
605
|
+
// Auto-focus on mount. If the editor already has focus (e.g. from a
|
|
606
|
+
// click that fired before our lifecycle ran), notify the controller
|
|
607
|
+
// instead of stealing focus.
|
|
608
|
+
if (this.autoFocus) {
|
|
609
|
+
queueMicrotask(() => {
|
|
610
|
+
if (typeof document === "undefined")
|
|
611
|
+
return;
|
|
612
|
+
if (document.activeElement === el) {
|
|
613
|
+
this.controller.setFocused(true);
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
el.focus();
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
this.bindDocumentSelectionListener();
|
|
621
|
+
this.bindBeforeInputListener();
|
|
622
|
+
this.bindStateSubscription();
|
|
623
|
+
}
|
|
624
|
+
ngOnDestroy() {
|
|
625
|
+
this.destroy$.next();
|
|
626
|
+
this.destroy$.complete();
|
|
627
|
+
if (this.documentSelectionListener && typeof document !== "undefined") {
|
|
628
|
+
document.removeEventListener("selectionchange", this.documentSelectionListener);
|
|
629
|
+
}
|
|
630
|
+
if (this.beforeInputListener) {
|
|
631
|
+
this.el.nativeElement.removeEventListener("beforeinput", this.beforeInputListener);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// === Host event handlers ===
|
|
635
|
+
onCompositionStart() {
|
|
636
|
+
this.composing = true;
|
|
637
|
+
}
|
|
638
|
+
onCompositionEnd() {
|
|
639
|
+
this.composing = false;
|
|
640
|
+
this.fireInput();
|
|
641
|
+
}
|
|
642
|
+
onInput() {
|
|
643
|
+
this.lastInputAt = performance.now();
|
|
644
|
+
this.fireInput();
|
|
645
|
+
this.controller.handleCaretAfterInput(getCursorOffset(this.el.nativeElement));
|
|
646
|
+
}
|
|
647
|
+
onKeyDown(event) {
|
|
648
|
+
this.controller.handleKeyDown(event);
|
|
649
|
+
}
|
|
650
|
+
onPaste(event) {
|
|
651
|
+
var _a, _b, _c;
|
|
652
|
+
event.preventDefault();
|
|
653
|
+
const el = this.el.nativeElement;
|
|
654
|
+
const text = ((_b = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData("text/plain")) !== null && _b !== void 0 ? _b : "").replace(/\r?\n/g, " ");
|
|
655
|
+
if (!text)
|
|
656
|
+
return;
|
|
657
|
+
const doc = (_c = el.ownerDocument) !== null && _c !== void 0 ? _c : document;
|
|
658
|
+
const sel = doc.getSelection();
|
|
659
|
+
if (!sel || sel.rangeCount === 0)
|
|
660
|
+
return;
|
|
661
|
+
const range = sel.getRangeAt(0);
|
|
662
|
+
if (!el.contains(range.startContainer))
|
|
663
|
+
return;
|
|
664
|
+
range.deleteContents();
|
|
665
|
+
const node = doc.createTextNode(text);
|
|
666
|
+
range.insertNode(node);
|
|
667
|
+
range.setStartAfter(node);
|
|
668
|
+
range.collapse(true);
|
|
669
|
+
sel.removeAllRanges();
|
|
670
|
+
sel.addRange(range);
|
|
671
|
+
this.fireInput();
|
|
672
|
+
}
|
|
673
|
+
onFocus() {
|
|
674
|
+
this.controller.setFocused(true);
|
|
675
|
+
}
|
|
676
|
+
onBlur() {
|
|
677
|
+
this.controller.setFocused(false);
|
|
678
|
+
}
|
|
679
|
+
// === Internal helpers ===
|
|
680
|
+
fireInput() {
|
|
681
|
+
if (this.composing)
|
|
682
|
+
return;
|
|
683
|
+
const el = this.el.nativeElement;
|
|
684
|
+
const raw = extractPlainText(el);
|
|
685
|
+
const shouldCapitalize = raw.length > 0 && raw[0] !== raw[0].toUpperCase();
|
|
686
|
+
const next = shouldCapitalize ? raw[0].toUpperCase() + raw.slice(1) : raw;
|
|
687
|
+
this.controller.handleTextChange(next);
|
|
688
|
+
}
|
|
689
|
+
bindDocumentSelectionListener() {
|
|
690
|
+
if (typeof document === "undefined")
|
|
691
|
+
return;
|
|
692
|
+
const el = this.el.nativeElement;
|
|
693
|
+
// selectionchange fires on every keystroke and caret move — run outside
|
|
694
|
+
// Angular's zone (§16.1) and re-enter only when we actually need to
|
|
695
|
+
// notify the controller of a meaningful change.
|
|
696
|
+
this.zone.runOutsideAngular(() => {
|
|
697
|
+
const handler = () => {
|
|
698
|
+
var _a;
|
|
699
|
+
const sel = document.getSelection();
|
|
700
|
+
if (!sel || sel.rangeCount === 0)
|
|
701
|
+
return;
|
|
702
|
+
if (!sel.anchorNode || !el.contains(sel.anchorNode))
|
|
703
|
+
return;
|
|
704
|
+
const anchor = sel.anchorNode;
|
|
705
|
+
const startEl = anchor.nodeType === Node.ELEMENT_NODE ? anchor : anchor.parentElement;
|
|
706
|
+
const strong = startEl === null || startEl === void 0 ? void 0 : startEl.closest('strong[data-seg="completed"][data-param-id]');
|
|
707
|
+
const enclosing = (_a = strong === null || strong === void 0 ? void 0 : strong.dataset.paramId) !== null && _a !== void 0 ? _a : null;
|
|
708
|
+
if (enclosing && enclosing !== this.editingParamId) {
|
|
709
|
+
this.zone.run(() => this.controller.startEditingParam(enclosing));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (performance.now() - this.lastInputAt < 50)
|
|
713
|
+
return;
|
|
714
|
+
this.zone.run(() => this.controller.handleCaretMove(getCursorOffset(el)));
|
|
715
|
+
};
|
|
716
|
+
this.documentSelectionListener = handler;
|
|
717
|
+
document.addEventListener("selectionchange", handler);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
bindBeforeInputListener() {
|
|
721
|
+
const el = this.el.nativeElement;
|
|
722
|
+
// Native beforeinput — React's synthetic onBeforeInput is wired to the
|
|
723
|
+
// legacy textInput event and doesn't fire reliably for delete* types.
|
|
724
|
+
const handler = (event) => {
|
|
725
|
+
var _a;
|
|
726
|
+
const inputEvent = event;
|
|
727
|
+
const t = inputEvent.inputType;
|
|
728
|
+
if (t === "insertParagraph" || t === "insertLineBreak" || t === "insertFromDrop") {
|
|
729
|
+
event.preventDefault();
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (t.startsWith("insert") || t.startsWith("delete")) {
|
|
733
|
+
const replacement = t.startsWith("delete") ? "" : ((_a = inputEvent.data) !== null && _a !== void 0 ? _a : "");
|
|
734
|
+
if (this.controller.replaceEditingRange(replacement)) {
|
|
735
|
+
event.preventDefault();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
this.beforeInputListener = handler;
|
|
740
|
+
el.addEventListener("beforeinput", handler);
|
|
741
|
+
}
|
|
742
|
+
bindStateSubscription() {
|
|
743
|
+
const el = this.el.nativeElement;
|
|
744
|
+
this.controller.state$.pipe(takeUntil(this.destroy$)).subscribe((state) => {
|
|
745
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
746
|
+
this.editingParamId = (_b = (_a = state.editingParam) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
747
|
+
renderEditableContent({
|
|
748
|
+
input: el,
|
|
749
|
+
segments: state.segments,
|
|
750
|
+
newParamId: state.newParamId,
|
|
751
|
+
editingParamId: (_d = (_c = state.editingParam) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null,
|
|
752
|
+
placeholderText: state.placeholderText,
|
|
753
|
+
isFocused: state.isFocused,
|
|
754
|
+
});
|
|
755
|
+
// Post-promote refocus + caret-after-bold placement (mirrors React's
|
|
756
|
+
// useLayoutEffect on newParamId).
|
|
757
|
+
const currentParamId = (_e = state.newParamId) !== null && _e !== void 0 ? _e : "";
|
|
758
|
+
if (currentParamId && currentParamId !== this.lastSeenParamId) {
|
|
759
|
+
this.lastSeenParamId = currentParamId;
|
|
760
|
+
el.focus();
|
|
761
|
+
const desired = (_f = state.caretOffset) !== null && _f !== void 0 ? _f : plainTextLength(el);
|
|
762
|
+
setCursorOffset(el, desired);
|
|
763
|
+
}
|
|
764
|
+
else if (!currentParamId) {
|
|
765
|
+
this.lastSeenParamId = "";
|
|
766
|
+
}
|
|
767
|
+
// Re-edit caret park (mirrors React's useLayoutEffect on editingParam).
|
|
768
|
+
const editingId = (_h = (_g = state.editingParam) === null || _g === void 0 ? void 0 : _g.id) !== null && _h !== void 0 ? _h : "";
|
|
769
|
+
if (editingId && editingId !== this.lastEditingId && state.editingAnchor != null) {
|
|
770
|
+
this.lastEditingId = editingId;
|
|
771
|
+
setCursorOffset(el, state.editingAnchor);
|
|
772
|
+
}
|
|
773
|
+
else if (!editingId) {
|
|
774
|
+
this.lastEditingId = "";
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
AIAutocompleteEditorHostDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteEditorHostDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
780
|
+
AIAutocompleteEditorHostDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteEditorHostDirective, isStandalone: true, selector: "[aiaEditorHost]", inputs: { controller: ["aiaEditorHost", "controller"], autoFocus: "autoFocus" }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list", "aria-haspopup": "listbox", "spellcheck": "true", "enterkeyhint": "send", "tabindex": "0" }, listeners: { "input": "onInput()", "keydown": "onKeyDown($event)", "compositionstart": "onCompositionStart()", "compositionend": "onCompositionEnd()", "paste": "onPaste($event)", "focus": "onFocus()", "blur": "onBlur()" } }, ngImport: i0 });
|
|
781
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteEditorHostDirective, decorators: [{
|
|
782
|
+
type: Directive,
|
|
783
|
+
args: [{
|
|
784
|
+
selector: "[aiaEditorHost]",
|
|
785
|
+
standalone: true,
|
|
786
|
+
host: {
|
|
787
|
+
role: "combobox",
|
|
788
|
+
"aria-autocomplete": "list",
|
|
789
|
+
"aria-haspopup": "listbox",
|
|
790
|
+
spellcheck: "true",
|
|
791
|
+
enterkeyhint: "send",
|
|
792
|
+
tabindex: "0",
|
|
793
|
+
"(input)": "onInput()",
|
|
794
|
+
"(keydown)": "onKeyDown($event)",
|
|
795
|
+
"(compositionstart)": "onCompositionStart()",
|
|
796
|
+
"(compositionend)": "onCompositionEnd()",
|
|
797
|
+
"(paste)": "onPaste($event)",
|
|
798
|
+
"(focus)": "onFocus()",
|
|
799
|
+
"(blur)": "onBlur()",
|
|
800
|
+
},
|
|
801
|
+
}]
|
|
802
|
+
}], propDecorators: { controller: [{
|
|
803
|
+
type: Input,
|
|
804
|
+
args: ["aiaEditorHost"]
|
|
805
|
+
}], autoFocus: [{
|
|
806
|
+
type: Input
|
|
807
|
+
}] } });
|
|
808
|
+
|
|
809
|
+
function resolveInitialMode(mode) {
|
|
810
|
+
if (mode !== "auto")
|
|
811
|
+
return mode;
|
|
812
|
+
if (typeof window === "undefined")
|
|
813
|
+
return "dark";
|
|
814
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Tier 1 component — `<ai-autocomplete>`. The Angular equivalent of React's
|
|
818
|
+
* `<AIAutocomplete />`. Owns the contentEditable editor, inline pills, mode
|
|
819
|
+
* controller, submit button slot, and an embedded
|
|
820
|
+
* {@link AIAutocompleteController} that drives the dropdown.
|
|
821
|
+
*
|
|
822
|
+
* @public
|
|
823
|
+
*/
|
|
824
|
+
class AIAutocompleteComponent {
|
|
825
|
+
constructor() {
|
|
826
|
+
this.maskCompletedText = false;
|
|
827
|
+
/** Where unfilled pills render. Default: "inline". */
|
|
828
|
+
this.pillPlacement = "inline";
|
|
829
|
+
/** Color mode. "auto" follows prefers-color-scheme. Default: "auto". */
|
|
830
|
+
this.mode = "auto";
|
|
831
|
+
this.optionsPosition = "below";
|
|
832
|
+
this.animations = true;
|
|
833
|
+
/** Focus the editor on mount. Default: true. */
|
|
834
|
+
this.autoFocus = true;
|
|
835
|
+
// === Outputs ===
|
|
836
|
+
/** Called on Enter or submit-button click. */
|
|
837
|
+
this.submitted = new EventEmitter();
|
|
838
|
+
this.errored = new EventEmitter();
|
|
839
|
+
this.valueChange = new EventEmitter();
|
|
840
|
+
this.completedParamsChange = new EventEmitter();
|
|
841
|
+
this.focused = new EventEmitter();
|
|
842
|
+
this.blurred = new EventEmitter();
|
|
843
|
+
this.resolvedMode = "dark";
|
|
844
|
+
this.onChangeFn = noop$1;
|
|
845
|
+
this.onTouchedFn = noop$1;
|
|
846
|
+
// §16.9: prefer `inject()` over constructor-parameter DI for components.
|
|
847
|
+
this.platformId = inject(PLATFORM_ID);
|
|
848
|
+
this.el = inject(ElementRef);
|
|
849
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
850
|
+
this.zone = inject(NgZone);
|
|
851
|
+
}
|
|
852
|
+
ngOnInit() {
|
|
853
|
+
if (!isPlatformBrowser(this.platformId))
|
|
854
|
+
return;
|
|
855
|
+
this.resolvedMode = resolveInitialMode(this.mode);
|
|
856
|
+
this.controller = new AIAutocompleteController({
|
|
857
|
+
apiConfig: this.apiConfig,
|
|
858
|
+
optionOverrides: this.optionOverrides,
|
|
859
|
+
maskCompletedText: this.maskCompletedText,
|
|
860
|
+
columns: this.columns,
|
|
861
|
+
dropdownTrigger: this.dropdownTrigger,
|
|
862
|
+
optionsPosition: this.optionsPosition,
|
|
863
|
+
closeDropdownOnBlur: this.closeDropdownOnBlur,
|
|
864
|
+
showNonTappableOptions: this.showNonTappableOptions,
|
|
865
|
+
source: "full-sdk",
|
|
866
|
+
value: this.value,
|
|
867
|
+
completedParams: this.completedParams,
|
|
868
|
+
onSubmit: (result) => this.handleSubmit(result),
|
|
869
|
+
onError: (error) => this.errored.emit(error),
|
|
870
|
+
onChange: (text) => {
|
|
871
|
+
this.valueChange.emit(text);
|
|
872
|
+
this.onChangeFn(text);
|
|
873
|
+
},
|
|
874
|
+
onParamsChange: (params) => this.completedParamsChange.emit(params),
|
|
875
|
+
onFocus: () => this.focused.emit(),
|
|
876
|
+
onBlur: () => {
|
|
877
|
+
this.blurred.emit();
|
|
878
|
+
this.onTouchedFn();
|
|
879
|
+
},
|
|
880
|
+
setCursor: (offset) => {
|
|
881
|
+
var _a;
|
|
882
|
+
const editor = (_a = this.editorRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
883
|
+
if (!editor)
|
|
884
|
+
return;
|
|
885
|
+
editor.focus();
|
|
886
|
+
setCursorOffset(editor, offset);
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
ngAfterViewInit() {
|
|
891
|
+
if (!isPlatformBrowser(this.platformId))
|
|
892
|
+
return;
|
|
893
|
+
const container = this.el.nativeElement;
|
|
894
|
+
this.modeController = new ModeController(container, this.mode);
|
|
895
|
+
// Pill-wrap detection — when the pill list wraps below the editor's last
|
|
896
|
+
// line, drop its 8px left margin via a data attribute the CSS keys off.
|
|
897
|
+
// Mirrors the React component's useLayoutEffect.
|
|
898
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
899
|
+
this.zone.runOutsideAngular(() => {
|
|
900
|
+
var _a;
|
|
901
|
+
this.resizeObserver = new ResizeObserver(() => this.measurePillWrap());
|
|
902
|
+
const editor = (_a = this.editorRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
903
|
+
if (editor)
|
|
904
|
+
this.resizeObserver.observe(editor);
|
|
905
|
+
});
|
|
906
|
+
this.measurePillWrap();
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
ngOnChanges(changes) {
|
|
910
|
+
var _a;
|
|
911
|
+
if (!this.controller)
|
|
912
|
+
return;
|
|
913
|
+
// Controlled-mode propagation
|
|
914
|
+
if (changes.value && this.value !== undefined) {
|
|
915
|
+
this.controller.setValue(this.value);
|
|
916
|
+
}
|
|
917
|
+
if (changes.completedParams && this.completedParams !== undefined) {
|
|
918
|
+
this.controller.setCompletedParams(this.completedParams);
|
|
919
|
+
}
|
|
920
|
+
// Reactive options
|
|
921
|
+
if (changes.apiConfig ||
|
|
922
|
+
changes.optionOverrides ||
|
|
923
|
+
changes.dropdownTrigger ||
|
|
924
|
+
changes.optionsPosition ||
|
|
925
|
+
changes.closeDropdownOnBlur ||
|
|
926
|
+
changes.showNonTappableOptions) {
|
|
927
|
+
this.controller.update({
|
|
928
|
+
apiConfig: this.apiConfig,
|
|
929
|
+
optionOverrides: this.optionOverrides,
|
|
930
|
+
dropdownTrigger: this.dropdownTrigger,
|
|
931
|
+
optionsPosition: this.optionsPosition,
|
|
932
|
+
closeDropdownOnBlur: this.closeDropdownOnBlur,
|
|
933
|
+
showNonTappableOptions: this.showNonTappableOptions,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
if (changes.mode) {
|
|
937
|
+
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(this.mode);
|
|
938
|
+
this.resolvedMode = resolveInitialMode(this.mode);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
ngOnDestroy() {
|
|
942
|
+
var _a, _b, _c;
|
|
943
|
+
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
944
|
+
(_b = this.modeController) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
945
|
+
(_c = this.controller) === null || _c === void 0 ? void 0 : _c.destroy();
|
|
946
|
+
}
|
|
947
|
+
// === Imperative API (exposed on the component instance via @ViewChild) ===
|
|
948
|
+
/** Programmatically focus the editor. */
|
|
949
|
+
focus() {
|
|
950
|
+
var _a;
|
|
951
|
+
(_a = this.editorRef) === null || _a === void 0 ? void 0 : _a.nativeElement.focus();
|
|
952
|
+
}
|
|
953
|
+
/** Programmatically blur the editor. */
|
|
954
|
+
blur() {
|
|
955
|
+
var _a;
|
|
956
|
+
(_a = this.editorRef) === null || _a === void 0 ? void 0 : _a.nativeElement.blur();
|
|
957
|
+
}
|
|
958
|
+
/** Clear all state, rotate session, and re-fetch. */
|
|
959
|
+
reset() {
|
|
960
|
+
var _a;
|
|
961
|
+
(_a = this.controller) === null || _a === void 0 ? void 0 : _a.reset();
|
|
962
|
+
}
|
|
963
|
+
/** Switch color mode at runtime. */
|
|
964
|
+
setMode(mode) {
|
|
965
|
+
var _a;
|
|
966
|
+
this.mode = mode;
|
|
967
|
+
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
|
|
968
|
+
this.resolvedMode = resolveInitialMode(mode);
|
|
969
|
+
this.cdr.markForCheck();
|
|
970
|
+
}
|
|
971
|
+
// === Template helpers ===
|
|
972
|
+
/**
|
|
973
|
+
* 'default' → render the built-in arrow button.
|
|
974
|
+
* 'custom' → project the consumer-supplied TemplateRef.
|
|
975
|
+
* 'hidden' → no button.
|
|
976
|
+
*/
|
|
977
|
+
get submitButtonState() {
|
|
978
|
+
if (this.submitButton === null)
|
|
979
|
+
return "hidden";
|
|
980
|
+
if (this.submitButton === undefined)
|
|
981
|
+
return "default";
|
|
982
|
+
return "custom";
|
|
983
|
+
}
|
|
984
|
+
/** Narrowed accessor for *ngTemplateOutlet — never undefined. */
|
|
985
|
+
get customSubmitTemplate() {
|
|
986
|
+
var _a;
|
|
987
|
+
return (_a = this.submitButton) !== null && _a !== void 0 ? _a : null;
|
|
988
|
+
}
|
|
989
|
+
get showInlinePills() {
|
|
990
|
+
return this.pillPlacement === "inline";
|
|
991
|
+
}
|
|
992
|
+
get showDropdownPills() {
|
|
993
|
+
return this.pillPlacement === "dropdown";
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* `true` when there's something to submit — driven by current segments
|
|
997
|
+
* and completed params. Read off the live editor via the controller state.
|
|
998
|
+
*/
|
|
999
|
+
canSubmit(state) {
|
|
1000
|
+
return state.segments.length > 0 || state.completedParams.length > 0;
|
|
1001
|
+
}
|
|
1002
|
+
onWrapperClick(event) {
|
|
1003
|
+
// Clicking a pill should activate it without stealing focus elsewhere.
|
|
1004
|
+
const target = event.target;
|
|
1005
|
+
if (target === null || target === void 0 ? void 0 : target.closest("[data-aia-pill]"))
|
|
1006
|
+
return;
|
|
1007
|
+
this.focus();
|
|
1008
|
+
}
|
|
1009
|
+
onSubmitButtonClick(event) {
|
|
1010
|
+
event.stopPropagation();
|
|
1011
|
+
this.handleSubmitFromUserAction();
|
|
1012
|
+
}
|
|
1013
|
+
onSubmitSlotClick(event) {
|
|
1014
|
+
event.stopPropagation();
|
|
1015
|
+
this.handleSubmitFromUserAction();
|
|
1016
|
+
}
|
|
1017
|
+
onSelectPill(index) {
|
|
1018
|
+
this.controller.setActivePill(index);
|
|
1019
|
+
}
|
|
1020
|
+
// === ControlValueAccessor ===
|
|
1021
|
+
writeValue(value) {
|
|
1022
|
+
if (!this.controller)
|
|
1023
|
+
return;
|
|
1024
|
+
this.controller.setValue(value !== null && value !== void 0 ? value : "");
|
|
1025
|
+
}
|
|
1026
|
+
registerOnChange(fn) {
|
|
1027
|
+
this.onChangeFn = fn;
|
|
1028
|
+
}
|
|
1029
|
+
registerOnTouched(fn) {
|
|
1030
|
+
this.onTouchedFn = fn;
|
|
1031
|
+
}
|
|
1032
|
+
setDisabledState(_disabled) {
|
|
1033
|
+
// The contentEditable host doesn't have a native disabled attribute. For
|
|
1034
|
+
// v0.1 we accept the call so Angular's forms layer doesn't throw, but
|
|
1035
|
+
// the editor stays interactive. A future enhancement can toggle
|
|
1036
|
+
// contentEditable + ARIA disabled here.
|
|
1037
|
+
}
|
|
1038
|
+
// === Submit flow ===
|
|
1039
|
+
/**
|
|
1040
|
+
* Called by the controller's onSubmit callback (which fires from the
|
|
1041
|
+
* keyboard controller on Enter). Reads the live editor text — same trick
|
|
1042
|
+
* the React component uses — so the emitted query reflects whatever's in
|
|
1043
|
+
* the DOM right now, not a possibly-stale state snapshot.
|
|
1044
|
+
*/
|
|
1045
|
+
handleSubmit(_result) {
|
|
1046
|
+
var _a, _b;
|
|
1047
|
+
const editor = (_a = this.editorRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
1048
|
+
if (!editor)
|
|
1049
|
+
return;
|
|
1050
|
+
const text = extractPlainText(editor);
|
|
1051
|
+
const params = (_b = this.completedParams) !== null && _b !== void 0 ? _b : this.controller.getState().completedParams;
|
|
1052
|
+
const built = buildQuery(text, params);
|
|
1053
|
+
this.submitted.emit({
|
|
1054
|
+
query: text.trim(),
|
|
1055
|
+
raw_query: built.rawQuery,
|
|
1056
|
+
completed_params: built.completedParams,
|
|
1057
|
+
});
|
|
1058
|
+
this.controller.reset();
|
|
1059
|
+
}
|
|
1060
|
+
handleSubmitFromUserAction() {
|
|
1061
|
+
const state = this.controller.getState();
|
|
1062
|
+
if (!this.canSubmit(state))
|
|
1063
|
+
return;
|
|
1064
|
+
// Synthesize the result for the submit-button click path; same shape
|
|
1065
|
+
// the controller would deliver from a keyboard Enter, so handleSubmit
|
|
1066
|
+
// can take either entry point.
|
|
1067
|
+
this.handleSubmit({
|
|
1068
|
+
query: state.text.trim(),
|
|
1069
|
+
raw_query: "",
|
|
1070
|
+
completed_params: [],
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
measurePillWrap() {
|
|
1074
|
+
var _a, _b;
|
|
1075
|
+
const container = (_a = this.pillContainerRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
1076
|
+
const editor = (_b = this.editorRef) === null || _b === void 0 ? void 0 : _b.nativeElement;
|
|
1077
|
+
if (!container || !editor)
|
|
1078
|
+
return;
|
|
1079
|
+
const inner = container.firstElementChild;
|
|
1080
|
+
if (!inner)
|
|
1081
|
+
return;
|
|
1082
|
+
// Wrap detection: the pill row's top vs the editor's bottom. When the
|
|
1083
|
+
// pills sit on (or above) the editor's last line they're NOT wrapped —
|
|
1084
|
+
// keep the 8px margin so they don't butt against typed text or the
|
|
1085
|
+
// placeholder. This handles the empty-editor + placeholder case
|
|
1086
|
+
// naturally: the placeholder still occupies the editor's line box.
|
|
1087
|
+
const cRect = inner.getBoundingClientRect();
|
|
1088
|
+
const eRect = editor.getBoundingClientRect();
|
|
1089
|
+
const wrapped = cRect.top >= eRect.bottom - 2;
|
|
1090
|
+
this.zone.run(() => {
|
|
1091
|
+
if (wrapped)
|
|
1092
|
+
container.setAttribute("data-aia-pill-wrapped", "");
|
|
1093
|
+
else
|
|
1094
|
+
container.removeAttribute("data-aia-pill-wrapped");
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
AIAutocompleteComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1099
|
+
AIAutocompleteComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteComponent, isStandalone: true, selector: "ai-autocomplete", inputs: { apiConfig: "apiConfig", optionOverrides: "optionOverrides", maskCompletedText: "maskCompletedText", columns: "columns", pillPlacement: "pillPlacement", mode: "mode", optionsPosition: "optionsPosition", animations: "animations", dropdownTrigger: "dropdownTrigger", closeDropdownOnBlur: "closeDropdownOnBlur", showNonTappableOptions: "showNonTappableOptions", autoFocus: "autoFocus", value: "value", completedParams: "completedParams", submitButton: "submitButton" }, outputs: { submitted: "submitted", errored: "errored", valueChange: "valueChange", completedParamsChange: "completedParamsChange", focused: "focused", blurred: "blurred" }, providers: [
|
|
1100
|
+
{
|
|
1101
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1102
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
1103
|
+
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1104
|
+
multi: true,
|
|
1105
|
+
},
|
|
1106
|
+
], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: AIAutocompleteDropdownComponent, selector: "ai-autocomplete-dropdown", inputs: ["controller", "showPills", "className"] }, { kind: "directive", type: AIAutocompleteEditorHostDirective, selector: "[aiaEditorHost]", inputs: ["aiaEditorHost", "autoFocus"] }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1107
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, decorators: [{
|
|
1108
|
+
type: Component,
|
|
1109
|
+
args: [{ selector: "ai-autocomplete", standalone: true, imports: [
|
|
1110
|
+
CommonModule,
|
|
1111
|
+
AIAutocompleteDropdownComponent,
|
|
1112
|
+
AIAutocompleteEditorHostDirective,
|
|
1113
|
+
PillListComponent,
|
|
1114
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
|
|
1115
|
+
{
|
|
1116
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1117
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
1118
|
+
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1119
|
+
multi: true,
|
|
1120
|
+
},
|
|
1121
|
+
], template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"] }]
|
|
1122
|
+
}], propDecorators: { apiConfig: [{
|
|
1123
|
+
type: Input
|
|
1124
|
+
}], optionOverrides: [{
|
|
1125
|
+
type: Input
|
|
1126
|
+
}], maskCompletedText: [{
|
|
1127
|
+
type: Input
|
|
1128
|
+
}], columns: [{
|
|
1129
|
+
type: Input
|
|
1130
|
+
}], pillPlacement: [{
|
|
1131
|
+
type: Input
|
|
1132
|
+
}], mode: [{
|
|
1133
|
+
type: Input
|
|
1134
|
+
}], optionsPosition: [{
|
|
1135
|
+
type: Input
|
|
1136
|
+
}], animations: [{
|
|
1137
|
+
type: Input
|
|
1138
|
+
}], dropdownTrigger: [{
|
|
1139
|
+
type: Input
|
|
1140
|
+
}], closeDropdownOnBlur: [{
|
|
1141
|
+
type: Input
|
|
1142
|
+
}], showNonTappableOptions: [{
|
|
1143
|
+
type: Input
|
|
1144
|
+
}], autoFocus: [{
|
|
1145
|
+
type: Input
|
|
1146
|
+
}], value: [{
|
|
1147
|
+
type: Input
|
|
1148
|
+
}], completedParams: [{
|
|
1149
|
+
type: Input
|
|
1150
|
+
}], submitButton: [{
|
|
1151
|
+
type: Input
|
|
1152
|
+
}], submitted: [{
|
|
1153
|
+
type: Output
|
|
1154
|
+
}], errored: [{
|
|
1155
|
+
type: Output
|
|
1156
|
+
}], valueChange: [{
|
|
1157
|
+
type: Output
|
|
1158
|
+
}], completedParamsChange: [{
|
|
1159
|
+
type: Output
|
|
1160
|
+
}], focused: [{
|
|
1161
|
+
type: Output
|
|
1162
|
+
}], blurred: [{
|
|
1163
|
+
type: Output
|
|
1164
|
+
}], editorRef: [{
|
|
1165
|
+
type: ViewChild,
|
|
1166
|
+
args: ["editor", { static: false }]
|
|
1167
|
+
}], pillContainerRef: [{
|
|
1168
|
+
type: ViewChild,
|
|
1169
|
+
args: ["pillContainer"]
|
|
1170
|
+
}] } });
|
|
1171
|
+
function noop$1() {
|
|
1172
|
+
// Default no-op for CVA callbacks.
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Tier 2 input binder. Attach to a `<textarea>` or `<input>` and pass an
|
|
1177
|
+
* {@link AIAutocompleteController}:
|
|
1178
|
+
*
|
|
1179
|
+
* ```html
|
|
1180
|
+
* <textarea [aiaInput]="ac"></textarea>
|
|
1181
|
+
* ```
|
|
1182
|
+
*
|
|
1183
|
+
* The directive wires the input's events (input/keydown/focus/blur) to the
|
|
1184
|
+
* controller, mirrors controller state into ARIA attributes
|
|
1185
|
+
* (`role="combobox"`, `aria-expanded`, `aria-activedescendant`,
|
|
1186
|
+
* `aria-autocomplete`, `aria-controls`), and implements
|
|
1187
|
+
* {@link ControlValueAccessor} so the host element participates in Reactive
|
|
1188
|
+
* Forms out of the box:
|
|
1189
|
+
*
|
|
1190
|
+
* ```html
|
|
1191
|
+
* <textarea [aiaInput]="ac" [formControl]="myControl"></textarea>
|
|
1192
|
+
* ```
|
|
1193
|
+
*
|
|
1194
|
+
* Per the React hook's behavior, the first typed character is auto-capitalized
|
|
1195
|
+
* — typing "h" becomes "H" before being forwarded to the controller. The
|
|
1196
|
+
* cursor position is preserved across that rewrite.
|
|
1197
|
+
*
|
|
1198
|
+
* @public
|
|
1199
|
+
*/
|
|
1200
|
+
class AIAutocompleteInputDirective {
|
|
1201
|
+
constructor() {
|
|
1202
|
+
this.el = inject(ElementRef);
|
|
1203
|
+
this.renderer = inject(Renderer2);
|
|
1204
|
+
this.destroy$ = new Subject();
|
|
1205
|
+
this.onChangeFn = noop;
|
|
1206
|
+
this.onTouchedFn = noop;
|
|
1207
|
+
}
|
|
1208
|
+
ngOnInit() {
|
|
1209
|
+
if (!this.controller) {
|
|
1210
|
+
throw new Error('[aiaInput] requires a controller — use `[aiaInput]="myController"`.');
|
|
1211
|
+
}
|
|
1212
|
+
const el = this.el.nativeElement;
|
|
1213
|
+
this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
|
|
1214
|
+
// Sync editor text → DOM. Skips the write when values already match, so
|
|
1215
|
+
// user keystrokes don't fight with our writeback. Preserves the caret
|
|
1216
|
+
// position when we do write (and the element still has focus).
|
|
1217
|
+
this.controller.text$.pipe(takeUntil(this.destroy$)).subscribe((text) => {
|
|
1218
|
+
if (el.value === text)
|
|
1219
|
+
return;
|
|
1220
|
+
const wasFocused = typeof document !== "undefined" && document.activeElement === el;
|
|
1221
|
+
const start = el.selectionStart;
|
|
1222
|
+
const end = el.selectionEnd;
|
|
1223
|
+
this.renderer.setProperty(el, "value", text);
|
|
1224
|
+
if (wasFocused && start != null && end != null) {
|
|
1225
|
+
const clampedStart = Math.min(start, text.length);
|
|
1226
|
+
const clampedEnd = Math.min(end, text.length);
|
|
1227
|
+
try {
|
|
1228
|
+
el.setSelectionRange(clampedStart, clampedEnd);
|
|
1229
|
+
}
|
|
1230
|
+
catch (_a) {
|
|
1231
|
+
// `setSelectionRange` throws on some input types (e.g. "number");
|
|
1232
|
+
// safe to ignore — those inputs don't have a meaningful selection.
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
this.controller.isDropdownOpen$.pipe(takeUntil(this.destroy$)).subscribe((isOpen) => {
|
|
1237
|
+
this.renderer.setAttribute(el, "aria-expanded", String(isOpen));
|
|
1238
|
+
});
|
|
1239
|
+
this.controller.activeIndex$.pipe(takeUntil(this.destroy$)).subscribe((idx) => {
|
|
1240
|
+
if (idx >= 0) {
|
|
1241
|
+
this.renderer.setAttribute(el, "aria-activedescendant", `${this.controller.listboxId}-option-${idx}`);
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
this.renderer.removeAttribute(el, "aria-activedescendant");
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
ngOnDestroy() {
|
|
1249
|
+
this.destroy$.next();
|
|
1250
|
+
this.destroy$.complete();
|
|
1251
|
+
}
|
|
1252
|
+
// === Event handlers (wired via host metadata above) ===
|
|
1253
|
+
/**
|
|
1254
|
+
* Auto-capitalizes the first character on the user's first keystroke so the
|
|
1255
|
+
* server receives "Create" instead of "create". Matches React's
|
|
1256
|
+
* `handleChange` in `useAIAutocomplete`. Skipped during IME composition so
|
|
1257
|
+
* we don't disrupt multi-keystroke input methods (Pinyin, Hangul, etc.).
|
|
1258
|
+
*/
|
|
1259
|
+
onInput(event) {
|
|
1260
|
+
const target = event.target;
|
|
1261
|
+
const raw = target.value;
|
|
1262
|
+
const isComposing = event.isComposing === true;
|
|
1263
|
+
const shouldCapitalize = raw.length > 0 && !isComposing && raw[0] !== raw[0].toUpperCase();
|
|
1264
|
+
let next = raw;
|
|
1265
|
+
if (shouldCapitalize) {
|
|
1266
|
+
const start = target.selectionStart;
|
|
1267
|
+
const end = target.selectionEnd;
|
|
1268
|
+
next = raw[0].toUpperCase() + raw.slice(1);
|
|
1269
|
+
this.renderer.setProperty(target, "value", next);
|
|
1270
|
+
if (start != null && end != null) {
|
|
1271
|
+
try {
|
|
1272
|
+
target.setSelectionRange(start, end);
|
|
1273
|
+
}
|
|
1274
|
+
catch (_a) {
|
|
1275
|
+
// See note in ngOnInit's text$ subscriber.
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
this.controller.handleTextChange(next);
|
|
1280
|
+
this.onChangeFn(next);
|
|
1281
|
+
}
|
|
1282
|
+
onKeyDown(event) {
|
|
1283
|
+
this.controller.handleKeyDown(event);
|
|
1284
|
+
}
|
|
1285
|
+
onFocus() {
|
|
1286
|
+
this.controller.setFocused(true);
|
|
1287
|
+
}
|
|
1288
|
+
onBlur() {
|
|
1289
|
+
this.controller.setFocused(false);
|
|
1290
|
+
this.onTouchedFn();
|
|
1291
|
+
}
|
|
1292
|
+
// === ControlValueAccessor ===
|
|
1293
|
+
writeValue(value) {
|
|
1294
|
+
const next = value !== null && value !== void 0 ? value : "";
|
|
1295
|
+
this.renderer.setProperty(this.el.nativeElement, "value", next);
|
|
1296
|
+
// The controller may be undefined transiently if Angular calls
|
|
1297
|
+
// writeValue() before ngOnInit (rare but possible during dynamic form
|
|
1298
|
+
// construction). Guard so we don't throw in that window.
|
|
1299
|
+
if (this.controller)
|
|
1300
|
+
this.controller.setValue(next);
|
|
1301
|
+
}
|
|
1302
|
+
registerOnChange(fn) {
|
|
1303
|
+
this.onChangeFn = fn;
|
|
1304
|
+
}
|
|
1305
|
+
registerOnTouched(fn) {
|
|
1306
|
+
this.onTouchedFn = fn;
|
|
1307
|
+
}
|
|
1308
|
+
setDisabledState(disabled) {
|
|
1309
|
+
this.renderer.setProperty(this.el.nativeElement, "disabled", disabled);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
AIAutocompleteInputDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1313
|
+
AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
|
|
1314
|
+
{
|
|
1315
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1316
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
1317
|
+
useExisting: forwardRef(() => AIAutocompleteInputDirective),
|
|
1318
|
+
multi: true,
|
|
1319
|
+
},
|
|
1320
|
+
], ngImport: i0 });
|
|
1321
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteInputDirective, decorators: [{
|
|
1322
|
+
type: Directive,
|
|
1323
|
+
args: [{
|
|
1324
|
+
selector: "[aiaInput]",
|
|
1325
|
+
standalone: true,
|
|
1326
|
+
host: {
|
|
1327
|
+
role: "combobox",
|
|
1328
|
+
"aria-autocomplete": "list",
|
|
1329
|
+
"(input)": "onInput($event)",
|
|
1330
|
+
"(keydown)": "onKeyDown($event)",
|
|
1331
|
+
"(focus)": "onFocus()",
|
|
1332
|
+
"(blur)": "onBlur()",
|
|
1333
|
+
},
|
|
1334
|
+
providers: [
|
|
1335
|
+
{
|
|
1336
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1337
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
1338
|
+
useExisting: forwardRef(() => AIAutocompleteInputDirective),
|
|
1339
|
+
multi: true,
|
|
1340
|
+
},
|
|
1341
|
+
],
|
|
1342
|
+
}]
|
|
1343
|
+
}], propDecorators: { controller: [{
|
|
1344
|
+
type: Input,
|
|
1345
|
+
args: ["aiaInput"]
|
|
1346
|
+
}] } });
|
|
1347
|
+
function noop() {
|
|
1348
|
+
// Default no-op callback for CVA — overwritten by Angular's form bridge.
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Convenience NgModule that re-exports every public component and directive
|
|
1353
|
+
* in this package. Standalone-first consumers can import the individual
|
|
1354
|
+
* symbols directly; legacy NgModule-based codebases import this once and
|
|
1355
|
+
* get everything.
|
|
1356
|
+
*
|
|
1357
|
+
* @public
|
|
1358
|
+
*/
|
|
1359
|
+
class AIAutocompleteModule {
|
|
1360
|
+
}
|
|
1361
|
+
AIAutocompleteModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1362
|
+
AIAutocompleteModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteModule, imports: [AIAutocompleteComponent, AIAutocompleteDropdownComponent, AIAutocompleteInputDirective], exports: [AIAutocompleteComponent, AIAutocompleteDropdownComponent, AIAutocompleteInputDirective] });
|
|
1363
|
+
AIAutocompleteModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteModule, imports: [AIAutocompleteComponent, AIAutocompleteDropdownComponent] });
|
|
1364
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteModule, decorators: [{
|
|
1365
|
+
type: NgModule,
|
|
1366
|
+
args: [{
|
|
1367
|
+
imports: [AIAutocompleteComponent, AIAutocompleteDropdownComponent, AIAutocompleteInputDirective],
|
|
1368
|
+
exports: [AIAutocompleteComponent, AIAutocompleteDropdownComponent, AIAutocompleteInputDirective],
|
|
1369
|
+
}]
|
|
1370
|
+
}] });
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Public API surface for @magicx-eng/ai-autocomplete-angular.
|
|
1374
|
+
*
|
|
1375
|
+
* Populated incrementally as features land — see TDB.md §6.6 for the full
|
|
1376
|
+
* planned export list. Only symbols re-exported here are considered part of
|
|
1377
|
+
* the package's public contract (TDB §19.2).
|
|
1378
|
+
*/
|
|
1379
|
+
// === Tier 1: Full component ===
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Generated bundle index. Do not edit.
|
|
1383
|
+
*/
|
|
1384
|
+
|
|
1385
|
+
export { AIAutocompleteComponent, AIAutocompleteController, AIAutocompleteDropdownComponent, AIAutocompleteInputDirective, AIAutocompleteModule };
|
|
1386
|
+
//# sourceMappingURL=magicx-eng-ai-autocomplete-angular.mjs.map
|