@magicx-eng/ai-autocomplete-angular 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/esm2020/lib/ai-autocomplete/ai-autocomplete.component.mjs +75 -29
- package/esm2020/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.mjs +46 -12
- package/esm2020/lib/controller/ai-autocomplete.controller.mjs +89 -7
- package/esm2020/lib/input/ai-autocomplete-input.directive.mjs +85 -6
- package/esm2020/lib/internal/editor-host/editor-host.directive.mjs +4 -2
- package/esm2020/lib/internal/layout/cluster.component.mjs +45 -0
- package/esm2020/lib/internal/layout/grid.component.mjs +95 -0
- package/esm2020/lib/internal/layout/stack.component.mjs +29 -0
- package/esm2020/lib/internal/pill-list/pill-list.component.mjs +8 -8
- package/esm2020/lib/internal/submit-button/submit-button.component.mjs +29 -0
- package/esm2020/lib/internal/suggestion-grid/suggestion-grid.component.mjs +10 -62
- package/esm2020/lib/internal/suggestion-item/suggestion-item.component.mjs +3 -3
- package/esm2020/lib/shared/types.mjs +1 -1
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs +503 -125
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs +495 -120
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts +11 -3
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts.map +1 -1
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts +29 -12
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts.map +1 -1
- package/lib/controller/ai-autocomplete.controller.d.ts.map +1 -1
- package/lib/input/ai-autocomplete-input.directive.d.ts +15 -0
- package/lib/input/ai-autocomplete-input.directive.d.ts.map +1 -1
- package/lib/internal/editor-host/editor-host.directive.d.ts.map +1 -1
- package/lib/internal/layout/cluster.component.d.ts +23 -0
- package/lib/internal/layout/cluster.component.d.ts.map +1 -0
- package/lib/internal/layout/grid.component.d.ts +43 -0
- package/lib/internal/layout/grid.component.d.ts.map +1 -0
- package/lib/internal/layout/stack.component.d.ts +16 -0
- package/lib/internal/layout/stack.component.d.ts.map +1 -0
- package/lib/internal/pill-list/pill-list.component.d.ts +2 -2
- package/lib/internal/pill-list/pill-list.component.d.ts.map +1 -1
- package/lib/internal/submit-button/submit-button.component.d.ts +17 -0
- package/lib/internal/submit-button/submit-button.component.d.ts.map +1 -0
- package/lib/internal/suggestion-grid/suggestion-grid.component.d.ts +7 -20
- package/lib/internal/suggestion-grid/suggestion-grid.component.d.ts.map +1 -1
- package/lib/shared/types.d.ts +2 -0
- package/lib/shared/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1,16 +1,86 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import {
|
|
4
|
+
import { Component, ChangeDetectionStrategy, ViewEncapsulation, Input, EventEmitter, Output, inject, NgZone, ChangeDetectorRef, ViewChild, ElementRef, Renderer2, Directive, PLATFORM_ID, forwardRef, NgModule } from '@angular/core';
|
|
5
5
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
|
-
import { AIAutocomplete, getCursorOffset, extractPlainText, renderEditableContent, plainTextLength, setCursorOffset, ModeController, buildQuery } from '@magicx-eng/ai-autocomplete-vanilla';
|
|
6
|
+
import { getFooterHint, AIAutocomplete, getCursorOffset, extractPlainText, renderEditableContent, plainTextLength, setCursorOffset, ModeController, buildQuery } from '@magicx-eng/ai-autocomplete-vanilla';
|
|
7
|
+
import { BehaviorSubject, combineLatest, map as map$1, distinctUntilChanged, shareReplay, Subject, takeUntil } from 'rxjs';
|
|
7
8
|
import { map } from 'rxjs/operators';
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Horizontal layout primitive — children sit in a row and wrap when out of
|
|
12
|
+
* room. Mirrors the React `<Cluster>` component / vanilla `.aia-cluster` CSS
|
|
13
|
+
* primitive. `noWrap` keeps a single row; `inline` makes it inline-level.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
class ClusterComponent {
|
|
18
|
+
constructor() {
|
|
19
|
+
/** Cross-axis alignment. Defaults to `center`. */
|
|
20
|
+
this.align = "center";
|
|
21
|
+
/** Main-axis distribution. Defaults to `start`. */
|
|
22
|
+
this.justify = "start";
|
|
23
|
+
/** When true, the cluster does NOT flex-wrap (single-row clusters). */
|
|
24
|
+
this.noWrap = false;
|
|
25
|
+
/** Render inline-level (inline-flex) instead of block. */
|
|
26
|
+
this.inline = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
ClusterComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ClusterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30
|
+
ClusterComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: ClusterComponent, isStandalone: true, selector: "aia-cluster", inputs: { gap: "gap", align: "align", justify: "justify", noWrap: "noWrap", inline: "inline" }, host: { properties: { "attr.data-align": "align", "attr.data-justify": "justify", "attr.data-nowrap": "noWrap ? '' : null", "attr.data-inline": "inline ? '' : null", "style.--aia-cluster-gap": "gap" }, classAttribute: "aia-cluster" }, ngImport: i0, template: "<ng-content></ng-content>", isInline: true, styles: ["@layer layout{.aia-cluster{display:flex;flex-wrap:wrap;gap:var(--aia-cluster-gap, .5rem)}.aia-cluster[data-inline]{display:inline-flex}.aia-cluster[data-nowrap]{flex-wrap:nowrap}.aia-cluster[data-align=start]{align-items:start}.aia-cluster[data-align=center]{align-items:center}.aia-cluster[data-align=end]{align-items:end}.aia-cluster[data-align=baseline]{align-items:baseline}.aia-cluster[data-justify=start]{justify-content:start}.aia-cluster[data-justify=center]{justify-content:center}.aia-cluster[data-justify=end]{justify-content:end}.aia-cluster[data-justify=between]{justify-content:space-between}.aia-cluster[data-justify=around]{justify-content:space-around}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
31
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ClusterComponent, decorators: [{
|
|
32
|
+
type: Component,
|
|
33
|
+
args: [{ selector: "aia-cluster", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-content></ng-content>", host: {
|
|
34
|
+
class: "aia-cluster",
|
|
35
|
+
"[attr.data-align]": "align",
|
|
36
|
+
"[attr.data-justify]": "justify",
|
|
37
|
+
"[attr.data-nowrap]": "noWrap ? '' : null",
|
|
38
|
+
"[attr.data-inline]": "inline ? '' : null",
|
|
39
|
+
"[style.--aia-cluster-gap]": "gap",
|
|
40
|
+
}, styles: ["@layer layout{.aia-cluster{display:flex;flex-wrap:wrap;gap:var(--aia-cluster-gap, .5rem)}.aia-cluster[data-inline]{display:inline-flex}.aia-cluster[data-nowrap]{flex-wrap:nowrap}.aia-cluster[data-align=start]{align-items:start}.aia-cluster[data-align=center]{align-items:center}.aia-cluster[data-align=end]{align-items:end}.aia-cluster[data-align=baseline]{align-items:baseline}.aia-cluster[data-justify=start]{justify-content:start}.aia-cluster[data-justify=center]{justify-content:center}.aia-cluster[data-justify=end]{justify-content:end}.aia-cluster[data-justify=between]{justify-content:space-between}.aia-cluster[data-justify=around]{justify-content:space-around}}\n"] }]
|
|
41
|
+
}], propDecorators: { gap: [{
|
|
42
|
+
type: Input
|
|
43
|
+
}], align: [{
|
|
44
|
+
type: Input
|
|
45
|
+
}], justify: [{
|
|
46
|
+
type: Input
|
|
47
|
+
}], noWrap: [{
|
|
48
|
+
type: Input
|
|
49
|
+
}], inline: [{
|
|
50
|
+
type: Input
|
|
51
|
+
}] } });
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Vertical layout primitive — children stack top-to-bottom with `space` gap.
|
|
55
|
+
* Mirrors the React `<Stack>` component / vanilla `.aia-stack` CSS primitive.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
class StackComponent {
|
|
60
|
+
constructor() {
|
|
61
|
+
/** Cross-axis alignment of children. Defaults to `stretch` (full width). */
|
|
62
|
+
this.align = "stretch";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
StackComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: StackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
66
|
+
StackComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: StackComponent, isStandalone: true, selector: "aia-stack", inputs: { space: "space", align: "align" }, host: { properties: { "attr.data-align": "align", "style.--aia-stack-space": "space" }, classAttribute: "aia-stack" }, ngImport: i0, template: "<ng-content></ng-content>", isInline: true, styles: ["@layer layout{.aia-stack{display:flex;flex-direction:column;gap:var(--aia-stack-space, .5rem)}.aia-stack[data-align=start]{align-items:start}.aia-stack[data-align=center]{align-items:center}.aia-stack[data-align=end]{align-items:end}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
67
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: StackComponent, decorators: [{
|
|
68
|
+
type: Component,
|
|
69
|
+
args: [{ selector: "aia-stack", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-content></ng-content>", host: {
|
|
70
|
+
class: "aia-stack",
|
|
71
|
+
"[attr.data-align]": "align",
|
|
72
|
+
"[style.--aia-stack-space]": "space",
|
|
73
|
+
}, styles: ["@layer layout{.aia-stack{display:flex;flex-direction:column;gap:var(--aia-stack-space, .5rem)}.aia-stack[data-align=start]{align-items:start}.aia-stack[data-align=center]{align-items:center}.aia-stack[data-align=end]{align-items:end}}\n"] }]
|
|
74
|
+
}], propDecorators: { space: [{
|
|
75
|
+
type: Input
|
|
76
|
+
}], align: [{
|
|
77
|
+
type: Input
|
|
78
|
+
}] } });
|
|
9
79
|
|
|
10
80
|
/**
|
|
11
81
|
* Internal pill list used by both the dropdown and Tier 1's inline pillbar.
|
|
12
82
|
* Mirrors `packages/react/src/components/PillList.tsx` one-for-one — including
|
|
13
|
-
* the per-index opacity ramp (
|
|
83
|
+
* the per-index opacity ramp (1 / 0.4 / 0.2) and the skeleton-loading
|
|
14
84
|
* fallback that preserves layout while the next fetch is in flight.
|
|
15
85
|
*
|
|
16
86
|
* @internal
|
|
@@ -33,13 +103,13 @@ class PillListComponent {
|
|
|
33
103
|
this.trackByType = (_, pill) => pill.type;
|
|
34
104
|
this.trackByWidth = (_, w) => w;
|
|
35
105
|
}
|
|
36
|
-
/** Per-index opacity ramp — mirrors PillList.tsx `
|
|
106
|
+
/** Per-index opacity ramp — mirrors PillList.tsx `PILL_OPACITY`. */
|
|
37
107
|
pillOpacity(index) {
|
|
38
108
|
if (index === 0)
|
|
39
|
-
return
|
|
109
|
+
return 1;
|
|
40
110
|
if (index === 1)
|
|
41
|
-
return 0.
|
|
42
|
-
return 0.
|
|
111
|
+
return 0.4;
|
|
112
|
+
return 0.2;
|
|
43
113
|
}
|
|
44
114
|
onPillClick(index) {
|
|
45
115
|
if (this.loading)
|
|
@@ -52,10 +122,10 @@ class PillListComponent {
|
|
|
52
122
|
}
|
|
53
123
|
}
|
|
54
124
|
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:
|
|
125
|
+
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:7px;align-items:center;vertical-align:middle}.aia-pill{display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 12px;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)) 30%,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}.aia-pill-rounded{border-radius:999px}.aia-pill-skeleton{pointer-events:none;cursor:default;color:transparent;animation:aiaPillSkeletonPulse 1.4s ease-in-out infinite}@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
126
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: PillListComponent, decorators: [{
|
|
57
127
|
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:
|
|
128
|
+
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:7px;align-items:center;vertical-align:middle}.aia-pill{display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 12px;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)) 30%,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}.aia-pill-rounded{border-radius:999px}.aia-pill-skeleton{pointer-events:none;cursor:default;color:transparent;animation:aiaPillSkeletonPulse 1.4s ease-in-out infinite}@keyframes aiaPillFadeIn{0%{opacity:0}}@keyframes aiaPillSkeletonPulse{0%,to{filter:brightness(1)}50%{filter:brightness(.55)}}\n"] }]
|
|
59
129
|
}], propDecorators: { pills: [{
|
|
60
130
|
type: Input
|
|
61
131
|
}], activePillIndex: [{
|
|
@@ -68,6 +138,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
68
138
|
type: Output
|
|
69
139
|
}] } });
|
|
70
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Intrinsic grid layout primitive — auto-fits as many columns as fit between
|
|
143
|
+
* `min` and `max` wide, wrapping to new rows when there's no more horizontal
|
|
144
|
+
* room. Mirrors the React `<Grid>` component / vanilla `.aia-grid` primitive.
|
|
145
|
+
*
|
|
146
|
+
* Optionally scrolls (`scroll`, capped height) with a bottom fade overlay
|
|
147
|
+
* (`fade`) that appears while there's hidden overflow below. The fade is driven
|
|
148
|
+
* by a ResizeObserver + scroll listener + MutationObserver (for projected
|
|
149
|
+
* content changes) — all run outside Angular's zone so high-frequency layout
|
|
150
|
+
* events don't trip change detection (mirrors the former SuggestionGrid).
|
|
151
|
+
*
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
class GridComponent {
|
|
155
|
+
constructor() {
|
|
156
|
+
/** Minimum cell width. CSS length, e.g. "16rem". Columns auto-fit at runtime. */
|
|
157
|
+
this.min = "16rem";
|
|
158
|
+
/** Cap the height and scroll vertically when content overflows. */
|
|
159
|
+
this.scroll = false;
|
|
160
|
+
/** Show a bottom fade overlay while the grid has hidden overflow below. */
|
|
161
|
+
this.fade = false;
|
|
162
|
+
this.hasBottomOverflow = false;
|
|
163
|
+
this.zone = inject(NgZone);
|
|
164
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
165
|
+
}
|
|
166
|
+
ngAfterViewInit() {
|
|
167
|
+
if (!this.fade)
|
|
168
|
+
return;
|
|
169
|
+
this.bindOverflowTracking();
|
|
170
|
+
this.measureOverflow();
|
|
171
|
+
}
|
|
172
|
+
ngOnDestroy() {
|
|
173
|
+
this.resizeObserver?.disconnect();
|
|
174
|
+
this.mutationObserver?.disconnect();
|
|
175
|
+
if (this.scrollListener && this.gridRef) {
|
|
176
|
+
this.gridRef.nativeElement.removeEventListener("scroll", this.scrollListener);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
bindOverflowTracking() {
|
|
180
|
+
const grid = this.gridRef?.nativeElement;
|
|
181
|
+
if (!grid)
|
|
182
|
+
return;
|
|
183
|
+
this.zone.runOutsideAngular(() => {
|
|
184
|
+
this.scrollListener = () => this.measureOverflow();
|
|
185
|
+
grid.addEventListener("scroll", this.scrollListener, { passive: true });
|
|
186
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
187
|
+
this.resizeObserver = new ResizeObserver(() => this.measureOverflow());
|
|
188
|
+
this.resizeObserver.observe(grid);
|
|
189
|
+
}
|
|
190
|
+
// Projected option count changes don't resize the capped grid box, so
|
|
191
|
+
// ResizeObserver misses them — watch the child list directly to match
|
|
192
|
+
// React's useLayoutEffect-on-options re-measure.
|
|
193
|
+
if (typeof MutationObserver !== "undefined") {
|
|
194
|
+
this.mutationObserver = new MutationObserver(() => this.measureOverflow());
|
|
195
|
+
this.mutationObserver.observe(grid, { childList: true, subtree: true });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
measureOverflow() {
|
|
200
|
+
const grid = this.gridRef?.nativeElement;
|
|
201
|
+
if (!grid)
|
|
202
|
+
return;
|
|
203
|
+
const next = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;
|
|
204
|
+
if (next !== this.hasBottomOverflow) {
|
|
205
|
+
this.zone.run(() => {
|
|
206
|
+
this.hasBottomOverflow = next;
|
|
207
|
+
this.cdr.markForCheck();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
GridComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: GridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
213
|
+
GridComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: GridComponent, isStandalone: true, selector: "aia-grid", inputs: { min: "min", max: "max", gap: "gap", scroll: "scroll", maxHeight: "maxHeight", fade: "fade" }, viewQueries: [{ propertyName: "gridRef", first: true, predicate: ["grid"], descendants: true }], ngImport: i0, template: "<div [class.aia-grid-fade]=\"fade\" [attr.data-fade]=\"fade && hasBottomOverflow ? '' : null\">\n <div\n #grid\n class=\"aia-grid\"\n [attr.data-scroll]=\"scroll ? '' : null\"\n [style.--aia-grid-min]=\"min\"\n [style.--aia-grid-max]=\"max\"\n [style.--aia-grid-gap]=\"gap\"\n [style.--aia-grid-max-height]=\"maxHeight\"\n >\n <ng-content></ng-content>\n </div>\n</div>\n", styles: ["aia-grid{display:contents}@layer layout{.aia-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--aia-grid-min),100%),var(--aia-grid-max, 1fr)));gap:var(--aia-grid-gap, 0)}.aia-grid[data-scroll]{grid-auto-rows:min-content;align-content:start;justify-content:start;padding:var(--aia-grid-scroll-pad, 0);max-height:var(--aia-grid-max-height, 200px);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3)) transparent}.aia-grid[data-scroll]::-webkit-scrollbar{width:6px}.aia-grid[data-scroll]::-webkit-scrollbar-track{background:transparent}.aia-grid[data-scroll]::-webkit-scrollbar-thumb{background:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3));border-radius:3px}.aia-grid-fade{position:relative}.aia-grid-fade: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-grid-fade[data-fade]:after{opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
214
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: GridComponent, decorators: [{
|
|
215
|
+
type: Component,
|
|
216
|
+
args: [{ selector: "aia-grid", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div [class.aia-grid-fade]=\"fade\" [attr.data-fade]=\"fade && hasBottomOverflow ? '' : null\">\n <div\n #grid\n class=\"aia-grid\"\n [attr.data-scroll]=\"scroll ? '' : null\"\n [style.--aia-grid-min]=\"min\"\n [style.--aia-grid-max]=\"max\"\n [style.--aia-grid-gap]=\"gap\"\n [style.--aia-grid-max-height]=\"maxHeight\"\n >\n <ng-content></ng-content>\n </div>\n</div>\n", styles: ["aia-grid{display:contents}@layer layout{.aia-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--aia-grid-min),100%),var(--aia-grid-max, 1fr)));gap:var(--aia-grid-gap, 0)}.aia-grid[data-scroll]{grid-auto-rows:min-content;align-content:start;justify-content:start;padding:var(--aia-grid-scroll-pad, 0);max-height:var(--aia-grid-max-height, 200px);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3)) transparent}.aia-grid[data-scroll]::-webkit-scrollbar{width:6px}.aia-grid[data-scroll]::-webkit-scrollbar-track{background:transparent}.aia-grid[data-scroll]::-webkit-scrollbar-thumb{background:var(--aia-scrollbar-thumb, rgba(0, 0, 0, .3));border-radius:3px}.aia-grid-fade{position:relative}.aia-grid-fade: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-grid-fade[data-fade]:after{opacity:1}}\n"] }]
|
|
217
|
+
}], propDecorators: { min: [{
|
|
218
|
+
type: Input
|
|
219
|
+
}], max: [{
|
|
220
|
+
type: Input
|
|
221
|
+
}], gap: [{
|
|
222
|
+
type: Input
|
|
223
|
+
}], scroll: [{
|
|
224
|
+
type: Input
|
|
225
|
+
}], maxHeight: [{
|
|
226
|
+
type: Input
|
|
227
|
+
}], fade: [{
|
|
228
|
+
type: Input
|
|
229
|
+
}], gridRef: [{
|
|
230
|
+
type: ViewChild,
|
|
231
|
+
args: ["grid", { static: false }]
|
|
232
|
+
}] } });
|
|
233
|
+
|
|
71
234
|
/**
|
|
72
235
|
* Internal single-option cell used inside the suggestion grid. Mirrors
|
|
73
236
|
* `packages/react/src/components/SuggestionItem.tsx`. Handles:
|
|
@@ -122,10 +285,10 @@ class SuggestionItemComponent {
|
|
|
122
285
|
}
|
|
123
286
|
}
|
|
124
287
|
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:
|
|
288
|
+
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:10px 15px;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
289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionItemComponent, decorators: [{
|
|
127
290
|
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:
|
|
291
|
+
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:10px 15px;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
292
|
}], propDecorators: { option: [{
|
|
130
293
|
type: Input
|
|
131
294
|
}], isHighlighted: [{
|
|
@@ -141,11 +304,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
141
304
|
}] } });
|
|
142
305
|
|
|
143
306
|
/**
|
|
144
|
-
* Internal
|
|
145
|
-
* `packages/react/src/components/SuggestionGrid.tsx` —
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
307
|
+
* Internal grid of suggestion options. Mirrors
|
|
308
|
+
* `packages/react/src/components/SuggestionGrid.tsx` — a thin wrapper over the
|
|
309
|
+
* `Grid` layout primitive (which owns the auto-fit columns and the
|
|
310
|
+
* capped-height scroll + bottom-fade). It only wires each option to its
|
|
311
|
+
* select/highlight handlers and listbox-scoped id.
|
|
149
312
|
*
|
|
150
313
|
* @internal
|
|
151
314
|
*/
|
|
@@ -157,64 +320,14 @@ class SuggestionGridComponent {
|
|
|
157
320
|
this.loading = false;
|
|
158
321
|
this.select = new EventEmitter();
|
|
159
322
|
this.highlight = new EventEmitter();
|
|
160
|
-
this.hasBottomOverflow = false;
|
|
161
|
-
this.zone = inject(NgZone);
|
|
162
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
163
323
|
this.trackByOptionText = (_, opt) => opt.text;
|
|
164
324
|
}
|
|
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
|
-
this.resizeObserver?.disconnect();
|
|
177
|
-
if (this.scrollListener && this.gridRef) {
|
|
178
|
-
this.gridRef.nativeElement.removeEventListener("scroll", this.scrollListener);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
onSelectItem(option) {
|
|
182
|
-
this.select.emit(option);
|
|
183
|
-
}
|
|
184
|
-
onHighlightItem(index) {
|
|
185
|
-
this.highlight.emit(index);
|
|
186
|
-
}
|
|
187
|
-
bindOverflowTracking() {
|
|
188
|
-
const grid = this.gridRef?.nativeElement;
|
|
189
|
-
if (!grid)
|
|
190
|
-
return;
|
|
191
|
-
this.zone.runOutsideAngular(() => {
|
|
192
|
-
this.scrollListener = () => this.measureOverflow();
|
|
193
|
-
grid.addEventListener("scroll", this.scrollListener, { passive: true });
|
|
194
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
195
|
-
this.resizeObserver = new ResizeObserver(() => this.measureOverflow());
|
|
196
|
-
this.resizeObserver.observe(grid);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
measureOverflow() {
|
|
201
|
-
const grid = this.gridRef?.nativeElement;
|
|
202
|
-
if (!grid)
|
|
203
|
-
return;
|
|
204
|
-
const next = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;
|
|
205
|
-
if (next !== this.hasBottomOverflow) {
|
|
206
|
-
this.zone.run(() => {
|
|
207
|
-
this.hasBottomOverflow = next;
|
|
208
|
-
this.cdr.markForCheck();
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
325
|
}
|
|
213
326
|
SuggestionGridComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
214
|
-
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" },
|
|
327
|
+
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" }, ngImport: i0, template: "<aia-grid min=\"250px\" max=\"250px\" gap=\"0\" [scroll]=\"true\" [fade]=\"true\">\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)=\"select.emit($event)\"\n (highlight)=\"highlight.emit(i)\"\n ></aia-suggestion-item>\n</aia-grid>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: GridComponent, selector: "aia-grid", inputs: ["min", "max", "gap", "scroll", "maxHeight", "fade"] }, { kind: "component", type: SuggestionItemComponent, selector: "aia-suggestion-item", inputs: ["option", "isHighlighted", "id", "loading"], outputs: ["select", "highlight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
215
328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionGridComponent, decorators: [{
|
|
216
329
|
type: Component,
|
|
217
|
-
args: [{ selector: "aia-suggestion-grid", standalone: true, imports: [CommonModule, SuggestionItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<
|
|
330
|
+
args: [{ selector: "aia-suggestion-grid", standalone: true, imports: [CommonModule, GridComponent, SuggestionItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<aia-grid min=\"250px\" max=\"250px\" gap=\"0\" [scroll]=\"true\" [fade]=\"true\">\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)=\"select.emit($event)\"\n (highlight)=\"highlight.emit(i)\"\n ></aia-suggestion-item>\n</aia-grid>\n" }]
|
|
218
331
|
}], propDecorators: { options: [{
|
|
219
332
|
type: Input
|
|
220
333
|
}], activeIndex: [{
|
|
@@ -227,9 +340,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
227
340
|
type: Output
|
|
228
341
|
}], highlight: [{
|
|
229
342
|
type: Output
|
|
230
|
-
}], gridRef: [{
|
|
231
|
-
type: ViewChild,
|
|
232
|
-
args: ["grid", { static: false }]
|
|
233
343
|
}] } });
|
|
234
344
|
|
|
235
345
|
/**
|
|
@@ -253,20 +363,42 @@ class AIAutocompleteDropdownComponent {
|
|
|
253
363
|
this.showPills = true;
|
|
254
364
|
/** Fallback skeleton-bar widths used when loading with zero cached options. */
|
|
255
365
|
this.fallbackSkeletonBarWidths = [159, 119, 164];
|
|
366
|
+
// Bridge for the @Input `showPills` into the reactive pipeline. Without this,
|
|
367
|
+
// changing the input after init wouldn't trigger a new vm emission until the
|
|
368
|
+
// next controller.dropdown$ tick — and we'd lose the showPills update entirely
|
|
369
|
+
// if the controller is idle. Updated from ngOnChanges below.
|
|
370
|
+
this.showPills$ = new BehaviorSubject(true);
|
|
256
371
|
this.trackByWidth = (_, w) => w;
|
|
257
372
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
373
|
+
ngOnInit() {
|
|
374
|
+
// Seed showPills$ from the input's resolved value at init time.
|
|
375
|
+
this.showPills$.next(this.showPills);
|
|
376
|
+
this.initVm();
|
|
377
|
+
}
|
|
378
|
+
ngOnChanges(changes) {
|
|
379
|
+
if (changes["showPills"]) {
|
|
380
|
+
this.showPills$.next(this.showPills);
|
|
381
|
+
}
|
|
382
|
+
// Re-bind vm$ when the controller @Input is swapped (e.g. a multi-step form
|
|
383
|
+
// toggling between controllers). Without this, vm$ stays subscribed to the
|
|
384
|
+
// previous controller's dropdown$ — rendering stale options, or, if that
|
|
385
|
+
// controller was destroy()-ed, receiving a completion that collapses the
|
|
386
|
+
// dropdown permanently. Skip the first change: ngOnChanges fires before
|
|
387
|
+
// ngOnInit, which owns the initial creation.
|
|
388
|
+
if (changes["controller"] && !changes["controller"].firstChange && this.controller) {
|
|
389
|
+
this.initVm();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Build the view-model stream from the current controller + showPills$. */
|
|
393
|
+
initVm() {
|
|
394
|
+
this.vm$ = combineLatest([this.controller.dropdown$, this.showPills$]).pipe(map(([dd, showPills]) => {
|
|
265
395
|
const options = dd.suggestions[0]?.options ?? [];
|
|
266
|
-
const showsPills =
|
|
396
|
+
const showsPills = showPills && (dd.pills.length > 0 || dd.isLoading);
|
|
267
397
|
const showsOptions = options.length > 0;
|
|
268
398
|
const showsFallbackSkeleton = dd.isLoading && !showsOptions;
|
|
269
399
|
const isVisible = dd.isOpen && (showsOptions || showsPills || dd.isLoading);
|
|
400
|
+
const optionHighlighted = dd.activeIndex >= 0 && Boolean(options[dd.activeIndex]?.is_tappable);
|
|
401
|
+
const { key: footerKey, hint: footerHint } = getFooterHint(dd.hasTabbedToHighlight, optionHighlighted);
|
|
270
402
|
return {
|
|
271
403
|
pills: dd.pills,
|
|
272
404
|
options,
|
|
@@ -276,6 +408,8 @@ class AIAutocompleteDropdownComponent {
|
|
|
276
408
|
showsOptions,
|
|
277
409
|
showsFallbackSkeleton,
|
|
278
410
|
isVisible,
|
|
411
|
+
footerKey,
|
|
412
|
+
footerHint,
|
|
279
413
|
};
|
|
280
414
|
}));
|
|
281
415
|
}
|
|
@@ -294,10 +428,16 @@ class AIAutocompleteDropdownComponent {
|
|
|
294
428
|
}
|
|
295
429
|
}
|
|
296
430
|
AIAutocompleteDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
297
|
-
AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n
|
|
431
|
+
AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n (mousedown)=\"preventBlur($event)\"\n>\n <aia-stack space=\"14px\">\n <aia-cluster\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n [noWrap]=\"true\"\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 </aia-cluster>\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\n <footer class=\"magicx-aia-footer\" data-aia-footer=\"\">\n <div class=\"magicx-aia-footer-divider\"></div>\n <div class=\"magicx-aia-footer-row\">\n <span class=\"magicx-aia-footer-hint-group\">\n <kbd class=\"magicx-aia-footer-key\">{{ vm.footerKey }}</kbd>\n <span class=\"magicx-aia-footer-hint\">{{ vm.footerHint }}</span>\n </span>\n <a\n class=\"magicx-aia-footer-brand-link\"\n href=\"https://ai-autocomplete.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span class=\"magicx-aia-footer-brand\">AI</span>\n <span class=\"magicx-aia-footer-badge\">Autocomplete</span>\n </a>\n </div>\n </footer>\n </aia-stack>\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;box-sizing:border-box;padding:15px 0;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);background:var(--aia-surface, #ffffff);border:1px solid var(--aia-border, #b0b0b0);border-radius:25px}.magicx-aia-dropdown[data-aia-surface=glass]{background:transparent;border-color:transparent;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)}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:0 15px;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-footer{display:flex;flex-direction:column;gap:12px;padding:0 15px}.magicx-aia-footer-divider{border-top:.5px solid var(--aia-footer-divider, rgba(176, 176, 176, .4))}.magicx-aia-footer-row{display:flex;align-items:center;justify-content:space-between}.magicx-aia-footer-hint-group{display:inline-flex;align-items:center;gap:5px}.magicx-aia-footer-brand-link{display:inline-flex;align-items:center;gap:2px;text-decoration:none;cursor:pointer;transition:opacity .15s ease-out}.magicx-aia-footer-brand-link:hover{opacity:.7}.magicx-aia-footer-key{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:22px;padding:2px 6px;border:.5px solid var(--aia-footer-hint-color, #505050);border-radius:5px;font-family:inherit;font-size:12px;line-height:18px;color:var(--aia-footer-hint-color, #505050)}.magicx-aia-footer-hint{font-family:inherit;font-size:12px;line-height:18px;color:var(--aia-footer-hint-color, #505050)}.magicx-aia-footer-brand{font-family:inherit;font-size:10px;line-height:18px;color:var(--aia-footer-brand-color, #b0b0b0)}.magicx-aia-footer-badge{display:inline-flex;align-items:center;height:23px;padding:2.5px 5.5px;border:.36px solid var(--aia-footer-badge-border, rgba(189, 189, 189, .51));border-radius:999px;font-family:inherit;font-size:10px;line-height:18px;color:var(--aia-footer-brand-color, #b0b0b0)}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:10px 15px}.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"] }, { kind: "component", type: StackComponent, selector: "aia-stack", inputs: ["space", "align"] }, { kind: "component", type: ClusterComponent, selector: "aia-cluster", inputs: ["gap", "align", "justify", "noWrap", "inline"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
298
432
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, decorators: [{
|
|
299
433
|
type: Component,
|
|
300
|
-
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [
|
|
434
|
+
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [
|
|
435
|
+
CommonModule,
|
|
436
|
+
PillListComponent,
|
|
437
|
+
SuggestionGridComponent,
|
|
438
|
+
StackComponent,
|
|
439
|
+
ClusterComponent,
|
|
440
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n (mousedown)=\"preventBlur($event)\"\n>\n <aia-stack space=\"14px\">\n <aia-cluster\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n [noWrap]=\"true\"\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 </aia-cluster>\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\n <footer class=\"magicx-aia-footer\" data-aia-footer=\"\">\n <div class=\"magicx-aia-footer-divider\"></div>\n <div class=\"magicx-aia-footer-row\">\n <span class=\"magicx-aia-footer-hint-group\">\n <kbd class=\"magicx-aia-footer-key\">{{ vm.footerKey }}</kbd>\n <span class=\"magicx-aia-footer-hint\">{{ vm.footerHint }}</span>\n </span>\n <a\n class=\"magicx-aia-footer-brand-link\"\n href=\"https://ai-autocomplete.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span class=\"magicx-aia-footer-brand\">AI</span>\n <span class=\"magicx-aia-footer-badge\">Autocomplete</span>\n </a>\n </div>\n </footer>\n </aia-stack>\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;box-sizing:border-box;padding:15px 0;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);background:var(--aia-surface, #ffffff);border:1px solid var(--aia-border, #b0b0b0);border-radius:25px}.magicx-aia-dropdown[data-aia-surface=glass]{background:transparent;border-color:transparent;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)}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:0 15px;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-footer{display:flex;flex-direction:column;gap:12px;padding:0 15px}.magicx-aia-footer-divider{border-top:.5px solid var(--aia-footer-divider, rgba(176, 176, 176, .4))}.magicx-aia-footer-row{display:flex;align-items:center;justify-content:space-between}.magicx-aia-footer-hint-group{display:inline-flex;align-items:center;gap:5px}.magicx-aia-footer-brand-link{display:inline-flex;align-items:center;gap:2px;text-decoration:none;cursor:pointer;transition:opacity .15s ease-out}.magicx-aia-footer-brand-link:hover{opacity:.7}.magicx-aia-footer-key{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:22px;padding:2px 6px;border:.5px solid var(--aia-footer-hint-color, #505050);border-radius:5px;font-family:inherit;font-size:12px;line-height:18px;color:var(--aia-footer-hint-color, #505050)}.magicx-aia-footer-hint{font-family:inherit;font-size:12px;line-height:18px;color:var(--aia-footer-hint-color, #505050)}.magicx-aia-footer-brand{font-family:inherit;font-size:10px;line-height:18px;color:var(--aia-footer-brand-color, #b0b0b0)}.magicx-aia-footer-badge{display:inline-flex;align-items:center;height:23px;padding:2.5px 5.5px;border:.36px solid var(--aia-footer-badge-border, rgba(189, 189, 189, .51));border-radius:999px;font-family:inherit;font-size:10px;line-height:18px;color:var(--aia-footer-brand-color, #b0b0b0)}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:10px 15px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"] }]
|
|
301
441
|
}], propDecorators: { controller: [{
|
|
302
442
|
type: Input
|
|
303
443
|
}], showPills: [{
|
|
@@ -366,17 +506,37 @@ class AIAutocompleteController {
|
|
|
366
506
|
optionsPosition: opts.optionsPosition ?? "below",
|
|
367
507
|
});
|
|
368
508
|
this.rawState$ = new BehaviorSubject(this.core.getState());
|
|
369
|
-
|
|
509
|
+
// Push the store's *current* state, not the snapshot the listener was
|
|
510
|
+
// handed. The core notifies listeners synchronously, and some actions
|
|
511
|
+
// trigger reentrant `store.set` calls (e.g. typing a non-matching char
|
|
512
|
+
// during re-edit makes the core synchronously exit re-edit and kick off a
|
|
513
|
+
// fetch). Reentrancy makes the outer set's listener loop continue with a
|
|
514
|
+
// now-stale snapshot, so the listener can fire *out of order* — delivering
|
|
515
|
+
// an older state (still in re-edit) after a newer one (re-edit exited).
|
|
516
|
+
// Reading `getState()` at emission time collapses every notification onto
|
|
517
|
+
// the true latest state, so a stale `editingParam` can't resurrect and
|
|
518
|
+
// retrigger the editor's re-edit caret-park (which would yank the caret
|
|
519
|
+
// back to where re-editing started). distinctUntilChanged downstream drops
|
|
520
|
+
// the resulting duplicate emissions. React avoids this because it batches
|
|
521
|
+
// renders and its caret effects are dependency-gated; our subscription is
|
|
522
|
+
// synchronous, so we coalesce here instead.
|
|
523
|
+
this.unsubscribeCore = this.core.subscribe(() => {
|
|
370
524
|
if (!this.isDestroyed)
|
|
371
|
-
this.rawState$.next(
|
|
525
|
+
this.rawState$.next(this.core.getState());
|
|
372
526
|
});
|
|
373
527
|
// === Per-field observables ===
|
|
528
|
+
// Array-valued fields use shallow equality. The vanilla store spreads on
|
|
529
|
+
// every write (`store.set({ completedParams: [...] })`), so reference
|
|
530
|
+
// equality would re-fire downstream subscribers on every state tick even
|
|
531
|
+
// when the array's contents are unchanged. The other fields are
|
|
532
|
+
// primitives or stable object references, so default identity equality
|
|
533
|
+
// works fine.
|
|
374
534
|
this.text$ = deriveField(this.rawState$, (s) => s.text);
|
|
375
|
-
this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams);
|
|
376
|
-
this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions);
|
|
377
|
-
this.segments$ = deriveField(this.rawState$, (s) => s.segments);
|
|
535
|
+
this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams, arrEq);
|
|
536
|
+
this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions, arrEq);
|
|
537
|
+
this.segments$ = deriveField(this.rawState$, (s) => s.segments, arrEq);
|
|
378
538
|
this.newParamId$ = deriveField(this.rawState$, (s) => s.newParamId);
|
|
379
|
-
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions);
|
|
539
|
+
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions, arrEq);
|
|
380
540
|
this.activeIndex$ = deriveField(this.rawState$, (s) => s.activeDropdownIndex);
|
|
381
541
|
this.isLoading$ = deriveField(this.rawState$, computeUiLoading);
|
|
382
542
|
this.isReady$ = deriveField(this.rawState$, (s) => s.isReady);
|
|
@@ -417,68 +577,109 @@ class AIAutocompleteController {
|
|
|
417
577
|
return computeState(this.rawState$.value);
|
|
418
578
|
}
|
|
419
579
|
// === Action methods (pass-through to the core) ===
|
|
580
|
+
//
|
|
581
|
+
// Every action method begins with an `isDestroyed` guard so that callers
|
|
582
|
+
// who hold a stale reference after `destroy()` don't accidentally trigger
|
|
583
|
+
// work on a torn-down core. The vanilla core does not gate its own methods
|
|
584
|
+
// post-dispose — `reset()`, `handleTextChange()`, and friends would still
|
|
585
|
+
// schedule fetches or fire HTTP requests against an aborted controller,
|
|
586
|
+
// and the responses would have nowhere to surface (rawState$ is already
|
|
587
|
+
// complete). The guard makes the JSDoc-promised "no-op silently"
|
|
588
|
+
// semantics actually true.
|
|
420
589
|
/** Clear all state, rotate `session_id`, and re-fetch. Call after submit. */
|
|
421
590
|
reset() {
|
|
591
|
+
if (this.isDestroyed)
|
|
592
|
+
return;
|
|
422
593
|
this.core.reset();
|
|
423
594
|
}
|
|
424
595
|
/** Pick a dropdown option (or replace the re-edited param if in re-edit mode). */
|
|
425
596
|
selectOption(option) {
|
|
597
|
+
if (this.isDestroyed)
|
|
598
|
+
return;
|
|
426
599
|
this.core.selectOption(option);
|
|
427
600
|
}
|
|
428
601
|
/** Promote the pill at `index` to active (front of the list). */
|
|
429
602
|
setActivePill(index) {
|
|
603
|
+
if (this.isDestroyed)
|
|
604
|
+
return;
|
|
430
605
|
this.core.setActivePill(index);
|
|
431
606
|
}
|
|
432
607
|
/** Drop the last completed param's "completed" status. Text stays as plain text. */
|
|
433
608
|
removeLastParam() {
|
|
609
|
+
if (this.isDestroyed)
|
|
610
|
+
return;
|
|
434
611
|
this.core.removeLastParam();
|
|
435
612
|
}
|
|
436
613
|
/** Clear the shimmer animation state for the most-recently-added param. */
|
|
437
614
|
clearNewParamId() {
|
|
615
|
+
if (this.isDestroyed)
|
|
616
|
+
return;
|
|
438
617
|
this.core.clearNewParamId();
|
|
439
618
|
}
|
|
440
619
|
/** Controlled-mode setter for editor text. */
|
|
441
620
|
setValue(text) {
|
|
621
|
+
if (this.isDestroyed)
|
|
622
|
+
return;
|
|
442
623
|
this.core.setValue(text);
|
|
443
624
|
}
|
|
444
625
|
/** Controlled-mode setter for completed params. */
|
|
445
626
|
setCompletedParams(params) {
|
|
627
|
+
if (this.isDestroyed)
|
|
628
|
+
return;
|
|
446
629
|
this.core.setCompletedParams(params);
|
|
447
630
|
}
|
|
448
631
|
/** Forward plain-text input from the consumer's `<textarea>` to the core. */
|
|
449
632
|
handleTextChange(value) {
|
|
633
|
+
if (this.isDestroyed)
|
|
634
|
+
return;
|
|
450
635
|
this.core.handleTextChange(value);
|
|
451
636
|
}
|
|
452
637
|
/** Forward keyboard events from the consumer's `<textarea>` to the core. */
|
|
453
638
|
handleKeyDown(event) {
|
|
639
|
+
if (this.isDestroyed)
|
|
640
|
+
return;
|
|
454
641
|
this.core.handleKeyDown(event);
|
|
455
642
|
}
|
|
456
643
|
/** Notify the core of focus state. */
|
|
457
644
|
setFocused(focused) {
|
|
645
|
+
if (this.isDestroyed)
|
|
646
|
+
return;
|
|
458
647
|
this.core.setFocused(focused);
|
|
459
648
|
}
|
|
460
649
|
/** Enter re-edit mode for the bold param with the given id. */
|
|
461
650
|
startEditingParam(paramId) {
|
|
651
|
+
if (this.isDestroyed)
|
|
652
|
+
return;
|
|
462
653
|
this.core.startEditingParam(paramId);
|
|
463
654
|
}
|
|
464
655
|
/** Exit re-edit mode without changing the bold param. */
|
|
465
656
|
exitEditMode() {
|
|
657
|
+
if (this.isDestroyed)
|
|
658
|
+
return;
|
|
466
659
|
this.core.exitEditMode();
|
|
467
660
|
}
|
|
468
661
|
/** Forward post-input caret position (extends the edit tail). */
|
|
469
662
|
handleCaretAfterInput(offset) {
|
|
663
|
+
if (this.isDestroyed)
|
|
664
|
+
return;
|
|
470
665
|
this.core.handleCaretAfterInput(offset);
|
|
471
666
|
}
|
|
472
667
|
/** Forward selection-only caret moves (may exit re-edit). */
|
|
473
668
|
handleCaretMove(offset) {
|
|
669
|
+
if (this.isDestroyed)
|
|
670
|
+
return;
|
|
474
671
|
this.core.handleCaretMove(offset);
|
|
475
672
|
}
|
|
476
673
|
/** Replace the bold param being re-edited. Returns true when applied. */
|
|
477
674
|
replaceEditingRange(replacement) {
|
|
675
|
+
if (this.isDestroyed)
|
|
676
|
+
return false;
|
|
478
677
|
return this.core.replaceEditingRange(replacement);
|
|
479
678
|
}
|
|
480
679
|
/** Set the highlighted option index (typically driven by hover). */
|
|
481
680
|
setActiveDropdownIndex(index) {
|
|
681
|
+
if (this.isDestroyed)
|
|
682
|
+
return;
|
|
482
683
|
this.core.setActiveDropdownIndex(index);
|
|
483
684
|
}
|
|
484
685
|
// === Lifecycle ===
|
|
@@ -490,6 +691,8 @@ class AIAutocompleteController {
|
|
|
490
691
|
* (e.g. `text$` instead of `onChange`).
|
|
491
692
|
*/
|
|
492
693
|
update(opts) {
|
|
694
|
+
if (this.isDestroyed)
|
|
695
|
+
return;
|
|
493
696
|
this.core.update(opts);
|
|
494
697
|
}
|
|
495
698
|
/**
|
|
@@ -507,6 +710,24 @@ class AIAutocompleteController {
|
|
|
507
710
|
}
|
|
508
711
|
}
|
|
509
712
|
// ===== Pure helpers — not part of the public surface =====
|
|
713
|
+
/**
|
|
714
|
+
* Shallow array equality. The vanilla store reassigns array fields on every
|
|
715
|
+
* write (`store.set({ completedParams: [...prev, x] })`), so reference
|
|
716
|
+
* equality alone would re-emit downstream even when the contents are
|
|
717
|
+
* identical. Used for `completedParams$`, `suggestions$`, `suggestionPills$`,
|
|
718
|
+
* and `segments$`.
|
|
719
|
+
*/
|
|
720
|
+
function arrEq(a, b) {
|
|
721
|
+
if (a === b)
|
|
722
|
+
return true;
|
|
723
|
+
if (a.length !== b.length)
|
|
724
|
+
return false;
|
|
725
|
+
for (let i = 0; i < a.length; i++) {
|
|
726
|
+
if (a[i] !== b[i])
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
510
731
|
function computeUiLoading(s) {
|
|
511
732
|
return s.isLoading && !s.editingParam && !s.inSelectionAnimation;
|
|
512
733
|
}
|
|
@@ -549,6 +770,7 @@ function computeDropdown(s) {
|
|
|
549
770
|
isOpen: s.isDropdownOpen,
|
|
550
771
|
pills: dropdownPills,
|
|
551
772
|
isLoading: computeUiLoading(s),
|
|
773
|
+
hasTabbedToHighlight: s.hasTabbedToHighlight,
|
|
552
774
|
};
|
|
553
775
|
}
|
|
554
776
|
|
|
@@ -701,7 +923,9 @@ class AIAutocompleteEditorHostDirective {
|
|
|
701
923
|
const anchor = sel.anchorNode;
|
|
702
924
|
const startEl = anchor.nodeType === Node.ELEMENT_NODE ? anchor : anchor.parentElement;
|
|
703
925
|
const strong = startEl?.closest('strong[data-seg="completed"][data-param-id]');
|
|
704
|
-
|
|
926
|
+
// `DOMStringMap` is an index signature; bracket notation is required
|
|
927
|
+
// by `noPropertyAccessFromIndexSignature`.
|
|
928
|
+
const enclosing = strong?.dataset["paramId"] ?? null;
|
|
705
929
|
if (enclosing && enclosing !== this.editingParamId) {
|
|
706
930
|
this.zone.run(() => this.controller.startEditingParam(enclosing));
|
|
707
931
|
return;
|
|
@@ -801,13 +1025,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
801
1025
|
type: Input
|
|
802
1026
|
}] } });
|
|
803
1027
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1028
|
+
/**
|
|
1029
|
+
* Internal default submit button (up-arrow). Angular analogue of React's
|
|
1030
|
+
* `SubmitButton`. The default/custom/hidden branching stays in the parent
|
|
1031
|
+
* (`AIAutocompleteComponent`); this is only the built-in button.
|
|
1032
|
+
*
|
|
1033
|
+
* @internal
|
|
1034
|
+
*/
|
|
1035
|
+
class SubmitButtonComponent {
|
|
1036
|
+
constructor() {
|
|
1037
|
+
this.disabled = false;
|
|
1038
|
+
this.submit = new EventEmitter();
|
|
1039
|
+
}
|
|
1040
|
+
onClick(event) {
|
|
1041
|
+
this.submit.emit(event);
|
|
1042
|
+
}
|
|
810
1043
|
}
|
|
1044
|
+
SubmitButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SubmitButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1045
|
+
SubmitButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: SubmitButtonComponent, isStandalone: true, selector: "aia-submit-button", inputs: { disabled: "disabled" }, outputs: { submit: "submit" }, ngImport: i0, template: "<button\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"disabled\"\n aria-label=\"Submit\"\n (click)=\"onClick($event)\"\n>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" role=\"img\" aria-label=\"Submit\">\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", styles: [".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-button:disabled{opacity:.4;cursor:default}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1046
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SubmitButtonComponent, decorators: [{
|
|
1047
|
+
type: Component,
|
|
1048
|
+
args: [{ selector: "aia-submit-button", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<button\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"disabled\"\n aria-label=\"Submit\"\n (click)=\"onClick($event)\"\n>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" role=\"img\" aria-label=\"Submit\">\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", styles: [".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-button:disabled{opacity:.4;cursor:default}\n"] }]
|
|
1049
|
+
}], propDecorators: { disabled: [{
|
|
1050
|
+
type: Input
|
|
1051
|
+
}], submit: [{
|
|
1052
|
+
type: Output
|
|
1053
|
+
}] } });
|
|
1054
|
+
|
|
811
1055
|
/**
|
|
812
1056
|
* Tier 1 component — `<ai-autocomplete>`. The Angular equivalent of React's
|
|
813
1057
|
* `<AIAutocomplete />`. Owns the contentEditable editor, inline pills, mode
|
|
@@ -835,19 +1079,26 @@ class AIAutocompleteComponent {
|
|
|
835
1079
|
this.completedParamsChange = new EventEmitter();
|
|
836
1080
|
this.focused = new EventEmitter();
|
|
837
1081
|
this.blurred = new EventEmitter();
|
|
1082
|
+
/**
|
|
1083
|
+
* Concrete light/dark value bound to `[attr.data-mode]`. Seeded in ngOnInit
|
|
1084
|
+
* so the attribute is present during SSR (where ModeController, gated behind
|
|
1085
|
+
* isPlatformBrowser in ngAfterViewInit, never runs). On the browser the
|
|
1086
|
+
* ModeController's resolve callback keeps this in sync — so its runtime
|
|
1087
|
+
* prefers-color-scheme updates and this binding always agree (no clobber).
|
|
1088
|
+
*/
|
|
838
1089
|
this.resolvedMode = "dark";
|
|
839
1090
|
this.onChangeFn = noop$1;
|
|
840
1091
|
this.onTouchedFn = noop$1;
|
|
841
1092
|
// §16.9: prefer `inject()` over constructor-parameter DI for components.
|
|
842
1093
|
this.platformId = inject(PLATFORM_ID);
|
|
843
|
-
this.el = inject(ElementRef);
|
|
844
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
845
1094
|
this.zone = inject(NgZone);
|
|
1095
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
846
1096
|
}
|
|
847
1097
|
ngOnInit() {
|
|
1098
|
+
// Seed before the browser guard so SSR renders a concrete data-mode.
|
|
1099
|
+
this.resolvedMode = resolveInitialMode(this.mode);
|
|
848
1100
|
if (!isPlatformBrowser(this.platformId))
|
|
849
1101
|
return;
|
|
850
|
-
this.resolvedMode = resolveInitialMode(this.mode);
|
|
851
1102
|
this.controller = new AIAutocompleteController({
|
|
852
1103
|
apiConfig: this.apiConfig,
|
|
853
1104
|
optionOverrides: this.optionOverrides,
|
|
@@ -880,12 +1131,26 @@ class AIAutocompleteComponent {
|
|
|
880
1131
|
setCursorOffset(editor, offset);
|
|
881
1132
|
},
|
|
882
1133
|
});
|
|
1134
|
+
if (this.pendingWriteValue !== undefined) {
|
|
1135
|
+
this.controller.setValue(this.pendingWriteValue);
|
|
1136
|
+
this.pendingWriteValue = undefined;
|
|
1137
|
+
}
|
|
883
1138
|
}
|
|
884
1139
|
ngAfterViewInit() {
|
|
885
1140
|
if (!isPlatformBrowser(this.platformId))
|
|
886
1141
|
return;
|
|
887
|
-
|
|
888
|
-
|
|
1142
|
+
// ModeController writes `data-mode` to the element it's given. The CSS
|
|
1143
|
+
// selectors are scoped to `.magicx-aia[data-mode="..."]` — which lives on
|
|
1144
|
+
// the inner `<div class="magicx-aia magicx-aia-container" #container>`,
|
|
1145
|
+
// NOT the host `<ai-autocomplete>` element. The resolve callback mirrors
|
|
1146
|
+
// the value into `resolvedMode` so the `[attr.data-mode]` binding stays in
|
|
1147
|
+
// lockstep with ModeController — the binding handles SSR, ModeController
|
|
1148
|
+
// handles runtime prefers-color-scheme switches, and because they always
|
|
1149
|
+
// agree neither clobbers the other.
|
|
1150
|
+
this.modeController = new ModeController(this.containerRef.nativeElement, this.mode, (resolved) => {
|
|
1151
|
+
this.resolvedMode = resolved;
|
|
1152
|
+
this.cdr.markForCheck();
|
|
1153
|
+
});
|
|
889
1154
|
// Pill-wrap detection — when the pill list wraps below the editor's last
|
|
890
1155
|
// line, drop its 8px left margin via a data attribute the CSS keys off.
|
|
891
1156
|
// Mirrors the React component's useLayoutEffect.
|
|
@@ -902,20 +1167,24 @@ class AIAutocompleteComponent {
|
|
|
902
1167
|
ngOnChanges(changes) {
|
|
903
1168
|
if (!this.controller)
|
|
904
1169
|
return;
|
|
1170
|
+
// `SimpleChanges` is `{ [key: string]: SimpleChange }`, so per
|
|
1171
|
+
// `noPropertyAccessFromIndexSignature` each key lookup uses bracket
|
|
1172
|
+
// notation — this keeps the strictness contract that the rest of the
|
|
1173
|
+
// workspace enforces.
|
|
905
1174
|
// Controlled-mode propagation
|
|
906
|
-
if (changes
|
|
1175
|
+
if (changes["value"] && this.value !== undefined) {
|
|
907
1176
|
this.controller.setValue(this.value);
|
|
908
1177
|
}
|
|
909
|
-
if (changes
|
|
1178
|
+
if (changes["completedParams"] && this.completedParams !== undefined) {
|
|
910
1179
|
this.controller.setCompletedParams(this.completedParams);
|
|
911
1180
|
}
|
|
912
1181
|
// Reactive options
|
|
913
|
-
if (changes
|
|
914
|
-
changes
|
|
915
|
-
changes
|
|
916
|
-
changes
|
|
917
|
-
changes
|
|
918
|
-
changes
|
|
1182
|
+
if (changes["apiConfig"] ||
|
|
1183
|
+
changes["optionOverrides"] ||
|
|
1184
|
+
changes["dropdownTrigger"] ||
|
|
1185
|
+
changes["optionsPosition"] ||
|
|
1186
|
+
changes["closeDropdownOnBlur"] ||
|
|
1187
|
+
changes["showNonTappableOptions"]) {
|
|
919
1188
|
this.controller.update({
|
|
920
1189
|
apiConfig: this.apiConfig,
|
|
921
1190
|
optionOverrides: this.optionOverrides,
|
|
@@ -925,9 +1194,12 @@ class AIAutocompleteComponent {
|
|
|
925
1194
|
showNonTappableOptions: this.showNonTappableOptions,
|
|
926
1195
|
});
|
|
927
1196
|
}
|
|
928
|
-
if (changes
|
|
929
|
-
|
|
1197
|
+
if (changes["mode"]) {
|
|
1198
|
+
// Keep the binding correct even when ModeController doesn't exist yet
|
|
1199
|
+
// (SSR / pre-ngAfterViewInit). On the browser, setMode triggers the
|
|
1200
|
+
// resolve callback which sets the same value.
|
|
930
1201
|
this.resolvedMode = resolveInitialMode(this.mode);
|
|
1202
|
+
this.modeController?.setMode(this.mode);
|
|
931
1203
|
}
|
|
932
1204
|
}
|
|
933
1205
|
ngOnDestroy() {
|
|
@@ -951,8 +1223,8 @@ class AIAutocompleteComponent {
|
|
|
951
1223
|
/** Switch color mode at runtime. */
|
|
952
1224
|
setMode(mode) {
|
|
953
1225
|
this.mode = mode;
|
|
954
|
-
this.modeController?.setMode(mode);
|
|
955
1226
|
this.resolvedMode = resolveInitialMode(mode);
|
|
1227
|
+
this.modeController?.setMode(mode);
|
|
956
1228
|
this.cdr.markForCheck();
|
|
957
1229
|
}
|
|
958
1230
|
// === Template helpers ===
|
|
@@ -1005,9 +1277,16 @@ class AIAutocompleteComponent {
|
|
|
1005
1277
|
}
|
|
1006
1278
|
// === ControlValueAccessor ===
|
|
1007
1279
|
writeValue(value) {
|
|
1008
|
-
|
|
1280
|
+
const next = value ?? "";
|
|
1281
|
+
if (!this.controller) {
|
|
1282
|
+
// Reactive Forms can call writeValue() before ngOnInit creates the
|
|
1283
|
+
// controller (dynamic form construction, or a FormControl that already
|
|
1284
|
+
// had a value at directive-creation time). Buffer the value; ngOnInit
|
|
1285
|
+
// flushes it as soon as the controller exists.
|
|
1286
|
+
this.pendingWriteValue = next;
|
|
1009
1287
|
return;
|
|
1010
|
-
|
|
1288
|
+
}
|
|
1289
|
+
this.controller.setValue(next);
|
|
1011
1290
|
}
|
|
1012
1291
|
registerOnChange(fn) {
|
|
1013
1292
|
this.onChangeFn = fn;
|
|
@@ -1087,7 +1366,7 @@ AIAutocompleteComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0"
|
|
|
1087
1366
|
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1088
1367
|
multi: true,
|
|
1089
1368
|
},
|
|
1090
|
-
], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n
|
|
1369
|
+
], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <aia-submit-button\n *ngSwitchCase=\"'default'\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n (submit)=\"onSubmitButtonClick($event)\"\n ></aia-submit-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-surface: #ffffff;--aia-border: #b0b0b0;--aia-pill-bg: #bdbdbd;--aia-pill-color: #333539;--aia-pill-font-size: 18px;--aia-option-bg: transparent;--aia-option-color: #505050;--aia-option-color-selected: #000000;--aia-option-font-size: 18px;--aia-written-text-color: #000000;--aia-written-text-font-size: 20px;--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);--aia-footer-divider: rgba(176, 176, 176, .4);--aia-footer-hint-color: #505050;--aia-footer-brand-color: #b0b0b0;--aia-footer-badge-border: rgba(189, 189, 189, .51)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-surface: #000000;--aia-border: #505050;--aia-pill-bg: #bdbdbd;--aia-pill-color: #b0b0b0;--aia-pill-font-size: 18px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 18px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 20px;--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);--aia-footer-divider: rgba(255, 255, 255, .15);--aia-footer-hint-color: #b0b0b0;--aia-footer-brand-color: #505050;--aia-footer-badge-border: #505050}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown] .aia-stack{flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown] .magicx-aia-footer{flex-direction:column-reverse}: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:19px 15px 20px 25px;border:1px solid var(--aia-border, #b0b0b0);border-radius:30px;background:var(--aia-surface, #ffffff);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-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"] }, { kind: "component", type: SubmitButtonComponent, selector: "aia-submit-button", inputs: ["disabled"], outputs: ["submit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1091
1370
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, decorators: [{
|
|
1092
1371
|
type: Component,
|
|
1093
1372
|
args: [{ selector: "ai-autocomplete", standalone: true, imports: [
|
|
@@ -1095,6 +1374,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1095
1374
|
AIAutocompleteDropdownComponent,
|
|
1096
1375
|
AIAutocompleteEditorHostDirective,
|
|
1097
1376
|
PillListComponent,
|
|
1377
|
+
SubmitButtonComponent,
|
|
1098
1378
|
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
|
|
1099
1379
|
{
|
|
1100
1380
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -1102,7 +1382,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1102
1382
|
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1103
1383
|
multi: true,
|
|
1104
1384
|
},
|
|
1105
|
-
], template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n
|
|
1385
|
+
], template: "<div\n #container\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <aia-submit-button\n *ngSwitchCase=\"'default'\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n (submit)=\"onSubmitButtonClick($event)\"\n ></aia-submit-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-surface: #ffffff;--aia-border: #b0b0b0;--aia-pill-bg: #bdbdbd;--aia-pill-color: #333539;--aia-pill-font-size: 18px;--aia-option-bg: transparent;--aia-option-color: #505050;--aia-option-color-selected: #000000;--aia-option-font-size: 18px;--aia-written-text-color: #000000;--aia-written-text-font-size: 20px;--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);--aia-footer-divider: rgba(176, 176, 176, .4);--aia-footer-hint-color: #505050;--aia-footer-brand-color: #b0b0b0;--aia-footer-badge-border: rgba(189, 189, 189, .51)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-surface: #000000;--aia-border: #505050;--aia-pill-bg: #bdbdbd;--aia-pill-color: #b0b0b0;--aia-pill-font-size: 18px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 18px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 20px;--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);--aia-footer-divider: rgba(255, 255, 255, .15);--aia-footer-hint-color: #b0b0b0;--aia-footer-brand-color: #505050;--aia-footer-badge-border: #505050}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown] .aia-stack{flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown] .magicx-aia-footer{flex-direction:column-reverse}: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:19px 15px 20px 25px;border:1px solid var(--aia-border, #b0b0b0);border-radius:30px;background:var(--aia-surface, #ffffff);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-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"] }]
|
|
1106
1386
|
}], propDecorators: { apiConfig: [{
|
|
1107
1387
|
type: Input
|
|
1108
1388
|
}], optionOverrides: [{
|
|
@@ -1145,6 +1425,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1145
1425
|
type: Output
|
|
1146
1426
|
}], blurred: [{
|
|
1147
1427
|
type: Output
|
|
1428
|
+
}], containerRef: [{
|
|
1429
|
+
type: ViewChild,
|
|
1430
|
+
args: ["container", { static: true }]
|
|
1148
1431
|
}], editorRef: [{
|
|
1149
1432
|
type: ViewChild,
|
|
1150
1433
|
args: ["editor", { static: false }]
|
|
@@ -1155,6 +1438,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1155
1438
|
function noop$1() {
|
|
1156
1439
|
// Default no-op for CVA callbacks.
|
|
1157
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Resolve a concrete light/dark value from the configured mode. Used to seed
|
|
1443
|
+
* the `data-mode` attribute synchronously (including during SSR, where
|
|
1444
|
+
* ModeController never runs). On the server `window` is undefined → defaults
|
|
1445
|
+
* to "dark" (matches the vanilla/React behavior).
|
|
1446
|
+
*/
|
|
1447
|
+
function resolveInitialMode(mode) {
|
|
1448
|
+
if (mode !== "auto")
|
|
1449
|
+
return mode;
|
|
1450
|
+
if (typeof window === "undefined")
|
|
1451
|
+
return "dark";
|
|
1452
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
1453
|
+
}
|
|
1158
1454
|
|
|
1159
1455
|
/**
|
|
1160
1456
|
* Tier 2 input binder. Attach to a `<textarea>` or `<input>` and pass an
|
|
@@ -1188,11 +1484,23 @@ class AIAutocompleteInputDirective {
|
|
|
1188
1484
|
this.destroy$ = new Subject();
|
|
1189
1485
|
this.onChangeFn = noop;
|
|
1190
1486
|
this.onTouchedFn = noop;
|
|
1487
|
+
// Timestamp of the last `input` event. A `keyup`/`select` that fires within
|
|
1488
|
+
// this window is the tail of a keystroke, not a deliberate caret move, so we
|
|
1489
|
+
// skip the caret-move handler there (mirrors the editor host's selectionchange
|
|
1490
|
+
// guard). performance.now() is monotonic; 0 means "no input yet".
|
|
1491
|
+
this.lastInputAt = 0;
|
|
1492
|
+
// Tracks the most-recently-promoted param so we only reposition the caret
|
|
1493
|
+
// once per option selection (mirrors the editor host's post-promote refocus).
|
|
1494
|
+
this.lastSeenParamId = "";
|
|
1191
1495
|
}
|
|
1192
1496
|
ngOnInit() {
|
|
1193
1497
|
if (!this.controller) {
|
|
1194
1498
|
throw new Error('[aiaInput] requires a controller — use `[aiaInput]="myController"`.');
|
|
1195
1499
|
}
|
|
1500
|
+
if (this.pendingWriteValue !== undefined) {
|
|
1501
|
+
this.controller.setValue(this.pendingWriteValue);
|
|
1502
|
+
this.pendingWriteValue = undefined;
|
|
1503
|
+
}
|
|
1196
1504
|
const el = this.el.nativeElement;
|
|
1197
1505
|
this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
|
|
1198
1506
|
// Sync editor text → DOM. Skips the write when values already match, so
|
|
@@ -1228,6 +1536,35 @@ class AIAutocompleteInputDirective {
|
|
|
1228
1536
|
this.renderer.removeAttribute(el, "aria-activedescendant");
|
|
1229
1537
|
}
|
|
1230
1538
|
});
|
|
1539
|
+
// After an option selection promotes a param, the core appends its text and
|
|
1540
|
+
// stamps `caretOffset` at the end of the inserted text. The text$ subscriber
|
|
1541
|
+
// above writes the new value but *restores the pre-write caret* (correct for
|
|
1542
|
+
// typing echoes, wrong here), so we move the caret to the promoted position.
|
|
1543
|
+
// Keyed on newParamId so it fires once per selection, not on every tick.
|
|
1544
|
+
// Registered after text$ so the new value is in the DOM before we seek.
|
|
1545
|
+
this.controller.newParamId$.pipe(takeUntil(this.destroy$)).subscribe((id) => {
|
|
1546
|
+
const current = id ?? "";
|
|
1547
|
+
if (current && current !== this.lastSeenParamId) {
|
|
1548
|
+
this.lastSeenParamId = current;
|
|
1549
|
+
const offset = this.controller.getState().caretOffset ?? el.value.length;
|
|
1550
|
+
const clamped = Math.min(Math.max(offset, 0), el.value.length);
|
|
1551
|
+
// Only grab focus if we don't already have it — don't yank focus back
|
|
1552
|
+
// from wherever the user moved it after selecting (e.g. a host-app
|
|
1553
|
+
// button). setSelectionRange still positions the caret regardless.
|
|
1554
|
+
if (typeof document !== "undefined" && document.activeElement !== el) {
|
|
1555
|
+
el.focus();
|
|
1556
|
+
}
|
|
1557
|
+
try {
|
|
1558
|
+
el.setSelectionRange(clamped, clamped);
|
|
1559
|
+
}
|
|
1560
|
+
catch {
|
|
1561
|
+
// Some input types (e.g. "number") don't support setSelectionRange.
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
else if (!current) {
|
|
1565
|
+
this.lastSeenParamId = "";
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1231
1568
|
}
|
|
1232
1569
|
ngOnDestroy() {
|
|
1233
1570
|
this.destroy$.next();
|
|
@@ -1241,6 +1578,11 @@ class AIAutocompleteInputDirective {
|
|
|
1241
1578
|
* we don't disrupt multi-keystroke input methods (Pinyin, Hangul, etc.).
|
|
1242
1579
|
*/
|
|
1243
1580
|
onInput(event) {
|
|
1581
|
+
// Stamp the input time before anything else. The autocapitalize path below
|
|
1582
|
+
// calls setSelectionRange, which can synchronously fire a `select` event →
|
|
1583
|
+
// onCaretMove; stamping first ensures that event falls inside the guard
|
|
1584
|
+
// window and is treated as part of this keystroke, not a caret move.
|
|
1585
|
+
this.lastInputAt = performance.now();
|
|
1244
1586
|
const target = event.target;
|
|
1245
1587
|
const raw = target.value;
|
|
1246
1588
|
const isComposing = event.isComposing === true;
|
|
@@ -1261,11 +1603,36 @@ class AIAutocompleteInputDirective {
|
|
|
1261
1603
|
}
|
|
1262
1604
|
}
|
|
1263
1605
|
this.controller.handleTextChange(next);
|
|
1606
|
+
// Sync the real caret into the core. The dropdown's auto-trigger gate only
|
|
1607
|
+
// opens when the caret is at the end of the text; without this, the core's
|
|
1608
|
+
// `caretOffset` stays pinned at whatever a prior promote/select set it to
|
|
1609
|
+
// and goes stale as the user keeps typing, wedging the dropdown closed.
|
|
1610
|
+
// selectionStart is null on some input types (number/email/color); treat
|
|
1611
|
+
// that as "caret at end" rather than feeding null into the core.
|
|
1612
|
+
this.controller.handleCaretAfterInput(target.selectionStart ?? target.value.length);
|
|
1264
1613
|
this.onChangeFn(next);
|
|
1265
1614
|
}
|
|
1266
1615
|
onKeyDown(event) {
|
|
1267
1616
|
this.controller.handleKeyDown(event);
|
|
1268
1617
|
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Caret moved by something other than typing — arrow keys, Home/End, or a
|
|
1620
|
+
* mouse click/drag (keyup + mouseup). Forward the new offset so the core can
|
|
1621
|
+
* re-evaluate the caret-at-end dropdown gate (and exit re-edit if the caret
|
|
1622
|
+
* left the region). We deliberately do NOT listen for the `select` event:
|
|
1623
|
+
* programmatic `setSelectionRange` calls (the text$ writeback, the post-select
|
|
1624
|
+
* caret reposition) fire it in jsdom/some browsers, which would re-enter here
|
|
1625
|
+
* and clobber `caretOffset`. `keyup`/`mouseup` only fire for real user input.
|
|
1626
|
+
* Skipped right after an `input` event, where the caret is already handled by
|
|
1627
|
+
* `handleCaretAfterInput` above.
|
|
1628
|
+
*/
|
|
1629
|
+
onCaretMove() {
|
|
1630
|
+
if (performance.now() - this.lastInputAt < 50)
|
|
1631
|
+
return;
|
|
1632
|
+
const el = this.el.nativeElement;
|
|
1633
|
+
// selectionStart is null on some input types — treat as caret at end.
|
|
1634
|
+
this.controller.handleCaretMove(el.selectionStart ?? el.value.length);
|
|
1635
|
+
}
|
|
1269
1636
|
onFocus() {
|
|
1270
1637
|
this.controller.setFocused(true);
|
|
1271
1638
|
}
|
|
@@ -1277,11 +1644,17 @@ class AIAutocompleteInputDirective {
|
|
|
1277
1644
|
writeValue(value) {
|
|
1278
1645
|
const next = value ?? "";
|
|
1279
1646
|
this.renderer.setProperty(this.el.nativeElement, "value", next);
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
|
|
1647
|
+
// If Angular calls writeValue() before ngOnInit (possible during dynamic
|
|
1648
|
+
// form construction), queue the value and let ngOnInit flush it. We
|
|
1649
|
+
// can't push it into the controller now because either the controller
|
|
1650
|
+
// binding hasn't resolved yet, or text$'s initial subscription would
|
|
1651
|
+
// overwrite it moments later.
|
|
1652
|
+
if (this.controller) {
|
|
1284
1653
|
this.controller.setValue(next);
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
this.pendingWriteValue = next;
|
|
1657
|
+
}
|
|
1285
1658
|
}
|
|
1286
1659
|
registerOnChange(fn) {
|
|
1287
1660
|
this.onChangeFn = fn;
|
|
@@ -1294,7 +1667,7 @@ class AIAutocompleteInputDirective {
|
|
|
1294
1667
|
}
|
|
1295
1668
|
}
|
|
1296
1669
|
AIAutocompleteInputDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1297
|
-
AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
|
|
1670
|
+
AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "keyup": "onCaretMove()", "mouseup": "onCaretMove()", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
|
|
1298
1671
|
{
|
|
1299
1672
|
provide: NG_VALUE_ACCESSOR,
|
|
1300
1673
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
@@ -1312,6 +1685,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1312
1685
|
"aria-autocomplete": "list",
|
|
1313
1686
|
"(input)": "onInput($event)",
|
|
1314
1687
|
"(keydown)": "onKeyDown($event)",
|
|
1688
|
+
"(keyup)": "onCaretMove()",
|
|
1689
|
+
"(mouseup)": "onCaretMove()",
|
|
1315
1690
|
"(focus)": "onFocus()",
|
|
1316
1691
|
"(blur)": "onBlur()",
|
|
1317
1692
|
},
|