@tailng-ui/primitives 0.25.0 → 0.26.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailng-ui/primitives",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@ export declare class TngMultiAutocomplete<T = unknown> {
19
19
  private _listboxId;
20
20
  private _activeId;
21
21
  private _listboxApi;
22
+ private _overlayElement;
22
23
  protected readonly dataSlot: "multi-autocomplete";
23
24
  protected get dataState(): 'open' | 'closed';
24
25
  protected get dataDisabled(): '' | null;
@@ -40,6 +41,9 @@ export declare class TngMultiAutocomplete<T = unknown> {
40
41
  getActiveDescendantId(): string | null;
41
42
  setListboxApi(api: TngMultiAutocompleteListboxApi | null): void;
42
43
  getListboxApi(): TngMultiAutocompleteListboxApi | null;
44
+ setOverlayElement(element: HTMLElement | null): void;
45
+ getOverlayElement(): HTMLElement | null;
46
+ containsOwnedNode(node: Node | null): boolean;
43
47
  static ɵfac: i0.ɵɵFactoryDeclaration<TngMultiAutocomplete<any>, never>;
44
48
  static ɵdir: i0.ɵɵDirectiveDeclaration<TngMultiAutocomplete<any>, "[tngMultiAutocomplete]", ["tngMultiAutocomplete"], { "open": { "alias": "open"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "query": { "alias": "query"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "value": "valueChange"; "query": "queryChange"; "queryChange": "queryChange"; }, never, never, true, never>;
45
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tng-multi-autocomplete.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;;AAE7F,qBAKa,oBAAoB,CAAC,CAAC,GAAG,OAAO;IAC3C,QAAQ,CAAC,WAAW,MAAiD;IAErE,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,+CAAyB;IAEtC,sBAAsB;IACtB,QAAQ,CAAC,QAAQ,+CAAyB;IAE1C,sCAAsC;IACtC,QAAQ,CAAC,KAAK,oDAA2B;IAEzC,gDAAgD;IAChD,QAAQ,CAAC,KAAK,8CAAqB;IAEnC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,mDAAoB;IAExC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,+CAAyB;IACzC,QAAQ,CAAC,OAAO,+CAAyB;IAGzC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAA+C;IAKlE,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,oBAAoB,CAAU;IAG5D,SAAS,KAAK,SAAS,IAAI,MAAM,GAAG,QAAQ,CAE3C;IAGD,SAAS,KAAK,YAAY,IAAI,EAAE,GAAG,IAAI,CAEtC;IAGD,SAAS,KAAK,WAAW,IAAI,EAAE,GAAG,IAAI,CAErC;IAGD,SAAS,KAAK,WAAW,IAAI,EAAE,GAAG,IAAI,CAErC;IAID,UAAU,IAAI,IAAI;IAKlB,KAAK,IAAI,IAAI;IAIb,UAAU,IAAI,IAAI;IASlB,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAUnB,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAWtB,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAatB,KAAK,IAAI,IAAI;IAKb,UAAU,IAAI,IAAI;IAalB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAGrC,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAGrC,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAG9C,qBAAqB,IAAI,MAAM,GAAG,IAAI;IAItC,aAAa,CAAC,GAAG,EAAE,8BAA8B,GAAG,IAAI,GAAG,IAAI;IAG/D,aAAa,IAAI,8BAA8B,GAAG,IAAI;yCArJ3C,oBAAoB;2CAApB,oBAAoB;CAwJhC"}
1
+ {"version":3,"file":"tng-multi-autocomplete.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;;AAE7F,qBAKa,oBAAoB,CAAC,CAAC,GAAG,OAAO;IAC3C,QAAQ,CAAC,WAAW,MAAiD;IAErE,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,+CAAyB;IAEtC,sBAAsB;IACtB,QAAQ,CAAC,QAAQ,+CAAyB;IAE1C,sCAAsC;IACtC,QAAQ,CAAC,KAAK,oDAA2B;IAEzC,gDAAgD;IAChD,QAAQ,CAAC,KAAK,8CAAqB;IAEnC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,mDAAoB;IAExC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,+CAAyB;IACzC,QAAQ,CAAC,OAAO,+CAAyB;IAGzC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,eAAe,CAA4B;IAKnD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,oBAAoB,CAAU;IAG5D,SAAS,KAAK,SAAS,IAAI,MAAM,GAAG,QAAQ,CAE3C;IAGD,SAAS,KAAK,YAAY,IAAI,EAAE,GAAG,IAAI,CAEtC;IAGD,SAAS,KAAK,WAAW,IAAI,EAAE,GAAG,IAAI,CAErC;IAGD,SAAS,KAAK,WAAW,IAAI,EAAE,GAAG,IAAI,CAErC;IAID,UAAU,IAAI,IAAI;IAKlB,KAAK,IAAI,IAAI;IAIb,UAAU,IAAI,IAAI;IASlB,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAUnB,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAWtB,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAatB,KAAK,IAAI,IAAI;IAKb,UAAU,IAAI,IAAI;IAalB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAGrC,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAGrC,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAG9C,qBAAqB,IAAI,MAAM,GAAG,IAAI;IAItC,aAAa,CAAC,GAAG,EAAE,8BAA8B,GAAG,IAAI,GAAG,IAAI;IAG/D,aAAa,IAAI,8BAA8B,GAAG,IAAI;IAItD,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAIpD,iBAAiB,IAAI,WAAW,GAAG,IAAI;IAIvC,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO;yCAlKlC,oBAAoB;2CAApB,oBAAoB;CA4KhC"}
@@ -21,6 +21,7 @@ export class TngMultiAutocomplete {
21
21
  _listboxId = null;
22
22
  _activeId = null;
23
23
  _listboxApi = null;
24
+ _overlayElement = null;
24
25
  // ---- host styling hooks ----
25
26
  dataSlot = 'multi-autocomplete';
26
27
  get dataState() {
@@ -122,6 +123,19 @@ export class TngMultiAutocomplete {
122
123
  getListboxApi() {
123
124
  return this._listboxApi;
124
125
  }
126
+ setOverlayElement(element) {
127
+ this._overlayElement = element;
128
+ }
129
+ getOverlayElement() {
130
+ return this._overlayElement;
131
+ }
132
+ containsOwnedNode(node) {
133
+ if (node === null) {
134
+ return false;
135
+ }
136
+ return (this.hostElement.contains(node) ||
137
+ this._overlayElement?.contains(node) === true);
138
+ }
125
139
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngMultiAutocomplete, deps: [], target: i0.ɵɵFactoryTarget.Directive });
126
140
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: TngMultiAutocomplete, isStandalone: true, selector: "[tngMultiAutocomplete]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange", query: "queryChange", queryChange: "queryChange" }, host: { properties: { "attr.data-slot": "this.dataSlot", "attr.data-state": "this.dataState", "attr.data-disabled": "this.dataDisabled", "attr.data-loading": "this.dataLoading", "attr.data-invalid": "this.dataInvalid" } }, providers: [{ provide: TNG_MULTI_AUTOCOMPLETE, useExisting: TngMultiAutocomplete }], exportAs: ["tngMultiAutocomplete"], ngImport: i0 });
127
141
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tng-multi-autocomplete.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n ElementRef,\n HostBinding,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { TNG_MULTI_AUTOCOMPLETE } from './tng-multi-autocomplete.tokens';\nimport type { TngMultiAutocompleteListboxApi } from './tng-multi-autocomplete.listbox.types';\n\n@Directive({\n selector: '[tngMultiAutocomplete]',\n exportAs: 'tngMultiAutocomplete',\n providers: [{ provide: TNG_MULTI_AUTOCOMPLETE, useExisting: TngMultiAutocomplete }],\n})\nexport class TngMultiAutocomplete<T = unknown> {\n readonly hostElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n /** Whether overlay is open. */\n readonly open = model<boolean>(false);\n\n /** Disabled state. */\n readonly disabled = input<boolean>(false);\n\n /** Selected values (always array). */\n readonly value = model<readonly T[]>([]);\n\n /** Current input query (used for filtering). */\n readonly query = model<string>('');\n\n /** Emits whenever query changes (focus-open emit, typing, selection-clear, etc.) */\n readonly queryChange = output<string>();\n\n /** Optional states (styling/aria). */\n readonly loading = input<boolean>(false);\n readonly invalid = input<boolean>(false);\n\n // ---- internal bridge state ----\n private _contentId: string | null = null;\n private _listboxId: string | null = null;\n private _activeId: string | null = null;\n private _listboxApi: TngMultiAutocompleteListboxApi | null = null;\n\n // ---- host styling hooks ----\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'multi-autocomplete' as const;\n\n @HostBinding('attr.data-state')\n protected get dataState(): 'open' | 'closed' {\n return this.open() ? 'open' : 'closed';\n }\n\n @HostBinding('attr.data-disabled')\n protected get dataDisabled(): '' | null {\n return this.disabled() ? '' : null;\n }\n\n @HostBinding('attr.data-loading')\n protected get dataLoading(): '' | null {\n return this.loading() ? '' : null;\n }\n\n @HostBinding('attr.data-invalid')\n protected get dataInvalid(): '' | null {\n return this.invalid() ? '' : null;\n }\n\n // ---- overlay control ----\n\n openSelect(): void {\n if (this.disabled()) return;\n this.open.set(true);\n }\n\n close(): void {\n this.open.set(false);\n }\n\n toggleOpen(): void {\n if (this.disabled()) return;\n this.open.set(!this.open());\n }\n\n // =========================================================\n // Selection API\n // =========================================================\n\n add(value: T): void {\n if (this.disabled()) return;\n\n const current = this.value();\n const exists = current.some((v) => Object.is(v, value));\n if (exists) return;\n\n this.value.set([...current, value]);\n }\n\n remove(value: T): void {\n if (this.disabled()) return;\n\n const current = this.value();\n const next = current.filter((v) => !Object.is(v, value));\n\n if (next.length !== current.length) {\n this.value.set(next);\n }\n }\n\n toggle(value: T): void {\n if (this.disabled()) return;\n\n const current = this.value();\n const exists = current.some((v) => Object.is(v, value));\n\n if (exists) {\n this.value.set(current.filter((v) => !Object.is(v, value)));\n } else {\n this.value.set([...current, value]);\n }\n }\n\n clear(): void {\n if (this.disabled()) return;\n this.value.set([]);\n }\n\n removeLast(): void {\n if (this.disabled()) return;\n\n const current = this.value();\n if (current.length === 0) return;\n\n this.value.set(current.slice(0, -1));\n }\n\n // =========================================================\n // Listbox bridge\n // =========================================================\n\n setContentId(id: string | null): void {\n this._contentId = id;\n }\n getContentId(): string | null {\n return this._contentId;\n }\n\n setListboxId(id: string | null): void {\n this._listboxId = id;\n }\n getListboxId(): string | null {\n return this._listboxId;\n }\n\n setActiveDescendantId(id: string | null): void {\n this._activeId = id;\n }\n getActiveDescendantId(): string | null {\n return this._activeId;\n }\n\n setListboxApi(api: TngMultiAutocompleteListboxApi | null): void {\n this._listboxApi = api;\n }\n getListboxApi(): TngMultiAutocompleteListboxApi | null {\n return this._listboxApi;\n }\n}\n"]}
1
+ {"version":3,"file":"tng-multi-autocomplete.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n ElementRef,\n HostBinding,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { TNG_MULTI_AUTOCOMPLETE } from './tng-multi-autocomplete.tokens';\nimport type { TngMultiAutocompleteListboxApi } from './tng-multi-autocomplete.listbox.types';\n\n@Directive({\n selector: '[tngMultiAutocomplete]',\n exportAs: 'tngMultiAutocomplete',\n providers: [{ provide: TNG_MULTI_AUTOCOMPLETE, useExisting: TngMultiAutocomplete }],\n})\nexport class TngMultiAutocomplete<T = unknown> {\n readonly hostElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n /** Whether overlay is open. */\n readonly open = model<boolean>(false);\n\n /** Disabled state. */\n readonly disabled = input<boolean>(false);\n\n /** Selected values (always array). */\n readonly value = model<readonly T[]>([]);\n\n /** Current input query (used for filtering). */\n readonly query = model<string>('');\n\n /** Emits whenever query changes (focus-open emit, typing, selection-clear, etc.) */\n readonly queryChange = output<string>();\n\n /** Optional states (styling/aria). */\n readonly loading = input<boolean>(false);\n readonly invalid = input<boolean>(false);\n\n // ---- internal bridge state ----\n private _contentId: string | null = null;\n private _listboxId: string | null = null;\n private _activeId: string | null = null;\n private _listboxApi: TngMultiAutocompleteListboxApi | null = null;\n private _overlayElement: HTMLElement | null = null;\n\n // ---- host styling hooks ----\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'multi-autocomplete' as const;\n\n @HostBinding('attr.data-state')\n protected get dataState(): 'open' | 'closed' {\n return this.open() ? 'open' : 'closed';\n }\n\n @HostBinding('attr.data-disabled')\n protected get dataDisabled(): '' | null {\n return this.disabled() ? '' : null;\n }\n\n @HostBinding('attr.data-loading')\n protected get dataLoading(): '' | null {\n return this.loading() ? '' : null;\n }\n\n @HostBinding('attr.data-invalid')\n protected get dataInvalid(): '' | null {\n return this.invalid() ? '' : null;\n }\n\n // ---- overlay control ----\n\n openSelect(): void {\n if (this.disabled()) return;\n this.open.set(true);\n }\n\n close(): void {\n this.open.set(false);\n }\n\n toggleOpen(): void {\n if (this.disabled()) return;\n this.open.set(!this.open());\n }\n\n // =========================================================\n // Selection API\n // =========================================================\n\n add(value: T): void {\n if (this.disabled()) return;\n\n const current = this.value();\n const exists = current.some((v) => Object.is(v, value));\n if (exists) return;\n\n this.value.set([...current, value]);\n }\n\n remove(value: T): void {\n if (this.disabled()) return;\n\n const current = this.value();\n const next = current.filter((v) => !Object.is(v, value));\n\n if (next.length !== current.length) {\n this.value.set(next);\n }\n }\n\n toggle(value: T): void {\n if (this.disabled()) return;\n\n const current = this.value();\n const exists = current.some((v) => Object.is(v, value));\n\n if (exists) {\n this.value.set(current.filter((v) => !Object.is(v, value)));\n } else {\n this.value.set([...current, value]);\n }\n }\n\n clear(): void {\n if (this.disabled()) return;\n this.value.set([]);\n }\n\n removeLast(): void {\n if (this.disabled()) return;\n\n const current = this.value();\n if (current.length === 0) return;\n\n this.value.set(current.slice(0, -1));\n }\n\n // =========================================================\n // Listbox bridge\n // =========================================================\n\n setContentId(id: string | null): void {\n this._contentId = id;\n }\n getContentId(): string | null {\n return this._contentId;\n }\n\n setListboxId(id: string | null): void {\n this._listboxId = id;\n }\n getListboxId(): string | null {\n return this._listboxId;\n }\n\n setActiveDescendantId(id: string | null): void {\n this._activeId = id;\n }\n getActiveDescendantId(): string | null {\n return this._activeId;\n }\n\n setListboxApi(api: TngMultiAutocompleteListboxApi | null): void {\n this._listboxApi = api;\n }\n getListboxApi(): TngMultiAutocompleteListboxApi | null {\n return this._listboxApi;\n }\n\n setOverlayElement(element: HTMLElement | null): void {\n this._overlayElement = element;\n }\n\n getOverlayElement(): HTMLElement | null {\n return this._overlayElement;\n }\n\n containsOwnedNode(node: Node | null): boolean {\n if (node === null) {\n return false;\n }\n\n return (\n this.hostElement.contains(node) ||\n this._overlayElement?.contains(node) === true\n );\n }\n}\n"]}
@@ -1,6 +1,25 @@
1
1
  import * as i0 from "@angular/core";
2
2
  export declare class TngMultiAutocompleteOverlay {
3
+ private readonly multi;
4
+ private readonly elRef;
5
+ private readonly destroyRef;
6
+ private removeResizeListener;
7
+ private removeScrollListener;
8
+ private resizeObserver;
9
+ private removeDocPointerListener;
10
+ private placeholder;
11
+ private originalParent;
3
12
  protected readonly dataSlot: "multi-autocomplete-overlay";
13
+ protected get hidden(): '' | null;
14
+ constructor();
15
+ private findAnchorEl;
16
+ private reposition;
17
+ private setupRepositionListeners;
18
+ private teardownRepositionListeners;
19
+ private setupOutsidePointer;
20
+ private teardownOutsidePointer;
21
+ private mountToBodyAndPosition;
22
+ private restoreToPlaceholder;
4
23
  static ɵfac: i0.ɵɵFactoryDeclaration<TngMultiAutocompleteOverlay, never>;
5
24
  static ɵdir: i0.ɵɵDirectiveDeclaration<TngMultiAutocompleteOverlay, "[tngMultiAutocompleteOverlay]", ["tngMultiAutocompleteOverlay"], {}, {}, never, never, true, never>;
6
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tng-multi-autocomplete.overlay.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.overlay.ts"],"names":[],"mappings":";AAEA,qBAIa,2BAA2B;IAEtC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,4BAA4B,CAAU;yCAFzD,2BAA2B;2CAA3B,2BAA2B;CAGvC"}
1
+ {"version":3,"file":"tng-multi-autocomplete.overlay.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.overlay.ts"],"names":[],"mappings":";AA+BA,qBAIa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwD;IAC9E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IAEjD,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,wBAAwB,CAA6B;IAC7D,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,cAAc,CAAqB;IAG3C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,4BAA4B,CAAU;IAGpE,SAAS,KAAK,MAAM,IAAI,EAAE,GAAG,IAAI,CAEhC;;IA2BD,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,wBAAwB;IA2BhC,OAAO,CAAC,2BAA2B;IASnC,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,sBAAsB;IAwB9B,OAAO,CAAC,oBAAoB;yCAzJjB,2BAA2B;2CAA3B,2BAA2B;CA8KvC"}
@@ -1,9 +1,171 @@
1
- import { Directive, HostBinding } from '@angular/core';
1
+ import { DestroyRef, Directive, ElementRef, HostBinding, effect, inject, } from '@angular/core';
2
+ import { computeOverlayPosition } from '@tailng-ui/cdk';
3
+ import { TNG_MULTI_AUTOCOMPLETE } from './tng-multi-autocomplete.tokens';
2
4
  import * as i0 from "@angular/core";
5
+ function rectFromClientRect(r) {
6
+ return { left: r.left, top: r.top, width: r.width, height: r.height };
7
+ }
8
+ function viewportRect() {
9
+ return {
10
+ left: 0,
11
+ top: 0,
12
+ width: window.innerWidth || 1024,
13
+ height: window.innerHeight || 768,
14
+ };
15
+ }
16
+ function isInside(target, container) {
17
+ return !!target && target instanceof Node && container.contains(target);
18
+ }
3
19
  export class TngMultiAutocompleteOverlay {
20
+ multi = inject(TNG_MULTI_AUTOCOMPLETE);
21
+ elRef = inject((ElementRef));
22
+ destroyRef = inject(DestroyRef);
23
+ removeResizeListener = null;
24
+ removeScrollListener = null;
25
+ resizeObserver = null;
26
+ removeDocPointerListener = null;
27
+ placeholder = null;
28
+ originalParent = null;
4
29
  dataSlot = 'multi-autocomplete-overlay';
30
+ get hidden() {
31
+ return this.multi.open() ? null : '';
32
+ }
33
+ constructor() {
34
+ const hostEl = this.elRef.nativeElement;
35
+ this.placeholder = document.createComment('tng-multi-autocomplete-overlay-anchor');
36
+ this.originalParent = hostEl.parentNode;
37
+ this.originalParent?.insertBefore(this.placeholder, hostEl);
38
+ this.multi.setOverlayElement(hostEl);
39
+ effect(() => {
40
+ const open = this.multi.open();
41
+ if (open) {
42
+ this.mountToBodyAndPosition();
43
+ }
44
+ else {
45
+ this.restoreToPlaceholder();
46
+ }
47
+ });
48
+ this.destroyRef.onDestroy(() => {
49
+ this.teardownOutsidePointer();
50
+ this.restoreToPlaceholder(true);
51
+ this.multi.setOverlayElement(null);
52
+ this.placeholder = null;
53
+ this.originalParent = null;
54
+ });
55
+ }
56
+ findAnchorEl() {
57
+ return this.multi.hostElement;
58
+ }
59
+ reposition() {
60
+ if (!this.multi.open())
61
+ return;
62
+ const panel = this.elRef.nativeElement;
63
+ const anchorEl = this.findAnchorEl();
64
+ const anchor = rectFromClientRect(anchorEl.getBoundingClientRect());
65
+ const overlay = rectFromClientRect(panel.getBoundingClientRect());
66
+ const viewport = viewportRect();
67
+ const result = computeOverlayPosition({
68
+ anchorRect: anchor,
69
+ overlayRect: overlay,
70
+ viewportRect: viewport,
71
+ });
72
+ panel.style.left = `${result.x}px`;
73
+ panel.style.top = `${result.y}px`;
74
+ }
75
+ setupRepositionListeners() {
76
+ let rafId = null;
77
+ const schedule = () => {
78
+ if (rafId !== null)
79
+ return;
80
+ rafId = requestAnimationFrame(() => {
81
+ rafId = null;
82
+ this.reposition();
83
+ });
84
+ };
85
+ const onResize = () => schedule();
86
+ const onScroll = () => schedule();
87
+ window.addEventListener('resize', onResize);
88
+ window.addEventListener('scroll', onScroll, true);
89
+ this.removeResizeListener = () => window.removeEventListener('resize', onResize);
90
+ this.removeScrollListener = () => window.removeEventListener('scroll', onScroll, true);
91
+ if ('ResizeObserver' in window) {
92
+ this.resizeObserver = new ResizeObserver(() => schedule());
93
+ this.resizeObserver.observe(this.findAnchorEl());
94
+ this.resizeObserver.observe(this.elRef.nativeElement);
95
+ }
96
+ }
97
+ teardownRepositionListeners() {
98
+ this.removeResizeListener?.();
99
+ this.removeScrollListener?.();
100
+ this.removeResizeListener = null;
101
+ this.removeScrollListener = null;
102
+ this.resizeObserver?.disconnect();
103
+ this.resizeObserver = null;
104
+ }
105
+ setupOutsidePointer() {
106
+ if (this.removeDocPointerListener)
107
+ return;
108
+ const onPointerDown = (event) => {
109
+ if (!this.multi.open())
110
+ return;
111
+ const panel = this.elRef.nativeElement;
112
+ if (isInside(event.target, panel))
113
+ return;
114
+ if (isInside(event.target, this.multi.hostElement))
115
+ return;
116
+ if (event.target && event.target.closest?.('[data-slot="multi-autocomplete-option"]')) {
117
+ return;
118
+ }
119
+ this.multi.close();
120
+ };
121
+ document.addEventListener('pointerdown', onPointerDown, true);
122
+ this.removeDocPointerListener = () => document.removeEventListener('pointerdown', onPointerDown, true);
123
+ }
124
+ teardownOutsidePointer() {
125
+ this.removeDocPointerListener?.();
126
+ this.removeDocPointerListener = null;
127
+ }
128
+ mountToBodyAndPosition() {
129
+ this.setupRepositionListeners();
130
+ const panel = this.elRef.nativeElement;
131
+ if (panel.parentNode !== document.body) {
132
+ document.body.appendChild(panel);
133
+ }
134
+ panel.style.position = 'fixed';
135
+ panel.style.left = '0px';
136
+ panel.style.top = '0px';
137
+ panel.style.zIndex = '1000';
138
+ queueMicrotask(() => {
139
+ if (!this.multi.open())
140
+ return;
141
+ const anchor = rectFromClientRect(this.findAnchorEl().getBoundingClientRect());
142
+ panel.style.minWidth = `${anchor.width}px`;
143
+ this.reposition();
144
+ });
145
+ this.setupOutsidePointer();
146
+ }
147
+ restoreToPlaceholder(force = false) {
148
+ const panel = this.elRef.nativeElement;
149
+ if (!force && panel.parentNode !== document.body) {
150
+ this.teardownOutsidePointer();
151
+ return;
152
+ }
153
+ if (this.placeholder?.parentNode) {
154
+ this.placeholder.parentNode.insertBefore(panel, this.placeholder);
155
+ }
156
+ else if (this.originalParent) {
157
+ this.originalParent.appendChild(panel);
158
+ }
159
+ this.teardownRepositionListeners();
160
+ panel.style.position = '';
161
+ panel.style.left = '';
162
+ panel.style.top = '';
163
+ panel.style.zIndex = '';
164
+ panel.style.minWidth = '';
165
+ this.teardownOutsidePointer();
166
+ }
5
167
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngMultiAutocompleteOverlay, deps: [], target: i0.ɵɵFactoryTarget.Directive });
6
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TngMultiAutocompleteOverlay, isStandalone: true, selector: "[tngMultiAutocompleteOverlay]", host: { properties: { "attr.data-slot": "this.dataSlot" } }, exportAs: ["tngMultiAutocompleteOverlay"], ngImport: i0 });
168
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TngMultiAutocompleteOverlay, isStandalone: true, selector: "[tngMultiAutocompleteOverlay]", host: { properties: { "attr.data-slot": "this.dataSlot", "attr.hidden": "this.hidden" } }, exportAs: ["tngMultiAutocompleteOverlay"], ngImport: i0 });
7
169
  }
