@besaitech/ng-design-system 0.0.53 → 0.0.55

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, numberAttribute, output, signal, forwardRef, viewChild, viewChildren, afterRenderEffect, model, ElementRef, Directive, Injector, contentChild, untracked, afterNextRender, TemplateRef, linkedSignal, Injectable, contentChildren, DestroyRef } from '@angular/core';
2
+ import { inject, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, isDevMode, effect, numberAttribute, output, signal, forwardRef, viewChild, viewChildren, afterRenderEffect, model, ElementRef, Directive, Injector, contentChild, untracked, afterNextRender, TemplateRef, linkedSignal, DestroyRef, 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';
@@ -119,6 +119,10 @@ const SDS_ICONS = {
119
119
  'grip-vertical': '<circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/>',
120
120
  'upload': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>',
121
121
  'image': '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
122
+ // A photo that failed or is missing (sds-avatar fallbacks, photo trays).
123
+ 'image-off': '<line x1="2" x2="22" y1="2" y2="22"/><path d="M10.41 10.41a2 2 0 1 1-2.83-2.83"/><line x1="13.5" x2="6" y1="13.5" y2="21"/><line x1="18" x2="21" y1="12" y2="15"/><path d="M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59"/><path d="M21 15V5a2 2 0 0 0-2-2H9"/>',
124
+ // Fork and knife: the placeholder of a menu item without a photo.
125
+ 'utensils': '<path d="M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2"/><path d="M7 2v20"/><path d="M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7"/>',
122
126
  'zoom-in': '<circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="11" x2="11" y1="8" y2="14"/><line x1="8" x2="14" y1="11" y2="11"/>',
123
127
  'zoom-out': '<circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="8" x2="14" y1="11" y2="11"/>',
124
128
  'trash-2': '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>',
@@ -218,7 +222,7 @@ const VARIANTS$2 = {
218
222
  'dark:hover:bg-white/10 dark:active:bg-white/15',
219
223
  danger: 'bg-danger text-white shadow-sm hover:brightness-95 active:brightness-90',
220
224
  };
221
- const SIZES$b = {
225
+ const SIZES$c = {
222
226
  sm: 'h-8 px-3 text-sm',
223
227
  md: 'h-10 px-4 text-sm',
224
228
  lg: 'h-12 px-5 text-base',
@@ -278,7 +282,7 @@ class SdsButton {
278
282
  BASE,
279
283
  this.shape() === 'pill' ? 'rounded-full' : 'rounded-control',
280
284
  VARIANTS$2[this.variant()],
281
- SIZES$b[size],
285
+ SIZES$c[size],
282
286
  this.iconOnly() ? ICON_ONLY[size] : '',
283
287
  this.block() ? 'w-full' : '',
284
288
  ]
@@ -330,8 +334,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
330
334
  `, 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"] }]
331
335
  }], 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 }] }] } });
332
336
 
333
- let nextId$l = 0;
334
- const SIZES$a = {
337
+ let nextId$n = 0;
338
+ const SIZES$b = {
335
339
  sm: 'h-9 text-sm',
336
340
  md: 'h-10 text-sm',
337
341
  lg: 'h-12 text-base',
@@ -343,7 +347,7 @@ const SIZES$a = {
343
347
  * sidebar search field).
344
348
  */
345
349
  class SdsInput {
346
- inputId = `sds-input-${nextId$l++}`;
350
+ inputId = `sds-input-${nextId$n++}`;
347
351
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
348
352
  /** Accessible name used when no visible [label] is provided. */
349
353
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -380,11 +384,10 @@ class SdsInput {
380
384
  /** Hard character cap on the native field; 0 means no limit. */
381
385
  maxLength = input(0, ...(ngDevMode ? [{ debugName: "maxLength", transform: numberAttribute }] : [{ transform: numberAttribute }]));
382
386
  /**
383
- * Accessible name for the clear button. French default so existing consumers are unchanged, but an
384
- * app serving more than one language MUST translate it — it is an icon-only control, so this string
385
- * is its only name.
387
+ * Accessible name for the clear button. English default; an app serving more than one language MUST
388
+ * translate it — it is an icon-only control, so this string is its only name.
386
389
  */
387
- clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
390
+ clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
388
391
  leadingIcon = input(null, ...(ngDevMode ? [{ debugName: "leadingIcon" }] : []));
389
392
  trailingIcon = input(null, ...(ngDevMode ? [{ debugName: "trailingIcon" }] : []));
390
393
  /**
@@ -428,7 +431,7 @@ class SdsInput {
428
431
  return `${left} ${right}`;
429
432
  }, ...(ngDevMode ? [{ debugName: "fieldPadding" }] : []));
430
433
  wrapperClasses = computed(() => {
431
- const parts = [SIZES$a[this.size()], this.pill() ? 'rounded-full' : 'rounded-control'];
434
+ const parts = [SIZES$b[this.size()], this.pill() ? 'rounded-full' : 'rounded-control'];
432
435
  if (this.isDisabled())
433
436
  parts.push('opacity-60 cursor-not-allowed');
434
437
  if (this.filled()) {
@@ -676,7 +679,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
676
679
  `, 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"] }]
677
680
  }], 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 }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", 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"] }] } });
678
681
 
679
- let nextId$k = 0;
682
+ let nextId$m = 0;
680
683
  const HEIGHTS = {
681
684
  sm: 'h-9',
682
685
  md: 'h-10',
@@ -697,9 +700,14 @@ const TEXT = {
697
700
  * The field formats **live as you type**: the currency symbol, grouping separators and prefix/suffix
698
701
  * stay visible on every keystroke, and the caret is kept where it belongs. The value is clamped to
699
702
  * {@link min}/{@link max} on blur and on every spinner step; ArrowUp / ArrowDown also step it.
703
+ *
704
+ * In a locale whose decimal separator is a comma (fr-BE, nl-BE, de-DE…) a typed "." — the key most
705
+ * keyboards and numpads offer — is taken as the decimal separator: "12.50" becomes "12,50". Pasted
706
+ * text works the same way, except that a "." followed by exactly three digits in a locale that groups
707
+ * with "." ("1.234" in nl-BE) stays a thousands separator.
700
708
  */
701
709
  class SdsInputNumber {
702
- inputId = `sds-input-number-${nextId$k++}`;
710
+ inputId = `sds-input-number-${nextId$m++}`;
703
711
  fieldRef = viewChild('field', ...(ngDevMode ? [{ debugName: "fieldRef" }] : []));
704
712
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
705
713
  /** Accessible name used when no visible [label] is provided. */
@@ -707,6 +715,13 @@ class SdsInputNumber {
707
715
  placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
708
716
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
709
717
  error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
718
+ /**
719
+ * Mark the field invalid (danger frame + `aria-invalid`) without showing an [error] message — for a
720
+ * host that renders the message itself, e.g. an editable table cell.
721
+ */
722
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
723
+ /** Extra id(s) for the field's `aria-describedby`, added to its own hint/error message id. */
724
+ ariaDescribedby = input('', ...(ngDevMode ? [{ debugName: "ariaDescribedby" }] : []));
710
725
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
711
726
  /** `filled` is a borderless field on a muted ground; `default` is the bordered white field. */
712
727
  appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
@@ -743,22 +758,25 @@ class SdsInputNumber {
743
758
  allowEmpty = input(true, ...(ngDevMode ? [{ debugName: "allowEmpty", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
744
759
  clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
745
760
  /**
746
- * Accessible names for the icon-only controls. French defaults so existing consumers are unchanged;
747
- * translate them in a multi-language app.
761
+ * Accessible names for the icon-only controls. English defaults; translate them in a
762
+ * multi-language app.
748
763
  */
749
- clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
750
- incrementLabel = input('Augmenter', ...(ngDevMode ? [{ debugName: "incrementLabel" }] : []));
751
- decrementLabel = input('Diminuer', ...(ngDevMode ? [{ debugName: "decrementLabel" }] : []));
764
+ clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
765
+ incrementLabel = input('Increase', ...(ngDevMode ? [{ debugName: "incrementLabel" }] : []));
766
+ decrementLabel = input('Decrease', ...(ngDevMode ? [{ debugName: "decrementLabel" }] : []));
752
767
  /** The committed numeric value (driven by CVA + user input). */
753
768
  value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
754
769
  cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
755
770
  isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
756
771
  describedBy = computed(() => {
772
+ const ids = [];
757
773
  if (this.error())
758
- return `${this.inputId}-err`;
759
- if (this.hint())
760
- return `${this.inputId}-hint`;
761
- return null;
774
+ ids.push(`${this.inputId}-err`);
775
+ else if (this.hint())
776
+ ids.push(`${this.inputId}-hint`);
777
+ if (this.ariaDescribedby())
778
+ ids.push(this.ariaDescribedby());
779
+ return ids.length ? ids.join(' ') : null;
762
780
  }, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
763
781
  clearVisible = computed(() => this.clearable() && this.value() !== null && !this.isDisabled() && !this.readonly(), ...(ngDevMode ? [{ debugName: "clearVisible" }] : []));
764
782
  incrementIconName = computed(() => this.incrementIcon() ?? (this.buttonLayout() === 'stacked' ? 'chevron-up' : 'plus'), ...(ngDevMode ? [{ debugName: "incrementIconName" }] : []));
@@ -829,13 +847,13 @@ class SdsInputNumber {
829
847
  parts.push('opacity-60 cursor-not-allowed');
830
848
  if (this.appearance() === 'filled') {
831
849
  parts.push('border-transparent bg-muted text-ink');
832
- parts.push(this.error()
850
+ parts.push(this.error() || this.invalid()
833
851
  ? 'border-danger focus-within:ring-2 focus-within:ring-danger/30'
834
852
  : 'focus-within:ring-2 focus-within:ring-brand-500/25');
835
853
  }
836
854
  else {
837
855
  parts.push('bg-surface text-ink');
838
- parts.push(this.error()
856
+ parts.push(this.error() || this.invalid()
839
857
  ? 'border-danger focus-within:border-danger focus-within:ring-2 focus-within:ring-danger/25'
840
858
  : 'border-border focus-within:border-brand-500 focus-within:ring-2 focus-within:ring-brand-500/25');
841
859
  }
@@ -964,6 +982,38 @@ class SdsInputNumber {
964
982
  const n = Number(s);
965
983
  return Number.isFinite(n) ? n : null;
966
984
  }
985
+ /**
986
+ * In a locale whose decimal separator is not "." (fr-BE, nl-BE, de-DE…), turn the "." people type
987
+ * for a decimal point into the locale separator before parsing, so "12.50" reads as 12.5.
988
+ * - `typed` is the text of the keystroke: a "." just typed sits right before the caret and is
989
+ * always the decimal point (even in a locale that groups with ".", whose group marks come from
990
+ * the formatter, never from the keyboard).
991
+ * - Otherwise (paste, autofill, programmatic input) and only when the text holds no locale decimal
992
+ * separator, the last "." is the decimal point — unless the locale groups with "." and exactly
993
+ * three digits follow it ("1.234" in nl-BE is one thousand two hundred thirty-four).
994
+ */
995
+ localizeDecimalPoint(raw, caret, typed) {
996
+ const { group, decimal } = this.separators();
997
+ if (decimal === '.' || !raw.includes('.') || this.maxFrac() === 0)
998
+ return raw;
999
+ if (typed === '.' && raw[caret - 1] === '.') {
1000
+ return raw.slice(0, caret - 1) + decimal + raw.slice(caret);
1001
+ }
1002
+ // A keystroke without a "." leaves the formatter's own "." group marks alone.
1003
+ if ((typed !== null && !typed.includes('.')) || raw.includes(decimal))
1004
+ return raw;
1005
+ // Only a "." inside the number counts, not one in a prefix/suffix (" sq.ft").
1006
+ const prefix = this.prefix();
1007
+ const suffix = this.suffix();
1008
+ const start = prefix && raw.startsWith(prefix) ? prefix.length : 0;
1009
+ const end = suffix && raw.endsWith(suffix) ? raw.length - suffix.length : raw.length;
1010
+ const last = raw.lastIndexOf('.', end - 1);
1011
+ if (last < start)
1012
+ return raw;
1013
+ if (group === '.' && /^\d{3}(?!\d)/.test(raw.slice(last + 1, end)))
1014
+ return raw;
1015
+ return raw.slice(0, last) + decimal + raw.slice(last + 1);
1016
+ }
967
1017
  /** Pull the sign / integer / fraction fragments out of a formatted, partially-typed string. */
968
1018
  parseInput(raw) {
969
1019
  const { group, decimal } = this.separators();
@@ -1032,8 +1082,12 @@ class SdsInputNumber {
1032
1082
  // --- Interaction ----------------------------------------------------------
1033
1083
  onInput(event) {
1034
1084
  const el = event.target;
1035
- const raw = el.value;
1036
- const caret = el.selectionStart ?? raw.length;
1085
+ const caret = el.selectionStart ?? el.value.length;
1086
+ // `data` is the keystroke's text; null/undefined for a paste, deletion or a synthetic event.
1087
+ const data = event.data;
1088
+ const kind = event.inputType;
1089
+ const typed = data != null && (kind === 'insertText' || kind === 'insertCompositionText') ? data : null;
1090
+ const raw = this.localizeDecimalPoint(el.value, caret, typed);
1037
1091
  const target = this.significantBefore(raw, caret);
1038
1092
  const parsed = this.parseInput(raw);
1039
1093
  if (parsed.intDigits === '' && parsed.fracDigits === '' && !parsed.hadDecimal) {
@@ -1116,7 +1170,7 @@ class SdsInputNumber {
1116
1170
  this.cvaDisabled.set(isDisabled);
1117
1171
  }
1118
1172
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsInputNumber, deps: [], target: i0.ɵɵFactoryTarget.Component });
1119
- 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: [
1173
+ 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "ariaDescribedby", 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: [
1120
1174
  {
1121
1175
  provide: NG_VALUE_ACCESSOR,
1122
1176
  useExisting: forwardRef(() => SdsInputNumber),
@@ -1169,7 +1223,7 @@ class SdsInputNumber {
1169
1223
  [disabled]="isDisabled()"
1170
1224
  [readOnly]="readonly()"
1171
1225
  [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
1172
- [attr.aria-invalid]="error() ? 'true' : null"
1226
+ [attr.aria-invalid]="error() || invalid() ? 'true' : null"
1173
1227
  [attr.aria-describedby]="describedBy()"
1174
1228
  [attr.aria-valuenow]="value()"
1175
1229
  [attr.aria-valuemin]="min()"
@@ -1324,7 +1378,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1324
1378
  [disabled]="isDisabled()"
1325
1379
  [readOnly]="readonly()"
1326
1380
  [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
1327
- [attr.aria-invalid]="error() ? 'true' : null"
1381
+ [attr.aria-invalid]="error() || invalid() ? 'true' : null"
1328
1382
  [attr.aria-describedby]="describedBy()"
1329
1383
  [attr.aria-valuenow]="value()"
1330
1384
  [attr.aria-valuemin]="min()"
@@ -1418,9 +1472,579 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1418
1472
  }
1419
1473
  `,
1420
1474
  }]
1421
- }], 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 }] }] } });
1475
+ }], 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 }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], ariaDescribedby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedby", 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 }] }] } });
1422
1476
 
1423
- let nextId$j = 0;
1477
+ let nextId$l = 0;
1478
+ const SIZES$a = {
1479
+ sm: 'h-9 text-sm',
1480
+ md: 'h-10 text-sm',
1481
+ lg: 'h-12 text-base',
1482
+ };
1483
+ const pad2$1 = (n) => String(n).padStart(2, '0');
1484
+ /** Parse `HH:mm` or `HH:mm:ss` (the seconds are ignored). Anything else is null. */
1485
+ function parseTime(value) {
1486
+ if (typeof value !== 'string')
1487
+ return null;
1488
+ const match = /^(\d{1,2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/.exec(value.trim());
1489
+ if (!match)
1490
+ return null;
1491
+ const h = Number(match[1]);
1492
+ const m = Number(match[2]);
1493
+ if (h > 23 || m > 59)
1494
+ return null;
1495
+ return { h, m };
1496
+ }
1497
+ /**
1498
+ * Time-of-day field, 24-hour, whatever the browser's locale: two segments (hours : minutes) in one
1499
+ * framed field, the value being a `"HH:mm"` string (or null) — the shape of a backend `LocalTime`.
1500
+ * Implements ControlValueAccessor: use `formControlName` or `[(ngModel)]`.
1501
+ *
1502
+ * Unlike a native `<input type="time">` it never switches to a 12-hour clock with AM/PM, and it is themed
1503
+ * like every other field (label, hint, error, sizes, `filled`, dark mode).
1504
+ *
1505
+ * Keyboard: type digits (the hours move on to the minutes by themselves after two digits, or one digit
1506
+ * above 2); `:`/`.`/→ jump to the minutes, ← or Backspace on an empty minute go back; ↑/↓ step the hours
1507
+ * by 1 and the minutes by [minuteStep] (wrapping, never carrying into the hour). Each segment is a
1508
+ * `spinbutton` with its own accessible name. An hour typed without minutes gets `:00` when the field is
1509
+ * left; emptying both segments sets null.
1510
+ *
1511
+ * @example
1512
+ * <sds-time-input label="Opens at" formControlName="start" [minuteStep]="15" />
1513
+ *
1514
+ * @example
1515
+ * <sds-time-input [ariaLabel]="'Closes at'" [ngModel]="end()" (ngModelChange)="end.set($event)" clearable />
1516
+ */
1517
+ class SdsTimeInput {
1518
+ fieldId = `sds-time-input-${nextId$l++}`;
1519
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
1520
+ /** Accessible name of the field when there is no visible [label]. */
1521
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
1522
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
1523
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
1524
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1525
+ /** `filled` is borderless on a muted ground, like sds-input's `filled`. */
1526
+ appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
1527
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1528
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1529
+ /** Show a clear button (sets null) while there is a value. */
1530
+ clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1531
+ /** Leading clock icon. */
1532
+ showIcon = input(true, ...(ngDevMode ? [{ debugName: "showIcon", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1533
+ /** Minutes moved by ↑/↓ on the minutes segment (snaps to the step first). Default 1. */
1534
+ minuteStep = input(1, ...(ngDevMode ? [{ debugName: "minuteStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
1535
+ /** Emit `HH:mm:ss` (seconds always `00`) instead of `HH:mm`. */
1536
+ withSeconds = input(false, ...(ngDevMode ? [{ debugName: "withSeconds", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1537
+ /** Placeholder of each empty segment. */
1538
+ placeholderText = input('--', ...(ngDevMode ? [{ debugName: "placeholderText" }] : []));
1539
+ /** Accessible name of the hours segment. English default — translate it. */
1540
+ hoursLabel = input('Hours', ...(ngDevMode ? [{ debugName: "hoursLabel" }] : []));
1541
+ /** Accessible name of the minutes segment. English default — translate it. */
1542
+ minutesLabel = input('Minutes', ...(ngDevMode ? [{ debugName: "minutesLabel" }] : []));
1543
+ /** Accessible name of the clear button. English default — translate it. */
1544
+ clearLabel = input('Clear time', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
1545
+ hoursEl = viewChild.required('hoursEl');
1546
+ minutesEl = viewChild.required('minutesEl');
1547
+ hours = signal(null, ...(ngDevMode ? [{ debugName: "hours" }] : []));
1548
+ minutes = signal(null, ...(ngDevMode ? [{ debugName: "minutes" }] : []));
1549
+ /** Raw text of a segment while it is being typed (null = show the committed value). */
1550
+ hoursDraft = signal(null, ...(ngDevMode ? [{ debugName: "hoursDraft" }] : []));
1551
+ minutesDraft = signal(null, ...(ngDevMode ? [{ debugName: "minutesDraft" }] : []));
1552
+ cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
1553
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
1554
+ hasValue = computed(() => this.hours() !== null || this.minutes() !== null, ...(ngDevMode ? [{ debugName: "hasValue" }] : []));
1555
+ hoursText = computed(() => this.hoursDraft() ?? (this.hours() === null ? '' : pad2$1(this.hours())), ...(ngDevMode ? [{ debugName: "hoursText" }] : []));
1556
+ minutesText = computed(() => this.minutesDraft() ?? (this.minutes() === null ? '' : pad2$1(this.minutes())), ...(ngDevMode ? [{ debugName: "minutesText" }] : []));
1557
+ describedBy = computed(() => {
1558
+ if (this.error())
1559
+ return `${this.fieldId}-err`;
1560
+ if (this.hint())
1561
+ return `${this.fieldId}-hint`;
1562
+ return null;
1563
+ }, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
1564
+ wrapperClasses = computed(() => {
1565
+ const parts = [SIZES$a[this.size()], 'rounded-control'];
1566
+ if (this.isDisabled())
1567
+ parts.push('opacity-60 cursor-not-allowed');
1568
+ if (this.appearance() === 'filled') {
1569
+ parts.push('border-transparent bg-muted');
1570
+ parts.push(this.error()
1571
+ ? 'border-danger focus-within:ring-2 focus-within:ring-danger/30'
1572
+ : 'focus-within:ring-2 focus-within:ring-brand-500/25');
1573
+ }
1574
+ else {
1575
+ parts.push('bg-surface');
1576
+ parts.push(this.error()
1577
+ ? 'border-danger focus-within:border-danger focus-within:ring-2 focus-within:ring-danger/25'
1578
+ : 'border-border focus-within:border-brand-500 focus-within:ring-2 focus-within:ring-brand-500/25');
1579
+ }
1580
+ return parts.join(' ');
1581
+ }, ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
1582
+ pad(n) {
1583
+ return pad2$1(n);
1584
+ }
1585
+ // --- ControlValueAccessor -------------------------------------------------
1586
+ onChange = () => { };
1587
+ onTouched = () => { };
1588
+ writeValue(value) {
1589
+ const t = parseTime(value);
1590
+ this.hours.set(t ? t.h : null);
1591
+ this.minutes.set(t ? t.m : null);
1592
+ this.hoursDraft.set(null);
1593
+ this.minutesDraft.set(null);
1594
+ }
1595
+ registerOnChange(fn) {
1596
+ this.onChange = fn;
1597
+ }
1598
+ registerOnTouched(fn) {
1599
+ this.onTouched = fn;
1600
+ }
1601
+ setDisabledState(isDisabled) {
1602
+ this.cvaDisabled.set(isDisabled);
1603
+ }
1604
+ // --- typing ---------------------------------------------------------------
1605
+ selectAll(event) {
1606
+ const el = event.target;
1607
+ queueMicrotask(() => el.select());
1608
+ }
1609
+ /** Clicking the frame (icon, padding) focuses the hours, like clicking a native field. */
1610
+ onFrameClick(event) {
1611
+ if (this.isDisabled())
1612
+ return;
1613
+ const target = event.target;
1614
+ if (target.tagName !== 'INPUT' && !target.closest('button'))
1615
+ this.hoursEl().nativeElement.focus();
1616
+ }
1617
+ onHoursInput(event) {
1618
+ const el = event.target;
1619
+ const text = el.value;
1620
+ // A pasted/autofilled full time ("09:45", "9.45", "9h45") fills both segments.
1621
+ if (/[:.h]/i.test(text)) {
1622
+ const t = parseTime(text.replace(/[.h]/i, ':'));
1623
+ if (t) {
1624
+ this.commitHours(t.h);
1625
+ this.commitMinutes(t.m);
1626
+ this.syncSegments();
1627
+ this.focusMinutes();
1628
+ return;
1629
+ }
1630
+ }
1631
+ const digits = text.replace(/\D/g, '');
1632
+ if (digits === '') {
1633
+ el.value = '';
1634
+ this.hoursDraft.set('');
1635
+ this.hours.set(null);
1636
+ this.emitIfComplete();
1637
+ return;
1638
+ }
1639
+ // How many leading digits make the hour: "9" is 09 (no hour starts with 3-9 and has a second digit);
1640
+ // "1"/"2" wait for a second digit; "25" is 2 then 5 minutes. Whatever follows spills into minutes, so
1641
+ // typing "945" in one go (or a fast/IME/autofill burst) still reads as 09:45.
1642
+ let hourLength;
1643
+ if (Number(digits[0]) > 2)
1644
+ hourLength = 1;
1645
+ else if (digits.length === 1)
1646
+ hourLength = 0;
1647
+ else
1648
+ hourLength = Number(digits.slice(0, 2)) <= 23 ? 2 : 1;
1649
+ if (hourLength === 0) {
1650
+ el.value = digits;
1651
+ this.hoursDraft.set(digits);
1652
+ return;
1653
+ }
1654
+ this.commitHours(Number(digits.slice(0, hourLength)));
1655
+ el.value = this.hoursText();
1656
+ const rest = digits.slice(hourLength, hourLength + 2);
1657
+ if (rest)
1658
+ this.takeMinuteDigits(rest);
1659
+ this.focusMinutes();
1660
+ }
1661
+ onMinutesInput(event) {
1662
+ const el = event.target;
1663
+ const digits = el.value.replace(/\D/g, '').slice(0, 2);
1664
+ if (digits === '') {
1665
+ el.value = '';
1666
+ this.minutesDraft.set('');
1667
+ this.minutes.set(null);
1668
+ this.emitIfComplete();
1669
+ return;
1670
+ }
1671
+ this.takeMinuteDigits(digits);
1672
+ el.value = this.minutesText();
1673
+ }
1674
+ /** "7" (no minute starts with 6-9 and has a second digit) and any 2 digits commit; "0"-"5" wait. */
1675
+ takeMinuteDigits(digits) {
1676
+ if (digits.length >= 2 || Number(digits) > 5) {
1677
+ this.commitMinutes(Math.min(59, Number(digits.slice(0, 2))));
1678
+ }
1679
+ else {
1680
+ this.minutesDraft.set(digits);
1681
+ }
1682
+ this.minutesEl().nativeElement.value = this.minutesText();
1683
+ }
1684
+ /** Force both inputs to show the committed values (a binding does not repaint an unchanged value). */
1685
+ syncSegments() {
1686
+ this.hoursEl().nativeElement.value = this.hoursText();
1687
+ this.minutesEl().nativeElement.value = this.minutesText();
1688
+ }
1689
+ onHoursKeydown(event) {
1690
+ if (this.readonly())
1691
+ return;
1692
+ const el = event.target;
1693
+ switch (event.key) {
1694
+ case 'ArrowUp':
1695
+ case 'ArrowDown': {
1696
+ event.preventDefault();
1697
+ const delta = event.key === 'ArrowUp' ? 1 : -1;
1698
+ const current = this.draftNumber(this.hoursDraft()) ?? this.hours();
1699
+ const next = current === null ? (delta > 0 ? 0 : 23) : (current + delta + 24) % 24;
1700
+ this.commitHours(next);
1701
+ this.syncSegments();
1702
+ break;
1703
+ }
1704
+ case ':':
1705
+ case '.':
1706
+ case 'h':
1707
+ event.preventDefault();
1708
+ this.flushHours();
1709
+ this.syncSegments();
1710
+ this.focusMinutes();
1711
+ break;
1712
+ case 'ArrowRight':
1713
+ if (el.selectionStart === el.value.length) {
1714
+ event.preventDefault();
1715
+ this.flushHours();
1716
+ this.focusMinutes();
1717
+ }
1718
+ break;
1719
+ }
1720
+ }
1721
+ onMinutesKeydown(event) {
1722
+ if (this.readonly())
1723
+ return;
1724
+ const el = event.target;
1725
+ switch (event.key) {
1726
+ case 'ArrowUp':
1727
+ case 'ArrowDown': {
1728
+ event.preventDefault();
1729
+ const step = Math.max(1, Math.min(60, this.minuteStep()));
1730
+ const current = this.draftNumber(this.minutesDraft()) ?? this.minutes();
1731
+ let next;
1732
+ if (current === null) {
1733
+ next = event.key === 'ArrowUp' ? 0 : 60 - step;
1734
+ }
1735
+ else if (event.key === 'ArrowUp') {
1736
+ next = (Math.floor(current / step) + 1) * step;
1737
+ }
1738
+ else {
1739
+ next = current % step === 0 ? current - step : Math.floor(current / step) * step;
1740
+ }
1741
+ this.commitMinutes(((next % 60) + 60) % 60);
1742
+ this.syncSegments();
1743
+ break;
1744
+ }
1745
+ case 'Backspace':
1746
+ if (el.value === '') {
1747
+ event.preventDefault();
1748
+ this.focusHours();
1749
+ }
1750
+ break;
1751
+ case 'ArrowLeft':
1752
+ if (el.selectionStart === 0 && el.selectionEnd === 0) {
1753
+ event.preventDefault();
1754
+ this.flushMinutes();
1755
+ this.focusHours();
1756
+ }
1757
+ break;
1758
+ }
1759
+ }
1760
+ /** Leaving the whole field completes a half-typed time and marks the control touched. */
1761
+ onFocusOut(event) {
1762
+ const next = event.relatedTarget;
1763
+ const group = event.currentTarget;
1764
+ if (next && group.contains(next))
1765
+ return;
1766
+ this.flushHours();
1767
+ this.flushMinutes();
1768
+ if (this.hours() !== null && this.minutes() === null)
1769
+ this.commitMinutes(0);
1770
+ else if (this.hours() === null && this.minutes() !== null)
1771
+ this.commitHours(0);
1772
+ this.syncSegments();
1773
+ this.onTouched();
1774
+ }
1775
+ clear() {
1776
+ this.hours.set(null);
1777
+ this.minutes.set(null);
1778
+ this.hoursDraft.set(null);
1779
+ this.minutesDraft.set(null);
1780
+ this.onChange(null);
1781
+ this.onTouched();
1782
+ }
1783
+ // --- internals ------------------------------------------------------------
1784
+ draftNumber(draft) {
1785
+ return draft === null || draft === '' ? null : Number(draft);
1786
+ }
1787
+ flushHours() {
1788
+ const d = this.hoursDraft();
1789
+ if (d !== null && d !== '')
1790
+ this.commitHours(Math.min(23, Number(d)));
1791
+ else
1792
+ this.hoursDraft.set(null);
1793
+ }
1794
+ flushMinutes() {
1795
+ const d = this.minutesDraft();
1796
+ if (d !== null && d !== '')
1797
+ this.commitMinutes(Math.min(59, Number(d)));
1798
+ else
1799
+ this.minutesDraft.set(null);
1800
+ }
1801
+ commitHours(h) {
1802
+ this.hours.set(h);
1803
+ this.hoursDraft.set(null);
1804
+ this.emitIfComplete();
1805
+ }
1806
+ commitMinutes(m) {
1807
+ this.minutes.set(m);
1808
+ this.minutesDraft.set(null);
1809
+ this.emitIfComplete();
1810
+ }
1811
+ /** Emits `HH:mm` once both segments hold a value, and null once both are empty. */
1812
+ emitIfComplete() {
1813
+ const h = this.hours();
1814
+ const m = this.minutes();
1815
+ if (h !== null && m !== null) {
1816
+ this.onChange(`${pad2$1(h)}:${pad2$1(m)}${this.withSeconds() ? ':00' : ''}`);
1817
+ }
1818
+ else if (h === null && m === null) {
1819
+ this.onChange(null);
1820
+ }
1821
+ }
1822
+ focusMinutes() {
1823
+ const el = this.minutesEl().nativeElement;
1824
+ el.focus();
1825
+ el.select();
1826
+ }
1827
+ focusHours() {
1828
+ const el = this.hoursEl().nativeElement;
1829
+ el.focus();
1830
+ el.select();
1831
+ }
1832
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTimeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
1833
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTimeInput, isStandalone: true, selector: "sds-time-input", inputs: { 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 }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, withSeconds: { classPropertyName: "withSeconds", publicName: "withSeconds", isSignal: true, isRequired: false, transformFunction: null }, placeholderText: { classPropertyName: "placeholderText", publicName: "placeholderText", isSignal: true, isRequired: false, transformFunction: null }, hoursLabel: { classPropertyName: "hoursLabel", publicName: "hoursLabel", isSignal: true, isRequired: false, transformFunction: null }, minutesLabel: { classPropertyName: "minutesLabel", publicName: "minutesLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [
1834
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsTimeInput), multi: true },
1835
+ ], viewQueries: [{ propertyName: "hoursEl", first: true, predicate: ["hoursEl"], descendants: true, isSignal: true }, { propertyName: "minutesEl", first: true, predicate: ["minutesEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1836
+ @if (label()) {
1837
+ <label
1838
+ [id]="fieldId + '-label'"
1839
+ [for]="fieldId + '-h'"
1840
+ class="mb-1.5 block text-sm font-medium text-ink"
1841
+ >
1842
+ {{ label() }}
1843
+ </label>
1844
+ }
1845
+
1846
+ <div
1847
+ role="group"
1848
+ [attr.aria-labelledby]="label() ? fieldId + '-label' : null"
1849
+ [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
1850
+ [attr.aria-describedby]="describedBy()"
1851
+ class="relative flex items-center border transition-colors"
1852
+ [class]="wrapperClasses()"
1853
+ (focusout)="onFocusOut($event)"
1854
+ (click)="onFrameClick($event)"
1855
+ >
1856
+ @if (showIcon()) {
1857
+ <sds-icon
1858
+ name="clock"
1859
+ [size]="16"
1860
+ class="pointer-events-none ml-3 shrink-0 text-ink-faint"
1861
+ />
1862
+ }
1863
+ <div class="flex items-center tabular-nums" [class]="showIcon() ? 'pl-2' : 'pl-3.5'">
1864
+ <input
1865
+ #hoursEl
1866
+ [id]="fieldId + '-h'"
1867
+ type="text"
1868
+ inputmode="numeric"
1869
+ autocomplete="off"
1870
+ maxlength="5"
1871
+ role="spinbutton"
1872
+ aria-valuemin="0"
1873
+ aria-valuemax="23"
1874
+ [attr.aria-valuenow]="hours()"
1875
+ [attr.aria-valuetext]="hours() === null ? null : pad(hours()!)"
1876
+ [attr.aria-label]="hoursLabel()"
1877
+ [attr.aria-invalid]="error() ? 'true' : null"
1878
+ [attr.aria-describedby]="describedBy()"
1879
+ [value]="hoursText()"
1880
+ [placeholder]="placeholderText()"
1881
+ [disabled]="isDisabled()"
1882
+ [readOnly]="readonly()"
1883
+ class="w-[2.6ch] bg-transparent text-center text-ink outline-none placeholder:text-ink-faint disabled:cursor-not-allowed"
1884
+ (focus)="selectAll($event)"
1885
+ (input)="onHoursInput($event)"
1886
+ (keydown)="onHoursKeydown($event)"
1887
+ />
1888
+ <span class="px-px text-ink-soft" aria-hidden="true">:</span>
1889
+ <input
1890
+ #minutesEl
1891
+ [id]="fieldId + '-m'"
1892
+ type="text"
1893
+ inputmode="numeric"
1894
+ autocomplete="off"
1895
+ maxlength="2"
1896
+ role="spinbutton"
1897
+ aria-valuemin="0"
1898
+ aria-valuemax="59"
1899
+ [attr.aria-valuenow]="minutes()"
1900
+ [attr.aria-valuetext]="minutes() === null ? null : pad(minutes()!)"
1901
+ [attr.aria-label]="minutesLabel()"
1902
+ [attr.aria-invalid]="error() ? 'true' : null"
1903
+ [attr.aria-describedby]="describedBy()"
1904
+ [value]="minutesText()"
1905
+ [placeholder]="placeholderText()"
1906
+ [disabled]="isDisabled()"
1907
+ [readOnly]="readonly()"
1908
+ class="w-[2.6ch] bg-transparent text-center text-ink outline-none placeholder:text-ink-faint disabled:cursor-not-allowed"
1909
+ (focus)="selectAll($event)"
1910
+ (input)="onMinutesInput($event)"
1911
+ (keydown)="onMinutesKeydown($event)"
1912
+ />
1913
+ </div>
1914
+
1915
+ @if (clearable() && hasValue() && !isDisabled() && !readonly()) {
1916
+ <button
1917
+ type="button"
1918
+ tabindex="-1"
1919
+ [attr.aria-label]="clearLabel()"
1920
+ class="ml-auto mr-2 inline-flex h-6 w-6 items-center justify-center rounded-full text-ink-faint hover:bg-black/5 hover:text-ink-soft dark:hover:bg-white/10"
1921
+ (click)="clear()"
1922
+ >
1923
+ <sds-icon name="x" [size]="15" />
1924
+ </button>
1925
+ }
1926
+ </div>
1927
+
1928
+ @if (error()) {
1929
+ <p [id]="fieldId + '-err'" class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
1930
+ } @else if (hint()) {
1931
+ <p [id]="fieldId + '-hint'" class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
1932
+ }
1933
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1934
+ }
1935
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTimeInput, decorators: [{
1936
+ type: Component,
1937
+ args: [{
1938
+ selector: 'sds-time-input',
1939
+ changeDetection: ChangeDetectionStrategy.OnPush,
1940
+ imports: [SdsIcon],
1941
+ providers: [
1942
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsTimeInput), multi: true },
1943
+ ],
1944
+ host: { class: 'block' },
1945
+ template: `
1946
+ @if (label()) {
1947
+ <label
1948
+ [id]="fieldId + '-label'"
1949
+ [for]="fieldId + '-h'"
1950
+ class="mb-1.5 block text-sm font-medium text-ink"
1951
+ >
1952
+ {{ label() }}
1953
+ </label>
1954
+ }
1955
+
1956
+ <div
1957
+ role="group"
1958
+ [attr.aria-labelledby]="label() ? fieldId + '-label' : null"
1959
+ [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
1960
+ [attr.aria-describedby]="describedBy()"
1961
+ class="relative flex items-center border transition-colors"
1962
+ [class]="wrapperClasses()"
1963
+ (focusout)="onFocusOut($event)"
1964
+ (click)="onFrameClick($event)"
1965
+ >
1966
+ @if (showIcon()) {
1967
+ <sds-icon
1968
+ name="clock"
1969
+ [size]="16"
1970
+ class="pointer-events-none ml-3 shrink-0 text-ink-faint"
1971
+ />
1972
+ }
1973
+ <div class="flex items-center tabular-nums" [class]="showIcon() ? 'pl-2' : 'pl-3.5'">
1974
+ <input
1975
+ #hoursEl
1976
+ [id]="fieldId + '-h'"
1977
+ type="text"
1978
+ inputmode="numeric"
1979
+ autocomplete="off"
1980
+ maxlength="5"
1981
+ role="spinbutton"
1982
+ aria-valuemin="0"
1983
+ aria-valuemax="23"
1984
+ [attr.aria-valuenow]="hours()"
1985
+ [attr.aria-valuetext]="hours() === null ? null : pad(hours()!)"
1986
+ [attr.aria-label]="hoursLabel()"
1987
+ [attr.aria-invalid]="error() ? 'true' : null"
1988
+ [attr.aria-describedby]="describedBy()"
1989
+ [value]="hoursText()"
1990
+ [placeholder]="placeholderText()"
1991
+ [disabled]="isDisabled()"
1992
+ [readOnly]="readonly()"
1993
+ class="w-[2.6ch] bg-transparent text-center text-ink outline-none placeholder:text-ink-faint disabled:cursor-not-allowed"
1994
+ (focus)="selectAll($event)"
1995
+ (input)="onHoursInput($event)"
1996
+ (keydown)="onHoursKeydown($event)"
1997
+ />
1998
+ <span class="px-px text-ink-soft" aria-hidden="true">:</span>
1999
+ <input
2000
+ #minutesEl
2001
+ [id]="fieldId + '-m'"
2002
+ type="text"
2003
+ inputmode="numeric"
2004
+ autocomplete="off"
2005
+ maxlength="2"
2006
+ role="spinbutton"
2007
+ aria-valuemin="0"
2008
+ aria-valuemax="59"
2009
+ [attr.aria-valuenow]="minutes()"
2010
+ [attr.aria-valuetext]="minutes() === null ? null : pad(minutes()!)"
2011
+ [attr.aria-label]="minutesLabel()"
2012
+ [attr.aria-invalid]="error() ? 'true' : null"
2013
+ [attr.aria-describedby]="describedBy()"
2014
+ [value]="minutesText()"
2015
+ [placeholder]="placeholderText()"
2016
+ [disabled]="isDisabled()"
2017
+ [readOnly]="readonly()"
2018
+ class="w-[2.6ch] bg-transparent text-center text-ink outline-none placeholder:text-ink-faint disabled:cursor-not-allowed"
2019
+ (focus)="selectAll($event)"
2020
+ (input)="onMinutesInput($event)"
2021
+ (keydown)="onMinutesKeydown($event)"
2022
+ />
2023
+ </div>
2024
+
2025
+ @if (clearable() && hasValue() && !isDisabled() && !readonly()) {
2026
+ <button
2027
+ type="button"
2028
+ tabindex="-1"
2029
+ [attr.aria-label]="clearLabel()"
2030
+ class="ml-auto mr-2 inline-flex h-6 w-6 items-center justify-center rounded-full text-ink-faint hover:bg-black/5 hover:text-ink-soft dark:hover:bg-white/10"
2031
+ (click)="clear()"
2032
+ >
2033
+ <sds-icon name="x" [size]="15" />
2034
+ </button>
2035
+ }
2036
+ </div>
2037
+
2038
+ @if (error()) {
2039
+ <p [id]="fieldId + '-err'" class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
2040
+ } @else if (hint()) {
2041
+ <p [id]="fieldId + '-hint'" class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
2042
+ }
2043
+ `,
2044
+ }]
2045
+ }], propDecorators: { 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 }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], withSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "withSeconds", required: false }] }], placeholderText: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderText", required: false }] }], hoursLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoursLabel", required: false }] }], minutesLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "minutesLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], hoursEl: [{ type: i0.ViewChild, args: ['hoursEl', { isSignal: true }] }], minutesEl: [{ type: i0.ViewChild, args: ['minutesEl', { isSignal: true }] }] } });
2046
+
2047
+ let nextId$k = 0;
1424
2048
  const SIZES$9 = {
1425
2049
  sm: 'min-h-9 text-sm',
1426
2050
  md: 'min-h-10 text-sm',
@@ -1440,7 +2064,7 @@ const SIZES$9 = {
1440
2064
  * `<sds-localized-input [languages]="['fr','nl','en']" [(ngModel)]="title" />`.
1441
2065
  */
1442
2066
  class SdsLocalizedInput {
1443
- inputId = `sds-localized-input-${nextId$j++}`;
2067
+ inputId = `sds-localized-input-${nextId$k++}`;
1444
2068
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
1445
2069
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
1446
2070
  placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
@@ -1454,11 +2078,11 @@ class SdsLocalizedInput {
1454
2078
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1455
2079
  /**
1456
2080
  * Accessible name for the in-field language switcher, and the "no translation yet" title suffix on each
1457
- * language button. French defaults so existing consumers are unchanged; translate them in a multi-language
1458
- * app (the buttons are letter-only, so the title carries their meaning).
2081
+ * language button. English defaults; translate them in a multi-language app (the buttons are
2082
+ * letter-only, so the title carries their meaning).
1459
2083
  */
1460
- languageSwitcherLabel = input('Langue', ...(ngDevMode ? [{ debugName: "languageSwitcherLabel" }] : []));
1461
- untranslatedLabel = input('à traduire', ...(ngDevMode ? [{ debugName: "untranslatedLabel" }] : []));
2084
+ languageSwitcherLabel = input('Language', ...(ngDevMode ? [{ debugName: "languageSwitcherLabel" }] : []));
2085
+ untranslatedLabel = input('not translated yet', ...(ngDevMode ? [{ debugName: "untranslatedLabel" }] : []));
1462
2086
  valueMap = signal({}, ...(ngDevMode ? [{ debugName: "valueMap" }] : []));
1463
2087
  /** Language chosen via the switcher; null means "follow currentLanguage / the first language". */
1464
2088
  manual = signal(null, ...(ngDevMode ? [{ debugName: "manual" }] : []));
@@ -1696,7 +2320,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1696
2320
  }]
1697
2321
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], languages: [{ type: i0.Input, args: [{ isSignal: true, alias: "languages", required: false }] }], currentLanguage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentLanguage", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], languageSwitcherLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "languageSwitcherLabel", required: false }] }], untranslatedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "untranslatedLabel", required: false }] }] } });
1698
2322
 
1699
- let nextId$i = 0;
2323
+ let nextId$j = 0;
1700
2324
  const SIZES$8 = {
1701
2325
  md: 'h-12 w-11 text-lg',
1702
2326
  lg: 'h-14 w-12 text-xl',
@@ -1713,7 +2337,7 @@ const SIZES$8 = {
1713
2337
  * first box and have it land correctly.
1714
2338
  */
1715
2339
  class SdsOtpInput {
1716
- groupId = `sds-otp-${nextId$i++}`;
2340
+ groupId = `sds-otp-${nextId$j++}`;
1717
2341
  /** How many boxes / characters the code has. */
1718
2342
  length = input(6, ...(ngDevMode ? [{ debugName: "length", transform: numberAttribute }] : [{ transform: numberAttribute }]));
1719
2343
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
@@ -1729,10 +2353,10 @@ class SdsOtpInput {
1729
2353
  autoFocus = input(false, ...(ngDevMode ? [{ debugName: "autoFocus", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
1730
2354
  /**
1731
2355
  * Accessible name for an individual box, suffixed with its 1-based position ("Digit 1", "Digit
1732
- * 2", …). French default so existing consumers are unchanged; translate it in an app serving more
1733
- * than one language, since each box is an icon-less control whose only name this is.
2356
+ * 2", …). English default; translate it in an app serving more than one language, since each box
2357
+ * is an icon-less control whose only name this is.
1734
2358
  */
1735
- cellLabel = input('Chiffre', ...(ngDevMode ? [{ debugName: "cellLabel" }] : []));
2359
+ cellLabel = input('Digit', ...(ngDevMode ? [{ debugName: "cellLabel" }] : []));
1736
2360
  /** Emitted with the full value the moment the last box is filled. */
1737
2361
  completed = output();
1738
2362
  cells = viewChildren('cell', ...(ngDevMode ? [{ debugName: "cells" }] : []));
@@ -2013,7 +2637,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2013
2637
  }]
2014
2638
  }], ctorParameters: () => [], propDecorators: { length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", 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 }] }], inputMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputMode", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], cellLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellLabel", required: false }] }], completed: [{ type: i0.Output, args: ["completed"] }], cells: [{ type: i0.ViewChildren, args: ['cell', { isSignal: true }] }] } });
2015
2639
 
2016
- let nextId$h = 0;
2640
+ let nextId$i = 0;
2017
2641
  const SIZES$7 = {
2018
2642
  sm: 'text-sm px-3 py-2',
2019
2643
  md: 'text-sm px-3.5 py-2.5',
@@ -2025,7 +2649,7 @@ const SIZES$7 = {
2025
2649
  * [(ngModel)].
2026
2650
  */
2027
2651
  class SdsTextarea {
2028
- fieldId = `sds-textarea-${nextId$h++}`;
2652
+ fieldId = `sds-textarea-${nextId$i++}`;
2029
2653
  field = viewChild('field', ...(ngDevMode ? [{ debugName: "field" }] : []));
2030
2654
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2031
2655
  /** Accessible name used when no visible [label] is provided. */
@@ -2050,6 +2674,13 @@ class SdsTextarea {
2050
2674
  maxLength = input(0, ...(ngDevMode ? [{ debugName: "maxLength", transform: numberAttribute }] : [{ transform: numberAttribute }]));
2051
2675
  /** Show a character counter row. */
2052
2676
  showCount = input(false, ...(ngDevMode ? [{ debugName: "showCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
2677
+ /**
2678
+ * Spoken text of the counter when {@link maxLength} is set; tokens `{count}` and `{max}`. English
2679
+ * default — translate it in a multi-language app.
2680
+ */
2681
+ countLimitLabel = input('{count} of {max} characters used', ...(ngDevMode ? [{ debugName: "countLimitLabel" }] : []));
2682
+ /** Spoken text of the counter without a {@link maxLength}; token `{count}`. */
2683
+ countLabelTemplate = input('{count} characters', ...(ngDevMode ? [{ debugName: "countLabelTemplate" }] : []));
2053
2684
  resize = input('vertical', ...(ngDevMode ? [{ debugName: "resize" }] : []));
2054
2685
  /** Current value (driven by CVA + user input). */
2055
2686
  value = signal('', ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -2058,9 +2689,9 @@ class SdsTextarea {
2058
2689
  count = computed(() => this.value().length, ...(ngDevMode ? [{ debugName: "count" }] : []));
2059
2690
  hasCounter = computed(() => this.showCount() || this.maxLength() > 0, ...(ngDevMode ? [{ debugName: "hasCounter" }] : []));
2060
2691
  /** Spoken description of the counter for assistive tech. */
2061
- countLabel = computed(() => this.maxLength()
2062
- ? `${this.count()} sur ${this.maxLength()} caractères utilisés`
2063
- : `${this.count()} caractère(s)`, ...(ngDevMode ? [{ debugName: "countLabel" }] : []));
2692
+ countLabel = computed(() => (this.maxLength() ? this.countLimitLabel() : this.countLabelTemplate())
2693
+ .replace('{count}', String(this.count()))
2694
+ .replace('{max}', String(this.maxLength())), ...(ngDevMode ? [{ debugName: "countLabel" }] : []));
2064
2695
  describedBy = computed(() => {
2065
2696
  const ids = [];
2066
2697
  if (this.error())
@@ -2137,7 +2768,7 @@ class SdsTextarea {
2137
2768
  el.style.height = `${el.scrollHeight}px`;
2138
2769
  }
2139
2770
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
2140
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTextarea, isStandalone: true, selector: "sds-textarea", 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 }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, autoResize: { classPropertyName: "autoResize", publicName: "autoResize", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null }, resize: { classPropertyName: "resize", publicName: "resize", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [
2771
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTextarea, isStandalone: true, selector: "sds-textarea", 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 }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, autoResize: { classPropertyName: "autoResize", publicName: "autoResize", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null }, countLimitLabel: { classPropertyName: "countLimitLabel", publicName: "countLimitLabel", isSignal: true, isRequired: false, transformFunction: null }, countLabelTemplate: { classPropertyName: "countLabelTemplate", publicName: "countLabelTemplate", isSignal: true, isRequired: false, transformFunction: null }, resize: { classPropertyName: "resize", publicName: "resize", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [
2141
2772
  {
2142
2773
  provide: NG_VALUE_ACCESSOR,
2143
2774
  useExisting: forwardRef(() => SdsTextarea),
@@ -2276,9 +2907,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2276
2907
  }
2277
2908
  `,
2278
2909
  }]
2279
- }], ctorParameters: () => [], propDecorators: { field: [{ 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 }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], autoResize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoResize", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", required: false }] }], resize: [{ type: i0.Input, args: [{ isSignal: true, alias: "resize", required: false }] }] } });
2910
+ }], ctorParameters: () => [], propDecorators: { field: [{ 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 }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], autoResize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoResize", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", required: false }] }], countLimitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "countLimitLabel", required: false }] }], countLabelTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "countLabelTemplate", required: false }] }], resize: [{ type: i0.Input, args: [{ isSignal: true, alias: "resize", required: false }] }] } });
2280
2911
 
