@quadrel-enterprise-ui/framework 20.6.1 → 20.6.2

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.
@@ -6855,185 +6855,230 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
6855
6855
  }] });
6856
6856
 
6857
6857
  /**
6858
- * Service for handling real-time server-sent events.
6858
+ * Manages a server-sent event (SSE) connection with automatic reconnection and heartbeat monitoring.
6859
6859
  *
6860
- * ### Benefits
6860
+ * All service endpoints — including SSE — must be protected. The connection is therefore secured
6861
+ * via Quadrel Auth (Bearer token) by default. When the access token refreshes, the connection is
6862
+ * transparently rebuilt. Only disable authentication for endpoints that are explicitly public.
6861
6863
  *
6862
- * - **Real-Time Interaction**: Enables live updates without user actions or polling.
6863
- * - **Scalable and Flexible**: The system is flexible and easily expandable to handle new events.
6864
- * - **Consistency**: Ensures synchronization between server and client states for a consistent user experience.
6865
- * - **Authorization**: It uses the user's authentication (QdAuth) to secure the connection against the backend out-of-the-box. This requires a proper QdAuth setup. You can disable this feature when starting the connection.
6864
+ * ### Reconnection behavior
6865
+ *
6866
+ * - **Heartbeat timeout**: If no HEARTBEAT event arrives within the expected interval, the service reconnects.
6867
+ * - **401 Unauthorized**: Triggers exponential-backoff reconnection (max 5 attempts, capped at 30 s).
6868
+ * - **Other errors**: Reconnects immediately when the EventSource transitions to CLOSED.
6869
+ *
6870
+ * Existing `observe()` subscriptions are preserved across reconnections.
6866
6871
  *
6867
6872
  * ### Usage
6868
6873
  *
6869
6874
  * ```ts
6870
- * // Start the connection to the events
6871
- * pushEventsService.connect('http://service-endpint/events');
6872
- * // or start without authentication in case you don't need:
6873
- * pushEventsService.connect('http://service-endpint/events', { disableAuthentication: true });
6874
- *
6875
- * // Subscribe to the event (Side Effect)
6876
- * const subscription = pushEventsService.observe('RESOURCE_UPDATED').subscribe(event => handleEvent(event));
6875
+ * pushEventsService.connect('https://api.example.com/events');
6877
6876
  *
6878
- * // Unsubscribe to prevent memory leaks
6879
- * subscription.unsubscribe();
6877
+ * const sub = pushEventsService.observe('RESOURCE_UPDATED').subscribe(event => handleEvent(event));
6880
6878
  *
6881
- * // Disconnect the connection
6879
+ * sub.unsubscribe();
6882
6880
  * pushEventsService.disconnect();
6883
6881
  * ```
6884
6882
  */
