@quadrel-enterprise-ui/framework 20.6.1-beta.127.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.
@@ -6046,20 +6046,16 @@ class QdDataFacetsCurrencyComponent {
6046
6046
  return '';
6047
6047
  // Swiss speciality with a period instead of a comma
6048
6048
  const localeWithSuffix = ['de', 'fr', 'it'].includes(lang) ? `${lang}-CH` : lang;
6049
- // Normalize U+0027 (apostrophe) to U+2019 (right single quotation mark), which is the
6050
- // correct Swiss thousands separator. Older ICU versions (used in some CI environments)
6051
- // incorrectly emit U+0027 instead.
6052
- const normalize = (s) => s.replace(/\u0027/g, '\u2019');
6053
6049
  if (this.config?.showCurrencyUnit) {
6054
- return normalize(new Intl.NumberFormat(localeWithSuffix, {
6050
+ return new Intl.NumberFormat(localeWithSuffix, {
6055
6051
  style: 'currency',
6056
6052
  currency: this.config?.currency ?? 'chf'
6057
- }).format(this.data));
6053
+ }).format(this.data);
6058
6054
  }
6059
- return normalize(new Intl.NumberFormat(localeWithSuffix, {
6055
+ return new Intl.NumberFormat(localeWithSuffix, {
6060
6056
  minimumFractionDigits: 2,
6061
6057
  maximumFractionDigits: 2
6062
- }).format(this.data));
6058
+ }).format(this.data);
6063
6059
  }
6064
6060
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDataFacetsCurrencyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6065
6061
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDataFacetsCurrencyComponent, isStandalone: false, selector: "qd-data-facets-currency", inputs: { config: "config", data: "data", testId: "testId" }, host: { properties: { "attr.data-test-id": "testId" }, classAttribute: "qd-data-facets" }, usesOnChanges: true, ngImport: i0, template: "{{ currency$ | async }}\n", styles: [":host{text-align:right}\n"], dependencies: [{ kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -6859,185 +6855,230 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
6859
6855
  }] });
6860
6856
 
6861
6857
  /**
6862
- * Service for handling real-time server-sent events.
6858
+ * Manages a server-sent event (SSE) connection with automatic reconnection and heartbeat monitoring.
6863
6859
  *
6864
- * ### 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.
6865
6863
  *
6866
- * - **Real-Time Interaction**: Enables live updates without user actions or polling.
6867
- * - **Scalable and Flexible**: The system is flexible and easily expandable to handle new events.
6868
- * - **Consistency**: Ensures synchronization between server and client states for a consistent user experience.
6869
- * - **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.
6870
6871
  *
6871
6872
  * ### Usage
6872
6873
  *
6873
6874
  * ```ts
6874
- * // Start the connection to the events
6875
- * pushEventsService.connect('http://service-endpint/events');
6876
- * // or start without authentication in case you don't need:
6877
- * pushEventsService.connect('http://service-endpint/events', { disableAuthentication: true });
6878
- *
6879
- * // Subscribe to the event (Side Effect)
6880
- * const subscription = pushEventsService.observe('RESOURCE_UPDATED').subscribe(event => handleEvent(event));
6875
+ * pushEventsService.connect('https://api.example.com/events');
6881
6876
  *
6882
- * // Unsubscribe to prevent memory leaks
6883
- * subscription.unsubscribe();
6877
+ * const sub = pushEventsService.observe('RESOURCE_UPDATED').subscribe(event => handleEvent(event));
6884
6878
  *
6885
- * // Disconnect the connection
6879
+ * sub.unsubscribe();
6886
6880
  * pushEventsService.disconnect();
6887
6881
  * ```
6888
6882
  */