2281
- let nextId$g = 0;
2912
+ let nextId$h = 0;
2282
2913
  const SIZES$6 = {
2283
2914
  sm: { track: 'h-5 w-9', knob: 'h-4 w-4', on: 'translate-x-4', icon: 11 },
2284
2915
  md: { track: 'h-6 w-11', knob: 'h-5 w-5', on: 'translate-x-5', icon: 13 },
@@ -2291,7 +2922,7 @@ const SIZES$6 = {
2291
2922
  * states.
2292
2923
  */
2293
2924
  class SdsToggleSwitch {
2294
- fieldId = `sds-toggle-switch-${nextId$g++}`;
2925
+ fieldId = `sds-toggle-switch-${nextId$h++}`;
2295
2926
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2296
2927
  /** Accessible name used when no visible [label] is provided. */
2297
2928
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -2535,7 +3166,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2535
3166
  }]
2536
3167
  }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], trueValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "trueValue", required: false }] }], falseValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "falseValue", required: false }] }], onIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "onIcon", required: false }] }], offIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "offIcon", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }] } });
2537
3168
 
2538
- let nextId$f = 0;
3169
+ let nextId$g = 0;
2539
3170
  /**
2540
3171
  * Boolean checkbox. Implements ControlValueAccessor — use with reactive forms
2541
3172
  * or `[(ngModel)]`. Renders a styled box (brand-filled with a check when on)
@@ -2543,24 +3174,33 @@ let nextId$f = 0;
2543
3174
  * of its own, so it can sit inside the consumer's `<label>` (a whole clickable
2544
3175
  * row), whose text then names it.
2545
3176
  *
3177
+ * Set [indeterminate] for a "some but not all" state (e.g. a select-all box over a partial
3178
+ * selection): the box shows a bar and reads as "mixed"; a click checks it.
3179
+ *
2546
3180
  * @example
2547
- * <sds-checkbox label="Fusion automatique" [(ngModel)]="autoMerge" />
3181
+ * <sds-checkbox label="Auto-merge" [(ngModel)]="autoMerge" />
2548
3182
  */
