ps-helix 6.0.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A comprehensive Angular component library built with Angular 22+ featuring modern design patterns, accessibility-first development, and optimal developer experience.
4
4
 
5
- [![npm version](https://img.shields.io/badge/npm-6.0.0-blue.svg)](https://www.npmjs.com/package/ps-helix)
5
+ [![npm version](https://img.shields.io/badge/npm-6.0.1-blue.svg)](https://www.npmjs.com/package/ps-helix)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Angular](https://img.shields.io/badge/Angular-22.0.0-red.svg)](https://angular.dev/)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-6.0.0-blue.svg)](https://www.typescriptlang.org/)
@@ -59,11 +59,11 @@ Helix is a production-ready design system that provides:
59
59
 
60
60
  Before installing Helix, ensure your development environment meets these requirements:
61
61
 
62
- - **Node.js**: 18.x or higher
63
- - **npm**: 9.x or higher
62
+ - **Node.js**: 22.22.3 or higher (or 24.15+)
63
+ - **npm**: 10.x or higher
64
64
  - **Angular**: 22.0.0 or higher
65
65
  - **Angular CLI**: 22.0.0 or higher
66
- - **TypeScript**: 5.9.0 or higher
66
+ - **TypeScript**: 6.0.0 or higher
67
67
 
68
68
  ### Required Peer Dependencies
69
69
 
@@ -106,7 +106,7 @@ After installation, verify that ps-helix is in your `package.json`:
106
106
  ```json
107
107
  {
108
108
  "dependencies": {
109
- "ps-helix": "^6.0.0"
109
+ "ps-helix": "^6.0.1"
110
110
  }
111
111
  }
112
112
  ```
@@ -1182,7 +1182,7 @@ Copyright (c) 2025 PACK Solutions
1182
1182
 
1183
1183
  ---
1184
1184
 
1185
- **Version**: 6.0.0
1185
+ **Version**: 6.0.1
1186
1186
  **Built with**: Angular 22.0.0, TypeScript 6.0.0, Phosphor Icons 2.0.3
1187
1187
  **Author**: Fabrice PEREZ | Product Designer at PACK Solutions
1188
1188
  **Last Updated**: January 2026
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, computed, ChangeDetectionStrategy, Component, model, inject, ElementRef, signal, PLATFORM_ID, ViewEncapsulation, InjectionToken, viewChild, effect, Injectable, DestroyRef, Directive, Injector, afterNextRender, ChangeDetectorRef, ViewChild, HostListener, Renderer2, contentChild, linkedSignal, EventEmitter, Output, Input, contentChildren } from '@angular/core';
2
+ import { input, output, computed, ChangeDetectionStrategy, Component, model, inject, ElementRef, signal, PLATFORM_ID, ViewEncapsulation, InjectionToken, viewChild, effect, Injectable, DestroyRef, Directive, Injector, afterNextRender, ChangeDetectorRef, ViewChild, HostListener, Renderer2, contentChild, linkedSignal, EventEmitter, Output, Input, ViewContainerRef, contentChildren } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule, DOCUMENT, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
5
5
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
@@ -3729,12 +3729,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
3729
3729
  type: Output
3730
3730
  }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
3731
3731
 
3732
+ /**
3733
+ * Teleports a `TemplateRef` into a single body-level overlay layer so popover
3734
+ * panels (select / dropdown / menu…) escape any ancestor `overflow`, `transform`
3735
+ * or stacking context and can layer above modals.
3736
+ *
3737
+ * A lightweight, CDK-free "manual portal": the embedded view is created from the
3738
+ * consumer's `ViewContainerRef` (so it stays in that component's change-detection
3739
+ * tree and keeps its bindings reactive), then its root nodes are moved into the
3740
+ * shared `.psh-overlay-layer`. The layer is `position: fixed; inset: 0;
3741
+ * pointer-events: none` at `z-index: var(--z-index-overlay)`; panels re-enable
3742
+ * pointer events. The layer is created lazily and removed once empty.
3743
+ */
3744
+ class PshPortalService {
3745
+ constructor() {
3746
+ this.document = inject(DOCUMENT);
3747
+ this.layer = null;
3748
+ this.count = 0;
3749
+ }
3750
+ /** Teleports `tpl` into the overlay layer and returns a handle to control it. */
3751
+ attach(tpl, vcr) {
3752
+ const layer = this.ensureLayer();
3753
+ const viewRef = vcr.createEmbeddedView(tpl);
3754
+ viewRef.detectChanges();
3755
+ const nodes = viewRef.rootNodes;
3756
+ const panel = (nodes.find((n) => n instanceof HTMLElement) ??
3757
+ nodes[0]);
3758
+ nodes.forEach(node => layer.appendChild(node));
3759
+ this.count++;
3760
+ return {
3761
+ panel,
3762
+ position: (anchor, side, gap) => this.position(panel, anchor, side, gap),
3763
+ detach: () => this.detach(viewRef),
3764
+ };
3765
+ }
3766
+ position(panel, anchor, side, gap) {
3767
+ const view = this.document.defaultView;
3768
+ if (!view)
3769
+ return;
3770
+ const rect = anchor.getBoundingClientRect();
3771
+ const style = panel.style;
3772
+ style.position = 'fixed';
3773
+ style.left = `${rect.left}px`;
3774
+ style.width = `${rect.width}px`;
3775
+ if (side === 'top') {
3776
+ const height = panel.offsetHeight;
3777
+ style.top = `${Math.max(gap, rect.top - height - gap)}px`;
3778
+ style.maxHeight = `${Math.max(0, rect.top - gap * 2)}px`;
3779
+ }
3780
+ else {
3781
+ style.top = `${rect.bottom + gap}px`;
3782
+ style.maxHeight = `${Math.max(0, view.innerHeight - rect.bottom - gap * 2)}px`;
3783
+ }
3784
+ }
3785
+ detach(viewRef) {
3786
+ viewRef.destroy();
3787
+ this.count = Math.max(0, this.count - 1);
3788
+ if (this.count === 0 && this.layer) {
3789
+ this.layer.remove();
3790
+ this.layer = null;
3791
+ }
3792
+ }
3793
+ ensureLayer() {
3794
+ if (this.layer)
3795
+ return this.layer;
3796
+ const layer = this.document.createElement('div');
3797
+ layer.className = 'psh-overlay-layer';
3798
+ layer.style.cssText =
3799
+ 'position:fixed;inset:0;z-index:var(--z-index-overlay);pointer-events:none;';
3800
+ this.document.body.appendChild(layer);
3801
+ this.layer = layer;
3802
+ return layer;
3803
+ }
3804
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PshPortalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3805
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PshPortalService, providedIn: 'root' }); }
3806
+ }
3807
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PshPortalService, decorators: [{
3808
+ type: Injectable,
3809
+ args: [{ providedIn: 'root' }]
3810
+ }] });
3811
+
3732
3812
  class PshSelectComponent {
3733
3813
  constructor() {
3734
3814
  this.elementRef = inject(ElementRef);
3735
3815
  this.destroyRef = inject(DestroyRef);
3736
3816
  this.overlayPosition = inject(PshOverlayPositionService);
3737
- this.injector = inject(Injector);
3817
+ this.portal = inject(PshPortalService);
3818
+ this.viewContainer = inject(ViewContainerRef);
3819
+ // The options panel, teleported to a body-level overlay layer on open so it
3820
+ // escapes any ancestor overflow / stacking context (e.g. a modal body).
3821
+ this.panelTpl = viewChild('panelTpl', /* @ts-ignore */
3822
+ ...(ngDevMode ? [{ debugName: "panelTpl" }] : /* istanbul ignore next */ []));
3823
+ this.portalRef = null;
3824
+ this.repositionHandler = () => this.reposition();
3738
3825
  // Side the panel actually opens on, after viewport collision/flip.
3739
3826
  this.resolvedSide = signal('bottom', /* @ts-ignore */
3740
3827
  ...(ngDevMode ? [{ debugName: "resolvedSide" }] : /* istanbul ignore next */ []));
@@ -3888,34 +3975,58 @@ class PshSelectComponent {
3888
3975
  ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
3889
3976
  this.onChange = () => { };
3890
3977
  this.onTouched = () => { };
3891
- // Close on outside click via the shared click-outside primitive.
3978
+ // Close on outside click via the shared click-outside primitive. The options
3979
+ // panel is teleported to the body (outside the host), so a click inside it
3980
+ // must NOT count as "outside" — otherwise selecting an option would close the
3981
+ // panel before select() runs.
3892
3982
  const clickOutside = inject(PshClickOutsideDirective);
3893
- const sub = clickOutside.pshClickOutside.subscribe(() => {
3894
- if (this.isOpen())
3895
- this.close();
3983
+ const sub = clickOutside.pshClickOutside.subscribe(event => {
3984
+ if (!this.isOpen())
3985
+ return;
3986
+ const target = event.target;
3987
+ if (target && this.portalRef?.panel.contains(target))
3988
+ return;
3989
+ this.close();
3896
3990
  });
3897
- this.destroyRef.onDestroy(() => sub.unsubscribe());
3898
- // Flip the options panel against the viewport on open; reset while closed.
3899
- effect(() => {
3900
- if (this.isOpen()) {
3901
- afterNextRender(() => this.reposition(), { injector: this.injector });
3902
- }
3903
- else {
3904
- this.resolvedSide.set('bottom');
3905
- }
3991
+ this.destroyRef.onDestroy(() => {
3992
+ sub.unsubscribe();
3993
+ this.closePanel();
3906
3994
  });
3907
3995
  }
3996
+ /** Teleports the options panel to the body-level overlay layer and positions it. */
3997
+ openPanel() {
3998
+ if (this.portalRef)
3999
+ return;
4000
+ const tpl = this.panelTpl();
4001
+ if (!tpl)
4002
+ return;
4003
+ this.portalRef = this.portal.attach(tpl, this.viewContainer);
4004
+ this.reposition();
4005
+ const view = this.elementRef.nativeElement.ownerDocument.defaultView;
4006
+ // Capture phase so inner (e.g. modal body) scrolls keep the panel aligned.
4007
+ view?.addEventListener('scroll', this.repositionHandler, true);
4008
+ view?.addEventListener('resize', this.repositionHandler);
4009
+ }
4010
+ closePanel() {
4011
+ const view = this.elementRef.nativeElement.ownerDocument.defaultView;
4012
+ view?.removeEventListener('scroll', this.repositionHandler, true);
4013
+ view?.removeEventListener('resize', this.repositionHandler);
4014
+ this.portalRef?.detach();
4015
+ this.portalRef = null;
4016
+ this.resolvedSide.set('bottom');
4017
+ }
3908
4018
  reposition() {
3909
- if (!this.isOpen())
4019
+ if (!this.portalRef)
3910
4020
  return;
3911
4021
  const host = this.elementRef.nativeElement;
3912
4022
  const trigger = host.querySelector('.select-trigger');
3913
- const panel = host.querySelector('.select-dropdown');
3914
4023
  if (!trigger)
3915
4024
  return;
3916
- this.resolvedSide.set(this.overlayPosition.flipSide(trigger, 'bottom', {
3917
- overlayHeight: panel?.offsetHeight ?? 0,
3918
- }));
4025
+ const side = this.overlayPosition.flipSide(trigger, 'bottom', {
4026
+ overlayHeight: this.portalRef.panel.offsetHeight,
4027
+ });
4028
+ this.resolvedSide.set(side);
4029
+ this.portalRef.position(trigger, side, 8);
3919
4030
  }
3920
4031
  writeValue(value) {
3921
4032
  this.value.set(value);
@@ -3932,6 +4043,7 @@ class PshSelectComponent {
3932
4043
  else {
3933
4044
  this.isOpenSignal.set(true);
3934
4045
  this.opened.emit();
4046
+ this.openPanel();
3935
4047
  }
3936
4048
  }
3937
4049
  close() {
@@ -3940,6 +4052,7 @@ class PshSelectComponent {
3940
4052
  this.isOpenSignal.set(false);
3941
4053
  this.searchTermSignal.set('');
3942
4054
  this.focusedIndex.set(-1);
4055
+ this.closePanel();
3943
4056
  this.closed.emit();
3944
4057
  }
3945
4058
  select(option, groupDisabled = false) {
@@ -4100,7 +4213,7 @@ class PshSelectComponent {
4100
4213
  useExisting: PshSelectComponent,
4101
4214
  multi: true
4102
4215
  }
4103
- ], hostDirectives: [{ directive: PshClickOutsideDirective }], ngImport: i0, template: "<div class=\"select-container\">\r\n @if (label()) {\r\n <!-- label[for] delegates focus to its control natively; the click handler is a convenience. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\r\n <label class=\"select-label\" [attr.for]=\"selectId\" (click)=\"focusSelect()\" [class.required]=\"required()\">\r\n {{ label() }}\r\n @if (required()) { <span class=\"required-mark\">*</span> }\r\n </label>\r\n }\r\n\r\n <div class=\"select-trigger-wrapper\">\r\n <div class=\"select-trigger\"\r\n role=\"combobox\"\r\n [id]=\"selectId\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-controls]=\"selectId + '-listbox'\"\r\n [attr.aria-activedescendant]=\"activeDescendant()\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-invalid]=\"!!error()\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-busy]=\"loading()\"\r\n [attr.aria-describedby]=\"describedBy()\"\r\n [attr.tabindex]=\"disabled() || loading() ? -1 : 0\"\r\n (keydown)=\"handleKeydown($event)\"\r\n (click)=\"toggle()\">\r\n <div class=\"select-value\">\r\n <span [class.placeholder]=\"!hasValue()\">\r\n {{ selectedLabel() }}\r\n </span>\r\n </div>\r\n <div class=\"select-actions\">\r\n @if (clearable() && hasValue() && !disabled()) {\r\n <button class=\"select-clear\" (click)=\"clear($event)\">\r\n <i class=\"ph ph-x\"></i>\r\n </button>\r\n }\r\n @if (loading()) {\r\n <div class=\"select-loader-trigger\"></div>\r\n } @else {\r\n <i class=\"ph ph-caret-down select-arrow\"></i>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div class=\"select-dropdown\" [class.open-top]=\"resolvedSide() === 'top'\">\r\n @if (searchable()) {\r\n <div class=\"select-search\">\r\n <i class=\"ph ph-magnifying-glass select-search-icon\"></i>\r\n <input type=\"text\"\r\n [placeholder]=\"searchConfig().placeholder\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearch($event)\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n }\r\n <div class=\"select-options\"\r\n role=\"listbox\"\r\n [id]=\"selectId + '-listbox'\"\r\n [attr.aria-multiselectable]=\"multiple() || null\"\r\n (scroll)=\"onScroll($event)\">\r\n @for (item of filteredOptions(); track getOptionKey(item)) {\r\n @if (isOptionGroup(item)) {\r\n <div class=\"select-group\" role=\"group\" [attr.aria-label]=\"item.label\">\r\n <div class=\"select-group-label\">{{ item.label }}</div>\r\n @for (opt of item.options; track opt.label) {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + opt.value\"\r\n [attr.aria-selected]=\"isSelected(opt)\"\r\n [attr.aria-disabled]=\"(item.disabled || opt.disabled) ? true : null\"\r\n [attr.tabindex]=\"(item.disabled || opt.disabled) ? -1 : null\"\r\n [class.selected]=\"isSelected(opt)\"\r\n [class.disabled]=\"item.disabled || opt.disabled\"\r\n [class.focused]=\"getFlatIndex(opt) === focusedIndex()\"\r\n (click)=\"select(opt, !!item.disabled)\">\r\n @if (opt.icon) { <i [class]=\"'ph ph-' + opt.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ opt.label }}</span>\r\n @if (opt.description) { <span class=\"select-option-description\">{{ opt.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + item.value\"\r\n [attr.aria-selected]=\"isSelected(item)\"\r\n [attr.aria-disabled]=\"item.disabled ? true : null\"\r\n [attr.tabindex]=\"item.disabled ? -1 : null\"\r\n [class.selected]=\"isSelected(item)\"\r\n [class.disabled]=\"item.disabled\"\r\n [class.focused]=\"getFlatIndex(item) === focusedIndex()\"\r\n (click)=\"select(item)\">\r\n @if (item.icon) { <i [class]=\"'ph ph-' + item.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ item.label }}</span>\r\n @if (item.description) { <span class=\"select-option-description\">{{ item.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n } @empty {\r\n <div class=\"select-no-results\">Aucun r\u00E9sultat</div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (error()) {\r\n <div id=\"error-message\" class=\"select-error-message\" role=\"alert\">{{ error() }}</div>\r\n } @else if (success()) {\r\n <div id=\"success-message\" class=\"select-success-message\" role=\"status\">{{ success() }}</div>\r\n } @else if (hint()) {\r\n <div id=\"hint-message\" class=\"select-hint\">{{ hint() }}</div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;max-width:18.75rem}:host.full-width{max-width:none}.select-container{width:100%;display:flex;flex-direction:column;gap:var(--form-field-gap)}.select-trigger-wrapper{position:relative}.select-label{display:block;color:var(--text-color);font-size:.875rem;font-weight:500;cursor:pointer}.select-trigger{display:flex;align-items:center;justify-content:space-between;width:100%;height:2.5rem;padding:0 1rem;background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);cursor:pointer;box-sizing:border-box}.select-trigger:focus{border-color:var(--primary-color);outline:none;box-shadow:0 0 0 .125rem var(--focus-ring-color)}:host.error .select-trigger{border-color:var(--danger-color)}:host.error .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-error)}:host.success .select-trigger{border-color:var(--success-color)}:host.success .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-success)}:host.disabled .select-trigger{background:var(--surface-ground);cursor:not-allowed;opacity:var(--opacity-disabled)}:host.filled .select-trigger{background:var(--surface-ground);border:.125rem solid var(--surface-border)}:host.filled .select-trigger:hover:not([disabled]):not(:focus){background:var(--surface-hover)}.select-value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.select-clear{background:none;border:none;cursor:pointer;padding:0;color:var(--text-color-secondary);display:flex}.select-clear:hover{color:var(--danger-color)}.select-loader-trigger{width:1rem;height:1rem;border:2px solid var(--surface-border);border-top-color:var(--primary-color);border-radius:50%;animation:spin .8s linear infinite}.select-arrow{transition:transform .2s;font-size:1rem;color:var(--text-color-secondary)}:host[aria-expanded=true] .select-arrow{transform:rotate(180deg)}.select-dropdown.open-top{top:auto;bottom:calc(100% + .25rem)}.select-dropdown{position:absolute;top:calc(100% + .25rem);left:0;right:0;z-index:var(--z-index-dropdown);background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);box-shadow:var(--shadow-lg)}.select-search{position:relative;padding:.5rem;border-bottom:.0625rem solid var(--surface-border)}.select-search-icon{position:absolute;left:1.25rem;top:50%;transform:translateY(-50%);color:var(--text-color-secondary);font-size:.875rem;pointer-events:none}.select-search input{width:100%;height:2rem;padding:0 .75rem 0 2rem;background:var(--surface-ground);border:.0625rem solid var(--surface-border);border-radius:var(--border-radius);color:var(--text-color);font-size:.875rem;outline:none;box-sizing:border-box;transition:border-color .15s}.select-search input::placeholder{color:var(--text-color-secondary)}.select-search input:focus{border-color:var(--primary-color)}.select-options{max-height:15rem;overflow-y:auto}.select-group-label{padding:.5rem 1rem;font-size:.75rem;font-weight:600;color:var(--text-color-secondary);background:var(--surface-ground)}.select-option{padding:.625rem 1rem;cursor:pointer;display:flex;align-items:flex-start;gap:.5rem}.select-option:hover{background:var(--surface-hover)}.select-option.selected{background:rgba(var(--primary-color-rgb),.1);color:var(--primary-color);font-weight:500}.select-option.focused{background:var(--surface-hover)}.select-option.disabled{opacity:var(--opacity-disabled);cursor:not-allowed}.select-option i{flex-shrink:0;line-height:1.4}.select-option-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:.125rem}.select-option-label{min-width:0}.select-option-description{font-size:.75rem;color:var(--text-color-secondary)}.select-error-message,.select-success-message,.select-hint{font-size:.875rem}.select-error-message{color:var(--danger-color)}.select-success-message{color:var(--success-color)}.select-hint{color:var(--text-color-secondary)}:host.small .select-trigger{text-overflow:ellipsis;height:2rem;font-size:.875rem}:host.large .select-trigger{text-overflow:ellipsis;height:3rem;font-size:1.125rem}@keyframes spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4216
+ ], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, isSignal: true }], hostDirectives: [{ directive: PshClickOutsideDirective }], ngImport: i0, template: "<div class=\"select-container\">\r\n @if (label()) {\r\n <!-- label[for] delegates focus to its control natively; the click handler is a convenience. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\r\n <label class=\"select-label\" [attr.for]=\"selectId\" (click)=\"focusSelect()\" [class.required]=\"required()\">\r\n {{ label() }}\r\n @if (required()) { <span class=\"required-mark\">*</span> }\r\n </label>\r\n }\r\n\r\n <div class=\"select-trigger-wrapper\">\r\n <div class=\"select-trigger\"\r\n role=\"combobox\"\r\n [id]=\"selectId\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-controls]=\"selectId + '-listbox'\"\r\n [attr.aria-activedescendant]=\"activeDescendant()\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-invalid]=\"!!error()\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-busy]=\"loading()\"\r\n [attr.aria-describedby]=\"describedBy()\"\r\n [attr.tabindex]=\"disabled() || loading() ? -1 : 0\"\r\n (keydown)=\"handleKeydown($event)\"\r\n (click)=\"toggle()\">\r\n <div class=\"select-value\">\r\n <span [class.placeholder]=\"!hasValue()\">\r\n {{ selectedLabel() }}\r\n </span>\r\n </div>\r\n <div class=\"select-actions\">\r\n @if (clearable() && hasValue() && !disabled()) {\r\n <button class=\"select-clear\" (click)=\"clear($event)\">\r\n <i class=\"ph ph-x\"></i>\r\n </button>\r\n }\r\n @if (loading()) {\r\n <div class=\"select-loader-trigger\"></div>\r\n } @else {\r\n <i class=\"ph ph-caret-down select-arrow\"></i>\r\n }\r\n </div>\r\n </div>\r\n\r\n <ng-template #panelTpl>\r\n <div class=\"select-dropdown\" [class.open-top]=\"resolvedSide() === 'top'\">\r\n @if (searchable()) {\r\n <div class=\"select-search\">\r\n <i class=\"ph ph-magnifying-glass select-search-icon\"></i>\r\n <input type=\"text\"\r\n [placeholder]=\"searchConfig().placeholder\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearch($event)\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n }\r\n <div class=\"select-options\"\r\n role=\"listbox\"\r\n [id]=\"selectId + '-listbox'\"\r\n [attr.aria-multiselectable]=\"multiple() || null\"\r\n (scroll)=\"onScroll($event)\">\r\n @for (item of filteredOptions(); track getOptionKey(item)) {\r\n @if (isOptionGroup(item)) {\r\n <div class=\"select-group\" role=\"group\" [attr.aria-label]=\"item.label\">\r\n <div class=\"select-group-label\">{{ item.label }}</div>\r\n @for (opt of item.options; track opt.label) {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + opt.value\"\r\n [attr.aria-selected]=\"isSelected(opt)\"\r\n [attr.aria-disabled]=\"(item.disabled || opt.disabled) ? true : null\"\r\n [attr.tabindex]=\"(item.disabled || opt.disabled) ? -1 : null\"\r\n [class.selected]=\"isSelected(opt)\"\r\n [class.disabled]=\"item.disabled || opt.disabled\"\r\n [class.focused]=\"getFlatIndex(opt) === focusedIndex()\"\r\n (click)=\"select(opt, !!item.disabled)\">\r\n @if (opt.icon) { <i [class]=\"'ph ph-' + opt.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ opt.label }}</span>\r\n @if (opt.description) { <span class=\"select-option-description\">{{ opt.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + item.value\"\r\n [attr.aria-selected]=\"isSelected(item)\"\r\n [attr.aria-disabled]=\"item.disabled ? true : null\"\r\n [attr.tabindex]=\"item.disabled ? -1 : null\"\r\n [class.selected]=\"isSelected(item)\"\r\n [class.disabled]=\"item.disabled\"\r\n [class.focused]=\"getFlatIndex(item) === focusedIndex()\"\r\n (click)=\"select(item)\">\r\n @if (item.icon) { <i [class]=\"'ph ph-' + item.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ item.label }}</span>\r\n @if (item.description) { <span class=\"select-option-description\">{{ item.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n } @empty {\r\n <div class=\"select-no-results\">Aucun r\u00E9sultat</div>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n @if (error()) {\r\n <div id=\"error-message\" class=\"select-error-message\" role=\"alert\">{{ error() }}</div>\r\n } @else if (success()) {\r\n <div id=\"success-message\" class=\"select-success-message\" role=\"status\">{{ success() }}</div>\r\n } @else if (hint()) {\r\n <div id=\"hint-message\" class=\"select-hint\">{{ hint() }}</div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;max-width:18.75rem}:host.full-width{max-width:none}.select-container{width:100%;display:flex;flex-direction:column;gap:var(--form-field-gap)}.select-trigger-wrapper{position:relative}.select-label{display:block;color:var(--text-color);font-size:.875rem;font-weight:500;cursor:pointer}.select-trigger{display:flex;align-items:center;justify-content:space-between;width:100%;height:2.5rem;padding:0 1rem;background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);cursor:pointer;box-sizing:border-box}.select-trigger:focus{border-color:var(--primary-color);outline:none;box-shadow:0 0 0 .125rem var(--focus-ring-color)}:host.error .select-trigger{border-color:var(--danger-color)}:host.error .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-error)}:host.success .select-trigger{border-color:var(--success-color)}:host.success .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-success)}:host.disabled .select-trigger{background:var(--surface-ground);cursor:not-allowed;opacity:var(--opacity-disabled)}:host.filled .select-trigger{background:var(--surface-ground);border:.125rem solid var(--surface-border)}:host.filled .select-trigger:hover:not([disabled]):not(:focus){background:var(--surface-hover)}.select-value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.select-clear{background:none;border:none;cursor:pointer;padding:0;color:var(--text-color-secondary);display:flex}.select-clear:hover{color:var(--danger-color)}.select-loader-trigger{width:1rem;height:1rem;border:2px solid var(--surface-border);border-top-color:var(--primary-color);border-radius:50%;animation:spin .8s linear infinite}.select-arrow{transition:transform .2s;font-size:1rem;color:var(--text-color-secondary)}:host[aria-expanded=true] .select-arrow{transform:rotate(180deg)}.select-dropdown{display:flex;flex-direction:column;overflow:hidden;pointer-events:auto;background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);box-shadow:var(--shadow-lg)}.select-search{position:relative;flex-shrink:0;padding:.5rem;border-bottom:.0625rem solid var(--surface-border)}.select-search-icon{position:absolute;left:1.25rem;top:50%;transform:translateY(-50%);color:var(--text-color-secondary);font-size:.875rem;pointer-events:none}.select-search input{width:100%;height:2rem;padding:0 .75rem 0 2rem;background:var(--surface-ground);border:.0625rem solid var(--surface-border);border-radius:var(--border-radius);color:var(--text-color);font-size:.875rem;outline:none;box-sizing:border-box;transition:border-color .15s}.select-search input::placeholder{color:var(--text-color-secondary)}.select-search input:focus{border-color:var(--primary-color)}.select-options{flex:0 1 auto;max-height:15rem;overflow-y:auto}.select-group-label{padding:.5rem 1rem;font-size:.75rem;font-weight:600;color:var(--text-color-secondary);background:var(--surface-ground)}.select-option{padding:.625rem 1rem;cursor:pointer;display:flex;align-items:flex-start;gap:.5rem}.select-option:hover{background:var(--surface-hover)}.select-option.selected{background:rgba(var(--primary-color-rgb),.1);color:var(--primary-color);font-weight:500}.select-option.focused{background:var(--surface-hover)}.select-option.disabled{opacity:var(--opacity-disabled);cursor:not-allowed}.select-option i{flex-shrink:0;line-height:1.4}.select-option-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:.125rem}.select-option-label{min-width:0}.select-option-description{font-size:.75rem;color:var(--text-color-secondary)}.select-error-message,.select-success-message,.select-hint{font-size:.875rem}.select-error-message{color:var(--danger-color)}.select-success-message{color:var(--success-color)}.select-hint{color:var(--text-color-secondary)}:host.small .select-trigger{text-overflow:ellipsis;height:2rem;font-size:.875rem}:host.large .select-trigger{text-overflow:ellipsis;height:3rem;font-size:1.125rem}@keyframes spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4104
4217
  }
4105
4218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PshSelectComponent, decorators: [{
4106
4219
  type: Component,
@@ -4122,8 +4235,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
4122
4235
  '[class.loading]': 'loading()',
4123
4236
  '[attr.aria-expanded]': 'isOpen().toString()',
4124
4237
  '[attr.data-state]': 'state()'
4125
- }, template: "<div class=\"select-container\">\r\n @if (label()) {\r\n <!-- label[for] delegates focus to its control natively; the click handler is a convenience. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\r\n <label class=\"select-label\" [attr.for]=\"selectId\" (click)=\"focusSelect()\" [class.required]=\"required()\">\r\n {{ label() }}\r\n @if (required()) { <span class=\"required-mark\">*</span> }\r\n </label>\r\n }\r\n\r\n <div class=\"select-trigger-wrapper\">\r\n <div class=\"select-trigger\"\r\n role=\"combobox\"\r\n [id]=\"selectId\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-controls]=\"selectId + '-listbox'\"\r\n [attr.aria-activedescendant]=\"activeDescendant()\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-invalid]=\"!!error()\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-busy]=\"loading()\"\r\n [attr.aria-describedby]=\"describedBy()\"\r\n [attr.tabindex]=\"disabled() || loading() ? -1 : 0\"\r\n (keydown)=\"handleKeydown($event)\"\r\n (click)=\"toggle()\">\r\n <div class=\"select-value\">\r\n <span [class.placeholder]=\"!hasValue()\">\r\n {{ selectedLabel() }}\r\n </span>\r\n </div>\r\n <div class=\"select-actions\">\r\n @if (clearable() && hasValue() && !disabled()) {\r\n <button class=\"select-clear\" (click)=\"clear($event)\">\r\n <i class=\"ph ph-x\"></i>\r\n </button>\r\n }\r\n @if (loading()) {\r\n <div class=\"select-loader-trigger\"></div>\r\n } @else {\r\n <i class=\"ph ph-caret-down select-arrow\"></i>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div class=\"select-dropdown\" [class.open-top]=\"resolvedSide() === 'top'\">\r\n @if (searchable()) {\r\n <div class=\"select-search\">\r\n <i class=\"ph ph-magnifying-glass select-search-icon\"></i>\r\n <input type=\"text\"\r\n [placeholder]=\"searchConfig().placeholder\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearch($event)\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n }\r\n <div class=\"select-options\"\r\n role=\"listbox\"\r\n [id]=\"selectId + '-listbox'\"\r\n [attr.aria-multiselectable]=\"multiple() || null\"\r\n (scroll)=\"onScroll($event)\">\r\n @for (item of filteredOptions(); track getOptionKey(item)) {\r\n @if (isOptionGroup(item)) {\r\n <div class=\"select-group\" role=\"group\" [attr.aria-label]=\"item.label\">\r\n <div class=\"select-group-label\">{{ item.label }}</div>\r\n @for (opt of item.options; track opt.label) {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + opt.value\"\r\n [attr.aria-selected]=\"isSelected(opt)\"\r\n [attr.aria-disabled]=\"(item.disabled || opt.disabled) ? true : null\"\r\n [attr.tabindex]=\"(item.disabled || opt.disabled) ? -1 : null\"\r\n [class.selected]=\"isSelected(opt)\"\r\n [class.disabled]=\"item.disabled || opt.disabled\"\r\n [class.focused]=\"getFlatIndex(opt) === focusedIndex()\"\r\n (click)=\"select(opt, !!item.disabled)\">\r\n @if (opt.icon) { <i [class]=\"'ph ph-' + opt.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ opt.label }}</span>\r\n @if (opt.description) { <span class=\"select-option-description\">{{ opt.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + item.value\"\r\n [attr.aria-selected]=\"isSelected(item)\"\r\n [attr.aria-disabled]=\"item.disabled ? true : null\"\r\n [attr.tabindex]=\"item.disabled ? -1 : null\"\r\n [class.selected]=\"isSelected(item)\"\r\n [class.disabled]=\"item.disabled\"\r\n [class.focused]=\"getFlatIndex(item) === focusedIndex()\"\r\n (click)=\"select(item)\">\r\n @if (item.icon) { <i [class]=\"'ph ph-' + item.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ item.label }}</span>\r\n @if (item.description) { <span class=\"select-option-description\">{{ item.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n } @empty {\r\n <div class=\"select-no-results\">Aucun r\u00E9sultat</div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (error()) {\r\n <div id=\"error-message\" class=\"select-error-message\" role=\"alert\">{{ error() }}</div>\r\n } @else if (success()) {\r\n <div id=\"success-message\" class=\"select-success-message\" role=\"status\">{{ success() }}</div>\r\n } @else if (hint()) {\r\n <div id=\"hint-message\" class=\"select-hint\">{{ hint() }}</div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;max-width:18.75rem}:host.full-width{max-width:none}.select-container{width:100%;display:flex;flex-direction:column;gap:var(--form-field-gap)}.select-trigger-wrapper{position:relative}.select-label{display:block;color:var(--text-color);font-size:.875rem;font-weight:500;cursor:pointer}.select-trigger{display:flex;align-items:center;justify-content:space-between;width:100%;height:2.5rem;padding:0 1rem;background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);cursor:pointer;box-sizing:border-box}.select-trigger:focus{border-color:var(--primary-color);outline:none;box-shadow:0 0 0 .125rem var(--focus-ring-color)}:host.error .select-trigger{border-color:var(--danger-color)}:host.error .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-error)}:host.success .select-trigger{border-color:var(--success-color)}:host.success .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-success)}:host.disabled .select-trigger{background:var(--surface-ground);cursor:not-allowed;opacity:var(--opacity-disabled)}:host.filled .select-trigger{background:var(--surface-ground);border:.125rem solid var(--surface-border)}:host.filled .select-trigger:hover:not([disabled]):not(:focus){background:var(--surface-hover)}.select-value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.select-clear{background:none;border:none;cursor:pointer;padding:0;color:var(--text-color-secondary);display:flex}.select-clear:hover{color:var(--danger-color)}.select-loader-trigger{width:1rem;height:1rem;border:2px solid var(--surface-border);border-top-color:var(--primary-color);border-radius:50%;animation:spin .8s linear infinite}.select-arrow{transition:transform .2s;font-size:1rem;color:var(--text-color-secondary)}:host[aria-expanded=true] .select-arrow{transform:rotate(180deg)}.select-dropdown.open-top{top:auto;bottom:calc(100% + .25rem)}.select-dropdown{position:absolute;top:calc(100% + .25rem);left:0;right:0;z-index:var(--z-index-dropdown);background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);box-shadow:var(--shadow-lg)}.select-search{position:relative;padding:.5rem;border-bottom:.0625rem solid var(--surface-border)}.select-search-icon{position:absolute;left:1.25rem;top:50%;transform:translateY(-50%);color:var(--text-color-secondary);font-size:.875rem;pointer-events:none}.select-search input{width:100%;height:2rem;padding:0 .75rem 0 2rem;background:var(--surface-ground);border:.0625rem solid var(--surface-border);border-radius:var(--border-radius);color:var(--text-color);font-size:.875rem;outline:none;box-sizing:border-box;transition:border-color .15s}.select-search input::placeholder{color:var(--text-color-secondary)}.select-search input:focus{border-color:var(--primary-color)}.select-options{max-height:15rem;overflow-y:auto}.select-group-label{padding:.5rem 1rem;font-size:.75rem;font-weight:600;color:var(--text-color-secondary);background:var(--surface-ground)}.select-option{padding:.625rem 1rem;cursor:pointer;display:flex;align-items:flex-start;gap:.5rem}.select-option:hover{background:var(--surface-hover)}.select-option.selected{background:rgba(var(--primary-color-rgb),.1);color:var(--primary-color);font-weight:500}.select-option.focused{background:var(--surface-hover)}.select-option.disabled{opacity:var(--opacity-disabled);cursor:not-allowed}.select-option i{flex-shrink:0;line-height:1.4}.select-option-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:.125rem}.select-option-label{min-width:0}.select-option-description{font-size:.75rem;color:var(--text-color-secondary)}.select-error-message,.select-success-message,.select-hint{font-size:.875rem}.select-error-message{color:var(--danger-color)}.select-success-message{color:var(--success-color)}.select-hint{color:var(--text-color-secondary)}:host.small .select-trigger{text-overflow:ellipsis;height:2rem;font-size:.875rem}:host.large .select-trigger{text-overflow:ellipsis;height:3rem;font-size:1.125rem}@keyframes spin{to{transform:rotate(360deg)}}\n"] }]
4126
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], multiplePlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiplePlaceholder", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], success: [{ type: i0.Input, args: [{ isSignal: true, alias: "success", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], maxSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelections", required: false }] }], minSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSelections", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], searchConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchConfig", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], searched: [{ type: i0.Output, args: ["searched"] }], scrollEnd: [{ type: i0.Output, args: ["scrollEnd"] }] } });
4238
+ }, template: "<div class=\"select-container\">\r\n @if (label()) {\r\n <!-- label[for] delegates focus to its control natively; the click handler is a convenience. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\r\n <label class=\"select-label\" [attr.for]=\"selectId\" (click)=\"focusSelect()\" [class.required]=\"required()\">\r\n {{ label() }}\r\n @if (required()) { <span class=\"required-mark\">*</span> }\r\n </label>\r\n }\r\n\r\n <div class=\"select-trigger-wrapper\">\r\n <div class=\"select-trigger\"\r\n role=\"combobox\"\r\n [id]=\"selectId\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-controls]=\"selectId + '-listbox'\"\r\n [attr.aria-activedescendant]=\"activeDescendant()\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-invalid]=\"!!error()\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-busy]=\"loading()\"\r\n [attr.aria-describedby]=\"describedBy()\"\r\n [attr.tabindex]=\"disabled() || loading() ? -1 : 0\"\r\n (keydown)=\"handleKeydown($event)\"\r\n (click)=\"toggle()\">\r\n <div class=\"select-value\">\r\n <span [class.placeholder]=\"!hasValue()\">\r\n {{ selectedLabel() }}\r\n </span>\r\n </div>\r\n <div class=\"select-actions\">\r\n @if (clearable() && hasValue() && !disabled()) {\r\n <button class=\"select-clear\" (click)=\"clear($event)\">\r\n <i class=\"ph ph-x\"></i>\r\n </button>\r\n }\r\n @if (loading()) {\r\n <div class=\"select-loader-trigger\"></div>\r\n } @else {\r\n <i class=\"ph ph-caret-down select-arrow\"></i>\r\n }\r\n </div>\r\n </div>\r\n\r\n <ng-template #panelTpl>\r\n <div class=\"select-dropdown\" [class.open-top]=\"resolvedSide() === 'top'\">\r\n @if (searchable()) {\r\n <div class=\"select-search\">\r\n <i class=\"ph ph-magnifying-glass select-search-icon\"></i>\r\n <input type=\"text\"\r\n [placeholder]=\"searchConfig().placeholder\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearch($event)\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n }\r\n <div class=\"select-options\"\r\n role=\"listbox\"\r\n [id]=\"selectId + '-listbox'\"\r\n [attr.aria-multiselectable]=\"multiple() || null\"\r\n (scroll)=\"onScroll($event)\">\r\n @for (item of filteredOptions(); track getOptionKey(item)) {\r\n @if (isOptionGroup(item)) {\r\n <div class=\"select-group\" role=\"group\" [attr.aria-label]=\"item.label\">\r\n <div class=\"select-group-label\">{{ item.label }}</div>\r\n @for (opt of item.options; track opt.label) {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + opt.value\"\r\n [attr.aria-selected]=\"isSelected(opt)\"\r\n [attr.aria-disabled]=\"(item.disabled || opt.disabled) ? true : null\"\r\n [attr.tabindex]=\"(item.disabled || opt.disabled) ? -1 : null\"\r\n [class.selected]=\"isSelected(opt)\"\r\n [class.disabled]=\"item.disabled || opt.disabled\"\r\n [class.focused]=\"getFlatIndex(opt) === focusedIndex()\"\r\n (click)=\"select(opt, !!item.disabled)\">\r\n @if (opt.icon) { <i [class]=\"'ph ph-' + opt.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ opt.label }}</span>\r\n @if (opt.description) { <span class=\"select-option-description\">{{ opt.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- option in a listbox: keyboard is handled at the widget level (arrows + aria-activedescendant). -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\r\n <div class=\"select-option\"\r\n role=\"option\"\r\n [id]=\"selectId + '-' + item.value\"\r\n [attr.aria-selected]=\"isSelected(item)\"\r\n [attr.aria-disabled]=\"item.disabled ? true : null\"\r\n [attr.tabindex]=\"item.disabled ? -1 : null\"\r\n [class.selected]=\"isSelected(item)\"\r\n [class.disabled]=\"item.disabled\"\r\n [class.focused]=\"getFlatIndex(item) === focusedIndex()\"\r\n (click)=\"select(item)\">\r\n @if (item.icon) { <i [class]=\"'ph ph-' + item.icon\" aria-hidden=\"true\"></i> }\r\n <div class=\"select-option-content\">\r\n <span class=\"select-option-label\">{{ item.label }}</span>\r\n @if (item.description) { <span class=\"select-option-description\">{{ item.description }}</span> }\r\n </div>\r\n </div>\r\n }\r\n } @empty {\r\n <div class=\"select-no-results\">Aucun r\u00E9sultat</div>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n @if (error()) {\r\n <div id=\"error-message\" class=\"select-error-message\" role=\"alert\">{{ error() }}</div>\r\n } @else if (success()) {\r\n <div id=\"success-message\" class=\"select-success-message\" role=\"status\">{{ success() }}</div>\r\n } @else if (hint()) {\r\n <div id=\"hint-message\" class=\"select-hint\">{{ hint() }}</div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;max-width:18.75rem}:host.full-width{max-width:none}.select-container{width:100%;display:flex;flex-direction:column;gap:var(--form-field-gap)}.select-trigger-wrapper{position:relative}.select-label{display:block;color:var(--text-color);font-size:.875rem;font-weight:500;cursor:pointer}.select-trigger{display:flex;align-items:center;justify-content:space-between;width:100%;height:2.5rem;padding:0 1rem;background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);cursor:pointer;box-sizing:border-box}.select-trigger:focus{border-color:var(--primary-color);outline:none;box-shadow:0 0 0 .125rem var(--focus-ring-color)}:host.error .select-trigger{border-color:var(--danger-color)}:host.error .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-error)}:host.success .select-trigger{border-color:var(--success-color)}:host.success .select-trigger:focus{box-shadow:0 0 0 .125rem var(--focus-ring-success)}:host.disabled .select-trigger{background:var(--surface-ground);cursor:not-allowed;opacity:var(--opacity-disabled)}:host.filled .select-trigger{background:var(--surface-ground);border:.125rem solid var(--surface-border)}:host.filled .select-trigger:hover:not([disabled]):not(:focus){background:var(--surface-hover)}.select-value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.select-clear{background:none;border:none;cursor:pointer;padding:0;color:var(--text-color-secondary);display:flex}.select-clear:hover{color:var(--danger-color)}.select-loader-trigger{width:1rem;height:1rem;border:2px solid var(--surface-border);border-top-color:var(--primary-color);border-radius:50%;animation:spin .8s linear infinite}.select-arrow{transition:transform .2s;font-size:1rem;color:var(--text-color-secondary)}:host[aria-expanded=true] .select-arrow{transform:rotate(180deg)}.select-dropdown{display:flex;flex-direction:column;overflow:hidden;pointer-events:auto;background:var(--surface-0);border:.125rem solid var(--surface-border);border-radius:var(--border-radius);box-shadow:var(--shadow-lg)}.select-search{position:relative;flex-shrink:0;padding:.5rem;border-bottom:.0625rem solid var(--surface-border)}.select-search-icon{position:absolute;left:1.25rem;top:50%;transform:translateY(-50%);color:var(--text-color-secondary);font-size:.875rem;pointer-events:none}.select-search input{width:100%;height:2rem;padding:0 .75rem 0 2rem;background:var(--surface-ground);border:.0625rem solid var(--surface-border);border-radius:var(--border-radius);color:var(--text-color);font-size:.875rem;outline:none;box-sizing:border-box;transition:border-color .15s}.select-search input::placeholder{color:var(--text-color-secondary)}.select-search input:focus{border-color:var(--primary-color)}.select-options{flex:0 1 auto;max-height:15rem;overflow-y:auto}.select-group-label{padding:.5rem 1rem;font-size:.75rem;font-weight:600;color:var(--text-color-secondary);background:var(--surface-ground)}.select-option{padding:.625rem 1rem;cursor:pointer;display:flex;align-items:flex-start;gap:.5rem}.select-option:hover{background:var(--surface-hover)}.select-option.selected{background:rgba(var(--primary-color-rgb),.1);color:var(--primary-color);font-weight:500}.select-option.focused{background:var(--surface-hover)}.select-option.disabled{opacity:var(--opacity-disabled);cursor:not-allowed}.select-option i{flex-shrink:0;line-height:1.4}.select-option-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:.125rem}.select-option-label{min-width:0}.select-option-description{font-size:.75rem;color:var(--text-color-secondary)}.select-error-message,.select-success-message,.select-hint{font-size:.875rem}.select-error-message{color:var(--danger-color)}.select-success-message{color:var(--success-color)}.select-hint{color:var(--text-color-secondary)}:host.small .select-trigger{text-overflow:ellipsis;height:2rem;font-size:.875rem}:host.large .select-trigger{text-overflow:ellipsis;height:3rem;font-size:1.125rem}@keyframes spin{to{transform:rotate(360deg)}}\n"] }]
4239
+ }], ctorParameters: () => [], propDecorators: { panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], multiplePlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiplePlaceholder", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], success: [{ type: i0.Input, args: [{ isSignal: true, alias: "success", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], maxSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelections", required: false }] }], minSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSelections", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], searchConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchConfig", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], searched: [{ type: i0.Output, args: ["searched"] }], scrollEnd: [{ type: i0.Output, args: ["scrollEnd"] }] } });
4127
4240
 
