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 +6 -6
- package/fesm2022/ps-helix.mjs +137 -24
- package/fesm2022/ps-helix.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/styles/tokens/effects.tokens.css +39 -38
- package/types/ps-helix.d.ts +58 -13
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
|
-
[](https://www.npmjs.com/package/ps-helix)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://angular.dev/)
|
|
8
8
|
[](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**:
|
|
63
|
-
- **npm**:
|
|
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**:
|
|
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.
|
|
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.
|
|
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
|
package/fesm2022/ps-helix.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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(() =>
|
|
3898
|
-
|
|
3899
|
-
|
|
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.
|
|
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.
|
|
3917
|
-
overlayHeight: panel
|
|
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
|