2549
3183
  class SdsCheckbox {
2550
- id = `sds-checkbox-${nextId$f++}`;
3184
+ id = `sds-checkbox-${nextId$g++}`;
2551
3185
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2552
3186
  /** Accessible name used when no visible [label] is provided. */
2553
3187
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
2554
3188
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
2555
3189
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
3190
+ /**
3191
+ * "Some but not all": shows a bar and is announced as mixed while the value is false. Clicking
3192
+ * checks it (the native behaviour); the host then decides the next [indeterminate].
3193
+ */
3194
+ indeterminate = input(false, ...(ngDevMode ? [{ debugName: "indeterminate", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
2556
3195
  checked = signal(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
3196
+ mixed = computed(() => this.indeterminate() && !this.checked(), ...(ngDevMode ? [{ debugName: "mixed" }] : []));
2557
3197
  cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
2558
3198
  isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2559
3199
  iconSize = computed(() => (this.size() === 'sm' ? 11 : 13), ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
2560
3200
  stateClasses = computed(() => this.isDisabled() ? 'cursor-not-allowed opacity-60' : 'cursor-pointer', ...(ngDevMode ? [{ debugName: "stateClasses" }] : []));
2561
3201
  boxClasses = computed(() => {
2562
3202
  const size = this.size() === 'sm' ? 'h-4 w-4' : 'h-5 w-5';
2563
- const state = this.checked()
3203
+ const state = this.checked() || this.mixed()
2564
3204
  ? 'border-brand-600 bg-brand-600 text-white'
2565
3205
  : 'border-border bg-surface text-transparent';
2566
3206
  const focus = 'focus-within:ring-2 focus-within:ring-brand-500/30 focus-within:border-brand-500';
@@ -2587,7 +3227,7 @@ class SdsCheckbox {
2587
3227
  this.onChange(next);
2588
3228
  }
2589
3229
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
2590
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsCheckbox, isStandalone: true, selector: "sds-checkbox", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "inline-flex" }, providers: [
3230
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsCheckbox, isStandalone: true, selector: "sds-checkbox", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "inline-flex align-middle" }, providers: [
2591
3231
  {
2592
3232
  provide: NG_VALUE_ACCESSOR,
2593
3233
  useExisting: forwardRef(() => SdsCheckbox),
@@ -2617,6 +3257,7 @@ class SdsCheckbox {
2617
3257
  class="absolute inset-0 m-0 cursor-[inherit] opacity-0"
2618
3258
  [id]="id"
2619
3259
  [checked]="checked()"
3260
+ [indeterminate]="mixed()"
2620
3261
  [disabled]="isDisabled()"
2621
3262
  [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
2622
3263
  (change)="onInput($event)"
@@ -2624,6 +3265,8 @@ class SdsCheckbox {
2624
3265
  />
2625
3266
  @if (checked()) {
2626
3267
  <sds-icon name="check" [size]="iconSize()" [strokeWidth]="3" />
3268
+ } @else if (mixed()) {
3269
+ <span class="h-0.5 w-2.5 rounded-full bg-current" aria-hidden="true"></span>
2627
3270
  }
2628
3271
  </span>
2629
3272
  </ng-template>
@@ -2642,7 +3285,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2642
3285
  multi: true,
2643
3286
  },
2644
3287
  ],
2645
- host: { class: 'inline-flex' },
3288
+ // align-middle: in an inline context (a table cell) an empty box and a checked one would otherwise
3289
+ // sit on different baselines, and the row would jump by a few pixels on every toggle.
3290
+ host: { class: 'inline-flex align-middle' },
2646
3291
  template: `
2647
3292
  <!-- With a visible label the whole thing is a <label>. Without one it is a plain span, so the box can
2648
3293
  sit inside the consumer's own <label> (a clickable row, say) without nesting labels. -->
@@ -2667,6 +3312,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2667
3312
  class="absolute inset-0 m-0 cursor-[inherit] opacity-0"
2668
3313
  [id]="id"
2669
3314
  [checked]="checked()"
3315
+ [indeterminate]="mixed()"
2670
3316
  [disabled]="isDisabled()"
2671
3317
  [attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
2672
3318
  (change)="onInput($event)"
@@ -2674,14 +3320,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2674
3320
  />
2675
3321
  @if (checked()) {
2676
3322
  <sds-icon name="check" [size]="iconSize()" [strokeWidth]="3" />
3323
+ } @else if (mixed()) {
3324
+ <span class="h-0.5 w-2.5 rounded-full bg-current" aria-hidden="true"></span>
2677
3325
  }
2678
3326
  </span>
2679
3327
  </ng-template>
2680
3328
  `,
2681
3329
  }]
2682
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
3330
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }] } });
2683
3331
 
2684
- let nextId$e = 0;
3332
+ let nextId$f = 0;
2685
3333
  /**
2686
3334
  * Mounted radios by group name. A pick updates the rest of its group, because a native radio says
2687
3335
  * nothing when the browser unchecks it — without this a reactive form would leave the old choice drawn.
@@ -2707,7 +3355,7 @@ const groups = new Map();
2707
3355
  * <sds-radio name="size" value="large" label="Grand" [(ngModel)]="size" />
2708
3356
  */
2709
3357
  class SdsRadio {
2710
- id = `sds-radio-${nextId$e++}`;
3358
+ id = `sds-radio-${nextId$f++}`;
2711
3359
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2712
3360
  /** Accessible name used when no visible [label] is provided. */
2713
3361
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -2890,8 +3538,30 @@ const STATUS_COLOR = {
2890
3538
  busy: 'bg-danger',
2891
3539
  away: 'bg-warning',
2892
3540
  };
3541
+ const SHAPE = {
3542
+ circle: 'rounded-full',
3543
+ rounded: 'rounded-control',
3544
+ square: 'rounded-none',
3545
+ };
2893
3546
  /**
2894
- * Circular avatar showing an image or initials, with an optional status dot.
3547
+ * Avatar showing an image, initials or a placeholder icon, with an optional status dot.
3548
+ *
3549
+ * - `shape`: `circle` (default, people), `rounded` (things: a dish photo, a product, a logo) or `square`.
3550
+ * - `fallbackIcon`: shown instead of initials when there is no image (or it fails to load) — e.g.
3551
+ * `utensils` or `image` for a menu item without a photo. The placeholder is drawn on `placeholderClass`
3552
+ * (a muted tile by default), not on the initials' brand fill.
3553
+ * - A broken `src` falls back to the icon or the initials instead of showing the browser's broken-image
3554
+ * glyph; a new `src` retries.
3555
+ * - `decorative`: the avatar only repeats a name that is already written next to it (a list row, a select
3556
+ * option), so the image gets `alt=""` and the whole avatar is hidden from assistive technology.
3557
+ *
3558
+ * @example
3559
+ * <sds-avatar name="Ada Lovelace" [src]="user.photoUrl" status="online" />
3560
+ *
3561
+ * @example
3562
+ * <!-- a menu item in a list: rounded photo, dish placeholder when there is none -->
3563
+ * <sds-avatar shape="rounded" [size]="32" [src]="item.thumbnailUrl" [name]="item.name"
3564
+ * fallbackIcon="utensils" decorative />
2895
3565
  */
2896
3566
  class SdsAvatar {
2897
3567
  name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
@@ -2899,8 +3569,48 @@ class SdsAvatar {
2899
3569
  src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : []));
2900
3570
  size = input(36, ...(ngDevMode ? [{ debugName: "size" }] : []));
2901
3571
  status = input(null, ...(ngDevMode ? [{ debugName: "status" }] : []));
3572
+ /** `circle` (default), `rounded` (radius of a control) or `square`. */
3573
+ shape = input('circle', ...(ngDevMode ? [{ debugName: "shape" }] : []));
2902
3574
  /** Tailwind bg utility for the initials background. */
2903
3575
  bgClass = input('bg-brand-400', ...(ngDevMode ? [{ debugName: "bgClass" }] : []));
3576
+ /**
3577
+ * Icon shown instead of initials when there is no usable image (e.g. `utensils` or `image` for a dish).
3578
+ * Null (default) keeps the initials.
3579
+ */
3580
+ fallbackIcon = input(null, ...(ngDevMode ? [{ debugName: "fallbackIcon" }] : []));
3581
+ /** Classes of the placeholder tile behind {@link fallbackIcon}: background and icon colour. */
3582
+ placeholderClass = input('bg-muted text-ink-faint', ...(ngDevMode ? [{ debugName: "placeholderClass" }] : []));
3583
+ /**
3584
+ * The avatar repeats a name already visible beside it: `alt=""` and `aria-hidden`, so a screen reader
3585
+ * does not read the name twice.
3586
+ */
3587
+ decorative = input(false, ...(ngDevMode ? [{ debugName: "decorative", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
3588
+ /** Native lazy loading for the image (default on — avatars are often in long lists). */
3589
+ lazy = input(true, ...(ngDevMode ? [{ debugName: "lazy", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
3590
+ /** The `src` that failed to load. Any other `src` is tried (so a new photo URL retries). */
3591
+ failedSrc = signal(undefined, ...(ngDevMode ? [{ debugName: "failedSrc" }] : []));
3592
+ showImage = computed(() => {
3593
+ const src = this.src();
3594
+ return !!src && src !== this.failedSrc();
3595
+ }, ...(ngDevMode ? [{ debugName: "showImage" }] : []));
3596
+ shapeClass = computed(() => SHAPE[this.shape()], ...(ngDevMode ? [{ debugName: "shapeClass" }] : []));
3597
+ frameClass = computed(() => {
3598
+ const parts = [this.shapeClass()];
3599
+ if (this.showImage()) {
3600
+ parts.push('bg-muted');
3601
+ }
3602
+ else if (this.fallbackIcon()) {
3603
+ parts.push(this.placeholderClass());
3604
+ }
3605
+ else {
3606
+ parts.push(this.bgClass(), 'text-white');
3607
+ }
3608
+ // People get the soft ring the avatar always had; things get a hairline so a white plate on a white
3609
+ // page still has an edge.
3610
+ parts.push(this.shape() === 'circle' ? 'ring-2 ring-white/20' : 'ring-1 ring-inset ring-border');
3611
+ return parts.join(' ');
3612
+ }, ...(ngDevMode ? [{ debugName: "frameClass" }] : []));
3613
+ iconSize = computed(() => Math.max(12, Math.round(this.size() * 0.5)), ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
2904
3614
  dotSize = computed(() => Math.max(8, Math.round(this.size() * 0.28)), ...(ngDevMode ? [{ debugName: "dotSize" }] : []));
2905
3615
  statusColor = computed(() => this.status() ? STATUS_COLOR[this.status()] : '', ...(ngDevMode ? [{ debugName: "statusColor" }] : []));
2906
3616
  resolvedInitials = computed(() => {
@@ -2914,19 +3624,31 @@ class SdsAvatar {
2914
3624
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
2915
3625
  }, ...(ngDevMode ? [{ debugName: "resolvedInitials" }] : []));
2916
3626
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsAvatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
2917
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsAvatar, isStandalone: true, selector: "sds-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, bgClass: { classPropertyName: "bgClass", publicName: "bgClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "inline-block shrink-0 align-middle" }, ngImport: i0, template: `
3627
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsAvatar, isStandalone: true, selector: "sds-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, bgClass: { classPropertyName: "bgClass", publicName: "bgClass", isSignal: true, isRequired: false, transformFunction: null }, fallbackIcon: { classPropertyName: "fallbackIcon", publicName: "fallbackIcon", isSignal: true, isRequired: false, transformFunction: null }, placeholderClass: { classPropertyName: "placeholderClass", publicName: "placeholderClass", isSignal: true, isRequired: false, transformFunction: null }, decorative: { classPropertyName: "decorative", publicName: "decorative", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "decorative() ? 'true' : null" }, classAttribute: "inline-block shrink-0 align-middle" }, ngImport: i0, template: `
2918
3628
  <div
2919
- class="relative grid place-items-center overflow-hidden rounded-full font-semibold text-white ring-2 ring-white/20"
2920
- [class]="bgClass()"
3629
+ class="relative grid place-items-center font-semibold"
3630
+ [class]="frameClass()"
2921
3631
  [style.width.px]="size()"
2922
3632
  [style.height.px]="size()"
2923
3633
  [style.fontSize.px]="size() * 0.4"
2924
3634
  >
2925
- @if (src()) {
2926
- <img [src]="src()" [alt]="name()" class="h-full w-full object-cover" />
2927
- } @else {
2928
- <span>{{ resolvedInitials() }}</span>
2929
- }
3635
+ <!-- The image and placeholder are clipped to the shape; the status dot is not (it straddles the edge). -->
3636
+ <div class="absolute inset-0 grid place-items-center overflow-hidden" [class]="shapeClass()">
3637
+ @if (showImage()) {
3638
+ <img
3639
+ [src]="src()"
3640
+ [alt]="decorative() ? '' : name()"
3641
+ [attr.loading]="lazy() ? 'lazy' : null"
3642
+ decoding="async"
3643
+ class="h-full w-full object-cover"
3644
+ (error)="failedSrc.set(src())"
3645
+ />
3646
+ } @else if (fallbackIcon()) {
3647
+ <sds-icon [name]="fallbackIcon()!" [size]="iconSize()" />
3648
+ } @else {
3649
+ <span>{{ resolvedInitials() }}</span>
3650
+ }
3651
+ </div>
2930
3652
 
2931
3653
  @if (status()) {
2932
3654
  <span
@@ -2937,27 +3659,43 @@ class SdsAvatar {
2937
3659
  ></span>
2938
3660
  }
2939
3661
  </div>
2940
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3662
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2941
3663
  }
2942
3664
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsAvatar, decorators: [{
2943
3665
  type: Component,
2944
3666
  args: [{
2945
3667
  selector: 'sds-avatar',
2946
3668
  changeDetection: ChangeDetectionStrategy.OnPush,
2947
- host: { class: 'inline-block shrink-0 align-middle' },
3669
+ imports: [SdsIcon],
3670
+ host: {
3671
+ class: 'inline-block shrink-0 align-middle',
3672
+ '[attr.aria-hidden]': "decorative() ? 'true' : null",
3673
+ },
2948
3674
  template: `
2949
3675
  <div
2950
- class="relative grid place-items-center overflow-hidden rounded-full font-semibold text-white ring-2 ring-white/20"
2951
- [class]="bgClass()"
3676
+ class="relative grid place-items-center font-semibold"
3677
+ [class]="frameClass()"
2952
3678
  [style.width.px]="size()"
2953
3679
  [style.height.px]="size()"
2954
3680
  [style.fontSize.px]="size() * 0.4"
2955
3681
  >
2956
- @if (src()) {
2957
- <img [src]="src()" [alt]="name()" class="h-full w-full object-cover" />
2958
- } @else {
2959
- <span>{{ resolvedInitials() }}</span>
2960
- }
3682
+ <!-- The image and placeholder are clipped to the shape; the status dot is not (it straddles the edge). -->
3683
+ <div class="absolute inset-0 grid place-items-center overflow-hidden" [class]="shapeClass()">
3684
+ @if (showImage()) {
3685
+ <img
3686
+ [src]="src()"
3687
+ [alt]="decorative() ? '' : name()"
3688
+ [attr.loading]="lazy() ? 'lazy' : null"
3689
+ decoding="async"
3690
+ class="h-full w-full object-cover"
3691
+ (error)="failedSrc.set(src())"
3692
+ />
3693
+ } @else if (fallbackIcon()) {
3694
+ <sds-icon [name]="fallbackIcon()!" [size]="iconSize()" />
3695
+ } @else {
3696
+ <span>{{ resolvedInitials() }}</span>
3697
+ }
3698
+ </div>
2961
3699
 
2962
3700
  @if (status()) {
2963
3701
  <span
@@ -2970,7 +3708,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2970
3708
  </div>
2971
3709
  `,
2972
3710
  }]
2973
- }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "initials", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], bgClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "bgClass", required: false }] }] } });
3711
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "initials", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], bgClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "bgClass", required: false }] }], fallbackIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackIcon", required: false }] }], placeholderClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderClass", required: false }] }], decorative: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorative", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }] } });
2974
3712
 
2975
3713
  const VARIANTS$1 = {
2976
3714
  neutral: 'bg-muted text-ink-soft border-border',
@@ -3193,7 +3931,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3193
3931
 
3194
3932
  /** Activation order: none → contains → may-contain → none. */
3195
3933
  const ORDER = ['none', 'contains', 'may-contain'];
3196
- let nextId$d = 0;
3934
+ let nextId$e = 0;
3197
3935
  /**
3198
3936
  * A chip that cycles through three states — `none`, `contains`, `may-contain` — built for the 14-tile
3199
3937
  * allergen declaration grid, but generic (any "no / yes / maybe" declaration).
@@ -3213,7 +3951,7 @@ let nextId$d = 0;
3213
3951
  * containsLabel="Contains" mayContainLabel="May contain" noneLabel="Not present" />
3214
3952
  */
3215
3953
  class SdsTriStateChip {
3216
- chipId = `sds-tri-state-chip-${nextId$d++}`;
3954
+ chipId = `sds-tri-state-chip-${nextId$e++}`;
3217
3955
  /** Current state. Two-way: `[(value)]`. */
3218
3956
  value = model('none', ...(ngDevMode ? [{ debugName: "value" }] : []));
3219
3957
  /** Visible name (e.g. the allergen). Required — the chip has no other accessible name. */
@@ -3435,7 +4173,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3435
4173
  }]
3436
4174
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showState: [{ type: i0.Input, args: [{ isSignal: true, alias: "showState", required: false }] }], noneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noneLabel", required: false }] }], containsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "containsLabel", required: false }] }], mayContainLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "mayContainLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
3437
4175
 
3438
- let nextId$c = 0;
4176
+ let nextId$d = 0;
3439
4177
  /**
3440
4178
  * Segmented control: a small set of mutually exclusive options shown side by side (e.g. "Pick exactly /
3441
4179
  * at least / up to"). Radio-group semantics: `role="radiogroup"` with `role="radio"` segments, one tab
@@ -3451,7 +4189,7 @@ let nextId$c = 0;
3451
4189
  */
