@magicx-eng/ai-autocomplete-angular 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/esm2020/lib/ai-autocomplete/ai-autocomplete.component.mjs +72 -28
- package/esm2020/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.mjs +33 -12
- package/esm2020/lib/controller/ai-autocomplete.controller.mjs +88 -7
- package/esm2020/lib/input/ai-autocomplete-input.directive.mjs +85 -6
- package/esm2020/lib/internal/editor-host/editor-host.directive.mjs +4 -2
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs +280 -51
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs +276 -50
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts +10 -2
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts.map +1 -1
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts +27 -12
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts.map +1 -1
- package/lib/controller/ai-autocomplete.controller.d.ts.map +1 -1
- package/lib/input/ai-autocomplete-input.directive.d.ts +15 -0
- package/lib/input/ai-autocomplete-input.directive.d.ts.map +1 -1
- package/lib/internal/editor-host/editor-host.directive.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -4,8 +4,8 @@ import * as i0 from '@angular/core';
|
|
|
4
4
|
import { EventEmitter, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, Output, inject, ChangeDetectorRef, NgZone, ViewChild, ElementRef, Renderer2, Directive, PLATFORM_ID, forwardRef, NgModule } from '@angular/core';
|
|
5
5
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
6
|
import { AIAutocomplete, getCursorOffset, extractPlainText, renderEditableContent, plainTextLength, setCursorOffset, ModeController, buildQuery } from '@magicx-eng/ai-autocomplete-vanilla';
|
|
7
|
+
import { BehaviorSubject, combineLatest, map as map$1, distinctUntilChanged, shareReplay, Subject, takeUntil } from 'rxjs';
|
|
7
8
|
import { map } from 'rxjs/operators';
|
|
8
|
-
import { map as map$1, distinctUntilChanged, shareReplay, BehaviorSubject, Subject, takeUntil } from 'rxjs';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Internal pill list used by both the dropdown and Tier 1's inline pillbar.
|
|
@@ -256,18 +256,38 @@ class AIAutocompleteDropdownComponent {
|
|
|
256
256
|
this.showPills = true;
|
|
257
257
|
/** Fallback skeleton-bar widths used when loading with zero cached options. */
|
|
258
258
|
this.fallbackSkeletonBarWidths = [159, 119, 164];
|
|
259
|
+
// Bridge for the @Input `showPills` into the reactive pipeline. Without this,
|
|
260
|
+
// changing the input after init wouldn't trigger a new vm emission until the
|
|
261
|
+
// next controller.dropdown$ tick — and we'd lose the showPills update entirely
|
|
262
|
+
// if the controller is idle. Updated from ngOnChanges below.
|
|
263
|
+
this.showPills$ = new BehaviorSubject(true);
|
|
259
264
|
this.trackByWidth = (_, w) => w;
|
|
260
265
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
ngOnInit() {
|
|
267
|
+
// Seed showPills$ from the input's resolved value at init time.
|
|
268
|
+
this.showPills$.next(this.showPills);
|
|
269
|
+
this.initVm();
|
|
270
|
+
}
|
|
271
|
+
ngOnChanges(changes) {
|
|
272
|
+
if (changes["showPills"]) {
|
|
273
|
+
this.showPills$.next(this.showPills);
|
|
274
|
+
}
|
|
275
|
+
// Re-bind vm$ when the controller @Input is swapped (e.g. a multi-step form
|
|
276
|
+
// toggling between controllers). Without this, vm$ stays subscribed to the
|
|
277
|
+
// previous controller's dropdown$ — rendering stale options, or, if that
|
|
278
|
+
// controller was destroy()-ed, receiving a completion that collapses the
|
|
279
|
+
// dropdown permanently. Skip the first change: ngOnChanges fires before
|
|
280
|
+
// ngOnInit, which owns the initial creation.
|
|
281
|
+
if (changes["controller"] && !changes["controller"].firstChange && this.controller) {
|
|
282
|
+
this.initVm();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** Build the view-model stream from the current controller + showPills$. */
|
|
286
|
+
initVm() {
|
|
287
|
+
this.vm$ = combineLatest([this.controller.dropdown$, this.showPills$]).pipe(map(([dd, showPills]) => {
|
|
268
288
|
var _a, _b;
|
|
269
289
|
const options = (_b = (_a = dd.suggestions[0]) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : [];
|
|
270
|
-
const showsPills =
|
|
290
|
+
const showsPills = showPills && (dd.pills.length > 0 || dd.isLoading);
|
|
271
291
|
const showsOptions = options.length > 0;
|
|
272
292
|
const showsFallbackSkeleton = dd.isLoading && !showsOptions;
|
|
273
293
|
const isVisible = dd.isOpen && (showsOptions || showsPills || dd.isLoading);
|
|
@@ -298,10 +318,10 @@ class AIAutocompleteDropdownComponent {
|
|
|
298
318
|
}
|
|
299
319
|
}
|
|
300
320
|
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
|
|
321
|
+
AIAutocompleteDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: AIAutocompleteDropdownComponent, isStandalone: true, selector: "ai-autocomplete-dropdown", inputs: { controller: "controller", showPills: "showPills", className: "className" }, usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }, { kind: "component", type: SuggestionGridComponent, selector: "aia-suggestion-grid", inputs: ["options", "activeIndex", "listboxId", "loading"], outputs: ["select", "highlight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
302
322
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteDropdownComponent, decorators: [{
|
|
303
323
|
type: Component,
|
|
304
|
-
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [CommonModule, PillListComponent, SuggestionGridComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n
|
|
324
|
+
args: [{ selector: "ai-autocomplete-dropdown", standalone: true, imports: [CommonModule, PillListComponent, SuggestionGridComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n *ngIf=\"vm$ | async as vm\"\n [id]=\"controller.listboxId\"\n role=\"listbox\"\n data-aia-dropdown=\"\"\n [attr.data-aia-loading]=\"vm.isLoading ? '' : null\"\n [class]=\"className ? 'magicx-aia-dropdown ' + className : 'magicx-aia-dropdown'\"\n [class.magicx-aia-dropdown-visible]=\"vm.isVisible\"\n (mousedown)=\"preventBlur($event)\"\n>\n <div\n *ngIf=\"vm.showsPills\"\n class=\"magicx-aia-dropdown-pillbar\"\n data-aia-pillbar=\"\"\n >\n <aia-pill-list\n [pills]=\"vm.pills\"\n [activePillIndex]=\"0\"\n [rounded]=\"true\"\n [loading]=\"vm.isLoading\"\n (selectPill)=\"onPillClick($event)\"\n ></aia-pill-list>\n </div>\n\n <aia-suggestion-grid\n *ngIf=\"vm.showsOptions\"\n [options]=\"vm.options\"\n [activeIndex]=\"vm.activeIndex\"\n [listboxId]=\"controller.listboxId\"\n [loading]=\"vm.isLoading\"\n (select)=\"onSelect($event)\"\n (highlight)=\"onHighlight($event)\"\n ></aia-suggestion-grid>\n\n <div\n *ngIf=\"vm.showsFallbackSkeleton\"\n class=\"magicx-aia-dropdown-skeleton-bars\"\n data-aia-skeleton-bars=\"\"\n >\n <span\n *ngFor=\"let w of fallbackSkeletonBarWidths; trackBy: trackByWidth\"\n class=\"magicx-aia-dropdown-skeleton-bar\"\n [style.width.px]=\"w\"\n ></span>\n </div>\n</div>\n", styles: [".magicx-aia-dropdown{position:absolute;left:0;right:0;top:100%;max-width:516px;margin-top:6px;display:flex;flex-direction:column;overflow:hidden;container-type:inline-size;z-index:10;opacity:0;pointer-events:none;transition:opacity .4s cubic-bezier(.4,0,.2,1);box-shadow:#fff -3.2px -3.2px 3.2px -3.2px inset,#fff 6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) -6.4px 6.4px 1.6px -8px inset,var(--aia-dropdown-bg, transparent) 6.4px -6.4px 1.6px -8px inset,#ffffff26 -1.6px 0 0 -1.6px inset,#ffffff26 0 -1.6px 0 -1.6px inset,#ffffff4d 0 1.6px inset,#ffffff4d 1.6px 0 inset,inset 0 0 30px 5px #0000000d,#00000014 0 0 30px 2px;backdrop-filter:blur(30px);border-radius:28px}.magicx-aia-dropdown-visible{opacity:1;pointer-events:auto}.magicx-aia-dropdown-pillbar{padding:27px 27px 3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%);-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 48px),transparent 100%)}.magicx-aia-dropdown-pillbar::-webkit-scrollbar{display:none}.magicx-aia-dropdown-skeleton-bars{display:flex;flex-direction:column;gap:20px;padding:25px}.magicx-aia-dropdown-skeleton-bar{display:block;height:19px;border-radius:999px;background:var(--aia-skeleton-bg, var(--aia-pill-bg, rgba(189, 189, 189, .51)));opacity:.5;animation:aiaDropdownSkeletonPulse 1.4s ease-in-out infinite}.magicx-aia-dropdown-skeleton-bar:nth-child(2){animation-delay:.15s}.magicx-aia-dropdown-skeleton-bar:nth-child(3){animation-delay:.3s}@keyframes aiaDropdownSkeletonPulse{0%,to{opacity:.5}50%{opacity:.25}}\n"] }]
|
|
305
325
|
}], propDecorators: { controller: [{
|
|
306
326
|
type: Input
|
|
307
327
|
}], showPills: [{
|
|
@@ -367,17 +387,37 @@ class AIAutocompleteController {
|
|
|
367
387
|
// arrow opens the dropdown from the input.
|
|
368
388
|
optionsPosition: (_b = opts.optionsPosition) !== null && _b !== void 0 ? _b : "below" }));
|
|
369
389
|
this.rawState$ = new BehaviorSubject(this.core.getState());
|
|
370
|
-
|
|
390
|
+
// Push the store's *current* state, not the snapshot the listener was
|
|
391
|
+
// handed. The core notifies listeners synchronously, and some actions
|
|
392
|
+
// trigger reentrant `store.set` calls (e.g. typing a non-matching char
|
|
393
|
+
// during re-edit makes the core synchronously exit re-edit and kick off a
|
|
394
|
+
// fetch). Reentrancy makes the outer set's listener loop continue with a
|
|
395
|
+
// now-stale snapshot, so the listener can fire *out of order* — delivering
|
|
396
|
+
// an older state (still in re-edit) after a newer one (re-edit exited).
|
|
397
|
+
// Reading `getState()` at emission time collapses every notification onto
|
|
398
|
+
// the true latest state, so a stale `editingParam` can't resurrect and
|
|
399
|
+
// retrigger the editor's re-edit caret-park (which would yank the caret
|
|
400
|
+
// back to where re-editing started). distinctUntilChanged downstream drops
|
|
401
|
+
// the resulting duplicate emissions. React avoids this because it batches
|
|
402
|
+
// renders and its caret effects are dependency-gated; our subscription is
|
|
403
|
+
// synchronous, so we coalesce here instead.
|
|
404
|
+
this.unsubscribeCore = this.core.subscribe(() => {
|
|
371
405
|
if (!this.isDestroyed)
|
|
372
|
-
this.rawState$.next(
|
|
406
|
+
this.rawState$.next(this.core.getState());
|
|
373
407
|
});
|
|
374
408
|
// === Per-field observables ===
|
|
409
|
+
// Array-valued fields use shallow equality. The vanilla store spreads on
|
|
410
|
+
// every write (`store.set({ completedParams: [...] })`), so reference
|
|
411
|
+
// equality would re-fire downstream subscribers on every state tick even
|
|
412
|
+
// when the array's contents are unchanged. The other fields are
|
|
413
|
+
// primitives or stable object references, so default identity equality
|
|
414
|
+
// works fine.
|
|
375
415
|
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);
|
|
416
|
+
this.completedParams$ = deriveField(this.rawState$, (s) => s.completedParams, arrEq);
|
|
417
|
+
this.suggestionPills$ = deriveField(this.rawState$, (s) => s.actionableSuggestions, arrEq);
|
|
418
|
+
this.segments$ = deriveField(this.rawState$, (s) => s.segments, arrEq);
|
|
379
419
|
this.newParamId$ = deriveField(this.rawState$, (s) => s.newParamId);
|
|
380
|
-
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions);
|
|
420
|
+
this.suggestions$ = deriveField(this.rawState$, (s) => s.suggestions, arrEq);
|
|
381
421
|
this.activeIndex$ = deriveField(this.rawState$, (s) => s.activeDropdownIndex);
|
|
382
422
|
this.isLoading$ = deriveField(this.rawState$, computeUiLoading);
|
|
383
423
|
this.isReady$ = deriveField(this.rawState$, (s) => s.isReady);
|
|
@@ -418,68 +458,109 @@ class AIAutocompleteController {
|
|
|
418
458
|
return computeState(this.rawState$.value);
|
|
419
459
|
}
|
|
420
460
|
// === Action methods (pass-through to the core) ===
|
|
461
|
+
//
|
|
462
|
+
// Every action method begins with an `isDestroyed` guard so that callers
|
|
463
|
+
// who hold a stale reference after `destroy()` don't accidentally trigger
|
|
464
|
+
// work on a torn-down core. The vanilla core does not gate its own methods
|
|
465
|
+
// post-dispose — `reset()`, `handleTextChange()`, and friends would still
|
|
466
|
+
// schedule fetches or fire HTTP requests against an aborted controller,
|
|
467
|
+
// and the responses would have nowhere to surface (rawState$ is already
|
|
468
|
+
// complete). The guard makes the JSDoc-promised "no-op silently"
|
|
469
|
+
// semantics actually true.
|
|
421
470
|
/** Clear all state, rotate `session_id`, and re-fetch. Call after submit. */
|
|
422
471
|
reset() {
|
|
472
|
+
if (this.isDestroyed)
|
|
473
|
+
return;
|
|
423
474
|
this.core.reset();
|
|
424
475
|
}
|
|
425
476
|
/** Pick a dropdown option (or replace the re-edited param if in re-edit mode). */
|
|
426
477
|
selectOption(option) {
|
|
478
|
+
if (this.isDestroyed)
|
|
479
|
+
return;
|
|
427
480
|
this.core.selectOption(option);
|
|
428
481
|
}
|
|
429
482
|
/** Promote the pill at `index` to active (front of the list). */
|
|
430
483
|
setActivePill(index) {
|
|
484
|
+
if (this.isDestroyed)
|
|
485
|
+
return;
|
|
431
486
|
this.core.setActivePill(index);
|
|
432
487
|
}
|
|
433
488
|
/** Drop the last completed param's "completed" status. Text stays as plain text. */
|
|
434
489
|
removeLastParam() {
|
|
490
|
+
if (this.isDestroyed)
|
|
491
|
+
return;
|
|
435
492
|
this.core.removeLastParam();
|
|
436
493
|
}
|
|
437
494
|
/** Clear the shimmer animation state for the most-recently-added param. */
|
|
438
495
|
clearNewParamId() {
|
|
496
|
+
if (this.isDestroyed)
|
|
497
|
+
return;
|
|
439
498
|
this.core.clearNewParamId();
|
|
440
499
|
}
|
|
441
500
|
/** Controlled-mode setter for editor text. */
|
|
442
501
|
setValue(text) {
|
|
502
|
+
if (this.isDestroyed)
|
|
503
|
+
return;
|
|
443
504
|
this.core.setValue(text);
|
|
444
505
|
}
|
|
445
506
|
/** Controlled-mode setter for completed params. */
|
|
446
507
|
setCompletedParams(params) {
|
|
508
|
+
if (this.isDestroyed)
|
|
509
|
+
return;
|
|
447
510
|
this.core.setCompletedParams(params);
|
|
448
511
|
}
|
|
449
512
|
/** Forward plain-text input from the consumer's `<textarea>` to the core. */
|
|
450
513
|
handleTextChange(value) {
|
|
514
|
+
if (this.isDestroyed)
|
|
515
|
+
return;
|
|
451
516
|
this.core.handleTextChange(value);
|
|
452
517
|
}
|
|
453
518
|
/** Forward keyboard events from the consumer's `<textarea>` to the core. */
|
|
454
519
|
handleKeyDown(event) {
|
|
520
|
+
if (this.isDestroyed)
|
|
521
|
+
return;
|
|
455
522
|
this.core.handleKeyDown(event);
|
|
456
523
|
}
|
|
457
524
|
/** Notify the core of focus state. */
|
|
458
525
|
setFocused(focused) {
|
|
526
|
+
if (this.isDestroyed)
|
|
527
|
+
return;
|
|
459
528
|
this.core.setFocused(focused);
|
|
460
529
|
}
|
|
461
530
|
/** Enter re-edit mode for the bold param with the given id. */
|
|
462
531
|
startEditingParam(paramId) {
|
|
532
|
+
if (this.isDestroyed)
|
|
533
|
+
return;
|
|
463
534
|
this.core.startEditingParam(paramId);
|
|
464
535
|
}
|
|
465
536
|
/** Exit re-edit mode without changing the bold param. */
|
|
466
537
|
exitEditMode() {
|
|
538
|
+
if (this.isDestroyed)
|
|
539
|
+
return;
|
|
467
540
|
this.core.exitEditMode();
|
|
468
541
|
}
|
|
469
542
|
/** Forward post-input caret position (extends the edit tail). */
|
|
470
543
|
handleCaretAfterInput(offset) {
|
|
544
|
+
if (this.isDestroyed)
|
|
545
|
+
return;
|
|
471
546
|
this.core.handleCaretAfterInput(offset);
|
|
472
547
|
}
|
|
473
548
|
/** Forward selection-only caret moves (may exit re-edit). */
|
|
474
549
|
handleCaretMove(offset) {
|
|
550
|
+
if (this.isDestroyed)
|
|
551
|
+
return;
|
|
475
552
|
this.core.handleCaretMove(offset);
|
|
476
553
|
}
|
|
477
554
|
/** Replace the bold param being re-edited. Returns true when applied. */
|
|
478
555
|
replaceEditingRange(replacement) {
|
|
556
|
+
if (this.isDestroyed)
|
|
557
|
+
return false;
|
|
479
558
|
return this.core.replaceEditingRange(replacement);
|
|
480
559
|
}
|
|
481
560
|
/** Set the highlighted option index (typically driven by hover). */
|
|
482
561
|
setActiveDropdownIndex(index) {
|
|
562
|
+
if (this.isDestroyed)
|
|
563
|
+
return;
|
|
483
564
|
this.core.setActiveDropdownIndex(index);
|
|
484
565
|
}
|
|
485
566
|
// === Lifecycle ===
|
|
@@ -491,6 +572,8 @@ class AIAutocompleteController {
|
|
|
491
572
|
* (e.g. `text$` instead of `onChange`).
|
|
492
573
|
*/
|
|
493
574
|
update(opts) {
|
|
575
|
+
if (this.isDestroyed)
|
|
576
|
+
return;
|
|
494
577
|
this.core.update(opts);
|
|
495
578
|
}
|
|
496
579
|
/**
|
|
@@ -508,6 +591,24 @@ class AIAutocompleteController {
|
|
|
508
591
|
}
|
|
509
592
|
}
|
|
510
593
|
// ===== Pure helpers — not part of the public surface =====
|
|
594
|
+
/**
|
|
595
|
+
* Shallow array equality. The vanilla store reassigns array fields on every
|
|
596
|
+
* write (`store.set({ completedParams: [...prev, x] })`), so reference
|
|
597
|
+
* equality alone would re-emit downstream even when the contents are
|
|
598
|
+
* identical. Used for `completedParams$`, `suggestions$`, `suggestionPills$`,
|
|
599
|
+
* and `segments$`.
|
|
600
|
+
*/
|
|
601
|
+
function arrEq(a, b) {
|
|
602
|
+
if (a === b)
|
|
603
|
+
return true;
|
|
604
|
+
if (a.length !== b.length)
|
|
605
|
+
return false;
|
|
606
|
+
for (let i = 0; i < a.length; i++) {
|
|
607
|
+
if (a[i] !== b[i])
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
511
612
|
function computeUiLoading(s) {
|
|
512
613
|
return s.isLoading && !s.editingParam && !s.inSelectionAnimation;
|
|
513
614
|
}
|
|
@@ -704,7 +805,9 @@ class AIAutocompleteEditorHostDirective {
|
|
|
704
805
|
const anchor = sel.anchorNode;
|
|
705
806
|
const startEl = anchor.nodeType === Node.ELEMENT_NODE ? anchor : anchor.parentElement;
|
|
706
807
|
const strong = startEl === null || startEl === void 0 ? void 0 : startEl.closest('strong[data-seg="completed"][data-param-id]');
|
|
707
|
-
|
|
808
|
+
// `DOMStringMap` is an index signature; bracket notation is required
|
|
809
|
+
// by `noPropertyAccessFromIndexSignature`.
|
|
810
|
+
const enclosing = (_a = strong === null || strong === void 0 ? void 0 : strong.dataset["paramId"]) !== null && _a !== void 0 ? _a : null;
|
|
708
811
|
if (enclosing && enclosing !== this.editingParamId) {
|
|
709
812
|
this.zone.run(() => this.controller.startEditingParam(enclosing));
|
|
710
813
|
return;
|
|
@@ -806,13 +909,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
806
909
|
type: Input
|
|
807
910
|
}] } });
|
|
808
911
|
|
|
809
|
-
function resolveInitialMode(mode) {
|
|
810
|
-
if (mode !== "auto")
|
|
811
|
-
return mode;
|
|
812
|
-
if (typeof window === "undefined")
|
|
813
|
-
return "dark";
|
|
814
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
815
|
-
}
|
|
816
912
|
/**
|
|
817
913
|
* Tier 1 component — `<ai-autocomplete>`. The Angular equivalent of React's
|
|
818
914
|
* `<AIAutocomplete />`. Owns the contentEditable editor, inline pills, mode
|
|
@@ -840,19 +936,26 @@ class AIAutocompleteComponent {
|
|
|
840
936
|
this.completedParamsChange = new EventEmitter();
|
|
841
937
|
this.focused = new EventEmitter();
|
|
842
938
|
this.blurred = new EventEmitter();
|
|
939
|
+
/**
|
|
940
|
+
* Concrete light/dark value bound to `[attr.data-mode]`. Seeded in ngOnInit
|
|
941
|
+
* so the attribute is present during SSR (where ModeController, gated behind
|
|
942
|
+
* isPlatformBrowser in ngAfterViewInit, never runs). On the browser the
|
|
943
|
+
* ModeController's resolve callback keeps this in sync — so its runtime
|
|
944
|
+
* prefers-color-scheme updates and this binding always agree (no clobber).
|
|
945
|
+
*/
|
|
843
946
|
this.resolvedMode = "dark";
|
|
844
947
|
this.onChangeFn = noop$1;
|
|
845
948
|
this.onTouchedFn = noop$1;
|
|
846
949
|
// §16.9: prefer `inject()` over constructor-parameter DI for components.
|
|
847
950
|
this.platformId = inject(PLATFORM_ID);
|
|
848
|
-
this.el = inject(ElementRef);
|
|
849
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
850
951
|
this.zone = inject(NgZone);
|
|
952
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
851
953
|
}
|
|
852
954
|
ngOnInit() {
|
|
955
|
+
// Seed before the browser guard so SSR renders a concrete data-mode.
|
|
956
|
+
this.resolvedMode = resolveInitialMode(this.mode);
|
|
853
957
|
if (!isPlatformBrowser(this.platformId))
|
|
854
958
|
return;
|
|
855
|
-
this.resolvedMode = resolveInitialMode(this.mode);
|
|
856
959
|
this.controller = new AIAutocompleteController({
|
|
857
960
|
apiConfig: this.apiConfig,
|
|
858
961
|
optionOverrides: this.optionOverrides,
|
|
@@ -886,12 +989,26 @@ class AIAutocompleteComponent {
|
|
|
886
989
|
setCursorOffset(editor, offset);
|
|
887
990
|
},
|
|
888
991
|
});
|
|
992
|
+
if (this.pendingWriteValue !== undefined) {
|
|
993
|
+
this.controller.setValue(this.pendingWriteValue);
|
|
994
|
+
this.pendingWriteValue = undefined;
|
|
995
|
+
}
|
|
889
996
|
}
|
|
890
997
|
ngAfterViewInit() {
|
|
891
998
|
if (!isPlatformBrowser(this.platformId))
|
|
892
999
|
return;
|
|
893
|
-
|
|
894
|
-
|
|
1000
|
+
// ModeController writes `data-mode` to the element it's given. The CSS
|
|
1001
|
+
// selectors are scoped to `.magicx-aia[data-mode="..."]` — which lives on
|
|
1002
|
+
// the inner `<div class="magicx-aia magicx-aia-container" #container>`,
|
|
1003
|
+
// NOT the host `<ai-autocomplete>` element. The resolve callback mirrors
|
|
1004
|
+
// the value into `resolvedMode` so the `[attr.data-mode]` binding stays in
|
|
1005
|
+
// lockstep with ModeController — the binding handles SSR, ModeController
|
|
1006
|
+
// handles runtime prefers-color-scheme switches, and because they always
|
|
1007
|
+
// agree neither clobbers the other.
|
|
1008
|
+
this.modeController = new ModeController(this.containerRef.nativeElement, this.mode, (resolved) => {
|
|
1009
|
+
this.resolvedMode = resolved;
|
|
1010
|
+
this.cdr.markForCheck();
|
|
1011
|
+
});
|
|
895
1012
|
// Pill-wrap detection — when the pill list wraps below the editor's last
|
|
896
1013
|
// line, drop its 8px left margin via a data attribute the CSS keys off.
|
|
897
1014
|
// Mirrors the React component's useLayoutEffect.
|
|
@@ -910,20 +1027,24 @@ class AIAutocompleteComponent {
|
|
|
910
1027
|
var _a;
|
|
911
1028
|
if (!this.controller)
|
|
912
1029
|
return;
|
|
1030
|
+
// `SimpleChanges` is `{ [key: string]: SimpleChange }`, so per
|
|
1031
|
+
// `noPropertyAccessFromIndexSignature` each key lookup uses bracket
|
|
1032
|
+
// notation — this keeps the strictness contract that the rest of the
|
|
1033
|
+
// workspace enforces.
|
|
913
1034
|
// Controlled-mode propagation
|
|
914
|
-
if (changes
|
|
1035
|
+
if (changes["value"] && this.value !== undefined) {
|
|
915
1036
|
this.controller.setValue(this.value);
|
|
916
1037
|
}
|
|
917
|
-
if (changes
|
|
1038
|
+
if (changes["completedParams"] && this.completedParams !== undefined) {
|
|
918
1039
|
this.controller.setCompletedParams(this.completedParams);
|
|
919
1040
|
}
|
|
920
1041
|
// Reactive options
|
|
921
|
-
if (changes
|
|
922
|
-
changes
|
|
923
|
-
changes
|
|
924
|
-
changes
|
|
925
|
-
changes
|
|
926
|
-
changes
|
|
1042
|
+
if (changes["apiConfig"] ||
|
|
1043
|
+
changes["optionOverrides"] ||
|
|
1044
|
+
changes["dropdownTrigger"] ||
|
|
1045
|
+
changes["optionsPosition"] ||
|
|
1046
|
+
changes["closeDropdownOnBlur"] ||
|
|
1047
|
+
changes["showNonTappableOptions"]) {
|
|
927
1048
|
this.controller.update({
|
|
928
1049
|
apiConfig: this.apiConfig,
|
|
929
1050
|
optionOverrides: this.optionOverrides,
|
|
@@ -933,9 +1054,12 @@ class AIAutocompleteComponent {
|
|
|
933
1054
|
showNonTappableOptions: this.showNonTappableOptions,
|
|
934
1055
|
});
|
|
935
1056
|
}
|
|
936
|
-
if (changes
|
|
937
|
-
|
|
1057
|
+
if (changes["mode"]) {
|
|
1058
|
+
// Keep the binding correct even when ModeController doesn't exist yet
|
|
1059
|
+
// (SSR / pre-ngAfterViewInit). On the browser, setMode triggers the
|
|
1060
|
+
// resolve callback which sets the same value.
|
|
938
1061
|
this.resolvedMode = resolveInitialMode(this.mode);
|
|
1062
|
+
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(this.mode);
|
|
939
1063
|
}
|
|
940
1064
|
}
|
|
941
1065
|
ngOnDestroy() {
|
|
@@ -964,8 +1088,8 @@ class AIAutocompleteComponent {
|
|
|
964
1088
|
setMode(mode) {
|
|
965
1089
|
var _a;
|
|
966
1090
|
this.mode = mode;
|
|
967
|
-
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
|
|
968
1091
|
this.resolvedMode = resolveInitialMode(mode);
|
|
1092
|
+
(_a = this.modeController) === null || _a === void 0 ? void 0 : _a.setMode(mode);
|
|
969
1093
|
this.cdr.markForCheck();
|
|
970
1094
|
}
|
|
971
1095
|
// === Template helpers ===
|
|
@@ -1019,9 +1143,16 @@ class AIAutocompleteComponent {
|
|
|
1019
1143
|
}
|
|
1020
1144
|
// === ControlValueAccessor ===
|
|
1021
1145
|
writeValue(value) {
|
|
1022
|
-
|
|
1146
|
+
const next = value !== null && value !== void 0 ? value : "";
|
|
1147
|
+
if (!this.controller) {
|
|
1148
|
+
// Reactive Forms can call writeValue() before ngOnInit creates the
|
|
1149
|
+
// controller (dynamic form construction, or a FormControl that already
|
|
1150
|
+
// had a value at directive-creation time). Buffer the value; ngOnInit
|
|
1151
|
+
// flushes it as soon as the controller exists.
|
|
1152
|
+
this.pendingWriteValue = next;
|
|
1023
1153
|
return;
|
|
1024
|
-
|
|
1154
|
+
}
|
|
1155
|
+
this.controller.setValue(next);
|
|
1025
1156
|
}
|
|
1026
1157
|
registerOnChange(fn) {
|
|
1027
1158
|
this.onChangeFn = fn;
|
|
@@ -1103,7 +1234,7 @@ AIAutocompleteComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0"
|
|
|
1103
1234
|
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1104
1235
|
multi: true,
|
|
1105
1236
|
},
|
|
1106
|
-
], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: AIAutocompleteDropdownComponent, selector: "ai-autocomplete-dropdown", inputs: ["controller", "showPills", "className"] }, { kind: "directive", type: AIAutocompleteEditorHostDirective, selector: "[aiaEditorHost]", inputs: ["aiaEditorHost", "autoFocus"] }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1237
|
+
], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }, { propertyName: "pillContainerRef", first: true, predicate: ["pillContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: AIAutocompleteDropdownComponent, selector: "ai-autocomplete-dropdown", inputs: ["controller", "showPills", "className"] }, { kind: "directive", type: AIAutocompleteEditorHostDirective, selector: "[aiaEditorHost]", inputs: ["aiaEditorHost", "autoFocus"] }, { kind: "component", type: PillListComponent, selector: "aia-pill-list", inputs: ["pills", "activePillIndex", "rounded", "loading"], outputs: ["selectPill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1107
1238
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AIAutocompleteComponent, decorators: [{
|
|
1108
1239
|
type: Component,
|
|
1109
1240
|
args: [{ selector: "ai-autocomplete", standalone: true, imports: [
|
|
@@ -1118,7 +1249,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1118
1249
|
useExisting: forwardRef(() => AIAutocompleteComponent),
|
|
1119
1250
|
multi: true,
|
|
1120
1251
|
},
|
|
1121
|
-
], template: "<div\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"] }]
|
|
1252
|
+
], template: "<div\n #container\n class=\"magicx-aia magicx-aia-container\"\n [attr.data-pill-placement]=\"pillPlacement\"\n [attr.data-options-position]=\"optionsPosition\"\n [attr.data-animations]=\"animations ? 'on' : 'off'\"\n [attr.data-mode]=\"resolvedMode\"\n>\n <ai-autocomplete-dropdown\n *ngIf=\"controller\"\n [controller]=\"controller\"\n [showPills]=\"showDropdownPills\"\n ></ai-autocomplete-dropdown>\n\n <div\n class=\"magicx-aia-input-wrapper\"\n (click)=\"onWrapperClick($event)\"\n (keyup)=\"$event.stopPropagation()\"\n >\n <div class=\"magicx-aia-editor-area\" data-aia-editor=\"\">\n <div\n #editor\n *ngIf=\"controller\"\n [aiaEditorHost]=\"controller\"\n [autoFocus]=\"autoFocus\"\n class=\"magicx-aia-input\"\n data-aia-input=\"\"\n ></div>\n\n <ng-container *ngIf=\"controller && showInlinePills\">\n <span\n #pillContainer\n class=\"magicx-aia-pill-list-container\"\n data-aia-pill-list-container=\"\"\n *ngIf=\"\n (controller.isLoading$ | async) ||\n ((controller.suggestionPills$ | async)?.length ?? 0) > 0\n \"\n >\n <aia-pill-list\n [pills]=\"(controller.suggestionPills$ | async) ?? []\"\n [activePillIndex]=\"0\"\n [loading]=\"(controller.isLoading$ | async) ?? false\"\n (selectPill)=\"onSelectPill($event)\"\n ></aia-pill-list>\n </span>\n </ng-container>\n </div>\n\n <ng-container [ngSwitch]=\"submitButtonState\">\n <button\n *ngSwitchCase=\"'default'\"\n type=\"button\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-button\"\n [disabled]=\"\n !(\n controller &&\n canSubmit({\n segments: (controller.segments$ | async) ?? [],\n completedParams: (controller.completedParams$ | async) ?? []\n })\n )\n \"\n aria-label=\"Submit\"\n (click)=\"onSubmitButtonClick($event)\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span\n *ngSwitchCase=\"'custom'\"\n data-aia-submit=\"\"\n class=\"magicx-aia-submit-slot\"\n (click)=\"onSubmitSlotClick($event)\"\n >\n <ng-container *ngTemplateOutlet=\"customSubmitTemplate\"></ng-container>\n </span>\n </ng-container>\n </div>\n</div>\n", styles: [":where(.magicx-aia,.magicx-aia *,.magicx-aia *::before,.magicx-aia *::after){box-sizing:border-box}:where(.magicx-aia),:where(.magicx-aia[data-mode=\"light\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #000000;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #4f4f4f;--aia-option-color-selected: #000000;--aia-option-font-size: 19px;--aia-written-text-color: #000000;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #000000);--aia-submit-bg: #000000;--aia-submit-color: #ffffff;--aia-color-text-muted: #6b7280;--aia-skeleton-bg: rgba(189, 189, 189, .51);--aia-streak-rgb: 99, 102, 241;--aia-streak-glass-bg: rgba(99, 102, 241, .1)}:where(.magicx-aia[data-mode=\"dark\"]){--aia-pill-bg: #bdbdbd;--aia-pill-color: #ffffff;--aia-pill-font-size: 19px;--aia-option-bg: transparent;--aia-option-color: #b0b0b0;--aia-option-color-selected: #ffffff;--aia-option-font-size: 19px;--aia-written-text-color: #ffffff;--aia-written-text-font-size: 19px;--aia-caret-color: var(--aia-written-text-color, #ffffff);--aia-submit-bg: #ffffff;--aia-submit-color: #000000;--aia-color-text-muted: #c1c4cb;--aia-skeleton-bg: #333539;--aia-streak-rgb: 255, 255, 255;--aia-streak-glass-bg: rgba(255, 255, 255, .1)}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-dropdown]{top:auto;bottom:100%;margin-top:0;margin-bottom:6px;flex-direction:column-reverse}:where(.magicx-aia[data-options-position=\"above\"]) [data-aia-pillbar]{padding:0 27px 27px}:where(.magicx-aia[data-animations=\"off\"]) *,:where(.magicx-aia[data-animations=\"off\"]) *:before,:where(.magicx-aia[data-animations=\"off\"]) *:after{animation-duration:0s!important;transition-duration:0s!important}.magicx-aia-container{position:relative;font-family:var(--aia-font-family, inherit);container-type:inline-size}.magicx-aia-input-wrapper{padding:20px;border:1px solid var(--aia-color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:4px}.magicx-aia-editor-area{position:relative;flex:1;min-width:0;min-height:26.6px;line-height:26.6px;font-family:inherit;font-size:var(--aia-written-text-font-size, 19px);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.magicx-aia-input{display:inline;outline:none;background:transparent;color:var(--aia-written-text-color, var(--aia-color-text-default, #fff));caret-color:var( --aia-caret-color, var(--aia-written-text-color, var(--aia-color-text-default, #fff)) );font-weight:250;vertical-align:middle}:where(.magicx-aia-input) strong{font-weight:500;letter-spacing:-.01em}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]{background-color:color-mix(in srgb,var(--aia-edit-outline, currentColor) 20%,transparent);border-radius:6px;padding:2px 3px;margin:0 -1.5px;caret-color:transparent}.magicx-aia-input:has(strong[class~=\"magicx-aia-segment--editing\"]){caret-color:transparent}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::selection{background:transparent;color:inherit}:where(.magicx-aia-input) strong[class~=magicx-aia-segment--editing]::-moz-selection{background:transparent;color:inherit}.magicx-aia-input[data-aia-empty=true][data-placeholder]:before{content:attr(data-placeholder);color:var(--aia-color-text-muted, #c1c4cb);opacity:.7;pointer-events:none}.magicx-aia-input[data-aia-empty=true]{display:inline-block;min-width:1px}.magicx-aia-pill-list-container{display:inline;margin-left:8px}.magicx-aia-pill-list-container:empty,.magicx-aia-pill-list-container[data-aia-pill-wrapped]{margin-left:0}.magicx-aia-submit-button{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--aia-submit-bg, var(--aia-color-text-default, #fff));color:var(--aia-submit-color, var(--aia-color-bg-default, #000));cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.magicx-aia-submit-button:hover{opacity:.85}.magicx-aia-submit-slot{display:contents}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-revealed]{color:transparent;background:linear-gradient(120deg,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 0%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 44%,#b0b0b0 48%,#b0b0b0 52%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 56%,var(--aia-written-text-color, var(--aia-color-text-default, #fff)) 100%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text}:where(.magicx-aia-input) strong[class~=magicx-aia-shimmer-sweep]{animation:aiaTextShimmer .65s ease-out forwards}@keyframes aiaTextShimmer{0%{background-position:100% 0}to{background-position:-50% 0}}\n"] }]
|
|
1122
1253
|
}], propDecorators: { apiConfig: [{
|
|
1123
1254
|
type: Input
|
|
1124
1255
|
}], optionOverrides: [{
|
|
@@ -1161,6 +1292,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1161
1292
|
type: Output
|
|
1162
1293
|
}], blurred: [{
|
|
1163
1294
|
type: Output
|
|
1295
|
+
}], containerRef: [{
|
|
1296
|
+
type: ViewChild,
|
|
1297
|
+
args: ["container", { static: true }]
|
|
1164
1298
|
}], editorRef: [{
|
|
1165
1299
|
type: ViewChild,
|
|
1166
1300
|
args: ["editor", { static: false }]
|
|
@@ -1171,6 +1305,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1171
1305
|
function noop$1() {
|
|
1172
1306
|
// Default no-op for CVA callbacks.
|
|
1173
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Resolve a concrete light/dark value from the configured mode. Used to seed
|
|
1310
|
+
* the `data-mode` attribute synchronously (including during SSR, where
|
|
1311
|
+
* ModeController never runs). On the server `window` is undefined → defaults
|
|
1312
|
+
* to "dark" (matches the vanilla/React behavior).
|
|
1313
|
+
*/
|
|
1314
|
+
function resolveInitialMode(mode) {
|
|
1315
|
+
if (mode !== "auto")
|
|
1316
|
+
return mode;
|
|
1317
|
+
if (typeof window === "undefined")
|
|
1318
|
+
return "dark";
|
|
1319
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
1320
|
+
}
|
|
1174
1321
|
|
|
1175
1322
|
/**
|
|
1176
1323
|
* Tier 2 input binder. Attach to a `<textarea>` or `<input>` and pass an
|
|
@@ -1204,11 +1351,23 @@ class AIAutocompleteInputDirective {
|
|
|
1204
1351
|
this.destroy$ = new Subject();
|
|
1205
1352
|
this.onChangeFn = noop;
|
|
1206
1353
|
this.onTouchedFn = noop;
|
|
1354
|
+
// Timestamp of the last `input` event. A `keyup`/`select` that fires within
|
|
1355
|
+
// this window is the tail of a keystroke, not a deliberate caret move, so we
|
|
1356
|
+
// skip the caret-move handler there (mirrors the editor host's selectionchange
|
|
1357
|
+
// guard). performance.now() is monotonic; 0 means "no input yet".
|
|
1358
|
+
this.lastInputAt = 0;
|
|
1359
|
+
// Tracks the most-recently-promoted param so we only reposition the caret
|
|
1360
|
+
// once per option selection (mirrors the editor host's post-promote refocus).
|
|
1361
|
+
this.lastSeenParamId = "";
|
|
1207
1362
|
}
|
|
1208
1363
|
ngOnInit() {
|
|
1209
1364
|
if (!this.controller) {
|
|
1210
1365
|
throw new Error('[aiaInput] requires a controller — use `[aiaInput]="myController"`.');
|
|
1211
1366
|
}
|
|
1367
|
+
if (this.pendingWriteValue !== undefined) {
|
|
1368
|
+
this.controller.setValue(this.pendingWriteValue);
|
|
1369
|
+
this.pendingWriteValue = undefined;
|
|
1370
|
+
}
|
|
1212
1371
|
const el = this.el.nativeElement;
|
|
1213
1372
|
this.renderer.setAttribute(el, "aria-controls", this.controller.listboxId);
|
|
1214
1373
|
// Sync editor text → DOM. Skips the write when values already match, so
|
|
@@ -1244,6 +1403,36 @@ class AIAutocompleteInputDirective {
|
|
|
1244
1403
|
this.renderer.removeAttribute(el, "aria-activedescendant");
|
|
1245
1404
|
}
|
|
1246
1405
|
});
|
|
1406
|
+
// After an option selection promotes a param, the core appends its text and
|
|
1407
|
+
// stamps `caretOffset` at the end of the inserted text. The text$ subscriber
|
|
1408
|
+
// above writes the new value but *restores the pre-write caret* (correct for
|
|
1409
|
+
// typing echoes, wrong here), so we move the caret to the promoted position.
|
|
1410
|
+
// Keyed on newParamId so it fires once per selection, not on every tick.
|
|
1411
|
+
// Registered after text$ so the new value is in the DOM before we seek.
|
|
1412
|
+
this.controller.newParamId$.pipe(takeUntil(this.destroy$)).subscribe((id) => {
|
|
1413
|
+
var _a;
|
|
1414
|
+
const current = id !== null && id !== void 0 ? id : "";
|
|
1415
|
+
if (current && current !== this.lastSeenParamId) {
|
|
1416
|
+
this.lastSeenParamId = current;
|
|
1417
|
+
const offset = (_a = this.controller.getState().caretOffset) !== null && _a !== void 0 ? _a : el.value.length;
|
|
1418
|
+
const clamped = Math.min(Math.max(offset, 0), el.value.length);
|
|
1419
|
+
// Only grab focus if we don't already have it — don't yank focus back
|
|
1420
|
+
// from wherever the user moved it after selecting (e.g. a host-app
|
|
1421
|
+
// button). setSelectionRange still positions the caret regardless.
|
|
1422
|
+
if (typeof document !== "undefined" && document.activeElement !== el) {
|
|
1423
|
+
el.focus();
|
|
1424
|
+
}
|
|
1425
|
+
try {
|
|
1426
|
+
el.setSelectionRange(clamped, clamped);
|
|
1427
|
+
}
|
|
1428
|
+
catch (_b) {
|
|
1429
|
+
// Some input types (e.g. "number") don't support setSelectionRange.
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
else if (!current) {
|
|
1433
|
+
this.lastSeenParamId = "";
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1247
1436
|
}
|
|
1248
1437
|
ngOnDestroy() {
|
|
1249
1438
|
this.destroy$.next();
|
|
@@ -1257,6 +1446,12 @@ class AIAutocompleteInputDirective {
|
|
|
1257
1446
|
* we don't disrupt multi-keystroke input methods (Pinyin, Hangul, etc.).
|
|
1258
1447
|
*/
|
|
1259
1448
|
onInput(event) {
|
|
1449
|
+
var _a;
|
|
1450
|
+
// Stamp the input time before anything else. The autocapitalize path below
|
|
1451
|
+
// calls setSelectionRange, which can synchronously fire a `select` event →
|
|
1452
|
+
// onCaretMove; stamping first ensures that event falls inside the guard
|
|
1453
|
+
// window and is treated as part of this keystroke, not a caret move.
|
|
1454
|
+
this.lastInputAt = performance.now();
|
|
1260
1455
|
const target = event.target;
|
|
1261
1456
|
const raw = target.value;
|
|
1262
1457
|
const isComposing = event.isComposing === true;
|
|
@@ -1271,17 +1466,43 @@ class AIAutocompleteInputDirective {
|
|
|
1271
1466
|
try {
|
|
1272
1467
|
target.setSelectionRange(start, end);
|
|
1273
1468
|
}
|
|
1274
|
-
catch (
|
|
1469
|
+
catch (_b) {
|
|
1275
1470
|
// See note in ngOnInit's text$ subscriber.
|
|
1276
1471
|
}
|
|
1277
1472
|
}
|
|
1278
1473
|
}
|
|
1279
1474
|
this.controller.handleTextChange(next);
|
|
1475
|
+
// Sync the real caret into the core. The dropdown's auto-trigger gate only
|
|
1476
|
+
// opens when the caret is at the end of the text; without this, the core's
|
|
1477
|
+
// `caretOffset` stays pinned at whatever a prior promote/select set it to
|
|
1478
|
+
// and goes stale as the user keeps typing, wedging the dropdown closed.
|
|
1479
|
+
// selectionStart is null on some input types (number/email/color); treat
|
|
1480
|
+
// that as "caret at end" rather than feeding null into the core.
|
|
1481
|
+
this.controller.handleCaretAfterInput((_a = target.selectionStart) !== null && _a !== void 0 ? _a : target.value.length);
|
|
1280
1482
|
this.onChangeFn(next);
|
|
1281
1483
|
}
|
|
1282
1484
|
onKeyDown(event) {
|
|
1283
1485
|
this.controller.handleKeyDown(event);
|
|
1284
1486
|
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Caret moved by something other than typing — arrow keys, Home/End, or a
|
|
1489
|
+
* mouse click/drag (keyup + mouseup). Forward the new offset so the core can
|
|
1490
|
+
* re-evaluate the caret-at-end dropdown gate (and exit re-edit if the caret
|
|
1491
|
+
* left the region). We deliberately do NOT listen for the `select` event:
|
|
1492
|
+
* programmatic `setSelectionRange` calls (the text$ writeback, the post-select
|
|
1493
|
+
* caret reposition) fire it in jsdom/some browsers, which would re-enter here
|
|
1494
|
+
* and clobber `caretOffset`. `keyup`/`mouseup` only fire for real user input.
|
|
1495
|
+
* Skipped right after an `input` event, where the caret is already handled by
|
|
1496
|
+
* `handleCaretAfterInput` above.
|
|
1497
|
+
*/
|
|
1498
|
+
onCaretMove() {
|
|
1499
|
+
var _a;
|
|
1500
|
+
if (performance.now() - this.lastInputAt < 50)
|
|
1501
|
+
return;
|
|
1502
|
+
const el = this.el.nativeElement;
|
|
1503
|
+
// selectionStart is null on some input types — treat as caret at end.
|
|
1504
|
+
this.controller.handleCaretMove((_a = el.selectionStart) !== null && _a !== void 0 ? _a : el.value.length);
|
|
1505
|
+
}
|
|
1285
1506
|
onFocus() {
|
|
1286
1507
|
this.controller.setFocused(true);
|
|
1287
1508
|
}
|
|
@@ -1293,11 +1514,17 @@ class AIAutocompleteInputDirective {
|
|
|
1293
1514
|
writeValue(value) {
|
|
1294
1515
|
const next = value !== null && value !== void 0 ? value : "";
|
|
1295
1516
|
this.renderer.setProperty(this.el.nativeElement, "value", next);
|
|
1296
|
-
//
|
|
1297
|
-
//
|
|
1298
|
-
//
|
|
1299
|
-
|
|
1517
|
+
// If Angular calls writeValue() before ngOnInit (possible during dynamic
|
|
1518
|
+
// form construction), queue the value and let ngOnInit flush it. We
|
|
1519
|
+
// can't push it into the controller now because either the controller
|
|
1520
|
+
// binding hasn't resolved yet, or text$'s initial subscription would
|
|
1521
|
+
// overwrite it moments later.
|
|
1522
|
+
if (this.controller) {
|
|
1300
1523
|
this.controller.setValue(next);
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
this.pendingWriteValue = next;
|
|
1527
|
+
}
|
|
1301
1528
|
}
|
|
1302
1529
|
registerOnChange(fn) {
|
|
1303
1530
|
this.onChangeFn = fn;
|
|
@@ -1310,7 +1537,7 @@ class AIAutocompleteInputDirective {
|
|
|
1310
1537
|
}
|
|
1311
1538
|
}
|
|
1312
1539
|
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: [
|
|
1540
|
+
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
1541
|
{
|
|
1315
1542
|
provide: NG_VALUE_ACCESSOR,
|
|
1316
1543
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
@@ -1328,6 +1555,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
1328
1555
|
"aria-autocomplete": "list",
|
|
1329
1556
|
"(input)": "onInput($event)",
|
|
1330
1557
|
"(keydown)": "onKeyDown($event)",
|
|
1558
|
+
"(keyup)": "onCaretMove()",
|
|
1559
|
+
"(mouseup)": "onCaretMove()",
|
|
1331
1560
|
"(focus)": "onFocus()",
|
|
1332
1561
|
"(blur)": "onBlur()",
|
|
1333
1562
|
},
|