6889
6883
  class QdPushEventsService {
6890
6884
  authenticationService = inject('QdAuthenticationService', { optional: true });
6891
6885
  _eventSource;
6892
- _eventSubscriptionSubjects = new Map();
6886
+ _subjects = new Map();
6893
6887
  _listeners = [];
6894
- _heartbeatTimeout;
6895
- _reconnectDelayTime = 10000;
6896
- _heartbeatReconnectDelayTime = 100;
6897
6888
  _accessTokenSub;
6898
6889
  _options;
6890
+ _heartbeatTimeout;
6891
+ _heartbeatGracePeriod = 100;
6892
+ _initialHeartbeatTimeout = 10000;
6899
6893
  _isUnauthorized = false;
6894
+ _reconnectAttempts = 0;
6895
+ _maxReconnectAttempts = 5;
6896
+ _backoffTimer;
6900
6897
  /**
6901
- * Establishes an EventSource connection to the given URL.
6902
- * Automatically reconnects if heartbeat fails or is delayed.
6903
- * Subscribers are retained across reconnections.
6898
+ * Opens an SSE connection to the given URL.
6904
6899
  *
6905
- * @param url The backend URL for the event stream.
6906
- * @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
+ * ```
6907
6915
  */
6908
6916
  connect(url, options) {
6909
6917
  if (this.isConnectedOrConnecting())
6910
6918
  return;
6911
- if (!options?.disableAuthentication) {
6912
- if (!this.authenticationService) {
6913
- this.logError("Can't connect to SSE without QdAuth as the connection has to be secured. Please install QdAuth or disable authentication when connecting.");
6914
- return;
6915
- }
6916
- if (this._accessTokenSub)
6917
- this._accessTokenSub.unsubscribe();
6918
- this._accessTokenSub = this.authenticationService.accessToken$.subscribe((token) => {
6919
- this._options = { url, options };
6920
- this.connectEventSource(url, {
6921
- headers: { Authorization: `Bearer ${token}` }
6922
- }, true);
6923
- });
6919
+ this._options = { url, options };
6920
+ if (options?.disableAuthentication) {
6921
+ this.openEventSource(url);
6924
6922
  }
6925
6923
  else {
6926
- this._options = { url, options };
6927
- this.connectEventSource(url);
6924
+ this.connectWithAuth(url);
6928
6925
  }
6929
6926
  }
6930
6927
  /**
6931
- * Closes the EventSource connection and clears all listeners.
6932
- * 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
+ * ```
6933
6936
  */
6934
6937
  disconnect() {
6935
6938
  if (!this._eventSource) {
6936
6939
  this.logWarn('No active connection to disconnect.');
6937
6940
  return;
6938
6941
  }
6939
- 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();
6940
6953
  this._eventSource.close();
6941
6954
  }
6942
6955
  /**
6943
- * Returns an Observable for the specified event name.
6944
- * If not connected, returns `NEVER` and logs an error.
6945
- * 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.
6946
6960
  *
6947
- * @param eventName The event type ('RESOURCE_CREATED', 'RESOURCE_UPDATED', 'RESOURCE_DELETED').
6948
- * @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
+ * ```
6949
6967
  */
6950
6968
  observe(eventName) {
6951
6969
  if (!this._eventSource) {
6952
- this.logError('Cannot observe events without a connection. Call connect() first.');
6970
+ this.logWarn('Cannot observe events without a connection. Call connect() first.');
6953
6971
  return NEVER;
6954
6972
  }
6955
- if (!this._eventSubscriptionSubjects.has(eventName)) {
6956
- this._eventSubscriptionSubjects.set(eventName, new Subject());
6957
- 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));
6958
6976
  }
6959
- return this._eventSubscriptionSubjects.get(eventName).asObservable();
6977
+ return this._subjects.get(eventName).asObservable();
6960
6978
  }
6961
6979
  /**
6962
- * Removes all listeners and clears all subscriptions.
6963
- * 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
+ * ```
6964
6988
  */
6965
6989
  unobserveAll() {
6966
- this.removeAllEventListenersFromEventSource();
6967
- this._eventSubscriptionSubjects.clear();
6990
+ this.removeAllListeners();
6991
+ this._subjects.clear();
6968
6992
  }