6885
6883
  class QdPushEventsService {
6886
6884
  authenticationService = inject('QdAuthenticationService', { optional: true });
6887
6885
  _eventSource;
6888
- _eventSubscriptionSubjects = new Map();
6886
+ _subjects = new Map();
6889
6887
  _listeners = [];
6890
- _heartbeatTimeout;
6891
- _reconnectDelayTime = 10000;
6892
- _heartbeatReconnectDelayTime = 100;
6893
6888
  _accessTokenSub;
6894
6889
  _options;
6890
+ _heartbeatTimeout;
6891
+ _heartbeatGracePeriod = 100;
6892
+ _initialHeartbeatTimeout = 10000;
6895
6893
  _isUnauthorized = false;
6894
+ _reconnectAttempts = 0;
6895
+ _maxReconnectAttempts = 5;
6896
+ _backoffTimer;
6896
6897
  /**
6897
- * Establishes an EventSource connection to the given URL.
6898
- * Automatically reconnects if heartbeat fails or is delayed.
6899
- * Subscribers are retained across reconnections.
6898
+ * Opens an SSE connection to the given URL.
6900
6899
  *
6901
- * @param url The backend URL for the event stream.
6902
- * @param options Options for the connection
6900
+ * With authentication enabled (default), the service subscribes to the `accessToken$` observable
6901
+ * provided by Quadrel Auth and rebuilds the connection whenever the token changes.
6902
+ * No-op if already connected.
6903
+ *
6904
+ * @param url The SSE endpoint URL.
6905
+ * @param options Set `{ disableAuthentication: true }` only for explicitly public endpoints.
6906
+ *
6907
+ * @example
6908
+ * ```ts
6909
+ * // Authenticated (default — requires Quadrel Auth)
6910
+ * pushEventsService.connect('https://api.example.com/events');
6911
+ *
6912
+ * // Without authentication (only for public endpoints)
6913
+ * pushEventsService.connect('https://api.example.com/public-events', { disableAuthentication: true });
6914
+ * ```
6903
6915
  */
6904
6916
  connect(url, options) {
6905
6917
  if (this.isConnectedOrConnecting())
6906
6918
  return;
6907
- if (!options?.disableAuthentication) {
6908
- if (!this.authenticationService) {
6909
- this.logError("Can't connect to SSE without QdAuth as the connection has to be secured. Please install QdAuth or disable authentication when connecting.");
6910
- return;
6911
- }
6912
- if (this._accessTokenSub)
6913
- this._accessTokenSub.unsubscribe();
6914
- this._accessTokenSub = this.authenticationService.accessToken$.subscribe((token) => {
6915
- this._options = { url, options };
6916
- this.connectEventSource(url, {
6917
- headers: { Authorization: `Bearer ${token}` }
6918
- }, true);
6919
- });
6919
+ this._options = { url, options };
6920
+ if (options?.disableAuthentication) {
6921
+ this.openEventSource(url);
6920
6922
  }
6921
6923
  else {
6922
- this._options = { url, options };
6923
- this.connectEventSource(url);
6924
+ this.connectWithAuth(url);
6924
6925
  }
6925
6926
  }
6926
6927
  /**
6927
- * Closes the EventSource connection and clears all listeners.
6928
- * Subscribers are preserved for reconnection.
6928
+ * Closes the EventSource connection, clears all event listeners, and cancels pending backoff timers.
6929
+ * Subscription subjects are preserved so that a subsequent `connect()` re-attaches them.
6930
+ *
6931
+ * @example
6932
+ * ```ts
6933
+ * pushEventsService.disconnect();
6934
+ * // Subjects survive — a new connect() will re-attach existing observe() subscriptions.
6935
+ * ```
6929
6936
  */
6930
6937
  disconnect() {
6931
6938
  if (!this._eventSource) {
6932
6939
  this.logWarn('No active connection to disconnect.');
6933
6940
  return;
6934
6941
  }
6935
- this.removeAllEventListenersFromEventSource();
6942
+ if (this._accessTokenSub) {
6943
+ this._accessTokenSub.unsubscribe();
6944
+ this._accessTokenSub = undefined;
6945
+ }
6946
+ if (this._heartbeatTimeout)
6947
+ clearTimeout(this._heartbeatTimeout);
6948
+ if (this._backoffTimer) {
6949
+ clearTimeout(this._backoffTimer);
6950
+ this._backoffTimer = undefined;
6951
+ }
6952
+ this.removeAllListeners();
6936
6953
  this._eventSource.close();
6937
6954
  }
6938
6955
  /**
6939
- * Returns an Observable for the specified event name.
6940
- * If not connected, returns `NEVER` and logs an error.
6941
- * Automatically adds a listener for the event if needed.
6956
+ * Returns an Observable that emits whenever the server sends an event of the given type.
6957
+ *
6958
+ * Lazily registers an EventSource listener on first call per event name.
6959
+ * Returns `NEVER` and logs an error if no connection exists.
6942
6960
  *
6943
- * @param eventName The event type ('RESOURCE_CREATED', 'RESOURCE_UPDATED', 'RESOURCE_DELETED').
6944
- * @returns Observable<MessageEvent> The event stream.
6961
+ * @param eventName The SSE event type to listen for.
6962
+ *
6963
+ * @example
6964
+ * ```ts
6965
+ * pushEventsService.observe('RESOURCE_UPDATED').subscribe(event => handleUpdate(event));
6966
+ * ```
6945
6967
  */
6946
6968
  observe(eventName) {
6947
6969
  if (!this._eventSource) {
6948
- this.logError('Cannot observe events without a connection. Call connect() first.');
6970
+ this.logWarn('Cannot observe events without a connection. Call connect() first.');
6949
6971
  return NEVER;
6950
6972
  }
6951
- if (!this._eventSubscriptionSubjects.has(eventName)) {
6952
- this._eventSubscriptionSubjects.set(eventName, new Subject());
6953
- this.addEventListenerForEventName(eventName);
6973
+ if (!this._subjects.has(eventName)) {
6974
+ this._subjects.set(eventName, new Subject());
6975
+ this.addListener(eventName, (event) => this._subjects.get(eventName).next(event));
6954
6976
  }
6955
- return this._eventSubscriptionSubjects.get(eventName).asObservable();
6977
+ return this._subjects.get(eventName).asObservable();
6956
6978
  }
6957
6979
  /**
6958
- * Removes all listeners and clears all subscriptions.
6959
- * The EventSource connection remains open.
6980
+ * Removes all event listeners and clears all subscription subjects. The connection stays open.
6981
+ * Use this to reset all subscriptions without disconnecting the SSE stream.
6982
+ *
6983
+ * @example
6984
+ * ```ts
6985
+ * pushEventsService.unobserveAll();
6986
+ * // Connection remains open, but no events are forwarded until observe() is called again.
6987
+ * ```
6960
6988
  */
6961
6989
  unobserveAll() {
6962
- this.removeAllEventListenersFromEventSource();
6963
- this._eventSubscriptionSubjects.clear();
6990
+ this.removeAllListeners();
6991
+ this._subjects.clear();
6964
6992
  }
6965
- /**
6966
- * Checks if the EventSource is connected or in the process of connecting.
6967
- */
6993
+ /** Returns `true` when the EventSource is in state OPEN or CONNECTING. */
6968
6994
  isConnectedOrConnecting() {
6969
6995
  return (this._eventSource &&
6970
6996
  (this._eventSource.readyState === EventSource.OPEN || this._eventSource.readyState === EventSource.CONNECTING));
6971
6997
  }
6972
- reconnect() {
6973
- if (this.isConnectedOrConnecting())
6998
+ connectWithAuth(url) {
6999
+ if (!this.authenticationService) {
7000
+ this.logError("Can't connect to SSE without Quadrel Auth as the connection has to be secured. Please install Quadrel Auth or disable authentication when connecting.");
6974
7001
  return;
6975
- if (this._isUnauthorized)
7002
+ }
7003
+ this._accessTokenSub = this.authenticationService.accessToken$.subscribe((token) => {
7004
+ this.openEventSource(url, { headers: { Authorization: `Bearer ${token}` } });
7005
+ });
7006
+ }
7007
+ openEventSource(url, options = {}) {
7008
+ this._isUnauthorized = false;
7009
+ if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED)
7010
+ this._eventSource.close();
7011
+ this._eventSource = new EventSourcePolyfill(url, options);
7012
+ this._eventSource.onerror = (err) => this.handleError(err);
7013
+ this._eventSource.addEventListener('HEARTBEAT', (message) => this.handleHeartbeat(message));
7014
+ if (this._heartbeatTimeout)
7015
+ clearTimeout(this._heartbeatTimeout);
7016
+ this._heartbeatTimeout = setTimeout(() => this.reconnect(), this._initialHeartbeatTimeout);
7017
+ if (this._backoffTimer) {
7018
+ clearTimeout(this._backoffTimer);
7019
+ this._backoffTimer = undefined;
7020
+ }
7021
+ this._listeners = [];
7022
+ this.reattachListeners();
7023
+ }
7024
+ reconnect() {
7025
+ if (this.isConnectedOrConnecting() || this._isUnauthorized)
6976
7026
  return;
6977
7027
  this.disconnect();
6978
7028
  this.connect(this._options.url, this._options.options);
6979
7029
  }
6980
- addEventListenerForEventName(eventName) {
6981
- const callback = (messageEvent) => {
6982
- const subject = this._eventSubscriptionSubjects.get(eventName);
6983
- if (subject)
6984
- subject.next(messageEvent);
6985
- };
6986
- this._eventSource.addEventListener(eventName, callback);
6987
- this._listeners.push([eventName, callback]);
7030
+ handleError(err) {
7031
+ const status = err?.status || err?.target?.status;
7032
+ if (status === 401) {
7033
+ this._isUnauthorized = true;
7034
+ this.logError('SSE connection unauthorized (401):', err);
7035
+ this._eventSource.close();
7036
+ this.scheduleRetry();
7037
+ return;
7038
+ }
7039
+ if (this._eventSource.readyState === EventSource.CLOSED)
7040
+ this.reconnect();
7041
+ this.logError('SSE connection error:', err);
6988
7042
  }
6989
- addEventListenersForExistingSubscriptions() {
6990
- this._eventSubscriptionSubjects.forEach((subject, eventName) => this.addEventListenerToEventSource(eventName, (messageEvent) => subject.next(messageEvent)));
7043
+ scheduleRetry() {
7044
+ if (this._reconnectAttempts >= this._maxReconnectAttempts) {
7045
+ this.logError(`SSE reconnect aborted after ${this._maxReconnectAttempts} failed attempts.`);
7046
+ return;
7047
+ }
7048
+ const delay = Math.min(1000 * Math.pow(2, this._reconnectAttempts), 30000);
7049
+ this._reconnectAttempts++;
7050
+ this._backoffTimer = setTimeout(() => {
7051
+ this._isUnauthorized = false;
7052
+ this.reconnect();
7053
+ }, delay);
7054
+ }
7055
+ handleHeartbeat(message) {
7056
+ if (this._isUnauthorized)
7057
+ return;
7058
+ if (this._heartbeatTimeout)
7059
+ clearTimeout(this._heartbeatTimeout);
7060
+ this._reconnectAttempts = 0;
7061
+ const interval = JSON.parse(message.data).interval;
7062
+ this._heartbeatTimeout = setTimeout(() => this.reconnect(), interval + this._heartbeatGracePeriod);
6991
7063
  }
6992
- addEventListenerToEventSource(eventName, callback) {
7064
+ addListener(eventName, callback) {
6993
7065
  this._eventSource.addEventListener(eventName, callback);
6994
7066
  this._listeners.push([eventName, callback]);
6995
7067
  }
6996
- removeAllEventListenersFromEventSource() {
6997
- if (!this._eventSource) {
6998
- this.logWarn('Cannot remove listeners: No active connection.');
7068
+ reattachListeners() {
7069
+ this._subjects.forEach((subject, eventName) => this.addListener(eventName, (event) => subject.next(event)));
7070
+ }
7071
+ removeAllListeners() {
7072
+ if (!this._eventSource)
6999
7073
  return;
7000
- }
7001
7074
  this._listeners.forEach(([eventName, callback]) => this._eventSource.removeEventListener(eventName, callback));
7002
7075
  this._listeners = [];
7003
7076
  }
7004
7077
  logWarn(message) {
7005
- console.warn(`QD-UI | QdPushEventsService - ${message}`);
7078
+ console.warn(`QD-UI | QdPushEvents - ${message}`);
7006
7079
  }
7007
7080
  logError(message, err) {
7008
- console.error(`QD-UI | QdPushEventsService - ${message}`, err);
7009
- }
7010
- connectEventSource(url, options = {}, reconnect) {
7011
- if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED)
7012
- this._eventSource.close();
7013
- if (!this._eventSource || this._eventSource.readyState === EventSource.CLOSED || reconnect)
7014
- this._eventSource = new EventSourcePolyfill(url, options);
7015
- this._eventSource.onerror = (err) => {
7016
- const status = err?.status || err?.target?.status;
7017
- if (status === 401) {
7018
- this._isUnauthorized = true;
7019
- this.logError('SSE connection unauthorized (401):', err);
7020
- this._eventSource.close();
7021
- return;
7022
- }
7023
- if (this._eventSource.readyState === EventSource.CLOSED)
7024
- this.reconnect();
7025
- this.logError('SSE connection error:', err);
7026
- };
7027
- this._eventSource.addEventListener('HEARTBEAT', (message) => {
7028
- if (this._isUnauthorized)
7029
- return;
7030
- if (this._heartbeatTimeout)
7031
- clearTimeout(this._heartbeatTimeout);
7032
- const interval = JSON.parse(message.data).interval;
7033
- this._heartbeatTimeout = setTimeout(() => this.reconnect(), interval + this._heartbeatReconnectDelayTime);
7034
- });
7035
- this._heartbeatTimeout = setTimeout(() => this.reconnect(), this._reconnectDelayTime);
7036
- this.addEventListenersForExistingSubscriptions();
7081
+ console.error(`QD-UI | QdPushEvents - ${message}`, err);
7037
7082
  }
7038
7083
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPushEventsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7039
7084
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPushEventsService, providedIn: 'root' });
@@ -20736,11 +20781,11 @@ class QdFilterItemSelectCategoryComponent {
20736
20781
  }));
20737
20782
  }
20738
20783
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFilterItemSelectCategoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20739
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFilterItemSelectCategoryComponent, isStandalone: false, selector: "qd-filter-category-select", inputs: { filterId: "filterId", categoryIndex: "categoryIndex", testId: ["data-test-id", "testId"] }, host: { classAttribute: "qd-filter__category-select" }, viewQueries: [{ propertyName: "popoverDirective", first: true, predicate: QdPopoverOnClickDirective, descendants: true }], ngImport: i0, template: "<div\n *ngIf=\"showSelectButton$ | async\"\n [class]=\"buttonClassName\"\n [qdPopoverOnClick]=\"filterLayer\"\n [qdPopoverCloseStrategy]=\"'onOutsideClick'\"\n [qdPopoverStopPropagation]=\"true\"\n [qdPopoverMaxHeight]=\"maxFlyoutHeight\"\n [positionStrategy]=\"positionStrategy\"\n (opened)=\"onLayerOpened()\"\n (closed)=\"onLayerClosed()\"\n [attr.data-test-id]=\"testId + '-select-button'\"\n>\n <qd-icon\n *ngIf=\"!open\"\n [icon]=\"'ctrlDown'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--closed'\"\n [attr.data-test-id]=\"testId + '-closed'\"\n ></qd-icon>\n <qd-icon\n *ngIf=\"open\"\n [icon]=\"'ctrlTop'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--open'\"\n [attr.data-test-id]=\"testId + '-opened'\"\n ></qd-icon>\n\n <div class=\"qd-filter__category-button-caption\">\n <span>\n {{ i18n | translate }}\n </span>\n </div>\n\n <ng-container *ngTemplateOutlet=\"SelectedChip\"></ng-container>\n</div>\n\n<ng-template #filterLayer>\n <div [class]=\"layerContentClassName\">\n <qd-icon\n *ngIf=\"closeButton\"\n [class]=\"'qd-filter__category-layer-close'\"\n (click)=\"closeLayer()\"\n [icon]=\"'timesLargeLight'\"\n [attr.data-test-id]=\"testId + '-layer-close'\"\n ></qd-icon>\n\n <ng-container [ngSwitch]=\"type\">\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-multi-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-multi-select>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-single-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n (closeEventEmitter)=\"closeLayer()\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-single-select>\n </ng-container>\n </div>\n </ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #SelectedChip>\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <!-- TODO: Add tooltip-->\n <qd-chip\n *ngIf=\"item.active\"\n [state]=\"'filter'\"\n [close]=\"true\"\n (closeClickEmitter)=\"close($event)\"\n [data]=\"itemIndex\"\n [data-test-id]=\"testId + '-selected-chip-' + item.item\"\n >{{ item.i18n | translate }}</qd-chip\n >\n </ng-container>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { 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: "component", type: QdChipComponent, selector: "qd-chip", inputs: ["state", "close", "data", "data-test-id"], outputs: ["closeClickEmitter"] }, { kind: "component", type: QdFilterFormItemsComponent, selector: "qd-filter-form-items", inputs: ["inputFilterValue", "data-test-id"], outputs: ["filterValueChange"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }, { kind: "directive", type: QdPopoverOnClickDirective, selector: "[qdPopoverOnClick]", inputs: ["qdPopoverOnClick", "positionStrategy", "qdPopoverCloseStrategy", "qdPopoverDisabled", "qdPopoverStopPropagation", "qdPopoverBackgroundColor", "qdPopoverMaxHeight", "qdPopoverMinWidth", "qdPopoverMaxWidth", "qdPopoverAutoSize", "qdPopoverEnableKeyControl"], outputs: ["opened", "closed"], exportAs: ["qdPopoverOnClick"] }, { kind: "component", type: QdFilterItemMultiSelectComponent, selector: "qd-filter-item-multi-select", inputs: ["filterId", "categoryIndex", "itemIndex", "data-test-id"] }, { kind: "component", type: QdFilterItemSingleSelectComponent, selector: "qd-filter-item-single-select", inputs: ["filterId", "categoryIndex", "itemIndex", "data-test-id"], outputs: ["closeEventEmitter"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20784
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFilterItemSelectCategoryComponent, isStandalone: false, selector: "qd-filter-category-select", inputs: { filterId: "filterId", categoryIndex: "categoryIndex", testId: ["data-test-id", "testId"] }, host: { classAttribute: "qd-filter__category-select" }, viewQueries: [{ propertyName: "popoverDirective", first: true, predicate: QdPopoverOnClickDirective, descendants: true }], ngImport: i0, template: "<div\n *ngIf=\"showSelectButton$ | async\"\n [class]=\"buttonClassName\"\n [qdPopoverOnClick]=\"filterLayer\"\n [qdPopoverCloseStrategy]=\"'onOutsideClick'\"\n [qdPopoverStopPropagation]=\"true\"\n [qdPopoverMaxHeight]=\"maxFlyoutHeight\"\n [positionStrategy]=\"positionStrategy\"\n (opened)=\"onLayerOpened()\"\n (closed)=\"onLayerClosed()\"\n [attr.data-test-id]=\"testId + '-select-button'\"\n>\n <qd-icon\n *ngIf=\"!open\"\n [icon]=\"'ctrlDown'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--closed'\"\n [attr.data-test-id]=\"testId + '-closed'\"\n ></qd-icon>\n <qd-icon\n *ngIf=\"open\"\n [icon]=\"'ctrlTop'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--open'\"\n [attr.data-test-id]=\"testId + '-opened'\"\n ></qd-icon>\n\n <div class=\"qd-filter__category-button-caption\">\n <span>\n {{ i18n | translate }}\n </span>\n </div>\n\n <ng-container *ngTemplateOutlet=\"SelectedChip\"></ng-container>\n</div>\n\n<ng-template #filterLayer>\n <div [class]=\"layerContentClassName\">\n <qd-icon\n *ngIf=\"closeButton\"\n [class]=\"'qd-filter__category-layer-close'\"\n (click)=\"closeLayer()\"\n [icon]=\"'timesLargeLight'\"\n [attr.data-test-id]=\"testId + '-layer-close'\"\n ></qd-icon>\n\n <ng-container [ngSwitch]=\"type\">\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-multi-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-multi-select>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-single-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n (closeEventEmitter)=\"closeLayer()\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-single-select>\n </ng-container>\n </div>\n </ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #SelectedChip>\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <!-- TODO: Add tooltip-->\n <qd-chip\n *ngIf=\"item.active\"\n [state]=\"'filter'\"\n [close]=\"true\"\n (closeClickEmitter)=\"close($event)\"\n [data]=\"itemIndex\"\n [data-test-id]=\"testId + '-selected-chip-' + item.item\"\n >{{ item.i18n | translate }}</qd-chip\n >\n </ng-container>\n</ng-template>\n", styles: ["::ng-deep .qd-filter__category-layer{display:flex;overflow:hidden;min-width:16.25rem;max-width:56.25rem;height:100%;flex-direction:column;padding:1rem 0 0;border:.0625rem solid rgb(151,151,151);background:#fff;box-shadow:#d5d5d5 .125rem .25rem .4375rem}::ng-deep .qd-filter__category-layer .qd-filter-form-items__filter{flex-shrink:0;margin-bottom:0}::ng-deep .qd-filter__category-layer .qd-checkbox__label{display:flex;height:2.5rem}::ng-deep .qd-filter__category-layer .qd-checkbox__indicator,::ng-deep .qd-filter__category-layer .qd-radio-buttons__indicator{transform:translateY(-.0625rem)}::ng-deep .qd-filter__category-layer-items{flex:1;overflow-y:auto}::ng-deep .qd-filter__category-layer-container{position:relative}::ng-deep .qd-filter__category-layer-close{position:absolute;z-index:1;top:1rem;right:1rem;color:#757575;cursor:pointer}::ng-deep .qd-filter__category-layer-close:before{position:absolute;content:\"\";inset:-.5rem}::ng-deep .qd-filter__category-layer-close:hover,::ng-deep .qd-filter__category-layer-close:focus{color:#171717}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { 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: "component", type: QdChipComponent, selector: "qd-chip", inputs: ["state", "close", "data", "data-test-id"], outputs: ["closeClickEmitter"] }, { kind: "component", type: QdFilterFormItemsComponent, selector: "qd-filter-form-items", inputs: ["inputFilterValue", "data-test-id"], outputs: ["filterValueChange"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }, { kind: "directive", type: QdPopoverOnClickDirective, selector: "[qdPopoverOnClick]", inputs: ["qdPopoverOnClick", "positionStrategy", "qdPopoverCloseStrategy", "qdPopoverDisabled", "qdPopoverStopPropagation", "qdPopoverBackgroundColor", "qdPopoverMaxHeight", "qdPopoverMinWidth", "qdPopoverMaxWidth", "qdPopoverAutoSize", "qdPopoverEnableKeyControl"], outputs: ["opened", "closed"], exportAs: ["qdPopoverOnClick"] }, { kind: "component", type: QdFilterItemMultiSelectComponent, selector: "qd-filter-item-multi-select", inputs: ["filterId", "categoryIndex", "itemIndex", "data-test-id"] }, { kind: "component", type: QdFilterItemSingleSelectComponent, selector: "qd-filter-item-single-select", inputs: ["filterId", "categoryIndex", "itemIndex", "data-test-id"], outputs: ["closeEventEmitter"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20740
20785
  }
20741
20786
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFilterItemSelectCategoryComponent, decorators: [{
20742
20787
  type: Component,
20743
- args: [{ selector: 'qd-filter-category-select', host: { class: 'qd-filter__category-select' }, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div\n *ngIf=\"showSelectButton$ | async\"\n [class]=\"buttonClassName\"\n [qdPopoverOnClick]=\"filterLayer\"\n [qdPopoverCloseStrategy]=\"'onOutsideClick'\"\n [qdPopoverStopPropagation]=\"true\"\n [qdPopoverMaxHeight]=\"maxFlyoutHeight\"\n [positionStrategy]=\"positionStrategy\"\n (opened)=\"onLayerOpened()\"\n (closed)=\"onLayerClosed()\"\n [attr.data-test-id]=\"testId + '-select-button'\"\n>\n <qd-icon\n *ngIf=\"!open\"\n [icon]=\"'ctrlDown'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--closed'\"\n [attr.data-test-id]=\"testId + '-closed'\"\n ></qd-icon>\n <qd-icon\n *ngIf=\"open\"\n [icon]=\"'ctrlTop'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--open'\"\n [attr.data-test-id]=\"testId + '-opened'\"\n ></qd-icon>\n\n <div class=\"qd-filter__category-button-caption\">\n <span>\n {{ i18n | translate }}\n </span>\n </div>\n\n <ng-container *ngTemplateOutlet=\"SelectedChip\"></ng-container>\n</div>\n\n<ng-template #filterLayer>\n <div [class]=\"layerContentClassName\">\n <qd-icon\n *ngIf=\"closeButton\"\n [class]=\"'qd-filter__category-layer-close'\"\n (click)=\"closeLayer()\"\n [icon]=\"'timesLargeLight'\"\n [attr.data-test-id]=\"testId + '-layer-close'\"\n ></qd-icon>\n\n <ng-container [ngSwitch]=\"type\">\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-multi-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-multi-select>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-single-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n (closeEventEmitter)=\"closeLayer()\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-single-select>\n </ng-container>\n </div>\n </ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #SelectedChip>\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <!-- TODO: Add tooltip-->\n <qd-chip\n *ngIf=\"item.active\"\n [state]=\"'filter'\"\n [close]=\"true\"\n (closeClickEmitter)=\"close($event)\"\n [data]=\"itemIndex\"\n [data-test-id]=\"testId + '-selected-chip-' + item.item\"\n >{{ item.i18n | translate }}</qd-chip\n >\n </ng-container>\n</ng-template>\n" }]
20788
+ args: [{ selector: 'qd-filter-category-select', host: { class: 'qd-filter__category-select' }, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div\n *ngIf=\"showSelectButton$ | async\"\n [class]=\"buttonClassName\"\n [qdPopoverOnClick]=\"filterLayer\"\n [qdPopoverCloseStrategy]=\"'onOutsideClick'\"\n [qdPopoverStopPropagation]=\"true\"\n [qdPopoverMaxHeight]=\"maxFlyoutHeight\"\n [positionStrategy]=\"positionStrategy\"\n (opened)=\"onLayerOpened()\"\n (closed)=\"onLayerClosed()\"\n [attr.data-test-id]=\"testId + '-select-button'\"\n>\n <qd-icon\n *ngIf=\"!open\"\n [icon]=\"'ctrlDown'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--closed'\"\n [attr.data-test-id]=\"testId + '-closed'\"\n ></qd-icon>\n <qd-icon\n *ngIf=\"open\"\n [icon]=\"'ctrlTop'\"\n [class]=\"'qd-filter__category-icon qd-filter__category-icon--open'\"\n [attr.data-test-id]=\"testId + '-opened'\"\n ></qd-icon>\n\n <div class=\"qd-filter__category-button-caption\">\n <span>\n {{ i18n | translate }}\n </span>\n </div>\n\n <ng-container *ngTemplateOutlet=\"SelectedChip\"></ng-container>\n</div>\n\n<ng-template #filterLayer>\n <div [class]=\"layerContentClassName\">\n <qd-icon\n *ngIf=\"closeButton\"\n [class]=\"'qd-filter__category-layer-close'\"\n (click)=\"closeLayer()\"\n [icon]=\"'timesLargeLight'\"\n [attr.data-test-id]=\"testId + '-layer-close'\"\n ></qd-icon>\n\n <ng-container [ngSwitch]=\"type\">\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-multi-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-multi-select>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <qd-filter-form-items\n *ngIf=\"filter\"\n [inputFilterValue]=\"filterCategoryValue$ | async\"\n (filterValueChange)=\"changeValue($event)\"\n [data-test-id]=\"testId\"\n ></qd-filter-form-items>\n\n <div class=\"qd-filter__category-layer-items\">\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <qd-filter-item-single-select\n *ngIf=\"!item.hidden\"\n [categoryIndex]=\"categoryIndex\"\n [itemIndex]=\"itemIndex\"\n (closeEventEmitter)=\"closeLayer()\"\n [filterId]=\"filterId\"\n [data-test-id]=\"testId + '-item-' + itemIndex + '-' + item.item\"\n >\n </qd-filter-item-single-select>\n </ng-container>\n </div>\n </ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #SelectedChip>\n <ng-container *ngFor=\"let item of items; let itemIndex = index\">\n <!-- TODO: Add tooltip-->\n <qd-chip\n *ngIf=\"item.active\"\n [state]=\"'filter'\"\n [close]=\"true\"\n (closeClickEmitter)=\"close($event)\"\n [data]=\"itemIndex\"\n [data-test-id]=\"testId + '-selected-chip-' + item.item\"\n >{{ item.i18n | translate }}</qd-chip\n >\n </ng-container>\n</ng-template>\n", styles: ["::ng-deep .qd-filter__category-layer{display:flex;overflow:hidden;min-width:16.25rem;max-width:56.25rem;height:100%;flex-direction:column;padding:1rem 0 0;border:.0625rem solid rgb(151,151,151);background:#fff;box-shadow:#d5d5d5 .125rem .25rem .4375rem}::ng-deep .qd-filter__category-layer .qd-filter-form-items__filter{flex-shrink:0;margin-bottom:0}::ng-deep .qd-filter__category-layer .qd-checkbox__label{display:flex;height:2.5rem}::ng-deep .qd-filter__category-layer .qd-checkbox__indicator,::ng-deep .qd-filter__category-layer .qd-radio-buttons__indicator{transform:translateY(-.0625rem)}::ng-deep .qd-filter__category-layer-items{flex:1;overflow-y:auto}::ng-deep .qd-filter__category-layer-container{position:relative}::ng-deep .qd-filter__category-layer-close{position:absolute;z-index:1;top:1rem;right:1rem;color:#757575;cursor:pointer}::ng-deep .qd-filter__category-layer-close:before{position:absolute;content:\"\";inset:-.5rem}::ng-deep .qd-filter__category-layer-close:hover,::ng-deep .qd-filter__category-layer-close:focus{color:#171717}\n"] }]
20744
20789
  }], propDecorators: { filterId: [{
20745
20790
  type: Input
20746
20791
  }], categoryIndex: [{