3452
4190
  class SdsSegmented {
3453
4191
  host = inject(ElementRef);
3454
- baseId = `sds-segmented-${nextId$c++}`;
4192
+ baseId = `sds-segmented-${nextId$d++}`;
3455
4193
  /** Options: `{ value, label, icon?, disabled? }` objects, or bare strings/numbers. */
3456
4194
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
3457
4195
  /** Selected value. Two-way: `[(value)]`. */
@@ -3496,17 +4234,347 @@ class SdsSegmented {
3496
4234
  ? `${base} bg-surface font-semibold text-ink shadow-sm ring-1 ring-border dark:bg-border`
3497
4235
  : `${base} cursor-pointer text-ink-soft hover:bg-surface/60 hover:text-ink`;
3498
4236
  }
3499
- choose(i) {
3500
- const seg = this.segments()[i];
3501
- if (!seg || seg.disabled || this.disabled())
4237
+ choose(i) {
4238
+ const seg = this.segments()[i];
4239
+ if (!seg || seg.disabled || this.disabled())
4240
+ return;
4241
+ this.value.set(seg.value);
4242
+ }
4243
+ onKeydown(event) {
4244
+ if (this.disabled())
4245
+ return;
4246
+ const segs = this.segments();
4247
+ if (!segs.length)
4248
+ return;
4249
+ const current = Number(event.target.dataset['index'] ?? this.tabStop());
4250
+ let next = null;
4251
+ switch (event.key) {
4252
+ case 'ArrowRight':
4253
+ case 'ArrowDown':
4254
+ next = this.step(current, 1);
4255
+ break;
4256
+ case 'ArrowLeft':
4257
+ case 'ArrowUp':
4258
+ next = this.step(current, -1);
4259
+ break;
4260
+ case 'Home':
4261
+ next = segs.findIndex((s) => !s.disabled);
4262
+ break;
4263
+ case 'End':
4264
+ for (let i = segs.length - 1; i >= 0; i--) {
4265
+ if (!segs[i].disabled) {
4266
+ next = i;
4267
+ break;
4268
+ }
4269
+ }
4270
+ break;
4271
+ default:
4272
+ return;
4273
+ }
4274
+ event.preventDefault();
4275
+ if (next === null || next < 0)
4276
+ return;
4277
+ this.choose(next);
4278
+ this.host.nativeElement.querySelector(`#${this.segmentId(next)}`)?.focus();
4279
+ }
4280
+ step(from, delta) {
4281
+ const segs = this.segments();
4282
+ let i = from;
4283
+ for (let n = 0; n < segs.length; n++) {
4284
+ i = (i + delta + segs.length) % segs.length;
4285
+ if (!segs[i].disabled)
4286
+ return i;
4287
+ }
4288
+ return null;
4289
+ }
4290
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
4291
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSegmented, isStandalone: true, selector: "sds-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.w-full": "fullWidth()" }, classAttribute: "inline-flex max-w-full" }, ngImport: i0, template: `
4292
+ <div
4293
+ role="radiogroup"
4294
+ [attr.aria-label]="ariaLabel() || null"
4295
+ [attr.aria-labelledby]="ariaLabelledby() || null"
4296
+ [attr.aria-disabled]="disabled() ? 'true' : null"
4297
+ class="flex max-w-full items-stretch gap-0.5 overflow-x-auto rounded-control border border-border bg-muted p-0.5"
4298
+ [class.w-full]="fullWidth()"
4299
+ (keydown)="onKeydown($event)"
4300
+ >
4301
+ @for (seg of segments(); track $index; let i = $index) {
4302
+ <button
4303
+ type="button"
4304
+ role="radio"
4305
+ [id]="segmentId(i)"
4306
+ [attr.aria-checked]="isSelected(seg)"
4307
+ [attr.aria-label]="iconOnly() ? seg.label : null"
4308
+ [attr.title]="iconOnly() ? seg.label : null"
4309
+ [attr.tabindex]="i === tabStop() ? 0 : -1"
4310
+ [attr.data-index]="i"
4311
+ [disabled]="disabled() || seg.disabled"
4312
+ [class]="segmentClass(seg)"
4313
+ (click)="choose(i)"
4314
+ >
4315
+ @if (seg.icon) {
4316
+ <sds-icon [name]="seg.icon" [size]="size() === 'sm' ? 15 : 17" class="shrink-0" />
4317
+ }
4318
+ @if (!iconOnly()) {
4319
+ <span class="truncate">{{ seg.label }}</span>
4320
+ }
4321
+ </button>
4322
+ }
4323
+ </div>
4324
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4325
+ }
4326
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSegmented, decorators: [{
4327
+ type: Component,
4328
+ args: [{
4329
+ selector: 'sds-segmented',
4330
+ changeDetection: ChangeDetectionStrategy.OnPush,
4331
+ imports: [SdsIcon],
4332
+ host: { class: 'inline-flex max-w-full', '[class.w-full]': 'fullWidth()' },
4333
+ template: `
4334
+ <div
4335
+ role="radiogroup"
4336
+ [attr.aria-label]="ariaLabel() || null"
4337
+ [attr.aria-labelledby]="ariaLabelledby() || null"
4338
+ [attr.aria-disabled]="disabled() ? 'true' : null"
4339
+ class="flex max-w-full items-stretch gap-0.5 overflow-x-auto rounded-control border border-border bg-muted p-0.5"
4340
+ [class.w-full]="fullWidth()"
4341
+ (keydown)="onKeydown($event)"
4342
+ >
4343
+ @for (seg of segments(); track $index; let i = $index) {
4344
+ <button
4345
+ type="button"
4346
+ role="radio"
4347
+ [id]="segmentId(i)"
4348
+ [attr.aria-checked]="isSelected(seg)"
4349
+ [attr.aria-label]="iconOnly() ? seg.label : null"
4350
+ [attr.title]="iconOnly() ? seg.label : null"
4351
+ [attr.tabindex]="i === tabStop() ? 0 : -1"
4352
+ [attr.data-index]="i"
4353
+ [disabled]="disabled() || seg.disabled"
4354
+ [class]="segmentClass(seg)"
4355
+ (click)="choose(i)"
4356
+ >
4357
+ @if (seg.icon) {
4358
+ <sds-icon [name]="seg.icon" [size]="size() === 'sm' ? 15 : 17" class="shrink-0" />
4359
+ }
4360
+ @if (!iconOnly()) {
4361
+ <span class="truncate">{{ seg.label }}</span>
4362
+ }
4363
+ </button>
4364
+ }
4365
+ </div>
4366
+ `,
4367
+ }]
4368
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }] } });
4369
+
4370
+ const SIZE = {
4371
+ sm: 'h-7 gap-1 px-2.5 text-xs',
4372
+ md: 'h-8 gap-1.5 px-3 text-sm',
4373
+ };
4374
+ /**
4375
+ * Chip look shared by sds-chip and sds-chip-group. Selected is more than a tint — a brand border, bolder
4376
+ * text and (by default) a check — so it reads in greyscale too. In dark mode the brand text/tint are
4377
+ * swapped for ink/muted (the library's rule for a monochrome brand's dark ramp).
4378
+ */
4379
+ function chipClass(size, selected) {
4380
+ const base = 'inline-flex shrink-0 select-none items-center whitespace-nowrap rounded-full border font-medium ' +
4381
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 ' +
4382
+ 'focus-visible:ring-offset-1 focus-visible:ring-offset-surface disabled:cursor-not-allowed disabled:opacity-50 ' +
4383
+ SIZE[size];
4384
+ return selected
4385
+ ? `${base} cursor-pointer border-brand-600 bg-brand-50 font-semibold text-brand-700 ` +
4386
+ 'dark:border-ink-soft dark:bg-muted dark:text-ink'
4387
+ : `${base} cursor-pointer border-border bg-surface text-ink-soft hover:border-ink-faint hover:text-ink`;
4388
+ }
4389
+ const COUNT_CLASS = 'inline-flex min-w-5 items-center justify-center rounded-full bg-ink/5 px-1.5 text-[11px] font-semibold tabular-nums dark:bg-white/10';
4390
+ /**
4391
+ * A single on/off filter chip (`aria-pressed`), e.g. "Sold out only" or "With comments". Two-way
4392
+ * `[(selected)]`. For a set of chips — one or several picked — use `sds-chip-group`.
4393
+ *
4394
+ * @example
4395
+ * <sds-chip label="Sold out only" icon="ban" [(selected)]="soldOutOnly" />
4396
+ */
4397
+ class SdsChip {
4398
+ /** Visible text — also the chip's accessible name. */
4399
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
4400
+ /** On/off. Two-way: `[(selected)]`. */
4401
+ selected = model(false, ...(ngDevMode ? [{ debugName: "selected" }] : []));
4402
+ icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
4403
+ count = input(null, ...(ngDevMode ? [{ debugName: "count" }] : []));
4404
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
4405
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4406
+ /** Replace the icon with a check while selected. */
4407
+ showCheck = input(true, ...(ngDevMode ? [{ debugName: "showCheck", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4408
+ countClass = COUNT_CLASS;
4409
+ classes = computed(() => chipClass(this.size(), this.selected()), ...(ngDevMode ? [{ debugName: "classes" }] : []));
4410
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
4411
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsChip, isStandalone: true, selector: "sds-chip", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showCheck: { classPropertyName: "showCheck", publicName: "showCheck", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, host: { classAttribute: "inline-flex" }, ngImport: i0, template: `
4412
+ <button
4413
+ type="button"
4414
+ [attr.aria-pressed]="selected()"
4415
+ [disabled]="disabled()"
4416
+ [class]="classes()"
4417
+ (click)="selected.set(!selected())"
4418
+ >
4419
+ @if (selected() && showCheck()) {
4420
+ <sds-icon
4421
+ name="check"
4422
+ [size]="size() === 'sm' ? 12 : 14"
4423
+ [strokeWidth]="3"
4424
+ class="shrink-0"
4425
+ />
4426
+ } @else if (icon()) {
4427
+ <sds-icon [name]="icon()!" [size]="size() === 'sm' ? 13 : 15" class="shrink-0" />
4428
+ }
4429
+ <span class="truncate">{{ label() }}</span>
4430
+ @if (count() !== null && count() !== undefined) {
4431
+ <span [class]="countClass">{{ count() }}</span>
4432
+ }
4433
+ </button>
4434
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4435
+ }
4436
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsChip, decorators: [{
4437
+ type: Component,
4438
+ args: [{
4439
+ selector: 'sds-chip',
4440
+ changeDetection: ChangeDetectionStrategy.OnPush,
4441
+ imports: [SdsIcon],
4442
+ host: { class: 'inline-flex' },
4443
+ template: `
4444
+ <button
4445
+ type="button"
4446
+ [attr.aria-pressed]="selected()"
4447
+ [disabled]="disabled()"
4448
+ [class]="classes()"
4449
+ (click)="selected.set(!selected())"
4450
+ >
4451
+ @if (selected() && showCheck()) {
4452
+ <sds-icon
4453
+ name="check"
4454
+ [size]="size() === 'sm' ? 12 : 14"
4455
+ [strokeWidth]="3"
4456
+ class="shrink-0"
4457
+ />
4458
+ } @else if (icon()) {
4459
+ <sds-icon [name]="icon()!" [size]="size() === 'sm' ? 13 : 15" class="shrink-0" />
4460
+ }
4461
+ <span class="truncate">{{ label() }}</span>
4462
+ @if (count() !== null && count() !== undefined) {
4463
+ <span [class]="countClass">{{ count() }}</span>
4464
+ }
4465
+ </button>
4466
+ `,
4467
+ }]
4468
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheck", required: false }] }] } });
4469
+ let nextId$c = 0;
4470
+ /**
4471
+ * A set of chips: pick ONE (a filter row such as "All · Missing photo · Hidden", VAT presets, a date
4472
+ * range) or, with `multiple`, SEVERAL (categories of an item, days of the week, dietary tags). Unlike
4473
+ * sds-segmented (a compact joined bar), chips wrap onto several lines and can carry a count.
4474
+ *
4475
+ * Value: two-way `[(value)]`, or a form control (`formControlName` / `[(ngModel)]` —
4476
+ * ControlValueAccessor). Single mode holds the picked value (or null); `multiple` holds an array, in the
4477
+ * order of the options.
4478
+ *
4479
+ * Accessibility: single mode is a `radiogroup` (one tab stop, arrows/Home/End move and select, like
4480
+ * native radios); `multiple` is a `group` of `aria-pressed` toggle buttons. Name it with [ariaLabel] or
4481
+ * [ariaLabelledby].
4482
+ *
4483
+ * @example
4484
+ * <sds-chip-group ariaLabel="Filter" [options]="filters()" [(value)]="issue" />
4485
+ * <!-- filters = [{ value: null, label: 'All', count: 34 }, { value: 'NO_PHOTO', label: 'Missing photo', count: 5 }] -->
4486
+ *
4487
+ * @example
4488
+ * <sds-chip-group ariaLabel="Categories" multiple [options]="categories()" formControlName="categoryIds" />
4489
+ */
4490
+ class SdsChipGroup {
4491
+ host = inject(ElementRef);
4492
+ baseId = `sds-chip-group-${nextId$c++}`;
4493
+ /** Options: `{ value, label, icon?, count?, disabled? }` objects, or bare strings/numbers. */
4494
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
4495
+ /** Picked value (single) or values (multiple). Two-way: `[(value)]`. */
4496
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
4497
+ /** Allow several chips at once; the value becomes an array. */
4498
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4499
+ /** Single mode: clicking the picked chip again clears the value to null. */
4500
+ deselectable = input(false, ...(ngDevMode ? [{ debugName: "deselectable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4501
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
4502
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
4503
+ ariaLabelledby = input('', ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
4504
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4505
+ /** Wrap onto several lines (default) or scroll horizontally on one line. */
4506
+ wrap = input(true, ...(ngDevMode ? [{ debugName: "wrap", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4507
+ /** `multiple`: a check replaces the icon on picked chips. */
4508
+ showCheck = input(true, ...(ngDevMode ? [{ debugName: "showCheck", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4509
+ countClass = COUNT_CLASS;
4510
+ cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
4511
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
4512
+ chips = computed(() => this.options().map((o) => o !== null && typeof o === 'object'
4513
+ ? {
4514
+ value: o.value,
4515
+ label: o.label,
4516
+ icon: o.icon ?? null,
4517
+ count: o.count ?? null,
4518
+ disabled: !!o.disabled,
4519
+ }
4520
+ : { value: o, label: String(o), icon: null, count: null, disabled: false }), ...(ngDevMode ? [{ debugName: "chips" }] : []));
4521
+ selectedValues = computed(() => {
4522
+ const v = this.value();
4523
+ if (this.multiple())
4524
+ return Array.isArray(v) ? v : [];
4525
+ return [v];
4526
+ }, ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
4527
+ /** Single mode's roving tab stop: the picked chip, else the first enabled one. */
4528
+ tabStop = computed(() => {
4529
+ const chips = this.chips();
4530
+ const sel = chips.findIndex((c) => this.isSelected(c));
4531
+ if (sel >= 0 && !chips[sel].disabled)
4532
+ return sel;
4533
+ return chips.findIndex((c) => !c.disabled);
4534
+ }, ...(ngDevMode ? [{ debugName: "tabStop" }] : []));
4535
+ isSelected(chip) {
4536
+ return this.selectedValues().includes(chip.value);
4537
+ }
4538
+ chipId(i) {
4539
+ return `${this.baseId}-${i}`;
4540
+ }
4541
+ classFor(chip) {
4542
+ return chipClass(this.size(), this.isSelected(chip));
4543
+ }
4544
+ toggle(i) {
4545
+ const chip = this.chips()[i];
4546
+ if (!chip || chip.disabled || this.isDisabled())
3502
4547
  return;
3503
- this.value.set(seg.value);
4548
+ let next;
4549
+ if (this.multiple()) {
4550
+ const picked = new Set(this.selectedValues());
4551
+ if (picked.has(chip.value))
4552
+ picked.delete(chip.value);
4553
+ else
4554
+ picked.add(chip.value);
4555
+ // Keep the options' order, whatever the click order.
4556
+ next = this.chips()
4557
+ .map((c) => c.value)
4558
+ .filter((v) => picked.has(v));
4559
+ }
4560
+ else {
4561
+ if (this.isSelected(chip)) {
4562
+ if (!this.deselectable())
4563
+ return;
4564
+ next = null;
4565
+ }
4566
+ else {
4567
+ next = chip.value;
4568
+ }
4569
+ }
4570
+ this.value.set(next);
4571
+ this.onChange(next);
3504
4572
  }
3505
4573
  onKeydown(event) {
3506
- if (this.disabled())
4574
+ if (this.multiple() || this.isDisabled())
3507
4575
  return;
3508
- const segs = this.segments();
3509
- if (!segs.length)
4576
+ const chips = this.chips();
4577
+ if (!chips.length)
3510
4578
  return;
3511
4579
  const current = Number(event.target.dataset['index'] ?? this.tabStop());
3512
4580
  let next = null;
@@ -3520,11 +4588,11 @@ class SdsSegmented {
3520
4588
  next = this.step(current, -1);
3521
4589
  break;
3522
4590
  case 'Home':
3523
- next = segs.findIndex((s) => !s.disabled);
4591
+ next = chips.findIndex((c) => !c.disabled);
3524
4592
  break;
3525
4593
  case 'End':
3526
- for (let i = segs.length - 1; i >= 0; i--) {
3527
- if (!segs[i].disabled) {
4594
+ for (let i = chips.length - 1; i >= 0; i--) {
4595
+ if (!chips[i].disabled) {
3528
4596
  next = i;
3529
4597
  break;
3530
4598
  }
@@ -3536,98 +4604,136 @@ class SdsSegmented {
3536
4604
  event.preventDefault();
3537
4605
  if (next === null || next < 0)
3538
4606
  return;
3539
- this.choose(next);
3540
- this.host.nativeElement.querySelector(`#${this.segmentId(next)}`)?.focus();
4607
+ if (!this.isSelected(chips[next]))
4608
+ this.toggle(next);
4609
+ this.host.nativeElement.querySelector(`#${this.chipId(next)}`)?.focus();
3541
4610
  }
3542
4611
  step(from, delta) {
3543
- const segs = this.segments();
4612
+ const chips = this.chips();
3544
4613
  let i = from;
3545
- for (let n = 0; n < segs.length; n++) {
3546
- i = (i + delta + segs.length) % segs.length;
3547
- if (!segs[i].disabled)
4614
+ for (let n = 0; n < chips.length; n++) {
4615
+ i = (i + delta + chips.length) % chips.length;
4616
+ if (!chips[i].disabled)
3548
4617
  return i;
3549
4618
  }
3550
4619
  return null;
3551
4620
  }
3552
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
3553
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSegmented, isStandalone: true, selector: "sds-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.w-full": "fullWidth()" }, classAttribute: "inline-flex max-w-full" }, ngImport: i0, template: `
4621
+ // --- ControlValueAccessor -------------------------------------------------
4622
+ onChange = () => { };
4623
+ onTouched = () => { };
4624
+ writeValue(value) {
4625
+ // Normalised on read (selectedValues), so a null written to a `multiple` group is simply "none".
4626
+ this.value.set(value ?? null);
4627
+ }
4628
+ registerOnChange(fn) {
4629
+ this.onChange = fn;
4630
+ }
4631
+ registerOnTouched(fn) {
4632
+ this.onTouched = fn;
4633
+ }
4634
+ setDisabledState(isDisabled) {
4635
+ this.cvaDisabled.set(isDisabled);
4636
+ }
4637
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsChipGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
4638
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsChipGroup, isStandalone: true, selector: "sds-chip-group", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, deselectable: { classPropertyName: "deselectable", publicName: "deselectable", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, showCheck: { classPropertyName: "showCheck", publicName: "showCheck", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "block" }, providers: [
4639
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsChipGroup), multi: true },
4640
+ ], ngImport: i0, template: `
3554
4641
  <div
3555
- role="radiogroup"
4642
+ [attr.role]="multiple() ? 'group' : 'radiogroup'"
3556
4643
  [attr.aria-label]="ariaLabel() || null"
3557
4644
  [attr.aria-labelledby]="ariaLabelledby() || null"
3558
- [attr.aria-disabled]="disabled() ? 'true' : null"
3559
- class="flex max-w-full items-stretch gap-0.5 overflow-x-auto rounded-control border border-border bg-muted p-0.5"
3560
- [class.w-full]="fullWidth()"
4645
+ [attr.aria-disabled]="isDisabled() ? 'true' : null"
4646
+ class="flex gap-2"
4647
+ [class]="wrap() ? 'flex-wrap' : 'overflow-x-auto pb-1'"
3561
4648
  (keydown)="onKeydown($event)"
4649
+ (focusout)="onTouched()"
3562
4650
  >
3563
- @for (seg of segments(); track $index; let i = $index) {
4651
+ @for (chip of chips(); track $index; let i = $index) {
3564
4652
  <button
3565
4653
  type="button"
3566
- role="radio"
3567
- [id]="segmentId(i)"
3568
- [attr.aria-checked]="isSelected(seg)"
3569
- [attr.aria-label]="iconOnly() ? seg.label : null"
3570
- [attr.title]="iconOnly() ? seg.label : null"
3571
- [attr.tabindex]="i === tabStop() ? 0 : -1"
4654
+ [id]="chipId(i)"
4655
+ [attr.role]="multiple() ? null : 'radio'"
4656
+ [attr.aria-checked]="multiple() ? null : isSelected(chip)"
4657
+ [attr.aria-pressed]="multiple() ? isSelected(chip) : null"
4658
+ [attr.tabindex]="multiple() || i === tabStop() ? 0 : -1"
3572
4659
  [attr.data-index]="i"
3573
- [disabled]="disabled() || seg.disabled"
3574
- [class]="segmentClass(seg)"
3575
- (click)="choose(i)"
4660
+ [disabled]="isDisabled() || chip.disabled"
4661
+ [class]="classFor(chip)"
4662
+ (click)="toggle(i)"
3576
4663
  >
3577
- @if (seg.icon) {
3578
- <sds-icon [name]="seg.icon" [size]="size() === 'sm' ? 15 : 17" class="shrink-0" />
4664
+ @if (isSelected(chip) && multiple() && showCheck()) {
4665
+ <sds-icon
4666
+ name="check"
4667
+ [size]="size() === 'sm' ? 12 : 14"
4668
+ [strokeWidth]="3"
4669
+ class="shrink-0"
4670
+ />
4671
+ } @else if (chip.icon) {
4672
+ <sds-icon [name]="chip.icon" [size]="size() === 'sm' ? 13 : 15" class="shrink-0" />
3579
4673
  }
3580
- @if (!iconOnly()) {
3581
- <span class="truncate">{{ seg.label }}</span>
4674
+ <span class="truncate">{{ chip.label }}</span>
4675
+ @if (chip.count !== null) {
4676
+ <span [class]="countClass">{{ chip.count }}</span>
3582
4677
  }
3583
4678
  </button>
3584
4679
  }
3585
4680
  </div>
3586
4681
  `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3587
4682
  }
3588
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSegmented, decorators: [{
4683
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsChipGroup, decorators: [{
3589
4684
  type: Component,
3590
4685
  args: [{
3591
- selector: 'sds-segmented',
4686
+ selector: 'sds-chip-group',
3592
4687
  changeDetection: ChangeDetectionStrategy.OnPush,
3593
4688
  imports: [SdsIcon],
3594
- host: { class: 'inline-flex max-w-full', '[class.w-full]': 'fullWidth()' },
4689
+ providers: [
4690
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsChipGroup), multi: true },
4691
+ ],
4692
+ host: { class: 'block' },
3595
4693
  template: `
3596
4694
  <div
3597
- role="radiogroup"
4695
+ [attr.role]="multiple() ? 'group' : 'radiogroup'"
3598
4696
  [attr.aria-label]="ariaLabel() || null"
3599
4697
  [attr.aria-labelledby]="ariaLabelledby() || null"
3600
- [attr.aria-disabled]="disabled() ? 'true' : null"
3601
- class="flex max-w-full items-stretch gap-0.5 overflow-x-auto rounded-control border border-border bg-muted p-0.5"
3602
- [class.w-full]="fullWidth()"
4698
+ [attr.aria-disabled]="isDisabled() ? 'true' : null"
4699
+ class="flex gap-2"
4700
+ [class]="wrap() ? 'flex-wrap' : 'overflow-x-auto pb-1'"
3603
4701
  (keydown)="onKeydown($event)"
4702
+ (focusout)="onTouched()"
3604
4703
  >
3605
- @for (seg of segments(); track $index; let i = $index) {
4704
+ @for (chip of chips(); track $index; let i = $index) {
3606
4705
  <button
3607
4706
  type="button"
3608
- role="radio"
3609
- [id]="segmentId(i)"
3610
- [attr.aria-checked]="isSelected(seg)"
3611
- [attr.aria-label]="iconOnly() ? seg.label : null"
3612
- [attr.title]="iconOnly() ? seg.label : null"
3613
- [attr.tabindex]="i === tabStop() ? 0 : -1"
4707
+ [id]="chipId(i)"
4708
+ [attr.role]="multiple() ? null : 'radio'"
4709
+ [attr.aria-checked]="multiple() ? null : isSelected(chip)"
4710
+ [attr.aria-pressed]="multiple() ? isSelected(chip) : null"
4711
+ [attr.tabindex]="multiple() || i === tabStop() ? 0 : -1"
3614
4712
  [attr.data-index]="i"
3615
- [disabled]="disabled() || seg.disabled"
3616
- [class]="segmentClass(seg)"
3617
- (click)="choose(i)"
4713
+ [disabled]="isDisabled() || chip.disabled"
4714
+ [class]="classFor(chip)"
4715
+ (click)="toggle(i)"
3618
4716
  >
3619
- @if (seg.icon) {
3620
- <sds-icon [name]="seg.icon" [size]="size() === 'sm' ? 15 : 17" class="shrink-0" />
4717
+ @if (isSelected(chip) && multiple() && showCheck()) {
4718
+ <sds-icon
4719
+ name="check"
4720
+ [size]="size() === 'sm' ? 12 : 14"
4721
+ [strokeWidth]="3"
4722
+ class="shrink-0"
4723
+ />
4724
+ } @else if (chip.icon) {
4725
+ <sds-icon [name]="chip.icon" [size]="size() === 'sm' ? 13 : 15" class="shrink-0" />
3621
4726
  }
3622
- @if (!iconOnly()) {
3623
- <span class="truncate">{{ seg.label }}</span>
4727
+ <span class="truncate">{{ chip.label }}</span>
4728
+ @if (chip.count !== null) {
4729
+ <span [class]="countClass">{{ chip.count }}</span>
3624
4730
  }
3625
4731
  </button>
3626
4732
  }
3627
4733
  </div>
3628
4734
  `,
3629
4735
  }]
3630
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }] } });
4736
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], deselectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "deselectable", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }], showCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheck", required: false }] }] } });
3631
4737
 
3632
4738
  /**
3633
4739
  * Each status has its own glyph SHAPE (circle-check, triangle, octagon, circle-i) as well as its own
@@ -4053,6 +5159,9 @@ const STATES = {
4053
5159
  * for multi-select ("select all matches"): it then carries `aria-pressed` and a corner check. [busy]
4054
5160
  * shows a spinner and sets `aria-busy` while a save is in flight.
4055
5161
  *
5162
+ * A photo that fails to load (404, blocked, not an image) falls back to the no-photo layout — never
5163
+ * a broken-image icon — and `(imageError)` reports the URL. A new [image] URL is tried again.
5164
+ *
4056
5165
  * @example
4057
5166
  * <sds-status-tile title="Bacon" subtitle="Extras" state="sold-out" note="Back at 22:00"
4058
5167
  * soldOutLabel="Sold out" (activate)="toggle(item)" />
@@ -4077,6 +5186,21 @@ class SdsStatusTile {
4077
5186
  hiddenLabel = input('Hidden', ...(ngDevMode ? [{ debugName: "hiddenLabel" }] : []));
4078
5187
  /** The tile was pressed (click, tap, Enter or Space). */
4079
5188
  activate = output();
5189
+ /** The [image] URL failed to load; the tile has fallen back to its no-photo layout. */
5190
+ imageError = output();
5191
+ /** The URL that last failed to load — hidden until [image] changes to another URL. */
5192
+ failedImage = signal(null, ...(ngDevMode ? [{ debugName: "failedImage" }] : []));
5193
+ showImage = computed(() => {
5194
+ const src = this.image();
5195
+ return !!src && src !== this.failedImage();
5196
+ }, ...(ngDevMode ? [{ debugName: "showImage" }] : []));
5197
+ onImageError() {
5198
+ const src = this.image();
5199
+ if (!src)
5200
+ return;
5201
+ this.failedImage.set(src);
5202
+ this.imageError.emit(src);
5203
+ }
4080
5204
  style = computed(() => STATES[this.state()] ?? STATES.available, ...(ngDevMode ? [{ debugName: "style" }] : []));
4081
5205
  stateText = computed(() => {
4082
5206
  switch (this.state()) {
@@ -4107,7 +5231,7 @@ class SdsStatusTile {
4107
5231
  ring);
4108
5232
  }, ...(ngDevMode ? [{ debugName: "tileClass" }] : []));
4109
5233
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsStatusTile, deps: [], target: i0.ɵɵFactoryTarget.Component });
4110
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsStatusTile, isStandalone: true, selector: "sds-status-tile", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: false, transformFunction: null }, imageAlt: { classPropertyName: "imageAlt", publicName: "imageAlt", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, availableLabel: { classPropertyName: "availableLabel", publicName: "availableLabel", isSignal: true, isRequired: false, transformFunction: null }, soldOutLabel: { classPropertyName: "soldOutLabel", publicName: "soldOutLabel", isSignal: true, isRequired: false, transformFunction: null }, hiddenLabel: { classPropertyName: "hiddenLabel", publicName: "hiddenLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activate: "activate" }, host: { classAttribute: "block" }, ngImport: i0, template: `
5234
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsStatusTile, isStandalone: true, selector: "sds-status-tile", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: false, transformFunction: null }, imageAlt: { classPropertyName: "imageAlt", publicName: "imageAlt", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, availableLabel: { classPropertyName: "availableLabel", publicName: "availableLabel", isSignal: true, isRequired: false, transformFunction: null }, soldOutLabel: { classPropertyName: "soldOutLabel", publicName: "soldOutLabel", isSignal: true, isRequired: false, transformFunction: null }, hiddenLabel: { classPropertyName: "hiddenLabel", publicName: "hiddenLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activate: "activate", imageError: "imageError" }, host: { classAttribute: "block" }, ngImport: i0, template: `
4111
5235
  <button
4112
5236
  type="button"
4113
5237
  [class]="tileClass()"
@@ -4118,11 +5242,12 @@ class SdsStatusTile {
4118
5242
  [style.background-image]="hatch()"
4119
5243
  (click)="activate.emit()"
4120
5244
  >
4121
- @if (image()) {
5245
+ @if (showImage()) {
4122
5246
  <span class="relative block h-[45%] w-full shrink-0 overflow-hidden bg-muted">
4123
5247
  <img
4124
5248
  [src]="image()"
4125
5249
  [alt]="imageAlt()"
5250
+ (error)="onImageError()"
4126
5251
  class="h-full w-full object-cover"
4127
5252
  [class.grayscale]="state() !== 'available'"
4128
5253
  [class.opacity-60]="state() !== 'available'"
@@ -4135,7 +5260,7 @@ class SdsStatusTile {
4135
5260
  <span class="flex min-h-0 w-full flex-1 flex-col gap-1 p-3 text-left">
4136
5261
  <span
4137
5262
  class="line-clamp-2 break-words font-bold leading-tight text-ink"
4138
- [class]="image() ? 'text-base' : 'text-lg'"
5263
+ [class]="showImage() ? 'text-base' : 'text-lg'"
4139
5264
  [class.line-through]="state() === 'sold-out'"
4140
5265
  [class.decoration-2]="state() === 'sold-out'"
4141
5266
  >{{ title() }}</span
@@ -4198,11 +5323,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4198
5323
  [style.background-image]="hatch()"
4199
5324
  (click)="activate.emit()"
4200
5325
  >
4201
- @if (image()) {
5326
+ @if (showImage()) {
4202
5327
  <span class="relative block h-[45%] w-full shrink-0 overflow-hidden bg-muted">
4203
5328
  <img
4204
5329
  [src]="image()"
4205
5330
  [alt]="imageAlt()"
5331
+ (error)="onImageError()"
4206
5332
  class="h-full w-full object-cover"
4207
5333
  [class.grayscale]="state() !== 'available'"
4208
5334
  [class.opacity-60]="state() !== 'available'"
@@ -4215,7 +5341,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4215
5341
  <span class="flex min-h-0 w-full flex-1 flex-col gap-1 p-3 text-left">
4216
5342
  <span
4217
5343
  class="line-clamp-2 break-words font-bold leading-tight text-ink"
4218
- [class]="image() ? 'text-base' : 'text-lg'"
5344
+ [class]="showImage() ? 'text-base' : 'text-lg'"
4219
5345
  [class.line-through]="state() === 'sold-out'"
4220
5346
  [class.decoration-2]="state() === 'sold-out'"
4221
5347
  >{{ title() }}</span
@@ -4260,7 +5386,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4260
5386
  </button>
4261
5387
  `,
4262
5388
  }]
4263
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], image: [{ type: i0.Input, args: [{ isSignal: true, alias: "image", required: false }] }], imageAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageAlt", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], availableLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableLabel", required: false }] }], soldOutLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "soldOutLabel", required: false }] }], hiddenLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenLabel", required: false }] }], activate: [{ type: i0.Output, args: ["activate"] }] } });
5389
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], image: [{ type: i0.Input, args: [{ isSignal: true, alias: "image", required: false }] }], imageAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageAlt", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], availableLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableLabel", required: false }] }], soldOutLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "soldOutLabel", required: false }] }], hiddenLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenLabel", required: false }] }], activate: [{ type: i0.Output, args: ["activate"] }], imageError: [{ type: i0.Output, args: ["imageError"] }] } });
4264
5390
 
4265
5391
  let nextId$b = 0;
4266
5392
  /**
@@ -4904,6 +6030,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4904
6030
  }]
4905
6031
  }] });
4906
6032
  let nextId$a = 0;
6033
+ /** Fastest auto-scroll step, in px per animation frame (reached at the very edge). */
6034
+ const AUTO_SCROLL_MAX_STEP = 18;
6035
+ /** The nearest ancestor that scrolls vertically (below the document, which is handled as the window). */
6036
+ function scrollParentOf(el) {
6037
+ const doc = el.ownerDocument;
6038
+ for (let node = el.parentElement; node; node = node.parentElement) {
6039
+ if (node === doc.body || node === doc.documentElement)
6040
+ break;
6041
+ const overflowY = getComputedStyle(node).overflowY;
6042
+ if (/(auto|scroll|overlay)/.test(overflowY) && node.scrollHeight > node.clientHeight)
6043
+ return node;
6044
+ }
6045
+ return null;
6046
+ }
6047
+ /**
6048
+ * Signed scroll step for a pointer at `y` inside the band [top, bottom]: negative near the top edge,
6049
+ * positive near the bottom, faster the closer it gets, 0 elsewhere.
6050
+ */
6051
+ function edgeStep(y, top, bottom, edge) {
6052
+ if (bottom - top < edge * 2)
6053
+ edge = (bottom - top) / 3;
6054
+ if (edge <= 0)
6055
+ return 0;
6056
+ const speed = (depth) => Math.ceil(AUTO_SCROLL_MAX_STEP * Math.min(1, depth / edge));
6057
+ if (y < top + edge)
6058
+ return -speed(top + edge - y);
6059
+ if (y > bottom - edge)
6060
+ return speed(y - (bottom - edge));
6061
+ return 0;
6062
+ }
4907
6063
  /** Move one entry of `list` from `from` to `to`, returning a new array. */
4908
6064
  function moveItem(list, from, to) {
4909
6065
  const next = [...list];
@@ -4919,6 +6075,10 @@ function moveItem(list, from, to) {
4919
6075
  * - **Keyboard**: focus a handle, **Space/Enter** picks the item up, **↑/↓** (and Home/End) move it,
4920
6076
  * **Space/Enter** drops it, **Escape** cancels (the event is stopped, so an enclosing drawer stays open).
4921
6077
  * Moving focus away also cancels.
6078
+ * - **Long lists**: while a pointer drag nears the top or bottom edge of the nearest scrollable
6079
+ * ancestor (a drawer body, a panel) — or of the window — that container scrolls, faster closer to
6080
+ * the edge, and the dragged row stays under the pointer. A keyboard move scrolls just enough to keep
6081
+ * the moving row in view. Turn off with `[autoScroll]="false"`.
4922
6082
  * - **Screen readers**: every handle is named ("Reorder Burgers") and described by the instructions; an
4923
6083
  * assertive live region announces pick-up, each move ("moved to position 3 of 8"), the drop and a cancel.
4924
6084
  *
@@ -4955,6 +6115,13 @@ class SdsSortableList {
4955
6115
  appearance = input('card', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
4956
6116
  /** Accessible name of the list. */
4957
6117
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
6118
+ /**
6119
+ * Scroll the nearest scrollable ancestor (then the window) while dragging near its top or bottom
6120
+ * edge, and keep a keyboard-moved row in view.
6121
+ */
6122
+ autoScroll = input(true, ...(ngDevMode ? [{ debugName: "autoScroll", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
6123
+ /** Height (px) of the band along each scroll edge where a pointer drag starts auto-scrolling. */
6124
+ autoScrollEdge = input(56, ...(ngDevMode ? [{ debugName: "autoScrollEdge", transform: numberAttribute }] : [{ transform: numberAttribute }]));
4958
6125
  /** Name of each handle; `{item}` is the item's label. */
4959
6126
  handleLabel = input('Reorder {item}', ...(ngDevMode ? [{ debugName: "handleLabel" }] : []));
4960
6127
  /** Read after a handle's name (aria-describedby). */
@@ -4985,6 +6152,16 @@ class SdsSortableList {
4985
6152
  gap = 0;
4986
6153
  pointerId = null;
4987
6154
  startY = 0;
6155
+ /** Latest pointer position during a pointer drag (auto-scroll re-reads it every frame). */
6156
+ lastY = 0;
6157
+ /** The host's viewport top at drag start: the drift since then is how far its scrollers moved. */
6158
+ startHostTop = 0;
6159
+ /** Nearest scrollable ancestor, found at drag start. */
6160
+ scroller = null;
6161
+ frame = null;
6162
+ constructor() {
6163
+ inject(DestroyRef).onDestroy(() => this.stopAutoScroll());
6164
+ }
4988
6165
  /** Per-row offsets that open a gap where the moving row will land. */
4989
6166
  shifts = computed(() => {
4990
6167
  const a = this.active();
@@ -5118,7 +6295,7 @@ class SdsSortableList {
5118
6295
  case 'Escape':
5119
6296
  event.preventDefault();
5120
6297
  event.stopPropagation();
5121
- this.cancel();
6298
+ this.cancel(true, true);
5122
6299
  return;
5123
6300
  case 'Tab':
5124
6301
  this.cancel();
@@ -5130,6 +6307,7 @@ class SdsSortableList {
5130
6307
  return;
5131
6308
  this.target.set(to);
5132
6309
  this.announce(this.movedMessage(), to);
6310
+ this.revealActive();
5133
6311
  }
5134
6312
  /** Focus leaving the list while an item is picked up cancels the move. */
5135
6313
  onFocusOut(event) {
@@ -5152,17 +6330,39 @@ class SdsSortableList {
5152
6330
  event.currentTarget.focus({ preventScroll: true });
5153
6331
  this.pointerId = event.pointerId;
5154
6332
  this.startY = event.clientY;
6333
+ this.lastY = event.clientY;
5155
6334
  this.pointerDy.set(0);
5156
6335
  this.begin(i, 'pointer');
6336
+ this.startAutoScroll();
5157
6337
  }
5158
6338
  onPointerMove(event) {
5159
6339
  if (this.mode() !== 'pointer' || event.pointerId !== this.pointerId)
5160
6340
  return;
6341
+ this.lastY = event.clientY;
6342
+ this.track();
6343
+ }
6344
+ /**
6345
+ * Place the dragged row under the pointer and pick its landing slot. Positions are compared in
6346
+ * drag-start coordinates: the pointer's travel plus however far the list has scrolled since.
6347
+ */
6348
+ track() {
5161
6349
  const a = this.active();
5162
- if (a === null)
6350
+ if (a === null || !this.rects[a])
5163
6351
  return;
5164
- const dy = event.clientY - this.startY;
6352
+ // Keep the row inside the list: it can't be dropped beyond either end, and a row dragged past the
6353
+ // end would grow the scroll area under an auto-scroll that then never stops.
6354
+ const first = this.rects[0];
6355
+ const last = this.rects[this.rects.length - 1];
6356
+ const minDy = first.top - this.rects[a].top;
6357
+ const maxDy = last.top + last.height - (this.rects[a].top + this.rects[a].height);
6358
+ const dy = Math.min(maxDy, Math.max(minDy, this.lastY - this.startY + this.scrolledBy()));
5165
6359
  this.pointerDy.set(dy);
6360
+ // Pinned at an end of the list: that end's slot (with equal row heights the centre test below
6361
+ // would stop one short of the last slot).
6362
+ if (dy >= maxDy)
6363
+ return this.target.set(this.rects.length - 1);
6364
+ if (dy <= minDy)
6365
+ return this.target.set(0);
5166
6366
  const center = this.rects[a].top + this.rects[a].height / 2 + dy;
5167
6367
  let t = 0;
5168
6368
  this.rects.forEach((r, j) => {
@@ -5171,6 +6371,103 @@ class SdsSortableList {
5171
6371
  });
5172
6372
  this.target.set(t);
5173
6373
  }
6374
+ /** How far (px) the list has moved up the viewport since the drag began — any ancestor or the window. */
6375
+ scrolledBy() {
6376
+ return this.startHostTop - this.host.nativeElement.getBoundingClientRect().top;
6377
+ }
6378
+ // --- auto-scroll ----------------------------------------------------------------
6379
+ startAutoScroll() {
6380
+ if (!this.autoScroll() || typeof requestAnimationFrame === 'undefined')
6381
+ return;
6382
+ this.stopAutoScroll();
6383
+ const tick = () => {
6384
+ this.frame = null;
6385
+ if (this.mode() !== 'pointer')
6386
+ return;
6387
+ this.scrollNearEdge(this.lastY);
6388
+ // Re-track every frame: auto-scroll, or a wheel/trackpad scroll, moves the list under a still pointer.
6389
+ this.track();
6390
+ this.frame = requestAnimationFrame(tick);
6391
+ };
6392
+ this.frame = requestAnimationFrame(tick);
6393
+ }
6394
+ stopAutoScroll() {
6395
+ if (this.frame !== null)
6396
+ cancelAnimationFrame(this.frame);
6397
+ this.frame = null;
6398
+ }
6399
+ /**
6400
+ * One auto-scroll step for a pointer at `y`: the scroll container first, then the window. A side
6401
+ * only scrolls while part of the list is still hidden past that edge.
6402
+ */
6403
+ scrollNearEdge(y) {
6404
+ const edge = Math.max(0, this.autoScrollEdge());
6405
+ const view = this.host.nativeElement.ownerDocument.defaultView;
6406
+ const vh = view?.innerHeight ?? 0;
6407
+ const list = this.host.nativeElement.getBoundingClientRect();
6408
+ /** A step toward a side of the band [top, bottom], or 0 when the list doesn't extend past it. */
6409
+ const stepFor = (top, bottom) => {
6410
+ const step = edgeStep(y, top, bottom, edge);
6411
+ if (step < 0 && list.top >= top)
6412
+ return 0;
6413
+ if (step > 0 && list.bottom <= bottom)
6414
+ return 0;
6415
+ return step;
6416
+ };
6417
+ const el = this.scroller;
6418
+ if (el) {
6419
+ const box = el.getBoundingClientRect();
6420
+ const step = stepFor(Math.max(box.top, 0), Math.min(box.bottom, vh));
6421
+ const room = step < 0 ? el.scrollTop : el.scrollHeight - el.clientHeight - el.scrollTop;
6422
+ if (step !== 0 && room > 0.5) {
6423
+ el.scrollTop += step;
6424
+ return true;
6425
+ }
6426
+ }
6427
+ const doc = this.host.nativeElement.ownerDocument.scrollingElement;
6428
+ if (!view || !doc)
6429
+ return false;
6430
+ const step = stepFor(0, vh);
6431
+ const room = step < 0 ? doc.scrollTop : doc.scrollHeight - doc.clientHeight - doc.scrollTop;
6432
+ if (step === 0 || room <= 0.5)
6433
+ return false;
6434
+ view.scrollBy(0, step);
6435
+ return true;
6436
+ }
6437
+ /**
6438
+ * Keyboard move: scroll just enough (container, then window) to keep the moving row in view. Its
6439
+ * resting box comes from drag-start geometry, since its transform may still be animating.
6440
+ */
6441
+ revealActive() {
6442
+ if (!this.autoScroll())
6443
+ return;
6444
+ afterNextRender(() => {
6445
+ const a = this.active();
6446
+ if (a === null || !this.rects[a] || this.mode() !== 'keyboard')
6447
+ return;
6448
+ const margin = 8;
6449
+ const view = this.host.nativeElement.ownerDocument.defaultView;
6450
+ const vh = view?.innerHeight ?? 0;
6451
+ /** How far the row sticks out of [lo, hi]: negative above, positive below, 0 inside. */
6452
+ const overflow = (lo, hi) => {
6453
+ const top = this.rects[a].top + this.activeOffset() - this.scrolledBy();
6454
+ const bottom = top + this.rects[a].height;
6455
+ return top < lo ? top - lo : bottom > hi ? bottom - hi : 0;
6456
+ };
6457
+ const el = this.scroller;
6458
+ if (el) {
6459
+ const box = el.getBoundingClientRect();
6460
+ const d = overflow(Math.max(box.top, 0) + margin, Math.min(box.bottom, vh) - margin);
6461
+ if (d)
6462
+ el.scrollTop += d;
6463
+ }
6464
+ if (view) {
6465
+ const d = overflow(margin, vh - margin);
6466
+ if (d)
6467
+ view.scrollBy(0, d);
6468
+ }
6469
+ }, { injector: this.injector });
6470
+ }
5174
6471
  onPointerUp(event) {
5175
6472
  if (this.mode() !== 'pointer' || event.pointerId !== this.pointerId)
5176
6473
  return;
@@ -5183,6 +6480,8 @@ class SdsSortableList {
5183
6480
  // --- lifecycle of a move -------------------------------------------------------
5184
6481
  begin(i, mode) {
5185
6482
  this.measure();
6483
+ this.startHostTop = this.host.nativeElement.getBoundingClientRect().top;
6484
+ this.scroller = this.autoScroll() ? scrollParentOf(this.host.nativeElement) : null;
5186
6485
  this.active.set(i);
5187
6486
  this.target.set(i);
5188
6487
  this.mode.set(mode);
@@ -5223,20 +6522,29 @@ class SdsSortableList {
5223
6522
  this.focusHandle(to);
5224
6523
  }
5225
6524
  /** Nothing was moved in the DOM while dragging, so focus is still where it should be. */
5226
- cancel(announce = true) {
6525
+ cancel(announce = true, reveal = false) {
5227
6526
  const from = this.active();
5228
6527
  if (from === null)
5229
6528
  return;
6529
+ const keyboard = this.mode() === 'keyboard';
5230
6530
  this.reset();
5231
6531
  if (announce)
5232
6532
  this.announce(this.cancelledMessage(), from);
6533
+ // Escape after a keyboard move that scrolled away: bring the row (and its focused handle) back.
6534
+ if (reveal && keyboard && this.autoScroll()) {
6535
+ afterNextRender(() => this.host.nativeElement
6536
+ .querySelector(`[data-sortable-index="${from}"]`)
6537
+ ?.scrollIntoView({ block: 'nearest' }), { injector: this.injector });
6538
+ }
5233
6539
  }
5234
6540
  reset() {
6541
+ this.stopAutoScroll();
5235
6542
  this.active.set(null);
5236
6543
  this.target.set(null);
5237
6544
  this.mode.set(null);
5238
6545
  this.pointerDy.set(0);
5239
6546
  this.pointerId = null;
6547
+ this.scroller = null;
5240
6548
  }
5241
6549
  focusHandle(index) {
5242
6550
  afterNextRender(() => {
@@ -5257,7 +6565,7 @@ class SdsSortableList {
5257
6565
  queueMicrotask(() => this.liveMessage.set(text));
5258
6566
  }
5259
6567
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSortableList, deps: [], target: i0.ɵɵFactoryTarget.Component });
5260
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSortableList, isStandalone: true, selector: "sds-sortable-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, labelField: { classPropertyName: "labelField", publicName: "labelField", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemDisabled: { classPropertyName: "itemDisabled", publicName: "itemDisabled", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, handleLabel: { classPropertyName: "handleLabel", publicName: "handleLabel", isSignal: true, isRequired: false, transformFunction: null }, instructions: { classPropertyName: "instructions", publicName: "instructions", isSignal: true, isRequired: false, transformFunction: null }, pickedUpMessage: { classPropertyName: "pickedUpMessage", publicName: "pickedUpMessage", isSignal: true, isRequired: false, transformFunction: null }, movedMessage: { classPropertyName: "movedMessage", publicName: "movedMessage", isSignal: true, isRequired: false, transformFunction: null }, droppedMessage: { classPropertyName: "droppedMessage", publicName: "droppedMessage", isSignal: true, isRequired: false, transformFunction: null }, cancelledMessage: { classPropertyName: "cancelledMessage", publicName: "cancelledMessage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { items: "itemsChange", reorder: "reorder" }, host: { classAttribute: "block" }, queries: [{ propertyName: "itemTpl", first: true, predicate: SdsSortableItem, descendants: true, isSignal: true }, { propertyName: "handleTpl", first: true, predicate: SdsSortableHandle, descendants: true, isSignal: true }], ngImport: i0, template: `
6568
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSortableList, isStandalone: true, selector: "sds-sortable-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, labelField: { classPropertyName: "labelField", publicName: "labelField", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemDisabled: { classPropertyName: "itemDisabled", publicName: "itemDisabled", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, autoScroll: { classPropertyName: "autoScroll", publicName: "autoScroll", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEdge: { classPropertyName: "autoScrollEdge", publicName: "autoScrollEdge", isSignal: true, isRequired: false, transformFunction: null }, handleLabel: { classPropertyName: "handleLabel", publicName: "handleLabel", isSignal: true, isRequired: false, transformFunction: null }, instructions: { classPropertyName: "instructions", publicName: "instructions", isSignal: true, isRequired: false, transformFunction: null }, pickedUpMessage: { classPropertyName: "pickedUpMessage", publicName: "pickedUpMessage", isSignal: true, isRequired: false, transformFunction: null }, movedMessage: { classPropertyName: "movedMessage", publicName: "movedMessage", isSignal: true, isRequired: false, transformFunction: null }, droppedMessage: { classPropertyName: "droppedMessage", publicName: "droppedMessage", isSignal: true, isRequired: false, transformFunction: null }, cancelledMessage: { classPropertyName: "cancelledMessage", publicName: "cancelledMessage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { items: "itemsChange", reorder: "reorder" }, host: { classAttribute: "block" }, queries: [{ propertyName: "itemTpl", first: true, predicate: SdsSortableItem, descendants: true, isSignal: true }, { propertyName: "handleTpl", first: true, predicate: SdsSortableHandle, descendants: true, isSignal: true }], ngImport: i0, template: `
5261
6569
  <ul
5262
6570
  role="list"
5263
6571
  [attr.aria-label]="ariaLabel() || null"
@@ -5380,7 +6688,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
5380
6688
  <div class="sr-only" aria-live="assertive" aria-atomic="true">{{ liveMessage() }}</div>
5381
6689
  `,
5382
6690
  }]
5383
- }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }, { type: i0.Output, args: ["itemsChange"] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], labelField: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelField", required: false }] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], itemDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemDisabled", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], handleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "handleLabel", required: false }] }], instructions: [{ type: i0.Input, args: [{ isSignal: true, alias: "instructions", required: false }] }], pickedUpMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickedUpMessage", required: false }] }], movedMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "movedMessage", required: false }] }], droppedMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "droppedMessage", required: false }] }], cancelledMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelledMessage", required: false }] }], reorder: [{ type: i0.Output, args: ["reorder"] }], itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSortableItem), { isSignal: true }] }], handleTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSortableHandle), { isSignal: true }] }] } });
6691
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }, { type: i0.Output, args: ["itemsChange"] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], labelField: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelField", required: false }] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], itemDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemDisabled", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], autoScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScroll", required: false }] }], autoScrollEdge: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollEdge", required: false }] }], handleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "handleLabel", required: false }] }], instructions: [{ type: i0.Input, args: [{ isSignal: true, alias: "instructions", required: false }] }], pickedUpMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickedUpMessage", required: false }] }], movedMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "movedMessage", required: false }] }], droppedMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "droppedMessage", required: false }] }], cancelledMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelledMessage", required: false }] }], reorder: [{ type: i0.Output, args: ["reorder"] }], itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSortableItem), { isSignal: true }] }], handleTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSortableHandle), { isSignal: true }] }] } });
5384
6692
 