6969
- /**
6970
- * Checks if the EventSource is connected or in the process of connecting.
6971
- */
6993
+ /** Returns `true` when the EventSource is in state OPEN or CONNECTING. */
6972
6994
  isConnectedOrConnecting() {
6973
6995
  return (this._eventSource &&
6974
6996
  (this._eventSource.readyState === EventSource.OPEN || this._eventSource.readyState === EventSource.CONNECTING));
6975
6997
  }
6976
- reconnect() {
6977
- 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.");
6978
7001
  return;
6979
- 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)
6980
7026
  return;
6981
7027
  this.disconnect();
6982
7028
  this.connect(this._options.url, this._options.options);
6983
7029
  }
6984
- addEventListenerForEventName(eventName) {
6985
- const callback = (messageEvent) => {
6986
- const subject = this._eventSubscriptionSubjects.get(eventName);
6987
- if (subject)
6988
- subject.next(messageEvent);
6989
- };
6990
- this._eventSource.addEventListener(eventName, callback);
6991
- 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);
6992
7042
  }
6993
- addEventListenersForExistingSubscriptions() {
6994
- 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);
6995
7063
  }
6996
- addEventListenerToEventSource(eventName, callback) {
7064
+ addListener(eventName, callback) {
6997
7065
  this._eventSource.addEventListener(eventName, callback);
6998
7066
  this._listeners.push([eventName, callback]);
6999
7067
  }
7000
- removeAllEventListenersFromEventSource() {
7001
- if (!this._eventSource) {
7002
- 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)
7003
7073
  return;
7004
- }
7005
7074
  this._listeners.forEach(([eventName, callback]) => this._eventSource.removeEventListener(eventName, callback));
7006
7075
  this._listeners = [];
7007
7076
  }
7008
7077
  logWarn(message) {
7009
- console.warn(`QD-UI | QdPushEventsService - ${message}`);
7078
+ console.warn(`QD-UI | QdPushEvents - ${message}`);
7010
7079
  }
7011
7080
  logError(message, err) {
7012
- console.error(`QD-UI | QdPushEventsService - ${message}`, err);
7013
- }
7014
- connectEventSource(url, options = {}, reconnect) {
7015
- if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED)
7016
- this._eventSource.close();
7017
- if (!this._eventSource || this._eventSource.readyState === EventSource.CLOSED || reconnect)
7018
- this._eventSource = new EventSourcePolyfill(url, options);
7019
- this._eventSource.onerror = (err) => {
7020
- const status = err?.status || err?.target?.status;
7021
- if (status === 401) {
7022
- this._isUnauthorized = true;
7023
- this.logError('SSE connection unauthorized (401):', err);
7024
- this._eventSource.close();
7025
- return;
7026
- }
7027
- if (this._eventSource.readyState === EventSource.CLOSED)
7028
- this.reconnect();
7029
- this.logError('SSE connection error:', err);
7030
- };
7031
- this._eventSource.addEventListener('HEARTBEAT', (message) => {
7032
- if (this._isUnauthorized)
7033
- return;
7034
- if (this._heartbeatTimeout)
7035
- clearTimeout(this._heartbeatTimeout);
7036
- const interval = JSON.parse(message.data).interval;
7037
- this._heartbeatTimeout = setTimeout(() => this.reconnect(), interval + this._heartbeatReconnectDelayTime);
7038
- });
7039
- this._heartbeatTimeout = setTimeout(() => this.reconnect(), this._reconnectDelayTime);
7040
- this.addEventListenersForExistingSubscriptions();
7081
+ console.error(`QD-UI | QdPushEvents - ${message}`, err);
7041
7082
  }
7042
7083
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPushEventsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7043
7084
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPushEventsService, providedIn: 'root' });
@@ -20740,11 +20781,11 @@ class QdFilterItemSelectCategoryComponent {
20740
20781
  }));
20741
20782
  }
20742
20783
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFilterItemSelectCategoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20743
- 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 });
20744
20785
  }
20745
20786
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFilterItemSelectCategoryComponent, decorators: [{
20746
20787
  type: Component,
20747
- 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"] }]
20748
20789
  }], propDecorators: { filterId: [{
20749
20790
  type: Input
20750
20791
  }], categoryIndex: [{