@besaitech/ng-design-system 0.0.41 → 0.0.44

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, isDevMode, effect, output, signal, forwardRef, numberAttribute, viewChildren, afterRenderEffect, viewChild, ElementRef, model, Injector, afterNextRender, Injectable, TemplateRef, Directive, contentChildren, contentChild } from '@angular/core';
2
+ import { inject, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, isDevMode, effect, output, signal, forwardRef, viewChild, numberAttribute, viewChildren, afterRenderEffect, ElementRef, model, Injector, afterNextRender, TemplateRef, Directive, contentChild, Injectable, contentChildren } from '@angular/core';
3
3
  import { DomSanitizer } from '@angular/platform-browser';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormsModule } from '@angular/forms';
@@ -48,6 +48,11 @@ const SDS_ICONS = {
48
48
  'chevron-right': '<path d="m9 18 6-6-6-6"/>',
49
49
  'chevron-left': '<path d="m15 18-6-6 6-6"/>',
50
50
  'chevron-down': '<path d="m6 9 6 6 6-6"/>',
51
+ // Stepper glyphs for sds-input-number: chevrons for the stacked spinner, plus/minus for the
52
+ // horizontal/vertical layouts.
53
+ 'chevron-up': '<path d="m18 15-6-6-6 6"/>',
54
+ plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
55
+ minus: '<path d="M5 12h14"/>',
51
56
  'chevrons-left': '<path d="m11 17-5-5 5-5"/><path d="m18 17-5-5 5-5"/>',
52
57
  'chevrons-right': '<path d="m6 17 5-5-5-5"/><path d="m13 17 5-5-5-5"/>',
53
58
  x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
@@ -267,7 +272,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
267
272
  `, styles: [".sds-spinner{width:1em;height:1em;border:2px solid currentColor;border-right-color:transparent;border-radius:9999px;animation:sds-spin .6s linear infinite}@keyframes sds-spin{to{transform:rotate(360deg)}}\n"] }]
268
273
  }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], block: [{ type: i0.Input, args: [{ isSignal: true, alias: "block", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
269
274
 
270
- let nextId$c = 0;
275
+ let nextId$d = 0;
271
276
  const SIZES$a = {
272
277
  sm: 'h-9 text-sm',
273
278
  md: 'h-10 text-sm',
@@ -280,7 +285,7 @@ const SIZES$a = {
280
285
  * sidebar search field).
281
286
  */
282
287
  class SdsInput {
283
- inputId = `sds-input-${nextId$c++}`;
288
+ inputId = `sds-input-${nextId$d++}`;
284
289
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
285
290
  /** Accessible name used when no visible [label] is provided. */
286
291
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -601,6 +606,736 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
601
606
  `, styles: ["input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus,input:-webkit-autofill:active{transition:background-color 100000s ease-in-out 0s;-webkit-text-fill-color:currentColor;caret-color:currentColor}\n"] }]
602
607
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], leadingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leadingIcon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], trailingAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingAction", required: false }] }], trailingActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionLabel", required: false }] }], trailingActionPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionPressed", required: false }] }], trailingActionClick: [{ type: i0.Output, args: ["trailingActionClick"] }] } });
603
608
 