8
170
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngMultiAutocompleteOverlay, decorators: [{
9
171
  type: Directive,
@@ -11,8 +173,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
11
173
  selector: '[tngMultiAutocompleteOverlay]',
12
174
  exportAs: 'tngMultiAutocompleteOverlay',
13
175
  }]
14
- }], propDecorators: { dataSlot: [{
176
+ }], ctorParameters: () => [], propDecorators: { dataSlot: [{
15
177
  type: HostBinding,
16
178
  args: ['attr.data-slot']
179
+ }], hidden: [{
180
+ type: HostBinding,
181
+ args: ['attr.hidden']
17
182
  }] } });
18
183
  //# sourceMappingURL=tng-multi-autocomplete.overlay.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tng-multi-autocomplete.overlay.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.overlay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;;AAMvD,MAAM,OAAO,2BAA2B;IAEnB,QAAQ,GAAG,4BAAqC,CAAC;uGAFzD,2BAA2B;2FAA3B,2BAA2B;;2FAA3B,2BAA2B;kBAJvC,SAAS;mBAAC;oBACT,QAAQ,EAAE,+BAA+B;oBACzC,QAAQ,EAAE,6BAA6B;iBACxC;;sBAEE,WAAW;uBAAC,gBAAgB","sourcesContent":["import { Directive, HostBinding } from '@angular/core';\n\n@Directive({\n selector: '[tngMultiAutocompleteOverlay]',\n exportAs: 'tngMultiAutocompleteOverlay',\n})\nexport class TngMultiAutocompleteOverlay {\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'multi-autocomplete-overlay' as const;\n}"]}