5385
6693
  let nextId$9 = 0;
5386
6694
  /** "1.5 MB" style size (decimal units, like file managers show them), for messages. */
@@ -6282,41 +7590,41 @@ class SdsCalendar {
6282
7590
  max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
6283
7591
  /** 0 = Sunday, 1 = Monday (default, European). */
6284
7592
  firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : []));
6285
- locale = input('fr-FR', ...(ngDevMode ? [{ debugName: "locale" }] : []));
7593
+ /** BCP 47 locale for month and weekday names. Defaults to `en-GB`; pass the app's locale. */
7594
+ locale = input('en-GB', ...(ngDevMode ? [{ debugName: "locale" }] : []));
6286
7595
  showActions = input(true, ...(ngDevMode ? [{ debugName: "showActions" }] : []));
6287
7596
  /** When true, a time editor (HH:mm, 24h) is shown and the value keeps its time-of-day. */
6288
7597
  showTime = input(false, ...(ngDevMode ? [{ debugName: "showTime", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
6289
7598
  /** When true, a "done" action is shown (used by the overlay picker to close the panel). */
6290
7599
  showDone = input(false, ...(ngDevMode ? [{ debugName: "showDone", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
6291
7600
  /** Visible label of the time editor. Translate it in the consuming app. */
6292
- timeLabel = input('Heure', ...(ngDevMode ? [{ debugName: "timeLabel" }] : []));
7601
+ timeLabel = input('Time', ...(ngDevMode ? [{ debugName: "timeLabel" }] : []));
6293
7602
  /** Accessible name of the hours field (it is a bare numeric input). */
6294
- hoursLabel = input('Heures', ...(ngDevMode ? [{ debugName: "hoursLabel" }] : []));
7603
+ hoursLabel = input('Hours', ...(ngDevMode ? [{ debugName: "hoursLabel" }] : []));
6295
7604
  /** Accessible name of the minutes field. */
6296
7605
  minutesLabel = input('Minutes', ...(ngDevMode ? [{ debugName: "minutesLabel" }] : []));
6297
7606
  /** Visible text of the "done" action (shown when {@link showDone} is true). */
6298
- doneLabel = input('Terminé', ...(ngDevMode ? [{ debugName: "doneLabel" }] : []));
7607
+ doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : []));
6299
7608
  /**
6300
- * Accessible name for the calendar container. French default so existing consumers are unchanged,
6301
- * but an app serving more than one language MUST translate it.
7609
+ * Accessible name for the calendar container. English default; an app serving more than one
7610
+ * language MUST translate it.
6302
7611
  */
6303
- calendarLabel = input('Calendrier', ...(ngDevMode ? [{ debugName: "calendarLabel" }] : []));
7612
+ calendarLabel = input('Calendar', ...(ngDevMode ? [{ debugName: "calendarLabel" }] : []));
6304
7613
  /**
6305
7614
  * Accessible name for the "previous" nav button. It is icon-only, so this string is its ONLY
6306
- * accessible name — a hardcoded French one is simply wrong in every other locale. Translate it in
6307
- * the consuming app.
7615
+ * accessible name. English default; translate it in the consuming app.
6308
7616
  */
6309
- previousLabel = input('Précédent', ...(ngDevMode ? [{ debugName: "previousLabel" }] : []));
7617
+ previousLabel = input('Previous', ...(ngDevMode ? [{ debugName: "previousLabel" }] : []));
6310
7618
  /** Accessible name for the icon-only "next" nav button. See {@link previousLabel}. */
6311
- nextLabel = input('Suivant', ...(ngDevMode ? [{ debugName: "nextLabel" }] : []));
7619
+ nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : []));
6312
7620
  /**
6313
7621
  * Visible text of the "today" action (shown when {@link showActions} is true). Translate it in the
6314
7622
  * consuming app — the library ships no i18n of its own.
6315
7623
  */
6316
- todayActionLabel = input("Aujourd'hui", ...(ngDevMode ? [{ debugName: "todayActionLabel" }] : []));
7624
+ todayActionLabel = input('Today', ...(ngDevMode ? [{ debugName: "todayActionLabel" }] : []));
6317
7625
  /** Visible text of the "clear" action. See {@link todayActionLabel}. */
6318
- clearActionLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearActionLabel" }] : []));
6319
- /** Emitted when a concrete date is picked (day click or "Aujourd'hui"). */
7626
+ clearActionLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearActionLabel" }] : []));
7627
+ /** Emitted when a concrete date is picked (day click or "Today"). */
6320
7628
  dateSelect = output();
6321
7629
  /** Emitted when the "done" action is used (see {@link showDone}). */
6322
7630
  done = output();
@@ -7079,7 +8387,7 @@ const SIZES$5 = {
7079
8387
  * [(ngModel)]; the value is a `Date | null`.
7080
8388
  *
7081
8389
  * @example
7082
- * <sds-date-picker label="Date de garde" [(ngModel)]="date" />
8390
+ * <sds-date-picker label="Delivery date" [(ngModel)]="date" />
7083
8391
  */
7084
8392
  class SdsDatePicker {
7085
8393
  host = inject(ElementRef);
@@ -7087,14 +8395,18 @@ class SdsDatePicker {
7087
8395
  fieldId = `sds-date-${nextId$7++}`;
7088
8396
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
7089
8397
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
7090
- placeholder = input('jj/mm/aaaa', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
8398
+ placeholder = input('dd/mm/yyyy', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
7091
8399
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
7092
8400
  error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
7093
8401
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
7094
8402
  min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
7095
8403
  max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
7096
8404
  firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : []));
7097
- locale = input('fr-FR', ...(ngDevMode ? [{ debugName: "locale" }] : []));
8405
+ /**
8406
+ * BCP 47 locale for the calendar's month and weekday names. Defaults to `en-GB` (English names, and
8407
+ * the same day-first order as the default dd/MM/yyyy display); pass the app's locale.
8408
+ */
8409
+ locale = input('en-GB', ...(ngDevMode ? [{ debugName: "locale" }] : []));
7098
8410
  inline = input(false, ...(ngDevMode ? [{ debugName: "inline", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
7099
8411
  clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
7100
8412
  showActions = input(true, ...(ngDevMode ? [{ debugName: "showActions", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
@@ -7108,38 +8420,37 @@ class SdsDatePicker {
7108
8420
  /** Custom display formatter for the DATE part (default dd/MM/yyyy). The time is appended as HH:mm. */
7109
8421
  format = input(formatDdMmYyyy, ...(ngDevMode ? [{ debugName: "format" }] : []));
7110
8422
  /** Visible label of the time editor (forwarded to the calendar). See {@link showTime}. */
7111
- timeLabel = input('Heure', ...(ngDevMode ? [{ debugName: "timeLabel" }] : []));
8423
+ timeLabel = input('Time', ...(ngDevMode ? [{ debugName: "timeLabel" }] : []));
7112
8424
  /** Accessible name of the hours field (forwarded to the calendar). */
7113
- hoursLabel = input('Heures', ...(ngDevMode ? [{ debugName: "hoursLabel" }] : []));
8425
+ hoursLabel = input('Hours', ...(ngDevMode ? [{ debugName: "hoursLabel" }] : []));
7114
8426
  /** Accessible name of the minutes field (forwarded to the calendar). */
7115
8427
  minutesLabel = input('Minutes', ...(ngDevMode ? [{ debugName: "minutesLabel" }] : []));
7116
8428
  /** Visible text of the calendar's "done" action, shown in the overlay when {@link showTime}. */
7117
- doneLabel = input('Terminé', ...(ngDevMode ? [{ debugName: "doneLabel" }] : []));
8429
+ doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : []));
7118
8430
  /**
7119
- * Accessible name for the clear button. French default so existing consumers are unchanged, but an
7120
- * app serving more than one language MUST translate it — it is an icon-only control, so this string
7121
- * is its only name.
8431
+ * Accessible name for the clear button. English default; an app serving more than one language MUST
8432
+ * translate it — it is an icon-only control, so this string is its only name.
7122
8433
  */
7123
- clearLabel = input('Effacer la date', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
8434
+ clearLabel = input('Clear date', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
7124
8435
  /**
7125
8436
  * Accessible name for the icon-only calendar toggle while the panel is CLOSED. See
7126
8437
  * {@link clearLabel} for why this has to be translatable.
7127
8438
  */
7128
- openCalendarLabel = input('Ouvrir le calendrier', ...(ngDevMode ? [{ debugName: "openCalendarLabel" }] : []));
8439
+ openCalendarLabel = input('Open calendar', ...(ngDevMode ? [{ debugName: "openCalendarLabel" }] : []));
7129
8440
  /** Accessible name for the calendar toggle while the panel is OPEN. See {@link openCalendarLabel}. */
7130
- closeCalendarLabel = input('Fermer le calendrier', ...(ngDevMode ? [{ debugName: "closeCalendarLabel" }] : []));
8441
+ closeCalendarLabel = input('Close calendar', ...(ngDevMode ? [{ debugName: "closeCalendarLabel" }] : []));
7131
8442
  /** Accessible name for the overlay dialog that holds the calendar. */
7132
- panelLabel = input('Sélection de date', ...(ngDevMode ? [{ debugName: "panelLabel" }] : []));
8443
+ panelLabel = input('Choose a date', ...(ngDevMode ? [{ debugName: "panelLabel" }] : []));
7133
8444
  /**
7134
8445
  * Labels forwarded to the embedded <sds-calendar>. They live here because the picker is how the
7135
8446
  * calendar is normally reached — without forwarding, a consumer could translate the field but not
7136
8447
  * the panel it opens. Defaults match SdsCalendar's own, so nothing changes for existing consumers.
7137
8448
  */
7138
- calendarLabel = input('Calendrier', ...(ngDevMode ? [{ debugName: "calendarLabel" }] : []));
7139
- previousLabel = input('Précédent', ...(ngDevMode ? [{ debugName: "previousLabel" }] : []));
7140
- nextLabel = input('Suivant', ...(ngDevMode ? [{ debugName: "nextLabel" }] : []));
7141
- todayActionLabel = input("Aujourd'hui", ...(ngDevMode ? [{ debugName: "todayActionLabel" }] : []));
7142
- clearActionLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearActionLabel" }] : []));
8449
+ calendarLabel = input('Calendar', ...(ngDevMode ? [{ debugName: "calendarLabel" }] : []));
8450
+ previousLabel = input('Previous', ...(ngDevMode ? [{ debugName: "previousLabel" }] : []));
8451
+ nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : []));
8452
+ todayActionLabel = input('Today', ...(ngDevMode ? [{ debugName: "todayActionLabel" }] : []));
8453
+ clearActionLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearActionLabel" }] : []));
7143
8454
  value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
7144
8455
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
7145
8456
  cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
@@ -7705,7 +9016,7 @@ let nextId$6 = 0;
7705
9016
  *
7706
9017
  * @example
7707
9018
  * <sds-select [options]="services" optionLabel="name" optionValue="id"
7708
- * placeholder="Choisir un service" [(ngModel)]="serviceId" filter clearable />
9019
+ * placeholder="Choose a service" [(ngModel)]="serviceId" filter clearable />
7709
9020
  *
7710
9021
  * @example
7711
9022
  * <sds-select [options]="categories" optionLabel="name" optionValue="id" [(ngModel)]="categoryId">
@@ -7725,7 +9036,7 @@ class SdsSelect {
7725
9036
  optionDisabled = input('disabled', ...(ngDevMode ? [{ debugName: "optionDisabled" }] : []));
7726
9037
  /** Key used to compare object values for selection equality. */
7727
9038
  dataKey = input('', ...(ngDevMode ? [{ debugName: "dataKey" }] : []));
7728
- placeholder = input('Sélectionner…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
9039
+ placeholder = input('Select…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
7729
9040
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
7730
9041
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
7731
9042
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
@@ -7735,8 +9046,15 @@ class SdsSelect {
7735
9046
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
7736
9047
  clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
7737
9048
  filter = input(false, ...(ngDevMode ? [{ debugName: "filter", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
7738
- filterPlaceholder = input('Rechercher…', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
7739
- emptyMessage = input('Aucun résultat', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
9049
+ filterPlaceholder = input('Search…', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
9050
+ emptyMessage = input('No results', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
9051
+ /**
9052
+ * Accessible name of the icon-only clear button (see {@link clearable}). English default; an app
9053
+ * serving more than one language passes a translated value — it is the button's only name.
9054
+ */
9055
+ clearLabel = input('Clear selection', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
9056
+ /** Accessible name of the filter field inside the panel (see {@link filter}). */
9057
+ filterLabel = input('Search', ...(ngDevMode ? [{ debugName: "filterLabel" }] : []));
7740
9058
  /** Optional projected `<ng-template sdsSelectOption>` — custom option + selected-value rendering. */
7741
9059
  optionTpl = contentChild(SdsSelectOption, ...(ngDevMode ? [{ debugName: "optionTpl" }] : []));
7742
9060
  /** Optional projected `<ng-template sdsSelectEmpty>` — custom no-results content. */
@@ -8092,7 +9410,7 @@ class SdsSelect {
8092
9410
  this.close();
8093
9411
  }
8094
9412
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
8095
- 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: [
9413
+ 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 }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block" }, providers: [
8096
9414
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsSelect), multi: true },
8097
9415
  ], 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: `
8098
9416
  @if (label()) {
@@ -8137,7 +9455,7 @@ class SdsSelect {
8137
9455
  <button
8138
9456
  type="button"
8139
9457
  tabindex="-1"
8140
- aria-label="Effacer la sélection"
9458
+ [attr.aria-label]="clearLabel()"
8141
9459
  class="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-ink-faint hover:bg-black/5 hover:text-ink-soft"
8142
9460
  (click)="clear($event)"
8143
9461
  >
@@ -8170,7 +9488,7 @@ class SdsSelect {
8170
9488
  [value]="filterText()"
8171
9489
  (input)="onFilter($event)"
8172
9490
  (keydown)="onFilterKeydown($event)"
8173
- aria-label="Rechercher"
9491
+ [attr.aria-label]="filterLabel()"
8174
9492
  class="h-9 w-full bg-transparent pl-8 pr-2 text-sm text-ink outline-none placeholder:text-ink-faint"
8175
9493
  />
8176
9494
  </div>
@@ -8293,7 +9611,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
8293
9611
  <button
8294
9612
  type="button"
8295
9613
  tabindex="-1"
8296
- aria-label="Effacer la sélection"
9614
+ [attr.aria-label]="clearLabel()"
8297
9615
  class="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-ink-faint hover:bg-black/5 hover:text-ink-soft"
8298
9616
  (click)="clear($event)"
8299
9617
  >
@@ -8326,7 +9644,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
8326
9644
  [value]="filterText()"
8327
9645
  (input)="onFilter($event)"
8328
9646
  (keydown)="onFilterKeydown($event)"
8329
- aria-label="Rechercher"
9647
+ [attr.aria-label]="filterLabel()"
8330
9648
  class="h-9 w-full bg-transparent pl-8 pr-2 text-sm text-ink outline-none placeholder:text-ink-faint"
8331
9649
  />
8332
9650
  </div>
@@ -8393,7 +9711,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
8393
9711
  }
8394
9712
  `,
8395
9713
  }]
8396
- }], 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 }] }] } });
9714
+ }], 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 }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], filterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLabel", 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 }] }] } });
8397
9715
 
8398
9716
  const SIZES$3 = {
8399
9717
  sm: 'min-h-9 text-sm',
@@ -8440,7 +9758,7 @@ class SdsMultiSelect {
8440
9758
  optionValue = input('', ...(ngDevMode ? [{ debugName: "optionValue" }] : []));
8441
9759
  optionDisabled = input('disabled', ...(ngDevMode ? [{ debugName: "optionDisabled" }] : []));
8442
9760
  dataKey = input('', ...(ngDevMode ? [{ debugName: "dataKey" }] : []));
8443
- placeholder = input('Sélectionner…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
9761
+ placeholder = input('Select…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
8444
9762
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
8445
9763
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
8446
9764
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
@@ -8449,13 +9767,24 @@ class SdsMultiSelect {
8449
9767
  display = input('comma', ...(ngDevMode ? [{ debugName: "display" }] : []));
8450
9768
  /** In comma mode, show a count label once more than this many are selected. */
8451
9769
  maxSelectedLabels = input(3, ...(ngDevMode ? [{ debugName: "maxSelectedLabels", transform: numberAttribute }] : [{ transform: numberAttribute }]));
8452
- selectedItemsLabel = input('{0} éléments sélectionnés', ...(ngDevMode ? [{ debugName: "selectedItemsLabel" }] : []));
9770
+ selectedItemsLabel = input('{0} items selected', ...(ngDevMode ? [{ debugName: "selectedItemsLabel" }] : []));
8453
9771
  showToggleAll = input(true, ...(ngDevMode ? [{ debugName: "showToggleAll", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
8454
9772
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
8455
9773
  clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
8456
9774
  filter = input(false, ...(ngDevMode ? [{ debugName: "filter", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
8457
- filterPlaceholder = input('Rechercher…', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
8458
- emptyMessage = input('Aucun résultat', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
9775
+ filterPlaceholder = input('Search…', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
9776
+ emptyMessage = input('No results', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
9777
+ /**
9778
+ * Accessible name of a chip's icon-only remove button (`display="chip"`); `{label}` is the option's
9779
+ * label. English defaults throughout — an app serving more than one language passes translations.
9780
+ */
9781
+ removeLabel = input('Remove {label}', ...(ngDevMode ? [{ debugName: "removeLabel" }] : []));
9782
+ /** Accessible name of the icon-only clear-all button (see {@link clearable}). */
9783
+ clearLabel = input('Clear all', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
9784
+ /** Accessible name of the filter field inside the panel (see {@link filter}). */
9785
+ filterLabel = input('Search', ...(ngDevMode ? [{ debugName: "filterLabel" }] : []));
9786
+ /** Visible text of the "select all" row (see {@link showToggleAll}). */
9787
+ selectAllLabel = input('Select all', ...(ngDevMode ? [{ debugName: "selectAllLabel" }] : []));
8459
9788
  /** Optional projected `<ng-template sdsSelectOption>` — custom option-row rendering. */
8460
9789
  optionTpl = contentChild(SdsSelectOption, ...(ngDevMode ? [{ debugName: "optionTpl" }] : []));
8461
9790
  /** Optional projected `<ng-template sdsSelectEmpty>` — custom no-results content. */
@@ -8855,7 +10184,7 @@ class SdsMultiSelect {
8855
10184
  this.close();
8856
10185
  }
8857
10186
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsMultiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
8858
- 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: [
10187
+ 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 }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block" }, providers: [
8859
10188
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsMultiSelect), multi: true },
8860
10189
  ], 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: `
8861
10190
  @if (label()) {
@@ -8888,7 +10217,7 @@ class SdsMultiSelect {
8888
10217
  <button
8889
10218
  type="button"
8890
10219
  tabindex="-1"
8891
- [attr.aria-label]="'Retirer ' + opt.label"
10220
+ [attr.aria-label]="removeLabel().replace('{label}', opt.label)"
8892
10221
  class="inline-flex h-4 w-4 items-center justify-center rounded-full hover:bg-brand-100"
8893
10222
  (click)="removeChip($event, opt)"
8894
10223
  >
@@ -8910,7 +10239,7 @@ class SdsMultiSelect {
8910
10239
  <button
8911
10240
  type="button"
8912
10241
  tabindex="-1"
8913
- aria-label="Tout effacer"
10242
+ [attr.aria-label]="clearLabel()"
8914
10243
  class="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center self-center rounded-full text-ink-faint hover:bg-black/5 hover:text-ink-soft"
8915
10244
  (click)="clear($event)"
8916
10245
  >
@@ -8943,7 +10272,7 @@ class SdsMultiSelect {
8943
10272
  [value]="filterText()"
8944
10273
  (input)="onFilter($event)"
8945
10274
  (keydown)="onFilterKeydown($event)"
8946
- aria-label="Rechercher"
10275
+ [attr.aria-label]="filterLabel()"
8947
10276
  class="h-9 w-full bg-transparent pl-8 pr-2 text-sm text-ink outline-none placeholder:text-ink-faint"
8948
10277
  />
8949
10278
  </div>
@@ -8969,7 +10298,7 @@ class SdsMultiSelect {
8969
10298
  <span class="h-0.5 w-2.5 rounded-full bg-white"></span>
8970
10299
  }
8971
10300
  </span>
8972
- Tout sélectionner
10301
+ {{ selectAllLabel() }}
8973
10302
  </button>
8974
10303
  }
8975
10304
 
@@ -9079,7 +10408,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9079
10408
  <button
9080
10409
  type="button"
9081
10410
  tabindex="-1"
9082
- [attr.aria-label]="'Retirer ' + opt.label"
10411
+ [attr.aria-label]="removeLabel().replace('{label}', opt.label)"
9083
10412
  class="inline-flex h-4 w-4 items-center justify-center rounded-full hover:bg-brand-100"
9084
10413
  (click)="removeChip($event, opt)"
9085
10414
  >
@@ -9101,7 +10430,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9101
10430
  <button
9102
10431
  type="button"
9103
10432
  tabindex="-1"
9104
- aria-label="Tout effacer"
10433
+ [attr.aria-label]="clearLabel()"
9105
10434
  class="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center self-center rounded-full text-ink-faint hover:bg-black/5 hover:text-ink-soft"
9106
10435
  (click)="clear($event)"
9107
10436
  >
@@ -9134,7 +10463,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9134
10463
  [value]="filterText()"
9135
10464
  (input)="onFilter($event)"
9136
10465
  (keydown)="onFilterKeydown($event)"
9137
- aria-label="Rechercher"
10466
+ [attr.aria-label]="filterLabel()"
9138
10467
  class="h-9 w-full bg-transparent pl-8 pr-2 text-sm text-ink outline-none placeholder:text-ink-faint"
9139
10468
  />
9140
10469
  </div>
@@ -9160,7 +10489,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9160
10489
  <span class="h-0.5 w-2.5 rounded-full bg-white"></span>
9161
10490
  }
9162
10491
  </span>
9163
- Tout sélectionner
10492
+ {{ selectAllLabel() }}
9164
10493
  </button>
9165
10494
  }
9166
10495
 
@@ -9226,29 +10555,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9226
10555
  }
9227
10556
  `,
9228
10557
  }]
9229
- }], 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 }] }] } });
10558
+ }], 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 }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], filterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLabel", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", 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 }] }] } });
9230
10559
 
9231
10560
  const SDS_PHONE_COUNTRIES = [
9232
- { iso2: 'BE', name: 'Belgique', dialCode: '32', lengths: [9], format: '### ## ## ##', example: '470123456' },
10561
+ { iso2: 'BE', name: 'Belgium', dialCode: '32', lengths: [9], format: '### ## ## ##', example: '470123456' },
9233
10562
  { iso2: 'FR', name: 'France', dialCode: '33', lengths: [9], format: '# ## ## ## ##', example: '612345678' },
9234
10563
  { iso2: 'LU', name: 'Luxembourg', dialCode: '352', lengths: [9, 8, 6], format: '### ### ###', example: '628123456' },
9235
- { iso2: 'NL', name: 'Pays-Bas', dialCode: '31', lengths: [9], format: '# ## ## ## ##', example: '612345678' },
9236
- { iso2: 'DE', name: 'Allemagne', dialCode: '49', lengths: [10, 11], format: '#### #######', example: '15123456789' },
9237
- { iso2: 'GB', name: 'Royaume-Uni', dialCode: '44', lengths: [10], format: '#### ######', example: '7400123456' },
9238
- { iso2: 'IE', name: 'Irlande', dialCode: '353', lengths: [9], format: '## ### ####', example: '850123456' },
9239
- { iso2: 'CH', name: 'Suisse', dialCode: '41', lengths: [9], format: '## ### ## ##', example: '781234567' },
9240
- { iso2: 'ES', name: 'Espagne', dialCode: '34', lengths: [9], format: '### ## ## ##', example: '612345678' },
9241
- { iso2: 'IT', name: 'Italie', dialCode: '39', lengths: [9, 10], format: '### ### ####', example: '3123456789' },
10564
+ { iso2: 'NL', name: 'Netherlands', dialCode: '31', lengths: [9], format: '# ## ## ## ##', example: '612345678' },
10565
+ { iso2: 'DE', name: 'Germany', dialCode: '49', lengths: [10, 11], format: '#### #######', example: '15123456789' },
10566
+ { iso2: 'GB', name: 'United Kingdom', dialCode: '44', lengths: [10], format: '#### ######', example: '7400123456' },
10567
+ { iso2: 'IE', name: 'Ireland', dialCode: '353', lengths: [9], format: '## ### ####', example: '850123456' },
10568
+ { iso2: 'CH', name: 'Switzerland', dialCode: '41', lengths: [9], format: '## ### ## ##', example: '781234567' },
10569
+ { iso2: 'ES', name: 'Spain', dialCode: '34', lengths: [9], format: '### ## ## ##', example: '612345678' },
10570
+ { iso2: 'IT', name: 'Italy', dialCode: '39', lengths: [9, 10], format: '### ### ####', example: '3123456789' },
9242
10571
  { iso2: 'PT', name: 'Portugal', dialCode: '351', lengths: [9], format: '### ### ###', example: '912345678' },
9243
- { iso2: 'AT', name: 'Autriche', dialCode: '43', lengths: [10, 11], format: '### #######', example: '6641234567' },
9244
- { iso2: 'DK', name: 'Danemark', dialCode: '45', lengths: [8], format: '## ## ## ##', example: '20123456' },
9245
- { iso2: 'SE', name: 'Suède', dialCode: '46', lengths: [9], format: '## ### ## ##', example: '701234567' },
9246
- { iso2: 'NO', name: 'Norvège', dialCode: '47', lengths: [8], format: '### ## ###', example: '40612345' },
9247
- { iso2: 'PL', name: 'Pologne', dialCode: '48', lengths: [9], format: '### ### ###', example: '512345678' },
9248
- { iso2: 'US', name: 'États-Unis', dialCode: '1', lengths: [10], format: '(###) ###-####', example: '2015550123' },
10572
+ { iso2: 'AT', name: 'Austria', dialCode: '43', lengths: [10, 11], format: '### #######', example: '6641234567' },
10573
+ { iso2: 'DK', name: 'Denmark', dialCode: '45', lengths: [8], format: '## ## ## ##', example: '20123456' },
10574
+ { iso2: 'SE', name: 'Sweden', dialCode: '46', lengths: [9], format: '## ### ## ##', example: '701234567' },
10575
+ { iso2: 'NO', name: 'Norway', dialCode: '47', lengths: [8], format: '### ## ###', example: '40612345' },
10576
+ { iso2: 'PL', name: 'Poland', dialCode: '48', lengths: [9], format: '### ### ###', example: '512345678' },
10577
+ { iso2: 'US', name: 'United States', dialCode: '1', lengths: [10], format: '(###) ###-####', example: '2015550123' },
9249
10578
  { iso2: 'CA', name: 'Canada', dialCode: '1', lengths: [10], format: '(###) ###-####', example: '2042345678' },
9250
- { iso2: 'MA', name: 'Maroc', dialCode: '212', lengths: [9], format: '### ## ## ##', example: '612345678' },
9251
- { iso2: 'TR', name: 'Turquie', dialCode: '90', lengths: [10], format: '### ### ## ##', example: '5012345678' },
10579
+ { iso2: 'MA', name: 'Morocco', dialCode: '212', lengths: [9], format: '### ## ## ##', example: '612345678' },
10580
+ { iso2: 'TR', name: 'Turkey', dialCode: '90', lengths: [10], format: '### ### ## ##', example: '5012345678' },
9252
10581
  ];
9253
10582
  /** Reduce raw input to national-significant digits (drops non-digits & leading trunk zeros). */
9254
10583
  function sdsNormalizeSignificant(raw) {
@@ -9303,7 +10632,7 @@ let nextId$4 = 0;
9303
10632
  * '+32470123456') and Validator (national length is checked per country).
9304
10633
  *
9305
10634
  * @example
9306
- * <sds-phone-input label="Téléphone" defaultCountry="BE"
10635
+ * <sds-phone-input label="Phone" defaultCountry="BE"
9307
10636
  * [preferredCountries]="['BE','FR','LU']"
9308
10637
  * [(ngModel)]="phone" required />
9309
10638
  */
@@ -9328,43 +10657,67 @@ class SdsPhoneInput {
9328
10657
  onlyCountries = input([], ...(ngDevMode ? [{ debugName: "onlyCountries" }] : []));
9329
10658
  /** Show an example number as a hint when no [hint] is provided. */
9330
10659
  showExampleHint = input(true, ...(ngDevMode ? [{ debugName: "showExampleHint", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
9331
- requiredMessage = input('Numéro de téléphone requis', ...(ngDevMode ? [{ debugName: "requiredMessage" }] : []));
10660
+ /**
10661
+ * BCP 47 locale for the country names (list, search and the selector's accessible name), resolved
10662
+ * with `Intl.DisplayNames` — e.g. `nl-BE` shows "België". Empty (default) uses the built-in English
10663
+ * names. The search also matches the English name.
10664
+ */
10665
+ locale = input('', ...(ngDevMode ? [{ debugName: "locale" }] : []));
10666
+ requiredMessage = input('Phone number required', ...(ngDevMode ? [{ debugName: "requiredMessage" }] : []));
9332
10667
  /**
9333
10668
  * Accessible name of the country selector. `{country}` is replaced with the selected country's
9334
- * name. A token rather than a prefix because word order differs per language. French default so
9335
- * existing consumers are unchanged; translate it in any app serving more than one language.
9336
- *
9337
- * Note the interpolated country NAME still comes from the library's built-in country table, which
9338
- * is French-only — translating this template fixes the wrapper, not the name inside it.
10669
+ * name (localised by {@link locale}). A token rather than a prefix because word order differs per
10670
+ * language. English default; translate it in any app serving more than one language.
9339
10671
  */
9340
- countryLabel = input('Indicatif pays : {country}', ...(ngDevMode ? [{ debugName: "countryLabel" }] : []));
10672
+ countryLabel = input('Country code: {country}', ...(ngDevMode ? [{ debugName: "countryLabel" }] : []));
9341
10673
  /**
9342
10674
  * Accessible name for the clear button. It is icon-only, so this string is its ONLY accessible
9343
- * name — a hardcoded French one is simply wrong in every other locale.
10675
+ * name — translate it in any app serving more than one language.
9344
10676
  */
9345
- clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
10677
+ clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
9346
10678
  /** Accessible name for the country-search field inside the dropdown. */
9347
- searchLabel = input('Rechercher un pays', ...(ngDevMode ? [{ debugName: "searchLabel" }] : []));
10679
+ searchLabel = input('Search countries', ...(ngDevMode ? [{ debugName: "searchLabel" }] : []));
9348
10680
  /** Placeholder text of the country-search field. */
9349
- searchPlaceholder = input('Rechercher un pays…', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
10681
+ searchPlaceholder = input('Search countries…', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
9350
10682
  /** Shown in place of the list when no country matches the filter. */
9351
- noResultsLabel = input('Aucun pays', ...(ngDevMode ? [{ debugName: "noResultsLabel" }] : []));
10683
+ noResultsLabel = input('No countries found', ...(ngDevMode ? [{ debugName: "noResultsLabel" }] : []));
9352
10684
  /**
9353
10685
  * Built-in validation messages. `{lengths}` is replaced with the country's accepted national
9354
- * lengths, joined with {@link lengthSeparator}. Siblings of {@link requiredMessage}, which was
9355
- * already translatable — these two were not, so an English or Dutch app showed French errors.
10686
+ * lengths, joined with {@link lengthSeparator}. English defaults; translate them alongside
10687
+ * {@link requiredMessage}.
9356
10688
  */
9357
- incompleteMessage = input('Numéro incomplet — {lengths} chiffres attendus', ...(ngDevMode ? [{ debugName: "incompleteMessage" }] : []));
9358
- invalidMessage = input('Numéro invalide — {lengths} chiffres attendus', ...(ngDevMode ? [{ debugName: "invalidMessage" }] : []));
9359
- /** Joins the accepted lengths when a country allows more than one (e.g. "8 ou 9"). */
9360
- lengthSeparator = input(' ou ', ...(ngDevMode ? [{ debugName: "lengthSeparator" }] : []));
10689
+ incompleteMessage = input('Incomplete number — {lengths} digits expected', ...(ngDevMode ? [{ debugName: "incompleteMessage" }] : []));
10690
+ invalidMessage = input('Invalid number — {lengths} digits expected', ...(ngDevMode ? [{ debugName: "invalidMessage" }] : []));
10691
+ /** Joins the accepted lengths when a country allows more than one (e.g. "8 or 9"). */
10692
+ lengthSeparator = input(' or ', ...(ngDevMode ? [{ debugName: "lengthSeparator" }] : []));
9361
10693
  /**
9362
10694
  * Auto-generated example hint (see {@link showExampleHint}). `{number}` is replaced with the
9363
10695
  * formatted international example number.
9364
10696
  */
9365
- exampleHintTemplate = input('Ex. {number}', ...(ngDevMode ? [{ debugName: "exampleHintTemplate" }] : []));
10697
+ exampleHintTemplate = input('e.g. {number}', ...(ngDevMode ? [{ debugName: "exampleHintTemplate" }] : []));
9366
10698
  /** Emitted with the parsed parts whenever the number or country changes. */
9367
10699
  phoneChange = output();
10700
+ /** `Intl.DisplayNames` for {@link locale}, or null (built-in English names). */
10701
+ regionNames = computed(() => {
10702
+ const locale = this.locale();
10703
+ if (!locale || typeof Intl === 'undefined' || !('DisplayNames' in Intl))
10704
+ return null;
10705
+ try {
10706
+ return new Intl.DisplayNames([locale], { type: 'region' });
10707
+ }
10708
+ catch {
10709
+ return null;
10710
+ }
10711
+ }, ...(ngDevMode ? [{ debugName: "regionNames" }] : []));
10712
+ /** The country's display name in {@link locale}, falling back to the built-in English name. */
10713
+ countryName(c) {
10714
+ try {
10715
+ return this.regionNames()?.of(c.iso2) ?? c.name;
10716
+ }
10717
+ catch {
10718
+ return c.name;
10719
+ }
10720
+ }
9368
10721
  national = signal('', ...(ngDevMode ? [{ debugName: "national" }] : []));
9369
10722
  countryIso = signal('', ...(ngDevMode ? [{ debugName: "countryIso" }] : []));
9370
10723
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
@@ -9403,7 +10756,8 @@ class SdsPhoneInput {
9403
10756
  const all = this.availableCountries();
9404
10757
  if (!f)
9405
10758
  return all;
9406
- return all.filter((c) => c.name.toLowerCase().includes(f) ||
10759
+ return all.filter((c) => this.countryName(c).toLowerCase().includes(f) ||
10760
+ c.name.toLowerCase().includes(f) ||
9407
10761
  c.iso2.toLowerCase().includes(f) ||
9408
10762
  c.dialCode.includes(f.replace('+', '')));
9409
10763
  }, ...(ngDevMode ? [{ debugName: "filteredCountries" }] : []));
@@ -9416,7 +10770,7 @@ class SdsPhoneInput {
9416
10770
  const number = `+${c.dialCode} ${sdsFormatNational(c.example, c.format)}`;
9417
10771
  return this.exampleHintTemplate().replace('{number}', number);
9418
10772
  }, ...(ngDevMode ? [{ debugName: "autoHint" }] : []));
9419
- countryAriaLabel = computed(() => this.countryLabel().replace('{country}', this.country().name), ...(ngDevMode ? [{ debugName: "countryAriaLabel" }] : []));
10773
+ countryAriaLabel = computed(() => this.countryLabel().replace('{country}', this.countryName(this.country())), ...(ngDevMode ? [{ debugName: "countryAriaLabel" }] : []));
9420
10774
  e164 = computed(() => {
9421
10775
  const d = this.national();
9422
10776
  return d ? `+${this.country().dialCode}${d}` : '';
@@ -9732,7 +11086,7 @@ class SdsPhoneInput {
9732
11086
  this.onValidatorChange = fn;
9733
11087
  }
9734
11088
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsPhoneInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
9735
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsPhoneInput, isStandalone: true, selector: "sds-phone-input", inputs: { label: { classPropertyName: "label", publicName: "label", 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 }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, defaultCountry: { classPropertyName: "defaultCountry", publicName: "defaultCountry", isSignal: true, isRequired: false, transformFunction: null }, preferredCountries: { classPropertyName: "preferredCountries", publicName: "preferredCountries", isSignal: true, isRequired: false, transformFunction: null }, onlyCountries: { classPropertyName: "onlyCountries", publicName: "onlyCountries", isSignal: true, isRequired: false, transformFunction: null }, showExampleHint: { classPropertyName: "showExampleHint", publicName: "showExampleHint", isSignal: true, isRequired: false, transformFunction: null }, requiredMessage: { classPropertyName: "requiredMessage", publicName: "requiredMessage", isSignal: true, isRequired: false, transformFunction: null }, countryLabel: { classPropertyName: "countryLabel", publicName: "countryLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, noResultsLabel: { classPropertyName: "noResultsLabel", publicName: "noResultsLabel", isSignal: true, isRequired: false, transformFunction: null }, incompleteMessage: { classPropertyName: "incompleteMessage", publicName: "incompleteMessage", isSignal: true, isRequired: false, transformFunction: null }, invalidMessage: { classPropertyName: "invalidMessage", publicName: "invalidMessage", isSignal: true, isRequired: false, transformFunction: null }, lengthSeparator: { classPropertyName: "lengthSeparator", publicName: "lengthSeparator", isSignal: true, isRequired: false, transformFunction: null }, exampleHintTemplate: { classPropertyName: "exampleHintTemplate", publicName: "exampleHintTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { phoneChange: "phoneChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block" }, providers: [
11089
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsPhoneInput, isStandalone: true, selector: "sds-phone-input", inputs: { label: { classPropertyName: "label", publicName: "label", 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 }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, defaultCountry: { classPropertyName: "defaultCountry", publicName: "defaultCountry", isSignal: true, isRequired: false, transformFunction: null }, preferredCountries: { classPropertyName: "preferredCountries", publicName: "preferredCountries", isSignal: true, isRequired: false, transformFunction: null }, onlyCountries: { classPropertyName: "onlyCountries", publicName: "onlyCountries", isSignal: true, isRequired: false, transformFunction: null }, showExampleHint: { classPropertyName: "showExampleHint", publicName: "showExampleHint", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, requiredMessage: { classPropertyName: "requiredMessage", publicName: "requiredMessage", isSignal: true, isRequired: false, transformFunction: null }, countryLabel: { classPropertyName: "countryLabel", publicName: "countryLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, noResultsLabel: { classPropertyName: "noResultsLabel", publicName: "noResultsLabel", isSignal: true, isRequired: false, transformFunction: null }, incompleteMessage: { classPropertyName: "incompleteMessage", publicName: "incompleteMessage", isSignal: true, isRequired: false, transformFunction: null }, invalidMessage: { classPropertyName: "invalidMessage", publicName: "invalidMessage", isSignal: true, isRequired: false, transformFunction: null }, lengthSeparator: { classPropertyName: "lengthSeparator", publicName: "lengthSeparator", isSignal: true, isRequired: false, transformFunction: null }, exampleHintTemplate: { classPropertyName: "exampleHintTemplate", publicName: "exampleHintTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { phoneChange: "phoneChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block" }, providers: [
9736
11090
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SdsPhoneInput), multi: true },
9737
11091
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => SdsPhoneInput), multi: true },
9738
11092
  ], viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, isSignal: true }, { propertyName: "field", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: `
@@ -9841,7 +11195,7 @@ class SdsPhoneInput {
9841
11195
  <span class="grid h-5 min-w-7 place-items-center rounded bg-muted px-1 text-[11px] font-semibold tracking-wide text-ink-soft">
9842
11196
  {{ c.iso2 }}
9843
11197
  </span>
9844
- <span class="flex-1 truncate">{{ c.name }}</span>
11198
+ <span class="flex-1 truncate">{{ countryName(c) }}</span>
9845
11199
  <span class="text-ink-faint">+{{ c.dialCode }}</span>
9846
11200
  </li>
9847
11201
  } @empty {
@@ -9982,7 +11336,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9982
11336
  <span class="grid h-5 min-w-7 place-items-center rounded bg-muted px-1 text-[11px] font-semibold tracking-wide text-ink-soft">
9983
11337
  {{ c.iso2 }}
9984
11338
  </span>
9985
- <span class="flex-1 truncate">{{ c.name }}</span>
11339
+ <span class="flex-1 truncate">{{ countryName(c) }}</span>
9986
11340
  <span class="text-ink-faint">+{{ c.dialCode }}</span>
9987
11341
  </li>
9988
11342
  } @empty {
@@ -10003,7 +11357,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10003
11357
  }
10004
11358
  `,
10005
11359
  }]
10006
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", 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 }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], defaultCountry: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCountry", required: false }] }], preferredCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "preferredCountries", required: false }] }], onlyCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "onlyCountries", required: false }] }], showExampleHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExampleHint", required: false }] }], requiredMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredMessage", required: false }] }], countryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "countryLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], searchLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchLabel", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], noResultsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsLabel", required: false }] }], incompleteMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "incompleteMessage", required: false }] }], invalidMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidMessage", required: false }] }], lengthSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "lengthSeparator", required: false }] }], exampleHintTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "exampleHintTemplate", required: false }] }], phoneChange: [{ type: i0.Output, args: ["phoneChange"] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { isSignal: true }] }], field: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
11360
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", 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 }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], defaultCountry: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCountry", required: false }] }], preferredCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "preferredCountries", required: false }] }], onlyCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "onlyCountries", required: false }] }], showExampleHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExampleHint", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], requiredMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredMessage", required: false }] }], countryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "countryLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], searchLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchLabel", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], noResultsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsLabel", required: false }] }], incompleteMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "incompleteMessage", required: false }] }], invalidMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidMessage", required: false }] }], lengthSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "lengthSeparator", required: false }] }], exampleHintTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "exampleHintTemplate", required: false }] }], phoneChange: [{ type: i0.Output, args: ["phoneChange"] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { isSignal: true }] }], field: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
10007
11361
 
