@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,102 @@ 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
|
+
var _a, _b;
|
|
174
|
+
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
175
|
+
(_b = this.mutationObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
176
|
+
if (this.scrollListener && this.gridRef) {
|
|
177
|
+
this.gridRef.nativeElement.removeEventListener("scroll", this.scrollListener);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
bindOverflowTracking() {
|
|
181
|
+
var _a;
|
|
182
|
+
const grid = (_a = this.gridRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
183
|
+
if (!grid)
|
|
184
|
+
return;
|
|
185
|
+
this.zone.runOutsideAngular(() => {
|
|
186
|
+
this.scrollListener = () => this.measureOverflow();
|
|
187
|
+
grid.addEventListener("scroll", this.scrollListener, { passive: true });
|
|
188
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
189
|
+
this.resizeObserver = new ResizeObserver(() => this.measureOverflow());
|
|
190
|
+
this.resizeObserver.observe(grid);
|
|
191
|
+
}
|
|
192
|
+
// Projected option count changes don't resize the capped grid box, so
|
|
193
|
+
// ResizeObserver misses them — watch the child list directly to match
|
|
194
|
+
// React's useLayoutEffect-on-options re-measure.
|
|
195
|
+
if (typeof MutationObserver !== "undefined") {
|
|
196
|
+
this.mutationObserver = new MutationObserver(() => this.measureOverflow());
|
|
197
|
+
this.mutationObserver.observe(grid, { childList: true, subtree: true });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
measureOverflow() {
|
|
202
|
+
var _a;
|
|
203
|
+
const grid = (_a = this.gridRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
204
|
+
if (!grid)
|
|
205
|
+
return;
|
|
206
|
+
const next = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;
|
|
207
|
+
if (next !== this.hasBottomOverflow) {
|
|
208
|
+
this.zone.run(() => {
|
|
209
|
+
this.hasBottomOverflow = next;
|
|
210
|
+
this.cdr.markForCheck();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
GridComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: GridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
216
|
+
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 });
|
|
217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: GridComponent, decorators: [{
|
|
218
|
+
type: Component,
|
|
219
|
+
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"] }]
|
|
220
|
+
}], propDecorators: { min: [{
|
|
221
|
+
type: Input
|
|
222
|
+
}], max: [{
|
|
223
|
+
type: Input
|
|
224
|
+
}], gap: [{
|
|
225
|
+
type: Input
|
|
226
|
+
}], scroll: [{
|
|
227
|
+
type: Input
|
|
228
|
+
}], maxHeight: [{
|
|
229
|
+
type: Input
|
|
230
|
+
}], fade: [{
|
|
231
|
+
type: Input
|
|
232
|
+
}], gridRef: [{
|
|
233
|
+
type: ViewChild,
|
|
234
|
+
args: ["grid", { static: false }]
|
|
235
|
+
}] } });
|
|
236
|
+
|
|
71
237
|
/**
|
|
72
238
|
* Internal single-option cell used inside the suggestion grid. Mirrors
|
|
73
239
|
* `packages/react/src/components/SuggestionItem.tsx`. Handles:
|
|
@@ -122,10 +288,10 @@ class SuggestionItemComponent {
|
|
|
122
288
|
}
|
|
123
289
|
}
|
|
124
290
|
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:
|
|
291
|
+
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
292
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionItemComponent, decorators: [{
|
|
127
293
|
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:
|
|
294
|
+
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
295
|
}], propDecorators: { option: [{
|
|
130
296
|
type: Input
|
|
131
297
|
}], isHighlighted: [{
|
|
@@ -141,11 +307,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
141
307
|
}] } });
|
|
142
308
|
|
|
143
309
|
/**
|
|
144
|
-
* Internal
|
|
145
|
-
* `packages/react/src/components/SuggestionGrid.tsx` —
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
310
|
+
* Internal grid of suggestion options. Mirrors
|
|
311
|
+
* `packages/react/src/components/SuggestionGrid.tsx` — a thin wrapper over the
|
|
312
|
+
* `Grid` layout primitive (which owns the auto-fit columns and the
|
|
313
|
+
* capped-height scroll + bottom-fade). It only wires each option to its
|
|
314
|
+
* select/highlight handlers and listbox-scoped id.
|
|
149
315
|
*
|
|
150
316
|
* @internal
|
|
151
317
|
*/
|
|
@@ -157,67 +323,14 @@ class SuggestionGridComponent {
|
|
|
157
323
|
this.loading = false;
|
|
158
324
|
this.select = new EventEmitter();
|
|
159
325
|
this.highlight = new EventEmitter();
|
|
160
|
-
this.hasBottomOverflow = false;
|
|
161
|
-
this.zone = inject(NgZone);
|
|
162
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
163
326
|
this.trackByOptionText = (_, opt) => opt.text;
|
|
164
327
|
}
|
|
165
|
-
ngAfterViewInit() {
|
|
166
|
-
this.bindOverflowTracking();
|
|
167
|
-
this.measureOverflow();
|
|
168
|
-
}
|
|
169
|
-
ngOnChanges() {
|
|
170
|
-
// Synchronously re-measure when options change — matches React's
|
|
171
|
-
// useLayoutEffect on [options]. ResizeObserver alone misses this case
|
|
172
|
-
// because the observed box doesn't grow when the grid hits max-height.
|
|
173
|
-
queueMicrotask(() => this.measureOverflow());
|
|
174
|
-
}
|
|
175
|
-
ngOnDestroy() {
|
|
176
|
-
var _a;
|
|
177
|
-
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
178
|
-
if (this.scrollListener && this.gridRef) {
|
|
179
|
-
this.gridRef.nativeElement.removeEventListener("scroll", this.scrollListener);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
onSelectItem(option) {
|
|
183
|
-
this.select.emit(option);
|
|
184
|
-
}
|
|
185
|
-
onHighlightItem(index) {
|
|
186
|
-
this.highlight.emit(index);
|
|
187
|
-
}
|
|
188
|
-
bindOverflowTracking() {
|
|
189
|
-
var _a;
|
|
190
|
-
const grid = (_a = this.gridRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
191
|
-
if (!grid)
|
|
192
|
-
return;
|
|
193
|
-
this.zone.runOutsideAngular(() => {
|
|
194
|
-
this.scrollListener = () => this.measureOverflow();
|
|
195
|
-
grid.addEventListener("scroll", this.scrollListener, { passive: true });
|
|
196
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
197
|
-
this.resizeObserver = new ResizeObserver(() => this.measureOverflow());
|
|
198
|
-
this.resizeObserver.observe(grid);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
measureOverflow() {
|
|
203
|
-
var _a;
|
|
204
|
-
const grid = (_a = this.gridRef) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
205
|
-
if (!grid)
|
|
206
|
-
return;
|
|
207
|
-
const next = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;
|
|
208
|
-
if (next !== this.hasBottomOverflow) {
|
|
209
|
-
this.zone.run(() => {
|
|
210
|
-
this.hasBottomOverflow = next;
|
|
211
|
-
this.cdr.markForCheck();
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
328
|
}
|
|
216
329
|
SuggestionGridComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
217
|
-
SuggestionGridComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: SuggestionGridComponent, isStandalone: true, selector: "aia-suggestion-grid", inputs: { options: "options", activeIndex: "activeIndex", listboxId: "listboxId", loading: "loading" }, outputs: { select: "select", highlight: "highlight" },
|
|
330
|
+
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 });
|
|
218
331
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SuggestionGridComponent, decorators: [{
|
|
219
332
|
type: Component,
|
|
220
|
-
args: [{ selector: "aia-suggestion-grid", standalone: true, imports: [CommonModule, SuggestionItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<
|
|
333
|
+
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" }]
|
|
221
334
|
}], propDecorators: { options: [{
|
|
222
335
|
type: Input
|
|
223
336
|
}], activeIndex: [{
|
|
@@ -230,9 +343,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
230
343
|
type: Output
|
|
231
344
|
}], highlight: [{
|
|
232
345
|
type: Output
|
|
233
|
-
}], gridRef: [{
|
|
234
|
-
type: ViewChild,
|
|
235
|
-
args: ["grid", { static: false }]
|
|
236
346
|
}] } });
|
|
237
347
|
|
|
238
348
|
/**
|
|
@@ -256,21 +366,43 @@ class AIAutocompleteDropdownComponent {
|
|
|
256
366
|
this.showPills = true;
|
|
257
367
|
/** Fallback skeleton-bar widths used when loading with zero cached options. */
|
|
258
368
|
this.fallbackSkeletonBarWidths = [159, 119, 164];
|
|
369
|
+
// Bridge for the @Input `showPills` into the reactive pipeline. Without this,
|
|
370
|
+
// changing the input after init wouldn't trigger a new vm emission until the
|
|
371
|
+
// next controller.dropdown$ tick — and we'd lose the showPills update entirely
|
|
372
|
+
// if the controller is idle. Updated from ngOnChanges below.
|
|
373
|
+
this.showPills$ = new BehaviorSubject(true);
|
|
259
374
|
this.trackByWidth = (_, w) => w;
|
|
260
375
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
376
|
+
ngOnInit() {
|
|
377
|
+
// Seed showPills$ from the input's resolved value at init time.
|
|
378
|
+
this.showPills$.next(this.showPills);
|
|
379
|
+
this.initVm();
|
|
380
|
+
}
|
|
381
|
+
ngOnChanges(changes) {
|
|
382
|
+
if (changes["showPills"]) {
|
|
383
|
+
this.showPills$.next(this.showPills);
|
|
384
|
+
}
|
|
385
|
+
// Re-bind vm$ when the controller @Input is swapped (e.g. a multi-step form
|
|
386
|
+
// toggling between controllers). Without this, vm$ stays subscribed to the
|
|
387
|
+
// previous controller's dropdown$ — rendering stale options, or, if that
|
|
388
|
+
// controller was destroy()-ed, receiving a completion that collapses the
|
|
389
|
+
// dropdown permanently. Skip the first change: ngOnChanges fires before
|
|
390
|
+
// ngOnInit, which owns the initial creation.
|
|
391
|
+
if (changes["controller"] && !changes["controller"].firstChange && this.controller) {
|
|
392
|
+
this.initVm();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/** Build the view-model stream from the current controller + showPills$. */
|
|
396
|
+
initVm() {
|
|
397
|
+
this.vm$ = combineLatest([this.controller.dropdown$, this.showPills$]).pipe(map(([dd, showPills]) => {
|
|
398
|
+
var _a, _b, _c;
|
|
269
399
|
const options = (_b = (_a = dd.suggestions[0]) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : [];
|
|
270
|
-
const showsPills =
|
|
400
|
+
const showsPills = showPills && (dd.pills.length > 0 || dd.isLoading);
|
|
271
401
|
const showsOptions = options.length > 0;
|
|
272
402
|
const showsFallbackSkeleton = dd.isLoading && !showsOptions;
|
|
273
403
|
const isVisible = dd.isOpen && (showsOptions || showsPills || dd.isLoading);
|
|
404
|
+
const optionHighlighted = dd.activeIndex >= 0 && Boolean((_c = options[dd.activeIndex]) === null || _c === void 0 ? void 0 : _c.is_tappable);
|
|
405
|
+
const { key: footerKey, hint: footerHint } = getFooterHint(dd.hasTabbedToHighlight, optionHighlighted);
|
|
274
406
|
return {
|
|
275
407
|
pills: dd.pills,
|
|
276
408
|
options,
|
|
@@ -280,6 +412,8 @@ class AIAutocompleteDropdownComponent {
|
|
|
280
412
|
showsOptions,
|
|
281
413
|
showsFallbackSkeleton,
|
|
282
414
|
isVisible,
|
|
415
|
+
footerKey,
|
|
416
|
+
footerHint,
|
|
283
417
|
};
|
|
284
418
|
}));
|
|
285
419
|
}
|
|
@@ -298,10 +432,16 @@ class AIAutocompleteDropdownComponent {
|
|
|
298
432
|
}
|
|
299
433
|
}
|
|
300
434
|
AIAutocompleteDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
301
|
-
AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n
|
|
435
|
+
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 });
|
|
302
436
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, decorators: [{
|
|
303
437
|
type: Component,
|
|
304
|
-
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [
|
|
438
|
+
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [
|
|
439
|
+
CommonModule,
|
|
440
|
+
PillListComponent,
|
|
441
|
+
SuggestionGridComponent,
|
|
442
|
+
StackComponent,
|
|
443
|
+
ClusterComponent,
|
|
444
|
+
], 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"] }]
|
|
305
445
|
}], propDecorators: { controller: [{
|
|
306
446
|
type: Input
|
|
307
447
|
}], showPills: [{
|
|
@@ -367,17 +507,37 @@ class AIAutocompleteController {
|
|
|
367
507
|
// arrow opens the dropdown from the input.
|
|
368
508
|
optionsPosition: (_b = opts.optionsPosition) !== null && _b !== void 0 ? _b : "below" }));
|
|
369
509
|
this.rawState$ = new BehaviorSubject(this.core.getState());
|
|
370
|
-
|
|
510
|
+
// Push the store's *current* state, not the snapshot the listener was
|
|
511
|
+
// handed. The core notifies listeners synchronously, and some actions
|
|
512
|
+
// trigger reentrant `store.set` calls (e.g. typing a non-matching char
|
|
513
|
+
// during re-edit makes the core synchronously exit re-edit and kick off a
|
|
514
|
+
// fetch). Reentrancy makes the outer set's listener loop continue with a
|
|
515
|
+
// now-stale snapshot, so the listener can fire *out of order* — delivering
|
|
516
|
+
// an older state (still in re-edit) after a newer one (re-edit exited).
|
|
517
|
+
// Reading `getState()` at emission time collapses every notification onto
|
|
518
|
+
// the true latest state, so a stale `editingParam` can't resurrect and
|
|
519
|
+
// retrigger the editor's re-edit caret-park (which would yank the caret
|
|
520
|
+
// back to where re-editing started). distinctUntilChanged downstream drops
|
|
521
|
+
// the resulting duplicate emissions. React avoids this because it batches
|
|
522
|
+
// renders and its caret effects are dependency-gated; our subscription is
|
|
523
|
+
// synchronous, so we coalesce here instead.
|
|
524
|
+
this.unsubscribeCore = this.core.subscribe(() => {
|
|
371
525
|
if (!this.isDestroyed)
|
|
372
|
-
this.rawState$.next(
|
|
526
|
+
this.rawState$.next(this.core.getState());
|
|
373
527
|
});
|
|
374
528
|
// === Per-field observables ===
|
|
529
|
+
// Array-valued fields use shallow equality. The vanilla store spreads on
|
|
530
|
+
// every write (`store.set({ completedParams: [...] })`), so reference
|
|
531
|
+
// equality would re-fire downstream subscribers on every state tick even
|
|
532
|
+
// when the array's contents are unchanged. The other fields are
|
|
533
|
+
// primitives or stable object references, so default identity equality
|
|
534
|
+
// works fine.
|
|
375
535
|
this.text$ = deriveField(this.rawState$, (s) => s.text);
|
|
376
|
-
this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams);
|
|
377
|
-
this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions);
|
|
378
|
-
this.segments$ = deriveField(this.rawState$, (s) => s.segments);
|
|
536
|
+
this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams, arrEq);
|
|
537
|
+
this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions, arrEq);
|
|
538
|
+
this.segments$ = deriveField(this.rawState$, (s) => s.segments, arrEq);
|
|
379
539
|
this.newParamId$ = deriveField(this.rawState$, (s) => s.newParamId);
|
|
380
|
-
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions);
|
|
540
|
+
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions, arrEq);
|
|
381
541
|
this.activeIndex$ = deriveField(this.rawState$, (s) => s.activeDropdownIndex);
|
|
382
542
|
this.isLoading$ = deriveField(this.rawState$, computeUiLoading);
|
|
383
543
|
this.isReady$ = deriveField(this.rawState$, (s) => s.isReady);
|
|
@@ -418,68 +578,109 @@ class AIAutocompleteController {
|
|
|
418
578
|
return computeState(this.rawState$.value);
|
|
419
579
|
}
|
|
420
580
|
// === Action methods (pass-through to the core) ===
|
|
581
|
+
//
|
|
582
|
+
// Every action method begins with an `isDestroyed` guard so that callers
|
|
583
|
+
// who hold a stale reference after `destroy()` don't accidentally trigger
|
|
584
|
+
// work on a torn-down core. The vanilla core does not gate its own methods
|
|
585
|
+
// post-dispose — `reset()`, `handleTextChange()`, and friends would still
|
|
586
|
+
// schedule fetches or fire HTTP requests against an aborted controller,
|
|
587
|
+
// and the responses would have nowhere to surface (rawState$ is already
|
|
588
|
+
// complete). The guard makes the JSDoc-promised "no-op silently"
|
|
589
|
+
// semantics actually true.
|
|
421
590
|
/** Clear all state, rotate `session_id`, and re-fetch. Call after submit. */
|
|
422
591
|
reset() {
|
|
592
|
+
if (this.isDestroyed)
|
|
593
|
+
return;
|
|
423
594
|
this.core.reset();
|
|
424
595
|
}
|
|
425
596
|
/** Pick a dropdown option (or replace the re-edited param if in re-edit mode). */
|
|
426
597
|
selectOption(option) {
|
|
598
|
+
if (this.isDestroyed)
|
|
599
|
+
return;
|
|
427
600
|
this.core.selectOption(option);
|
|
428
601
|
}
|
|
429
602
|
/** Promote the pill at `index` to active (front of the list). */
|
|
430
603
|
setActivePill(index) {
|
|
604
|
+
if (this.isDestroyed)
|
|
605
|
+
return;
|
|
431
606
|
this.core.setActivePill(index);
|
|
432
607
|
}
|
|
433
608
|
/** Drop the last completed param's "completed" status. Text stays as plain text. */
|
|
434
609
|
removeLastParam() {
|
|
610
|
+
if (this.isDestroyed)
|
|
611
|
+
return;
|
|
435
612
|
this.core.removeLastParam();
|
|
436
613
|
}
|
|
437
614
|
/** Clear the shimmer animation state for the most-recently-added param. */
|
|
438
615
|
clearNewParamId() {
|
|
616
|
+
if (this.isDestroyed)
|
|
617
|
+
return;
|
|
439
618
|
this.core.clearNewParamId();
|
|
440
619
|
}
|
|
441
620
|
/** Controlled-mode setter for editor text. */
|
|
442
621
|
setValue(text) {
|
|
622
|
+
if (this.isDestroyed)
|
|
623
|
+
return;
|
|
443
624
|
this.core.setValue(text);
|
|
444
625
|
}
|
|
445
626
|
/** Controlled-mode setter for completed params. */
|
|
446
627
|
setCompletedParams(params) {
|
|
628
|
+
if (this.isDestroyed)
|
|
629
|
+
return;
|
|
447
630
|
this.core.setCompletedParams(params);
|
|
448
631
|
}
|
|
449
632
|
/** Forward plain-text input from the consumer's `<textarea>` to the core. */
|
|
450
633
|
handleTextChange(value) {
|
|
634
|
+
if (this.isDestroyed)
|
|
635
|
+
return;
|
|
451
636
|
this.core.handleTextChange(value);
|
|
452
637
|
}
|
|
453
638
|
/** Forward keyboard events from the consumer's `<textarea>` to the core. */
|
|
454
639
|
handleKeyDown(event) {
|
|
640
|
+
if (this.isDestroyed)
|
|
641
|
+
return;
|
|
455
642
|
this.core.handleKeyDown(event);
|
|
456
643
|
}
|
|
457
644
|
/** Notify the core of focus state. */
|
|
458
645
|
setFocused(focused) {
|
|
646
|
+
if (this.isDestroyed)
|
|
647
|
+
return;
|
|
459
648
|
this.core.setFocused(focused);
|
|
460
649
|
}
|
|
461
650
|
/** Enter re-edit mode for the bold param with the given id. */
|
|
462
651
|
startEditingParam(paramId) {
|
|
652
|
+
if (this.isDestroyed)
|
|
653
|
+
return;
|
|
463
654
|
this.core.startEditingParam(paramId);
|
|
464
655
|
}
|
|
465
656
|
/** Exit re-edit mode without changing the bold param. */
|
|
466
657
|
exitEditMode() {
|
|
658
|
+
if (this.isDestroyed)
|
|
659
|
+
return;
|
|
467
660
|
this.core.exitEditMode();
|
|
468
661
|
}
|
|
469
662
|
/** Forward post-input caret position (extends the edit tail). */
|
|
470
663
|
handleCaretAfterInput(offset) {
|
|
664
|
+
if (this.isDestroyed)
|
|
665
|
+
return;
|
|
471
666
|
this.core.handleCaretAfterInput(offset);
|
|
472
667
|
}
|
|
473
668
|
/** Forward selection-only caret moves (may exit re-edit). */
|
|
474
669
|
handleCaretMove(offset) {
|
|
670
|
+
if (this.isDestroyed)
|
|
671
|
+
return;
|
|
475
672
|
this.core.handleCaretMove(offset);
|
|
476
673
|
}
|
|
477
674
|
/** Replace the bold param being re-edited. Returns true when applied. */
|
|
478
675
|
replaceEditingRange(replacement) {
|
|
676
|
+
if (this.isDestroyed)
|
|
677
|
+
return false;
|
|
479
678
|
return this.core.replaceEditingRange(replacement);
|
|
480
679
|
}
|
|
481
680
|
/** Set the highlighted option index (typically driven by hover). */
|
|
482
681
|
setActiveDropdownIndex(index) {
|
|
682
|
+
if (this.isDestroyed)
|
|
683
|
+
return;
|
|
483
684
|
this.core.setActiveDropdownIndex(index);
|
|
484
685
|
}
|
|
485
686
|
// === Lifecycle ===
|
|
@@ -491,6 +692,8 @@ class AIAutocompleteController {
|
|
|
491
692
|
* (e.g. `text$` instead of `onChange`).
|
|
492
693
|
*/
|
|
493
694
|
update(opts) {
|
|
695
|
+
if (this.isDestroyed)
|
|
696
|
+
return;
|
|
494
697
|
this.core.update(opts);
|
|
495
698
|
}
|
|
496
699
|
/**
|
|
@@ -508,6 +711,24 @@ class AIAutocompleteController {
|
|
|
508
711
|
}
|
|
509
712
|
}
|
|
510
713
|
// ===== Pure helpers — not part of the public surface =====
|
|
714
|
+
/**
|
|
715
|
+
* Shallow array equality. The vanilla store reassigns array fields on every
|
|
716
|
+
* write (`store.set({ completedParams: [...prev, x] })`), so reference
|
|
717
|
+
* equality alone would re-emit downstream even when the contents are
|
|
718
|
+
* identical. Used for `completedParams$`, `suggestions$`, `suggestionPills$`,
|
|
719
|
+
* and `segments$`.
|
|
720
|
+
*/
|
|
721
|
+
function arrEq(a, b) {
|
|
722
|
+
if (a === b)
|
|
723
|
+
return true;
|
|
724
|
+
if (a.length !== b.length)
|
|
725
|
+
return false;
|
|
726
|
+
for (let i = 0; i < a.length; i++) {
|
|
727
|
+
if (a[i] !== b[i])
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
511
732
|
function computeUiLoading(s) {
|
|
512
733
|
return s.isLoading && !s.editingParam && !s.inSelectionAnimation;
|
|
513
734
|
}
|
|
@@ -550,6 +771,7 @@ function computeDropdown(s) {
|
|
|
550
771
|
isOpen: s.isDropdownOpen,
|
|
551
772
|
pills: dropdownPills,
|
|
552
773
|
isLoading: computeUiLoading(s),
|
|
774
|
+
hasTabbedToHighlight: s.hasTabbedToHighlight,
|
|
553
775
|
};
|
|
554
776
|
}
|
|
555
777
|
|
|
@@ -704,7 +926,9 @@ class AIAutocompleteEditorHostDirective {
|
|
|
704
926
|
const anchor = sel.anchorNode;
|
|
705
927
|
const startEl = anchor.nodeType === Node.ELEMENT_NODE ? anchor : anchor.parentElement;
|
|
706
928
|
const strong = startEl === null || startEl === void 0 ? void 0 : startEl.closest('strong[data-seg="completed"][data-param-id]');
|
|
707
|
-
|
|
929
|
+
// `DOMStringMap` is an index signature; bracket notation is required
|
|
930
|
+
// by `noPropertyAccessFromIndexSignature`.
|
|
931
|
+
const enclosing = (_a = strong === null || strong === void 0 ? void 0 : strong.dataset["paramId"]) !== null && _a !== void 0 ? _a : null;
|
|
708
932
|
if (enclosing && enclosing !== this.editingParamId) {
|
|
709
933
|
this.zone.run(() => this.controller.startEditingParam(enclosing));
|
|
710
934
|
return;
|
|
@@ -806,13 +1030,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
806
1030
|
type: Input
|
|
807
1031
|
}] } });
|
|
808
1032
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1033
|
+
/**
|
|
1034
|
+
* Internal default submit button (up-arrow). Angular analogue of React's
|
|
1035
|
+
* `SubmitButton`. The default/custom/hidden branching stays in the parent
|
|
1036
|
+
* (`AIAutocompleteComponent`); this is only the built-in button.
|
|
1037
|
+
*
|
|
1038
|
+
* @internal
|
|
1039
|
+
*/
|
|
1040
|
+
class SubmitButtonComponent {
|
|
1041
|
+
constructor() {
|
|
1042
|
+
this.disabled = false;
|
|
1043
|
+
this.submit = new EventEmitter();
|
|
1044
|
+
}
|
|
1045
|
+
onClick(event) {
|
|
1046
|
+
this.submit.emit(event);
|
|
1047
|
+
}
|
|
815
1048
|
}
|
|
1049
|
+
SubmitButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SubmitButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1050
|
+
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 });
|
|
1051
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SubmitButtonComponent, decorators: [{
|
|
1052
|
+
type: Component,
|
|
1053
|
+
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"] }]
|
|
1054
|
+
}], propDecorators: { disabled: [{
|
|
1055
|
+
type: Input
|
|
1056
|
+
}], submit: [{
|
|
1057
|
+
type: Output
|
|
1058
|
+
}] } });
|
|
1059
|
+
|
|
816
1060
|
/**
|
|
817
1061
|
* Tier 1 component — `<ai-autocomplete>`. The Angular equivalent of React's
|
|
818
1062
|
* `<AIAutocomplete />`. Owns the contentEditable editor, inline pills, mode
|
|
@@ -840,19 +1084,26 @@ class AIAutocompleteComponent {
|
|
|
840
1084
|
this.completedParamsChange = new EventEmitter();
|
|
841
1085
|
this.focused = new EventEmitter();
|
|
842
1086
|
this.blurred = new EventEmitter();
|
|
1087
|
+
/**
|
|
1088
|
+
* Concrete light/dark value bound to `[attr.data-mode]`. Seeded in ngOnInit
|
|
1089
|
+
* so the attribute is present during SSR (where ModeController, gated behind
|
|
1090
|
+
* isPlatformBrowser in ngAfterViewInit, never runs). On the browser the
|
|
1091
|
+
* ModeController's resolve callback keeps this in sync — so its runtime
|
|
1092
|
+
* prefers-color-scheme updates and this binding always agree (no clobber).
|
|
1093
|
+
*/
|
|
843
1094
|
this.resolvedMode = "dark";
|
|
844
1095
|
this.onChangeFn = noop$1;
|
|
845
1096
|
this.onTouchedFn = noop$1;
|
|
846
1097
|
// §16.9: prefer `inject()` over constructor-parameter DI for components.
|
|
847
1098
|
this.platformId = inject(PLATFORM_ID);
|
|
848
|
-
this.el = inject(ElementRef);
|
|
849
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
850
1099
|
this.zone = inject(NgZone);
|
|
1100
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
851
1101
|
}
|
|
852
1102
|
ngOnInit() {
|
|
1103
|
+
// Seed before the browser guard so SSR renders a concrete data-mode.
|
|
1104
|
+
this.resolvedMode = resolveInitialMode(this.mode);
|
|
853
1105
|
if (!isPlatformBrowser(this.platformId))
|
|
854
1106
|
return;
|
|
855
|
-
this.resolvedMode = resolveInitialMode(this.mode);
|
|
856
1107
|
this.controller = new AIAutocompleteController({
|
|
857
1108
|
apiConfig: this.apiConfig,
|
|
858
1109
|
optionOverrides: this.optionOverrides,
|
|
@@ -886,12 +1137,26 @@ class AIAutocompleteComponent {
|
|
|
886
1137
|
setCursorOffset(editor, offset);
|
|
887
1138
|
},
|
|
888
1139
|
});
|
|
1140
|
+
if (this.pendingWriteValue !== undefined) {
|
|
1141
|
+
this.controller.setValue(this.pendingWriteValue);
|
|
1142
|
+
this.pendingWriteValue = undefined;
|
|
1143
|
+
}
|
|
889
1144
|
}
|
|
890
1145
|
ngAfterViewInit() {
|
|
891
1146
|
if (!isPlatformBrowser(this.platformId))
|
|
892
1147
|
return;
|
|
893
|
-
|
|
894
|
-
|
|
1148
|
+
// ModeController writes `data-mode` to the element it's given. The CSS
|
|
1149
|
+
// selectors are scoped to `.magicx-aia[data-mode="..."]` — which lives on
|
|
1150
|
+
// the inner `<div class="magicx-aia magicx-aia-container" #container>`,
|
|
1151
|
+
// NOT the host `<ai-autocomplete>` element. The resolve callback mirrors
|
|
1152
|
+
// the value into `resolvedMode` so the `[attr.data-mode]` binding stays in
|
|
1153
|
+
// lockstep with ModeController — the binding handles SSR, ModeController
|
|
1154
|
+
// handles runtime prefers-color-scheme switches, and because they always
|
|
1155
|
+
// agree neither clobbers the other.
|
|
1156
|
+
this.modeController = new ModeController(this.containerRef.nativeElement, this.mode, (resolved) => {
|
|
1157
|
+
this.resolvedMode = resolved;
|
|
1158
|
+
this.cdr.markForCheck();
|
|
1159
|
+
});
|
|
895
1160
|
// Pill-wrap detection — when the pill list wraps below the editor's last
|
|
896
1161
|
// line, drop its 8px left margin via a data attribute the CSS keys off.
|
|
897
1162
|
// Mirrors the React component's useLayoutEffect.
|
|
@@ -910,20 +1175,24 @@ class AIAutocompleteComponent {
|
|
|
910
1175
|
var _a;
|
|
911
1176
|
if (!this.controller)
|
|
912
1177
|
return;
|
|
1178
|
+
// `SimpleChanges` is `{ [key: string]: SimpleChange }`, so per
|
|
1179
|
+
// `noPropertyAccessFromIndexSignature` each key lookup uses bracket
|
|
1180
|
+
// notation — this keeps the strictness contract that the rest of the
|
|
1181
|
+
// workspace enforces.
|
|
913
1182
|
// Controlled-mode propagation
|
|
914
|
-
if (changes
|
|
1183
|
+
if (changes["value"] && this.value !== undefined) {
|
|
915
1184
|
this.controller.setValue(this.value);
|
|
916
1185
|
}
|
|
917
|
-
if (changes
|
|
1186
|
+
if (changes["completedParams"] && this.completedParams !== undefined) {
|
|
918
1187
|
this.controller.setCompletedParams(this.completedParams);
|
|
919
1188
|
}
|
|
920
1189
|
// Reactive options
|
|
921
|
-
if (changes
|
|
922
|
-
changes
|
|
923
|
-
changes
|
|
924
|
-
changes
|
|
925
|
-
changes
|
|
926
|
-
changes
|
|
1190
|
+
if (changes["apiConfig"] ||
|
|
1191
|
+
changes["optionOverrides"] ||
|
|
1192
|
+
changes["dropdownTrigger"] ||
|
|
1193
|
+
changes["optionsPosition"] ||
|
|
1194
|
+
changes["closeDropdownOnBlur"] ||
|
|
1195
|
+
changes["showNonTappableOptions"]) {
|
|
927
1196
|
this.controller.update({
|
|
928
1197
|
apiConfig: this.apiConfig,
|
|
929
1198
|
optionOverrides: this.optionOverrides,
|
|
@@ -933,9 +1202,12 @@ class AIAutocompleteComponent {
|
|
|
933
1202
|
showNonTappableOptions: this.showNonTappableOptions,
|
|
934
1203
|
});
|
|
935
1204
|
}
|
|
936
|
-
if (changes
|
|
937
|
-
|
|
1205
|
+
if (changes["mode"]) {
|
|
1206
|
+
// Keep the binding correct even when ModeController doesn't exist yet
|
|
1207
|
+
// (SSR / pre-ngAfterViewInit). On the browser, setMode triggers the
|
|
1208
|
+
// resolve callback which sets the same value.
|
|
938
1209
|
this.resolvedMode = resolveInitialMode(this.mode);
|
|
1210
|
+
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(this.mode);
|
|
939
1211
|
}
|
|
940
1212
|
}
|
|
941
1213
|
ngOnDestroy() {
|
|
@@ -964,8 +1236,8 @@ class AIAutocompleteComponent {
|
|
|
964
1236
|
setMode(mode) {
|
|
965
1237
|
var _a;
|
|
966
1238
|
this.mode = mode;
|
|
967
|
-
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
|
|
968
1239
|
this.resolvedMode = resolveInitialMode(mode);
|
|
1240
|
+
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
|
|
969
1241
|
this.cdr.markForCheck();
|
|
970
1242
|
}
|
|
971
1243
|
// === Template helpers ===
|
|
@@ -1019,9 +1291,16 @@ class AIAutocompleteComponent {
|
|
|
1019
1291
|
}
|
|
1020
1292
|
// === ControlValueAccessor ===
|
|
1021
1293
|
writeValue(value) {
|
|
1022
|
-
|
|
1294
|
+
const next = value !== null && value !== void 0 ? value : "";
|
|
1295
|
+
if (!this.controller) {
|
|
1296
|
+
// Reactive Forms can call writeValue() before ngOnInit creates the
|
|
1297
|
+
// controller (dynamic form construction, or a FormControl that already
|
|
1298
|
+
// had a value at directive-creation time). Buffer the value; ngOnInit
|
|
1299
|
+
// flushes it as soon as the controller exists.
|
|
1300
|
+
this.pendingWriteValue = next;
|
|
1023
1301
|
return;
|
|
1024
|
-
|
|
1302
|
+
}
|
|
1303
|
+
this.controller.setValue(next);
|
|
1025
1304
|
}
|
|
1026
1305
|
registerOnChange(fn) {
|
|
1027
1306
|
this.onChangeFn = fn;
|
|
@@ -1103,7 +1382,7 @@ AIAutocompleteComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0"
|
|
|
1103
1382
|
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1104
1383
|
multi: true,
|
|
1105
1384
|
},
|
|
1106
|
-
], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n
|
|
1385
|
+
], 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 });
|
|
1107
1386
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, decorators: [{
|
|
1108
1387
|
type: Component,
|
|
1109
1388
|
args: [{ selector: "ai-autocomplete", standalone: true, imports: [
|
|
@@ -1111,6 +1390,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1111
1390
|
AIAutocompleteDropdownComponent,
|
|
1112
1391
|
AIAutocompleteEditorHostDirective,
|
|
1113
1392
|
PillListComponent,
|
|
1393
|
+
SubmitButtonComponent,
|
|
1114
1394
|
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
|
|
1115
1395
|
{
|
|
1116
1396
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -1118,7 +1398,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1118
1398
|
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1119
1399
|
multi: true,
|
|
1120
1400
|
},
|
|
1121
|
-
], template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n
|
|
1401
|
+
], 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"] }]
|
|
1122
1402
|
}], propDecorators: { apiConfig: [{
|
|
1123
1403
|
type: Input
|
|
1124
1404
|
}], optionOverrides: [{
|
|
@@ -1161,6 +1441,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1161
1441
|
type: Output
|
|
1162
1442
|
}], blurred: [{
|
|
1163
1443
|
type: Output
|
|
1444
|
+
}], containerRef: [{
|
|
1445
|
+
type: ViewChild,
|
|
1446
|
+
args: ["container", { static: true }]
|
|
1164
1447
|
}], editorRef: [{
|
|
1165
1448
|
type: ViewChild,
|
|
1166
1449
|
args: ["editor", { static: false }]
|
|
@@ -1171,6 +1454,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1171
1454
|
function noop$1() {
|
|
1172
1455
|
// Default no-op for CVA callbacks.
|
|
1173
1456
|
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Resolve a concrete light/dark value from the configured mode. Used to seed
|
|
1459
|
+
* the `data-mode` attribute synchronously (including during SSR, where
|
|
1460
|
+
* ModeController never runs). On the server `window` is undefined → defaults
|
|
1461
|
+
* to "dark" (matches the vanilla/React behavior).
|
|
1462
|
+
*/
|
|
1463
|
+
function resolveInitialMode(mode) {
|
|
1464
|
+
if (mode !== "auto")
|
|
1465
|
+
return mode;
|
|
1466
|
+
if (typeof window === "undefined")
|
|
1467
|
+
return "dark";
|
|
1468
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
1469
|
+
}
|
|
1174
1470
|
|
|
1175
1471
|
/**
|
|
1176
1472
|
* Tier 2 input binder. Attach to a `<textarea>` or `<input>` and pass an
|
|
@@ -1204,11 +1500,23 @@ class AIAutocompleteInputDirective {
|
|
|
1204
1500
|
this.destroy$ = new Subject();
|
|
1205
1501
|
this.onChangeFn = noop;
|
|
1206
1502
|
this.onTouchedFn = noop;
|
|
1503
|
+
// Timestamp of the last `input` event. A `keyup`/`select` that fires within
|
|
1504
|
+
// this window is the tail of a keystroke, not a deliberate caret move, so we
|
|
1505
|
+
// skip the caret-move handler there (mirrors the editor host's selectionchange
|
|
1506
|
+
// guard). performance.now() is monotonic; 0 means "no input yet".
|
|
1507
|
+
this.lastInputAt = 0;
|
|
1508
|
+
// Tracks the most-recently-promoted param so we only reposition the caret
|
|
1509
|
+
// once per option selection (mirrors the editor host's post-promote refocus).
|
|
1510
|
+
this.lastSeenParamId = "";
|
|
1207
1511
|
}
|
|
1208
1512
|
ngOnInit() {
|
|
1209
1513
|
if (!this.controller) {
|
|
1210
1514
|
throw new Error('[aiaInput] requires a controller — use `[aiaInput]="myController"`.');
|
|
1211
1515
|
}
|
|
1516
|
+
if (this.pendingWriteValue !== undefined) {
|
|
1517
|
+
this.controller.setValue(this.pendingWriteValue);
|
|
1518
|
+
this.pendingWriteValue = undefined;
|
|
1519
|
+
}
|
|
1212
1520
|
const el = this.el.nativeElement;
|
|
1213
1521
|
this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
|
|
1214
1522
|
// Sync editor text → DOM. Skips the write when values already match, so
|
|
@@ -1244,6 +1552,36 @@ class AIAutocompleteInputDirective {
|
|
|
1244
1552
|
this.renderer.removeAttribute(el, "aria-activedescendant");
|
|
1245
1553
|
}
|
|
1246
1554
|
});
|
|
1555
|
+
// After an option selection promotes a param, the core appends its text and
|
|
1556
|
+
// stamps `caretOffset` at the end of the inserted text. The text$ subscriber
|
|
1557
|
+
// above writes the new value but *restores the pre-write caret* (correct for
|
|
1558
|
+
// typing echoes, wrong here), so we move the caret to the promoted position.
|
|
1559
|
+
// Keyed on newParamId so it fires once per selection, not on every tick.
|
|
1560
|
+
// Registered after text$ so the new value is in the DOM before we seek.
|
|
1561
|
+
this.controller.newParamId$.pipe(takeUntil(this.destroy$)).subscribe((id) => {
|
|
1562
|
+
var _a;
|
|
1563
|
+
const current = id !== null && id !== void 0 ? id : "";
|
|
1564
|
+
if (current && current !== this.lastSeenParamId) {
|
|
1565
|
+
this.lastSeenParamId = current;
|
|
1566
|
+
const offset = (_a = this.controller.getState().caretOffset) !== null && _a !== void 0 ? _a : el.value.length;
|
|
1567
|
+
const clamped = Math.min(Math.max(offset, 0), el.value.length);
|
|
1568
|
+
// Only grab focus if we don't already have it — don't yank focus back
|
|
1569
|
+
// from wherever the user moved it after selecting (e.g. a host-app
|
|
1570
|
+
// button). setSelectionRange still positions the caret regardless.
|
|
1571
|
+
if (typeof document !== "undefined" && document.activeElement !== el) {
|
|
1572
|
+
el.focus();
|
|
1573
|
+
}
|
|
1574
|
+
try {
|
|
1575
|
+
el.setSelectionRange(clamped, clamped);
|
|
1576
|
+
}
|
|
1577
|
+
catch (_b) {
|
|
1578
|
+
// Some input types (e.g. "number") don't support setSelectionRange.
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
else if (!current) {
|
|
1582
|
+
this.lastSeenParamId = "";
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1247
1585
|
}
|
|
1248
1586
|
ngOnDestroy() {
|
|
1249
1587
|
this.destroy$.next();
|
|
@@ -1257,6 +1595,12 @@ class AIAutocompleteInputDirective {
|
|
|
1257
1595
|
* we don't disrupt multi-keystroke input methods (Pinyin, Hangul, etc.).
|
|
1258
1596
|
*/
|
|
1259
1597
|
onInput(event) {
|
|
1598
|
+
var _a;
|
|
1599
|
+
// Stamp the input time before anything else. The autocapitalize path below
|
|
1600
|
+
// calls setSelectionRange, which can synchronously fire a `select` event →
|
|
1601
|
+
// onCaretMove; stamping first ensures that event falls inside the guard
|
|
1602
|
+
// window and is treated as part of this keystroke, not a caret move.
|
|
1603
|
+
this.lastInputAt = performance.now();
|
|
1260
1604
|
const target = event.target;
|
|
1261
1605
|
const raw = target.value;
|
|
1262
1606
|
const isComposing = event.isComposing === true;
|
|
@@ -1271,17 +1615,43 @@ class AIAutocompleteInputDirective {
|
|
|
1271
1615
|
try {
|
|
1272
1616
|
target.setSelectionRange(start, end);
|
|
1273
1617
|
}
|
|
1274
|
-
catch (
|
|
1618
|
+
catch (_b) {
|
|
1275
1619
|
// See note in ngOnInit's text$ subscriber.
|
|
1276
1620
|
}
|
|
1277
1621
|
}
|
|
1278
1622
|
}
|
|
1279
1623
|
this.controller.handleTextChange(next);
|
|
1624
|
+
// Sync the real caret into the core. The dropdown's auto-trigger gate only
|
|
1625
|
+
// opens when the caret is at the end of the text; without this, the core's
|
|
1626
|
+
// `caretOffset` stays pinned at whatever a prior promote/select set it to
|
|
1627
|
+
// and goes stale as the user keeps typing, wedging the dropdown closed.
|
|
1628
|
+
// selectionStart is null on some input types (number/email/color); treat
|
|
1629
|
+
// that as "caret at end" rather than feeding null into the core.
|
|
1630
|
+
this.controller.handleCaretAfterInput((_a = target.selectionStart) !== null && _a !== void 0 ? _a : target.value.length);
|
|
1280
1631
|
this.onChangeFn(next);
|
|
1281
1632
|
}
|
|
1282
1633
|
onKeyDown(event) {
|
|
1283
1634
|
this.controller.handleKeyDown(event);
|
|
1284
1635
|
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Caret moved by something other than typing — arrow keys, Home/End, or a
|
|
1638
|
+
* mouse click/drag (keyup + mouseup). Forward the new offset so the core can
|
|
1639
|
+
* re-evaluate the caret-at-end dropdown gate (and exit re-edit if the caret
|
|
1640
|
+
* left the region). We deliberately do NOT listen for the `select` event:
|
|
1641
|
+
* programmatic `setSelectionRange` calls (the text$ writeback, the post-select
|
|
1642
|
+
* caret reposition) fire it in jsdom/some browsers, which would re-enter here
|
|
1643
|
+
* and clobber `caretOffset`. `keyup`/`mouseup` only fire for real user input.
|
|
1644
|
+
* Skipped right after an `input` event, where the caret is already handled by
|
|
1645
|
+
* `handleCaretAfterInput` above.
|
|
1646
|
+
*/
|
|
1647
|
+
onCaretMove() {
|
|
1648
|
+
var _a;
|
|
1649
|
+
if (performance.now() - this.lastInputAt < 50)
|
|
1650
|
+
return;
|
|
1651
|
+
const el = this.el.nativeElement;
|
|
1652
|
+
// selectionStart is null on some input types — treat as caret at end.
|
|
1653
|
+
this.controller.handleCaretMove((_a = el.selectionStart) !== null && _a !== void 0 ? _a : el.value.length);
|
|
1654
|
+
}
|
|
1285
1655
|
onFocus() {
|
|
1286
1656
|
this.controller.setFocused(true);
|
|
1287
1657
|
}
|
|
@@ -1293,11 +1663,17 @@ class AIAutocompleteInputDirective {
|
|
|
1293
1663
|
writeValue(value) {
|
|
1294
1664
|
const next = value !== null && value !== void 0 ? value : "";
|
|
1295
1665
|
this.renderer.setProperty(this.el.nativeElement, "value", next);
|
|
1296
|
-
//
|
|
1297
|
-
//
|
|
1298
|
-
//
|
|
1299
|
-
|
|
1666
|
+
// If Angular calls writeValue() before ngOnInit (possible during dynamic
|
|
1667
|
+
// form construction), queue the value and let ngOnInit flush it. We
|
|
1668
|
+
// can't push it into the controller now because either the controller
|
|
1669
|
+
// binding hasn't resolved yet, or text$'s initial subscription would
|
|
1670
|
+
// overwrite it moments later.
|
|
1671
|
+
if (this.controller) {
|
|
1300
1672
|
this.controller.setValue(next);
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
this.pendingWriteValue = next;
|
|
1676
|
+
}
|
|
1301
1677
|
}
|
|
1302
1678
|
registerOnChange(fn) {
|
|
1303
1679
|
this.onChangeFn = fn;
|
|
@@ -1310,7 +1686,7 @@ class AIAutocompleteInputDirective {
|
|
|
1310
1686
|
}
|
|
1311
1687
|
}
|
|
1312
1688
|
AIAutocompleteInputDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1313
|
-
AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
|
|
1689
|
+
AIAutocompleteInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteInputDirective, isStandalone: true, selector: "[aiaInput]", inputs: { controller: ["aiaInput", "controller"] }, host: { attributes: { "role": "combobox", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "keydown": "onKeyDown($event)", "keyup": "onCaretMove()", "mouseup": "onCaretMove()", "focus": "onFocus()", "blur": "onBlur()" } }, providers: [
|
|
1314
1690
|
{
|
|
1315
1691
|
provide: NG_VALUE_ACCESSOR,
|
|
1316
1692
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
@@ -1328,6 +1704,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1328
1704
|
"aria-autocomplete": "list",
|
|
1329
1705
|
"(input)": "onInput($event)",
|
|
1330
1706
|
"(keydown)": "onKeyDown($event)",
|
|
1707
|
+
"(keyup)": "onCaretMove()",
|
|
1708
|
+
"(mouseup)": "onCaretMove()",
|
|
1331
1709
|
"(focus)": "onFocus()",
|
|
1332
1710
|
"(blur)": "onBlur()",
|
|
1333
1711
|
},
|