1
+ {"version":3,"file":"tng-multi-autocomplete.overlay.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.overlay.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DestroyRef,\n Directive,\n ElementRef,\n HostBinding,\n effect,\n inject,\n} from '@angular/core';\nimport { computeOverlayPosition } from '@tailng-ui/cdk';\nimport { TNG_MULTI_AUTOCOMPLETE } from './tng-multi-autocomplete.tokens';\nimport type { TngMultiAutocomplete } from './tng-multi-autocomplete';\n\ntype MaybeRect = Readonly<{ left: number; top: number; width: number; height: number }>;\n\nfunction rectFromClientRect(r: DOMRect | ClientRect): MaybeRect {\n return { left: r.left, top: r.top, width: r.width, height: r.height };\n}\n\nfunction viewportRect(): MaybeRect {\n return {\n left: 0,\n top: 0,\n width: window.innerWidth || 1024,\n height: window.innerHeight || 768,\n };\n}\n\nfunction isInside(target: EventTarget | null, container: HTMLElement): boolean {\n return !!target && target instanceof Node && container.contains(target);\n}\n\n@Directive({\n selector: '[tngMultiAutocompleteOverlay]',\n exportAs: 'tngMultiAutocompleteOverlay',\n})\nexport class TngMultiAutocompleteOverlay {\n private readonly multi = inject<TngMultiAutocomplete>(TNG_MULTI_AUTOCOMPLETE);\n private readonly elRef = inject(ElementRef<HTMLElement>);\n private readonly destroyRef = inject(DestroyRef);\n\n private removeResizeListener: (() => void) | null = null;\n private removeScrollListener: (() => void) | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private removeDocPointerListener: (() => void) | null = null;\n private placeholder: Comment | null = null;\n private originalParent: Node | null = null;\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'multi-autocomplete-overlay' as const;\n\n @HostBinding('attr.hidden')\n protected get hidden(): '' | null {\n return this.multi.open() ? null : '';\n }\n\n constructor() {\n const hostEl = this.elRef.nativeElement;\n this.placeholder = document.createComment('tng-multi-autocomplete-overlay-anchor');\n this.originalParent = hostEl.parentNode;\n this.originalParent?.insertBefore(this.placeholder, hostEl);\n this.multi.setOverlayElement(hostEl);\n\n effect(() => {\n const open = this.multi.open();\n if (open) {\n this.mountToBodyAndPosition();\n } else {\n this.restoreToPlaceholder();\n }\n });\n\n this.destroyRef.onDestroy(() => {\n this.teardownOutsidePointer();\n this.restoreToPlaceholder(true);\n this.multi.setOverlayElement(null);\n this.placeholder = null;\n this.originalParent = null;\n });\n }\n\n private findAnchorEl(): HTMLElement {\n return this.multi.hostElement;\n }\n\n private reposition(): void {\n if (!this.multi.open()) return;\n\n const panel = this.elRef.nativeElement;\n const anchorEl = this.findAnchorEl();\n const anchor = rectFromClientRect(anchorEl.getBoundingClientRect());\n const overlay = rectFromClientRect(panel.getBoundingClientRect());\n const viewport = viewportRect();\n const result = computeOverlayPosition({\n anchorRect: anchor,\n overlayRect: overlay,\n viewportRect: viewport,\n });\n\n panel.style.left = `${result.x}px`;\n panel.style.top = `${result.y}px`;\n }\n\n private setupRepositionListeners(): void {\n let rafId: number | null = null;\n\n const schedule = () => {\n if (rafId !== null) return;\n\n rafId = requestAnimationFrame(() => {\n rafId = null;\n this.reposition();\n });\n };\n\n const onResize = () => schedule();\n const onScroll = () => schedule();\n\n window.addEventListener('resize', onResize);\n window.addEventListener('scroll', onScroll, true);\n this.removeResizeListener = () => window.removeEventListener('resize', onResize);\n this.removeScrollListener = () => window.removeEventListener('scroll', onScroll, true);\n\n if ('ResizeObserver' in window) {\n this.resizeObserver = new ResizeObserver(() => schedule());\n this.resizeObserver.observe(this.findAnchorEl());\n this.resizeObserver.observe(this.elRef.nativeElement);\n }\n }\n\n private teardownRepositionListeners(): void {\n this.removeResizeListener?.();\n this.removeScrollListener?.();\n this.removeResizeListener = null;\n this.removeScrollListener = null;\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n }\n\n private setupOutsidePointer(): void {\n if (this.removeDocPointerListener) return;\n\n const onPointerDown = (event: PointerEvent) => {\n if (!this.multi.open()) return;\n\n const panel = this.elRef.nativeElement;\n if (isInside(event.target, panel)) return;\n if (isInside(event.target, this.multi.hostElement)) return;\n if (event.target && (event.target as Element).closest?.('[data-slot=\"multi-autocomplete-option\"]')) {\n return;\n }\n\n this.multi.close();\n };\n\n document.addEventListener('pointerdown', onPointerDown, true);\n this.removeDocPointerListener = () =>\n document.removeEventListener('pointerdown', onPointerDown, true);\n }\n\n private teardownOutsidePointer(): void {\n this.removeDocPointerListener?.();\n this.removeDocPointerListener = null;\n }\n\n private mountToBodyAndPosition(): void {\n this.setupRepositionListeners();\n\n const panel = this.elRef.nativeElement;\n if (panel.parentNode !== document.body) {\n document.body.appendChild(panel);\n }\n\n panel.style.position = 'fixed';\n panel.style.left = '0px';\n panel.style.top = '0px';\n panel.style.zIndex = '1000';\n\n queueMicrotask(() => {\n if (!this.multi.open()) return;\n\n const anchor = rectFromClientRect(this.findAnchorEl().getBoundingClientRect());\n panel.style.minWidth = `${anchor.width}px`;\n this.reposition();\n });\n\n this.setupOutsidePointer();\n }\n\n private restoreToPlaceholder(force = false): void {\n const panel = this.elRef.nativeElement;\n if (!force && panel.parentNode !== document.body) {\n this.teardownOutsidePointer();\n return;\n }\n\n if (this.placeholder?.parentNode) {\n this.placeholder.parentNode.insertBefore(panel, this.placeholder);\n } else if (this.originalParent) {\n this.originalParent.appendChild(panel);\n }\n\n this.teardownRepositionListeners();\n panel.style.position = '';\n panel.style.left = '';\n panel.style.top = '';\n panel.style.zIndex = '';\n panel.style.minWidth = '';\n this.teardownOutsidePointer();\n }\n}\n"]}
@@ -6,6 +6,7 @@ export declare class TngMultiAutocompleteTrigger {
6
6
  private get listbox();
7
7
  private composing;
8
8
  private lastEmittedQuery;
9
+ private reopenOnNextInput;
9
10
  protected readonly dataSlot: "multi-autocomplete-trigger";
10
11
  protected readonly role: "combobox";
11
12
  protected readonly haspopup: "listbox";
@@ -1 +1 @@
1
- {"version":3,"file":"tng-multi-autocomplete.trigger.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.trigger.ts"],"names":[],"mappings":";AAeA,qBAIa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwD;IAC9E,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAwC;IAE3D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG3B;IAEL,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAuB;IAG/C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,4BAA4B,CAAU;IAGpE,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IAG9C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;IAGjD,SAAS,KAAK,YAAY,IAAI,MAAM,GAAG,OAAO,CAE7C;IAGD,SAAS,KAAK,YAAY,IAAI,MAAM,GAAG,IAAI,CAE1C;IAGD,SAAS,KAAK,YAAY,IAAI,MAAM,GAAG,IAAI,CAG1C;IAGD,SAAS,KAAK,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAGlD;IAGD,SAAS,CAAC,OAAO,IAAI,IAAI;IAiBzB,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAiBrC,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAqJ/C,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAoB7C,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAKpC,SAAS,CAAC,gBAAgB,IAAI,IAAI;yCAhQvB,2BAA2B;2CAA3B,2BAA2B;CA+QvC"}
1
+ {"version":3,"file":"tng-multi-autocomplete.trigger.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.trigger.ts"],"names":[],"mappings":";AAeA,qBAIa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwD;IAC9E,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAwC;IAE3D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG3B;IAEL,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,iBAAiB,CAAS;IAGlC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,4BAA4B,CAAU;IAGpE,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IAG9C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;IAGjD,SAAS,KAAK,YAAY,IAAI,MAAM,GAAG,OAAO,CAE7C;IAGD,SAAS,KAAK,YAAY,IAAI,MAAM,GAAG,IAAI,CAE1C;IAGD,SAAS,KAAK,YAAY,IAAI,MAAM,GAAG,IAAI,CAG1C;IAGD,SAAS,KAAK,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAGlD;IAGD,SAAS,CAAC,OAAO,IAAI,IAAI;IAmBzB,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAyBrC,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAwJ/C,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAsB7C,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAKpC,SAAS,CAAC,gBAAgB,IAAI,IAAI;yCAhRvB,2BAA2B;2CAA3B,2BAA2B;CA+RvC"}
@@ -14,6 +14,7 @@ export class TngMultiAutocompleteTrigger {
14
14
  }