10008
11362
  const SIZES$1 = {
10009
11363
  sm: 'h-8 min-w-8 text-xs',
@@ -10279,7 +11633,7 @@ class SdsPaginator {
10279
11633
  </label>
10280
11634
  }
10281
11635
  </div>
10282
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }, { kind: "component", type: SdsSelect, selector: "sds-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "placeholder", "label", "ariaLabel", "hint", "error", "size", "appearance", "disabled", "clearable", "filter", "filterPlaceholder", "emptyMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11636
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }, { kind: "component", type: SdsSelect, selector: "sds-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "placeholder", "label", "ariaLabel", "hint", "error", "size", "appearance", "disabled", "clearable", "filter", "filterPlaceholder", "emptyMessage", "clearLabel", "filterLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10283
11637
  }
10284
11638
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsPaginator, decorators: [{
10285
11639
  type: Component,
@@ -10405,8 +11759,13 @@ class SdsTabsStore {
10405
11759
  id = `sds-tabs-${nextId$3++}`;
10406
11760
  /** Whether the tab list scrolls horizontally (mirrors [scrollable]). */
10407
11761
  scrollable = signal(false, ...(ngDevMode ? [{ debugName: "scrollable" }] : []));
11762
+ /** Controlled mode (mirrors [controlled]): a click only REQUESTS a tab, the host sets [value]. */
11763
+ controlled = signal(false, ...(ngDevMode ? [{ debugName: "controlled" }] : []));
11764
+ /** Called with every tab the user picks (wired to the host's (tabSelect) output). */
11765
+ onSelect = () => { };
10408
11766
  value;
10409
11767
  registered = signal([], ...(ngDevMode ? [{ debugName: "registered" }] : []));
11768
+ panels = signal([], ...(ngDevMode ? [{ debugName: "panels" }] : []));
10410
11769
  /** Called once by <sds-tabs> to share its two-way `value` signal. */
10411
11770
  connect(value) {
10412
11771
  this.value = value;
@@ -10417,8 +11776,11 @@ class SdsTabsStore {
10417
11776
  isActive(value) {
10418
11777
  return this.value() === value;
10419
11778
  }
11779
+ /** A user pick. Uncontrolled: becomes the active tab. Controlled: only reported to the host. */
10420
11780
  select(value) {
10421
- this.value.set(value);
11781
+ this.onSelect(value);
11782
+ if (!this.controlled())
11783
+ this.value.set(value);
10422
11784
  }
10423
11785
  registerTab(value) {
10424
11786
  this.registered.update((list) => (list.includes(value) ? list : [...list, value]));
@@ -10429,6 +11791,16 @@ class SdsTabsStore {
10429
11791
  unregisterTab(value) {
10430
11792
  this.registered.update((list) => list.filter((v) => v !== value));
10431
11793
  }
11794
+ registerPanel(value) {
11795
+ this.panels.update((list) => (list.includes(value) ? list : [...list, value]));
11796
+ }
11797
+ unregisterPanel(value) {
11798
+ this.panels.update((list) => list.filter((v) => v !== value));
11799
+ }
11800
+ /** Whether an <sds-tab-panel> exists for this value (tabs whose content lives elsewhere have none). */
11801
+ hasPanel(value) {
11802
+ return this.panels().includes(value);
11803
+ }
10432
11804
  tabId(value) {
10433
11805
  return `${this.id}-tab-${value}`;
10434
11806
  }
@@ -10448,10 +11820,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10448
11820
  * <sds-tab-panel> [value]; it is two-way bindable via [(value)]. When omitted,
10449
11821
  * the first registered tab is selected.
10450
11822
  *
11823
+ * **Controlled tabs** (`controlled`): a click, arrow key or Home/End only emits `(tabSelect)`; the active
11824
+ * tab changes when the host changes `[value]`. Use it when the active tab lives somewhere else — a
11825
+ * `?tab=` query param — or when switching can be refused (an unsaved-changes guard): the tab never
11826
+ * shows as selected before the host agrees. `(tabSelect)` also fires in the default mode.
11827
+ *
11828
+ * **Panels are optional.** A tab set whose content is rendered by the host (an `@switch` on the value, a
11829
+ * router outlet) can omit <sds-tab-panels>; a tab then carries no `aria-controls` to a missing panel.
11830
+ *
10451
11831
  * @example
10452
11832
  * <sds-tabs [(value)]="active">
10453
11833
  * <sds-tab-list>
10454
- * <sds-tab value="0">Détails</sds-tab>
11834
+ * <sds-tab value="0">Details</sds-tab>
10455
11835
  * <sds-tab value="1">Historique</sds-tab>
10456
11836
  * </sds-tab-list>
10457
11837
  * <sds-tab-panels>
@@ -10459,6 +11839,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10459
11839
  * <sds-tab-panel value="1">…</sds-tab-panel>
10460
11840
  * </sds-tab-panels>
10461
11841
  * </sds-tabs>
11842
+ *
11843
+ * @example
11844
+ * <!-- driven by a query param, content rendered by the host -->
11845
+ * <sds-tabs controlled [value]="activeTab()" (tabSelect)="selectTab($event)">
11846
+ * <sds-tab-list>
11847
+ * @for (tab of tabs; track tab) {
11848
+ * <sds-tab [value]="tab">{{ 'menu.tabs.' + tab | translate }}</sds-tab>
11849
+ * }
11850
+ * </sds-tab-list>
11851
+ * </sds-tabs>
10462
11852
  */
10463
11853
  class SdsTabs {
10464
11854
  store = inject(SdsTabsStore);
@@ -10466,12 +11856,21 @@ class SdsTabs {
10466
11856
  value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
10467
11857
  /** Let the tab list scroll horizontally instead of overflowing. */
10468
11858
  scrollable = input(false, ...(ngDevMode ? [{ debugName: "scrollable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11859
+ /**
11860
+ * Controlled mode: a user pick only emits (tabSelect); the host decides by setting [value]. See the
11861
+ * class note.
11862
+ */
11863
+ controlled = input(false, ...(ngDevMode ? [{ debugName: "controlled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11864
+ /** Emits the value of the tab the user picked (in both modes). */
11865
+ tabSelect = output();
10469
11866
  constructor() {
10470
11867
  this.store.connect(this.value);
11868
+ this.store.onSelect = (value) => this.tabSelect.emit(value);
10471
11869
  effect(() => this.store.scrollable.set(this.scrollable()));
11870
+ effect(() => this.store.controlled.set(this.controlled()));
10472
11871
  }
10473
11872
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
10474
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: SdsTabs, isStandalone: true, selector: "sds-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "block" }, providers: [SdsTabsStore], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
11873
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: SdsTabs, isStandalone: true, selector: "sds-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, controlled: { classPropertyName: "controlled", publicName: "controlled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", tabSelect: "tabSelect" }, host: { classAttribute: "block" }, providers: [SdsTabsStore], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
10475
11874
  }
10476
11875
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabs, decorators: [{
10477
11876
  type: Component,
@@ -10482,7 +11881,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10482
11881
  host: { class: 'block' },
10483
11882
  template: `<ng-content />`,
10484
11883
  }]
10485
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }] } });
11884
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }], controlled: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlled", required: false }] }], tabSelect: [{ type: i0.Output, args: ["tabSelect"] }] } });
10486
11885
 
10487
11886
  /**
10488
11887
  * Container (role="tablist") for the <sds-tab> headers. Renders the bottom rule
@@ -10579,7 +11978,7 @@ class SdsTab {
10579
11978
  role="tab"
10580
11979
  [id]="store.tabId(value())"
10581
11980
  [attr.aria-selected]="active()"
10582
- [attr.aria-controls]="store.panelId(value())"
11981
+ [attr.aria-controls]="store.hasPanel(value()) ? store.panelId(value()) : null"
10583
11982
  [attr.aria-disabled]="disabled() ? 'true' : null"
10584
11983
  [attr.tabindex]="active() && !disabled() ? 0 : -1"
10585
11984
  [disabled]="disabled()"
@@ -10602,7 +12001,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10602
12001
  role="tab"
10603
12002
  [id]="store.tabId(value())"
10604
12003
  [attr.aria-selected]="active()"
10605
- [attr.aria-controls]="store.panelId(value())"
12004
+ [attr.aria-controls]="store.hasPanel(value()) ? store.panelId(value()) : null"
10606
12005
  [attr.aria-disabled]="disabled() ? 'true' : null"
10607
12006
  [attr.tabindex]="active() && !disabled() ? 0 : -1"
10608
12007
  [disabled]="disabled()"
@@ -10647,6 +12046,12 @@ class SdsTabPanel {
10647
12046
  store = inject(SdsTabsStore);
10648
12047
  value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
10649
12048
  active = computed(() => this.store.isActive(this.value()), ...(ngDevMode ? [{ debugName: "active" }] : []));
12049
+ ngOnInit() {
12050
+ this.store.registerPanel(this.value());
12051
+ }
12052
+ ngOnDestroy() {
12053
+ this.store.unregisterPanel(this.value());
12054
+ }
10650
12055
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
10651
12056
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTabPanel, isStandalone: true, selector: "sds-tab-panel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
10652
12057
  @if (active()) {
@@ -10968,8 +12373,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10968
12373
  *
10969
12374
  * @example
10970
12375
  * <sds-table [value]="rows" [columns]="cols">
10971
- * <ng-template sdsCell="statut" let-value let-row="row">
10972
- * <sds-badge [variant]="row.actif ? 'success' : 'neutral'">{{ value }}</sds-badge>
12376
+ * <ng-template sdsCell="status" let-value let-row="row">
12377
+ * <sds-badge [variant]="row.active ? 'success' : 'neutral'">{{ value }}</sds-badge>
10973
12378
  * </ng-template>
10974
12379
  * </sds-table>
10975
12380
  */
@@ -10993,6 +12398,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10993
12398
  }]
10994
12399
  }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "sdsCell", required: true }] }] } });
10995
12400
 
12401
+ /**
12402
+ * Supplies a custom template for a column's header cell, matched by its field key. The template
12403
+ * replaces the header's whole content — label and sort button — so it may hold its own controls
12404
+ * (a column with a header template is not sortable from the header). Typical use: a "select all"
12405
+ * checkbox above a checkbox column rendered with `sdsCell`.
12406
+ *
12407
+ * @example
12408
+ * <sds-table [value]="rows" [columns]="cols" dataKey="id">
12409
+ * <ng-template sdsHeader="select" let-rows="rows">
12410
+ * <sds-checkbox ariaLabel="Select all rows" [ngModel]="allSelected(rows)"
12411
+ * [indeterminate]="someSelected(rows)" (ngModelChange)="selectAll(rows, $event)" />
12412
+ * </ng-template>
12413
+ * <ng-template sdsCell="select" let-row="row">
12414
+ * <sds-checkbox [ariaLabel]="'Select ' + row.name" [ngModel]="isSelected(row)"
12415
+ * (ngModelChange)="toggle(row, $event)" />
12416
+ * </ng-template>
12417
+ * </sds-table>
12418
+ */
12419
+ class SdsTableHeader {
12420
+ /** The column `field` whose header this template renders. */
12421
+ column = input.required(...(ngDevMode ? [{ debugName: "column", alias: 'sdsHeader' }] : [{ alias: 'sdsHeader' }]));
12422
+ /** The wrapped template, rendered by the host table via ngTemplateOutlet. */
12423
+ template = inject(TemplateRef);
12424
+ /** Lets the compiler type-check `let-*` bindings against SdsTableHeaderContext. */
12425
+ static ngTemplateContextGuard(_dir, _ctx) {
12426
+ return true;
12427
+ }
12428
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTableHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
12429
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.25", type: SdsTableHeader, isStandalone: true, selector: "ng-template[sdsHeader]", inputs: { column: { classPropertyName: "column", publicName: "sdsHeader", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
12430
+ }
12431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTableHeader, decorators: [{
12432
+ type: Directive,
12433
+ args: [{
12434
+ selector: 'ng-template[sdsHeader]',
12435
+ standalone: true,
12436
+ }]
12437
+ }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "sdsHeader", required: true }] }] } });
12438
+
10996
12439
  /**
10997
12440
  * Supplies the detail template rendered beneath an expanded row when the host
10998
12441
  * `<sds-table>` has `expandable`. Use as a structural template inside the table:
@@ -11000,7 +12443,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
11000
12443
  * @example
11001
12444
  * <sds-table [value]="rows" [columns]="cols" expandable>
11002
12445
  * <ng-template sdsRowExpansion let-row>
11003
- * <div>Détails de {{ row.nom }}</div>
12446
+ * <div>Details of {{ row.name }}</div>
11004
12447
  * </ng-template>
11005
12448
  * </sds-table>
11006
12449
  */
@@ -11022,6 +12465,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
11022
12465
  }]
11023
12466
  }] });
11024
12467
 