609
+ let nextId$c = 0;
610
+ const HEIGHTS = {
611
+ sm: 'h-9',
612
+ md: 'h-10',
613
+ lg: 'h-12',
614
+ };
615
+ const TEXT = {
616
+ sm: 'text-sm',
617
+ md: 'text-sm',
618
+ lg: 'text-base',
619
+ };
620
+ /**
621
+ * Numeric input with locale-aware formatting, optional currency, prefix/suffix affixes, min/max/step
622
+ * boundaries and spinner buttons — the design-system counterpart of PrimeNG's `InputNumber`.
623
+ *
624
+ * The value is a `number | null` bound through ControlValueAccessor, so it works with reactive forms
625
+ * or `[(ngModel)]`: `<sds-input-number [(ngModel)]="amount" mode="currency" currency="EUR" />`.
626
+ *
627
+ * The field formats **live as you type**: the currency symbol, grouping separators and prefix/suffix
628
+ * stay visible on every keystroke, and the caret is kept where it belongs. The value is clamped to
629
+ * {@link min}/{@link max} on blur and on every spinner step; ArrowUp / ArrowDown also step it.
630
+ */
631
+ class SdsInputNumber {
632
+ inputId = `sds-input-number-${nextId$c++}`;
633
+ fieldRef = viewChild('field', ...(ngDevMode ? [{ debugName: "fieldRef" }] : []));
634
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
635
+ /** Accessible name used when no visible [label] is provided. */
636
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
637
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
638
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
639
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
640
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
641
+ /** `filled` is a borderless field on a muted ground; `default` is the bordered white field. */
642
+ appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
643
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
644
+ /** Read-only: the value is shown and selectable but cannot be typed or stepped. */
645
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
646
+ // --- Formatting -----------------------------------------------------------
647
+ mode = input('decimal', ...(ngDevMode ? [{ debugName: "mode" }] : []));
648
+ /** BCP 47 locale for grouping/decimal symbols (e.g. `fr-FR`). Empty follows the runtime locale. */
649
+ locale = input('', ...(ngDevMode ? [{ debugName: "locale" }] : []));
650
+ /** ISO 4217 code, required when {@link mode} is `currency` (e.g. `EUR`, `USD`). */
651
+ currency = input('', ...(ngDevMode ? [{ debugName: "currency" }] : []));
652
+ currencyDisplay = input('symbol', ...(ngDevMode ? [{ debugName: "currencyDisplay" }] : []));
653
+ /** Thousands/grouping separators. */
654
+ useGrouping = input(true, ...(ngDevMode ? [{ debugName: "useGrouping", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
655
+ minFractionDigits = input(null, ...(ngDevMode ? [{ debugName: "minFractionDigits" }] : []));
656
+ maxFractionDigits = input(null, ...(ngDevMode ? [{ debugName: "maxFractionDigits" }] : []));
657
+ /** Text shown before the value (e.g. `↑ `). */
658
+ prefix = input('', ...(ngDevMode ? [{ debugName: "prefix" }] : []));
659
+ /** Text shown after the value (e.g. ` kg`, `%`). */
660
+ suffix = input('', ...(ngDevMode ? [{ debugName: "suffix" }] : []));
661
+ // --- Boundaries & spinner -------------------------------------------------
662
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
663
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
664
+ step = input(1, ...(ngDevMode ? [{ debugName: "step", transform: numberAttribute }] : [{ transform: numberAttribute }]));
665
+ /** Show the increment/decrement spinner buttons. */
666
+ showButtons = input(false, ...(ngDevMode ? [{ debugName: "showButtons", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
667
+ buttonLayout = input('stacked', ...(ngDevMode ? [{ debugName: "buttonLayout" }] : []));
668
+ /** Override the increment button icon (defaults: chevron-up when stacked, plus otherwise). */
669
+ incrementIcon = input(null, ...(ngDevMode ? [{ debugName: "incrementIcon" }] : []));
670
+ /** Override the decrement button icon (defaults: chevron-down when stacked, minus otherwise). */
671
+ decrementIcon = input(null, ...(ngDevMode ? [{ debugName: "decrementIcon" }] : []));
672
+ /** Allow an empty (null) value. When false, blurring an empty field falls back to {@link min} or 0. */
673
+ allowEmpty = input(true, ...(ngDevMode ? [{ debugName: "allowEmpty", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
674
+ clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
675
+ /**
676
+ * Accessible names for the icon-only controls. French defaults so existing consumers are unchanged;
677
+ * translate them in a multi-language app.
678
+ */
679
+ clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
680
+ incrementLabel = input('Augmenter', ...(ngDevMode ? [{ debugName: "incrementLabel" }] : []));
681
+ decrementLabel = input('Diminuer', ...(ngDevMode ? [{ debugName: "decrementLabel" }] : []));
682
+ /** The committed numeric value (driven by CVA + user input). */
683
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
684
+ cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
685
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
686
+ describedBy = computed(() => {
687
+ if (this.error())
688
+ return `${this.inputId}-err`;
689
+ if (this.hint())
690
+ return `${this.inputId}-hint`;
691
+ return null;
692
+ }, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
693
+ clearVisible = computed(() => this.clearable() && this.value() !== null && !this.isDisabled() && !this.readonly(), ...(ngDevMode ? [{ debugName: "clearVisible" }] : []));
694
+ incrementIconName = computed(() => this.incrementIcon() ?? (this.buttonLayout() === 'stacked' ? 'chevron-up' : 'plus'), ...(ngDevMode ? [{ debugName: "incrementIconName" }] : []));
695
+ decrementIconName = computed(() => this.decrementIcon() ?? (this.buttonLayout() === 'stacked' ? 'chevron-down' : 'minus'), ...(ngDevMode ? [{ debugName: "decrementIconName" }] : []));
696
+ canIncrement = computed(() => {
697
+ if (this.isDisabled() || this.readonly())
698
+ return false;
699
+ const max = this.max();
700
+ return max === null || (this.value() ?? this.min() ?? 0) < max;
701
+ }, ...(ngDevMode ? [{ debugName: "canIncrement" }] : []));
702
+ canDecrement = computed(() => {
703
+ if (this.isDisabled() || this.readonly())
704
+ return false;
705
+ const min = this.min();
706
+ return min === null || (this.value() ?? min ?? 0) > min;
707
+ }, ...(ngDevMode ? [{ debugName: "canDecrement" }] : []));
708
+ /** Locale grouping/decimal separators, derived from Intl so parsing matches formatting. */
709
+ separators = computed(() => {
710
+ const sample = new Intl.NumberFormat(this.locale() || undefined, {
711
+ useGrouping: true,
712
+ }).formatToParts(1234567.8);
713
+ return {
714
+ group: sample.find((p) => p.type === 'group')?.value ?? ',',
715
+ decimal: sample.find((p) => p.type === 'decimal')?.value ?? '.',
716
+ };
717
+ }, ...(ngDevMode ? [{ debugName: "separators" }] : []));
718
+ /** Base Intl options (style/currency/grouping) without any fraction-digit constraints. */
719
+ baseOptions() {
720
+ const opts = { useGrouping: this.useGrouping() };
721
+ if (this.mode() === 'currency') {
722
+ opts.style = 'currency';
723
+ opts.currency = this.currency() || 'USD';
724
+ opts.currencyDisplay = this.currencyDisplay();
725
+ }
726
+ else {
727
+ opts.style = 'decimal';
728
+ }
729
+ return opts;
730
+ }
731
+ committedOptions = computed(() => {
732
+ const opts = this.baseOptions();
733
+ const min = this.minFractionDigits();
734
+ const max = this.maxFractionDigits();
735
+ if (min !== null)
736
+ opts.minimumFractionDigits = min;
737
+ if (max !== null)
738
+ opts.maximumFractionDigits = min !== null ? Math.max(max, min) : max;
739
+ return opts;
740
+ }, ...(ngDevMode ? [{ debugName: "committedOptions" }] : []));
741
+ /** The most fraction digits the field will keep — the resolved Intl maximum (2 for USD, 0 for JPY…). */
742
+ maxFrac = computed(() => {
743
+ const fallback = this.maxFractionDigits() ?? (this.mode() === 'currency' ? 2 : 3);
744
+ try {
745
+ const resolved = new Intl.NumberFormat(this.locale() || undefined, this.committedOptions()).resolvedOptions().maximumFractionDigits;
746
+ return resolved ?? fallback;
747
+ }
748
+ catch {
749
+ return fallback;
750
+ }
751
+ }, ...(ngDevMode ? [{ debugName: "maxFrac" }] : []));
752
+ wrapperClasses = computed(() => {
753
+ const vertical = this.buttonLayout() === 'vertical' && this.showButtons();
754
+ const parts = [
755
+ 'overflow-hidden rounded-control',
756
+ vertical ? 'flex flex-col' : `flex items-stretch ${HEIGHTS[this.size()]}`,
757
+ ];
758
+ if (this.isDisabled())
759
+ parts.push('opacity-60 cursor-not-allowed');
760
+ if (this.appearance() === 'filled') {
761
+ parts.push('border-transparent bg-muted text-ink');
762
+ parts.push(this.error()
763
+ ? 'border-danger focus-within:ring-2 focus-within:ring-danger/30'
764
+ : 'focus-within:ring-2 focus-within:ring-brand-500/25');
765
+ }
766
+ else {
767
+ parts.push('bg-white text-ink');
768
+ parts.push(this.error()
769
+ ? 'border-danger focus-within:border-danger focus-within:ring-2 focus-within:ring-danger/25'
770
+ : 'border-border focus-within:border-brand-500 focus-within:ring-2 focus-within:ring-brand-500/25');
771
+ }
772
+ return parts.join(' ');
773
+ }, ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
774
+ fieldCellClasses = computed(() => this.buttonLayout() === 'vertical' && this.showButtons() ? HEIGHTS[this.size()] : 'h-full', ...(ngDevMode ? [{ debugName: "fieldCellClasses" }] : []));
775
+ inputClasses = computed(() => {
776
+ const parts = [TEXT[this.size()], 'pl-3.5', this.clearVisible() ? 'pr-9' : 'pr-3.5'];
777
+ if (this.showButtons() && this.buttonLayout() !== 'stacked')
778
+ parts.push('text-center');
779
+ return parts.join(' ');
780
+ }, ...(ngDevMode ? [{ debugName: "inputClasses" }] : []));
781
+ buttonBase = 'inline-flex shrink-0 items-center justify-center text-ink-soft transition-colors ' +
782
+ 'hover:bg-muted hover:text-ink disabled:cursor-not-allowed disabled:opacity-40 select-none';
783
+ edgeButtonClasses = computed(() => this.buttonLayout() === 'vertical'
784
+ ? `${this.buttonBase} ${HEIGHTS[this.size()]} border-border`
785
+ : `${this.buttonBase} w-10 border-border`, ...(ngDevMode ? [{ debugName: "edgeButtonClasses" }] : []));
786
+ stackedButtonClasses = computed(() => this.buttonBase, ...(ngDevMode ? [{ debugName: "stackedButtonClasses" }] : []));
787
+ constructor() {
788
+ // Reflect the committed value into the field whenever it — or a formatting input — changes from
789
+ // OUTSIDE typing (initial render, writeValue, a spin while blurred, a currency/locale switch).
790
+ // While the field is the active element the input handler owns the DOM value and its caret, so this
791
+ // stands down: gating on the live `document.activeElement` (not a signal) is immune to focus-event
792
+ // timing, which is what makes live formatting safe.
793
+ effect(() => {
794
+ const el = this.fieldRef()?.nativeElement;
795
+ const v = this.value();
796
+ // Touch the formatting inputs so a currency/locale/affix change repaints a blurred field.
797
+ this.committedOptions();
798
+ this.prefix();
799
+ this.suffix();
800
+ if (!el || this.isActive(el))
801
+ return;
802
+ el.value = v === null ? '' : this.format(v);
803
+ });
804
+ }
805
+ isActive(el) {
806
+ return typeof document !== 'undefined' && document.activeElement === el;
807
+ }
808
+ /** Paint the committed value into the field imperatively (used by spin/clear/blur). */
809
+ paintCommitted() {
810
+ const el = this.fieldRef()?.nativeElement;
811
+ if (!el)
812
+ return;
813
+ const v = this.value();
814
+ el.value = v === null ? '' : this.format(v);
815
+ }
816
+ // --- Formatting / parsing helpers -----------------------------------------
817
+ /** Format a committed value with the configured min/max fraction digits (used at rest and on blur). */
818
+ format(n) {
819
+ try {
820
+ const body = new Intl.NumberFormat(this.locale() || undefined, this.committedOptions()).format(n);
821
+ return `${this.prefix()}${body}${this.suffix()}`;
822
+ }
823
+ catch {
824
+ return `${this.prefix()}${n}${this.suffix()}`;
825
+ }
826
+ }
827
+ /**
828
+ * Format a partially-typed value, keeping exactly the fraction digits the user has entered (so
829
+ * trailing zeros and a lone decimal separator survive) while still grouping the integer part and
830
+ * showing the currency symbol / affixes.
831
+ */
832
+ formatLive(p) {
833
+ const locale = this.locale() || undefined;
834
+ const fracCount = p.fracDigits.length;
835
+ const num = this.toNumber(p) ?? 0;
836
+ if (fracCount > 0 || !p.hadDecimal) {
837
+ try {
838
+ const body = new Intl.NumberFormat(locale, {
839
+ ...this.baseOptions(),
840
+ minimumFractionDigits: fracCount,
841
+ maximumFractionDigits: fracCount,
842
+ }).format(num);
843
+ return `${this.prefix()}${body}${this.suffix()}`;
844
+ }
845
+ catch {
846
+ return `${this.prefix()}${num}${this.suffix()}`;
847
+ }
848
+ }
849
+ // A lone decimal separator was just typed ("1234,"): format the integer, then splice the separator
850
+ // in right after the integer digits — which, for a trailing-symbol currency, is NOT the string end.
851
+ const dec = this.separators().decimal;
852
+ let out = '';
853
+ let inserted = false;
854
+ try {
855
+ const parts = new Intl.NumberFormat(locale, {
856
+ ...this.baseOptions(),
857
+ maximumFractionDigits: 0,
858
+ }).formatToParts(num);
859
+ parts.forEach((part, i) => {
860
+ out += part.value;
861
+ const here = part.type === 'integer' || part.type === 'group';
862
+ const next = parts[i + 1];
863
+ const nextHere = next && (next.type === 'integer' || next.type === 'group');
864
+ if (here && !nextHere && !inserted) {
865
+ out += dec;
866
+ inserted = true;
867
+ }
868
+ });
869
+ }
870
+ catch {
871
+ out = `${num}${dec}`;
872
+ inserted = true;
873
+ }
874
+ if (!inserted)
875
+ out += dec;
876
+ return `${this.prefix()}${out}${this.suffix()}`;
877
+ }
878
+ toNumber(p) {
879
+ const s = `${p.negative ? '-' : ''}${p.intDigits || '0'}${p.fracDigits ? '.' + p.fracDigits : ''}`;
880
+ const n = Number(s);
881
+ return Number.isFinite(n) ? n : null;
882
+ }
883
+ /** Pull the sign / integer / fraction fragments out of a formatted, partially-typed string. */
884
+ parseInput(raw) {
885
+ const { group, decimal } = this.separators();
886
+ let t = raw;
887
+ const prefix = this.prefix();
888
+ const suffix = this.suffix();
889
+ if (prefix && t.startsWith(prefix))
890
+ t = t.slice(prefix.length);
891
+ if (suffix && t.endsWith(suffix))
892
+ t = t.slice(0, t.length - suffix.length);
893
+ if (group)
894
+ t = t.split(group).join('');
895
+ if (decimal && decimal !== '.')
896
+ t = t.split(decimal).join('.');
897
+ t = t.replace(/[^0-9.\-]/g, '');
898
+ const negative = t.includes('-');
899
+ t = t.replace(/-/g, '');
900
+ const dot = t.indexOf('.');
901
+ const hadDecimal = dot !== -1 && this.maxFrac() > 0;
902
+ let intDigits = (hadDecimal ? t.slice(0, dot) : t.replace(/\./g, '')).replace(/\D/g, '');
903
+ let fracDigits = hadDecimal ? t.slice(dot + 1).replace(/\D/g, '') : '';
904
+ // Strip a leading run of zeros ("007" -> "7") but keep a lone "0".
905
+ intDigits = intDigits.replace(/^0+(?=\d)/, '');
906
+ fracDigits = fracDigits.slice(0, this.maxFrac());
907
+ return { negative, intDigits, fracDigits, hadDecimal };
908
+ }
909
+ /** Characters that "carry meaning" for caret placement (digits, minus, the decimal separator). */
910
+ isSignificant(ch) {
911
+ return (ch >= '0' && ch <= '9') || ch === '-' || ch === this.separators().decimal;
912
+ }
913
+ significantBefore(s, upto) {
914
+ let n = 0;
915
+ for (let i = 0; i < upto && i < s.length; i++)
916
+ if (this.isSignificant(s[i]))
917
+ n++;
918
+ return n;
919
+ }
920
+ /** The caret index in `formatted` that sits just after `target` significant characters. */
921
+ caretAfter(formatted, target) {
922
+ if (target <= 0) {
923
+ for (let i = 0; i < formatted.length; i++)
924
+ if (this.isSignificant(formatted[i]))
925
+ return i;
926
+ return formatted.length;
927
+ }
928
+ let n = 0;
929
+ for (let i = 0; i < formatted.length; i++) {
930
+ if (this.isSignificant(formatted[i]) && ++n === target)
931
+ return i + 1;
932
+ }
933
+ return formatted.length;
934
+ }
935
+ clamp(n) {
936
+ const min = this.min();
937
+ const max = this.max();
938
+ if (min !== null && n < min)
939
+ n = min;
940
+ if (max !== null && n > max)
941
+ n = max;
942
+ return n;
943
+ }
944
+ /** Trim binary-float drift (0.1 + 0.2) back to a clean decimal. */
945
+ tidy(n) {
946
+ return Math.round(n * 1e10) / 1e10;
947
+ }
948
+ // --- Interaction ----------------------------------------------------------
949
+ onInput(event) {
950
+ const el = event.target;
951
+ const raw = el.value;
952
+ const caret = el.selectionStart ?? raw.length;
953
+ const target = this.significantBefore(raw, caret);
954
+ const parsed = this.parseInput(raw);
955
+ if (parsed.intDigits === '' && parsed.fracDigits === '' && !parsed.hadDecimal) {
956
+ // Nothing numeric left — allow a lone "-" mid-entry, otherwise empty.
957
+ const text = parsed.negative ? '-' : '';
958
+ el.value = text;
959
+ el.setSelectionRange(text.length, text.length);
960
+ this.value.set(null);
961
+ this.onChange(null);
962
+ return;
963
+ }
964
+ const num = this.toNumber(parsed);
965
+ const formatted = this.formatLive(parsed);
966
+ el.value = formatted;
967
+ const next = this.caretAfter(formatted, target);
968
+ el.setSelectionRange(next, next);
969
+ this.value.set(num);
970
+ this.onChange(num);
971
+ }
972
+ onKeydown(event) {
973
+ if (event.key === 'ArrowUp') {
974
+ event.preventDefault();
975
+ this.spin(1);
976
+ }
977
+ else if (event.key === 'ArrowDown') {
978
+ event.preventDefault();
979
+ this.spin(-1);
980
+ }
981
+ }
982
+ onBlur() {
983
+ let v = this.value();
984
+ if (v === null && !this.allowEmpty())
985
+ v = this.min() ?? 0;
986
+ if (v !== null)
987
+ v = this.clamp(this.tidy(v));
988
+ // Re-render through the committed formatter (applies min-fraction padding + clamping).
989
+ this.value.set(v);
990
+ this.paintCommitted();
991
+ this.onChange(v);
992
+ this.onTouched();
993
+ }
994
+ spin(direction) {
995
+ if (this.isDisabled() || this.readonly())
996
+ return;
997
+ const base = this.value() ?? this.min() ?? 0;
998
+ const nextValue = this.clamp(this.tidy(base + direction * this.step()));
999
+ this.value.set(nextValue);
1000
+ this.onChange(nextValue);
1001
+ this.onTouched();
1002
+ // Paint the stepped value ourselves — the effect stands down while the field is focused.
1003
+ const el = this.fieldRef()?.nativeElement;
1004
+ if (el) {
1005
+ const text = this.format(nextValue);
1006
+ el.value = text;
1007
+ if (this.isActive(el))
1008
+ el.setSelectionRange(text.length, text.length);
1009
+ }
1010
+ }
1011
+ clear() {
1012
+ this.value.set(null);
1013
+ this.onChange(null);
1014
+ this.onTouched();
1015
+ const el = this.fieldRef()?.nativeElement;
1016
+ if (el)
1017
+ el.value = '';
1018
+ }
1019
+ // --- ControlValueAccessor -------------------------------------------------
1020
+ onChange = () => { };
1021
+ onTouched = () => { };
1022
+ writeValue(value) {
1023
+ this.value.set(typeof value === 'number' && Number.isFinite(value) ? value : null);
1024
+ }
1025
+ registerOnChange(fn) {
1026
+ this.onChange = fn;
1027
+ }
1028
+ registerOnTouched(fn) {
1029
+ this.onTouched = fn;
1030
+ }
1031
+ setDisabledState(isDisabled) {
1032
+ this.cvaDisabled.set(isDisabled);
1033
+ }
1034
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsInputNumber, deps: [], target: i0.ɵɵFactoryTarget.Component });
1035
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsInputNumber, isStandalone: true, selector: "sds-input-number", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, currencyDisplay: { classPropertyName: "currencyDisplay", publicName: "currencyDisplay", isSignal: true, isRequired: false, transformFunction: null }, useGrouping: { classPropertyName: "useGrouping", publicName: "useGrouping", isSignal: true, isRequired: false, transformFunction: null }, minFractionDigits: { classPropertyName: "minFractionDigits", publicName: "minFractionDigits", isSignal: true, isRequired: false, transformFunction: null }, maxFractionDigits: { classPropertyName: "maxFractionDigits", publicName: "maxFractionDigits", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, buttonLayout: { classPropertyName: "buttonLayout", publicName: "buttonLayout", isSignal: true, isRequired: false, transformFunction: null }, incrementIcon: { classPropertyName: "incrementIcon", publicName: "incrementIcon", isSignal: true, isRequired: false, transformFunction: null }, decrementIcon: { classPropertyName: "decrementIcon", publicName: "decrementIcon", isSignal: true, isRequired: false, transformFunction: null }, allowEmpty: { classPropertyName: "allowEmpty", publicName: "allowEmpty", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, incrementLabel: { classPropertyName: "incrementLabel", publicName: "incrementLabel", isSignal: true, isRequired: false, transformFunction: null }, decrementLabel: { classPropertyName: "decrementLabel", publicName: "decrementLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [
1036
+ {
1037
+ provide: NG_VALUE_ACCESSOR,
1038
+ useExisting: forwardRef(() => SdsInputNumber),
1039
+ multi: true,
1040
+ },
1041
+ ], viewQueries: [{ propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: `
1042
+ @if (label()) {
1043
+ <label [for]="inputId" class="mb-1.5 block text-sm font-medium text-ink">{{ label() }}</label>
1044
+ }
1045
+
1046
+ <div class="relative border transition-colors" [class]="wrapperClasses()">
1047
+ <!-- Horizontal: minus before the field. Vertical: plus above it. -->
1048
+ @if (showButtons() && buttonLayout() === 'horizontal') {
1049
+ <button
1050
+ type="button"
1051
+ tabindex="-1"
1052
+ [attr.aria-label]="decrementLabel()"
1053
+ [disabled]="!canDecrement()"
1054
+ class="border-r"
1055
+ [class]="edgeButtonClasses()"
1056
+ (mousedown)="$event.preventDefault()"
1057
+ (click)="spin(-1)"
1058
+ >
1059
+ <sds-icon [name]="decrementIconName()" [size]="16" />
1060
+ </button>
1061
+ }
1062
+ @if (showButtons() && buttonLayout() === 'vertical') {
1063
+ <button
1064
+ type="button"
1065
+ tabindex="-1"
1066
+ [attr.aria-label]="incrementLabel()"
1067
+ [disabled]="!canIncrement()"
1068
+ class="w-full border-b"
1069
+ [class]="edgeButtonClasses()"
1070
+ (mousedown)="$event.preventDefault()"
1071
+ (click)="spin(1)"
1072
+ >
1073
+ <sds-icon [name]="incrementIconName()" [size]="16" />
1074
+ </button>
1075
+ }
1076
+
1077
+ <div class="relative flex min-w-0 flex-1 items-center" [class]="fieldCellClasses()">
1078
+ <input
1079
+ #field
1080
+ [id]="inputId"
1081
+ type="text"
1082
+ inputmode="decimal"
1083
+ role="spinbutton"
1084
+ [placeholder]="placeholder()"
1085
+ [disabled]="isDisabled()"
1086
+ [readOnly]="readonly()"
1087
+ [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
1088
+ [attr.aria-invalid]="error() ? 'true' : null"
1089
+ [attr.aria-describedby]="describedBy()"
1090
+ [attr.aria-valuenow]="value()"
1091
+ [attr.aria-valuemin]="min()"
1092
+ [attr.aria-valuemax]="max()"
1093
+ class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
1094
+ [class]="inputClasses()"
1095
+ (input)="onInput($event)"
1096
+ (keydown)="onKeydown($event)"
1097
+ (blur)="onBlur()"
1098
+ />
1099
+
1100
+ @if (clearVisible()) {
1101
+ <button
1102
+ type="button"
1103
+ tabindex="-1"
1104
+ [attr.aria-label]="clearLabel()"
1105
+ class="absolute right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full opacity-60 hover:bg-black/5 hover:opacity-100"
1106
+ (mousedown)="$event.preventDefault()"
1107
+ (click)="clear()"
1108
+ >
1109
+ <sds-icon name="x" [size]="15" />
1110
+ </button>
1111
+ }
1112
+ </div>
1113
+
1114
+ <!-- Horizontal: plus after the field. Vertical: minus below it. -->
1115
+ @if (showButtons() && buttonLayout() === 'horizontal') {
1116
+ <button
1117
+ type="button"
1118
+ tabindex="-1"
1119
+ [attr.aria-label]="incrementLabel()"
1120
+ [disabled]="!canIncrement()"
1121
+ class="border-l"
1122
+ [class]="edgeButtonClasses()"
1123
+ (mousedown)="$event.preventDefault()"
1124
+ (click)="spin(1)"
1125
+ >
1126
+ <sds-icon [name]="incrementIconName()" [size]="16" />
1127
+ </button>
1128
+ }
1129
+ @if (showButtons() && buttonLayout() === 'vertical') {
1130
+ <button
1131
+ type="button"
1132
+ tabindex="-1"
1133
+ [attr.aria-label]="decrementLabel()"
1134
+ [disabled]="!canDecrement()"
1135
+ class="w-full border-t"
1136
+ [class]="edgeButtonClasses()"
1137
+ (mousedown)="$event.preventDefault()"
1138
+ (click)="spin(-1)"
1139
+ >
1140
+ <sds-icon [name]="decrementIconName()" [size]="16" />
1141
+ </button>
1142
+ }
1143
+
1144
+ <!-- Stacked: a chevron column at the trailing edge. -->
1145
+ @if (showButtons() && buttonLayout() === 'stacked') {
1146
+ <div class="flex w-9 shrink-0 flex-col divide-y divide-border border-l border-border">
1147
+ <button
1148
+ type="button"
1149
+ tabindex="-1"
1150
+ [attr.aria-label]="incrementLabel()"
1151
+ [disabled]="!canIncrement()"
1152
+ class="min-h-0 flex-1"
1153
+ [class]="stackedButtonClasses()"
1154
+ (mousedown)="$event.preventDefault()"
1155
+ (click)="spin(1)"
1156
+ >
1157
+ <sds-icon [name]="incrementIconName()" [size]="14" />
1158
+ </button>
1159
+ <button
1160
+ type="button"
1161
+ tabindex="-1"
1162
+ [attr.aria-label]="decrementLabel()"
1163
+ [disabled]="!canDecrement()"
1164
+ class="min-h-0 flex-1"
1165
+ [class]="stackedButtonClasses()"
1166
+ (mousedown)="$event.preventDefault()"
1167
+ (click)="spin(-1)"
1168
+ >
1169
+ <sds-icon [name]="decrementIconName()" [size]="14" />
1170
+ </button>
1171
+ </div>
1172
+ }
1173
+ </div>
1174
+
1175
+ @if (error()) {
1176
+ <p [id]="inputId + '-err'" class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
1177
+ } @else if (hint()) {
1178
+ <p [id]="inputId + '-hint'" class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
1179
+ }
1180
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1181
+ }
1182
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsInputNumber, decorators: [{
1183
+ type: Component,
1184
+ args: [{
1185
+ selector: 'sds-input-number',
1186
+ changeDetection: ChangeDetectionStrategy.OnPush,
1187
+ imports: [SdsIcon],
1188
+ providers: [
1189
+ {
1190
+ provide: NG_VALUE_ACCESSOR,
1191
+ useExisting: forwardRef(() => SdsInputNumber),
1192
+ multi: true,
1193
+ },
1194
+ ],
1195
+ host: { class: 'block' },
1196
+ template: `
1197
+ @if (label()) {
1198
+ <label [for]="inputId" class="mb-1.5 block text-sm font-medium text-ink">{{ label() }}</label>
1199
+ }
1200
+
1201
+ <div class="relative border transition-colors" [class]="wrapperClasses()">
1202
+ <!-- Horizontal: minus before the field. Vertical: plus above it. -->
1203
+ @if (showButtons() && buttonLayout() === 'horizontal') {
1204
+ <button
1205
+ type="button"
1206
+ tabindex="-1"
1207
+ [attr.aria-label]="decrementLabel()"
1208
+ [disabled]="!canDecrement()"
1209
+ class="border-r"
1210
+ [class]="edgeButtonClasses()"
1211
+ (mousedown)="$event.preventDefault()"
1212
+ (click)="spin(-1)"
1213
+ >
1214
+ <sds-icon [name]="decrementIconName()" [size]="16" />
1215
+ </button>
1216
+ }
1217
+ @if (showButtons() && buttonLayout() === 'vertical') {
1218
+ <button
1219
+ type="button"
1220
+ tabindex="-1"
1221
+ [attr.aria-label]="incrementLabel()"
1222
+ [disabled]="!canIncrement()"
1223
+ class="w-full border-b"
1224
+ [class]="edgeButtonClasses()"
1225
+ (mousedown)="$event.preventDefault()"
1226
+ (click)="spin(1)"
1227
+ >
1228
+ <sds-icon [name]="incrementIconName()" [size]="16" />
1229
+ </button>
1230
+ }
1231
+
1232
+ <div class="relative flex min-w-0 flex-1 items-center" [class]="fieldCellClasses()">
1233
+ <input
1234
+ #field
1235
+ [id]="inputId"
1236
+ type="text"
1237
+ inputmode="decimal"
1238
+ role="spinbutton"
1239
+ [placeholder]="placeholder()"
1240
+ [disabled]="isDisabled()"
1241
+ [readOnly]="readonly()"
1242
+ [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
1243
+ [attr.aria-invalid]="error() ? 'true' : null"
1244
+ [attr.aria-describedby]="describedBy()"
1245
+ [attr.aria-valuenow]="value()"
1246
+ [attr.aria-valuemin]="min()"
1247
+ [attr.aria-valuemax]="max()"
1248
+ class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
1249
+ [class]="inputClasses()"
1250
+ (input)="onInput($event)"
1251
+ (keydown)="onKeydown($event)"
1252
+ (blur)="onBlur()"
1253
+ />
1254
+
1255
+ @if (clearVisible()) {
1256
+ <button
1257
+ type="button"
1258
+ tabindex="-1"
1259
+ [attr.aria-label]="clearLabel()"
1260
+ class="absolute right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full opacity-60 hover:bg-black/5 hover:opacity-100"
1261
+ (mousedown)="$event.preventDefault()"
1262
+ (click)="clear()"
1263
+ >
1264
+ <sds-icon name="x" [size]="15" />
1265
+ </button>
1266
+ }
1267
+ </div>
1268
+
1269
+ <!-- Horizontal: plus after the field. Vertical: minus below it. -->
1270
+ @if (showButtons() && buttonLayout() === 'horizontal') {
1271
+ <button
1272
+ type="button"
1273
+ tabindex="-1"
1274
+ [attr.aria-label]="incrementLabel()"
1275
+ [disabled]="!canIncrement()"
1276
+ class="border-l"
1277
+ [class]="edgeButtonClasses()"
1278
+ (mousedown)="$event.preventDefault()"
1279
+ (click)="spin(1)"
1280
+ >
1281
+ <sds-icon [name]="incrementIconName()" [size]="16" />
1282
+ </button>
1283
+ }
1284
+ @if (showButtons() && buttonLayout() === 'vertical') {
1285
+ <button
1286
+ type="button"
1287
+ tabindex="-1"
1288
+ [attr.aria-label]="decrementLabel()"
1289
+ [disabled]="!canDecrement()"
1290
+ class="w-full border-t"
1291
+ [class]="edgeButtonClasses()"
1292
+ (mousedown)="$event.preventDefault()"
1293
+ (click)="spin(-1)"
1294
+ >
1295
+ <sds-icon [name]="decrementIconName()" [size]="16" />
1296
+ </button>
1297
+ }
1298
+
1299
+ <!-- Stacked: a chevron column at the trailing edge. -->
1300
+ @if (showButtons() && buttonLayout() === 'stacked') {
1301
+ <div class="flex w-9 shrink-0 flex-col divide-y divide-border border-l border-border">
1302
+ <button
1303
+ type="button"
1304
+ tabindex="-1"
1305
+ [attr.aria-label]="incrementLabel()"
1306
+ [disabled]="!canIncrement()"
1307
+ class="min-h-0 flex-1"
1308
+ [class]="stackedButtonClasses()"
1309
+ (mousedown)="$event.preventDefault()"
1310
+ (click)="spin(1)"
1311
+ >
1312
+ <sds-icon [name]="incrementIconName()" [size]="14" />
1313
+ </button>
1314
+ <button
1315
+ type="button"
1316
+ tabindex="-1"
1317
+ [attr.aria-label]="decrementLabel()"
1318
+ [disabled]="!canDecrement()"
1319
+ class="min-h-0 flex-1"
1320
+ [class]="stackedButtonClasses()"
1321
+ (mousedown)="$event.preventDefault()"
1322
+ (click)="spin(-1)"
1323
+ >
1324
+ <sds-icon [name]="decrementIconName()" [size]="14" />
1325
+ </button>
1326
+ </div>
1327
+ }
1328
+ </div>
1329
+
1330
+ @if (error()) {
1331
+ <p [id]="inputId + '-err'" class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
1332
+ } @else if (hint()) {
1333
+ <p [id]="inputId + '-hint'" class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
1334
+ }
1335
+ `,
1336
+ }]
1337
+ }], ctorParameters: () => [], propDecorators: { fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], currencyDisplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencyDisplay", required: false }] }], useGrouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "useGrouping", required: false }] }], minFractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "minFractionDigits", required: false }] }], maxFractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFractionDigits", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], showButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtons", required: false }] }], buttonLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLayout", required: false }] }], incrementIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementIcon", required: false }] }], decrementIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementIcon", required: false }] }], allowEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowEmpty", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], incrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementLabel", required: false }] }], decrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementLabel", required: false }] }] } });
1338
+
604
1339
  let nextId$b = 0;
605
1340
  const SIZES$9 = {
606
1341
  sm: 'min-h-9 text-sm',
@@ -3395,6 +4130,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3395
4130
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], timeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeLabel", required: false }] }], hoursLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoursLabel", required: false }] }], minutesLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "minutesLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], openCalendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "openCalendarLabel", required: false }] }], closeCalendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeCalendarLabel", required: false }] }], panelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLabel", required: false }] }], calendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarLabel", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], todayActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "todayActionLabel", required: false }] }], clearActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearActionLabel", required: false }] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdsCalendar), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
3396
4131
  let nextId$6 = 0;
3397
4132
 
4133
+ /**
4134
+ * Supplies a custom template for each option row (and the selected value shown
4135
+ * in the trigger) of an `<sds-select>` / `<sds-multi-select>`. Use as a
4136
+ * structural template projected into the select:
4137
+ *
4138
+ * @example
4139
+ * <sds-select [options]="categories" optionLabel="name" optionValue="id" [(ngModel)]="catId">
4140
+ * <ng-template sdsSelectOption let-option let-selected="selected">
4141
+ * <span class="dot" [style.background]="option.color"></span>
4142
+ * <span class="truncate">{{ option.name }}</span>
4143
+ * </ng-template>
4144
+ * </sds-select>
4145
+ */
4146
+ class SdsSelectOption {
4147
+ /** The wrapped template, rendered by the host select via ngTemplateOutlet. */
4148
+ template = inject(TemplateRef);
4149
+ /** Lets the compiler type-check `let-*` bindings against SdsSelectOptionContext. */
4150
+ static ngTemplateContextGuard(_dir, _ctx) {
4151
+ return true;
4152
+ }
4153
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelectOption, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4154
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.25", type: SdsSelectOption, isStandalone: true, selector: "ng-template[sdsSelectOption]", ngImport: i0 });
4155
+ }
4156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelectOption, decorators: [{
4157
+ type: Directive,
4158
+ args: [{
4159
+ selector: 'ng-template[sdsSelectOption]',
4160
+ standalone: true,
4161
+ }]
4162
+ }] });
4163
+ /**
4164
+ * Supplies a custom template shown in place of the default "no results" message
4165
+ * when a select has no (matching) options.
4166
+ *
4167
+ * @example
4168
+ * <ng-template sdsSelectEmpty let-q>No category matches "{{ q }}".</ng-template>
4169
+ */
4170
+ class SdsSelectEmpty {
4171
+ template = inject(TemplateRef);
4172
+ static ngTemplateContextGuard(_dir, _ctx) {
4173
+ return true;
4174
+ }
4175
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelectEmpty, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4176
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.25", type: SdsSelectEmpty, isStandalone: true, selector: "ng-template[sdsSelectEmpty]", ngImport: i0 });
4177
+ }
4178
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelectEmpty, decorators: [{
4179
+ type: Directive,
4180
+ args: [{
4181
+ selector: 'ng-template[sdsSelectEmpty]',
4182
+ standalone: true,
4183
+ }]
4184
+ }] });
4185
+ /**
4186
+ * Supplies a custom sticky footer rendered below the option list — e.g. a
4187
+ * "+ Create new" action. The `close` context callback dismisses the panel.
4188
+ *
4189
+ * @example
4190
+ * <ng-template sdsSelectFooter let-close="close">
4191
+ * <button (click)="createCategory(); close()">+ New category</button>
4192
+ * </ng-template>
4193
+ */
4194
+ class SdsSelectFooter {
4195
+ template = inject(TemplateRef);
4196
+ static ngTemplateContextGuard(_dir, _ctx) {
4197
+ return true;
4198
+ }
4199
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelectFooter, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4200
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.25", type: SdsSelectFooter, isStandalone: true, selector: "ng-template[sdsSelectFooter]", ngImport: i0 });
4201
+ }
4202
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelectFooter, decorators: [{
4203
+ type: Directive,
4204
+ args: [{
4205
+ selector: 'ng-template[sdsSelectFooter]',
4206
+ standalone: true,
4207
+ }]
4208
+ }] });
4209
+
3398
4210
  const SIZES$4 = {
3399
4211
  sm: 'h-9 text-sm',
3400
4212
  md: 'h-10 text-sm',
@@ -3411,9 +4223,25 @@ let nextId$5 = 0;
3411
4223
  * key; otherwise selection matching falls back to reference equality (a written
3412
4224
  * copy of an option will not match).
3413
4225
  *
4226
+ * Options render as plain text by default. To render richer options (an icon,
4227
+ * an avatar, a two-line row…) project template directives — the same content
4228
+ * also renders in the trigger for the selected value:
4229
+ * - `<ng-template sdsSelectOption let-option>` — one option row (see {@link SdsSelectOption});
4230
+ * context: the raw `option` (implicit), plus `label`, `value`, `selected`, `disabled`, `index`.
4231
+ * - `<ng-template sdsSelectEmpty let-query>` — replaces the "no results" message (see {@link SdsSelectEmpty}).
4232
+ * - `<ng-template sdsSelectFooter let-close="close">` — a sticky footer action, e.g. "+ New…"
4233
+ * (see {@link SdsSelectFooter}); `close()` dismisses the panel.
4234
+ *
3414
4235
  * @example
3415
4236
  * <sds-select [options]="services" optionLabel="name" optionValue="id"
3416
4237
  * placeholder="Choisir un service" [(ngModel)]="serviceId" filter clearable />
4238
+ *
4239
+ * @example
4240
+ * <sds-select [options]="categories" optionLabel="name" optionValue="id" [(ngModel)]="categoryId">
4241
+ * <ng-template sdsSelectOption let-option>
4242
+ * <span class="dot" [style.background]="option.color"></span> {{ option.name }}
4243
+ * </ng-template>
4244
+ * </sds-select>
3417
4245
  */
3418
4246
  class SdsSelect {
3419
4247
  host = inject(ElementRef);
@@ -3438,6 +4266,12 @@ class SdsSelect {
3438
4266
  filter = input(false, ...(ngDevMode ? [{ debugName: "filter", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
3439
4267
  filterPlaceholder = input('Rechercher…', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
3440
4268
  emptyMessage = input('Aucun résultat', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
4269
+ /** Optional projected `<ng-template sdsSelectOption>` — custom option + selected-value rendering. */
4270
+ optionTpl = contentChild(SdsSelectOption, ...(ngDevMode ? [{ debugName: "optionTpl" }] : []));
4271
+ /** Optional projected `<ng-template sdsSelectEmpty>` — custom no-results content. */
4272
+ emptyTpl = contentChild(SdsSelectEmpty, ...(ngDevMode ? [{ debugName: "emptyTpl" }] : []));
4273
+ /** Optional projected `<ng-template sdsSelectFooter>` — a sticky footer action. */
4274
+ footerTpl = contentChild(SdsSelectFooter, ...(ngDevMode ? [{ debugName: "footerTpl" }] : []));
3441
4275
  value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
3442
4276
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
3443
4277
  filterText = signal('', ...(ngDevMode ? [{ debugName: "filterText" }] : []));
@@ -3494,9 +4328,30 @@ class SdsSelect {
3494
4328
  const label = labelKey && labelKey in o ? String(o[labelKey]) : String(o['label'] ?? raw);
3495
4329
  const value = this.optionValue() ? o[this.optionValue()] : raw;
3496
4330
  const disabled = this.optionDisabled() ? !!o[this.optionDisabled()] : !!o['disabled'];
3497
- return { label, value, disabled };
4331
+ return { label, value, disabled, raw };
3498
4332
  }
3499
- return { label: String(raw), value: raw, disabled: false };
4333
+ return { label: String(raw), value: raw, disabled: false, raw };
4334
+ }
4335
+ /** Context for a projected option template (an option row). */
4336
+ optionContext(opt, index) {
4337
+ return {
4338
+ $implicit: opt.raw,
4339
+ option: opt.raw,
4340
+ label: opt.label,
4341
+ value: opt.value,
4342
+ selected: this.isSelected(opt.value),
4343
+ disabled: opt.disabled,
4344
+ index,
4345
+ };
4346
+ }
4347
+ /** Context for the option template reused in the trigger to render the selected value. */
4348
+ triggerContext = computed(() => {
4349
+ const sel = this.selectedOption();
4350
+ return sel ? this.optionContext(sel, -1) : null;
4351
+ }, ...(ngDevMode ? [{ debugName: "triggerContext" }] : []));
4352
+ /** Context for a projected footer template. */
4353
+ footerContext() {
4354
+ return { $implicit: this.filterText(), filterText: this.filterText(), close: () => this.close(true) };
3500
4355
  }
3501
4356
  equals(a, b) {
3502
4357
  const k = this.dataKey();
@@ -3768,7 +4623,7 @@ class SdsSelect {
3768
4623
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
3769
4624
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSelect, isStandalone: true, selector: "sds-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: false, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: false, transformFunction: null }, optionDisabled: { classPropertyName: "optionDisabled", publicName: "optionDisabled", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block" }, providers: [
3770
4625
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsSelect), multi: true },
3771
- ], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: `
4626
+ ], queries: [{ propertyName: "optionTpl", first: true, predicate: SdsSelectOption, descendants: true, isSignal: true }, { propertyName: "emptyTpl", first: true, predicate: SdsSelectEmpty, descendants: true, isSignal: true }, { propertyName: "footerTpl", first: true, predicate: SdsSelectFooter, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: `
3772
4627
  @if (label()) {
3773
4628
  <label [for]="selectId" class="mb-1.5 block text-sm font-medium text-ink">
3774
4629
  {{ label() }}
@@ -3791,12 +4646,21 @@ class SdsSelect {
3791
4646
  (click)="toggle()"
3792
4647
  (keydown)="onTriggerKeydown($event)"
3793
4648
  >
3794
- <span
3795
- class="flex-1 truncate pl-3.5"
3796
- [class]="selectedOption() ? 'text-ink' : 'text-ink-faint'"
3797
- >
3798
- {{ selectedOption() ? selectedOption()!.label : placeholder() }}
3799
- </span>
4649
+ @if (optionTpl() && selectedOption()) {
4650
+ <span class="flex flex-1 items-center gap-2 truncate pl-3.5 text-ink">
4651
+ <ng-container
4652
+ [ngTemplateOutlet]="optionTpl()!.template"
4653
+ [ngTemplateOutletContext]="triggerContext()!"
4654
+ />
4655
+ </span>
4656
+ } @else {
4657
+ <span
4658
+ class="flex-1 truncate pl-3.5"
4659
+ [class]="selectedOption() ? 'text-ink' : 'text-ink-faint'"
4660
+ >
4661
+ {{ selectedOption() ? selectedOption()!.label : placeholder() }}
4662
+ </span>
4663
+ }
3800
4664
 
3801
4665
  @if (showClear()) {
3802
4666
  <button
@@ -3854,15 +4718,42 @@ class SdsSelect {
3854
4718
  (click)="opt.disabled ? null : selectOption(opt)"
3855
4719
  (mouseenter)="opt.disabled ? null : highlightedIndex.set(i)"
3856
4720
  >
3857
- <span class="truncate">{{ opt.label }}</span>
4721
+ @if (optionTpl()) {
4722
+ <span class="flex min-w-0 flex-1 items-center gap-2">
4723
+ <ng-container
4724
+ [ngTemplateOutlet]="optionTpl()!.template"
4725
+ [ngTemplateOutletContext]="optionContext(opt, i)"
4726
+ />
4727
+ </span>
4728
+ } @else {
4729
+ <span class="truncate">{{ opt.label }}</span>
4730
+ }
3858
4731
  @if (isSelected(opt.value)) {
3859
4732
  <sds-icon name="check" [size]="16" class="shrink-0 text-brand-600" />
3860
4733
  }
3861
4734
  </li>
3862
4735
  } @empty {
3863
- <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
4736
+ @if (emptyTpl()) {
4737
+ <li class="px-3 py-2 text-sm text-ink-faint">
4738
+ <ng-container
4739
+ [ngTemplateOutlet]="emptyTpl()!.template"
4740
+ [ngTemplateOutletContext]="{ $implicit: filterText(), filterText: filterText() }"
4741
+ />
4742
+ </li>
4743
+ } @else {
4744
+ <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
4745
+ }
3864
4746
  }
3865
4747
  </ul>
4748
+
4749
+ @if (footerTpl()) {
4750
+ <div class="border-t border-border">
4751
+ <ng-container
4752
+ [ngTemplateOutlet]="footerTpl()!.template"
4753
+ [ngTemplateOutletContext]="footerContext()"
4754
+ />
4755
+ </div>
4756
+ }
3866
4757
  </div>
3867
4758
  }
3868
4759
  </div>
@@ -3873,14 +4764,14 @@ class SdsSelect {
3873
4764
  @if (error()) {
3874
4765
  <p class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
3875
4766
  }
3876
- `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4767
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3877
4768
  }
3878
4769
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelect, decorators: [{
3879
4770
  type: Component,
3880
4771
  args: [{
3881
4772
  selector: 'sds-select',
3882
4773
  changeDetection: ChangeDetectionStrategy.OnPush,
3883
- imports: [SdsIcon],
4774
+ imports: [SdsIcon, NgTemplateOutlet],
3884
4775
  providers: [
3885
4776
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsSelect), multi: true },
3886
4777
  ],
@@ -3911,12 +4802,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3911
4802
  (click)="toggle()"
3912
4803
  (keydown)="onTriggerKeydown($event)"
3913
4804
  >
3914
- <span
3915
- class="flex-1 truncate pl-3.5"
3916
- [class]="selectedOption() ? 'text-ink' : 'text-ink-faint'"
3917
- >
3918
- {{ selectedOption() ? selectedOption()!.label : placeholder() }}
3919
- </span>
4805
+ @if (optionTpl() && selectedOption()) {
4806
+ <span class="flex flex-1 items-center gap-2 truncate pl-3.5 text-ink">
4807
+ <ng-container
4808
+ [ngTemplateOutlet]="optionTpl()!.template"
4809
+ [ngTemplateOutletContext]="triggerContext()!"
4810
+ />
4811
+ </span>
4812
+ } @else {
4813
+ <span
4814
+ class="flex-1 truncate pl-3.5"
4815
+ [class]="selectedOption() ? 'text-ink' : 'text-ink-faint'"
4816
+ >
4817
+ {{ selectedOption() ? selectedOption()!.label : placeholder() }}
4818
+ </span>
4819
+ }
3920
4820
 
3921
4821
  @if (showClear()) {
3922
4822
  <button
@@ -3974,15 +4874,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3974
4874
  (click)="opt.disabled ? null : selectOption(opt)"
3975
4875
  (mouseenter)="opt.disabled ? null : highlightedIndex.set(i)"
3976
4876
  >
3977
- <span class="truncate">{{ opt.label }}</span>
4877
+ @if (optionTpl()) {
4878
+ <span class="flex min-w-0 flex-1 items-center gap-2">
4879
+ <ng-container
4880
+ [ngTemplateOutlet]="optionTpl()!.template"
4881
+ [ngTemplateOutletContext]="optionContext(opt, i)"
4882
+ />
4883
+ </span>
4884
+ } @else {
4885
+ <span class="truncate">{{ opt.label }}</span>
4886
+ }
3978
4887
  @if (isSelected(opt.value)) {
3979
4888
  <sds-icon name="check" [size]="16" class="shrink-0 text-brand-600" />
3980
4889
  }
3981
4890
  </li>
3982
4891
  } @empty {
3983
- <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
4892
+ @if (emptyTpl()) {
4893
+ <li class="px-3 py-2 text-sm text-ink-faint">
4894
+ <ng-container
4895
+ [ngTemplateOutlet]="emptyTpl()!.template"
4896
+ [ngTemplateOutletContext]="{ $implicit: filterText(), filterText: filterText() }"
4897
+ />
4898
+ </li>
4899
+ } @else {
4900
+ <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
4901
+ }
3984
4902
  }
3985
4903
  </ul>
4904
+
4905
+ @if (footerTpl()) {
4906
+ <div class="border-t border-border">
4907
+ <ng-container
4908
+ [ngTemplateOutlet]="footerTpl()!.template"
4909
+ [ngTemplateOutletContext]="footerContext()"
4910
+ />
4911
+ </div>
4912
+ }
3986
4913
  </div>
3987
4914
  }
3988
4915
  </div>
@@ -3995,7 +4922,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3995
4922
  }
3996
4923
  `,
3997
4924
  }]
3998
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
4925
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], optionTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectOption), { isSignal: true }] }], emptyTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectEmpty), { isSignal: true }] }], footerTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectFooter), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
3999
4926
 
4000
4927
  const SIZES$3 = {
4001
4928
  sm: 'min-h-9 text-sm',
@@ -4013,9 +4940,24 @@ let nextId$4 = 0;
4013
4940
  * For object options, set [optionValue] or [dataKey] so written values match by
4014
4941
  * key; otherwise matching falls back to reference equality.
4015
4942
  *
4943
+ * Project template directives to customise the panel:
4944
+ * - `<ng-template sdsSelectOption let-option>` — the content of each option row, after its
4945
+ * checkbox (see {@link SdsSelectOption}); context: the raw `option` (implicit), plus `label`,
4946
+ * `value`, `selected`, `disabled`, `index`.
4947
+ * - `<ng-template sdsSelectEmpty let-query>` — replaces the "no results" message (see {@link SdsSelectEmpty}).
4948
+ * - `<ng-template sdsSelectFooter let-close="close">` — a sticky footer action (see {@link SdsSelectFooter});
4949
+ * `close()` dismisses the panel.
4950
+ *
4016
4951
  * @example
4017
4952
  * <sds-multi-select [options]="services" optionLabel="name" optionValue="code"
4018
4953
  * display="chip" filter [(ngModel)]="codes" />
4954
+ *
4955
+ * @example
4956
+ * <sds-multi-select [options]="tags" optionLabel="name" optionValue="id" [(ngModel)]="tagIds">
4957
+ * <ng-template sdsSelectOption let-option>
4958
+ * <span class="dot" [style.background]="option.color"></span> {{ option.name }}
4959
+ * </ng-template>
4960
+ * </sds-multi-select>
4019
4961
  */
4020
4962
  class SdsMultiSelect {
4021
4963
  host = inject(ElementRef);
@@ -4043,6 +4985,12 @@ class SdsMultiSelect {
4043
4985
  filter = input(false, ...(ngDevMode ? [{ debugName: "filter", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4044
4986
  filterPlaceholder = input('Rechercher…', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
4045
4987
  emptyMessage = input('Aucun résultat', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
4988
+ /** Optional projected `<ng-template sdsSelectOption>` — custom option-row rendering. */
4989
+ optionTpl = contentChild(SdsSelectOption, ...(ngDevMode ? [{ debugName: "optionTpl" }] : []));
4990
+ /** Optional projected `<ng-template sdsSelectEmpty>` — custom no-results content. */
4991
+ emptyTpl = contentChild(SdsSelectEmpty, ...(ngDevMode ? [{ debugName: "emptyTpl" }] : []));
4992
+ /** Optional projected `<ng-template sdsSelectFooter>` — a sticky footer action. */
4993
+ footerTpl = contentChild(SdsSelectFooter, ...(ngDevMode ? [{ debugName: "footerTpl" }] : []));
4046
4994
  value = signal([], ...(ngDevMode ? [{ debugName: "value" }] : []));
4047
4995
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
4048
4996
  filterText = signal('', ...(ngDevMode ? [{ debugName: "filterText" }] : []));
@@ -4108,9 +5056,25 @@ class SdsMultiSelect {
4108
5056
  const label = labelKey && labelKey in o ? String(o[labelKey]) : String(o['label'] ?? raw);
4109
5057
  const value = this.optionValue() ? o[this.optionValue()] : raw;
4110
5058
  const disabled = this.optionDisabled() ? !!o[this.optionDisabled()] : !!o['disabled'];
4111
- return { label, value, disabled };
5059
+ return { label, value, disabled, raw };
4112
5060
  }
4113
- return { label: String(raw), value: raw, disabled: false };
5061
+ return { label: String(raw), value: raw, disabled: false, raw };
5062
+ }
5063
+ /** Context for a projected option template (an option row). */
5064
+ optionContext(opt, index) {
5065
+ return {
5066
+ $implicit: opt.raw,
5067
+ option: opt.raw,
5068
+ label: opt.label,
5069
+ value: opt.value,
5070
+ selected: this.isSelected(opt.value),
5071
+ disabled: opt.disabled,
5072
+ index,
5073
+ };
5074
+ }
5075
+ /** Context for a projected footer template. */
5076
+ footerContext() {
5077
+ return { $implicit: this.filterText(), filterText: this.filterText(), close: () => this.close(true) };
4114
5078
  }
4115
5079
  equals(a, b) {
4116
5080
  const k = this.dataKey();
@@ -4422,7 +5386,7 @@ class SdsMultiSelect {
4422
5386
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsMultiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
4423
5387
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsMultiSelect, isStandalone: true, selector: "sds-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: false, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: false, transformFunction: null }, optionDisabled: { classPropertyName: "optionDisabled", publicName: "optionDisabled", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, maxSelectedLabels: { classPropertyName: "maxSelectedLabels", publicName: "maxSelectedLabels", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsLabel: { classPropertyName: "selectedItemsLabel", publicName: "selectedItemsLabel", isSignal: true, isRequired: false, transformFunction: null }, showToggleAll: { classPropertyName: "showToggleAll", publicName: "showToggleAll", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block" }, providers: [
4424
5388
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsMultiSelect), multi: true },
4425
- ], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: `
5389
+ ], queries: [{ propertyName: "optionTpl", first: true, predicate: SdsSelectOption, descendants: true, isSignal: true }, { propertyName: "emptyTpl", first: true, predicate: SdsSelectEmpty, descendants: true, isSignal: true }, { propertyName: "footerTpl", first: true, predicate: SdsSelectFooter, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: `
4426
5390
  @if (label()) {
4427
5391
  <label [for]="selectId" class="mb-1.5 block text-sm font-medium text-ink">
4428
5392
  {{ label() }}
@@ -4555,12 +5519,39 @@ class SdsMultiSelect {
4555
5519
  <sds-icon name="check" [size]="12" />
4556
5520
  }
4557
5521
  </span>
4558
- <span class="truncate">{{ opt.label }}</span>
5522
+ @if (optionTpl()) {
5523
+ <span class="flex min-w-0 flex-1 items-center gap-2">
5524
+ <ng-container
5525
+ [ngTemplateOutlet]="optionTpl()!.template"
5526
+ [ngTemplateOutletContext]="optionContext(opt, i)"
5527
+ />
5528
+ </span>
5529
+ } @else {
5530
+ <span class="truncate">{{ opt.label }}</span>
5531
+ }
4559
5532
  </li>
4560
5533
  } @empty {
4561
- <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
5534
+ @if (emptyTpl()) {
5535
+ <li class="px-3 py-2 text-sm text-ink-faint">
5536
+ <ng-container
5537
+ [ngTemplateOutlet]="emptyTpl()!.template"
5538
+ [ngTemplateOutletContext]="{ $implicit: filterText(), filterText: filterText() }"
5539
+ />
5540
+ </li>
5541
+ } @else {
5542
+ <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
5543
+ }
4562
5544
  }
4563
5545
  </ul>
5546
+
5547
+ @if (footerTpl()) {
5548
+ <div class="border-t border-border">
5549
+ <ng-container
5550
+ [ngTemplateOutlet]="footerTpl()!.template"
5551
+ [ngTemplateOutletContext]="footerContext()"
5552
+ />
5553
+ </div>
5554
+ }
4564
5555
  </div>
4565
5556
  }
4566
5557
  </div>
@@ -4571,14 +5562,14 @@ class SdsMultiSelect {
4571
5562
  @if (error()) {
4572
5563
  <p class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
4573
5564
  }
4574
- `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5565
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4575
5566
  }
4576
5567
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsMultiSelect, decorators: [{
4577
5568
  type: Component,
4578
5569
  args: [{
4579
5570
  selector: 'sds-multi-select',
4580
5571
  changeDetection: ChangeDetectionStrategy.OnPush,
4581
- imports: [SdsIcon],
5572
+ imports: [SdsIcon, NgTemplateOutlet],
4582
5573
  providers: [
4583
5574
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsMultiSelect), multi: true },
4584
5575
  ],
@@ -4719,12 +5710,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4719
5710
  <sds-icon name="check" [size]="12" />
4720
5711
  }
4721
5712
  </span>
4722
- <span class="truncate">{{ opt.label }}</span>
5713
+ @if (optionTpl()) {
5714
+ <span class="flex min-w-0 flex-1 items-center gap-2">
5715
+ <ng-container
5716
+ [ngTemplateOutlet]="optionTpl()!.template"
5717
+ [ngTemplateOutletContext]="optionContext(opt, i)"
5718
+ />
5719
+ </span>
5720
+ } @else {
5721
+ <span class="truncate">{{ opt.label }}</span>
5722
+ }
4723
5723
  </li>
4724
5724
  } @empty {
4725
- <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
5725
+ @if (emptyTpl()) {
5726
+ <li class="px-3 py-2 text-sm text-ink-faint">
5727
+ <ng-container
5728
+ [ngTemplateOutlet]="emptyTpl()!.template"
5729
+ [ngTemplateOutletContext]="{ $implicit: filterText(), filterText: filterText() }"
5730
+ />
5731
+ </li>
5732
+ } @else {
5733
+ <li class="px-3 py-2 text-sm text-ink-faint">{{ emptyMessage() }}</li>
5734
+ }
4726
5735
  }
4727
5736
  </ul>
5737
+
5738
+ @if (footerTpl()) {
5739
+ <div class="border-t border-border">
5740
+ <ng-container
5741
+ [ngTemplateOutlet]="footerTpl()!.template"
5742
+ [ngTemplateOutletContext]="footerContext()"
5743
+ />
5744
+ </div>
5745
+ }
4728
5746
  </div>
4729
5747
  }
4730
5748
  </div>
@@ -4737,7 +5755,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4737
5755
  }
4738
5756
  `,
4739
5757
  }]
4740
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], maxSelectedLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedLabels", required: false }] }], selectedItemsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItemsLabel", required: false }] }], showToggleAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToggleAll", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
5758
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], maxSelectedLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedLabels", required: false }] }], selectedItemsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItemsLabel", required: false }] }], showToggleAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToggleAll", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], optionTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectOption), { isSignal: true }] }], emptyTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectEmpty), { isSignal: true }] }], footerTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectFooter), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
4741
5759
 
4742
5760
  const SDS_PHONE_COUNTRIES = [
4743
5761
  { iso2: 'BE', name: 'Belgique', dialCode: '32', lengths: [9], format: '### ## ## ##', example: '470123456' },
@@ -8640,5 +9658,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
8640
9658
  * Generated bundle index. Do not edit.
8641
9659
  */
8642
9660
 
8643
- export { SDS_ICONS, SDS_PHONE_COUNTRIES, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAvatar, SdsBadge, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsDatePicker, SdsIcon, SdsInput, SdsLocalizedInput, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsSelect, SdsShell, SdsSidebar, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SidebarStore, addDays, addMonths, addMonthsKeepDay, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isoDay, pad2, sdsFormatNational, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth };
9661
+ export { SDS_ICONS, SDS_PHONE_COUNTRIES, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAvatar, SdsBadge, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsDatePicker, SdsIcon, SdsInput, SdsInputNumber, SdsLocalizedInput, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsSelect, SdsSelectEmpty, SdsSelectFooter, SdsSelectOption, SdsShell, SdsSidebar, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SidebarStore, addDays, addMonths, addMonthsKeepDay, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isoDay, pad2, sdsFormatNational, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth };
8644
9662
  //# sourceMappingURL=besaitech-ng-design-system.mjs.map