@besaitech/ng-design-system 0.0.69 → 0.0.70

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.
@@ -418,7 +418,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
418
418
  `, 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"] }]
419
419
  }], 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 }] }] } });
420
420
 
421
- let nextId$p = 0;
421
+ let nextId$q = 0;
422
422
  const SIZES$d = {
423
423
  sm: 'h-9 text-sm',
424
424
  md: 'h-10 text-sm',
@@ -431,7 +431,7 @@ const SIZES$d = {
431
431
  * sidebar search field).
432
432
  */
433
433
  class SdsInput {
434
- autoId = `sds-input-${nextId$p++}`;
434
+ autoId = `sds-input-${nextId$q++}`;
435
435
  /**
436
436
  * `id` of the native `<input>` — for a `<label for>` outside the component, or to focus the field with
437
437
  * `document.getElementById(id).focus()` (after adding a row, say). Empty (default) generates one. Set it
@@ -791,7 +791,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
791
791
  `, 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"] }]
792
792
  }], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], 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 }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", 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"] }] } });
793
793
 
794
- let nextId$o = 0;
794
+ let nextId$p = 0;
795
795
  const HEIGHTS = {
796
796
  sm: 'h-9',
797
797
  md: 'h-10',
@@ -819,7 +819,7 @@ const TEXT = {
819
819
  * with "." ("1.234" in nl-BE) stays a thousands separator.
820
820
  */
821
821
  class SdsInputNumber {
822
- inputId = `sds-input-number-${nextId$o++}`;
822
+ inputId = `sds-input-number-${nextId$p++}`;
823
823
  fieldRef = viewChild('field', ...(ngDevMode ? [{ debugName: "fieldRef" }] : []));
824
824
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
825
825
  /** Accessible name used when no visible [label] is provided. */
@@ -1603,7 +1603,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1603
1603
  }]
1604
1604
  }], 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 }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", 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 }] }] } });
1605
1605
 
1606
- let nextId$n = 0;
1606
+ let nextId$o = 0;
1607
1607
  const SIZES$c = {
1608
1608
  sm: 'h-9 text-sm',
1609
1609
  md: 'h-10 text-sm',
@@ -1644,7 +1644,7 @@ function parseTime(value) {
1644
1644
  * <sds-time-input [ariaLabel]="'Closes at'" [ngModel]="end()" (ngModelChange)="end.set($event)" clearable />
1645
1645
  */
1646
1646
  class SdsTimeInput {
1647
- fieldId = `sds-time-input-${nextId$n++}`;
1647
+ fieldId = `sds-time-input-${nextId$o++}`;
1648
1648
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
1649
1649
  /** Accessible name of the field when there is no visible [label]. */
1650
1650
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -2173,7 +2173,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2173
2173
  }]
2174
2174
  }], 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 }] }] } });
2175
2175
 
2176
- let nextId$m = 0;
2176
+ let nextId$n = 0;
2177
2177
  const SIZES$b = {
2178
2178
  sm: 'min-h-9 text-sm',
2179
2179
  md: 'min-h-10 text-sm',
@@ -2196,7 +2196,7 @@ const SIZES$b = {
2196
2196
  * `<sds-localized-input [languages]="['fr','nl','en']" [(ngModel)]="title" />`.
2197
2197
  */
2198
2198
  class SdsLocalizedInput {
2199
- autoId = `sds-localized-input-${nextId$m++}`;
2199
+ autoId = `sds-localized-input-${nextId$n++}`;
2200
2200
  /** The native field's id; a generated one when empty (set it to pair an external `<label for>`). */
2201
2201
  inputId = input('', ...(ngDevMode ? [{ debugName: "inputId" }] : []));
2202
2202
  /** The native field's id: {@link inputId} or the generated one. */
@@ -2556,7 +2556,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2556
2556
  }]
2557
2557
  }], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", 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 }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], requiredLanguages: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredLanguages", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", 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 }] }], requiredLanguageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredLanguageLabel", required: false }] }] } });
2558
2558
 
2559
- let nextId$l = 0;
2559
+ let nextId$m = 0;
2560
2560
  const SIZES$a = {
2561
2561
  md: 'h-12 w-11 text-lg',
2562
2562
  lg: 'h-14 w-12 text-xl',
@@ -2573,7 +2573,7 @@ const SIZES$a = {
2573
2573
  * first box and have it land correctly.
2574
2574
  */
2575
2575
  class SdsOtpInput {
2576
- groupId = `sds-otp-${nextId$l++}`;
2576
+ groupId = `sds-otp-${nextId$m++}`;
2577
2577
  /** How many boxes / characters the code has. */
2578
2578
  length = input(6, ...(ngDevMode ? [{ debugName: "length", transform: numberAttribute }] : [{ transform: numberAttribute }]));
2579
2579
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
@@ -2873,7 +2873,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2873
2873
  }]
2874
2874
  }], 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 }] }] } });
2875
2875
 
2876
- let nextId$k = 0;
2876
+ let nextId$l = 0;
2877
2877
  const SIZES$9 = {
2878
2878
  sm: 'text-sm px-3 py-2',
2879
2879
  md: 'text-sm px-3.5 py-2.5',
@@ -2885,7 +2885,7 @@ const SIZES$9 = {
2885
2885
  * [(ngModel)].
2886
2886
  */
2887
2887
  class SdsTextarea {
2888
- fieldId = `sds-textarea-${nextId$k++}`;
2888
+ fieldId = `sds-textarea-${nextId$l++}`;
2889
2889
  field = viewChild('field', ...(ngDevMode ? [{ debugName: "field" }] : []));
2890
2890
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2891
2891
  /** Accessible name used when no visible [label] is provided. */
@@ -3152,7 +3152,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3152
3152
  }]
3153
3153
  }], 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 }] }] } });
3154
3154
 
3155
- let nextId$j = 0;
3155
+ let nextId$k = 0;
3156
3156
  const SIZES$8 = {
3157
3157
  sm: { track: 'h-5 w-9', knob: 'h-4 w-4', on: 'translate-x-4', icon: 11 },
3158
3158
  md: { track: 'h-6 w-11', knob: 'h-5 w-5', on: 'translate-x-5', icon: 13 },
@@ -3165,7 +3165,7 @@ const SIZES$8 = {
3165
3165
  * states.
3166
3166
  */
3167
3167
  class SdsToggleSwitch {
3168
- fieldId = `sds-toggle-switch-${nextId$j++}`;
3168
+ fieldId = `sds-toggle-switch-${nextId$k++}`;
3169
3169
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
3170
3170
  /** Accessible name used when no visible [label] is provided. */
3171
3171
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -3412,7 +3412,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3412
3412
  }]
3413
3413
  }], 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 }] }] } });
3414
3414
 
3415
- let nextId$i = 0;
3415
+ let nextId$j = 0;
3416
3416
  /**
3417
3417
  * Boolean checkbox. Implements ControlValueAccessor — use with reactive forms
3418
3418
  * or `[(ngModel)]`. Renders a styled box (brand-filled with a check when on)
@@ -3427,7 +3427,7 @@ let nextId$i = 0;
3427
3427
  * <sds-checkbox label="Auto-merge" [(ngModel)]="autoMerge" />
3428
3428
  */
3429
3429
  class SdsCheckbox {
3430
- id = `sds-checkbox-${nextId$i++}`;
3430
+ id = `sds-checkbox-${nextId$j++}`;
3431
3431
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
3432
3432
  /** Accessible name used when no visible [label] is provided. */
3433
3433
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -3575,7 +3575,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3575
3575
  }]
3576
3576
  }], 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 }] }] } });
3577
3577
 
3578
- let nextId$h = 0;
3578
+ let nextId$i = 0;
3579
3579
  /**
3580
3580
  * Mounted radios by group name. A pick updates the rest of its group, because a native radio says
3581
3581
  * nothing when the browser unchecks it — without this a reactive form would leave the old choice drawn.
@@ -3601,7 +3601,7 @@ const groups = new Map();
3601
3601
  * <sds-radio name="size" value="large" label="Grand" [(ngModel)]="size" />
3602
3602
  */
3603
3603
  class SdsRadio {
3604
- id = `sds-radio-${nextId$h++}`;
3604
+ id = `sds-radio-${nextId$i++}`;
3605
3605
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
3606
3606
  /** Accessible name used when no visible [label] is provided. */
3607
3607
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
@@ -4177,7 +4177,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4177
4177
 
4178
4178
  /** Activation order: none → contains → may-contain → none. */
4179
4179
  const ORDER = ['none', 'contains', 'may-contain'];
4180
- let nextId$g = 0;
4180
+ let nextId$h = 0;
4181
4181
  /**
4182
4182
  * A chip that cycles through three states — `none`, `contains`, `may-contain` — built for the 14-tile
4183
4183
  * allergen declaration grid, but generic (any "no / yes / maybe" declaration).
@@ -4197,7 +4197,7 @@ let nextId$g = 0;
4197
4197
  * containsLabel="Contains" mayContainLabel="May contain" noneLabel="Not present" />
4198
4198
  */
4199
4199
  class SdsTriStateChip {
4200
- chipId = `sds-tri-state-chip-${nextId$g++}`;
4200
+ chipId = `sds-tri-state-chip-${nextId$h++}`;
4201
4201
  /** Current state. Two-way: `[(value)]`. */
4202
4202
  value = model('none', ...(ngDevMode ? [{ debugName: "value" }] : []));
4203
4203
  /** Visible name (e.g. the allergen). Required — the chip has no other accessible name. */
@@ -4422,7 +4422,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4422
4422
  }]
4423
4423
  }], 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 }] }] } });
4424
4424
 
4425
- let nextId$f = 0;
4425
+ let nextId$g = 0;
4426
4426
  /**
4427
4427
  * Segmented control: a small set of mutually exclusive options shown side by side (e.g. "Pick exactly /
4428
4428
  * at least / up to"). Radio-group semantics: `role="radiogroup"` with `role="radio"` segments, one tab
@@ -4438,7 +4438,7 @@ let nextId$f = 0;
4438
4438
  */
4439
4439
  class SdsSegmented {
4440
4440
  host = inject(ElementRef);
4441
- baseId = `sds-segmented-${nextId$f++}`;
4441
+ baseId = `sds-segmented-${nextId$g++}`;
4442
4442
  /** Options: `{ value, label, icon?, disabled? }` objects, or bare strings/numbers. */
4443
4443
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
4444
4444
  /** Selected value. Two-way: `[(value)]`. */
@@ -4716,7 +4716,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4716
4716
  `,
4717
4717
  }]
4718
4718
  }], 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 }] }] } });
4719
- let nextId$e = 0;
4719
+ let nextId$f = 0;
4720
4720
  /**
4721
4721
  * A set of chips: pick ONE (a filter row such as "All · Missing photo · Hidden", VAT presets, a date
4722
4722
  * range) or, with `multiple`, SEVERAL (categories of an item, days of the week, dietary tags). Unlike
@@ -4739,7 +4739,7 @@ let nextId$e = 0;
4739
4739
  */
4740
4740
  class SdsChipGroup {
4741
4741
  host = inject(ElementRef);
4742
- baseId = `sds-chip-group-${nextId$e++}`;
4742
+ baseId = `sds-chip-group-${nextId$f++}`;
4743
4743
  /** Options: `{ value, label, icon?, count?, disabled? }` objects, or bare strings/numbers. */
4744
4744
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
4745
4745
  /** Picked value (single) or values (multiple). Two-way: `[(value)]`. */
@@ -5641,7 +5641,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
5641
5641
  }]
5642
5642
  }], 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"] }] } });
5643
5643
 
5644
- let nextId$d = 0;
5644
+ let nextId$e = 0;
5645
5645
  /**
5646
5646
  * Marks the element inside `<sds-menu>` that opens it. It may be a native button or a component that
5647
5647
  * renders one (e.g. `<sds-button sdsMenuTrigger>`): the menu wires `aria-haspopup`, `aria-expanded` and
@@ -5689,7 +5689,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
5689
5689
  class SdsMenu {
5690
5690
  host = inject(ElementRef);
5691
5691
  injector = inject(Injector);
5692
- menuId = `sds-menu-${nextId$d++}`;
5692
+ menuId = `sds-menu-${nextId$e++}`;
5693
5693
  triggerId = `${this.menuId}-trigger`;
5694
5694
  /** Actions and separators, in order. */
5695
5695
  items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
@@ -6285,7 +6285,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
6285
6285
  standalone: true,
6286
6286
  }]
6287
6287
  }] });
6288
- let nextId$c = 0;
6288
+ let nextId$d = 0;
6289
6289
  /** Fastest auto-scroll step, in px per animation frame (reached at the very edge). */
6290
6290
  const AUTO_SCROLL_MAX_STEP = 18;
6291
6291
  /** The nearest ancestor that scrolls vertically (below the document, which is handled as the window). */
@@ -6354,7 +6354,7 @@ function moveItem(list, from, to) {
6354
6354
  class SdsSortableList {
6355
6355
  host = inject(ElementRef);
6356
6356
  injector = inject(Injector);
6357
- instructionsId = `sds-sortable-${nextId$c++}-help`;
6357
+ instructionsId = `sds-sortable-${nextId$d++}-help`;
6358
6358
  /** The items, in order. Two-way: `[(items)]` receives the new order on drop. */
6359
6359
  items = model([], ...(ngDevMode ? [{ debugName: "items" }] : []));
6360
6360
  /** Property used as a stable identity for rows (strongly recommended). */
@@ -6946,7 +6946,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
6946
6946
  }]
6947
6947
  }], 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 }] }] } });
6948
6948
 
6949
- let nextId$b = 0;
6949
+ let nextId$c = 0;
6950
6950
  /** "1.5 MB" style size (decimal units, like file managers show them), for messages. */
6951
6951
  function sdsFormatBytes(bytes) {
6952
6952
  if (!Number.isFinite(bytes) || bytes < 0)
@@ -7003,7 +7003,7 @@ function sdsFileMatchesAccept(file, accept) {
7003
7003
  * (files)="upload($event)" (rejected)="onRejected($event)" />
7004
7004
  */
7005
7005
  class SdsFileDrop {
7006
- zoneId = `sds-file-drop-${nextId$b++}`;
7006
+ zoneId = `sds-file-drop-${nextId$c++}`;
7007
7007
  fileInput = viewChild('fileInput', ...(ngDevMode ? [{ debugName: "fileInput" }] : []));
7008
7008
  /** Accepted types, native `accept` syntax. Empty = anything. */
7009
7009
  accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
@@ -7385,7 +7385,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
7385
7385
  }]
7386
7386
  }], propDecorators: { fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], dropLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropLabel", required: false }] }], browseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "browseLabel", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], wrongTypeMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrongTypeMessage", required: false }] }], tooLargeMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooLargeMessage", required: false }] }], tooManyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooManyMessage", required: false }] }], files: [{ type: i0.Output, args: ["files"] }], rejected: [{ type: i0.Output, args: ["rejected"] }] } });
7387
7387
 
7388
- let nextId$a = 0;
7388
+ let nextId$b = 0;
7389
7389
  /**
7390
7390
  * Crops an image to a FIXED aspect ratio (e.g. the shop's card ratio): the frame is fixed and the image
7391
7391
  * moves and zooms behind it, always covering the frame, so every crop is valid.
@@ -7403,7 +7403,7 @@ let nextId$a = 0;
7403
7403
  * <sds-button (click)="upload(await cropper.toBlob({ width: 1600 }))">Save</sds-button>
7404
7404
  */
7405
7405
  class SdsImageCropper {
7406
- helpId = `sds-image-cropper-${nextId$a++}-help`;
7406
+ helpId = `sds-image-cropper-${nextId$b++}-help`;
7407
7407
  viewport = viewChild('viewport', ...(ngDevMode ? [{ debugName: "viewport" }] : []));
7408
7408
  /** Image to crop: a URL, or a Blob/File. */
7409
7409
  src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : []));
@@ -8789,7 +8789,7 @@ const SIZES$7 = {
8789
8789
  class SdsDatePicker {
8790
8790
  host = inject(ElementRef);
8791
8791
  injector = inject(Injector);
8792
- fieldId = `sds-date-${nextId$9++}`;
8792
+ fieldId = `sds-date-${nextId$a++}`;
8793
8793
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
8794
8794
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
8795
8795
  placeholder = input('dd/mm/yyyy', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
@@ -9307,7 +9307,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9307
9307
  `,
9308
9308
  }]
