@magicx-eng/ai-autocomplete-angular 0.5.8 → 0.7.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 +146 -4
- package/esm2020/lib/ai-autocomplete/ai-autocomplete.component.mjs +25 -13
- package/esm2020/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.mjs +19 -4
- package/esm2020/lib/controller/ai-autocomplete.controller.mjs +20 -1
- package/esm2020/lib/internal/product-strip/product-strip.component.mjs +0 -0
- package/esm2020/lib/shared/types.mjs +1 -1
- package/esm2020/public-api.mjs +3 -1
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs +166 -16
- package/fesm2015/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs +163 -16
- package/fesm2020/magicx-eng-ai-autocomplete-angular.mjs.map +1 -1
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts +20 -2
- package/lib/ai-autocomplete/ai-autocomplete.component.d.ts.map +1 -1
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts +9 -1
- package/lib/ai-autocomplete-dropdown/ai-autocomplete-dropdown.component.d.ts.map +1 -1
- package/lib/controller/ai-autocomplete.controller.d.ts +24 -1
- package/lib/controller/ai-autocomplete.controller.d.ts.map +1 -1
- package/lib/internal/product-strip/product-strip.component.d.ts +56 -0
- package/lib/internal/product-strip/product-strip.component.d.ts.map +1 -0
- package/lib/shared/types.d.ts +29 -2
- package/lib/shared/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/public-api.d.ts +2 -1
- package/public-api.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -306,6 +306,7 @@ Standalone-first apps don't need this — import the components/directive direct
|
|
|
306
306
|
| `autoFocus` | `boolean` | `true` | Focus the input on mount. Set to `false` to leave focus to the consumer. |
|
|
307
307
|
| `value` | `string` | — | Controlled text value. Pair with `(valueChange)` or use `[(value)]`. |
|
|
308
308
|
| `completedParams` | `CompletedParamState[]` | — | Controlled completed params. Pair with `(completedParamsChange)` or use `[(completedParams)]`. |
|
|
309
|
+
| `products` | `ProductsConfig` | — | Opt-in product strip — see [Product strip](#product-strip). Bind a stable reference (a component field, not an inline object literal). Omit it and nothing about the dropdown changes. |
|
|
309
310
|
| `submitButton` | `TemplateRef<unknown> \| null` | — | Custom submit button. Pass a `<ng-template>` to replace the default arrow button. Pass `null` to render no button. Clicks bubble up and trigger submit, so consumer-supplied buttons work without re-wiring. |
|
|
310
311
|
|
|
311
312
|
| Output | Payload | Description |
|
|
@@ -316,6 +317,7 @@ Standalone-first apps don't need this — import the components/directive direct
|
|
|
316
317
|
| `completedParamsChange` | `CompletedParamState[]` | Two-way binding partner for `[completedParams]`. |
|
|
317
318
|
| `focused` | `void` | The input gained focus. |
|
|
318
319
|
| `blurred` | `void` | The input lost focus. |
|
|
320
|
+
| `productSelect` | `Product` | A product card was activated. The SDK never navigates. |
|
|
319
321
|
|
|
320
322
|
**Imperative methods** (via `@ViewChild`):
|
|
321
323
|
|
|
@@ -326,6 +328,112 @@ Standalone-first apps don't need this — import the components/directive direct
|
|
|
326
328
|
| `reset()` | Clear all state, re-fetch, and start a new session (rotates `session_id`). |
|
|
327
329
|
| `setMode(mode)` | Switch color mode at runtime. |
|
|
328
330
|
|
|
331
|
+
#### Product strip
|
|
332
|
+
|
|
333
|
+
Opt in with `[products]` and the dropdown renders a horizontal row of product
|
|
334
|
+
cards below the options grid. Every platform (Shopify today, others later) has
|
|
335
|
+
its own search endpoint and its own response shape, so the SDK owns the UI and
|
|
336
|
+
the integration owns only the fetching and the mapping.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
import { Component } from "@angular/core";
|
|
340
|
+
import type { Product, ProductsConfig } from "@magicx-eng/ai-autocomplete-angular";
|
|
341
|
+
|
|
342
|
+
@Component({
|
|
343
|
+
template: `<ai-autocomplete
|
|
344
|
+
[apiConfig]="apiConfig"
|
|
345
|
+
[products]="products"
|
|
346
|
+
(productSelect)="openProduct($event)"
|
|
347
|
+
(submitted)="onSubmit($event)"
|
|
348
|
+
></ai-autocomplete>`,
|
|
349
|
+
})
|
|
350
|
+
export class SearchComponent {
|
|
351
|
+
// A component field, not an inline object literal: a new reference on each
|
|
352
|
+
// change-detection pass reads as a swapped integration and clears the
|
|
353
|
+
// current cards (the same rule `apiConfig` and `optionOverrides` follow).
|
|
354
|
+
products: ProductsConfig = {
|
|
355
|
+
// You own the request entirely — auth headers, GraphQL body, locale
|
|
356
|
+
// prefixes. Honour the signal: the SDK aborts it as soon as a newer query
|
|
357
|
+
// supersedes this one.
|
|
358
|
+
fetch: (query, signal) =>
|
|
359
|
+
fetch(`/api/products?q=${encodeURIComponent(query)}`, { signal }).then((r) => r.json()),
|
|
360
|
+
|
|
361
|
+
// Pure mapping, kept out of `fetch` so you can unit-test it without a
|
|
362
|
+
// network.
|
|
363
|
+
transform: (raw): Product[] =>
|
|
364
|
+
(raw as ApiResponse).items.map((item) => ({
|
|
365
|
+
id: item.id,
|
|
366
|
+
title: item.title,
|
|
367
|
+
url: item.url,
|
|
368
|
+
imageUrl: item.image?.src ?? null, // null renders the placeholder tile
|
|
369
|
+
price: formatMoney(item.price), // you know the currency
|
|
370
|
+
vendor: item.brand,
|
|
371
|
+
})),
|
|
372
|
+
|
|
373
|
+
limit: 8, // applied by the SDK, after transform
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Selection emits — the SDK never navigates.
|
|
377
|
+
openProduct(product: Product): void {
|
|
378
|
+
this.router.navigateByUrl(product.url);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`Product` is exactly six fields; only `id`, `title` and `url` are required:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
type Product = {
|
|
387
|
+
id: string;
|
|
388
|
+
title: string;
|
|
389
|
+
url: string;
|
|
390
|
+
imageUrl?: string | null;
|
|
391
|
+
price?: string;
|
|
392
|
+
vendor?: string;
|
|
393
|
+
};
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**What the SDK guarantees**
|
|
397
|
+
|
|
398
|
+
- **One fetch cadence.** The product search rides the same debounce, the same
|
|
399
|
+
`AbortController` and the same version guard as `/suggest` — there is no
|
|
400
|
+
second timer to drift out of step.
|
|
401
|
+
- **Empty queries never reach you.** On mount and after `reset()` the strip
|
|
402
|
+
clears.
|
|
403
|
+
- **Out-of-order responses are dropped.** A slow response for an older query is
|
|
404
|
+
never rendered over a newer one.
|
|
405
|
+
- **Failure is silent.** A rejected `fetch` or a throwing `transform` clears the
|
|
406
|
+
strip, logs once, and leaves the suggestions half untouched — no `error`
|
|
407
|
+
state, no `errored` output, no effect on `isLoading$`.
|
|
408
|
+
- **The two halves are independent.** The panel opens if *either* has content,
|
|
409
|
+
so products keep it open when option filtering empties the option list.
|
|
410
|
+
`dropdownTrigger` (`manual` / `hidden`) still gates the panel as before.
|
|
411
|
+
|
|
412
|
+
**Selection and links.** Cards are real `<a href>` elements, so cmd/ctrl-click,
|
|
413
|
+
middle-click and "copy link address" behave natively. A plain left click is
|
|
414
|
+
intercepted (`preventDefault`) and emits `(productSelect)` instead.
|
|
415
|
+
|
|
416
|
+
`product.url` is used verbatim — the SDK trusts your `transform` output and
|
|
417
|
+
does not sanitise it, so validate the URL there if the platform response isn't
|
|
418
|
+
fully under your control. (Angular additionally runs its own `[href]`
|
|
419
|
+
sanitiser, so unrecognised schemes like `myapp://…` are rewritten to
|
|
420
|
+
`unsafe:…` in that package only.)
|
|
421
|
+
|
|
422
|
+
**Accessibility.** The strip is a `role="group"` labelled "Products"; each card
|
|
423
|
+
is a `role="option"` in the tab order, activated with Enter or Space, with a
|
|
424
|
+
visible focus ring. Tabbing into the strip does not close the panel. The row
|
|
425
|
+
scrolls horizontally by trackpad, wheel, touch and keyboard; the page never
|
|
426
|
+
scrolls sideways and the scrollbar chrome is hidden in all engines.
|
|
427
|
+
|
|
428
|
+
Swapping `[products]` for a different config (or `undefined`) clears the current
|
|
429
|
+
cards; the strip repopulates on the *next* `/suggest` request rather than
|
|
430
|
+
immediately, since the product search rides that one scheduler.
|
|
431
|
+
|
|
432
|
+
Tier 2 gets the same thing: pass `products` / `onProductSelect` to the
|
|
433
|
+
`AIAutocompleteController` and `<ai-autocomplete-dropdown>` renders the strip.
|
|
434
|
+
Tier 3 consumers subscribe to `products$` and call
|
|
435
|
+
`controller.selectProduct(product)` from their own cards.
|
|
436
|
+
|
|
329
437
|
#### Custom submit button
|
|
330
438
|
|
|
331
439
|
```html
|
|
@@ -407,7 +515,7 @@ ac.destroy();
|
|
|
407
515
|
|
|
408
516
|
#### Constructor options
|
|
409
517
|
|
|
410
|
-
Accepts the same shape as `<ai-autocomplete>`'s inputs (`apiConfig`, `optionOverrides`, `columns`, `dropdownTrigger`, `optionsPosition`, `closeDropdownOnBlur`, `showNonTappableOptions`), plus event callbacks (`onSubmit`, `onError`, `onChange`, `onParamsChange`, `onFocus`, `onBlur`) and controlled-mode initial values (`value`, `completedParams`).
|
|
518
|
+
Accepts the same shape as `<ai-autocomplete>`'s inputs (`apiConfig`, `optionOverrides`, `columns`, `dropdownTrigger`, `optionsPosition`, `closeDropdownOnBlur`, `showNonTappableOptions`, `products`), plus event callbacks (`onSubmit`, `onError`, `onChange`, `onParamsChange`, `onFocus`, `onBlur`, `onProductSelect`) and controlled-mode initial values (`value`, `completedParams`).
|
|
411
519
|
|
|
412
520
|
#### State observables
|
|
413
521
|
|
|
@@ -415,10 +523,12 @@ Accepts the same shape as `<ai-autocomplete>`'s inputs (`apiConfig`, `optionOver
|
|
|
415
523
|
|---|---|---|
|
|
416
524
|
| `text$` | `Observable<string>` | Editor's plain-text content. |
|
|
417
525
|
| `completedParams$` | `Observable<CompletedParamState[]>` | Bold completed params currently in the editor. |
|
|
526
|
+
| `skippedParams$` | `Observable<SkippedParamState[]>` | Suggestions the user dismissed with <kbd>→</kbd>. Nothing renders them — pass them to `buildSubmitResult` for a hand-rolled submit. |
|
|
418
527
|
| `suggestionPills$` | `Observable<Suggestion[]>` | Actionable suggestion pills (first item is the active pill). |
|
|
419
528
|
| `segments$` | `Observable<Segment[]>` | Text + completed-param segments for rendering the editor body. |
|
|
420
529
|
| `newParamId$` | `Observable<string \| null>` | ID of the most-recently-promoted param (drives shimmer animation). |
|
|
421
530
|
| `suggestions$` | `Observable<Suggestion[]>` | All suggestions from the server (including non-actionable ones). |
|
|
531
|
+
| `products$` | `Observable<Product[]>` | Results of the latest product search. Always empty unless `products` is configured — see [Product strip](#product-strip). |
|
|
422
532
|
| `activeIndex$` | `Observable<number>` | Currently highlighted dropdown option index. `-1` = none. |
|
|
423
533
|
| `isLoading$` | `Observable<boolean>` | UI-visible loading flag (suppressed during selection animation and re-edit). |
|
|
424
534
|
| `isReady$` | `Observable<boolean>` | Server-reported "query is complete" flag. |
|
|
@@ -440,6 +550,7 @@ Every observable is gated by `distinctUntilChanged` + `shareReplay({ bufferSize:
|
|
|
440
550
|
|---|---|
|
|
441
551
|
| `reset()` | Clear all state, rotate `session_id`, and re-fetch. Call after submit. |
|
|
442
552
|
| `selectOption(option)` | Pick a dropdown option (or replace the re-edited param if in re-edit mode). |
|
|
553
|
+
| `selectProduct(product)` | Announce a product selection (fires `onProductSelect`). `<ai-autocomplete-dropdown>` calls this for you; hand-rolled strips call it themselves. Never navigates. |
|
|
443
554
|
| `setActivePill(index)` | Promote the pill at `index` to active. |
|
|
444
555
|
| `removeLastParam()` | Drop the last completed param's "completed" status. |
|
|
445
556
|
| `clearNewParamId()` | Clear the shimmer-animation state. |
|
|
@@ -492,7 +603,7 @@ The dropdown component for Tier 2:
|
|
|
492
603
|
| `mode` | `"light" \| "dark" \| "auto"` | — | Color mode for a **standalone** dropdown — self-scopes the SDK tokens so no `.magicx-aia` wrapper is needed. `"auto"` follows `prefers-color-scheme`. Leave unset when nested inside a `.magicx-aia` ancestor (e.g. Tier 1). |
|
|
493
604
|
| `className` | `string` | — | Extra CSS class applied to the dropdown root. |
|
|
494
605
|
|
|
495
|
-
Placement (`optionsPosition`) is read from the controller's options via `dropdown$` — set it once on the controller, not on the dropdown. All actions (option selection, hover, pill clicks) are forwarded to the controller — no outputs to wire.
|
|
606
|
+
Placement (`optionsPosition`) is read from the controller's options via `dropdown$` — set it once on the controller, not on the dropdown. The [product strip](#product-strip) comes from there too: configure `products` on the controller and the dropdown renders the cards. All actions (option selection, hover, pill clicks, product activation) are forwarded to the controller — no outputs to wire.
|
|
496
607
|
|
|
497
608
|
### `AutocompleteResult`
|
|
498
609
|
|
|
@@ -500,7 +611,19 @@ Placement (`optionsPosition`) is read from the controller's options via `dropdow
|
|
|
500
611
|
|---|---|---|
|
|
501
612
|
| `query` | `string` | Plain text as the user sees it. |
|
|
502
613
|
| `raw_query` | `string` | Text with placeholder tokens (e.g. `"Create a {{TASK_1}}"`). |
|
|
503
|
-
| `completed_params` | `CompletedParam[]` | Filled parameter values. |
|
|
614
|
+
| `completed_params` | `CompletedParam[]` | Filled parameter values, followed by any the user skipped (see below). |
|
|
615
|
+
|
|
616
|
+
#### Skipped parameters
|
|
617
|
+
|
|
618
|
+
Pressing <kbd>→</kbd> at the end of the input dismisses the active pill. The dismissal is reported to the server — and included in `completed_params` here — as an entry with no placeholder and the sentinel text `"skipped"`:
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
{ placeholder: "", type: "goal", text: "skipped", kind: null }
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Tier 2 consumers who build their own submit payload get the raw skips from the controller (`skippedParams$`, or `getState().skippedParams`) and can fold them in the same way with the exported `buildSubmitResult(text, completedParams, skippedParams)`.
|
|
625
|
+
|
|
626
|
+
> **Reading `skippedParams$` directly:** the array is append-only until `reset()`. The "drop a skip whose type got filled" rule is applied when the payload is built, not by pruning the array — so if the user skips `goal` and later fills one, the raw array still holds the `goal` entry. That's deliberate: the filter self-heals if they then delete that param's text, where pruning would discard the signal for good. `buildSubmitResult` applies the rule for you; to apply it elsewhere (say, a "you skipped X" badge), use the exported `withSkippedParams(completedParams, skippedParams)`.
|
|
504
627
|
|
|
505
628
|
---
|
|
506
629
|
|
|
@@ -531,6 +654,21 @@ Override on the host element (via `class`). All defaults use `:where()` (zero sp
|
|
|
531
654
|
| `--aia-scrollbar-thumb` | `rgba(0, 0, 0, 0.3)` | `rgba(0, 0, 0, 0.3)` | Color of the option list's scrollbar thumb (Firefox + WebKit). |
|
|
532
655
|
| `--aia-streak-rgb` | `99, 102, 241` | `255, 255, 255` | Comma-separated RGB triplet used to tint the option-selection streak animation. |
|
|
533
656
|
| `--aia-streak-glass-bg` | `rgba(99, 102, 241, 0.1)` | `rgba(255, 255, 255, 0.1)` | Background fill for the streak's glass-pill effect. |
|
|
657
|
+
| `--aia-product-card-width` | `116px` | `116px` | Width of a product card in the strip. The media tile is square, so this also sets its height. |
|
|
658
|
+
| `--aia-product-gap` | `8px` | `8px` | Gap between product cards. |
|
|
659
|
+
| `--aia-product-bg` | `transparent` | `transparent` | Product card background. |
|
|
660
|
+
| `--aia-product-bg-active` | `--aia-option-bg` | `--aia-option-bg` | Product card background on hover. |
|
|
661
|
+
| `--aia-product-media-bg` | `--aia-skeleton-bg` | `--aia-skeleton-bg` | Fill behind the product image, and of the placeholder tile when a product has no image. |
|
|
662
|
+
| `--aia-product-placeholder-color` | `--aia-option-color` | `--aia-option-color` | Glyph color of the no-image placeholder tile. |
|
|
663
|
+
| `--aia-product-title-color` | `--aia-option-color-selected` | `--aia-option-color-selected` | Product title text. Follows the option colors by default, so theming the panel moves suggestions and products together. |
|
|
664
|
+
| `--aia-product-price-color` | `--aia-option-color-selected` | `--aia-option-color-selected` | Product price text. |
|
|
665
|
+
| `--aia-product-vendor-color` | `--aia-option-color` | `--aia-option-color` | Product vendor line. |
|
|
666
|
+
| `--aia-product-focus-ring` | `--aia-option-color-selected` | `--aia-option-color-selected` | Focus ring drawn on a keyboard-focused card. |
|
|
667
|
+
| `--aia-products-label-color` | `--aia-option-color` | `--aia-option-color` | "Products" section label. |
|
|
668
|
+
| `--aia-product-title-font-size` | `12px` | `12px` | Product title font size. |
|
|
669
|
+
| `--aia-product-price-font-size` | `11px` | `11px` | Product price font size. |
|
|
670
|
+
| `--aia-product-vendor-font-size` | `10px` | `10px` | Product vendor font size. |
|
|
671
|
+
| `--aia-products-label-font-size` | `11px` | `11px` | Section label font size. |
|
|
534
672
|
| `--aia-skeleton-bg` | `rgba(189, 189, 189, 0.25)` | `#1a1b1d` | Fill color for the loading skeleton bars and masked text in cached pills/options. |
|
|
535
673
|
| `--aia-footer-chip-bg` | `--aia-surface` at 65% | `--aia-surface` at 65% | Fill behind the footer's keyboard hint and AI-Autocomplete badge. The option list scrolls under the footer, so this keeps both legible over the row passing behind them. `transparent` on the glass surface. |
|
|
536
674
|
|
|
@@ -558,7 +696,11 @@ For styling beyond the CSS variables, target these stable `data-aia-*` attribute
|
|
|
558
696
|
| `[data-aia-pill]` | Each unfilled-suggestion pill |
|
|
559
697
|
| `[data-aia-pillbar]` | Pill bar container inside the dropdown |
|
|
560
698
|
| `[data-aia-option]` | Each suggestion option |
|
|
561
|
-
| `[data-aia-dropdown]` | The dropdown root (listbox) |
|
|
699
|
+
| `[data-aia-dropdown]` | The dropdown root (listbox). Carries `data-aia-has-products` while the product strip has cards. |
|
|
700
|
+
| `[data-aia-products]` | Product strip section (label + row) |
|
|
701
|
+
| `[data-aia-products-row]` | The horizontally scrolling row of cards |
|
|
702
|
+
| `[data-aia-product]` | Each product card |
|
|
703
|
+
| `[data-aia-product-placeholder]` | Media tile of a card whose product has no image |
|
|
562
704
|
|
|
563
705
|
Completed params render as inline `<strong>` elements inside the editor. Override their weight with `[data-aia-input] strong { font-weight: 700; }`.
|
|
564
706
|
|