12468
+ /**
12469
+ * Replaces the table's plain `emptyMessage` with rich content — an explanation and a call to action,
12470
+ * a "no results for …" line. Rendered in the full-width empty row, only when not `loading`.
12471
+ *
12472
+ * @example
12473
+ * <sds-table [value]="categories()" [columns]="cols">
12474
+ * <ng-template sdsTableEmpty>
12475
+ * <p>No categories yet.</p>
12476
+ * <sds-button size="sm" (click)="create()">Add a category</sds-button>
12477
+ * </ng-template>
12478
+ * </sds-table>
12479
+ */
12480
+ class SdsTableEmpty {
12481
+ /** The wrapped template, rendered by the host table via ngTemplateOutlet. */
12482
+ template = inject(TemplateRef);
12483
+ /** Lets the compiler type-check `let-*` bindings against SdsTableEmptyContext. */
12484
+ static ngTemplateContextGuard(_dir, _ctx) {
12485
+ return true;
12486
+ }
12487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTableEmpty, deps: [], target: i0.ɵɵFactoryTarget.Directive });
12488
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.25", type: SdsTableEmpty, isStandalone: true, selector: "ng-template[sdsTableEmpty]", ngImport: i0 });
12489
+ }
12490
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTableEmpty, decorators: [{
12491
+ type: Directive,
12492
+ args: [{
12493
+ selector: 'ng-template[sdsTableEmpty]',
12494
+ standalone: true,
12495
+ }]
12496
+ }] });
12497
+
11025
12498
  const SIZES = {
11026
12499
  sm: { cell: 'px-3 py-2 text-sm', head: 'px-3 py-2' },
11027
12500
  md: { cell: 'px-4 py-2.5 text-sm', head: 'px-4 py-2.5' },
@@ -11050,6 +12523,16 @@ let nextId$1 = 0;
11050
12523
  * across all pages**, not just the current page. Toggling it selects/clears
11051
12524
  * every filtered row regardless of pagination (this is an intentional
11052
12525
  * deviation from PrimeNG's per-page select-all).
12526
+ * - **Custom headers:** `<ng-template sdsHeader="field">` replaces a column's header content (label and
12527
+ * sort button) — e.g. a "select all" checkbox above your own checkbox column. Its context carries the
12528
+ * filtered `rows` (all pages) and the displayed `pageRows` (see {@link SdsTableHeaderContext}).
12529
+ * - **Clickable rows:** `rowClickable` makes each row an action (pointer, tab stop, Enter/Space) that
12530
+ * emits `(rowClick)`; clicks on buttons, links and fields inside a cell are not row clicks, so no
12531
+ * `stopPropagation()` is needed. `[rowClass]` adds per-row classes (e.g. dim a blocked row).
12532
+ * - **Empty state:** `<ng-template sdsTableEmpty let-query>` replaces the plain `emptyMessage` (e.g. a
12533
+ * sentence and a "create" button); it is not shown while `loading`.
12534
+ * - **Labels:** every visible and spoken string is an input with an English default (paginator,
12535
+ * filter, selection, expansion, editor). An app serving more than one language passes translations.
11053
12536
  *
11054
12537
  * **Editable grid (opt-in).** Set `editable` and give columns an `editor` (`text` · `number` ·
11055
12538
  * `currency` · `select`). The table becomes an ARIA grid with one tab stop: arrow keys move between
@@ -11059,14 +12542,16 @@ let nextId$1 = 0;
11059
12542
  * commits. Each changed commit emits `(cellCommit)` — the host applies it to its data (the table never
11060
12543
  * mutates rows) — and marks the cell dirty until {@link clearDirty}. Errors from `[cellErrors]` show
11061
12544
  * under the cell with `aria-invalid`. Number/currency cells use sds-input-number (live, locale-aware
11062
- * formatting: "11,9" → "€11.90"). Everything else — sort, paging, selection — is unchanged.
12545
+ * formatting: "11,9" → "€11.90"; in a comma locale such as fr-BE or nl-BE a typed "." is taken as the
12546
+ * decimal separator, so "12.50" → "12,50 €"). The `select` editor is a styled native select with the
12547
+ * column header as its name. Everything else — sort, paging, selection — is unchanged.
11063
12548
  *
11064
12549
  * @example
11065
12550
  * <sds-table [value]="staff" [columns]="cols" dataKey="id"
11066
12551
  * sortable paginator [rows]="10" globalFilter
11067
12552
  * selectionMode="multiple" [(selection)]="selected">
11068
- * <ng-template sdsCell="statut" let-value let-row="row">
11069
- * <sds-badge [variant]="row.actif ? 'success' : 'neutral'">{{ value }}</sds-badge>
12553
+ * <ng-template sdsCell="status" let-value let-row="row">
12554
+ * <sds-badge [variant]="row.active ? 'success' : 'neutral'">{{ value }}</sds-badge>
11070
12555
  * </ng-template>
11071
12556
  * </sds-table>
11072
12557
  *
@@ -11117,6 +12602,14 @@ class SdsTable {
11117
12602
  selection = model(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
11118
12603
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11119
12604
  emptyMessage = input('No data', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
12605
+ /**
12606
+ * Rows are actions: a pointer cursor, a tab stop per row, and Enter/Space emit `(rowClick)` like a
12607
+ * click. Clicks that land on a control inside a cell (button, link, input, checkbox…) do not count as
12608
+ * a row click, so a row can open a drawer while its cells keep their own buttons.
12609
+ */
12610
+ rowClickable = input(false, ...(ngDevMode ? [{ debugName: "rowClickable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12611
+ /** Extra classes per body row, e.g. `(row) => row.blocked ? 'opacity-60' : ''`. */
12612
+ rowClass = input(null, ...(ngDevMode ? [{ debugName: "rowClass" }] : []));
11120
12613
  caption = input('', ...(ngDevMode ? [{ debugName: "caption" }] : []));
11121
12614
  /** Fallback accessible name for the `<table>` when no `caption` is set. */
11122
12615
  ariaLabel = input('Data table', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -11142,6 +12635,20 @@ class SdsTable {
11142
12635
  locale = input('', ...(ngDevMode ? [{ debugName: "locale" }] : []));
11143
12636
  /** Spoken marker of an edited (dirty) cell. */
11144
12637
  dirtyLabel = input('edited', ...(ngDevMode ? [{ debugName: "dirtyLabel" }] : []));
12638
+ /** Empty choice of a `select` editor while the cell has no value (a column's `placeholder` wins). */
12639
+ selectPlaceholder = input('Select…', ...(ngDevMode ? [{ debugName: "selectPlaceholder" }] : []));
12640
+ /**
12641
+ * Selection and expansion labels (English defaults; translate them in a multi-language app). The
12642
+ * checkbox/radio and expander buttons are icon-only, so these strings are their only names.
12643
+ */
12644
+ selectAllLabel = input('Select all rows', ...(ngDevMode ? [{ debugName: "selectAllLabel" }] : []));
12645
+ selectRowLabel = input('Select row', ...(ngDevMode ? [{ debugName: "selectRowLabel" }] : []));
12646
+ /** Screen-reader name of the selection column's header in `single` mode. */
12647
+ selectionColumnLabel = input('Selection', ...(ngDevMode ? [{ debugName: "selectionColumnLabel" }] : []));
12648
+ /** Screen-reader name of the expander column's header (see {@link expandable}). */
12649
+ expandColumnLabel = input('Details', ...(ngDevMode ? [{ debugName: "expandColumnLabel" }] : []));
12650
+ expandRowLabel = input('Expand row', ...(ngDevMode ? [{ debugName: "expandRowLabel" }] : []));
12651
+ collapseRowLabel = input('Collapse row', ...(ngDevMode ? [{ debugName: "collapseRowLabel" }] : []));
11145
12652
  // --- outputs --------------------------------------------------------------
11146
12653
  sortChange = output();
11147
12654
  pageChange = output();
@@ -11168,11 +12675,36 @@ class SdsTable {
11168
12675
  templateFor(field) {
11169
12676
  return this.templateMap().get(field) ?? null;
11170
12677
  }
12678
+ // --- custom header templates ----------------------------------------------
12679
+ headerTemplates = contentChildren(SdsTableHeader, ...(ngDevMode ? [{ debugName: "headerTemplates" }] : []));
12680
+ headerTemplateMap = computed(() => {
12681
+ const map = new Map();
12682
+ for (const h of this.headerTemplates())
12683
+ map.set(h.column(), h.template);
12684
+ return map;
12685
+ }, ...(ngDevMode ? [{ debugName: "headerTemplateMap" }] : []));
12686
+ headerTemplateFor(field) {
12687
+ return this.headerTemplateMap().get(field) ?? null;
12688
+ }
12689
+ headerContext(column, index) {
12690
+ return {
12691
+ $implicit: column,
12692
+ column,
12693
+ index,
12694
+ rows: this.filtered(),
12695
+ pageRows: this.displayRows(),
12696
+ };
12697
+ }
11171
12698
  // --- row-expansion template -----------------------------------------------
11172
12699
  rowExpansion = contentChild(SdsTableRowExpansion, ...(ngDevMode ? [{ debugName: "rowExpansion" }] : []));
11173
12700
  expansionTemplate() {
11174
12701
  return this.rowExpansion()?.template ?? null;
11175
12702
  }
12703
+ // --- empty-state template -------------------------------------------------
12704
+ emptyTpl = contentChild(SdsTableEmpty, ...(ngDevMode ? [{ debugName: "emptyTpl" }] : []));
12705
+ emptyTemplate() {
12706
+ return this.emptyTpl()?.template ?? null;
12707
+ }
11176
12708
  // --- pipeline state -------------------------------------------------------
11177
12709
  filterText = signal('', ...(ngDevMode ? [{ debugName: "filterText" }] : []));
11178
12710
  sortField = signal(null, ...(ngDevMode ? [{ debugName: "sortField" }] : []));
@@ -11293,7 +12825,8 @@ class SdsTable {
11293
12825
  totalColumnCount = computed(() => this.effectiveColumns().length + this.leadingColumnCount(), ...(ngDevMode ? [{ debugName: "totalColumnCount" }] : []));
11294
12826
  // --- sorting UI -----------------------------------------------------------
11295
12827
  isColumnSortable(col) {
11296
- return this.sortable() && !!col.sortable;
12828
+ // A custom header template owns the header content, so it has no sort button.
12829
+ return this.sortable() && !!col.sortable && !this.headerTemplateFor(col.field);
11297
12830
  }
11298
12831
  sortIcon(col) {
11299
12832
  if (this.sortField() !== col.field || this.sortOrder() === 0) {
@@ -11407,9 +12940,14 @@ class SdsTable {
11407
12940
  this.toggleRowSelection(row);
11408
12941
  }
11409
12942
  }
11410
- /** Enter/Space on a (single-mode) focusable row toggles its selection. */
12943
+ /** Enter/Space on a focusable row (single selection or `rowClickable`) acts like a click on it. */
11411
12944
  onRowKeydown(event, row) {
11412
- if (this.selectionMode() !== 'single' || this.loading() || this.editable())
12945
+ if (this.loading() || this.editable())
12946
+ return;
12947
+ if (this.selectionMode() !== 'single' && !this.rowClickable())
12948
+ return;
12949
+ // Only the row itself: Enter on a button inside a cell belongs to that button.
12950
+ if (event.target !== event.currentTarget)
11413
12951
  return;
11414
12952
  event.preventDefault();
11415
12953
  this.onRowClick(row);
@@ -11428,9 +12966,11 @@ class SdsTable {
11428
12966
  this.rowSelect.emit(row);
11429
12967
  }
11430
12968
  }
11431
- onRowClick(row) {
12969
+ onRowClick(row, event) {
11432
12970
  if (this.loading())
11433
12971
  return;
12972
+ if (event && this.fromInnerControl(event))
12973
+ return;
11434
12974
  this.rowClick.emit(row);
11435
12975
  if (this.selectionMode() === 'single') {
11436
12976
  if (this.equals(this.selection(), row)) {
@@ -11443,6 +12983,16 @@ class SdsTable {
11443
12983
  }
11444
12984
  }
11445
12985
  }
12986
+ /** Whether a click started on an interactive element inside the row (not on the row itself). */
12987
+ fromInnerControl(event) {
12988
+ const row = event.currentTarget;
12989
+ const target = event.target;
12990
+ if (!row || !target || !(target instanceof Element))
12991
+ return false;
12992
+ const control = target.closest('button, a[href], input, select, textarea, label, [contenteditable="true"], ' +
12993
+ '[role="button"], [role="checkbox"], [role="switch"], [role="radio"], [role="combobox"], [role="menuitem"]');
12994
+ return !!control && control !== row && row.contains(control);
12995
+ }
11446
12996
  // --- paginator ------------------------------------------------------------
11447
12997
  onPageChange(state) {
11448
12998
  this.editing.set(null);
@@ -11653,6 +13203,13 @@ class SdsTable {
11653
13203
  }
11654
13204
  return String(v);
11655
13205
  }
13206
+ /** Shared frame of the in-cell editors (text, select): height, radius, focus ring, error state. */
13207
+ editorClass(row, col) {
13208
+ const tone = this.cellErrorFor(row, col.field)
13209
+ ? 'border-danger ring-danger/25'
13210
+ : 'border-brand-500 ring-brand-500/25';
13211
+ return `h-9 w-full rounded-control border bg-surface text-sm text-ink outline-none ring-2 ${tone}`;
13212
+ }
11656
13213
  gridCellClass(row, col, r, c) {
11657
13214
  const align = ALIGN[col.align ?? 'left'];
11658
13215
  const editing = this.isEditing(r, c);
@@ -11969,7 +13526,7 @@ class SdsTable {
11969
13526
  const align = ALIGN[col?.align ?? 'left'];
11970
13527
  return `${SIZES[this.size()].cell} ${align} text-ink align-middle`;
11971
13528
  }
11972
- rowClass(row, index) {
13529
+ bodyRowClass(row, index) {
11973
13530
  const parts = ['transition-colors'];
11974
13531
  if (this.gridlines())
11975
13532
  parts.push('divide-x divide-border');
@@ -11981,9 +13538,12 @@ class SdsTable {
11981
13538
  }
11982
13539
  if (this.hoverable() && !this.isRowSelected(row))
11983
13540
  parts.push('hover:bg-muted/60');
11984
- if (this.selectionMode() === 'single') {
13541
+ if (this.selectionMode() === 'single' || (this.rowClickable() && !this.editable())) {
11985
13542
  parts.push('cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500');
11986
13543
  }
13544
+ const extra = this.rowClass()?.(row, index);
13545
+ if (extra)
13546
+ parts.push(extra);
11987
13547
  return parts.join(' ');
11988
13548
  }
11989
13549
  checkboxClass(checked, indeterminate) {
@@ -11992,7 +13552,7 @@ class SdsTable {
11992
13552
  : 'border-border bg-surface';
11993
13553
  }
11994
13554
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
11995
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTable, isStandalone: true, selector: "sds-table", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, striped: { classPropertyName: "striped", publicName: "striped", isSignal: true, isRequired: false, transformFunction: null }, gridlines: { classPropertyName: "gridlines", publicName: "gridlines", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, paginator: { classPropertyName: "paginator", publicName: "paginator", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, showCurrentPageReport: { classPropertyName: "showCurrentPageReport", publicName: "showCurrentPageReport", isSignal: true, isRequired: false, transformFunction: null }, currentPageReportTemplate: { classPropertyName: "currentPageReportTemplate", publicName: "currentPageReportTemplate", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageLabel: { classPropertyName: "rowsPerPageLabel", publicName: "rowsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, paginationLabel: { classPropertyName: "paginationLabel", publicName: "paginationLabel", isSignal: true, isRequired: false, transformFunction: null }, firstPageLabel: { classPropertyName: "firstPageLabel", publicName: "firstPageLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageLabel: { classPropertyName: "previousPageLabel", publicName: "previousPageLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageLabel: { classPropertyName: "nextPageLabel", publicName: "nextPageLabel", isSignal: true, isRequired: false, transformFunction: null }, lastPageLabel: { classPropertyName: "lastPageLabel", publicName: "lastPageLabel", isSignal: true, isRequired: false, transformFunction: null }, pageLinkLabel: { classPropertyName: "pageLinkLabel", publicName: "pageLinkLabel", isSignal: true, isRequired: false, transformFunction: null }, globalFilter: { classPropertyName: "globalFilter", publicName: "globalFilter", isSignal: true, isRequired: false, transformFunction: null }, globalFilterPlaceholder: { classPropertyName: "globalFilterPlaceholder", publicName: "globalFilterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, globalFilterAriaLabel: { classPropertyName: "globalFilterAriaLabel", publicName: "globalFilterAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, globalFilterFields: { classPropertyName: "globalFilterFields", publicName: "globalFilterFields", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, reorderableColumns: { classPropertyName: "reorderableColumns", publicName: "reorderableColumns", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, editOn: { classPropertyName: "editOn", publicName: "editOn", isSignal: true, isRequired: false, transformFunction: null }, cellErrors: { classPropertyName: "cellErrors", publicName: "cellErrors", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dirtyLabel: { classPropertyName: "dirtyLabel", publicName: "dirtyLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", sortChange: "sortChange", pageChange: "pageChange", rowSelect: "rowSelect", rowUnselect: "rowUnselect", lazyLoad: "lazyLoad", rowClick: "rowClick", rowExpand: "rowExpand", rowCollapse: "rowCollapse", colReorder: "colReorder", cellCommit: "cellCommit" }, host: { classAttribute: "block" }, queries: [{ propertyName: "cellTemplates", predicate: SdsTableCell, isSignal: true }, { propertyName: "rowExpansion", first: true, predicate: SdsTableRowExpansion, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "numberEditor", first: true, predicate: SdsInputNumber, descendants: true, isSignal: true }], ngImport: i0, template: `
13555
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTable, isStandalone: true, selector: "sds-table", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, striped: { classPropertyName: "striped", publicName: "striped", isSignal: true, isRequired: false, transformFunction: null }, gridlines: { classPropertyName: "gridlines", publicName: "gridlines", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, paginator: { classPropertyName: "paginator", publicName: "paginator", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, showCurrentPageReport: { classPropertyName: "showCurrentPageReport", publicName: "showCurrentPageReport", isSignal: true, isRequired: false, transformFunction: null }, currentPageReportTemplate: { classPropertyName: "currentPageReportTemplate", publicName: "currentPageReportTemplate", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageLabel: { classPropertyName: "rowsPerPageLabel", publicName: "rowsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, paginationLabel: { classPropertyName: "paginationLabel", publicName: "paginationLabel", isSignal: true, isRequired: false, transformFunction: null }, firstPageLabel: { classPropertyName: "firstPageLabel", publicName: "firstPageLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageLabel: { classPropertyName: "previousPageLabel", publicName: "previousPageLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageLabel: { classPropertyName: "nextPageLabel", publicName: "nextPageLabel", isSignal: true, isRequired: false, transformFunction: null }, lastPageLabel: { classPropertyName: "lastPageLabel", publicName: "lastPageLabel", isSignal: true, isRequired: false, transformFunction: null }, pageLinkLabel: { classPropertyName: "pageLinkLabel", publicName: "pageLinkLabel", isSignal: true, isRequired: false, transformFunction: null }, globalFilter: { classPropertyName: "globalFilter", publicName: "globalFilter", isSignal: true, isRequired: false, transformFunction: null }, globalFilterPlaceholder: { classPropertyName: "globalFilterPlaceholder", publicName: "globalFilterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, globalFilterAriaLabel: { classPropertyName: "globalFilterAriaLabel", publicName: "globalFilterAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, globalFilterFields: { classPropertyName: "globalFilterFields", publicName: "globalFilterFields", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, rowClickable: { classPropertyName: "rowClickable", publicName: "rowClickable", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, reorderableColumns: { classPropertyName: "reorderableColumns", publicName: "reorderableColumns", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, editOn: { classPropertyName: "editOn", publicName: "editOn", isSignal: true, isRequired: false, transformFunction: null }, cellErrors: { classPropertyName: "cellErrors", publicName: "cellErrors", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dirtyLabel: { classPropertyName: "dirtyLabel", publicName: "dirtyLabel", isSignal: true, isRequired: false, transformFunction: null }, selectPlaceholder: { classPropertyName: "selectPlaceholder", publicName: "selectPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, selectRowLabel: { classPropertyName: "selectRowLabel", publicName: "selectRowLabel", isSignal: true, isRequired: false, transformFunction: null }, selectionColumnLabel: { classPropertyName: "selectionColumnLabel", publicName: "selectionColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, expandColumnLabel: { classPropertyName: "expandColumnLabel", publicName: "expandColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, expandRowLabel: { classPropertyName: "expandRowLabel", publicName: "expandRowLabel", isSignal: true, isRequired: false, transformFunction: null }, collapseRowLabel: { classPropertyName: "collapseRowLabel", publicName: "collapseRowLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", sortChange: "sortChange", pageChange: "pageChange", rowSelect: "rowSelect", rowUnselect: "rowUnselect", lazyLoad: "lazyLoad", rowClick: "rowClick", rowExpand: "rowExpand", rowCollapse: "rowCollapse", colReorder: "colReorder", cellCommit: "cellCommit" }, host: { classAttribute: "block" }, queries: [{ propertyName: "cellTemplates", predicate: SdsTableCell, isSignal: true }, { propertyName: "headerTemplates", predicate: SdsTableHeader, isSignal: true }, { propertyName: "rowExpansion", first: true, predicate: SdsTableRowExpansion, descendants: true, isSignal: true }, { propertyName: "emptyTpl", first: true, predicate: SdsTableEmpty, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "numberEditor", first: true, predicate: SdsInputNumber, descendants: true, isSignal: true }], ngImport: i0, template: `
11996
13556
  <div class="rounded-card border border-border bg-surface overflow-hidden">
11997
13557
  <!-- Toolbar: caption + global filter -->
11998
13558
  @if (caption() || globalFilter()) {
@@ -12057,7 +13617,7 @@ class SdsTable {
12057
13617
  [class.z-10]="scrollHeight()"
12058
13618
  [class.bg-muted]="scrollHeight()"
12059
13619
  >
12060
- <span class="sr-only">Détails</span>
13620
+ <span class="sr-only">{{ expandColumnLabel() }}</span>
12061
13621
  </th>
12062
13622
  }
12063
13623
 
@@ -12076,7 +13636,7 @@ class SdsTable {
12076
13636
  type="button"
12077
13637
  role="checkbox"
12078
13638
  [attr.aria-checked]="headerChecked() ? 'true' : (headerIndeterminate() ? 'mixed' : 'false')"
12079
- aria-label="Tout sélectionner"
13639
+ [attr.aria-label]="selectAllLabel()"
12080
13640
  [disabled]="loading()"
12081
13641
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12082
13642
  [class]="checkboxClass(headerChecked(), headerIndeterminate())"
@@ -12089,7 +13649,7 @@ class SdsTable {
12089
13649
  }
12090
13650
  </button>
12091
13651
  } @else {
12092
- <span class="sr-only">Sélection</span>
13652
+ <span class="sr-only">{{ selectionColumnLabel() }}</span>
12093
13653
  }
12094
13654
  </th>
12095
13655
  }
@@ -12112,7 +13672,12 @@ class SdsTable {
12112
13672
  (drop)="onColDrop($event, i)"
12113
13673
  (dragend)="onColDragEnd()"
12114
13674
  >
12115
- @if (isColumnSortable(col)) {
13675
+ @if (headerTemplateFor(col.field); as htpl) {
13676
+ <ng-container
13677
+ [ngTemplateOutlet]="htpl"
13678
+ [ngTemplateOutletContext]="headerContext(col, i)"
13679
+ />
13680
+ } @else if (isColumnSortable(col)) {
12116
13681
  <button
12117
13682
  type="button"
12118
13683
  [disabled]="loading()"
@@ -12141,10 +13706,10 @@ class SdsTable {
12141
13706
  <tbody class="divide-y divide-border">
12142
13707
  @for (row of displayRows(); track trackRow($index, row); let i = $index) {
12143
13708
  <tr
12144
- [class]="rowClass(row, i)"
12145
- [attr.tabindex]="selectionMode() === 'single' && !loading() && !editable() ? 0 : null"
13709
+ [class]="bodyRowClass(row, i)"
13710
+ [attr.tabindex]="(selectionMode() === 'single' || rowClickable()) && !loading() && !editable() ? 0 : null"
12146
13711
  [attr.aria-selected]="selectionMode() === 'single' ? (isRowSelected(row) ? 'true' : 'false') : null"
12147
- (click)="onRowClick(row)"
13712
+ (click)="onRowClick(row, $event)"
12148
13713
  (keydown.enter)="onRowKeydown($event, row)"
12149
13714
  (keydown.space)="onRowKeydown($event, row)"
12150
13715
  >
@@ -12154,7 +13719,7 @@ class SdsTable {
12154
13719
  type="button"
12155
13720
  [attr.aria-expanded]="isExpanded(row)"
12156
13721
  [attr.aria-controls]="isExpanded(row) ? rowDetailId(row) : null"
12157
- [attr.aria-label]="isExpanded(row) ? 'Réduire la ligne' : 'Développer la ligne'"
13722
+ [attr.aria-label]="isExpanded(row) ? collapseRowLabel() : expandRowLabel()"
12158
13723
  [disabled]="loading()"
12159
13724
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded text-ink-soft hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12160
13725
  (click)="onExpanderClick($event, row)"
@@ -12179,7 +13744,7 @@ class SdsTable {
12179
13744
  type="button"
12180
13745
  role="checkbox"
12181
13746
  [attr.aria-checked]="isRowSelected(row) ? 'true' : 'false'"
12182
- aria-label="Sélectionner la ligne"
13747
+ [attr.aria-label]="selectRowLabel()"
12183
13748
  [disabled]="loading()"
12184
13749
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12185
13750
  [class]="checkboxClass(isRowSelected(row), false)"
@@ -12194,7 +13759,7 @@ class SdsTable {
12194
13759
  type="button"
12195
13760
  role="radio"
12196
13761
  [attr.aria-checked]="isRowSelected(row) ? 'true' : 'false'"
12197
- aria-label="Sélectionner la ligne"
13762
+ [attr.aria-label]="selectRowLabel()"
12198
13763
  [disabled]="loading()"
12199
13764
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded-full border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12200
13765
  [class]="checkboxClass(isRowSelected(row), false)"
@@ -12241,20 +13806,31 @@ class SdsTable {
12241
13806
  @if (isEditing(i, ci)) {
12242
13807
  @switch (col.editor) {
12243
13808
  @case ('select') {
12244
- <select
12245
- data-sds-cell-editor
12246
- class="h-9 w-full rounded-control border border-brand-500 bg-surface px-2 text-sm text-ink outline-none ring-2 ring-brand-500/25"
12247
- [attr.aria-label]="col.header"
12248
- (change)="onSelectEditorChange($event, col)"
12249
- (blur)="onEditorBlur()"
12250
- >
12251
- @if (draft() === null || draft() === undefined) {
12252
- <option value="" disabled selected>{{ col.placeholder ?? '' }}</option>
12253
- }
12254
- @for (opt of col.options ?? []; track $index; let oi = $index) {
12255
- <option [value]="oi" [selected]="opt.value === draft()">{{ opt.label }}</option>
12256
- }
12257
- </select>
13809
+ <!-- A native select (dependable inside a grid cell), dressed like the other editors:
13810
+ same height, radius, focus ring and padding, a library chevron, dark-mode popup. -->
13811
+ <div class="relative flex items-center">
13812
+ <select
13813
+ data-sds-cell-editor
13814
+ [class]="editorClass(row, col) + ' cursor-pointer appearance-none pl-2.5 pr-8 dark:scheme-dark'"
13815
+ [attr.aria-label]="col.header"
13816
+ [attr.aria-invalid]="cellErrorFor(row, col.field) ? 'true' : null"
13817
+ [attr.aria-describedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : null"
13818
+ (change)="onSelectEditorChange($event, col)"
13819
+ (blur)="onEditorBlur()"
13820
+ >
13821
+ @if (draft() === null || draft() === undefined) {
13822
+ <option value="" disabled selected>{{ col.placeholder ?? selectPlaceholder() }}</option>
13823
+ }
13824
+ @for (opt of col.options ?? []; track $index; let oi = $index) {
13825
+ <option [value]="oi" [selected]="opt.value === draft()">{{ opt.label }}</option>
13826
+ }
13827
+ </select>
13828
+ <sds-icon
13829
+ name="chevron-down"
13830
+ [size]="16"
13831
+ class="pointer-events-none absolute right-2.5 text-ink-soft"
13832
+ />
13833
+ </div>
12258
13834
  }
12259
13835
  @case ('number') {
12260
13836
  <sds-input-number
@@ -12269,6 +13845,8 @@ class SdsTable {
12269
13845
  [maxFractionDigits]="col.maxFractionDigits ?? null"
12270
13846
  [placeholder]="col.placeholder ?? ''"
12271
13847
  [ariaLabel]="col.header"
13848
+ [invalid]="!!cellErrorFor(row, col.field)"
13849
+ [ariaDescribedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : ''"
12272
13850
  (focusout)="onEditorBlur()"
12273
13851
  />
12274
13852
  }
@@ -12286,6 +13864,8 @@ class SdsTable {
12286
13864
  [maxFractionDigits]="col.maxFractionDigits ?? null"
12287
13865
  [placeholder]="col.placeholder ?? ''"
12288
13866
  [ariaLabel]="col.header"
13867
+ [invalid]="!!cellErrorFor(row, col.field)"
13868
+ [ariaDescribedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : ''"
12289
13869
  (focusout)="onEditorBlur()"
12290
13870
  />
12291
13871
  }
@@ -12293,10 +13873,12 @@ class SdsTable {
12293
13873
  <input
12294
13874
  data-sds-cell-editor
12295
13875
  type="text"
12296
- class="h-9 w-full rounded-control border border-brand-500 bg-surface px-2.5 text-sm text-ink outline-none ring-2 ring-brand-500/25 placeholder:text-ink-faint"
13876
+ [class]="editorClass(row, col) + ' px-2.5 placeholder:text-ink-faint'"
12297
13877
  [value]="draftText()"
12298
13878
  [placeholder]="col.placeholder ?? ''"
12299
13879
  [attr.aria-label]="col.header"
13880
+ [attr.aria-invalid]="cellErrorFor(row, col.field) ? 'true' : null"
13881
+ [attr.aria-describedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : null"
12300
13882
  (input)="onTextEditorInput($event)"
12301
13883
  (blur)="onEditorBlur()"
12302
13884
  />
@@ -12352,7 +13934,14 @@ class SdsTable {
12352
13934
  [attr.colspan]="totalColumnCount()"
12353
13935
  class="py-10 text-center text-ink-faint"
12354
13936
  >
12355
- {{ emptyMessage() }}
13937
+ @if (emptyTemplate(); as tpl) {
13938
+ <ng-container
13939
+ [ngTemplateOutlet]="tpl"
13940
+ [ngTemplateOutletContext]="{ $implicit: filterText(), filterText: filterText() }"
13941
+ />
13942
+ } @else {
13943
+ {{ emptyMessage() }}
13944
+ }
12356
13945
  </td>
12357
13946
  </tr>
12358
13947
  }
@@ -12396,7 +13985,7 @@ class SdsTable {
12396
13985
  </div>
12397
13986
  }
12398
13987
  </div>
12399
- `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }, { kind: "component", type: SdsPaginator, selector: "sds-paginator", inputs: ["totalRecords", "first", "rows", "rowsPerPageOptions", "pageLinkSize", "showFirstLastIcon", "showPageLinks", "showCurrentPageReport", "currentPageReportTemplate", "showJumpToPageInput", "rowsPerPageLabel", "jumpToPageLabel", "paginationLabel", "firstPageLabel", "previousPageLabel", "nextPageLabel", "lastPageLabel", "pageLinkLabel", "jumpToPageAriaLabel", "size", "disabled"], outputs: ["firstChange", "rowsChange", "pageChange"] }, { kind: "component", type: SdsInputNumber, selector: "sds-input-number", inputs: ["label", "ariaLabel", "placeholder", "hint", "error", "size", "appearance", "disabled", "readonly", "mode", "locale", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "min", "max", "step", "showButtons", "buttonLayout", "incrementIcon", "decrementIcon", "allowEmpty", "clearable", "clearLabel", "incrementLabel", "decrementLabel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13988
+ `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }, { kind: "component", type: SdsPaginator, selector: "sds-paginator", inputs: ["totalRecords", "first", "rows", "rowsPerPageOptions", "pageLinkSize", "showFirstLastIcon", "showPageLinks", "showCurrentPageReport", "currentPageReportTemplate", "showJumpToPageInput", "rowsPerPageLabel", "jumpToPageLabel", "paginationLabel", "firstPageLabel", "previousPageLabel", "nextPageLabel", "lastPageLabel", "pageLinkLabel", "jumpToPageAriaLabel", "size", "disabled"], outputs: ["firstChange", "rowsChange", "pageChange"] }, { kind: "component", type: SdsInputNumber, selector: "sds-input-number", inputs: ["label", "ariaLabel", "placeholder", "hint", "error", "invalid", "ariaDescribedby", "size", "appearance", "disabled", "readonly", "mode", "locale", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "min", "max", "step", "showButtons", "buttonLayout", "incrementIcon", "decrementIcon", "allowEmpty", "clearable", "clearLabel", "incrementLabel", "decrementLabel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12400
13989
  }
12401
13990
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTable, decorators: [{
12402
13991
  type: Component,
@@ -12470,7 +14059,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12470
14059
  [class.z-10]="scrollHeight()"
12471
14060
  [class.bg-muted]="scrollHeight()"
12472
14061
  >
12473
- <span class="sr-only">Détails</span>
14062
+ <span class="sr-only">{{ expandColumnLabel() }}</span>
12474
14063
  </th>
12475
14064
  }
12476
14065
 
@@ -12489,7 +14078,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12489
14078
  type="button"
12490
14079
  role="checkbox"
12491
14080
  [attr.aria-checked]="headerChecked() ? 'true' : (headerIndeterminate() ? 'mixed' : 'false')"
12492
- aria-label="Tout sélectionner"
14081
+ [attr.aria-label]="selectAllLabel()"
12493
14082
  [disabled]="loading()"
12494
14083
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12495
14084
  [class]="checkboxClass(headerChecked(), headerIndeterminate())"
@@ -12502,7 +14091,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12502
14091
  }
12503
14092
  </button>
12504
14093
  } @else {
12505
- <span class="sr-only">Sélection</span>
14094
+ <span class="sr-only">{{ selectionColumnLabel() }}</span>
12506
14095
  }
12507
14096
  </th>
12508
14097
  }
@@ -12525,7 +14114,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12525
14114
  (drop)="onColDrop($event, i)"
12526
14115
  (dragend)="onColDragEnd()"
12527
14116
  >
12528
- @if (isColumnSortable(col)) {
14117
+ @if (headerTemplateFor(col.field); as htpl) {
14118
+ <ng-container
14119
+ [ngTemplateOutlet]="htpl"
14120
+ [ngTemplateOutletContext]="headerContext(col, i)"
14121
+ />
14122
+ } @else if (isColumnSortable(col)) {
12529
14123
  <button
12530
14124
  type="button"
12531
14125
  [disabled]="loading()"
@@ -12554,10 +14148,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12554
14148
  <tbody class="divide-y divide-border">
12555
14149
  @for (row of displayRows(); track trackRow($index, row); let i = $index) {
12556
14150
  <tr
12557
- [class]="rowClass(row, i)"
12558
- [attr.tabindex]="selectionMode() === 'single' && !loading() && !editable() ? 0 : null"
14151
+ [class]="bodyRowClass(row, i)"
14152
+ [attr.tabindex]="(selectionMode() === 'single' || rowClickable()) && !loading() && !editable() ? 0 : null"
12559
14153
  [attr.aria-selected]="selectionMode() === 'single' ? (isRowSelected(row) ? 'true' : 'false') : null"
12560
- (click)="onRowClick(row)"
14154
+ (click)="onRowClick(row, $event)"
12561
14155
  (keydown.enter)="onRowKeydown($event, row)"
12562
14156
  (keydown.space)="onRowKeydown($event, row)"
12563
14157
  >
@@ -12567,7 +14161,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12567
14161
  type="button"
12568
14162
  [attr.aria-expanded]="isExpanded(row)"
12569
14163
  [attr.aria-controls]="isExpanded(row) ? rowDetailId(row) : null"
12570
- [attr.aria-label]="isExpanded(row) ? 'Réduire la ligne' : 'Développer la ligne'"
14164
+ [attr.aria-label]="isExpanded(row) ? collapseRowLabel() : expandRowLabel()"
12571
14165
  [disabled]="loading()"
12572
14166
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded text-ink-soft hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12573
14167
  (click)="onExpanderClick($event, row)"
@@ -12592,7 +14186,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12592
14186
  type="button"
12593
14187
  role="checkbox"
12594
14188
  [attr.aria-checked]="isRowSelected(row) ? 'true' : 'false'"
12595
- aria-label="Sélectionner la ligne"
14189
+ [attr.aria-label]="selectRowLabel()"
12596
14190
  [disabled]="loading()"
12597
14191
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12598
14192
  [class]="checkboxClass(isRowSelected(row), false)"
@@ -12607,7 +14201,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12607
14201
  type="button"
12608
14202
  role="radio"
12609
14203
  [attr.aria-checked]="isRowSelected(row) ? 'true' : 'false'"
12610
- aria-label="Sélectionner la ligne"
14204
+ [attr.aria-label]="selectRowLabel()"
12611
14205
  [disabled]="loading()"
12612
14206
  class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded-full border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-1 disabled:opacity-60"
12613
14207
  [class]="checkboxClass(isRowSelected(row), false)"
@@ -12654,20 +14248,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12654
14248
  @if (isEditing(i, ci)) {
12655
14249
  @switch (col.editor) {
12656
14250
  @case ('select') {
12657
- <select
12658
- data-sds-cell-editor
12659
- class="h-9 w-full rounded-control border border-brand-500 bg-surface px-2 text-sm text-ink outline-none ring-2 ring-brand-500/25"
12660
- [attr.aria-label]="col.header"
12661
- (change)="onSelectEditorChange($event, col)"
12662
- (blur)="onEditorBlur()"
12663
- >
12664
- @if (draft() === null || draft() === undefined) {
12665
- <option value="" disabled selected>{{ col.placeholder ?? '' }}</option>
12666
- }
12667
- @for (opt of col.options ?? []; track $index; let oi = $index) {
12668
- <option [value]="oi" [selected]="opt.value === draft()">{{ opt.label }}</option>
12669
- }
12670
- </select>
14251
+ <!-- A native select (dependable inside a grid cell), dressed like the other editors:
14252
+ same height, radius, focus ring and padding, a library chevron, dark-mode popup. -->
14253
+ <div class="relative flex items-center">
14254
+ <select
14255
+ data-sds-cell-editor
14256
+ [class]="editorClass(row, col) + ' cursor-pointer appearance-none pl-2.5 pr-8 dark:scheme-dark'"
14257
+ [attr.aria-label]="col.header"
14258
+ [attr.aria-invalid]="cellErrorFor(row, col.field) ? 'true' : null"
14259
+ [attr.aria-describedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : null"
14260
+ (change)="onSelectEditorChange($event, col)"
14261
+ (blur)="onEditorBlur()"
14262
+ >
14263
+ @if (draft() === null || draft() === undefined) {
14264
+ <option value="" disabled selected>{{ col.placeholder ?? selectPlaceholder() }}</option>
14265
+ }
14266
+ @for (opt of col.options ?? []; track $index; let oi = $index) {
14267
+ <option [value]="oi" [selected]="opt.value === draft()">{{ opt.label }}</option>
14268
+ }
14269
+ </select>
14270
+ <sds-icon
14271
+ name="chevron-down"
14272
+ [size]="16"
14273
+ class="pointer-events-none absolute right-2.5 text-ink-soft"
14274
+ />
14275
+ </div>
12671
14276
  }
12672
14277
  @case ('number') {
12673
14278
  <sds-input-number
@@ -12682,6 +14287,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12682
14287
  [maxFractionDigits]="col.maxFractionDigits ?? null"
12683
14288
  [placeholder]="col.placeholder ?? ''"
12684
14289
  [ariaLabel]="col.header"
14290
+ [invalid]="!!cellErrorFor(row, col.field)"
14291
+ [ariaDescribedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : ''"
12685
14292
  (focusout)="onEditorBlur()"
12686
14293
  />
12687
14294
  }
@@ -12699,6 +14306,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12699
14306
  [maxFractionDigits]="col.maxFractionDigits ?? null"
12700
14307
  [placeholder]="col.placeholder ?? ''"
12701
14308
  [ariaLabel]="col.header"
14309
+ [invalid]="!!cellErrorFor(row, col.field)"
14310
+ [ariaDescribedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : ''"
12702
14311
  (focusout)="onEditorBlur()"
12703
14312
  />
12704
14313
  }
@@ -12706,10 +14315,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12706
14315
  <input
12707
14316
  data-sds-cell-editor
12708
14317
  type="text"
12709
- class="h-9 w-full rounded-control border border-brand-500 bg-surface px-2.5 text-sm text-ink outline-none ring-2 ring-brand-500/25 placeholder:text-ink-faint"
14318
+ [class]="editorClass(row, col) + ' px-2.5 placeholder:text-ink-faint'"
12710
14319
  [value]="draftText()"
12711
14320
  [placeholder]="col.placeholder ?? ''"
12712
14321
  [attr.aria-label]="col.header"
14322
+ [attr.aria-invalid]="cellErrorFor(row, col.field) ? 'true' : null"
14323
+ [attr.aria-describedby]="cellErrorFor(row, col.field) ? cellErrorId(i, ci) : null"
12713
14324
  (input)="onTextEditorInput($event)"
12714
14325
  (blur)="onEditorBlur()"
12715
14326
  />
@@ -12765,7 +14376,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12765
14376
  [attr.colspan]="totalColumnCount()"
12766
14377
  class="py-10 text-center text-ink-faint"
12767
14378
  >
12768
- {{ emptyMessage() }}
14379
+ @if (emptyTemplate(); as tpl) {
14380
+ <ng-container
14381
+ [ngTemplateOutlet]="tpl"
14382
+ [ngTemplateOutletContext]="{ $implicit: filterText(), filterText: filterText() }"
14383
+ />
14384
+ } @else {
14385
+ {{ emptyMessage() }}
14386
+ }
12769
14387
  </td>
12770
14388
  </tr>
12771
14389
  }
@@ -12811,7 +14429,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12811
14429
  </div>
12812
14430
  `,
12813
14431
  }]
12814
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], striped: [{ type: i0.Input, args: [{ isSignal: true, alias: "striped", required: false }] }], gridlines: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridlines", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], paginator: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginator", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], showCurrentPageReport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCurrentPageReport", required: false }] }], currentPageReportTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPageReportTemplate", required: false }] }], rowsPerPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageLabel", required: false }] }], paginationLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationLabel", required: false }] }], firstPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstPageLabel", required: false }] }], previousPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousPageLabel", required: false }] }], nextPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextPageLabel", required: false }] }], lastPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "lastPageLabel", required: false }] }], pageLinkLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageLinkLabel", required: false }] }], globalFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilter", required: false }] }], globalFilterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterPlaceholder", required: false }] }], globalFilterAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterAriaLabel", required: false }] }], globalFilterFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterFields", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], scrollHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollHeight", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], editOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "editOn", required: false }] }], cellErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellErrors", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dirtyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirtyLabel", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }], rowUnselect: [{ type: i0.Output, args: ["rowUnselect"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowExpand: [{ type: i0.Output, args: ["rowExpand"] }], rowCollapse: [{ type: i0.Output, args: ["rowCollapse"] }], colReorder: [{ type: i0.Output, args: ["colReorder"] }], cellCommit: [{ type: i0.Output, args: ["cellCommit"] }], cellTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdsTableCell), { isSignal: true }] }], rowExpansion: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsTableRowExpansion), { isSignal: true }] }], numberEditor: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdsInputNumber), { isSignal: true }] }] } });
14432
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], striped: [{ type: i0.Input, args: [{ isSignal: true, alias: "striped", required: false }] }], gridlines: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridlines", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], paginator: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginator", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], showCurrentPageReport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCurrentPageReport", required: false }] }], currentPageReportTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPageReportTemplate", required: false }] }], rowsPerPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageLabel", required: false }] }], paginationLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationLabel", required: false }] }], firstPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstPageLabel", required: false }] }], previousPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousPageLabel", required: false }] }], nextPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextPageLabel", required: false }] }], lastPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "lastPageLabel", required: false }] }], pageLinkLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageLinkLabel", required: false }] }], globalFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilter", required: false }] }], globalFilterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterPlaceholder", required: false }] }], globalFilterAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterAriaLabel", required: false }] }], globalFilterFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterFields", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], rowClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClickable", required: false }] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], scrollHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollHeight", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], editOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "editOn", required: false }] }], cellErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellErrors", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dirtyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirtyLabel", required: false }] }], selectPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectPlaceholder", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", required: false }] }], selectRowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectRowLabel", required: false }] }], selectionColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionColumnLabel", required: false }] }], expandColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandColumnLabel", required: false }] }], expandRowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandRowLabel", required: false }] }], collapseRowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseRowLabel", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }], rowUnselect: [{ type: i0.Output, args: ["rowUnselect"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowExpand: [{ type: i0.Output, args: ["rowExpand"] }], rowCollapse: [{ type: i0.Output, args: ["rowCollapse"] }], colReorder: [{ type: i0.Output, args: ["colReorder"] }], cellCommit: [{ type: i0.Output, args: ["cellCommit"] }], cellTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdsTableCell), { isSignal: true }] }], headerTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdsTableHeader), { isSignal: true }] }], rowExpansion: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsTableRowExpansion), { isSignal: true }] }], emptyTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsTableEmpty), { isSignal: true }] }], numberEditor: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdsInputNumber), { isSignal: true }] }] } });
12815
14433
 