9309
9309
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], timeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeLabel", required: false }] }], hoursLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoursLabel", required: false }] }], minutesLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "minutesLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], openCalendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "openCalendarLabel", required: false }] }], closeCalendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeCalendarLabel", required: false }] }], panelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLabel", required: false }] }], calendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarLabel", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], todayActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "todayActionLabel", required: false }] }], clearActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearActionLabel", required: false }] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdsCalendar), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
9310
- let nextId$9 = 0;
9310
+ let nextId$a = 0;
9311
9311
 
9312
9312
  /** Same first and last day (time-of-day ignored). `null` only equals `null`. */
9313
9313
  function isSameRange(a, b) {
@@ -9912,7 +9912,7 @@ const SIZES$6 = {
9912
9912
  class SdsDateRangePicker {
9913
9913
  host = inject(ElementRef);
9914
9914
  injector = inject(Injector);
9915
- uid = nextId$8++;
9915
+ uid = nextId$9++;
9916
9916
  fieldId = `sds-date-range-${this.uid}`;
9917
9917
  labelId = `${this.fieldId}-label`;
9918
9918
  valueId = `${this.fieldId}-value`;
@@ -10428,7 +10428,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10428
10428
  `,
10429
10429
  }]
10430
10430
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], maxDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDays", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], months: [{ type: i0.Input, args: [{ isSignal: true, alias: "months", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], 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 }] }], panelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLabel", required: false }] }], presetsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "presetsLabel", required: false }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], endLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "endLabel", required: false }] }], applyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], calendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarLabel", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], presetSelect: [{ type: i0.Output, args: ["presetSelect"] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdsRangeCalendar), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
10431
- let nextId$8 = 0;
10431
+ let nextId$9 = 0;
10432
10432
 
10433
10433
  /**
10434
10434
  * Supplies a custom template for each option row (and the selected value shown
@@ -10512,7 +10512,7 @@ const SIZES$5 = {
10512
10512
  md: 'h-10 text-sm',
10513
10513
  lg: 'h-12 text-base',
10514
10514
  };
10515
- let nextId$7 = 0;
10515
+ let nextId$8 = 0;
10516
10516
  /**
10517
10517
  * Single-select dropdown. Options may be primitives or objects (configure via
10518
10518
  * [optionLabel]/[optionValue]/[optionDisabled]). The overlay is appended to
@@ -10546,7 +10546,7 @@ let nextId$7 = 0;
10546
10546
  class SdsSelect {
10547
10547
  host = inject(ElementRef);
10548
10548
  injector = inject(Injector);
10549
- selectId = `sds-select-${nextId$7++}`;
10549
+ selectId = `sds-select-${nextId$8++}`;
10550
10550
  listId = `${this.selectId}-list`;
10551
10551
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
10552
10552
  optionLabel = input('label', ...(ngDevMode ? [{ debugName: "optionLabel" }] : []));
@@ -11256,7 +11256,7 @@ const SIZES$4 = {
11256
11256
  md: 'min-h-10 text-sm',
11257
11257
  lg: 'min-h-12 text-base',
11258
11258
  };
11259
- let nextId$6 = 0;
11259
+ let nextId$7 = 0;
11260
11260
  /**
11261
11261
  * Multi-select dropdown (value is an array). Options may be primitives or
11262
11262
  * objects (configure via [optionLabel]/[optionValue]/[optionDisabled]). The
@@ -11289,7 +11289,7 @@ let nextId$6 = 0;
11289
11289
  class SdsMultiSelect {
11290
11290
  host = inject(ElementRef);
11291
11291
  injector = inject(Injector);
11292
- selectId = `sds-multiselect-${nextId$6++}`;
11292
+ selectId = `sds-multiselect-${nextId$7++}`;
11293
11293
  listId = `${this.selectId}-list`;
11294
11294
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
11295
11295
  optionLabel = input('label', ...(ngDevMode ? [{ debugName: "optionLabel" }] : []));
@@ -12172,7 +12172,7 @@ const SIZES$3 = {
12172
12172
  md: 'h-10 text-sm',
12173
12173
  lg: 'h-12 text-base',
12174
12174
  };
12175
- let nextId$5 = 0;
12175
+ let nextId$6 = 0;
12176
12176
  /**
12177
12177
  * International phone number input: a country selector (ISO chip + dial code)
12178
12178
  * with a searchable, body-appended dropdown, plus a formatted national-number
@@ -12187,7 +12187,7 @@ let nextId$5 = 0;
12187
12187
  class SdsPhoneInput {
12188
12188
  host = inject(ElementRef);
12189
12189
  injector = inject(Injector);
12190
- fieldId = `sds-phone-${nextId$5++}`;
12190
+ fieldId = `sds-phone-${nextId$6++}`;
12191
12191
  listId = `${this.fieldId}-list`;
12192
12192
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
12193
12193
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
@@ -12945,7 +12945,7 @@ const SIZES$2 = {
12945
12945
  md: 'h-10 text-sm',
12946
12946
  lg: 'h-12 text-base',
12947
12947
  };
12948
- let nextId$4 = 0;
12948
+ let nextId$5 = 0;
12949
12949
  /**
12950
12950
  * Address search field — a combobox over an app-supplied geocoder. Typing (at least `minChars`
12951
12951
  * characters) runs the `search` provider, debounced by `debounceMs`; only the latest query's response is
@@ -12980,7 +12980,7 @@ let nextId$4 = 0;
12980
12980
  */
12981
12981
  class SdsAddressAutocomplete {
12982
12982
  host = inject(ElementRef);
12983
- autoId = `sds-address-${nextId$4++}`;
12983
+ autoId = `sds-address-${nextId$5++}`;
12984
12984
  locateListId = `${this.autoId}-here`;
12985
12985
  listId = `${this.autoId}-list`;
12986
12986
  // --- providers ------------------------------------------------------------
@@ -14251,7 +14251,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14251
14251
  }]
14252
14252
  }], propDecorators: { totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], first: [{ type: i0.Input, args: [{ isSignal: true, alias: "first", required: false }] }, { type: i0.Output, args: ["firstChange"] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }, { type: i0.Output, args: ["rowsChange"] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], pageLinkSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageLinkSize", required: false }] }], showFirstLastIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFirstLastIcon", required: false }] }], showPageLinks: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageLinks", required: false }] }], showCurrentPageReport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCurrentPageReport", required: false }] }], currentPageReportTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPageReportTemplate", required: false }] }], showJumpToPageInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showJumpToPageInput", required: false }] }], rowsPerPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageLabel", required: false }] }], jumpToPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "jumpToPageLabel", 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 }] }], jumpToPageAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "jumpToPageAriaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
14253
14253
 
14254
- let nextId$3 = 0;
14254
+ let nextId$4 = 0;
14255
14255
  /**
14256
14256
  * Coordinates a single <sds-tabs> instance: holds the active value (shared from
14257
14257
  * the host's two-way model), tracks registered tabs for default selection and
@@ -14261,7 +14261,7 @@ let nextId$3 = 0;
14261
14261
  */
14262
14262
  class SdsTabsStore {
14263
14263
  /** Unique base id used to link tabs and panels via ARIA. */
14264
- id = `sds-tabs-${nextId$3++}`;
14264
+ id = `sds-tabs-${nextId$4++}`;
14265
14265
  /** Whether the tab list scrolls horizontally (mirrors [scrollable]). */
14266
14266
  scrollable = signal(false, ...(ngDevMode ? [{ debugName: "scrollable" }] : []));
14267
14267
  /** Header style (mirrors [variant]). */
@@ -14877,7 +14877,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14877
14877
  }]
14878
14878
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }] } });
14879
14879
 
14880
- let nextId$2 = 0;
14880
+ let nextId$3 = 0;
14881
14881
  /**
14882
14882
  * Coordinates a single <sds-accordion>: holds the open value(s) (shared from the
14883
14883
  * host's two-way model), the multiple-open flag, and generates the ARIA ids that
@@ -14887,7 +14887,7 @@ let nextId$2 = 0;
14887
14887
  */
14888
14888
  class SdsAccordionStore {
14889
14889
  /** Unique base id used to link headers and content regions via ARIA. */
14890
- id = `sds-accordion-${nextId$2++}`;
14890
+ id = `sds-accordion-${nextId$3++}`;
14891
14891
  /** Whether more than one panel may be open at a time (mirrors [multiple]). */
14892
14892
  multiple = signal(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
14893
14893
  value;
@@ -15293,7 +15293,7 @@ const ALIGN = {
15293
15293
  center: 'text-center',
15294
15294
  right: 'text-right',
15295
15295
  };
15296
- let nextId$1 = 0;
15296
+ let nextId$2 = 0;
15297
15297
  /**
15298
15298
  * Client-side data table mirroring PrimeNG's table, scoped to a strong local
15299
15299
  * feature set: sorting, pagination (reuses {@link SdsPaginator}), global filter,
@@ -15349,7 +15349,7 @@ let nextId$1 = 0;
15349
15349
  * dataKey="id" editable [cellErrors]="errors()" (cellCommit)="apply($event)" />
15350
15350
  */
15351
15351
  class SdsTable {
15352
- tableId = `sds-table-${nextId$1++}`;
15352
+ tableId = `sds-table-${nextId$2++}`;
15353
15353
  // --- inputs ---------------------------------------------------------------
15354
15354
  value = input([], ...(ngDevMode ? [{ debugName: "value" }] : []));
15355
15355
  columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : []));
@@ -17612,6 +17612,8 @@ function stripUiPrefs(url) {
17612
17612
  return absolute ? parsed.toString() : parsed.pathname + parsed.search + parsed.hash;
17613
17613
  }
17614
17614
 
17615
+ /** The breakpoint below which the rail is the off-canvas drawer (Tailwind's `lg`). */
17616
+ const DRAWER_QUERY = '(max-width: 1023.98px)';
17615
17617
  /**
17616
17618
  * Shared collapse state for the sidebar and its nav items.
17617
17619
  *
@@ -17620,16 +17622,45 @@ function stripUiPrefs(url) {
17620
17622
  * multiple independent sidebars, provide it at a component level instead.
17621
17623
  */
17622
17624
  class SidebarStore {
17623
- /** Desktop rail state: collapsed to icon-only width (applies at ≥ lg). */
17625
+ document = inject(DOCUMENT);
17626
+ /**
17627
+ * The person's desktop preference: icon-only rail. It is a PREFERENCE, not the rendered state — use
17628
+ * {@link railCollapsed} to know whether the rail is actually icon-only right now.
17629
+ */
17624
17630
  collapsed = signal(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
17631
+ /** True below `lg`, where the rail is the off-canvas drawer. */
17632
+ belowLg = signal(false, ...(ngDevMode ? [{ debugName: "belowLg" }] : []));
17633
+ /**
17634
+ * Whether the rail is icon-only RIGHT NOW: the desktop collapse applies at ≥ lg only. The mobile drawer
17635
+ * is always full width — reading `collapsed` there hid every collapsible group's pages behind a header
17636
+ * that could no longer open them (collapse on desktop, then open the drawer on a narrow screen).
17637
+ */
17638
+ railCollapsed = computed(() => this.collapsed() && !this.belowLg(), ...(ngDevMode ? [{ debugName: "railCollapsed" }] : []));
17625
17639
  /**
17626
17640
  * The rail's paint, published by <sds-sidebar> so projected <sds-nav-item>/<sds-nav-group> pick the
17627
17641
  * matching state and hover colours (white-on-dark for `brand`, ink-and-brand for `surface`) without
17628
17642
  * every item taking an input. One rail, one appearance — set by the sidebar, read by its children.
17629
17643
  */
17630
17644
  appearance = signal('brand', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
17645
+ /** Accordion mode, published by <sds-sidebar [accordion]>: opening one group closes the others. */
17646
+ accordion = signal(false, ...(ngDevMode ? [{ debugName: "accordion" }] : []));
17647
+ /** The collapsible group most recently opened by hand (accordion mode closes the rest). */
17648
+ openedGroup = signal(null, ...(ngDevMode ? [{ debugName: "openedGroup" }] : []));
17631
17649
  /** Mobile drawer state: whether the off-canvas sidebar is slid in (< lg). */
17632
17650
  mobileOpen = signal(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : []));
17651
+ constructor() {
17652
+ const mql = this.document.defaultView?.matchMedia?.(DRAWER_QUERY);
17653
+ if (mql) {
17654
+ this.belowLg.set(mql.matches);
17655
+ mql.addEventListener('change', (e) => {
17656
+ this.belowLg.set(e.matches);
17657
+ // Growing past lg turns the drawer into the in-flow rail: a drawer left "open" would otherwise
17658
+ // come back, scrim and all, the next time the window narrows.
17659
+ if (!e.matches)
17660
+ this.mobileOpen.set(false);
17661
+ });
17662
+ }
17663
+ }
17633
17664
  /** Toggle the desktop icon-rail. */
17634
17665
  toggle() {
17635
17666
  this.collapsed.update((v) => !v);
@@ -17655,7 +17686,7 @@ class SidebarStore {
17655
17686
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarStore, decorators: [{
17656
17687
  type: Injectable,
17657
17688
  args: [{ providedIn: 'root' }]
17658
- }] });
17689
+ }], ctorParameters: () => [] });
17659
17690
 
17660
17691
  /**
17661
17692
  * Application top bar: a logo slot, the app name, and three slots the app fills.
@@ -18031,6 +18062,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18031
18062
  }]
18032
18063
  }], propDecorators: { background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], decorations: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorations", required: false }] }] } });
18033
18064
 
18065
+ const SDS_NAV_GROUP = new InjectionToken('SDS_NAV_GROUP');
18066
+ const SDS_SIDEBAR = new InjectionToken('SDS_SIDEBAR');
18067
+
18034
18068
  /**
18035
18069
  * Collapsible navigation rail. Project <sds-nav-group>/<sds-nav-item> as the
18036
18070
  * default content, and pin a footer (e.g. search + version badge) via the
@@ -18043,11 +18077,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18043
18077
  *
18044
18078
  * Responsive: below `lg` it is a fixed off-canvas drawer opened by the top bar's hamburger; it is a
18045
18079
  * modal (scrim + trap), so while open Escape closes it, focus moves into it and is restored on close,
18046
- * and Tab is kept inside. While closed it is `inert`, so its off-screen items are out of the tab order.
18047
- * At `lg` and up it is the in-flow, collapsible rail.
18080
+ * Tab is kept inside, the page behind stops scrolling, and a swipe to the left closes it. While closed
18081
+ * it is `inert`, so its off-screen items are out of the tab order. The drawer is always full width —
18082
+ * the desktop collapse preference never applies to it. At `lg` and up it is the in-flow, collapsible
18083
+ * rail; collapsed to icons, its collapsible groups open as flyout panels and its items show tooltips.
18084
+ *
18085
+ * Project an `[sdsHeader]` element (typically the logo + app name) to brand the drawer's header row,
18086
+ * and set `[accordion]` to keep a single collapsible group open at a time.
18048
18087
  *
18049
18088
  * @example
18050
18089
  * <sds-sidebar appearance="surface" decorations>
18090
+ * <img sdsHeader class="h-7 lg:hidden" src="/logo.svg" alt="Saitech" />
18051
18091
  * <sds-nav-group label="Membres">
18052
18092
  * <sds-nav-item icon="user-plus" label="Add a member" link="/members/new" />
18053
18093
  * </sds-nav-group>
@@ -18060,18 +18100,27 @@ class SdsSidebar {
18060
18100
  drawer = viewChild('drawer', ...(ngDevMode ? [{ debugName: "drawer" }] : []));
18061
18101
  closeBtn = viewChild('closeBtn', ...(ngDevMode ? [{ debugName: "closeBtn" }] : []));
18062
18102
  /** True below the `lg` breakpoint — where the rail is the off-canvas modal drawer. */
18063
- belowLg = signal(false, ...(ngDevMode ? [{ debugName: "belowLg" }] : []));
18103
+ belowLg = this.store.belowLg;
18064
18104
  /** The element focused before the drawer opened, restored to it on close. */
18065
18105
  restoreFocusTo = null;
18106
+ /** Live horizontal offset while a swipe drags the drawer closed (≤ 0). */
18107
+ dragX = signal(0, ...(ngDevMode ? [{ debugName: "dragX" }] : []));
18108
+ touch = null;
18066
18109
  constructor() {
18067
- // Publish the rail's paint so projected nav items match it.
18110
+ // Publish the rail's paint (and accordion mode) so projected nav groups/items match it.
18068
18111
  effect(() => this.store.appearance.set(this.appearance()));
18069
- const view = this.document.defaultView;
18070
- const mql = view?.matchMedia('(max-width: 1023.98px)');
18071
- if (mql) {
18072
- this.belowLg.set(mql.matches);
18073
- mql.addEventListener('change', (e) => this.belowLg.set(e.matches));
18074
- }
18112
+ effect(() => this.store.accordion.set(this.accordion()));
18113
+ // The page behind an open drawer must not scroll under the person's thumb.
18114
+ const body = this.document.body;
18115
+ effect(() => {
18116
+ const lock = this.belowLg() && this.mobileOpen();
18117
+ if (body)
18118
+ body.style.overflow = lock ? 'hidden' : '';
18119
+ });
18120
+ inject(DestroyRef).onDestroy(() => {
18121
+ if (body)
18122
+ body.style.overflow = '';
18123
+ });
18075
18124
  // Modal focus handling for the mobile drawer: on open, remember what was focused (the hamburger) and
18076
18125
  // move focus to the close button; on close, restore it. A no-op at ≥ lg, where the rail is not modal.
18077
18126
  effect(() => {
@@ -18095,11 +18144,16 @@ class SdsSidebar {
18095
18144
  collapseLabel = input('Collapse menu', ...(ngDevMode ? [{ debugName: "collapseLabel" }] : []));
18096
18145
  expandLabel = input('Expand menu', ...(ngDevMode ? [{ debugName: "expandLabel" }] : []));
18097
18146
  closeLabel = input('Close menu', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
18147
+ /** Accessible name of the navigation landmark (e.g. "Main navigation"). */
18148
+ navLabel = input('', ...(ngDevMode ? [{ debugName: "navLabel" }] : []));
18098
18149
  /** `brand` (default) is the dark rail; `surface` is a light rail with brand accents. */
18099
18150
  appearance = input('brand', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
18151
+ /** Keep a single collapsible group open: opening one folds the others. */
18152
+ accordion = input(false, ...(ngDevMode ? [{ debugName: "accordion", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
18100
18153
  /** Add slow-drifting brand-tinted blobs behind the rail (opt-in). */
18101
18154
  decorations = input(false, ...(ngDevMode ? [{ debugName: "decorations", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
18102
- collapsed = this.store.collapsed;
18155
+ /** Icon-only right now (desktop preference, never in the mobile drawer). */
18156
+ railCollapsed = this.store.railCollapsed;
18103
18157
  mobileOpen = this.store.mobileOpen;
18104
18158
  asideClass = computed(() => {
18105
18159
  // The rail must stay OPAQUE: as a mobile drawer it slides over page content, so a translucent stop
@@ -18116,9 +18170,45 @@ class SdsSidebar {
18116
18170
  ? 'text-ink-soft hover:bg-muted hover:text-ink focus-visible:ring-brand-500/40'
18117
18171
  : 'text-white/70 hover:bg-white/10 hover:text-white focus-visible:ring-white/40', ...(ngDevMode ? [{ debugName: "railBtnClass" }] : []));
18118
18172
  /** Desktop rail width (full vs. collapsed); mobile always uses full width. */
18119
- railWidth = computed(() => this.collapsed()
18173
+ railWidth = computed(() => this.railCollapsed()
18120
18174
  ? 'var(--sds-sidebar-w-collapsed)'
18121
18175
  : 'var(--sds-sidebar-w)', ...(ngDevMode ? [{ debugName: "railWidth" }] : []));
18176
+ // --- Swipe to close (mobile drawer) ------------------------------------------------------------
18177
+ // The drawer follows the thumb once the gesture is clearly horizontal and leftward; released past a
18178
+ // fifth of its width (or 72 px) it closes, otherwise it springs back. Vertical swipes scroll the nav.
18179
+ onTouchStart(event) {
18180
+ if (!this.belowLg() || !this.mobileOpen() || event.touches.length !== 1)
18181
+ return;
18182
+ const t = event.touches[0];
18183
+ this.touch = { x: t.clientX, y: t.clientY, dragging: false };
18184
+ }
18185
+ onTouchMove(event) {
18186
+ if (!this.touch)
18187
+ return;
18188
+ const t = event.touches[0];
18189
+ const dx = t.clientX - this.touch.x;
18190
+ const dy = t.clientY - this.touch.y;
18191
+ if (!this.touch.dragging) {
18192
+ if (Math.abs(dx) < 10 && Math.abs(dy) < 10)
18193
+ return;
18194
+ if (dx >= 0 || Math.abs(dy) > Math.abs(dx)) {
18195
+ this.touch = null; // a scroll, or a swipe the wrong way
18196
+ return;
18197
+ }
18198
+ this.touch.dragging = true;
18199
+ }
18200
+ this.dragX.set(Math.min(0, dx));
18201
+ }
18202
+ onTouchEnd() {
18203
+ if (!this.touch)
18204
+ return;
18205
+ const width = this.drawer()?.nativeElement.offsetWidth ?? 280;
18206
+ const dx = this.dragX();
18207
+ this.touch = null;
18208
+ this.dragX.set(0);
18209
+ if (dx < -Math.min(72, width / 5))
18210
+ this.store.closeMobile();
18211
+ }
18122
18212
  onEscape() {
18123
18213
  if (this.mobileOpen())
18124
18214
  this.store.closeMobile();
@@ -18150,11 +18240,11 @@ class SdsSidebar {
18150
18240
  return Array.from(root.querySelectorAll(sel)).filter((el) => el.offsetParent !== null || el === this.document.activeElement);
18151
18241
  }
18152
18242
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
18153
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSidebar, isStandalone: true, selector: "sds-sidebar", inputs: { collapseLabel: { classPropertyName: "collapseLabel", publicName: "collapseLabel", isSignal: true, isRequired: false, transformFunction: null }, expandLabel: { classPropertyName: "expandLabel", publicName: "expandLabel", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, decorations: { classPropertyName: "decorations", publicName: "decorations", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown.escape": "onEscape()" }, classAttribute: "block h-full" }, viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true, isSignal: true }, { propertyName: "closeBtn", first: true, predicate: ["closeBtn"], descendants: true, isSignal: true }], ngImport: i0, template: `
18243
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSidebar, isStandalone: true, selector: "sds-sidebar", inputs: { collapseLabel: { classPropertyName: "collapseLabel", publicName: "collapseLabel", isSignal: true, isRequired: false, transformFunction: null }, expandLabel: { classPropertyName: "expandLabel", publicName: "expandLabel", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, navLabel: { classPropertyName: "navLabel", publicName: "navLabel", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, accordion: { classPropertyName: "accordion", publicName: "accordion", isSignal: true, isRequired: false, transformFunction: null }, decorations: { classPropertyName: "decorations", publicName: "decorations", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown.escape": "onEscape()" }, classAttribute: "block h-full" }, providers: [{ provide: SDS_SIDEBAR, useExisting: forwardRef(() => SdsSidebar) }], viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true, isSignal: true }, { propertyName: "closeBtn", first: true, predicate: ["closeBtn"], descendants: true, isSignal: true }], ngImport: i0, template: `
18154
18244
  <!-- Mobile backdrop (only when the drawer is open, < lg) -->
18155
18245
  @if (mobileOpen()) {
18156
18246
  <div
18157
- class="fixed inset-0 z-40 bg-black/50 lg:hidden"
18247
+ class="sds-scrim fixed inset-0 z-40 bg-black/45 backdrop-blur-[2px] lg:hidden"
18158
18248
  (click)="store.closeMobile()"
18159
18249
  aria-hidden="true"
18160
18250
  ></div>
@@ -18167,13 +18257,19 @@ class SdsSidebar {
18167
18257
  -->
18168
18258
  <aside
18169
18259
  #drawer
18170
- class="fixed inset-y-0 left-0 z-50 flex h-full w-[var(--sds-sidebar-w)] flex-col transition-transform duration-200 ease-out lg:relative lg:z-auto lg:w-[var(--sds-rail)] lg:translate-x-0 lg:transition-[width]"
18260
+ class="fixed inset-y-0 left-0 z-50 flex h-full w-[min(var(--sds-sidebar-w),86vw)] flex-col transition-transform duration-200 ease-out motion-reduce:transition-none lg:relative lg:z-auto lg:w-[var(--sds-rail)] lg:translate-none lg:transition-[width]"
18171
18261
  [class]="asideClass()"
18172
18262
  [style.--sds-rail]="railWidth()"
18263
+ [style.transform]="dragX() ? 'translateX(' + dragX() + 'px)' : null"
18264
+ [style.transition]="dragX() ? 'none' : null"
18173
18265
  [inert]="belowLg() && !mobileOpen()"
18174
18266
  [attr.role]="belowLg() && mobileOpen() ? 'dialog' : null"
18175
18267
  [attr.aria-modal]="belowLg() && mobileOpen() ? 'true' : null"
18176
18268
  (keydown)="onDrawerKeydown($event)"
18269
+ (touchstart)="onTouchStart($event)"
18270
+ (touchmove)="onTouchMove($event)"
18271
+ (touchend)="onTouchEnd()"
18272
+ (touchcancel)="onTouchEnd()"
18177
18273
  >
18178
18274
  @if (decorations()) {
18179
18275
  <span class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
@@ -18182,29 +18278,32 @@ class SdsSidebar {
18182
18278
  </span>
18183
18279
  }
18184
18280
 
18185
- <!-- Header: collapse toggle (desktop) / close (mobile) -->
18281
+ <!-- Header: optional [sdsHeader] (logo) + collapse toggle (desktop) / close (mobile) -->
18186
18282
  <div
18187
- class="relative flex items-center px-3"
18188
- style="height: 52px"
18189
- [class]="collapsed() ? 'justify-end lg:justify-center' : 'justify-end'"
18283
+ class="relative flex shrink-0 items-center gap-2 px-3"
18284
+ style="height: 56px"
18285
+ [class]="railCollapsed() ? 'justify-center' : 'justify-between'"
18190
18286
  >
18287
+ <div class="flex min-w-0 flex-1 items-center pl-1" [class.hidden]="railCollapsed()">
18288
+ <ng-content select="[sdsHeader]" />
18289
+ </div>
18191
18290
  <button
18192
18291
  type="button"
18193
- class="hidden h-9 w-9 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:inline-flex"
18292
+ class="hidden h-9 w-9 shrink-0 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:inline-flex"
18194
18293
  [class]="railBtnClass()"
18195
- [attr.aria-label]="collapsed() ? expandLabel() : collapseLabel()"
18196
- [attr.aria-expanded]="!collapsed()"
18294
+ [attr.aria-label]="railCollapsed() ? expandLabel() : collapseLabel()"
18295
+ [attr.aria-expanded]="!railCollapsed()"
18197
18296
  (click)="store.toggle()"
18198
18297
  >
18199
18298
  <sds-icon
18200
- [name]="collapsed() ? 'panel-left-open' : 'panel-left-close'"
18299
+ [name]="railCollapsed() ? 'panel-left-open' : 'panel-left-close'"
18201
18300
  [size]="20"
18202
18301
  />
18203
18302
  </button>
18204
18303
  <button
18205
18304
  #closeBtn
18206
18305
  type="button"
18207
- class="inline-flex h-9 w-9 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:hidden"
18306
+ class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:hidden"
18208
18307
  [class]="railBtnClass()"
18209
18308
  [attr.aria-label]="closeLabel()"
18210
18309
  (click)="store.closeMobile()"
@@ -18214,32 +18313,27 @@ class SdsSidebar {
18214
18313
  </div>
18215
18314
 
18216
18315
  <!-- Navigation -->
18217
- <nav class="relative flex-1 overflow-y-auto overflow-x-hidden px-3 pb-4">
18316
+ <nav class="relative flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-3 pb-4" [attr.aria-label]="navLabel() || null">
18218
18317
  <ng-content />
18219
18318
  </nav>
18220
18319
 
18221
18320
  <!-- Footer (search, version, …) -->
18222
- <div class="relative px-3 pb-4 pt-2">
18321
+ <div class="relative px-3 pt-2 pb-[max(1rem,env(safe-area-inset-bottom))]">
18223
18322
  <ng-content select="[sdsFooter]" />
18224
18323
  </div>
18225
18324
  </aside>
18226
- `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18325
+ `, isInline: true, styles: ["@keyframes sds-scrim-in{0%{opacity:0}}.sds-scrim{animation:sds-scrim-in .18s ease-out}@media(prefers-reduced-motion:reduce){.sds-scrim{animation:none}}\n"], dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18227
18326
  }
18228
18327
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSidebar, decorators: [{
18229
18328
  type: Component,
18230
- args: [{
18231
- selector: 'sds-sidebar',
18232
- changeDetection: ChangeDetectionStrategy.OnPush,
18233
- imports: [SdsIcon],
18234
- host: {
18329
+ args: [{ selector: 'sds-sidebar', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdsIcon], providers: [{ provide: SDS_SIDEBAR, useExisting: forwardRef(() => SdsSidebar) }], host: {
18235
18330
  class: 'block h-full',
18236
18331
  '(document:keydown.escape)': 'onEscape()',
18237
- },
18238
- template: `
18332
+ }, template: `
18239
18333
  <!-- Mobile backdrop (only when the drawer is open, < lg) -->
18240
18334
  @if (mobileOpen()) {
18241
18335
  <div
18242
- class="fixed inset-0 z-40 bg-black/50 lg:hidden"
18336
+ class="sds-scrim fixed inset-0 z-40 bg-black/45 backdrop-blur-[2px] lg:hidden"
18243
18337
  (click)="store.closeMobile()"
18244
18338
  aria-hidden="true"
18245
18339
  ></div>
@@ -18252,13 +18346,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18252
18346
  -->
18253
18347
  <aside
18254
18348
  #drawer
18255
- class="fixed inset-y-0 left-0 z-50 flex h-full w-[var(--sds-sidebar-w)] flex-col transition-transform duration-200 ease-out lg:relative lg:z-auto lg:w-[var(--sds-rail)] lg:translate-x-0 lg:transition-[width]"
18349
+ class="fixed inset-y-0 left-0 z-50 flex h-full w-[min(var(--sds-sidebar-w),86vw)] flex-col transition-transform duration-200 ease-out motion-reduce:transition-none lg:relative lg:z-auto lg:w-[var(--sds-rail)] lg:translate-none lg:transition-[width]"
18256
18350
  [class]="asideClass()"
18257
18351
  [style.--sds-rail]="railWidth()"
18352
+ [style.transform]="dragX() ? 'translateX(' + dragX() + 'px)' : null"
18353
+ [style.transition]="dragX() ? 'none' : null"
18258
18354
  [inert]="belowLg() && !mobileOpen()"
18259
18355
  [attr.role]="belowLg() && mobileOpen() ? 'dialog' : null"
18260
18356
  [attr.aria-modal]="belowLg() && mobileOpen() ? 'true' : null"
18261
18357
  (keydown)="onDrawerKeydown($event)"
18358
+ (touchstart)="onTouchStart($event)"
18359
+ (touchmove)="onTouchMove($event)"
18360
+ (touchend)="onTouchEnd()"
18361
+ (touchcancel)="onTouchEnd()"
18262
18362
  >
18263
18363
  @if (decorations()) {
18264
18364
  <span class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
@@ -18267,29 +18367,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18267
18367
  </span>
18268
18368
  }
18269
18369
 
18270
- <!-- Header: collapse toggle (desktop) / close (mobile) -->
18370
+ <!-- Header: optional [sdsHeader] (logo) + collapse toggle (desktop) / close (mobile) -->
18271
18371
  <div
18272
- class="relative flex items-center px-3"
18273
- style="height: 52px"
18274
- [class]="collapsed() ? 'justify-end lg:justify-center' : 'justify-end'"
18372
+ class="relative flex shrink-0 items-center gap-2 px-3"
18373
+ style="height: 56px"
18374
+ [class]="railCollapsed() ? 'justify-center' : 'justify-between'"
18275
18375
  >
18376
+ <div class="flex min-w-0 flex-1 items-center pl-1" [class.hidden]="railCollapsed()">
18377
+ <ng-content select="[sdsHeader]" />
18378
+ </div>
18276
18379
  <button
18277
18380
  type="button"
18278
- class="hidden h-9 w-9 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:inline-flex"
18381
+ class="hidden h-9 w-9 shrink-0 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:inline-flex"
18279
18382
  [class]="railBtnClass()"
18280
- [attr.aria-label]="collapsed() ? expandLabel() : collapseLabel()"
18281
- [attr.aria-expanded]="!collapsed()"
18383
+ [attr.aria-label]="railCollapsed() ? expandLabel() : collapseLabel()"
18384
+ [attr.aria-expanded]="!railCollapsed()"
18282
18385
  (click)="store.toggle()"
18283
18386
  >
18284
18387
  <sds-icon
18285
- [name]="collapsed() ? 'panel-left-open' : 'panel-left-close'"
18388
+ [name]="railCollapsed() ? 'panel-left-open' : 'panel-left-close'"
18286
18389
  [size]="20"
18287
18390
  />
18288
18391
  </button>
18289
18392
  <button
18290
18393
  #closeBtn
18291
18394
  type="button"
18292
- class="inline-flex h-9 w-9 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:hidden"
18395
+ class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:hidden"
18293
18396
  [class]="railBtnClass()"
18294
18397
  [attr.aria-label]="closeLabel()"
18295
18398
  (click)="store.closeMobile()"
@@ -18299,49 +18402,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18299
18402
  </div>
18300
18403
 
18301
18404
  <!-- Navigation -->
18302
- <nav class="relative flex-1 overflow-y-auto overflow-x-hidden px-3 pb-4">
18405
+ <nav class="relative flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-3 pb-4" [attr.aria-label]="navLabel() || null">
18303
18406
  <ng-content />
18304
18407
  </nav>
18305
18408
 
18306
18409
  <!-- Footer (search, version, …) -->
18307
- <div class="relative px-3 pb-4 pt-2">
18410
+ <div class="relative px-3 pt-2 pb-[max(1rem,env(safe-area-inset-bottom))]">
18308
18411
  <ng-content select="[sdsFooter]" />
18309
18412
  </div>
18310
18413
  </aside>
18311
- `,
18312
- }]
18313
- }], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }], closeBtn: [{ type: i0.ViewChild, args: ['closeBtn', { isSignal: true }] }], collapseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseLabel", required: false }] }], expandLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandLabel", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], decorations: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorations", required: false }] }] } });
18414
+ `, styles: ["@keyframes sds-scrim-in{0%{opacity:0}}.sds-scrim{animation:sds-scrim-in .18s ease-out}@media(prefers-reduced-motion:reduce){.sds-scrim{animation:none}}\n"] }]
18415
+ }], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }], closeBtn: [{ type: i0.ViewChild, args: ['closeBtn', { isSignal: true }] }], collapseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseLabel", required: false }] }], expandLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandLabel", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], navLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "navLabel", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], accordion: [{ type: i0.Input, args: [{ isSignal: true, alias: "accordion", required: false }] }], decorations: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorations", required: false }] }] } });
18314
18416
 
18417
+ let nextId$1 = 0;
18315
18418
  /**
18316
- * Groups nav items under an optional uppercase section label.
18419
+ * Groups nav items under an optional section label.
18317
18420
  *
18318
18421
  * Two modes:
18319
- * - **Section** (default) — a static label over its items. The label is replaced by a divider when
18320
- * the rail is collapsed.
18321
- * - **Collapsible** (`[collapsible]`) — the label becomes a button that expands and collapses the
18322
- * group. Set `[basePath]` and the group opens itself whenever the active route is inside it, so a
18323
- * deep link or a browser Back lands with the right section already open instead of the person
18324
- * having to find and re-open it. A group whose pages live under several prefixes (a business area
18325
- * grouping `/people`, `/customers`, `/drivers`) lists them all in `[basePaths]`.
18422
+ * - **Section** (default) — a static uppercase caption over its items. On the collapsed rail the caption
18423
+ * becomes a divider and the items (which carry their own icons) stay visible.
18424
+ * - **Collapsible** (`[collapsible]`) — the label becomes a row-height header that opens and closes the
18425
+ * group with a short height animation. Its items render as nested sub-destinations on a tree guide
18426
+ * line, the active one marked on the line itself. Set `[basePath]` (or several `[basePaths]`) and the
18427
+ * group opens itself whenever the active route is inside it, so a deep link or a browser Back lands
18428
+ * with the right section already open; while closed it still shows that it holds the current page
18429
+ * (a highlighted header with a dot).
18430
+ *
18431
+ * On the collapsed desktop rail a collapsible group is its icon alone, and its pages open in a FLYOUT
18432
+ * panel beside the rail — on hover, on click/tap (which pins it), or from the keyboard (Enter, Space or
18433
+ * → opens it and focuses the first page; ↑/↓ move, Esc or ← closes back to the header). So every
18434
+ * destination stays reachable on the icon rail, without navigating anywhere just to see what a section
18435
+ * holds.
18326
18436
  *
18327
18437
  * A collapsible group can carry a `[badge]` of its own — typically the sum of its children's. It is
18328
18438
  * drawn on the header only while the children are hidden, so a closed section still says that
18329
18439
  * something inside it is waiting, and an open one does not show the same count twice.
18330
18440
  *
18331
- * Collapsible mode exists because a flat rail stops scaling somewhere around a dozen destinations,
18332
- * and the alternative — showing every child of every section at once — buries the ones in use.
18333
- *
18334
- * On the collapsed desktop rail there is nowhere to put a header, so a collapsible group renders
18335
- * exactly like a section: divider, children visible. Hiding them there would leave the rail with
18336
- * unreachable destinations and no affordance to reveal them.
18441
+ * Inside the mobile drawer the rail is always full width, whatever the desktop collapse preference.
18337
18442
  */
18338
18443
  class SdsNavGroup {
18339
18444
  store = inject(SidebarStore);
18340
18445
  router = inject(Router);
18446
+ document = inject(DOCUMENT);
18447
+ host = inject(ElementRef);
18448
+ injector = inject(Injector);
18449
+ id = ++nextId$1;
18450
+ headerId = `sds-nav-group-${this.id}-header`;
18451
+ panelId = `sds-nav-group-${this.id}-panel`;
18452
+ header = viewChild('header', ...(ngDevMode ? [{ debugName: "header" }] : []));
18453
+ panel = viewChild('panel', ...(ngDevMode ? [{ debugName: "panel" }] : []));
18341
18454
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
18342
18455
  /** Turn the label into an expand/collapse control. */
18343
18456
  collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
18344
- /** Icon for a collapsible header, so it reads at the same weight as the items it sits among. */
18457
+ /** Start open (until the person toggles it), even when the route is not inside the group. */
18458
+ defaultOpen = input(false, ...(ngDevMode ? [{ debugName: "defaultOpen", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
18459
+ /** Icon for a collapsible header — and the group's only representation on the collapsed rail. */
18345
18460
  icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
18346
18461
  /**
18347
18462
  * URL prefix owned by this group, e.g. `/payments`. While the active route starts with it the
@@ -18364,7 +18479,10 @@ class SdsNavGroup {
18364
18479
  badgeMax = input(99, ...(ngDevMode ? [{ debugName: "badgeMax" }] : []));
18365
18480
  /** Badge colour, as on {@link SdsNavItem}. */
18366
18481
  badgeTone = input('brand', ...(ngDevMode ? [{ debugName: "badgeTone" }] : []));
18367
- /** Emits the new state on every manual toggle. Not emitted for route-driven auto-expansion. */
18482
+ /**
18483
+ * Emits the new state on every manual inline toggle. Not emitted for route-driven auto-expansion, nor
18484
+ * for the collapsed rail's flyout (which shows the pages instead — no need to navigate to see them).
18485
+ */
18368
18486
  expandedChange = output();
18369
18487
  /** Explicit user choice; null means "follow the route". */
18370
18488
  manual = signal(null, ...(ngDevMode ? [{ debugName: "manual" }] : []));
@@ -18381,8 +18499,65 @@ class SdsNavGroup {
18381
18499
  const path = this.url().split(/[?#]/)[0];
18382
18500
  return bases.some((base) => path === base || path.startsWith(base + '/'));
18383
18501
  }, ...(ngDevMode ? [{ debugName: "routeActive" }] : []));
18384
- expanded = computed(() => this.manual() ?? this.routeActive(), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
18385
- collapsed = this.store.collapsed;
18502
+ expanded = computed(() => this.manual() ?? (this.routeActive() || this.defaultOpen()), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
18503
+ /** Icon-only desktop rail right now (never inside the mobile drawer). */
18504
+ railCollapsed = this.store.railCollapsed;
18505
+ // --- Flyout (collapsed rail) ------------------------------------------------------------------
18506
+ flyoutOpen = signal(false, ...(ngDevMode ? [{ debugName: "flyoutOpen" }] : []));
18507
+ /** Opened by click / keyboard: stays open when the pointer leaves, until a choice, Esc or outside click. */
18508
+ pinned = false;
18509
+ pos = signal({ top: 0, left: 0, maxHeight: 480 }, ...(ngDevMode ? [{ debugName: "pos" }] : []));
18510
+ openTimer;
18511
+ closeTimer;
18512
+ flyoutShown = computed(() => this.collapsible() && this.railCollapsed() && this.flyoutOpen(), ...(ngDevMode ? [{ debugName: "flyoutShown" }] : []));
18513
+ // --- Context for projected items -------------------------------------------------------------
18514
+ nested = computed(() => this.collapsible(), ...(ngDevMode ? [{ debugName: "nested" }] : []));
18515
+ inFlyout = computed(() => this.collapsible() && this.railCollapsed(), ...(ngDevMode ? [{ debugName: "inFlyout" }] : []));
18516
+ constructor() {
18517
+ // Accordion: another group was opened by hand — fold this one.
18518
+ effect(() => {
18519
+ const opened = this.store.openedGroup();
18520
+ if (!this.store.accordion() || opened === null || opened === this.id)
18521
+ return;
18522
+ untracked(() => {
18523
+ if (this.expanded())
18524
+ this.manual.set(false);
18525
+ });
18526
+ });
18527
+ // The flyout belongs to the collapsed rail only.
18528
+ effect(() => {
18529
+ if (!this.railCollapsed())
18530
+ untracked(() => this.closeFlyout());
18531
+ });
18532
+ // While the flyout is open: an outside press, a resize or a scroll of anything but the panel closes
18533
+ // it (a fixed panel would otherwise float away from its header).
18534
+ effect((onCleanup) => {
18535
+ if (!this.flyoutOpen())
18536
+ return;
18537
+ const win = this.document.defaultView;
18538
+ if (!win)
18539
+ return;
18540
+ const inside = (t) => t instanceof Node && this.host.nativeElement.contains(t);
18541
+ const onDown = (e) => {
18542
+ if (!inside(e.target))
18543
+ this.closeFlyout();
18544
+ };
18545
+ const onScroll = (e) => {
18546
+ if (!inside(e.target))
18547
+ this.closeFlyout();
18548
+ };
18549
+ const onResize = () => this.closeFlyout();
18550
+ this.document.addEventListener('pointerdown', onDown, true);
18551
+ win.addEventListener('scroll', onScroll, true);
18552
+ win.addEventListener('resize', onResize);
18553
+ onCleanup(() => {
18554
+ this.document.removeEventListener('pointerdown', onDown, true);
18555
+ win.removeEventListener('scroll', onScroll, true);
18556
+ win.removeEventListener('resize', onResize);
18557
+ });
18558
+ });
18559
+ inject(DestroyRef).onDestroy(() => this.clearTimers());
18560
+ }
18386
18561
  /** The header badge's spoken text — only while the children (which show their own) are hidden. */
18387
18562
  headerBadgeText = computed(() => {
18388
18563
  const n = this.badge();
@@ -18396,119 +18571,283 @@ class SdsNavGroup {
18396
18571
  const n = this.badge() ?? 0;
18397
18572
  return n > this.badgeMax() ? `${this.badgeMax()}+` : String(n);
18398
18573
  }, ...(ngDevMode ? [{ debugName: "badgeDisplay" }] : []));
18399
- /** The same pill as {@link SdsNavItem}'s, pinned to the icon's corner on the collapsed rail. */
18400
- badgeClass = computed(() => {
18401
- const tone = this.badgeTone() === 'danger'
18574
+ /** Accessible name of the icon-only header: "Money, 3 pending". */
18575
+ collapsedName = computed(() => {
18576
+ const text = this.headerBadgeText();
18577
+ return text ? `${this.label()}, ${text}` : this.label();
18578
+ }, ...(ngDevMode ? [{ debugName: "collapsedName" }] : []));
18579
+ /** Rail paint, shared via the store — see SidebarStore.appearance. Drives the header/caption tones. */
18580
+ rail = inject(SDS_SIDEBAR, { optional: true });
18581
+ surface = computed(() => (this.rail?.appearance() ?? this.store.appearance()) === 'surface', ...(ngDevMode ? [{ debugName: "surface" }] : []));
18582
+ badgeTone$() {
18583
+ return this.badgeTone() === 'danger'
18402
18584
  ? 'bg-danger text-white'
18403
18585
  : this.badgeTone() === 'neutral'
18404
18586
  ? 'bg-muted text-ink-soft border border-border'
18405
- : this.store.appearance() === 'surface'
18587
+ : this.surface()
18406
18588
  ? `bg-brand-600 text-white ${DARK_PRIMARY_BG} ${DARK_ON_PRIMARY}`
18407
18589
  : 'bg-white text-brand-800';
18408
- const base = 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 ' +
18409
- 'text-[11px] font-bold tabular-nums leading-none';
18410
- const collapsed = this.collapsed()
18411
- ? ' lg:absolute lg:right-1.5 lg:top-0.5 lg:h-4 lg:min-w-4 lg:px-1 lg:text-[10px]' +
18412
- (this.store.appearance() === 'surface' ? ' lg:ring-2 lg:ring-surface' : '')
18413
- : '';
18414
- return `${base} ${tone}${collapsed}`;
18415
- }, ...(ngDevMode ? [{ debugName: "badgeClass" }] : []));
18416
- /** Rail paint, shared via the store — see SidebarStore.appearance. Drives the header/caption tones. */
18417
- surface = computed(() => this.store.appearance() === 'surface', ...(ngDevMode ? [{ debugName: "surface" }] : []));
18590
+ }
18591
+ badgeClass = computed(() => 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 ' +
18592
+ 'text-[11px] font-bold tabular-nums leading-none ' +
18593
+ this.badgeTone$(), ...(ngDevMode ? [{ debugName: "badgeClass" }] : []));
18594
+ /** The badge as a small pill on the icon's corner (collapsed rail). */
18595
+ pipClass = computed(() => 'absolute right-1 top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 ' +
18596
+ 'text-[10px] font-bold tabular-nums leading-none ' +
18597
+ this.badgeTone$() +
18598
+ (this.surface() ? ' ring-2 ring-surface' : ' ring-2 ring-brand-900'), ...(ngDevMode ? [{ debugName: "pipClass" }] : []));
18599
+ activeDotClass = computed(() => 'size-1.5 shrink-0 rounded-full ' + (this.surface() ? 'bg-brand-500' : 'bg-white'), ...(ngDevMode ? [{ debugName: "activeDotClass" }] : []));
18600
+ wrapClass = computed(() => (this.collapsible() ? 'mt-1 first:mt-2' : 'mt-5 first:mt-3'), ...(ngDevMode ? [{ debugName: "wrapClass" }] : []));
18418
18601
  /** Collapsible header, painted for the active rail (ink+brand on `surface`, white on `brand`). */
18419
18602
  headerClass = computed(() => {
18420
18603
  const s = this.surface();
18421
- const base = 'relative mb-1 flex h-10 w-full items-center gap-3 rounded-control px-3 text-sm font-medium ' +
18422
- 'transition-colors focus-visible:outline-none focus-visible:ring-2 ' +
18423
- (this.collapsed() ? 'lg:justify-center ' : '') +
18424
- (s
18425
- ? 'text-ink-soft hover:bg-muted hover:text-ink focus-visible:ring-brand-500/40'
18426
- : 'text-white/70 hover:bg-white/8 hover:text-white focus-visible:ring-white/40');
18427
- // text-ink (not brand-700) stays readable in dark, where the grayscale brand-700 is near-black.
18428
- const active = this.routeActive() ? (s ? ' text-ink' : ' text-white') : '';
18429
- return base + active;
18604
+ const rail = this.railCollapsed();
18605
+ const base = 'group/header relative flex w-full items-center gap-3 rounded-control text-left text-sm font-medium ' +
18606
+ 'transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 ' +
18607
+ (rail ? 'h-10 justify-center ' : 'h-11 px-3 lg:h-10 ') +
18608
+ (s ? 'focus-visible:ring-brand-500 ' : 'focus-visible:ring-white/50 ');
18609
+ let state;
18610
+ if (rail && this.routeActive()) {
18611
+ // The icon IS the group here: wear the active item's pill when the current page is inside.
18612
+ state = s
18613
+ ? 'bg-linear-to-r from-brand-500/15 to-brand-500/5 text-ink shadow-sm'
18614
+ : 'bg-brand-500 text-white shadow-sm';
18615
+ }
18616
+ else if (rail && this.flyoutOpen()) {
18617
+ state = s ? 'bg-muted text-ink' : 'bg-white/10 text-white';
18618
+ }
18619
+ else if (this.routeActive() && !this.expanded()) {
18620
+ state = s ? 'bg-brand-500/8 text-ink hover:bg-brand-500/12' : 'bg-white/8 text-white hover:bg-white/12';
18621
+ }
18622
+ else if (this.expanded()) {
18623
+ state = s ? 'text-ink hover:bg-brand-500/8' : 'text-white hover:bg-white/8';
18624
+ }
18625
+ else {
18626
+ state = s ? 'text-ink-soft hover:bg-brand-500/8 hover:text-ink' : 'text-white/75 hover:bg-white/8 hover:text-white';
18627
+ }
18628
+ return base + state;
18430
18629
  }, ...(ngDevMode ? [{ debugName: "headerClass" }] : []));
18431
- chevronClass = computed(() => 'shrink-0 transition-transform ' + (this.surface() ? 'text-ink-faint' : 'text-white/50'), ...(ngDevMode ? [{ debugName: "chevronClass" }] : []));
18630
+ chevronClass = computed(() => 'shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none ' +
18631
+ (this.expanded() ? 'rotate-90 ' : '') +
18632
+ (this.surface() ? 'text-ink-faint group-hover/header:text-ink-soft' : 'text-white/45 group-hover/header:text-white/80'), ...(ngDevMode ? [{ debugName: "chevronClass" }] : []));
18432
18633
  dividerClass = computed(() => 'mx-3 mb-2 mt-2 h-px ' + (this.surface() ? 'bg-border' : 'bg-white/10'), ...(ngDevMode ? [{ debugName: "dividerClass" }] : []));
18433
18634
  captionClass = computed(() => 'mb-1.5 px-3 text-[11px] font-semibold uppercase tracking-wider ' +
18434
- (this.surface() ? 'text-ink-faint' : 'text-white/60'), ...(ngDevMode ? [{ debugName: "captionClass" }] : []));
18635
+ (this.surface() ? 'text-ink-faint' : 'text-white/55'), ...(ngDevMode ? [{ debugName: "captionClass" }] : []));
18636
+ flyoutTitleClass = computed(() => 'px-3 pb-1.5 pt-1 text-[11px] font-semibold uppercase tracking-wider ' +
18637
+ (this.surface() ? 'text-ink-faint' : 'text-white/55'), ...(ngDevMode ? [{ debugName: "flyoutTitleClass" }] : []));
18435
18638
  /**
18436
- * A plain section always shows its items — they carry their own icons, so they survive the collapsed
18437
- * rail. A collapsible group hides its children there instead: its children are sub-destinations and
18438
- * typically have no icon, which on an icon-only rail is a row with nothing in it.
18639
+ * Whether the items are on screen: a plain section always; a collapsible group when open on the full
18640
+ * rail (its items have no icon of their own, so on the icon rail they live in the flyout instead).
18439
18641
  */
18440
- childrenVisible = computed(() => !this.collapsible() || (!this.collapsed() && this.expanded()), ...(ngDevMode ? [{ debugName: "childrenVisible" }] : []));
18441
- /**
18442
- * One binding rather than `[class]` alongside `[class.hidden]` — mixing a whole-list binding with a
18443
- * single-class toggle on the same element depends on Angular's styling precedence to resolve, which
18444
- * is a subtlety no template should rest on.
18445
- *
18446
- * The indent rule appears only on an expanded collapsible group on the full-width rail: it is what
18447
- * shows the children belong to the header above them.
18448
- */
18449
- childrenClass = computed(() => {
18450
- const parts = ['flex flex-col gap-1'];
18451
- if (!this.childrenVisible()) {
18452
- parts.push('hidden');
18453
- }
18454
- if (this.collapsible() && !this.collapsed()) {
18455
- parts.push(this.surface() ? 'ml-5 border-l border-border pl-3' : 'ml-5 border-l border-white/10 pl-3');
18456
- }
18457
- return parts.join(' ');
18458
- }, ...(ngDevMode ? [{ debugName: "childrenClass" }] : []));
18642
+ childrenVisible = computed(() => !this.collapsible() || (!this.railCollapsed() && this.expanded()), ...(ngDevMode ? [{ debugName: "childrenVisible" }] : []));
18643
+ /** Closed panels are `inert`, so their (visually hidden) links leave the tab order. */
18644
+ childrenInteractive = computed(() => this.childrenVisible() || this.flyoutShown(), ...(ngDevMode ? [{ debugName: "childrenInteractive" }] : []));
18645
+ panelClass = computed(() => {
18646
+ if (!this.collapsible())
18647
+ return 'block';
18648
+ if (this.railCollapsed()) {
18649
+ if (!this.flyoutOpen())
18650
+ return 'hidden';
18651
+ return ('sds-flyout fixed z-[70] flex w-60 flex-col rounded-card border p-1.5 shadow-xl ' +
18652
+ (this.surface() ? 'border-border bg-surface text-ink' : 'border-white/10 bg-brand-900 text-white ring-1 ring-black/10'));
18653
+ }
18654
+ // grid-rows 0fr → 1fr animates to the content's real height with no measuring.
18655
+ return ('grid transition-[grid-template-rows,opacity] duration-200 ease-out motion-reduce:transition-none ' +
18656
+ (this.expanded() ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'));
18657
+ }, ...(ngDevMode ? [{ debugName: "panelClass" }] : []));
18658
+ innerClass = computed(() => {
18659
+ if (!this.collapsible())
18660
+ return '';
18661
+ return this.railCollapsed() ? 'min-h-0 overflow-y-auto overscroll-contain' : 'min-h-0 overflow-hidden';
18662
+ }, ...(ngDevMode ? [{ debugName: "innerClass" }] : []));
18459
18663
  /**
18460
- * On the collapsed rail there is nothing to expand into, so the toggle always reports "opening" —
18461
- * the app's handler takes that as the cue to navigate into the section, which is the only useful
18462
- * meaning the control can have there.
18664
+ * The tree guide: a hairline under the header's icon (22 px = the header's 12 px padding + half its
18665
+ * 20 px icon), with the items indented beside it. The active item marks its own stretch of the line.
18463
18666
  */
18667
+ listClass = computed(() => {
18668
+ if (!this.collapsible())
18669
+ return 'flex flex-col gap-1';
18670
+ if (this.railCollapsed())
18671
+ return 'flex flex-col gap-0.5';
18672
+ return ('relative ml-[1.375rem] mt-0.5 flex flex-col gap-0.5 pb-1.5 pl-3 ' +
18673
+ 'before:pointer-events-none before:absolute before:inset-y-1 before:left-0 before:w-px ' +
18674
+ (this.surface() ? 'before:bg-border' : 'before:bg-white/12'));
18675
+ }, ...(ngDevMode ? [{ debugName: "listClass" }] : []));
18676
+ /** Header click: toggle inline; on the collapsed rail, open (and pin) or close the flyout. */
18464
18677
  toggle() {
18465
- const next = this.collapsed() ? true : !this.expanded();
18678
+ if (this.railCollapsed()) {
18679
+ if (this.flyoutOpen() && this.pinned)
18680
+ this.closeFlyout();
18681
+ else
18682
+ this.openFlyout(false);
18683
+ return;
18684
+ }
18685
+ const next = !this.expanded();
18466
18686
  this.manual.set(next);
18687
+ if (next)
18688
+ this.store.openedGroup.set(this.id);
18467
18689
  this.expandedChange.emit(next);
18468
18690
  }
18691
+ onHeaderKeydown(event) {
18692
+ if (!this.railCollapsed())
18693
+ return;
18694
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowRight' || event.key === 'ArrowDown') {
18695
+ event.preventDefault();
18696
+ this.openFlyout(true);
18697
+ }
18698
+ }
18699
+ onHeaderEnter() {
18700
+ if (!this.railCollapsed())
18701
+ return;
18702
+ this.cancelClose();
18703
+ if (this.flyoutOpen())
18704
+ return;
18705
+ clearTimeout(this.openTimer);
18706
+ // A short intent delay, so sweeping the pointer down the rail doesn't flash every panel.
18707
+ this.openTimer = setTimeout(() => this.openFlyout(false, false), 90);
18708
+ }
18709
+ scheduleClose() {
18710
+ clearTimeout(this.openTimer);
18711
+ if (!this.railCollapsed() || !this.flyoutOpen() || this.pinned)
18712
+ return;
18713
+ clearTimeout(this.closeTimer);
18714
+ this.closeTimer = setTimeout(() => this.closeFlyout(), 220);
18715
+ }
18716
+ cancelClose() {
18717
+ clearTimeout(this.closeTimer);
18718
+ }
18719
+ openFlyout(focusFirst, pin = true) {
18720
+ const btn = this.header()?.nativeElement;
18721
+ const win = this.document.defaultView;
18722
+ if (!btn || !win)
18723
+ return;
18724
+ this.clearTimers();
18725
+ const r = btn.getBoundingClientRect();
18726
+ const margin = 8;
18727
+ this.pinned = this.pinned || pin;
18728
+ // Beside the RAIL, not the button: the panel must clear the rail's edge, not overlap its padding.
18729
+ const railRight = btn.closest('aside')?.getBoundingClientRect().right ?? r.right;
18730
+ this.pos.set({ top: Math.max(margin, r.top - 6), left: railRight + 8, maxHeight: win.innerHeight - margin * 2 });
18731
+ this.flyoutOpen.set(true);
18732
+ afterNextRender(() => {
18733
+ const el = this.panel()?.nativeElement;
18734
+ if (!el)
18735
+ return;
18736
+ // Keep the whole panel on screen: lift it when it would run past the bottom edge.
18737
+ const h = el.offsetHeight;
18738
+ const top = this.pos().top;
18739
+ if (top + h > win.innerHeight - margin)
18740
+ this.pos.update((p) => ({ ...p, top: Math.max(margin, win.innerHeight - margin - h) }));
18741
+ if (focusFirst)
18742
+ this.items()[0]?.focus();
18743
+ }, { injector: this.injector });
18744
+ }
18745
+ /** {@link SdsNavGroupContext} — also called by an item chosen inside the flyout. */
18746
+ closeFlyout(restoreFocus = false) {
18747
+ this.clearTimers();
18748
+ this.pinned = false;
18749
+ if (!this.flyoutOpen())
18750
+ return;
18751
+ this.flyoutOpen.set(false);
18752
+ if (restoreFocus)
18753
+ this.header()?.nativeElement.focus();
18754
+ }
18755
+ onEscape() {
18756
+ if (this.flyoutOpen()) {
18757
+ const within = this.host.nativeElement.contains(this.document.activeElement);
18758
+ this.closeFlyout(within);
18759
+ }
18760
+ }
18761
+ /** Focus leaving the header + flyout altogether closes the flyout (Tab past the last page). */
18762
+ onFocusOut(event) {
18763
+ if (!this.flyoutOpen())
18764
+ return;
18765
+ const next = event.relatedTarget;
18766
+ if (next instanceof Node && this.host.nativeElement.contains(next))
18767
+ return;
18768
+ if (next === null)
18769
+ return; // focus went nowhere in particular (e.g. a click on the page) — the pointerdown handler decides
18770
+ this.closeFlyout();
18771
+ }
18772
+ /** ↑/↓ (and Home/End) move between the flyout's pages; ← closes back to the header. */
18773
+ onPanelKeydown(event) {
18774
+ if (!this.flyoutShown())
18775
+ return;
18776
+ const items = this.items();
18777
+ const i = items.indexOf(this.document.activeElement);
18778
+ let next = -1;
18779
+ switch (event.key) {
18780
+ case 'ArrowDown':
18781
+ next = i < 0 ? 0 : (i + 1) % items.length;
18782
+ break;
18783
+ case 'ArrowUp':
18784
+ next = i < 0 ? items.length - 1 : (i - 1 + items.length) % items.length;
18785
+ break;
18786
+ case 'Home':
18787
+ next = 0;
18788
+ break;
18789
+ case 'End':
18790
+ next = items.length - 1;
18791
+ break;
18792
+ case 'ArrowLeft':
18793
+ event.preventDefault();
18794
+ this.closeFlyout(true);
18795
+ return;
18796
+ default:
18797
+ return;
18798
+ }
18799
+ event.preventDefault();
18800
+ items[next]?.focus();
18801
+ }
18802
+ items() {
18803
+ const el = this.panel()?.nativeElement;
18804
+ return el ? Array.from(el.querySelectorAll('a[href],button:not([disabled])')) : [];
18805
+ }
18806
+ clearTimers() {
18807
+ clearTimeout(this.openTimer);
18808
+ clearTimeout(this.closeTimer);
18809
+ }
18469
18810
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsNavGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
18470
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsNavGroup, isStandalone: true, selector: "sds-nav-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, basePaths: { classPropertyName: "basePaths", publicName: "basePaths", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, badgeLabel: { classPropertyName: "badgeLabel", publicName: "badgeLabel", isSignal: true, isRequired: false, transformFunction: null }, badgeMax: { classPropertyName: "badgeMax", publicName: "badgeMax", isSignal: true, isRequired: false, transformFunction: null }, badgeTone: { classPropertyName: "badgeTone", publicName: "badgeTone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedChange: "expandedChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
18471
- <div class="mt-5 first:mt-3">
18811
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsNavGroup, isStandalone: true, selector: "sds-nav-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, basePaths: { classPropertyName: "basePaths", publicName: "basePaths", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, badgeLabel: { classPropertyName: "badgeLabel", publicName: "badgeLabel", isSignal: true, isRequired: false, transformFunction: null }, badgeMax: { classPropertyName: "badgeMax", publicName: "badgeMax", isSignal: true, isRequired: false, transformFunction: null }, badgeTone: { classPropertyName: "badgeTone", publicName: "badgeTone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedChange: "expandedChange" }, host: { listeners: { "document:keydown.escape": "onEscape()", "focusout": "onFocusOut($event)" }, classAttribute: "block" }, providers: [{ provide: SDS_NAV_GROUP, useExisting: forwardRef(() => SdsNavGroup) }], viewQueries: [{ propertyName: "header", first: true, predicate: ["header"], descendants: true, isSignal: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
18812
+ <div [class]="wrapClass()">
18472
18813
  @if (label()) {
18473
18814
  @if (collapsible()) {
18474
- <!--
18475
- A collapsible header is styled as a NAV ROW, not as a section caption. A small uppercase
18476
- caption says "everything under me belongs to me" — which is a lie the moment the group is
18477
- collapsed and the next section's items sit directly beneath it. At row scale, with an icon
18478
- and a chevron, it reads as the control it is.
18479
-
18480
- It renders on the collapsed rail too, as an icon alone, because there it is the group's
18481
- ONLY representation: the children are hidden (a child with no icon of its own would be a
18482
- blank row), so the app is expected to treat the toggle as "take me into this section" —
18483
- which is what expandedChange already means.
18484
- -->
18485
18815
  <button
18816
+ #header
18486
18817
  type="button"
18818
+ [id]="headerId"
18487
18819
  [class]="headerClass()"
18488
- [attr.aria-expanded]="collapsed() ? null : expanded()"
18489
- [attr.title]="collapsed() ? label() : null"
18490
- [attr.aria-label]="collapsed() ? label() : null"
18820
+ [attr.aria-expanded]="railCollapsed() ? flyoutOpen() : expanded()"
18821
+ [attr.aria-controls]="panelId"
18822
+ [attr.aria-haspopup]="railCollapsed() ? 'true' : null"
18823
+ [attr.aria-label]="railCollapsed() ? collapsedName() : null"
18491
18824
  (click)="toggle()"
18825
+ (keydown)="onHeaderKeydown($event)"
18826
+ (pointerenter)="onHeaderEnter()"
18827
+ (pointerleave)="scheduleClose()"
18492
18828
  >
18493
18829
  @if (icon()) {
18494
18830
  <sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
18495
18831
  }
18496
- <span class="flex-1 truncate text-left" [class.lg:hidden]="collapsed()">
18497
- {{ label() }}
18498
- </span>
18499
- @if (headerBadgeText(); as text) {
18500
- <span [class]="badgeClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18501
- <span class="sr-only">, {{ text }}</span>
18832
+ @if (railCollapsed()) {
18833
+ @if (headerBadgeText()) {
18834
+ <span [class]="pipClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18835
+ } @else if (routeActive()) {
18836
+ <span [class]="activeDotClass() + ' absolute right-2 top-2'" aria-hidden="true"></span>
18837
+ }
18838
+ } @else {
18839
+ <span class="min-w-0 flex-1 truncate text-left">{{ label() }}</span>
18840
+ @if (headerBadgeText(); as text) {
18841
+ <span [class]="badgeClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18842
+ <span class="sr-only">, {{ text }}</span>
18843
+ } @else if (routeActive() && !expanded()) {
18844
+ <!-- Closed, but the current page lives in here: say so without opening it. -->
18845
+ <span [class]="activeDotClass()" aria-hidden="true"></span>
18846
+ }
18847
+ <sds-icon name="chevron-right" [size]="16" [class]="chevronClass()" />
18502
18848
  }
18503
- <sds-icon
18504
- name="chevron-down"
18505
- [size]="16"
18506
- [class]="chevronClass()"
18507
- [class.lg:hidden]="collapsed()"
18508
- [class.rotate-180]="expanded()"
18509
- />
18510
18849
  </button>
18511
- } @else if (collapsed()) {
18850
+ } @else if (railCollapsed()) {
18512
18851
  <div [class]="dividerClass()"></div>
18513
18852
  } @else {
18514
18853
  <p [class]="captionClass()">
@@ -18517,61 +18856,83 @@ class SdsNavGroup {
18517
18856
  }
18518
18857
  }
18519
18858
 
18520
- <div [class]="childrenClass()">
18521
- <ng-content />
18859
+ <!--
18860
+ One panel, three shapes: the always-visible list of a plain section; the height-animated, inert-
18861
+ when-closed list of an open/closed collapsible group; or, on the collapsed rail, the fixed flyout
18862
+ beside it. The projected items are the same DOM nodes in all three — only the classes change.
18863
+ -->
18864
+ <div
18865
+ #panel
18866
+ [id]="panelId"
18867
+ [class]="panelClass()"
18868
+ [style.top.px]="flyoutShown() ? pos().top : null"
18869
+ [style.left.px]="flyoutShown() ? pos().left : null"
18870
+ [style.max-height.px]="flyoutShown() ? pos().maxHeight : null"
18871
+ [attr.role]="collapsible() && railCollapsed() ? 'group' : null"
18872
+ [attr.aria-labelledby]="collapsible() && railCollapsed() ? panelId + '-title' : null"
18873
+ [inert]="!childrenInteractive()"
18874
+ (pointerenter)="cancelClose()"
18875
+ (pointerleave)="scheduleClose()"
18876
+ (keydown)="onPanelKeydown($event)"
18877
+ >
18878
+ @if (collapsible() && railCollapsed()) {
18879
+ <p [id]="panelId + '-title'" [class]="flyoutTitleClass()">{{ label() }}</p>
18880
+ }
18881
+ <div [class]="innerClass()">
18882
+ <div [class]="listClass()">
18883
+ <ng-content />
18884
+ </div>
18885
+ </div>
18522
18886
  </div>
18523
18887
  </div>
18524
- `, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18888
+ `, isInline: true, styles: ["@keyframes sds-flyout-in{0%{opacity:0;transform:translate(-4px) scale(.98)}to{opacity:1;transform:none}}.sds-flyout{animation:sds-flyout-in .14s ease-out;transform-origin:left top}@media(prefers-reduced-motion:reduce){.sds-flyout{animation:none}}\n"], dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18525
18889
  }
18526
18890
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsNavGroup, decorators: [{
18527
18891
  type: Component,
18528
- args: [{
18529
- selector: 'sds-nav-group',
18530
- changeDetection: ChangeDetectionStrategy.OnPush,
18531
- imports: [SdsIcon],
18532
- host: { class: 'block' },
18533
- template: `
18534
- <div class="mt-5 first:mt-3">
18892
+ args: [{ selector: 'sds-nav-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdsIcon], providers: [{ provide: SDS_NAV_GROUP, useExisting: forwardRef(() => SdsNavGroup) }], host: {
18893
+ class: 'block',
18894
+ '(document:keydown.escape)': 'onEscape()',
18895
+ '(focusout)': 'onFocusOut($event)',
18896
+ }, template: `
18897
+ <div [class]="wrapClass()">
18535
18898
  @if (label()) {
18536
18899
  @if (collapsible()) {
18537
- <!--
18538
- A collapsible header is styled as a NAV ROW, not as a section caption. A small uppercase
18539
- caption says "everything under me belongs to me" — which is a lie the moment the group is
18540
- collapsed and the next section's items sit directly beneath it. At row scale, with an icon
18541
- and a chevron, it reads as the control it is.
18542
-
18543
- It renders on the collapsed rail too, as an icon alone, because there it is the group's
18544
- ONLY representation: the children are hidden (a child with no icon of its own would be a
18545
- blank row), so the app is expected to treat the toggle as "take me into this section" —
18546
- which is what expandedChange already means.
18547
- -->
18548
18900
  <button
18901
+ #header
18549
18902
  type="button"
18903
+ [id]="headerId"
18550
18904
  [class]="headerClass()"
18551
- [attr.aria-expanded]="collapsed() ? null : expanded()"
18552
- [attr.title]="collapsed() ? label() : null"
18553
- [attr.aria-label]="collapsed() ? label() : null"
18905
+ [attr.aria-expanded]="railCollapsed() ? flyoutOpen() : expanded()"
18906
+ [attr.aria-controls]="panelId"
18907
+ [attr.aria-haspopup]="railCollapsed() ? 'true' : null"
18908
+ [attr.aria-label]="railCollapsed() ? collapsedName() : null"
18554
18909
  (click)="toggle()"
18910
+ (keydown)="onHeaderKeydown($event)"
18911
+ (pointerenter)="onHeaderEnter()"
18912
+ (pointerleave)="scheduleClose()"
18555
18913
  >
18556
18914
  @if (icon()) {
18557
18915
  <sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
18558
18916
  }
18559
- <span class="flex-1 truncate text-left" [class.lg:hidden]="collapsed()">
18560
- {{ label() }}
18561
- </span>
18562
- @if (headerBadgeText(); as text) {
18563
- <span [class]="badgeClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18564
- <span class="sr-only">, {{ text }}</span>
18917
+ @if (railCollapsed()) {
18918
+ @if (headerBadgeText()) {
18919
+ <span [class]="pipClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18920
+ } @else if (routeActive()) {
18921
+ <span [class]="activeDotClass() + ' absolute right-2 top-2'" aria-hidden="true"></span>
18922
+ }
18923
+ } @else {
18924
+ <span class="min-w-0 flex-1 truncate text-left">{{ label() }}</span>
18925
+ @if (headerBadgeText(); as text) {
18926
+ <span [class]="badgeClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18927
+ <span class="sr-only">, {{ text }}</span>
18928
+ } @else if (routeActive() && !expanded()) {
18929
+ <!-- Closed, but the current page lives in here: say so without opening it. -->
18930
+ <span [class]="activeDotClass()" aria-hidden="true"></span>
18931
+ }
18932
+ <sds-icon name="chevron-right" [size]="16" [class]="chevronClass()" />
18565
18933
  }
18566
- <sds-icon
18567
- name="chevron-down"
18568
- [size]="16"
18569
- [class]="chevronClass()"
18570
- [class.lg:hidden]="collapsed()"
18571
- [class.rotate-180]="expanded()"
18572
- />
18573
18934
  </button>
18574
- } @else if (collapsed()) {
18935
+ } @else if (railCollapsed()) {
18575
18936
  <div [class]="dividerClass()"></div>
18576
18937
  } @else {
18577
18938
  <p [class]="captionClass()">
@@ -18580,18 +18941,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18580
18941
  }
18581
18942
  }
18582
18943
 
18583
- <div [class]="childrenClass()">
18584
- <ng-content />
18944
+ <!--
18945
+ One panel, three shapes: the always-visible list of a plain section; the height-animated, inert-
18946
+ when-closed list of an open/closed collapsible group; or, on the collapsed rail, the fixed flyout
18947
+ beside it. The projected items are the same DOM nodes in all three — only the classes change.
18948
+ -->
18949
+ <div
18950
+ #panel
18951
+ [id]="panelId"
18952
+ [class]="panelClass()"
18953
+ [style.top.px]="flyoutShown() ? pos().top : null"
18954
+ [style.left.px]="flyoutShown() ? pos().left : null"
18955
+ [style.max-height.px]="flyoutShown() ? pos().maxHeight : null"
18956
+ [attr.role]="collapsible() && railCollapsed() ? 'group' : null"
18957
+ [attr.aria-labelledby]="collapsible() && railCollapsed() ? panelId + '-title' : null"
18958
+ [inert]="!childrenInteractive()"
18959
+ (pointerenter)="cancelClose()"
18960
+ (pointerleave)="scheduleClose()"
18961
+ (keydown)="onPanelKeydown($event)"
18962
+ >
18963
+ @if (collapsible() && railCollapsed()) {
18964
+ <p [id]="panelId + '-title'" [class]="flyoutTitleClass()">{{ label() }}</p>
18965
+ }
18966
+ <div [class]="innerClass()">
18967
+ <div [class]="listClass()">
18968
+ <ng-content />
18969
+ </div>
18970
+ </div>
18585
18971
  </div>
18586
18972
  </div>
18587
- `,
18588
- }]
18589
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], basePaths: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePaths", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], badgeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeLabel", required: false }] }], badgeMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeMax", required: false }] }], badgeTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeTone", required: false }] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }] } });
18973
+ `, styles: ["@keyframes sds-flyout-in{0%{opacity:0;transform:translate(-4px) scale(.98)}to{opacity:1;transform:none}}.sds-flyout{animation:sds-flyout-in .14s ease-out;transform-origin:left top}@media(prefers-reduced-motion:reduce){.sds-flyout{animation:none}}\n"] }]
18974
+ }], ctorParameters: () => [], propDecorators: { header: [{ type: i0.ViewChild, args: ['header', { isSignal: true }] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], basePaths: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePaths", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], badgeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeLabel", required: false }] }], badgeMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeMax", required: false }] }], badgeTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeTone", required: false }] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }] } });
18590
18975
 
18591
18976
  /**
18592
18977
  * A single sidebar navigation entry. Renders an anchor when [link] is set
18593
18978
  * (with automatic active state via routerLinkActive) or a button otherwise.
18594
18979
  *
18980
+ * It adapts to where it sits, with no input from the app:
18981
+ * - at the top level, an icon + label row with a pill and accent bar when active;
18982
+ * - inside a collapsible <sds-nav-group>, a nested sub-destination on the group's tree guide (no icon
18983
+ * needed), the active one marked on the guide line;
18984
+ * - inside the collapsed rail's flyout panel, a full row with its label;
18985
+ * - on the collapsed rail itself, its icon alone, with a tooltip naming it on hover and focus.
18986
+ *
18987
+ * Choosing an item closes the mobile drawer and any open flyout.
18988
+ *
18595
18989
  * Requires the host application to provide the Angular Router (`provideRouter`),
18596
18990
  * as it depends on RouterLink/RouterLinkActive even when used as a button.
18597
18991
  *
@@ -18601,6 +18995,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18601
18995
  */
18602
18996
  class SdsNavItem {
18603
18997
  store = inject(SidebarStore);
18998
+ group = inject(SDS_NAV_GROUP, { optional: true });
18604
18999
  icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
18605
19000
  label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
18606
19001
  /** Router link target; when null, the item behaves as a button. */
@@ -18620,7 +19015,17 @@ class SdsNavItem {
18620
19015
  /** Badge colour. `danger` for things that block, `brand` (default) for a neutral count. */
18621
19016
  badgeTone = input('brand', ...(ngDevMode ? [{ debugName: "badgeTone" }] : []));
18622
19017
  select = output();
18623
- collapsed = this.store.collapsed;
19018
+ /** Inside a collapsible group: a nested sub-destination. */
19019
+ nested = computed(() => this.group?.nested() ?? false, ...(ngDevMode ? [{ debugName: "nested" }] : []));
19020
+ /** A row of the collapsed rail's flyout panel. */
19021
+ inFlyout = computed(() => this.group?.inFlyout() ?? false, ...(ngDevMode ? [{ debugName: "inFlyout" }] : []));
19022
+ /** Icon alone: on the collapsed desktop rail, unless it is listed in a flyout. */
19023
+ iconOnly = computed(() => this.store.railCollapsed() && !this.inFlyout(), ...(ngDevMode ? [{ debugName: "iconOnly" }] : []));
19024
+ /** Tooltip anchor (viewport coordinates) while hovered/focused on the icon rail. */
19025
+ tip = signal(null, ...(ngDevMode ? [{ debugName: "tip" }] : []));
19026
+ rail = inject(SDS_SIDEBAR, { optional: true });
19027
+ /** Paint of the rail this item sits in (its own sidebar's, else the store's for a free-standing item). */
19028
+ surface = computed(() => (this.rail?.appearance() ?? this.store.appearance()) === 'surface', ...(ngDevMode ? [{ debugName: "surface" }] : []));
18624
19029
  /** The badge's spoken text, or null when there is nothing to show. */
18625
19030
  badgeText = computed(() => {
18626
19031
  const n = this.badge();
@@ -18642,44 +19047,84 @@ class SdsNavItem {
18642
19047
  ? 'bg-danger text-white'
18643
19048
  : this.badgeTone() === 'neutral'
18644
19049
  ? 'bg-muted text-ink-soft border border-border'
18645
- : this.store.appearance() === 'surface'
19050
+ : this.surface()
18646
19051
  ? `bg-brand-600 text-white ${DARK_PRIMARY_BG} ${DARK_ON_PRIMARY}`
18647
19052
  : 'bg-white text-brand-800';
18648
- const base = 'ml-auto inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 ' +
18649
- 'text-[11px] font-bold tabular-nums leading-none';
18650
- // Collapsed desktop rail: a small pill pinned to the top-right of the centred icon.
18651
- const collapsed = this.collapsed()
18652
- ? ' lg:absolute lg:right-1.5 lg:top-0.5 lg:ml-0 lg:h-4 lg:min-w-4 lg:px-1 lg:text-[10px]' +
18653
- (this.store.appearance() === 'surface' ? ' lg:ring-2 lg:ring-surface' : '')
18654
- : '';
18655
- return `${base} ${tone}${collapsed}`;
19053
+ if (this.iconOnly()) {
19054
+ // A small pill pinned to the top-right of the centred icon.
19055
+ return ('absolute right-1 top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 ' +
19056
+ `text-[10px] font-bold tabular-nums leading-none ${tone} ` +
19057
+ (this.surface() ? 'ring-2 ring-surface' : 'ring-2 ring-brand-900'));
19058
+ }
19059
+ return ('ml-auto inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 ' +
19060
+ `text-[11px] font-bold tabular-nums leading-none ${tone}`);
18656
19061
  }, ...(ngDevMode ? [{ debugName: "badgeClass" }] : []));
18657
19062
  itemClass(isActive) {
18658
- // A `surface` rail is light: an active item is a brand-tinted pill with a left accent bar, brand
18659
- // text and a soft shadow (matching the certicheck reference); inactive is muted ink that lifts on
18660
- // hover. A `brand` rail is dark, so an active item is a solid brand-500 pill in white. Read from the
18661
- // shared store so the whole rail switches from one input on <sds-sidebar>.
18662
- const surface = this.store.appearance() === 'surface';
18663
- const base = 'group relative flex w-full items-center gap-3 rounded-control px-3 text-sm font-medium ' +
19063
+ // `surface` (light rail): an active item is a brand-tinted pill with an accent bar; inactive is muted
19064
+ // ink that lifts on hover. `brand` (dark rail): an active top-level item is a solid brand-500 pill;
19065
+ // a nested one is a quieter white wash, its accent riding the tree guide. One input on <sds-sidebar>
19066
+ // switches the whole rail via the shared store.
19067
+ const s = this.surface();
19068
+ const nested = this.nested() && !this.iconOnly();
19069
+ const size = this.iconOnly()
19070
+ ? 'h-10 justify-center'
19071
+ : nested
19072
+ ? 'h-10 px-3 lg:h-9'
19073
+ : 'h-11 px-3 lg:h-10';
19074
+ const base = 'group relative flex w-full items-center gap-3 rounded-control text-left text-sm font-medium ' +
18664
19075
  'transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 ' +
18665
- (surface ? 'focus-visible:ring-brand-500/40 ' : 'focus-visible:ring-white/40 ') +
18666
- (this.collapsed() ? 'h-10 lg:justify-center' : 'h-10');
18667
- const state = isActive
18668
- ? surface
18669
- ? 'bg-linear-to-r from-brand-500/15 to-brand-500/5 text-ink shadow-sm'
18670
- : 'bg-brand-500 text-white shadow-sm'
18671
- : surface
18672
- ? 'text-ink-soft hover:bg-brand-500/8 hover:text-ink'
18673
- : 'text-white/70 hover:bg-white/8 hover:text-white';
18674
- return `${base} ${state}`;
19076
+ (s ? 'focus-visible:ring-brand-500 ' : 'focus-visible:ring-white/50 ') +
19077
+ size +
19078
+ ' ';
19079
+ let state;
19080
+ if (nested) {
19081
+ // A brand-tinted wash (green on emerald, grey on a monochrome brand) — quieter than the top-level
19082
+ // pill, so the parent header stays the stronger landmark.
19083
+ state = isActive
19084
+ ? s
19085
+ ? 'bg-brand-500/10 text-ink'
19086
+ : 'bg-brand-500/35 text-white'
19087
+ : s
19088
+ ? 'text-ink-soft hover:bg-brand-500/6 hover:text-ink'
19089
+ : 'text-white/65 hover:bg-white/6 hover:text-white';
19090
+ }
19091
+ else {
19092
+ state = isActive
19093
+ ? s
19094
+ ? 'bg-linear-to-r from-brand-500/15 to-brand-500/5 text-ink shadow-sm'
19095
+ : 'bg-brand-500 text-white shadow-sm'
19096
+ : s
19097
+ ? 'text-ink-soft hover:bg-brand-500/8 hover:text-ink'
19098
+ : 'text-white/75 hover:bg-white/8 hover:text-white';
19099
+ }
19100
+ return base + state;
18675
19101
  }
18676
19102
  /**
18677
- * The left accent bar on an active item — the strongest "selected" cue, and the one that survives a
18678
- * grayscale brand where a tinted pill alone is nearly invisible. brand-600 on the light rail, white on
18679
- * the dark one; on a coloured brand it is the brand colour, matching the certicheck accent bar.
19103
+ * The active marker — the strongest "selected" cue, and the one that survives a grayscale brand where a
19104
+ * tinted pill alone is nearly invisible. A top-level (or flyout) item carries it inside its pill; a
19105
+ * nested item draws it ON the group's tree guide (12 px to its left), so the line itself points at the
19106
+ * current page.
18680
19107
  */
18681
- accentBarClass = computed(() => 'pointer-events-none absolute left-1 top-1/2 h-5 w-1 -translate-y-1/2 rounded-full ' +
18682
- (this.store.appearance() === 'surface' ? 'bg-brand-500' : 'bg-white'), ...(ngDevMode ? [{ debugName: "accentBarClass" }] : []));
19108
+ accentBarClass = computed(() => {
19109
+ const color = this.surface() ? 'bg-brand-500' : 'bg-white';
19110
+ const nestedInline = this.nested() && !this.inFlyout() && !this.iconOnly();
19111
+ return nestedInline
19112
+ ? `pointer-events-none absolute -left-3 top-1/2 h-5 w-0.5 -translate-x-1/2 -translate-y-1/2 rounded-full ${color}`
19113
+ : `pointer-events-none absolute left-1 top-1/2 h-5 w-1 -translate-y-1/2 rounded-full ${color}`;
19114
+ }, ...(ngDevMode ? [{ debugName: "accentBarClass" }] : []));
19115
+ showTip(event) {
19116
+ if (!this.iconOnly())
19117
+ return;
19118
+ const el = event.currentTarget;
19119
+ const r = el.getBoundingClientRect();
19120
+ const railRight = el.closest('aside')?.getBoundingClientRect().right ?? r.right;
19121
+ this.tip.set({ top: r.top + r.height / 2, left: railRight + 8 });
19122
+ }
19123
+ chosen() {
19124
+ this.tip.set(null);
19125
+ this.store.closeMobile();
19126
+ this.group?.closeFlyout();
19127
+ }
18683
19128
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
18684
19129
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsNavItem, isStandalone: true, selector: "sds-nav-item", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null }, exact: { classPropertyName: "exact", publicName: "exact", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, badgeLabel: { classPropertyName: "badgeLabel", publicName: "badgeLabel", isSignal: true, isRequired: false, transformFunction: null }, badgeMax: { classPropertyName: "badgeMax", publicName: "badgeMax", isSignal: true, isRequired: false, transformFunction: null }, badgeTone: { classPropertyName: "badgeTone", publicName: "badgeTone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select" }, host: { classAttribute: "block" }, ngImport: i0, template: `
18685
19130
  @if (link() !== null) {
@@ -18689,10 +19134,13 @@ class SdsNavItem {
18689
19134
  #rla="routerLinkActive"
18690
19135
  [routerLinkActiveOptions]="{ exact: exact() }"
18691
19136
  [class]="itemClass(rla.isActive || active())"
18692
- [attr.title]="collapsed() ? collapsedName() : null"
18693
- [attr.aria-label]="collapsed() ? collapsedName() : null"
19137
+ [attr.aria-label]="iconOnly() ? collapsedName() : null"
18694
19138
  [attr.aria-current]="rla.isActive || active() ? 'page' : null"
18695
- (click)="store.closeMobile()"
19139
+ (click)="chosen()"
19140
+ (pointerenter)="showTip($event)"
19141
+ (pointerleave)="tip.set(null)"
19142
+ (focus)="showTip($event)"
19143
+ (blur)="tip.set(null)"
18696
19144
  >
18697
19145
  @if (rla.isActive || active()) {
18698
19146
  <span [class]="accentBarClass()" aria-hidden="true"></span>
@@ -18703,10 +19151,13 @@ class SdsNavItem {
18703
19151
  <button
18704
19152
  type="button"
18705
19153
  [class]="itemClass(active())"
18706
- [attr.title]="collapsed() ? collapsedName() : null"
18707
- [attr.aria-label]="collapsed() ? collapsedName() : null"
19154
+ [attr.aria-label]="iconOnly() ? collapsedName() : null"
18708
19155
  [attr.aria-current]="active() ? 'page' : null"
18709
- (click)="select.emit(); store.closeMobile()"
19156
+ (click)="select.emit(); chosen()"
19157
+ (pointerenter)="showTip($event)"
19158
+ (pointerleave)="tip.set(null)"
19159
+ (focus)="showTip($event)"
19160
+ (blur)="tip.set(null)"
18710
19161
  >
18711
19162
  @if (active()) {
18712
19163
  <span [class]="accentBarClass()" aria-hidden="true"></span>
@@ -18717,15 +19168,32 @@ class SdsNavItem {
18717
19168
 
18718
19169
  <ng-template #content>
18719
19170
  @if (icon()) {
18720
- <sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
19171
+ <sds-icon [name]="icon()!" [size]="nested() && !iconOnly() ? 18 : 20" class="shrink-0" />
19172
+ }
19173
+ @if (!iconOnly()) {
19174
+ <span class="min-w-0 flex-1 truncate">{{ label() }}</span>
18721
19175
  }
18722
- <!-- Label is hidden only on the collapsed desktop rail; always shown in the mobile drawer. -->
18723
- <span class="truncate" [class]="collapsed() ? 'lg:hidden' : ''">{{ label() }}</span>
18724
19176
  @if (badgeText(); as text) {
18725
- <!-- The number is drawn; the words are what is spoken ("Menu, 3 pending"). On the collapsed
18726
- desktop rail it becomes a small corner pill on the icon. -->
19177
+ <!-- The number is drawn; the words are what is spoken ("Menu, 3 pending"). On the icon rail it
19178
+ becomes a small pill on the icon's corner. -->
18727
19179
  <span [class]="badgeClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18728
- <span class="sr-only">, {{ text }}</span>
19180
+ @if (!iconOnly()) {
19181
+ <span class="sr-only">, {{ text }}</span>
19182
+ }
19183
+ }
19184
+ @if (tip(); as t) {
19185
+ <!-- Icon-rail tooltip: fixed, so the rail's scroll container never clips it. -->
19186
+ <span
19187
+ aria-hidden="true"
19188
+ class="pointer-events-none fixed z-[70] -translate-y-1/2 whitespace-nowrap rounded-control bg-ink px-2.5 py-1.5 text-xs font-medium text-surface shadow-lg"
19189
+ [style.top.px]="t.top"
19190
+ [style.left.px]="t.left"
19191
+ >
19192
+ {{ label() }}
19193
+ @if (badgeText()) {
19194
+ <span class="ml-1 opacity-70">· {{ badgeDisplay() }}</span>
19195
+ }
19196
+ </span>
18729
19197
  }
18730
19198
  </ng-template>
18731
19199
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -18745,10 +19213,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18745
19213
  #rla="routerLinkActive"
18746
19214
  [routerLinkActiveOptions]="{ exact: exact() }"
18747
19215
  [class]="itemClass(rla.isActive || active())"
18748
- [attr.title]="collapsed() ? collapsedName() : null"
18749
- [attr.aria-label]="collapsed() ? collapsedName() : null"
19216
+ [attr.aria-label]="iconOnly() ? collapsedName() : null"
18750
19217
  [attr.aria-current]="rla.isActive || active() ? 'page' : null"
18751
- (click)="store.closeMobile()"
19218
+ (click)="chosen()"
19219
+ (pointerenter)="showTip($event)"
19220
+ (pointerleave)="tip.set(null)"
19221
+ (focus)="showTip($event)"
19222
+ (blur)="tip.set(null)"
18752
19223
  >
18753
19224
  @if (rla.isActive || active()) {
18754
19225
  <span [class]="accentBarClass()" aria-hidden="true"></span>
@@ -18759,10 +19230,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18759
19230
  <button
18760
19231
  type="button"
18761
19232
  [class]="itemClass(active())"
18762
- [attr.title]="collapsed() ? collapsedName() : null"
18763
- [attr.aria-label]="collapsed() ? collapsedName() : null"
19233
+ [attr.aria-label]="iconOnly() ? collapsedName() : null"
18764
19234
  [attr.aria-current]="active() ? 'page' : null"
18765
- (click)="select.emit(); store.closeMobile()"
19235
+ (click)="select.emit(); chosen()"
19236
+ (pointerenter)="showTip($event)"
19237
+ (pointerleave)="tip.set(null)"
19238
+ (focus)="showTip($event)"
19239
+ (blur)="tip.set(null)"
18766
19240
  >
18767
19241
  @if (active()) {
18768
19242
  <span [class]="accentBarClass()" aria-hidden="true"></span>
@@ -18773,15 +19247,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
18773
19247
 
18774
19248
  <ng-template #content>
18775
19249
  @if (icon()) {
18776
- <sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
19250
+ <sds-icon [name]="icon()!" [size]="nested() && !iconOnly() ? 18 : 20" class="shrink-0" />
19251
+ }
19252
+ @if (!iconOnly()) {
19253
+ <span class="min-w-0 flex-1 truncate">{{ label() }}</span>
18777
19254
  }
18778
- <!-- Label is hidden only on the collapsed desktop rail; always shown in the mobile drawer. -->
18779
- <span class="truncate" [class]="collapsed() ? 'lg:hidden' : ''">{{ label() }}</span>
18780
19255
  @if (badgeText(); as text) {
18781
- <!-- The number is drawn; the words are what is spoken ("Menu, 3 pending"). On the collapsed
18782
- desktop rail it becomes a small corner pill on the icon. -->
19256
+ <!-- The number is drawn; the words are what is spoken ("Menu, 3 pending"). On the icon rail it
19257
+ becomes a small pill on the icon's corner. -->
18783
19258
  <span [class]="badgeClass()" aria-hidden="true">{{ badgeDisplay() }}</span>
18784
- <span class="sr-only">, {{ text }}</span>
19259
+ @if (!iconOnly()) {
19260
+ <span class="sr-only">, {{ text }}</span>
19261
+ }
19262
+ }
19263
+ @if (tip(); as t) {
19264
+ <!-- Icon-rail tooltip: fixed, so the rail's scroll container never clips it. -->
19265
+ <span
19266
+ aria-hidden="true"
19267
+ class="pointer-events-none fixed z-[70] -translate-y-1/2 whitespace-nowrap rounded-control bg-ink px-2.5 py-1.5 text-xs font-medium text-surface shadow-lg"
19268
+ [style.top.px]="t.top"
19269
+ [style.left.px]="t.left"
19270
+ >
19271
+ {{ label() }}
19272
+ @if (badgeText()) {
19273
+ <span class="ml-1 opacity-70">· {{ badgeDisplay() }}</span>
19274
+ }
19275
+ </span>
18785
19276
  }
18786
19277
  </ng-template>
18787
19278
  `,