15
15
  composing = false;
16
16
  lastEmittedQuery = null;
17
+ reopenOnNextInput = false;
17
18
  dataSlot = 'multi-autocomplete-trigger';
18
19
  role = 'combobox';
19
20
  haspopup = 'listbox';
@@ -36,6 +37,7 @@ export class TngMultiAutocompleteTrigger {
36
37
  onFocus() {
37
38
  if (this.multi.disabled())
38
39
  return;
40
+ this.reopenOnNextInput = false;
39
41
  if (!this.multi.open()) {
40
42
  this.multi.openSelect();
41
43
  const q = this.el.nativeElement.value ?? '';
@@ -49,6 +51,12 @@ export class TngMultiAutocompleteTrigger {
49
51
  if (this.multi.disabled())
50
52
  return;
51
53
  const value = event.target?.value ?? '';
54
+ // If Escape closed the overlay but the trigger remained focused, reopen on the next
55
+ // real input so filtering can continue without a blur/refocus cycle.
56
+ if (!this.multi.open() && this.reopenOnNextInput) {
57
+ this.reopenOnNextInput = false;
58
+ this.multi.openSelect();
59
+ }
52
60
  this.multi.query.set(value);
53
61
  // Policy A: do not emit during IME composition
54
62
  if (this.composing)
@@ -74,8 +82,12 @@ export class TngMultiAutocompleteTrigger {
74
82
  if (this.multi.open()) {
75
83
  event.preventDefault();
76
84
  event.stopPropagation();
85
+ this.reopenOnNextInput = true;
77
86
  this.multi.close();
78
87
  }
88
+ else {
89
+ this.reopenOnNextInput = false;
90
+ }
79
91
  return;
80
92
  }
81
93
  // Backspace removes last chip when input is empty
@@ -186,16 +198,17 @@ export class TngMultiAutocompleteTrigger {
186
198
  // printable keys are NOT prevented (input stays editable)
187
199
  }
188
200
  onFocusOut(event) {
201
+ this.reopenOnNextInput = false;
189
202
  if (!this.multi.open())
190
203
  return;
191
204
  const next = event.relatedTarget;
192
- if (next && this.multi.hostElement.contains(next)) {
205
+ if (next && this.multi.containsOwnedNode(next)) {
193
206
  return;
194
207
  }
195
208
  // Some focus transitions report null relatedTarget. Re-check after DOM focus settles.
196
209
  queueMicrotask(() => {
197
210
  const active = this.el.nativeElement.ownerDocument.activeElement;
198
- if (active && this.multi.hostElement.contains(active)) {
211
+ if (active && this.multi.containsOwnedNode(active)) {
199
212
  return;
200
213
  }
201
214
  this.multi.close();
@@ -1 +1 @@
1
- {"version":3,"file":"tng-multi-autocomplete.trigger.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.trigger.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n ElementRef,\n HostBinding,\n HostListener,\n inject,\n} from '@angular/core';\n\nimport type { TngMultiAutocomplete } from './tng-multi-autocomplete';\nimport { TNG_MULTI_AUTOCOMPLETE } from './tng-multi-autocomplete.tokens';\nimport { TNG_MULTI_AUTOCOMPLETE_LISTBOX } from './tng-multi-autocomplete.listbox.tokens';\nimport type { TngMultiAutocompleteListboxApi } from './tng-multi-autocomplete.listbox.types';\n\nconst NAV_KEYS = new Set(['ArrowDown', 'ArrowUp'] as const);\n\n@Directive({\n selector: '[tngMultiAutocompleteTrigger]',\n exportAs: 'tngMultiAutocompleteTrigger',\n})\nexport class TngMultiAutocompleteTrigger {\n private readonly multi = inject<TngMultiAutocomplete>(TNG_MULTI_AUTOCOMPLETE);\n private readonly el = inject(ElementRef<HTMLInputElement>);\n\n private readonly injectedListbox =\n inject<TngMultiAutocompleteListboxApi>(TNG_MULTI_AUTOCOMPLETE_LISTBOX, {\n optional: true,\n });\n\n private get listbox(): TngMultiAutocompleteListboxApi | null {\n return this.multi.getListboxApi() ?? this.injectedListbox ?? null;\n }\n\n private composing = false;\n private lastEmittedQuery: string | null = null;\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'multi-autocomplete-trigger' as const;\n\n @HostBinding('attr.role')\n protected readonly role = 'combobox' as const;\n\n @HostBinding('attr.aria-haspopup')\n protected readonly haspopup = 'listbox' as const;\n\n @HostBinding('attr.aria-expanded')\n protected get ariaExpanded(): 'true' | 'false' {\n return this.multi.open() ? 'true' : 'false';\n }\n\n @HostBinding('attr.aria-disabled')\n protected get ariaDisabled(): 'true' | null {\n return this.multi.disabled() ? 'true' : null;\n }\n\n @HostBinding('attr.aria-controls')\n protected get ariaControls(): string | null {\n if (!this.multi.open()) return null;\n return this.multi.getContentId() ?? this.multi.getListboxId();\n }\n\n @HostBinding('attr.aria-activedescendant')\n protected get ariaActiveDescendant(): string | null {\n if (!this.multi.open()) return null;\n return this.listbox?.activeId?.() ?? this.multi.getActiveDescendantId();\n }\n\n @HostListener('focus')\n protected onFocus(): void {\n if (this.multi.disabled()) return;\n\n if (!this.multi.open()) {\n this.multi.openSelect();\n\n const q = this.el.nativeElement.value ?? '';\n this.multi.query.set(q);\n\n this.lastEmittedQuery = q;\n this.multi.queryChange.emit(q);\n\n this.listbox?.ensureActive('first');\n }\n }\n\n @HostListener('input', ['$event'])\n protected onInput(event: Event): void {\n if (this.multi.disabled()) return;\n\n const value = (event.target as HTMLInputElement | null)?.value ?? '';\n this.multi.query.set(value);\n\n // Policy A: do not emit during IME composition\n if (this.composing) return;\n\n // Avoid double-emit (e.g., compositionend followed by input)\n if (this.lastEmittedQuery === value) return;\n\n this.lastEmittedQuery = value;\n this.multi.queryChange.emit(value);\n }\n\n @HostListener('keydown', ['$event'])\n protected onKeydown(event: KeyboardEvent): void {\n if (this.multi.disabled()) return;\n\n // Tab should leave the widget and close any open overlay.\n if (event.key === 'Tab') {\n if (this.multi.open()) {\n this.multi.close();\n }\n return;\n }\n\n // Escape closes overlay (no selection changes)\n if (event.key === 'Escape') {\n if (this.multi.open()) {\n event.preventDefault();\n event.stopPropagation();\n this.multi.close();\n }\n return;\n }\n\n // Backspace removes last chip when input is empty\n if (event.key === 'Backspace') {\n const inputValue = this.el.nativeElement.value ?? '';\n if (inputValue.length > 0) return;\n\n const selected = this.multi.value();\n if (selected.length === 0) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.multi.removeLast();\n return;\n }\n\n // Enter commits active option (toggle) and stays open\n if (event.key === 'Enter') {\n if (!this.multi.open()) return;\n if (!this.listbox) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.listbox.commitActive();\n return;\n }\n\n // ArrowLeft on input at caret-start → focus last chip (chips UX loop)\n if (event.key === 'ArrowLeft') {\n const input = this.el.nativeElement;\n\n const start = input.selectionStart ?? 0;\n const end = input.selectionEnd ?? 0;\n\n // Only when caret is at the beginning and there's no selection range.\n if (start === 0 && end === 0) {\n const chips = Array.from(\n this.multi.hostElement.querySelectorAll('[data-slot=\"multi-autocomplete-chip\"]'),\n ) as HTMLElement[];\n\n const lastChip = chips[chips.length - 1] ?? null;\n if (lastChip) {\n event.preventDefault();\n event.stopPropagation();\n lastChip.focus();\n }\n }\n\n return;\n }\n\n // Home / End\n // Home\n if (event.key === 'Home') {\n const input = this.el.nativeElement;\n const start = input.selectionStart ?? 0;\n const end = input.selectionEnd ?? 0;\n\n // Chip UX has priority over listbox navigation\n if (start === 0 && end === 0) {\n const chips = Array.from(\n this.multi.hostElement.querySelectorAll('[data-slot=\"multi-autocomplete-chip\"]'),\n ) as HTMLElement[];\n\n const firstChip = chips[0] ?? null;\n if (firstChip) {\n event.preventDefault();\n event.stopPropagation();\n firstChip.focus();\n return;\n }\n }\n\n // Otherwise delegate to listbox when open\n if (this.multi.open()) {\n const handled = this.listbox?.handleKey(event.key, event.shiftKey);\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n return;\n }\n\n // End\n if (event.key === 'End') {\n // When open → delegate to listbox\n if (this.multi.open()) {\n const handled = this.listbox?.handleKey(event.key, event.shiftKey);\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n return;\n }\n\n // When closed → let browser move caret\n return;\n }\n \n // Navigation keys\n if (NAV_KEYS.has(event.key as any)) {\n if (!this.multi.open()) {\n event.preventDefault();\n event.stopPropagation();\n\n this.multi.openSelect();\n if (event.key === 'ArrowUp' || event.key === 'End') {\n this.listbox?.ensureActive('last');\n } else {\n this.listbox?.ensureActive('first');\n }\n return;\n }\n\n if (this.listbox) {\n const handled = this.listbox.handleKey(event.key, event.shiftKey);\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n return;\n }\n\n // printable keys are NOT prevented (input stays editable)\n }\n\n @HostListener('focusout', ['$event'])\n protected onFocusOut(event: FocusEvent): void {\n if (!this.multi.open()) return;\n\n const next = event.relatedTarget as Node | null;\n if (next && this.multi.hostElement.contains(next)) {\n return;\n }\n\n // Some focus transitions report null relatedTarget. Re-check after DOM focus settles.\n queueMicrotask(() => {\n const active = this.el.nativeElement.ownerDocument.activeElement;\n if (active && this.multi.hostElement.contains(active)) {\n return;\n }\n\n this.multi.close();\n });\n }\n\n @HostListener('compositionstart')\n protected onCompositionStart(): void {\n this.composing = true;\n }\n\n @HostListener('compositionend')\n protected onCompositionEnd(): void {\n this.composing = false;\n\n if (this.multi.disabled()) return;\n\n // Policy A: emit once with the final committed value\n const q = this.el.nativeElement.value ?? '';\n this.multi.query.set(q);\n\n if (this.lastEmittedQuery !== q) {\n this.lastEmittedQuery = q;\n this.multi.queryChange.emit(q);\n }\n }\n\n}\n"]}
1
+ {"version":3,"file":"tng-multi-autocomplete.trigger.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/multi-autocomplete/tng-multi-autocomplete.trigger.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n ElementRef,\n HostBinding,\n HostListener,\n inject,\n} from '@angular/core';\n\nimport type { TngMultiAutocomplete } from './tng-multi-autocomplete';\nimport { TNG_MULTI_AUTOCOMPLETE } from './tng-multi-autocomplete.tokens';\nimport { TNG_MULTI_AUTOCOMPLETE_LISTBOX } from './tng-multi-autocomplete.listbox.tokens';\nimport type { TngMultiAutocompleteListboxApi } from './tng-multi-autocomplete.listbox.types';\n\nconst NAV_KEYS = new Set(['ArrowDown', 'ArrowUp'] as const);\n\n@Directive({\n selector: '[tngMultiAutocompleteTrigger]',\n exportAs: 'tngMultiAutocompleteTrigger',\n})\nexport class TngMultiAutocompleteTrigger {\n private readonly multi = inject<TngMultiAutocomplete>(TNG_MULTI_AUTOCOMPLETE);\n private readonly el = inject(ElementRef<HTMLInputElement>);\n\n private readonly injectedListbox =\n inject<TngMultiAutocompleteListboxApi>(TNG_MULTI_AUTOCOMPLETE_LISTBOX, {\n optional: true,\n });\n\n private get listbox(): TngMultiAutocompleteListboxApi | null {\n return this.multi.getListboxApi() ?? this.injectedListbox ?? null;\n }\n\n private composing = false;\n private lastEmittedQuery: string | null = null;\n private reopenOnNextInput = false;\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'multi-autocomplete-trigger' as const;\n\n @HostBinding('attr.role')\n protected readonly role = 'combobox' as const;\n\n @HostBinding('attr.aria-haspopup')\n protected readonly haspopup = 'listbox' as const;\n\n @HostBinding('attr.aria-expanded')\n protected get ariaExpanded(): 'true' | 'false' {\n return this.multi.open() ? 'true' : 'false';\n }\n\n @HostBinding('attr.aria-disabled')\n protected get ariaDisabled(): 'true' | null {\n return this.multi.disabled() ? 'true' : null;\n }\n\n @HostBinding('attr.aria-controls')\n protected get ariaControls(): string | null {\n if (!this.multi.open()) return null;\n return this.multi.getContentId() ?? this.multi.getListboxId();\n }\n\n @HostBinding('attr.aria-activedescendant')\n protected get ariaActiveDescendant(): string | null {\n if (!this.multi.open()) return null;\n return this.listbox?.activeId?.() ?? this.multi.getActiveDescendantId();\n }\n\n @HostListener('focus')\n protected onFocus(): void {\n if (this.multi.disabled()) return;\n\n this.reopenOnNextInput = false;\n\n if (!this.multi.open()) {\n this.multi.openSelect();\n\n const q = this.el.nativeElement.value ?? '';\n this.multi.query.set(q);\n\n this.lastEmittedQuery = q;\n this.multi.queryChange.emit(q);\n\n this.listbox?.ensureActive('first');\n }\n }\n\n @HostListener('input', ['$event'])\n protected onInput(event: Event): void {\n if (this.multi.disabled()) return;\n\n const value = (event.target as HTMLInputElement | null)?.value ?? '';\n\n // If Escape closed the overlay but the trigger remained focused, reopen on the next\n // real input so filtering can continue without a blur/refocus cycle.\n if (!this.multi.open() && this.reopenOnNextInput) {\n this.reopenOnNextInput = false;\n this.multi.openSelect();\n }\n\n this.multi.query.set(value);\n\n // Policy A: do not emit during IME composition\n if (this.composing) return;\n\n // Avoid double-emit (e.g., compositionend followed by input)\n if (this.lastEmittedQuery === value) return;\n\n this.lastEmittedQuery = value;\n this.multi.queryChange.emit(value);\n }\n\n @HostListener('keydown', ['$event'])\n protected onKeydown(event: KeyboardEvent): void {\n if (this.multi.disabled()) return;\n\n // Tab should leave the widget and close any open overlay.\n if (event.key === 'Tab') {\n if (this.multi.open()) {\n this.multi.close();\n }\n return;\n }\n\n // Escape closes overlay (no selection changes)\n if (event.key === 'Escape') {\n if (this.multi.open()) {\n event.preventDefault();\n event.stopPropagation();\n this.reopenOnNextInput = true;\n this.multi.close();\n } else {\n this.reopenOnNextInput = false;\n }\n return;\n }\n\n // Backspace removes last chip when input is empty\n if (event.key === 'Backspace') {\n const inputValue = this.el.nativeElement.value ?? '';\n if (inputValue.length > 0) return;\n\n const selected = this.multi.value();\n if (selected.length === 0) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.multi.removeLast();\n return;\n }\n\n // Enter commits active option (toggle) and stays open\n if (event.key === 'Enter') {\n if (!this.multi.open()) return;\n if (!this.listbox) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.listbox.commitActive();\n return;\n }\n\n // ArrowLeft on input at caret-start → focus last chip (chips UX loop)\n if (event.key === 'ArrowLeft') {\n const input = this.el.nativeElement;\n\n const start = input.selectionStart ?? 0;\n const end = input.selectionEnd ?? 0;\n\n // Only when caret is at the beginning and there's no selection range.\n if (start === 0 && end === 0) {\n const chips = Array.from(\n this.multi.hostElement.querySelectorAll('[data-slot=\"multi-autocomplete-chip\"]'),\n ) as HTMLElement[];\n\n const lastChip = chips[chips.length - 1] ?? null;\n if (lastChip) {\n event.preventDefault();\n event.stopPropagation();\n lastChip.focus();\n }\n }\n\n return;\n }\n\n // Home / End\n // Home\n if (event.key === 'Home') {\n const input = this.el.nativeElement;\n const start = input.selectionStart ?? 0;\n const end = input.selectionEnd ?? 0;\n\n // Chip UX has priority over listbox navigation\n if (start === 0 && end === 0) {\n const chips = Array.from(\n this.multi.hostElement.querySelectorAll('[data-slot=\"multi-autocomplete-chip\"]'),\n ) as HTMLElement[];\n\n const firstChip = chips[0] ?? null;\n if (firstChip) {\n event.preventDefault();\n event.stopPropagation();\n firstChip.focus();\n return;\n }\n }\n\n // Otherwise delegate to listbox when open\n if (this.multi.open()) {\n const handled = this.listbox?.handleKey(event.key, event.shiftKey);\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n return;\n }\n\n // End\n if (event.key === 'End') {\n // When open → delegate to listbox\n if (this.multi.open()) {\n const handled = this.listbox?.handleKey(event.key, event.shiftKey);\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n return;\n }\n\n // When closed → let browser move caret\n return;\n }\n \n // Navigation keys\n if (NAV_KEYS.has(event.key as any)) {\n if (!this.multi.open()) {\n event.preventDefault();\n event.stopPropagation();\n\n this.multi.openSelect();\n if (event.key === 'ArrowUp' || event.key === 'End') {\n this.listbox?.ensureActive('last');\n } else {\n this.listbox?.ensureActive('first');\n }\n return;\n }\n\n if (this.listbox) {\n const handled = this.listbox.handleKey(event.key, event.shiftKey);\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n return;\n }\n\n // printable keys are NOT prevented (input stays editable)\n }\n\n @HostListener('focusout', ['$event'])\n protected onFocusOut(event: FocusEvent): void {\n this.reopenOnNextInput = false;\n\n if (!this.multi.open()) return;\n\n const next = event.relatedTarget as Node | null;\n if (next && this.multi.containsOwnedNode(next)) {\n return;\n }\n\n // Some focus transitions report null relatedTarget. Re-check after DOM focus settles.\n queueMicrotask(() => {\n const active = this.el.nativeElement.ownerDocument.activeElement;\n if (active && this.multi.containsOwnedNode(active)) {\n return;\n }\n\n this.multi.close();\n });\n }\n\n @HostListener('compositionstart')\n protected onCompositionStart(): void {\n this.composing = true;\n }\n\n @HostListener('compositionend')\n protected onCompositionEnd(): void {\n this.composing = false;\n\n if (this.multi.disabled()) return;\n\n // Policy A: emit once with the final committed value\n const q = this.el.nativeElement.value ?? '';\n this.multi.query.set(q);\n\n if (this.lastEmittedQuery !== q) {\n this.lastEmittedQuery = q;\n this.multi.queryChange.emit(q);\n }\n }\n\n}\n"]}