4128
4241
  const SIDEBAR_CONFIG = new InjectionToken('SIDEBAR_CONFIG', {
4129
4242
  factory: () => ({
@@ -6153,5 +6266,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
6153
6266
  * Generated bundle index. Do not edit.
6154
6267
  */
6155
6268
 
6156
- export { CHECKBOX_CONFIG, CUSTOMER_CONTEXT_SERVICE, INPUT_LABELS, MODAL_CONFIG, ModalService, NgxTranslateProvider, PAGINATION_CONFIG, PROGRESSBAR_CONFIG, PSH_THEME_OPTIONS, PshAlertComponent, PshAvatarComponent, PshBadgeComponent, PshButtonComponent, PshCardComponent, PshCheckboxComponent, PshClickOutsideDirective, PshCollapseComponent, PshDropdownComponent, PshFlowStepComponent, PshFocusTrapDirective, PshHorizontalCardComponent, PshInfoCardComponent, PshInputComponent, PshLiveAnnouncerService, PshMenuComponent, PshModalComponent, PshOverlayPositionService, PshOverlayService, PshPaginationComponent, PshProgressbarComponent, PshRadioComponent, PshSelectComponent, PshSidebarComponent, PshSpinLoaderComponent, PshStatCardComponent, PshStateFlowIndicatorComponent, PshStepComponent, PshStepperComponent, PshSwitchComponent, PshTabBarComponent, PshTabComponent, PshTableComponent, PshTabsComponent, PshTagComponent, PshTextareaComponent, PshToastComponent, PshToastService, PshTooltipComponent, RADIO_CONFIG, RADIO_STYLES, SIDEBAR_CONFIG, SPINLOADER_CONFIG, STATE_FLOW_INDICATOR_CONFIG, STEPPER_CONFIG, SWITCH_CONFIG, ScrollService, TABLE_CONFIG, TABS_CONFIG, TAB_BAR_CONFIG, TAG_CONFIG, TEXTAREA_LABELS, TOAST_CONFIG, TOOLTIP_CONFIG, TRANSLATION_PROVIDER, ThemeService, ToastComponent, ToastService, provideTranslation };
6269
+ export { CHECKBOX_CONFIG, CUSTOMER_CONTEXT_SERVICE, INPUT_LABELS, MODAL_CONFIG, ModalService, NgxTranslateProvider, PAGINATION_CONFIG, PROGRESSBAR_CONFIG, PSH_THEME_OPTIONS, PshAlertComponent, PshAvatarComponent, PshBadgeComponent, PshButtonComponent, PshCardComponent, PshCheckboxComponent, PshClickOutsideDirective, PshCollapseComponent, PshDropdownComponent, PshFlowStepComponent, PshFocusTrapDirective, PshHorizontalCardComponent, PshInfoCardComponent, PshInputComponent, PshLiveAnnouncerService, PshMenuComponent, PshModalComponent, PshOverlayPositionService, PshOverlayService, PshPaginationComponent, PshPortalService, PshProgressbarComponent, PshRadioComponent, PshSelectComponent, PshSidebarComponent, PshSpinLoaderComponent, PshStatCardComponent, PshStateFlowIndicatorComponent, PshStepComponent, PshStepperComponent, PshSwitchComponent, PshTabBarComponent, PshTabComponent, PshTableComponent, PshTabsComponent, PshTagComponent, PshTextareaComponent, PshToastComponent, PshToastService, PshTooltipComponent, RADIO_CONFIG, RADIO_STYLES, SIDEBAR_CONFIG, SPINLOADER_CONFIG, STATE_FLOW_INDICATOR_CONFIG, STEPPER_CONFIG, SWITCH_CONFIG, ScrollService, TABLE_CONFIG, TABS_CONFIG, TAB_BAR_CONFIG, TAG_CONFIG, TEXTAREA_LABELS, TOAST_CONFIG, TOOLTIP_CONFIG, TRANSLATION_PROVIDER, ThemeService, ToastComponent, ToastService, provideTranslation };
6157
6270
  //# sourceMappingURL=ps-helix.mjs.map