12816
14434
  /**
12817
14435
  * A tiny transient-notification bus. Application code calls {@link success} / {@link error} / {@link info}
@@ -12823,7 +14441,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12823
14441
  *
12824
14442
  * ```ts
12825
14443
  * private readonly toasts = inject(SdsToastService);
12826
- * this.toasts.success('Enregistré.');
14444
+ * this.toasts.success('Saved.');
12827
14445
  * ```
12828
14446
  *
12829
14447
  * Mount the host once (e.g. in the shell): `<sds-toast-host />`.
@@ -12873,10 +14491,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
12873
14491
  class SdsToastHost {
12874
14492
  toasts = inject(SdsToastService);
12875
14493
  /**
12876
- * Accessible name for each toast's close button. French default so existing consumers are unchanged; the
12877
- * button is icon-only, so an app serving more than one language MUST translate it.
14494
+ * Accessible name for each toast's close button. English default; the button is icon-only, so an app
14495
+ * serving more than one language MUST translate it.
12878
14496
  */
12879
- closeLabel = input('Fermer', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
14497
+ closeLabel = input('Close', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
12880
14498
  errors = computed(() => this.toasts.toasts().filter((t) => t.kind === 'error'), ...(ngDevMode ? [{ debugName: "errors" }] : []));
12881
14499
  notices = computed(() => this.toasts.toasts().filter((t) => t.kind !== 'error'), ...(ngDevMode ? [{ debugName: "notices" }] : []));
12882
14500
  icon(kind) {
@@ -13059,11 +14677,11 @@ class SdsThemeToggle {
13059
14677
  /** `surface` for light/glass bars, `on-brand` for the dark brand bar. */
13060
14678
  appearance = input('surface', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
13061
14679
  /**
13062
- * Accessible names for the two actions. French defaults keep existing consumers unchanged; a
13063
- * multilingual app must pass translated values.
14680
+ * Accessible names for the two actions. English defaults; a multilingual app must pass translated
14681
+ * values.
13064
14682
  */
13065
- darkLabel = input('Passer en mode sombre', ...(ngDevMode ? [{ debugName: "darkLabel" }] : []));
13066
- lightLabel = input('Passer en mode clair', ...(ngDevMode ? [{ debugName: "lightLabel" }] : []));
14683
+ darkLabel = input('Switch to dark mode', ...(ngDevMode ? [{ debugName: "darkLabel" }] : []));
14684
+ lightLabel = input('Switch to light mode', ...(ngDevMode ? [{ debugName: "lightLabel" }] : []));
13067
14685
  iconSize = computed(() => Math.round(this.size() * 0.5), ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
13068
14686
  label = computed(() => (this.isDark() ? this.lightLabel() : this.darkLabel()), ...(ngDevMode ? [{ debugName: "label" }] : []));
13069
14687
  buttonClasses = computed(() => this.appearance() === 'on-brand'
@@ -13204,11 +14822,10 @@ class SdsTopbar {
13204
14822
  /** Show the mobile hamburger that toggles the sidebar drawer (< lg). */
13205
14823
  menuButton = input(true, ...(ngDevMode ? [{ debugName: "menuButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
13206
14824
  /**
13207
- * Accessible name for the hamburger. Defaults to French so nothing changes for existing consumers,
13208
- * but any app serving more than one language MUST pass a translated value — an icon-only control's
13209
- * aria-label is its only name, and a hardcoded one is simply wrong in every other locale.
14825
+ * Accessible name for the hamburger. English default; any app serving more than one language MUST
14826
+ * pass a translated value — an icon-only control's aria-label is its only name.
13210
14827
  */
13211
- menuLabel = input('Ouvrir le menu', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
14828
+ menuLabel = input('Open menu', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
13212
14829
  /**
13213
14830
  * `brand` (default) paints the gradient and the wave; `surface` is white on a hairline border;
13214
14831
  * `glass` is a translucent, brand-tinted bar with a backdrop blur.
@@ -13549,7 +15166,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
13549
15166
  * @example
13550
15167
  * <sds-sidebar appearance="surface" decorations>
13551
15168
  * <sds-nav-group label="Membres">
13552
- * <sds-nav-item icon="user-plus" label="Créer un membre" link="/membres/new" />
15169
+ * <sds-nav-item icon="user-plus" label="Add a member" link="/members/new" />
13553
15170
  * </sds-nav-group>
13554
15171
  * <div sdsFooter>…</div>
13555
15172
  * </sds-sidebar>
@@ -13589,12 +15206,12 @@ class SdsSidebar {
13589
15206
  });
13590
15207
  }
13591
15208
  /**
13592
- * Accessible names for the rail's three icon-only controls. French defaults keep existing consumers
13593
- * unchanged; a multilingual app must pass translated values.
15209
+ * Accessible names for the rail's three icon-only controls. English defaults; a multilingual app
15210
+ * must pass translated values.
13594
15211
  */
13595
- collapseLabel = input('Replier le menu', ...(ngDevMode ? [{ debugName: "collapseLabel" }] : []));
13596
- expandLabel = input('Déplier le menu', ...(ngDevMode ? [{ debugName: "expandLabel" }] : []));
13597
- closeLabel = input('Fermer le menu', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
15212
+ collapseLabel = input('Collapse menu', ...(ngDevMode ? [{ debugName: "collapseLabel" }] : []));
15213
+ expandLabel = input('Expand menu', ...(ngDevMode ? [{ debugName: "expandLabel" }] : []));
15214
+ closeLabel = input('Close menu', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
13598
15215
  /** `brand` (default) is the dark rail; `surface` is a light rail with brand accents. */
13599
15216
  appearance = input('brand', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
13600
15217
  /** Add slow-drifting brand-tinted blobs behind the rail (opt-in). */
@@ -14246,8 +15863,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14246
15863
  * @example
14247
15864
  * <sds-user-menu name="Souhail AIT SAID" email="souhail@example.com" status="online" appearance="surface">
14248
15865
  * <sds-menu-item icon="user" label="Profil" link="/profile" />
14249
- * <sds-menu-item icon="settings" label="Paramètres" link="/settings" />
14250
- * <sds-menu-item icon="log-out" label="Se déconnecter" tone="danger" (select)="signOut()" />
15866
+ * <sds-menu-item icon="settings" label="Settings" link="/settings" />
15867
+ * <sds-menu-item icon="log-out" label="Sign out" tone="danger" (select)="signOut()" />
14251
15868
  * </sds-user-menu>
14252
15869
  */
14253
15870
  class SdsUserMenu {
@@ -14269,7 +15886,7 @@ class SdsUserMenu {
14269
15886
  /** Override the trigger avatar's initials background; defaults per appearance. */
14270
15887
  avatarBgClass = input(null, ...(ngDevMode ? [{ debugName: "avatarBgClass" }] : []));
14271
15888
  /** Accessible name for the trigger (icon/avatar only when details are hidden). */
14272
- menuLabel = input('Menu du compte', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
15889
+ menuLabel = input('Account menu', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
14273
15890
  openChange = output();
14274
15891
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
14275
15892
  toggle() {
@@ -14376,7 +15993,7 @@ class SdsUserMenu {
14376
15993
  </div>
14377
15994
  </div>
14378
15995
  }
14379
- `, isInline: true, styles: [".animate-sds-menu{animation:sds-menu-in .14s ease-out}@keyframes sds-menu-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@media(prefers-reduced-motion:reduce){.animate-sds-menu{animation:none}}\n"], dependencies: [{ kind: "component", type: SdsAvatar, selector: "sds-avatar", inputs: ["name", "initials", "src", "size", "status", "bgClass"] }, { kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15996
+ `, isInline: true, styles: [".animate-sds-menu{animation:sds-menu-in .14s ease-out}@keyframes sds-menu-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@media(prefers-reduced-motion:reduce){.animate-sds-menu{animation:none}}\n"], dependencies: [{ kind: "component", type: SdsAvatar, selector: "sds-avatar", inputs: ["name", "initials", "src", "size", "status", "shape", "bgClass", "fallbackIcon", "placeholderClass", "decorative", "lazy"] }, { kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14380
15997
  }
14381
15998
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsUserMenu, decorators: [{
14382
15999
  type: Component,
@@ -14465,7 +16082,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14465
16082
  * @example
14466
16083
  * <sds-menu-item icon="user" label="Profil" link="/profile" /> <!-- in-app route -->
14467
16084
  * <sds-menu-item icon="external-link" label="Site public" href="https://…" target="_blank" />
14468
- * <sds-menu-item icon="log-out" label="Se déconnecter" tone="danger" (select)="signOut()" />
16085
+ * <sds-menu-item icon="log-out" label="Sign out" tone="danger" (select)="signOut()" />
14469
16086
  */
14470
16087
  class SdsMenuItem {
14471
16088
  icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
@@ -14593,7 +16210,7 @@ class SdsSearchCommand {
14593
16210
  /** The searchable entries. Order is preserved; when the field is empty they show as quick navigation. */
14594
16211
  items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
14595
16212
  /** Placeholder on the trigger pill and the search field. */
14596
- placeholder = input('Rechercher…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
16213
+ placeholder = input('Search…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
14597
16214
  /** Show the built-in trigger pill. Turn off to drive the palette from your own control (`cmd.open()`). */
14598
16215
  showTrigger = input(true, ...(ngDevMode ? [{ debugName: "showTrigger", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14599
16216
  /** Enable the global ⌘K / Ctrl-K shortcut. */
@@ -14603,15 +16220,15 @@ class SdsSearchCommand {
14603
16220
  /** `surface` (default) for a light / glass bar; `on-brand` for the dark brand bar. */
14604
16221
  appearance = input('surface', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
14605
16222
  /**
14606
- * User-facing copy. French defaults; a multilingual app passes translated values (the trigger and the
14607
- * field share `placeholder`, and `searchLabel` is the accessible name for both).
16223
+ * User-facing copy. English defaults; a multilingual app passes translated values (the trigger and
16224
+ * the field share `placeholder`, and `searchLabel` is the accessible name for both).
14608
16225
  */
14609
- searchLabel = input('Rechercher', ...(ngDevMode ? [{ debugName: "searchLabel" }] : []));
14610
- clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
14611
- emptyLabel = input('Aucun résultat', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : []));
14612
- selectHint = input('Sélectionner', ...(ngDevMode ? [{ debugName: "selectHint" }] : []));
14613
- navigateHint = input('Naviguer', ...(ngDevMode ? [{ debugName: "navigateHint" }] : []));
14614
- closeHint = input('Fermer', ...(ngDevMode ? [{ debugName: "closeHint" }] : []));
16226
+ searchLabel = input('Search', ...(ngDevMode ? [{ debugName: "searchLabel" }] : []));
16227
+ clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
16228
+ emptyLabel = input('No results', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : []));
16229
+ selectHint = input('Select', ...(ngDevMode ? [{ debugName: "selectHint" }] : []));
16230
+ navigateHint = input('Navigate', ...(ngDevMode ? [{ debugName: "navigateHint" }] : []));
16231
+ closeHint = input('Close', ...(ngDevMode ? [{ debugName: "closeHint" }] : []));
14615
16232
  select = output();
14616
16233
  openChange = output();
14617
16234
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
@@ -15085,8 +16702,8 @@ class SdsLanguageSwitcher {
15085
16702
  /** `up` prefers opening the panel above the trigger — for a switcher near the bottom of the page. */
15086
16703
  direction = input('down', ...(ngDevMode ? [{ debugName: "direction" }] : []));
15087
16704
  /** Accessible name for the trigger and panel. */
15088
- menuLabel = input('Choisir la langue', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
15089
- /** Optional visible header above the list (e.g. "Langue"). Omitted when empty. */
16705
+ menuLabel = input('Choose language', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
16706
+ /** Optional visible header above the list (e.g. "Language"). Omitted when empty. */
15090
16707
  heading = input('', ...(ngDevMode ? [{ debugName: "heading" }] : []));
15091
16708
  openChange = output();
15092
16709
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
@@ -15450,5 +17067,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15450
17067
  * Generated bundle index. Do not edit.
15451
17068
  */
15452
17069
 
15453
- export { SDS_ALLERGENS, SDS_ALLERGEN_ICONS, SDS_ALLERGEN_LABELS_EN, SDS_ICONS, SDS_PHONE_COUNTRIES, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAllergenIcon, SdsAvatar, SdsBadge, SdsBulkBar, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsChecklistRow, SdsDatePicker, SdsFileDrop, SdsIcon, SdsImageCropper, SdsInput, SdsInputNumber, SdsLanguageSwitcher, SdsLocalizedInput, SdsMenu, SdsMenuItem, SdsMenuTrigger, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsRadio, SdsScore, SdsSearchCommand, SdsSegmented, SdsSelect, SdsSelectEmpty, SdsSelectFooter, SdsSelectOption, SdsShell, SdsSidebar, SdsSortableHandle, SdsSortableItem, SdsSortableList, SdsStatusTile, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsThemeService, SdsThemeToggle, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SdsTriStateChip, SdsUserMenu, SidebarStore, addDays, addMonths, addMonthsKeepDay, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isoDay, pad2, sdsFileMatchesAccept, sdsFormatBytes, sdsFormatNational, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth };
17070
+ export { SDS_ALLERGENS, SDS_ALLERGEN_ICONS, SDS_ALLERGEN_LABELS_EN, SDS_ICONS, SDS_PHONE_COUNTRIES, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAllergenIcon, SdsAvatar, SdsBadge, SdsBulkBar, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsChecklistRow, SdsChip, SdsChipGroup, SdsDatePicker, SdsFileDrop, SdsIcon, SdsImageCropper, SdsInput, SdsInputNumber, SdsLanguageSwitcher, SdsLocalizedInput, SdsMenu, SdsMenuItem, SdsMenuTrigger, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsRadio, SdsScore, SdsSearchCommand, SdsSegmented, SdsSelect, SdsSelectEmpty, SdsSelectFooter, SdsSelectOption, SdsShell, SdsSidebar, SdsSortableHandle, SdsSortableItem, SdsSortableList, SdsStatusTile, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableEmpty, SdsTableHeader, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsThemeService, SdsThemeToggle, SdsTimeInput, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SdsTriStateChip, SdsUserMenu, SidebarStore, addDays, addMonths, addMonthsKeepDay, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isoDay, pad2, sdsFileMatchesAccept, sdsFormatBytes, sdsFormatNational, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth };
15454
17071
  //# sourceMappingURL=besaitech-ng-design-system.mjs.map