@besaitech/ng-design-system 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, isDevMode, effect, output, signal, forwardRef, numberAttribute, viewChildren, afterRenderEffect,
|
|
2
|
+
import { inject, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, isDevMode, effect, output, signal, forwardRef, viewChild, numberAttribute, viewChildren, afterRenderEffect, ElementRef, model, Injector, afterNextRender, Injectable, TemplateRef, Directive, contentChildren, contentChild } from '@angular/core';
|
|
3
3
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
4
|
import * as i1 from '@angular/forms';
|
|
5
5
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormsModule } from '@angular/forms';
|
|
@@ -48,6 +48,11 @@ const SDS_ICONS = {
|
|
|
48
48
|
'chevron-right': '<path d="m9 18 6-6-6-6"/>',
|
|
49
49
|
'chevron-left': '<path d="m15 18-6-6 6-6"/>',
|
|
50
50
|
'chevron-down': '<path d="m6 9 6 6 6-6"/>',
|
|
51
|
+
// Stepper glyphs for sds-input-number: chevrons for the stacked spinner, plus/minus for the
|
|
52
|
+
// horizontal/vertical layouts.
|
|
53
|
+
'chevron-up': '<path d="m18 15-6-6-6 6"/>',
|
|
54
|
+
plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
|
|
55
|
+
minus: '<path d="M5 12h14"/>',
|
|
51
56
|
'chevrons-left': '<path d="m11 17-5-5 5-5"/><path d="m18 17-5-5 5-5"/>',
|
|
52
57
|
'chevrons-right': '<path d="m6 17 5-5-5-5"/><path d="m13 17 5-5-5-5"/>',
|
|
53
58
|
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
@@ -155,7 +160,7 @@ const VARIANTS$2 = {
|
|
|
155
160
|
ghost: 'bg-transparent text-ink-soft hover:bg-black/5 active:bg-black/10',
|
|
156
161
|
danger: 'bg-danger text-white shadow-sm hover:brightness-95 active:brightness-90',
|
|
157
162
|
};
|
|
158
|
-
const SIZES$
|
|
163
|
+
const SIZES$b = {
|
|
159
164
|
sm: 'h-8 px-3 text-sm',
|
|
160
165
|
md: 'h-10 px-4 text-sm',
|
|
161
166
|
lg: 'h-12 px-5 text-base',
|
|
@@ -215,7 +220,7 @@ class SdsButton {
|
|
|
215
220
|
BASE,
|
|
216
221
|
this.shape() === 'pill' ? 'rounded-full' : 'rounded-control',
|
|
217
222
|
VARIANTS$2[this.variant()],
|
|
218
|
-
SIZES$
|
|
223
|
+
SIZES$b[size],
|
|
219
224
|
this.iconOnly() ? ICON_ONLY[size] : '',
|
|
220
225
|
this.block() ? 'w-full' : '',
|
|
221
226
|
]
|
|
@@ -267,8 +272,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
267
272
|
`, styles: [".sds-spinner{width:1em;height:1em;border:2px solid currentColor;border-right-color:transparent;border-radius:9999px;animation:sds-spin .6s linear infinite}@keyframes sds-spin{to{transform:rotate(360deg)}}\n"] }]
|
|
268
273
|
}], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], block: [{ type: i0.Input, args: [{ isSignal: true, alias: "block", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
269
274
|
|
|
270
|
-
let nextId$
|
|
271
|
-
const SIZES$
|
|
275
|
+
let nextId$d = 0;
|
|
276
|
+
const SIZES$a = {
|
|
272
277
|
sm: 'h-9 text-sm',
|
|
273
278
|
md: 'h-10 text-sm',
|
|
274
279
|
lg: 'h-12 text-base',
|
|
@@ -280,7 +285,7 @@ const SIZES$9 = {
|
|
|
280
285
|
* sidebar search field).
|
|
281
286
|
*/
|
|
282
287
|
class SdsInput {
|
|
283
|
-
inputId = `sds-input-${nextId$
|
|
288
|
+
inputId = `sds-input-${nextId$d++}`;
|
|
284
289
|
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
285
290
|
/** Accessible name used when no visible [label] is provided. */
|
|
286
291
|
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
@@ -357,7 +362,7 @@ class SdsInput {
|
|
|
357
362
|
return `${left} ${right}`;
|
|
358
363
|
}, ...(ngDevMode ? [{ debugName: "fieldPadding" }] : []));
|
|
359
364
|
wrapperClasses = computed(() => {
|
|
360
|
-
const parts = [SIZES$
|
|
365
|
+
const parts = [SIZES$a[this.size()], this.pill() ? 'rounded-full' : 'rounded-control'];
|
|
361
366
|
if (this.isDisabled())
|
|
362
367
|
parts.push('opacity-60 cursor-not-allowed');
|
|
363
368
|
if (this.filled()) {
|
|
@@ -601,6 +606,1012 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
601
606
|
`, styles: ["input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus,input:-webkit-autofill:active{transition:background-color 100000s ease-in-out 0s;-webkit-text-fill-color:currentColor;caret-color:currentColor}\n"] }]
|
|
602
607
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], leadingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leadingIcon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], trailingAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingAction", required: false }] }], trailingActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionLabel", required: false }] }], trailingActionPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionPressed", required: false }] }], trailingActionClick: [{ type: i0.Output, args: ["trailingActionClick"] }] } });
|
|
603
608
|
|
|
609
|
+
let nextId$c = 0;
|
|
610
|
+
const HEIGHTS = {
|
|
611
|
+
sm: 'h-9',
|
|
612
|
+
md: 'h-10',
|
|
613
|
+
lg: 'h-12',
|
|
614
|
+
};
|
|
615
|
+
const TEXT = {
|
|
616
|
+
sm: 'text-sm',
|
|
617
|
+
md: 'text-sm',
|
|
618
|
+
lg: 'text-base',
|
|
619
|
+
};
|
|
620
|
+
/**
|
|
621
|
+
* Numeric input with locale-aware formatting, optional currency, prefix/suffix affixes, min/max/step
|
|
622
|
+
* boundaries and spinner buttons — the design-system counterpart of PrimeNG's `InputNumber`.
|
|
623
|
+
*
|
|
624
|
+
* The value is a `number | null` bound through ControlValueAccessor, so it works with reactive forms
|
|
625
|
+
* or `[(ngModel)]`: `<sds-input-number [(ngModel)]="amount" mode="currency" currency="EUR" />`.
|
|
626
|
+
*
|
|
627
|
+
* The field formats **live as you type**: the currency symbol, grouping separators and prefix/suffix
|
|
628
|
+
* stay visible on every keystroke, and the caret is kept where it belongs. The value is clamped to
|
|
629
|
+
* {@link min}/{@link max} on blur and on every spinner step; ArrowUp / ArrowDown also step it.
|
|
630
|
+
*/
|
|
631
|
+
class SdsInputNumber {
|
|
632
|
+
inputId = `sds-input-number-${nextId$c++}`;
|
|
633
|
+
fieldRef = viewChild('field', ...(ngDevMode ? [{ debugName: "fieldRef" }] : []));
|
|
634
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
635
|
+
/** Accessible name used when no visible [label] is provided. */
|
|
636
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
637
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
638
|
+
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
639
|
+
error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
640
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
641
|
+
/** `filled` is a borderless field on a muted ground; `default` is the bordered white field. */
|
|
642
|
+
appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
|
|
643
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
644
|
+
/** Read-only: the value is shown and selectable but cannot be typed or stepped. */
|
|
645
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
646
|
+
// --- Formatting -----------------------------------------------------------
|
|
647
|
+
mode = input('decimal', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
648
|
+
/** BCP 47 locale for grouping/decimal symbols (e.g. `fr-FR`). Empty follows the runtime locale. */
|
|
649
|
+
locale = input('', ...(ngDevMode ? [{ debugName: "locale" }] : []));
|
|
650
|
+
/** ISO 4217 code, required when {@link mode} is `currency` (e.g. `EUR`, `USD`). */
|
|
651
|
+
currency = input('', ...(ngDevMode ? [{ debugName: "currency" }] : []));
|
|
652
|
+
currencyDisplay = input('symbol', ...(ngDevMode ? [{ debugName: "currencyDisplay" }] : []));
|
|
653
|
+
/** Thousands/grouping separators. */
|
|
654
|
+
useGrouping = input(true, ...(ngDevMode ? [{ debugName: "useGrouping", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
655
|
+
minFractionDigits = input(null, ...(ngDevMode ? [{ debugName: "minFractionDigits" }] : []));
|
|
656
|
+
maxFractionDigits = input(null, ...(ngDevMode ? [{ debugName: "maxFractionDigits" }] : []));
|
|
657
|
+
/** Text shown before the value (e.g. `↑ `). */
|
|
658
|
+
prefix = input('', ...(ngDevMode ? [{ debugName: "prefix" }] : []));
|
|
659
|
+
/** Text shown after the value (e.g. ` kg`, `%`). */
|
|
660
|
+
suffix = input('', ...(ngDevMode ? [{ debugName: "suffix" }] : []));
|
|
661
|
+
// --- Boundaries & spinner -------------------------------------------------
|
|
662
|
+
min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
|
|
663
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
|
|
664
|
+
step = input(1, ...(ngDevMode ? [{ debugName: "step", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
665
|
+
/** Show the increment/decrement spinner buttons. */
|
|
666
|
+
showButtons = input(false, ...(ngDevMode ? [{ debugName: "showButtons", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
667
|
+
buttonLayout = input('stacked', ...(ngDevMode ? [{ debugName: "buttonLayout" }] : []));
|
|
668
|
+
/** Override the increment button icon (defaults: chevron-up when stacked, plus otherwise). */
|
|
669
|
+
incrementIcon = input(null, ...(ngDevMode ? [{ debugName: "incrementIcon" }] : []));
|
|
670
|
+
/** Override the decrement button icon (defaults: chevron-down when stacked, minus otherwise). */
|
|
671
|
+
decrementIcon = input(null, ...(ngDevMode ? [{ debugName: "decrementIcon" }] : []));
|
|
672
|
+
/** Allow an empty (null) value. When false, blurring an empty field falls back to {@link min} or 0. */
|
|
673
|
+
allowEmpty = input(true, ...(ngDevMode ? [{ debugName: "allowEmpty", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
674
|
+
clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
675
|
+
/**
|
|
676
|
+
* Accessible names for the icon-only controls. French defaults so existing consumers are unchanged;
|
|
677
|
+
* translate them in a multi-language app.
|
|
678
|
+
*/
|
|
679
|
+
clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
|
|
680
|
+
incrementLabel = input('Augmenter', ...(ngDevMode ? [{ debugName: "incrementLabel" }] : []));
|
|
681
|
+
decrementLabel = input('Diminuer', ...(ngDevMode ? [{ debugName: "decrementLabel" }] : []));
|
|
682
|
+
/** The committed numeric value (driven by CVA + user input). */
|
|
683
|
+
value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
684
|
+
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
685
|
+
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
686
|
+
describedBy = computed(() => {
|
|
687
|
+
if (this.error())
|
|
688
|
+
return `${this.inputId}-err`;
|
|
689
|
+
if (this.hint())
|
|
690
|
+
return `${this.inputId}-hint`;
|
|
691
|
+
return null;
|
|
692
|
+
}, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
|
|
693
|
+
clearVisible = computed(() => this.clearable() && this.value() !== null && !this.isDisabled() && !this.readonly(), ...(ngDevMode ? [{ debugName: "clearVisible" }] : []));
|
|
694
|
+
incrementIconName = computed(() => this.incrementIcon() ?? (this.buttonLayout() === 'stacked' ? 'chevron-up' : 'plus'), ...(ngDevMode ? [{ debugName: "incrementIconName" }] : []));
|
|
695
|
+
decrementIconName = computed(() => this.decrementIcon() ?? (this.buttonLayout() === 'stacked' ? 'chevron-down' : 'minus'), ...(ngDevMode ? [{ debugName: "decrementIconName" }] : []));
|
|
696
|
+
canIncrement = computed(() => {
|
|
697
|
+
if (this.isDisabled() || this.readonly())
|
|
698
|
+
return false;
|
|
699
|
+
const max = this.max();
|
|
700
|
+
return max === null || (this.value() ?? this.min() ?? 0) < max;
|
|
701
|
+
}, ...(ngDevMode ? [{ debugName: "canIncrement" }] : []));
|
|
702
|
+
canDecrement = computed(() => {
|
|
703
|
+
if (this.isDisabled() || this.readonly())
|
|
704
|
+
return false;
|
|
705
|
+
const min = this.min();
|
|
706
|
+
return min === null || (this.value() ?? min ?? 0) > min;
|
|
707
|
+
}, ...(ngDevMode ? [{ debugName: "canDecrement" }] : []));
|
|
708
|
+
/** Locale grouping/decimal separators, derived from Intl so parsing matches formatting. */
|
|
709
|
+
separators = computed(() => {
|
|
710
|
+
const sample = new Intl.NumberFormat(this.locale() || undefined, {
|
|
711
|
+
useGrouping: true,
|
|
712
|
+
}).formatToParts(1234567.8);
|
|
713
|
+
return {
|
|
714
|
+
group: sample.find((p) => p.type === 'group')?.value ?? ',',
|
|
715
|
+
decimal: sample.find((p) => p.type === 'decimal')?.value ?? '.',
|
|
716
|
+
};
|
|
717
|
+
}, ...(ngDevMode ? [{ debugName: "separators" }] : []));
|
|
718
|
+
/** Base Intl options (style/currency/grouping) without any fraction-digit constraints. */
|
|
719
|
+
baseOptions() {
|
|
720
|
+
const opts = { useGrouping: this.useGrouping() };
|
|
721
|
+
if (this.mode() === 'currency') {
|
|
722
|
+
opts.style = 'currency';
|
|
723
|
+
opts.currency = this.currency() || 'USD';
|
|
724
|
+
opts.currencyDisplay = this.currencyDisplay();
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
opts.style = 'decimal';
|
|
728
|
+
}
|
|
729
|
+
return opts;
|
|
730
|
+
}
|
|
731
|
+
committedOptions = computed(() => {
|
|
732
|
+
const opts = this.baseOptions();
|
|
733
|
+
const min = this.minFractionDigits();
|
|
734
|
+
const max = this.maxFractionDigits();
|
|
735
|
+
if (min !== null)
|
|
736
|
+
opts.minimumFractionDigits = min;
|
|
737
|
+
if (max !== null)
|
|
738
|
+
opts.maximumFractionDigits = min !== null ? Math.max(max, min) : max;
|
|
739
|
+
return opts;
|
|
740
|
+
}, ...(ngDevMode ? [{ debugName: "committedOptions" }] : []));
|
|
741
|
+
/** The most fraction digits the field will keep — the resolved Intl maximum (2 for USD, 0 for JPY…). */
|
|
742
|
+
maxFrac = computed(() => {
|
|
743
|
+
const fallback = this.maxFractionDigits() ?? (this.mode() === 'currency' ? 2 : 3);
|
|
744
|
+
try {
|
|
745
|
+
const resolved = new Intl.NumberFormat(this.locale() || undefined, this.committedOptions()).resolvedOptions().maximumFractionDigits;
|
|
746
|
+
return resolved ?? fallback;
|
|
747
|
+
}
|
|
748
|
+
catch {
|
|
749
|
+
return fallback;
|
|
750
|
+
}
|
|
751
|
+
}, ...(ngDevMode ? [{ debugName: "maxFrac" }] : []));
|
|
752
|
+
wrapperClasses = computed(() => {
|
|
753
|
+
const vertical = this.buttonLayout() === 'vertical' && this.showButtons();
|
|
754
|
+
const parts = [
|
|
755
|
+
'overflow-hidden rounded-control',
|
|
756
|
+
vertical ? 'flex flex-col' : `flex items-stretch ${HEIGHTS[this.size()]}`,
|
|
757
|
+
];
|
|
758
|
+
if (this.isDisabled())
|
|
759
|
+
parts.push('opacity-60 cursor-not-allowed');
|
|
760
|
+
if (this.appearance() === 'filled') {
|
|
761
|
+
parts.push('border-transparent bg-muted text-ink');
|
|
762
|
+
parts.push(this.error()
|
|
763
|
+
? 'border-danger focus-within:ring-2 focus-within:ring-danger/30'
|
|
764
|
+
: 'focus-within:ring-2 focus-within:ring-brand-500/25');
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
parts.push('bg-white text-ink');
|
|
768
|
+
parts.push(this.error()
|
|
769
|
+
? 'border-danger focus-within:border-danger focus-within:ring-2 focus-within:ring-danger/25'
|
|
770
|
+
: 'border-border focus-within:border-brand-500 focus-within:ring-2 focus-within:ring-brand-500/25');
|
|
771
|
+
}
|
|
772
|
+
return parts.join(' ');
|
|
773
|
+
}, ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
|
|
774
|
+
fieldCellClasses = computed(() => this.buttonLayout() === 'vertical' && this.showButtons() ? HEIGHTS[this.size()] : 'h-full', ...(ngDevMode ? [{ debugName: "fieldCellClasses" }] : []));
|
|
775
|
+
inputClasses = computed(() => {
|
|
776
|
+
const parts = [TEXT[this.size()], 'pl-3.5', this.clearVisible() ? 'pr-9' : 'pr-3.5'];
|
|
777
|
+
if (this.showButtons() && this.buttonLayout() !== 'stacked')
|
|
778
|
+
parts.push('text-center');
|
|
779
|
+
return parts.join(' ');
|
|
780
|
+
}, ...(ngDevMode ? [{ debugName: "inputClasses" }] : []));
|
|
781
|
+
buttonBase = 'inline-flex shrink-0 items-center justify-center text-ink-soft transition-colors ' +
|
|
782
|
+
'hover:bg-muted hover:text-ink disabled:cursor-not-allowed disabled:opacity-40 select-none';
|
|
783
|
+
edgeButtonClasses = computed(() => this.buttonLayout() === 'vertical'
|
|
784
|
+
? `${this.buttonBase} ${HEIGHTS[this.size()]} border-border`
|
|
785
|
+
: `${this.buttonBase} w-10 border-border`, ...(ngDevMode ? [{ debugName: "edgeButtonClasses" }] : []));
|
|
786
|
+
stackedButtonClasses = computed(() => this.buttonBase, ...(ngDevMode ? [{ debugName: "stackedButtonClasses" }] : []));
|
|
787
|
+
constructor() {
|
|
788
|
+
// Reflect the committed value into the field whenever it — or a formatting input — changes from
|
|
789
|
+
// OUTSIDE typing (initial render, writeValue, a spin while blurred, a currency/locale switch).
|
|
790
|
+
// While the field is the active element the input handler owns the DOM value and its caret, so this
|
|
791
|
+
// stands down: gating on the live `document.activeElement` (not a signal) is immune to focus-event
|
|
792
|
+
// timing, which is what makes live formatting safe.
|
|
793
|
+
effect(() => {
|
|
794
|
+
const el = this.fieldRef()?.nativeElement;
|
|
795
|
+
const v = this.value();
|
|
796
|
+
// Touch the formatting inputs so a currency/locale/affix change repaints a blurred field.
|
|
797
|
+
this.committedOptions();
|
|
798
|
+
this.prefix();
|
|
799
|
+
this.suffix();
|
|
800
|
+
if (!el || this.isActive(el))
|
|
801
|
+
return;
|
|
802
|
+
el.value = v === null ? '' : this.format(v);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
isActive(el) {
|
|
806
|
+
return typeof document !== 'undefined' && document.activeElement === el;
|
|
807
|
+
}
|
|
808
|
+
/** Paint the committed value into the field imperatively (used by spin/clear/blur). */
|
|
809
|
+
paintCommitted() {
|
|
810
|
+
const el = this.fieldRef()?.nativeElement;
|
|
811
|
+
if (!el)
|
|
812
|
+
return;
|
|
813
|
+
const v = this.value();
|
|
814
|
+
el.value = v === null ? '' : this.format(v);
|
|
815
|
+
}
|
|
816
|
+
// --- Formatting / parsing helpers -----------------------------------------
|
|
817
|
+
/** Format a committed value with the configured min/max fraction digits (used at rest and on blur). */
|
|
818
|
+
format(n) {
|
|
819
|
+
try {
|
|
820
|
+
const body = new Intl.NumberFormat(this.locale() || undefined, this.committedOptions()).format(n);
|
|
821
|
+
return `${this.prefix()}${body}${this.suffix()}`;
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
return `${this.prefix()}${n}${this.suffix()}`;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Format a partially-typed value, keeping exactly the fraction digits the user has entered (so
|
|
829
|
+
* trailing zeros and a lone decimal separator survive) while still grouping the integer part and
|
|
830
|
+
* showing the currency symbol / affixes.
|
|
831
|
+
*/
|
|
832
|
+
formatLive(p) {
|
|
833
|
+
const locale = this.locale() || undefined;
|
|
834
|
+
const fracCount = p.fracDigits.length;
|
|
835
|
+
const num = this.toNumber(p) ?? 0;
|
|
836
|
+
if (fracCount > 0 || !p.hadDecimal) {
|
|
837
|
+
try {
|
|
838
|
+
const body = new Intl.NumberFormat(locale, {
|
|
839
|
+
...this.baseOptions(),
|
|
840
|
+
minimumFractionDigits: fracCount,
|
|
841
|
+
maximumFractionDigits: fracCount,
|
|
842
|
+
}).format(num);
|
|
843
|
+
return `${this.prefix()}${body}${this.suffix()}`;
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
return `${this.prefix()}${num}${this.suffix()}`;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
// A lone decimal separator was just typed ("1234,"): format the integer, then splice the separator
|
|
850
|
+
// in right after the integer digits — which, for a trailing-symbol currency, is NOT the string end.
|
|
851
|
+
const dec = this.separators().decimal;
|
|
852
|
+
let out = '';
|
|
853
|
+
let inserted = false;
|
|
854
|
+
try {
|
|
855
|
+
const parts = new Intl.NumberFormat(locale, {
|
|
856
|
+
...this.baseOptions(),
|
|
857
|
+
maximumFractionDigits: 0,
|
|
858
|
+
}).formatToParts(num);
|
|
859
|
+
parts.forEach((part, i) => {
|
|
860
|
+
out += part.value;
|
|
861
|
+
const here = part.type === 'integer' || part.type === 'group';
|
|
862
|
+
const next = parts[i + 1];
|
|
863
|
+
const nextHere = next && (next.type === 'integer' || next.type === 'group');
|
|
864
|
+
if (here && !nextHere && !inserted) {
|
|
865
|
+
out += dec;
|
|
866
|
+
inserted = true;
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
catch {
|
|
871
|
+
out = `${num}${dec}`;
|
|
872
|
+
inserted = true;
|
|
873
|
+
}
|
|
874
|
+
if (!inserted)
|
|
875
|
+
out += dec;
|
|
876
|
+
return `${this.prefix()}${out}${this.suffix()}`;
|
|
877
|
+
}
|
|
878
|
+
toNumber(p) {
|
|
879
|
+
const s = `${p.negative ? '-' : ''}${p.intDigits || '0'}${p.fracDigits ? '.' + p.fracDigits : ''}`;
|
|
880
|
+
const n = Number(s);
|
|
881
|
+
return Number.isFinite(n) ? n : null;
|
|
882
|
+
}
|
|
883
|
+
/** Pull the sign / integer / fraction fragments out of a formatted, partially-typed string. */
|
|
884
|
+
parseInput(raw) {
|
|
885
|
+
const { group, decimal } = this.separators();
|
|
886
|
+
let t = raw;
|
|
887
|
+
const prefix = this.prefix();
|
|
888
|
+
const suffix = this.suffix();
|
|
889
|
+
if (prefix && t.startsWith(prefix))
|
|
890
|
+
t = t.slice(prefix.length);
|
|
891
|
+
if (suffix && t.endsWith(suffix))
|
|
892
|
+
t = t.slice(0, t.length - suffix.length);
|
|
893
|
+
if (group)
|
|
894
|
+
t = t.split(group).join('');
|
|
895
|
+
if (decimal && decimal !== '.')
|
|
896
|
+
t = t.split(decimal).join('.');
|
|
897
|
+
t = t.replace(/[^0-9.\-]/g, '');
|
|
898
|
+
const negative = t.includes('-');
|
|
899
|
+
t = t.replace(/-/g, '');
|
|
900
|
+
const dot = t.indexOf('.');
|
|
901
|
+
const hadDecimal = dot !== -1 && this.maxFrac() > 0;
|
|
902
|
+
let intDigits = (hadDecimal ? t.slice(0, dot) : t.replace(/\./g, '')).replace(/\D/g, '');
|
|
903
|
+
let fracDigits = hadDecimal ? t.slice(dot + 1).replace(/\D/g, '') : '';
|
|
904
|
+
// Strip a leading run of zeros ("007" -> "7") but keep a lone "0".
|
|
905
|
+
intDigits = intDigits.replace(/^0+(?=\d)/, '');
|
|
906
|
+
fracDigits = fracDigits.slice(0, this.maxFrac());
|
|
907
|
+
return { negative, intDigits, fracDigits, hadDecimal };
|
|
908
|
+
}
|
|
909
|
+
/** Characters that "carry meaning" for caret placement (digits, minus, the decimal separator). */
|
|
910
|
+
isSignificant(ch) {
|
|
911
|
+
return (ch >= '0' && ch <= '9') || ch === '-' || ch === this.separators().decimal;
|
|
912
|
+
}
|
|
913
|
+
significantBefore(s, upto) {
|
|
914
|
+
let n = 0;
|
|
915
|
+
for (let i = 0; i < upto && i < s.length; i++)
|
|
916
|
+
if (this.isSignificant(s[i]))
|
|
917
|
+
n++;
|
|
918
|
+
return n;
|
|
919
|
+
}
|
|
920
|
+
/** The caret index in `formatted` that sits just after `target` significant characters. */
|
|
921
|
+
caretAfter(formatted, target) {
|
|
922
|
+
if (target <= 0) {
|
|
923
|
+
for (let i = 0; i < formatted.length; i++)
|
|
924
|
+
if (this.isSignificant(formatted[i]))
|
|
925
|
+
return i;
|
|
926
|
+
return formatted.length;
|
|
927
|
+
}
|
|
928
|
+
let n = 0;
|
|
929
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
930
|
+
if (this.isSignificant(formatted[i]) && ++n === target)
|
|
931
|
+
return i + 1;
|
|
932
|
+
}
|
|
933
|
+
return formatted.length;
|
|
934
|
+
}
|
|
935
|
+
clamp(n) {
|
|
936
|
+
const min = this.min();
|
|
937
|
+
const max = this.max();
|
|
938
|
+
if (min !== null && n < min)
|
|
939
|
+
n = min;
|
|
940
|
+
if (max !== null && n > max)
|
|
941
|
+
n = max;
|
|
942
|
+
return n;
|
|
943
|
+
}
|
|
944
|
+
/** Trim binary-float drift (0.1 + 0.2) back to a clean decimal. */
|
|
945
|
+
tidy(n) {
|
|
946
|
+
return Math.round(n * 1e10) / 1e10;
|
|
947
|
+
}
|
|
948
|
+
// --- Interaction ----------------------------------------------------------
|
|
949
|
+
onInput(event) {
|
|
950
|
+
const el = event.target;
|
|
951
|
+
const raw = el.value;
|
|
952
|
+
const caret = el.selectionStart ?? raw.length;
|
|
953
|
+
const target = this.significantBefore(raw, caret);
|
|
954
|
+
const parsed = this.parseInput(raw);
|
|
955
|
+
if (parsed.intDigits === '' && parsed.fracDigits === '' && !parsed.hadDecimal) {
|
|
956
|
+
// Nothing numeric left — allow a lone "-" mid-entry, otherwise empty.
|
|
957
|
+
const text = parsed.negative ? '-' : '';
|
|
958
|
+
el.value = text;
|
|
959
|
+
el.setSelectionRange(text.length, text.length);
|
|
960
|
+
this.value.set(null);
|
|
961
|
+
this.onChange(null);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const num = this.toNumber(parsed);
|
|
965
|
+
const formatted = this.formatLive(parsed);
|
|
966
|
+
el.value = formatted;
|
|
967
|
+
const next = this.caretAfter(formatted, target);
|
|
968
|
+
el.setSelectionRange(next, next);
|
|
969
|
+
this.value.set(num);
|
|
970
|
+
this.onChange(num);
|
|
971
|
+
}
|
|
972
|
+
onKeydown(event) {
|
|
973
|
+
if (event.key === 'ArrowUp') {
|
|
974
|
+
event.preventDefault();
|
|
975
|
+
this.spin(1);
|
|
976
|
+
}
|
|
977
|
+
else if (event.key === 'ArrowDown') {
|
|
978
|
+
event.preventDefault();
|
|
979
|
+
this.spin(-1);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
onBlur() {
|
|
983
|
+
let v = this.value();
|
|
984
|
+
if (v === null && !this.allowEmpty())
|
|
985
|
+
v = this.min() ?? 0;
|
|
986
|
+
if (v !== null)
|
|
987
|
+
v = this.clamp(this.tidy(v));
|
|
988
|
+
// Re-render through the committed formatter (applies min-fraction padding + clamping).
|
|
989
|
+
this.value.set(v);
|
|
990
|
+
this.paintCommitted();
|
|
991
|
+
this.onChange(v);
|
|
992
|
+
this.onTouched();
|
|
993
|
+
}
|
|
994
|
+
spin(direction) {
|
|
995
|
+
if (this.isDisabled() || this.readonly())
|
|
996
|
+
return;
|
|
997
|
+
const base = this.value() ?? this.min() ?? 0;
|
|
998
|
+
const nextValue = this.clamp(this.tidy(base + direction * this.step()));
|
|
999
|
+
this.value.set(nextValue);
|
|
1000
|
+
this.onChange(nextValue);
|
|
1001
|
+
this.onTouched();
|
|
1002
|
+
// Paint the stepped value ourselves — the effect stands down while the field is focused.
|
|
1003
|
+
const el = this.fieldRef()?.nativeElement;
|
|
1004
|
+
if (el) {
|
|
1005
|
+
const text = this.format(nextValue);
|
|
1006
|
+
el.value = text;
|
|
1007
|
+
if (this.isActive(el))
|
|
1008
|
+
el.setSelectionRange(text.length, text.length);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
clear() {
|
|
1012
|
+
this.value.set(null);
|
|
1013
|
+
this.onChange(null);
|
|
1014
|
+
this.onTouched();
|
|
1015
|
+
const el = this.fieldRef()?.nativeElement;
|
|
1016
|
+
if (el)
|
|
1017
|
+
el.value = '';
|
|
1018
|
+
}
|
|
1019
|
+
// --- ControlValueAccessor -------------------------------------------------
|
|
1020
|
+
onChange = () => { };
|
|
1021
|
+
onTouched = () => { };
|
|
1022
|
+
writeValue(value) {
|
|
1023
|
+
this.value.set(typeof value === 'number' && Number.isFinite(value) ? value : null);
|
|
1024
|
+
}
|
|
1025
|
+
registerOnChange(fn) {
|
|
1026
|
+
this.onChange = fn;
|
|
1027
|
+
}
|
|
1028
|
+
registerOnTouched(fn) {
|
|
1029
|
+
this.onTouched = fn;
|
|
1030
|
+
}
|
|
1031
|
+
setDisabledState(isDisabled) {
|
|
1032
|
+
this.cvaDisabled.set(isDisabled);
|
|
1033
|
+
}
|
|
1034
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsInputNumber, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1035
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsInputNumber, isStandalone: true, selector: "sds-input-number", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, currencyDisplay: { classPropertyName: "currencyDisplay", publicName: "currencyDisplay", isSignal: true, isRequired: false, transformFunction: null }, useGrouping: { classPropertyName: "useGrouping", publicName: "useGrouping", isSignal: true, isRequired: false, transformFunction: null }, minFractionDigits: { classPropertyName: "minFractionDigits", publicName: "minFractionDigits", isSignal: true, isRequired: false, transformFunction: null }, maxFractionDigits: { classPropertyName: "maxFractionDigits", publicName: "maxFractionDigits", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, buttonLayout: { classPropertyName: "buttonLayout", publicName: "buttonLayout", isSignal: true, isRequired: false, transformFunction: null }, incrementIcon: { classPropertyName: "incrementIcon", publicName: "incrementIcon", isSignal: true, isRequired: false, transformFunction: null }, decrementIcon: { classPropertyName: "decrementIcon", publicName: "decrementIcon", isSignal: true, isRequired: false, transformFunction: null }, allowEmpty: { classPropertyName: "allowEmpty", publicName: "allowEmpty", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, incrementLabel: { classPropertyName: "incrementLabel", publicName: "incrementLabel", isSignal: true, isRequired: false, transformFunction: null }, decrementLabel: { classPropertyName: "decrementLabel", publicName: "decrementLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [
|
|
1036
|
+
{
|
|
1037
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1038
|
+
useExisting: forwardRef(() => SdsInputNumber),
|
|
1039
|
+
multi: true,
|
|
1040
|
+
},
|
|
1041
|
+
], viewQueries: [{ propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1042
|
+
@if (label()) {
|
|
1043
|
+
<label [for]="inputId" class="mb-1.5 block text-sm font-medium text-ink">{{ label() }}</label>
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
<div class="relative border transition-colors" [class]="wrapperClasses()">
|
|
1047
|
+
<!-- Horizontal: minus before the field. Vertical: plus above it. -->
|
|
1048
|
+
@if (showButtons() && buttonLayout() === 'horizontal') {
|
|
1049
|
+
<button
|
|
1050
|
+
type="button"
|
|
1051
|
+
tabindex="-1"
|
|
1052
|
+
[attr.aria-label]="decrementLabel()"
|
|
1053
|
+
[disabled]="!canDecrement()"
|
|
1054
|
+
class="border-r"
|
|
1055
|
+
[class]="edgeButtonClasses()"
|
|
1056
|
+
(mousedown)="$event.preventDefault()"
|
|
1057
|
+
(click)="spin(-1)"
|
|
1058
|
+
>
|
|
1059
|
+
<sds-icon [name]="decrementIconName()" [size]="16" />
|
|
1060
|
+
</button>
|
|
1061
|
+
}
|
|
1062
|
+
@if (showButtons() && buttonLayout() === 'vertical') {
|
|
1063
|
+
<button
|
|
1064
|
+
type="button"
|
|
1065
|
+
tabindex="-1"
|
|
1066
|
+
[attr.aria-label]="incrementLabel()"
|
|
1067
|
+
[disabled]="!canIncrement()"
|
|
1068
|
+
class="w-full border-b"
|
|
1069
|
+
[class]="edgeButtonClasses()"
|
|
1070
|
+
(mousedown)="$event.preventDefault()"
|
|
1071
|
+
(click)="spin(1)"
|
|
1072
|
+
>
|
|
1073
|
+
<sds-icon [name]="incrementIconName()" [size]="16" />
|
|
1074
|
+
</button>
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
<div class="relative flex min-w-0 flex-1 items-center" [class]="fieldCellClasses()">
|
|
1078
|
+
<input
|
|
1079
|
+
#field
|
|
1080
|
+
[id]="inputId"
|
|
1081
|
+
type="text"
|
|
1082
|
+
inputmode="decimal"
|
|
1083
|
+
role="spinbutton"
|
|
1084
|
+
[placeholder]="placeholder()"
|
|
1085
|
+
[disabled]="isDisabled()"
|
|
1086
|
+
[readOnly]="readonly()"
|
|
1087
|
+
[attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
|
|
1088
|
+
[attr.aria-invalid]="error() ? 'true' : null"
|
|
1089
|
+
[attr.aria-describedby]="describedBy()"
|
|
1090
|
+
[attr.aria-valuenow]="value()"
|
|
1091
|
+
[attr.aria-valuemin]="min()"
|
|
1092
|
+
[attr.aria-valuemax]="max()"
|
|
1093
|
+
class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
|
|
1094
|
+
[class]="inputClasses()"
|
|
1095
|
+
(input)="onInput($event)"
|
|
1096
|
+
(keydown)="onKeydown($event)"
|
|
1097
|
+
(blur)="onBlur()"
|
|
1098
|
+
/>
|
|
1099
|
+
|
|
1100
|
+
@if (clearVisible()) {
|
|
1101
|
+
<button
|
|
1102
|
+
type="button"
|
|
1103
|
+
tabindex="-1"
|
|
1104
|
+
[attr.aria-label]="clearLabel()"
|
|
1105
|
+
class="absolute right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full opacity-60 hover:bg-black/5 hover:opacity-100"
|
|
1106
|
+
(mousedown)="$event.preventDefault()"
|
|
1107
|
+
(click)="clear()"
|
|
1108
|
+
>
|
|
1109
|
+
<sds-icon name="x" [size]="15" />
|
|
1110
|
+
</button>
|
|
1111
|
+
}
|
|
1112
|
+
</div>
|
|
1113
|
+
|
|
1114
|
+
<!-- Horizontal: plus after the field. Vertical: minus below it. -->
|
|
1115
|
+
@if (showButtons() && buttonLayout() === 'horizontal') {
|
|
1116
|
+
<button
|
|
1117
|
+
type="button"
|
|
1118
|
+
tabindex="-1"
|
|
1119
|
+
[attr.aria-label]="incrementLabel()"
|
|
1120
|
+
[disabled]="!canIncrement()"
|
|
1121
|
+
class="border-l"
|
|
1122
|
+
[class]="edgeButtonClasses()"
|
|
1123
|
+
(mousedown)="$event.preventDefault()"
|
|
1124
|
+
(click)="spin(1)"
|
|
1125
|
+
>
|
|
1126
|
+
<sds-icon [name]="incrementIconName()" [size]="16" />
|
|
1127
|
+
</button>
|
|
1128
|
+
}
|
|
1129
|
+
@if (showButtons() && buttonLayout() === 'vertical') {
|
|
1130
|
+
<button
|
|
1131
|
+
type="button"
|
|
1132
|
+
tabindex="-1"
|
|
1133
|
+
[attr.aria-label]="decrementLabel()"
|
|
1134
|
+
[disabled]="!canDecrement()"
|
|
1135
|
+
class="w-full border-t"
|
|
1136
|
+
[class]="edgeButtonClasses()"
|
|
1137
|
+
(mousedown)="$event.preventDefault()"
|
|
1138
|
+
(click)="spin(-1)"
|
|
1139
|
+
>
|
|
1140
|
+
<sds-icon [name]="decrementIconName()" [size]="16" />
|
|
1141
|
+
</button>
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
<!-- Stacked: a chevron column at the trailing edge. -->
|
|
1145
|
+
@if (showButtons() && buttonLayout() === 'stacked') {
|
|
1146
|
+
<div class="flex w-9 shrink-0 flex-col divide-y divide-border border-l border-border">
|
|
1147
|
+
<button
|
|
1148
|
+
type="button"
|
|
1149
|
+
tabindex="-1"
|
|
1150
|
+
[attr.aria-label]="incrementLabel()"
|
|
1151
|
+
[disabled]="!canIncrement()"
|
|
1152
|
+
class="min-h-0 flex-1"
|
|
1153
|
+
[class]="stackedButtonClasses()"
|
|
1154
|
+
(mousedown)="$event.preventDefault()"
|
|
1155
|
+
(click)="spin(1)"
|
|
1156
|
+
>
|
|
1157
|
+
<sds-icon [name]="incrementIconName()" [size]="14" />
|
|
1158
|
+
</button>
|
|
1159
|
+
<button
|
|
1160
|
+
type="button"
|
|
1161
|
+
tabindex="-1"
|
|
1162
|
+
[attr.aria-label]="decrementLabel()"
|
|
1163
|
+
[disabled]="!canDecrement()"
|
|
1164
|
+
class="min-h-0 flex-1"
|
|
1165
|
+
[class]="stackedButtonClasses()"
|
|
1166
|
+
(mousedown)="$event.preventDefault()"
|
|
1167
|
+
(click)="spin(-1)"
|
|
1168
|
+
>
|
|
1169
|
+
<sds-icon [name]="decrementIconName()" [size]="14" />
|
|
1170
|
+
</button>
|
|
1171
|
+
</div>
|
|
1172
|
+
}
|
|
1173
|
+
</div>
|
|
1174
|
+
|
|
1175
|
+
@if (error()) {
|
|
1176
|
+
<p [id]="inputId + '-err'" class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
|
|
1177
|
+
} @else if (hint()) {
|
|
1178
|
+
<p [id]="inputId + '-hint'" class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
|
|
1179
|
+
}
|
|
1180
|
+
`, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1181
|
+
}
|
|
1182
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsInputNumber, decorators: [{
|
|
1183
|
+
type: Component,
|
|
1184
|
+
args: [{
|
|
1185
|
+
selector: 'sds-input-number',
|
|
1186
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1187
|
+
imports: [SdsIcon],
|
|
1188
|
+
providers: [
|
|
1189
|
+
{
|
|
1190
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1191
|
+
useExisting: forwardRef(() => SdsInputNumber),
|
|
1192
|
+
multi: true,
|
|
1193
|
+
},
|
|
1194
|
+
],
|
|
1195
|
+
host: { class: 'block' },
|
|
1196
|
+
template: `
|
|
1197
|
+
@if (label()) {
|
|
1198
|
+
<label [for]="inputId" class="mb-1.5 block text-sm font-medium text-ink">{{ label() }}</label>
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
<div class="relative border transition-colors" [class]="wrapperClasses()">
|
|
1202
|
+
<!-- Horizontal: minus before the field. Vertical: plus above it. -->
|
|
1203
|
+
@if (showButtons() && buttonLayout() === 'horizontal') {
|
|
1204
|
+
<button
|
|
1205
|
+
type="button"
|
|
1206
|
+
tabindex="-1"
|
|
1207
|
+
[attr.aria-label]="decrementLabel()"
|
|
1208
|
+
[disabled]="!canDecrement()"
|
|
1209
|
+
class="border-r"
|
|
1210
|
+
[class]="edgeButtonClasses()"
|
|
1211
|
+
(mousedown)="$event.preventDefault()"
|
|
1212
|
+
(click)="spin(-1)"
|
|
1213
|
+
>
|
|
1214
|
+
<sds-icon [name]="decrementIconName()" [size]="16" />
|
|
1215
|
+
</button>
|
|
1216
|
+
}
|
|
1217
|
+
@if (showButtons() && buttonLayout() === 'vertical') {
|
|
1218
|
+
<button
|
|
1219
|
+
type="button"
|
|
1220
|
+
tabindex="-1"
|
|
1221
|
+
[attr.aria-label]="incrementLabel()"
|
|
1222
|
+
[disabled]="!canIncrement()"
|
|
1223
|
+
class="w-full border-b"
|
|
1224
|
+
[class]="edgeButtonClasses()"
|
|
1225
|
+
(mousedown)="$event.preventDefault()"
|
|
1226
|
+
(click)="spin(1)"
|
|
1227
|
+
>
|
|
1228
|
+
<sds-icon [name]="incrementIconName()" [size]="16" />
|
|
1229
|
+
</button>
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
<div class="relative flex min-w-0 flex-1 items-center" [class]="fieldCellClasses()">
|
|
1233
|
+
<input
|
|
1234
|
+
#field
|
|
1235
|
+
[id]="inputId"
|
|
1236
|
+
type="text"
|
|
1237
|
+
inputmode="decimal"
|
|
1238
|
+
role="spinbutton"
|
|
1239
|
+
[placeholder]="placeholder()"
|
|
1240
|
+
[disabled]="isDisabled()"
|
|
1241
|
+
[readOnly]="readonly()"
|
|
1242
|
+
[attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
|
|
1243
|
+
[attr.aria-invalid]="error() ? 'true' : null"
|
|
1244
|
+
[attr.aria-describedby]="describedBy()"
|
|
1245
|
+
[attr.aria-valuenow]="value()"
|
|
1246
|
+
[attr.aria-valuemin]="min()"
|
|
1247
|
+
[attr.aria-valuemax]="max()"
|
|
1248
|
+
class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
|
|
1249
|
+
[class]="inputClasses()"
|
|
1250
|
+
(input)="onInput($event)"
|
|
1251
|
+
(keydown)="onKeydown($event)"
|
|
1252
|
+
(blur)="onBlur()"
|
|
1253
|
+
/>
|
|
1254
|
+
|
|
1255
|
+
@if (clearVisible()) {
|
|
1256
|
+
<button
|
|
1257
|
+
type="button"
|
|
1258
|
+
tabindex="-1"
|
|
1259
|
+
[attr.aria-label]="clearLabel()"
|
|
1260
|
+
class="absolute right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full opacity-60 hover:bg-black/5 hover:opacity-100"
|
|
1261
|
+
(mousedown)="$event.preventDefault()"
|
|
1262
|
+
(click)="clear()"
|
|
1263
|
+
>
|
|
1264
|
+
<sds-icon name="x" [size]="15" />
|
|
1265
|
+
</button>
|
|
1266
|
+
}
|
|
1267
|
+
</div>
|
|
1268
|
+
|
|
1269
|
+
<!-- Horizontal: plus after the field. Vertical: minus below it. -->
|
|
1270
|
+
@if (showButtons() && buttonLayout() === 'horizontal') {
|
|
1271
|
+
<button
|
|
1272
|
+
type="button"
|
|
1273
|
+
tabindex="-1"
|
|
1274
|
+
[attr.aria-label]="incrementLabel()"
|
|
1275
|
+
[disabled]="!canIncrement()"
|
|
1276
|
+
class="border-l"
|
|
1277
|
+
[class]="edgeButtonClasses()"
|
|
1278
|
+
(mousedown)="$event.preventDefault()"
|
|
1279
|
+
(click)="spin(1)"
|
|
1280
|
+
>
|
|
1281
|
+
<sds-icon [name]="incrementIconName()" [size]="16" />
|
|
1282
|
+
</button>
|
|
1283
|
+
}
|
|
1284
|
+
@if (showButtons() && buttonLayout() === 'vertical') {
|
|
1285
|
+
<button
|
|
1286
|
+
type="button"
|
|
1287
|
+
tabindex="-1"
|
|
1288
|
+
[attr.aria-label]="decrementLabel()"
|
|
1289
|
+
[disabled]="!canDecrement()"
|
|
1290
|
+
class="w-full border-t"
|
|
1291
|
+
[class]="edgeButtonClasses()"
|
|
1292
|
+
(mousedown)="$event.preventDefault()"
|
|
1293
|
+
(click)="spin(-1)"
|
|
1294
|
+
>
|
|
1295
|
+
<sds-icon [name]="decrementIconName()" [size]="16" />
|
|
1296
|
+
</button>
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
<!-- Stacked: a chevron column at the trailing edge. -->
|
|
1300
|
+
@if (showButtons() && buttonLayout() === 'stacked') {
|
|
1301
|
+
<div class="flex w-9 shrink-0 flex-col divide-y divide-border border-l border-border">
|
|
1302
|
+
<button
|
|
1303
|
+
type="button"
|
|
1304
|
+
tabindex="-1"
|
|
1305
|
+
[attr.aria-label]="incrementLabel()"
|
|
1306
|
+
[disabled]="!canIncrement()"
|
|
1307
|
+
class="min-h-0 flex-1"
|
|
1308
|
+
[class]="stackedButtonClasses()"
|
|
1309
|
+
(mousedown)="$event.preventDefault()"
|
|
1310
|
+
(click)="spin(1)"
|
|
1311
|
+
>
|
|
1312
|
+
<sds-icon [name]="incrementIconName()" [size]="14" />
|
|
1313
|
+
</button>
|
|
1314
|
+
<button
|
|
1315
|
+
type="button"
|
|
1316
|
+
tabindex="-1"
|
|
1317
|
+
[attr.aria-label]="decrementLabel()"
|
|
1318
|
+
[disabled]="!canDecrement()"
|
|
1319
|
+
class="min-h-0 flex-1"
|
|
1320
|
+
[class]="stackedButtonClasses()"
|
|
1321
|
+
(mousedown)="$event.preventDefault()"
|
|
1322
|
+
(click)="spin(-1)"
|
|
1323
|
+
>
|
|
1324
|
+
<sds-icon [name]="decrementIconName()" [size]="14" />
|
|
1325
|
+
</button>
|
|
1326
|
+
</div>
|
|
1327
|
+
}
|
|
1328
|
+
</div>
|
|
1329
|
+
|
|
1330
|
+
@if (error()) {
|
|
1331
|
+
<p [id]="inputId + '-err'" class="mt-1.5 text-xs font-medium text-danger">{{ error() }}</p>
|
|
1332
|
+
} @else if (hint()) {
|
|
1333
|
+
<p [id]="inputId + '-hint'" class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
|
|
1334
|
+
}
|
|
1335
|
+
`,
|
|
1336
|
+
}]
|
|
1337
|
+
}], ctorParameters: () => [], propDecorators: { fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], currencyDisplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencyDisplay", required: false }] }], useGrouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "useGrouping", required: false }] }], minFractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "minFractionDigits", required: false }] }], maxFractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFractionDigits", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], showButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtons", required: false }] }], buttonLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLayout", required: false }] }], incrementIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementIcon", required: false }] }], decrementIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementIcon", required: false }] }], allowEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowEmpty", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], incrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementLabel", required: false }] }], decrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementLabel", required: false }] }] } });
|
|
1338
|
+
|
|
1339
|
+
let nextId$b = 0;
|
|
1340
|
+
const SIZES$9 = {
|
|
1341
|
+
sm: 'min-h-9 text-sm',
|
|
1342
|
+
md: 'min-h-10 text-sm',
|
|
1343
|
+
lg: 'min-h-12 text-base',
|
|
1344
|
+
};
|
|
1345
|
+
/**
|
|
1346
|
+
* One language-aware text field for editing a translated value (a {@link SdsLocalizedValue} — a title,
|
|
1347
|
+
* label or description in several languages). Only one language is edited at a time; a compact switcher
|
|
1348
|
+
* sitting **inside the field** flips which translation you are editing, and a dot marks the languages that
|
|
1349
|
+
* already have content, so a partially-translated value is obvious at a glance.
|
|
1350
|
+
*
|
|
1351
|
+
* The active language defaults to {@link currentLanguage} when it is one of {@link languages}, otherwise the
|
|
1352
|
+
* first accepted language — so the field "opens on the language you are working in". Empty translations are
|
|
1353
|
+
* pruned from the emitted value, keeping the stored map clean.
|
|
1354
|
+
*
|
|
1355
|
+
* Implements ControlValueAccessor over the map — use with reactive forms or `[(ngModel)]`:
|
|
1356
|
+
* `<sds-localized-input [languages]="['fr','nl','en']" [(ngModel)]="title" />`.
|
|
1357
|
+
*/
|
|
1358
|
+
class SdsLocalizedInput {
|
|
1359
|
+
inputId = `sds-localized-input-${nextId$b++}`;
|
|
1360
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
1361
|
+
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
1362
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
1363
|
+
/** The accepted language codes, in display order (e.g. `['fr','nl','en']`). */
|
|
1364
|
+
languages = input([], ...(ngDevMode ? [{ debugName: "languages" }] : []));
|
|
1365
|
+
/** Language to pre-select; used when it is one of {@link languages}, else the first accepted language. */
|
|
1366
|
+
currentLanguage = input(null, ...(ngDevMode ? [{ debugName: "currentLanguage" }] : []));
|
|
1367
|
+
multiline = input(false, ...(ngDevMode ? [{ debugName: "multiline", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
1368
|
+
rows = input(3, ...(ngDevMode ? [{ debugName: "rows", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
1369
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
1370
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
1371
|
+
/**
|
|
1372
|
+
* Accessible name for the in-field language switcher, and the "no translation yet" title suffix on each
|
|
1373
|
+
* language button. French defaults so existing consumers are unchanged; translate them in a multi-language
|
|
1374
|
+
* app (the buttons are letter-only, so the title carries their meaning).
|
|
1375
|
+
*/
|
|
1376
|
+
languageSwitcherLabel = input('Langue', ...(ngDevMode ? [{ debugName: "languageSwitcherLabel" }] : []));
|
|
1377
|
+
untranslatedLabel = input('à traduire', ...(ngDevMode ? [{ debugName: "untranslatedLabel" }] : []));
|
|
1378
|
+
valueMap = signal({}, ...(ngDevMode ? [{ debugName: "valueMap" }] : []));
|
|
1379
|
+
/** Language chosen via the switcher; null means "follow currentLanguage / the first language". */
|
|
1380
|
+
manual = signal(null, ...(ngDevMode ? [{ debugName: "manual" }] : []));
|
|
1381
|
+
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
1382
|
+
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
1383
|
+
wrapperClasses = computed(() => {
|
|
1384
|
+
const parts = [SIZES$9[this.size()]];
|
|
1385
|
+
parts.push(this.isDisabled()
|
|
1386
|
+
? 'border-border opacity-60 cursor-not-allowed'
|
|
1387
|
+
: 'border-border focus-within:border-brand-500 focus-within:ring-2 focus-within:ring-brand-500/25');
|
|
1388
|
+
return parts.join(' ');
|
|
1389
|
+
}, ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
|
|
1390
|
+
/** The language currently being edited. */
|
|
1391
|
+
activeLang = computed(() => {
|
|
1392
|
+
const langs = this.languages();
|
|
1393
|
+
if (!langs.length) {
|
|
1394
|
+
return '';
|
|
1395
|
+
}
|
|
1396
|
+
const manual = this.manual();
|
|
1397
|
+
if (manual && langs.includes(manual)) {
|
|
1398
|
+
return manual;
|
|
1399
|
+
}
|
|
1400
|
+
const preferred = this.currentLanguage();
|
|
1401
|
+
return preferred && langs.includes(preferred) ? preferred : langs[0];
|
|
1402
|
+
}, ...(ngDevMode ? [{ debugName: "activeLang" }] : []));
|
|
1403
|
+
current = computed(() => this.valueMap()[this.activeLang()] ?? '', ...(ngDevMode ? [{ debugName: "current" }] : []));
|
|
1404
|
+
filled(lang) {
|
|
1405
|
+
const text = this.valueMap()[lang];
|
|
1406
|
+
return !!text && text.trim().length > 0;
|
|
1407
|
+
}
|
|
1408
|
+
fieldPlaceholder() {
|
|
1409
|
+
const base = this.placeholder();
|
|
1410
|
+
const lang = this.activeLang().toUpperCase();
|
|
1411
|
+
if (base) {
|
|
1412
|
+
return lang ? `${base} (${lang})` : base;
|
|
1413
|
+
}
|
|
1414
|
+
return lang ? `(${lang})` : '';
|
|
1415
|
+
}
|
|
1416
|
+
select(lang) {
|
|
1417
|
+
this.manual.set(lang);
|
|
1418
|
+
this.onTouched();
|
|
1419
|
+
}
|
|
1420
|
+
write(event) {
|
|
1421
|
+
const lang = this.activeLang();
|
|
1422
|
+
if (!lang) {
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const text = event.target.value;
|
|
1426
|
+
const next = { ...this.valueMap() };
|
|
1427
|
+
if (text) {
|
|
1428
|
+
next[lang] = text;
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
delete next[lang];
|
|
1432
|
+
}
|
|
1433
|
+
this.valueMap.set(next);
|
|
1434
|
+
this.onChange(next);
|
|
1435
|
+
}
|
|
1436
|
+
// --- ControlValueAccessor -------------------------------------------------
|
|
1437
|
+
onChange = () => { };
|
|
1438
|
+
onTouched = () => { };
|
|
1439
|
+
writeValue(value) {
|
|
1440
|
+
this.valueMap.set(value ? { ...value } : {});
|
|
1441
|
+
}
|
|
1442
|
+
registerOnChange(fn) {
|
|
1443
|
+
this.onChange = fn;
|
|
1444
|
+
}
|
|
1445
|
+
registerOnTouched(fn) {
|
|
1446
|
+
this.onTouched = fn;
|
|
1447
|
+
}
|
|
1448
|
+
setDisabledState(isDisabled) {
|
|
1449
|
+
this.cvaDisabled.set(isDisabled);
|
|
1450
|
+
}
|
|
1451
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsLocalizedInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1452
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsLocalizedInput, isStandalone: true, selector: "sds-localized-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, languages: { classPropertyName: "languages", publicName: "languages", isSignal: true, isRequired: false, transformFunction: null }, currentLanguage: { classPropertyName: "currentLanguage", publicName: "currentLanguage", isSignal: true, isRequired: false, transformFunction: null }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, languageSwitcherLabel: { classPropertyName: "languageSwitcherLabel", publicName: "languageSwitcherLabel", isSignal: true, isRequired: false, transformFunction: null }, untranslatedLabel: { classPropertyName: "untranslatedLabel", publicName: "untranslatedLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [
|
|
1453
|
+
{
|
|
1454
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1455
|
+
useExisting: forwardRef(() => SdsLocalizedInput),
|
|
1456
|
+
multi: true,
|
|
1457
|
+
},
|
|
1458
|
+
], ngImport: i0, template: `
|
|
1459
|
+
@if (label()) {
|
|
1460
|
+
<label [for]="inputId" class="mb-1.5 block text-sm font-medium text-ink">{{ label() }}</label>
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
<div
|
|
1464
|
+
class="relative flex items-stretch rounded-control border bg-white text-ink transition-colors"
|
|
1465
|
+
[class]="wrapperClasses()"
|
|
1466
|
+
>
|
|
1467
|
+
@if (multiline()) {
|
|
1468
|
+
<textarea
|
|
1469
|
+
[id]="inputId"
|
|
1470
|
+
[rows]="rows()"
|
|
1471
|
+
[placeholder]="fieldPlaceholder()"
|
|
1472
|
+
[disabled]="isDisabled()"
|
|
1473
|
+
[value]="current()"
|
|
1474
|
+
(input)="write($event)"
|
|
1475
|
+
(blur)="onTouched()"
|
|
1476
|
+
class="min-w-0 flex-1 resize-y bg-transparent px-3.5 py-2 outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
|
|
1477
|
+
></textarea>
|
|
1478
|
+
} @else {
|
|
1479
|
+
<input
|
|
1480
|
+
[id]="inputId"
|
|
1481
|
+
[placeholder]="fieldPlaceholder()"
|
|
1482
|
+
[disabled]="isDisabled()"
|
|
1483
|
+
[value]="current()"
|
|
1484
|
+
(input)="write($event)"
|
|
1485
|
+
(blur)="onTouched()"
|
|
1486
|
+
class="min-w-0 flex-1 bg-transparent px-3.5 outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
|
|
1487
|
+
/>
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
@if (languages().length > 1) {
|
|
1491
|
+
<div
|
|
1492
|
+
class="flex shrink-0 items-center gap-0.5 self-start p-1"
|
|
1493
|
+
[class]="multiline() ? '' : 'self-center'"
|
|
1494
|
+
role="tablist"
|
|
1495
|
+
[attr.aria-label]="languageSwitcherLabel()"
|
|
1496
|
+
>
|
|
1497
|
+
@for (lang of languages(); track lang) {
|
|
1498
|
+
<button
|
|
1499
|
+
type="button"
|
|
1500
|
+
role="tab"
|
|
1501
|
+
tabindex="-1"
|
|
1502
|
+
[disabled]="isDisabled()"
|
|
1503
|
+
(click)="select(lang)"
|
|
1504
|
+
[attr.aria-selected]="activeLang() === lang"
|
|
1505
|
+
[attr.title]="lang.toUpperCase() + (filled(lang) ? '' : ' — ' + untranslatedLabel())"
|
|
1506
|
+
class="flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase leading-none transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
1507
|
+
[class]="
|
|
1508
|
+
activeLang() === lang
|
|
1509
|
+
? 'bg-brand-50 text-brand-700'
|
|
1510
|
+
: 'text-ink-faint hover:bg-muted hover:text-ink-soft'
|
|
1511
|
+
"
|
|
1512
|
+
>
|
|
1513
|
+
{{ lang }}
|
|
1514
|
+
<span
|
|
1515
|
+
class="inline-block h-1 w-1 rounded-full"
|
|
1516
|
+
[class]="filled(lang) ? 'bg-success' : 'bg-current/30'"
|
|
1517
|
+
></span>
|
|
1518
|
+
</button>
|
|
1519
|
+
}
|
|
1520
|
+
</div>
|
|
1521
|
+
}
|
|
1522
|
+
</div>
|
|
1523
|
+
|
|
1524
|
+
@if (hint()) {
|
|
1525
|
+
<p class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
|
|
1526
|
+
}
|
|
1527
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1528
|
+
}
|
|
1529
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsLocalizedInput, decorators: [{
|
|
1530
|
+
type: Component,
|
|
1531
|
+
args: [{
|
|
1532
|
+
selector: 'sds-localized-input',
|
|
1533
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1534
|
+
providers: [
|
|
1535
|
+
{
|
|
1536
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1537
|
+
useExisting: forwardRef(() => SdsLocalizedInput),
|
|
1538
|
+
multi: true,
|
|
1539
|
+
},
|
|
1540
|
+
],
|
|
1541
|
+
host: { class: 'block' },
|
|
1542
|
+
template: `
|
|
1543
|
+
@if (label()) {
|
|
1544
|
+
<label [for]="inputId" class="mb-1.5 block text-sm font-medium text-ink">{{ label() }}</label>
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
<div
|
|
1548
|
+
class="relative flex items-stretch rounded-control border bg-white text-ink transition-colors"
|
|
1549
|
+
[class]="wrapperClasses()"
|
|
1550
|
+
>
|
|
1551
|
+
@if (multiline()) {
|
|
1552
|
+
<textarea
|
|
1553
|
+
[id]="inputId"
|
|
1554
|
+
[rows]="rows()"
|
|
1555
|
+
[placeholder]="fieldPlaceholder()"
|
|
1556
|
+
[disabled]="isDisabled()"
|
|
1557
|
+
[value]="current()"
|
|
1558
|
+
(input)="write($event)"
|
|
1559
|
+
(blur)="onTouched()"
|
|
1560
|
+
class="min-w-0 flex-1 resize-y bg-transparent px-3.5 py-2 outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
|
|
1561
|
+
></textarea>
|
|
1562
|
+
} @else {
|
|
1563
|
+
<input
|
|
1564
|
+
[id]="inputId"
|
|
1565
|
+
[placeholder]="fieldPlaceholder()"
|
|
1566
|
+
[disabled]="isDisabled()"
|
|
1567
|
+
[value]="current()"
|
|
1568
|
+
(input)="write($event)"
|
|
1569
|
+
(blur)="onTouched()"
|
|
1570
|
+
class="min-w-0 flex-1 bg-transparent px-3.5 outline-none placeholder:text-current/50 disabled:cursor-not-allowed"
|
|
1571
|
+
/>
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
@if (languages().length > 1) {
|
|
1575
|
+
<div
|
|
1576
|
+
class="flex shrink-0 items-center gap-0.5 self-start p-1"
|
|
1577
|
+
[class]="multiline() ? '' : 'self-center'"
|
|
1578
|
+
role="tablist"
|
|
1579
|
+
[attr.aria-label]="languageSwitcherLabel()"
|
|
1580
|
+
>
|
|
1581
|
+
@for (lang of languages(); track lang) {
|
|
1582
|
+
<button
|
|
1583
|
+
type="button"
|
|
1584
|
+
role="tab"
|
|
1585
|
+
tabindex="-1"
|
|
1586
|
+
[disabled]="isDisabled()"
|
|
1587
|
+
(click)="select(lang)"
|
|
1588
|
+
[attr.aria-selected]="activeLang() === lang"
|
|
1589
|
+
[attr.title]="lang.toUpperCase() + (filled(lang) ? '' : ' — ' + untranslatedLabel())"
|
|
1590
|
+
class="flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase leading-none transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
1591
|
+
[class]="
|
|
1592
|
+
activeLang() === lang
|
|
1593
|
+
? 'bg-brand-50 text-brand-700'
|
|
1594
|
+
: 'text-ink-faint hover:bg-muted hover:text-ink-soft'
|
|
1595
|
+
"
|
|
1596
|
+
>
|
|
1597
|
+
{{ lang }}
|
|
1598
|
+
<span
|
|
1599
|
+
class="inline-block h-1 w-1 rounded-full"
|
|
1600
|
+
[class]="filled(lang) ? 'bg-success' : 'bg-current/30'"
|
|
1601
|
+
></span>
|
|
1602
|
+
</button>
|
|
1603
|
+
}
|
|
1604
|
+
</div>
|
|
1605
|
+
}
|
|
1606
|
+
</div>
|
|
1607
|
+
|
|
1608
|
+
@if (hint()) {
|
|
1609
|
+
<p class="mt-1.5 text-xs text-ink-faint">{{ hint() }}</p>
|
|
1610
|
+
}
|
|
1611
|
+
`,
|
|
1612
|
+
}]
|
|
1613
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], languages: [{ type: i0.Input, args: [{ isSignal: true, alias: "languages", required: false }] }], currentLanguage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentLanguage", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], languageSwitcherLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "languageSwitcherLabel", required: false }] }], untranslatedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "untranslatedLabel", required: false }] }] } });
|
|
1614
|
+
|
|
604
1615
|
let nextId$a = 0;
|
|
605
1616
|
const SIZES$8 = {
|
|
606
1617
|
md: 'h-12 w-11 text-lg',
|
|
@@ -7398,6 +8409,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
7398
8409
|
}]
|
|
7399
8410
|
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], striped: [{ type: i0.Input, args: [{ isSignal: true, alias: "striped", required: false }] }], gridlines: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridlines", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], paginator: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginator", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], showCurrentPageReport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCurrentPageReport", required: false }] }], currentPageReportTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPageReportTemplate", required: false }] }], rowsPerPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageLabel", required: false }] }], paginationLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationLabel", required: false }] }], firstPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstPageLabel", required: false }] }], previousPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousPageLabel", required: false }] }], nextPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextPageLabel", required: false }] }], lastPageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "lastPageLabel", required: false }] }], pageLinkLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageLinkLabel", required: false }] }], globalFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilter", required: false }] }], globalFilterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterPlaceholder", required: false }] }], globalFilterAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterAriaLabel", required: false }] }], globalFilterFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalFilterFields", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], scrollHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollHeight", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }], rowUnselect: [{ type: i0.Output, args: ["rowUnselect"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowExpand: [{ type: i0.Output, args: ["rowExpand"] }], rowCollapse: [{ type: i0.Output, args: ["rowCollapse"] }], colReorder: [{ type: i0.Output, args: ["colReorder"] }], cellTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdsTableCell), { isSignal: true }] }], rowExpansion: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsTableRowExpansion), { isSignal: true }] }] } });
|
|
7400
8411
|
|
|
8412
|
+
/**
|
|
8413
|
+
* A tiny transient-notification bus. Application code calls {@link success} / {@link error} / {@link info}
|
|
8414
|
+
* after an action; a single {@link SdsToastHost} mounted in the app shell renders the queue and each toast
|
|
8415
|
+
* auto-dismisses. Deliberately minimal — no positions, no actions-in-toast — because it exists to confirm
|
|
8416
|
+
* an action, not to be a notification centre.
|
|
8417
|
+
*
|
|
8418
|
+
* `providedIn: 'root'`, so injecting it anywhere shares one queue:
|
|
8419
|
+
*
|
|
8420
|
+
* ```ts
|
|
8421
|
+
* private readonly toasts = inject(SdsToastService);
|
|
8422
|
+
* this.toasts.success('Enregistré.');
|
|
8423
|
+
* ```
|
|
8424
|
+
*
|
|
8425
|
+
* Mount the host once (e.g. in the shell): `<sds-toast-host />`.
|
|
8426
|
+
*/
|
|
8427
|
+
class SdsToastService {
|
|
8428
|
+
seq = 0;
|
|
8429
|
+
toasts = signal([], ...(ngDevMode ? [{ debugName: "toasts" }] : []));
|
|
8430
|
+
/** How long a toast stays, in ms, by kind. Errors linger; success/info are brief. */
|
|
8431
|
+
static TTL = {
|
|
8432
|
+
success: 3500,
|
|
8433
|
+
info: 3500,
|
|
8434
|
+
error: 6000,
|
|
8435
|
+
};
|
|
8436
|
+
success(text) {
|
|
8437
|
+
this.push('success', text);
|
|
8438
|
+
}
|
|
8439
|
+
error(text) {
|
|
8440
|
+
this.push('error', text);
|
|
8441
|
+
}
|
|
8442
|
+
info(text) {
|
|
8443
|
+
this.push('info', text);
|
|
8444
|
+
}
|
|
8445
|
+
dismiss(id) {
|
|
8446
|
+
this.toasts.update((list) => list.filter((t) => t.id !== id));
|
|
8447
|
+
}
|
|
8448
|
+
push(kind, text) {
|
|
8449
|
+
const id = ++this.seq;
|
|
8450
|
+
this.toasts.update((list) => [...list, { id, kind, text }]);
|
|
8451
|
+
setTimeout(() => this.dismiss(id), SdsToastService.TTL[kind]);
|
|
8452
|
+
}
|
|
8453
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8454
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsToastService, providedIn: 'root' });
|
|
8455
|
+
}
|
|
8456
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsToastService, decorators: [{
|
|
8457
|
+
type: Injectable,
|
|
8458
|
+
args: [{ providedIn: 'root' }]
|
|
8459
|
+
}] });
|
|
8460
|
+
|
|
8461
|
+
/**
|
|
8462
|
+
* Renders the {@link SdsToastService} queue, fixed bottom-centre. Mount it once, in the app shell:
|
|
8463
|
+
* `<sds-toast-host />`. Application code pushes toasts through the injected service.
|
|
8464
|
+
*
|
|
8465
|
+
* Accessibility: the two live regions are ALWAYS in the DOM (even when empty), because a live region must
|
|
8466
|
+
* pre-exist for its later content changes to be announced. Errors go to an assertive `role="alert"` region
|
|
8467
|
+
* so a failure interrupts; success/info go to a polite region so they queue.
|
|
8468
|
+
*/
|
|
8469
|
+
class SdsToastHost {
|
|
8470
|
+
toasts = inject(SdsToastService);
|
|
8471
|
+
/**
|
|
8472
|
+
* Accessible name for each toast's close button. French default so existing consumers are unchanged; the
|
|
8473
|
+
* button is icon-only, so an app serving more than one language MUST translate it.
|
|
8474
|
+
*/
|
|
8475
|
+
closeLabel = input('Fermer', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
|
|
8476
|
+
errors = computed(() => this.toasts.toasts().filter((t) => t.kind === 'error'), ...(ngDevMode ? [{ debugName: "errors" }] : []));
|
|
8477
|
+
notices = computed(() => this.toasts.toasts().filter((t) => t.kind !== 'error'), ...(ngDevMode ? [{ debugName: "notices" }] : []));
|
|
8478
|
+
icon(kind) {
|
|
8479
|
+
return kind === 'success' ? 'check-circle' : kind === 'error' ? 'alert-circle' : 'info';
|
|
8480
|
+
}
|
|
8481
|
+
tone(kind) {
|
|
8482
|
+
return kind === 'success' ? 'text-success' : kind === 'error' ? 'text-danger' : 'text-accent-blue';
|
|
8483
|
+
}
|
|
8484
|
+
border(kind) {
|
|
8485
|
+
const accent = kind === 'success' ? 'border-l-success' : kind === 'error' ? 'border-l-danger' : 'border-l-accent-blue';
|
|
8486
|
+
return `border-l-4 ${accent} border-y-border border-r-border`;
|
|
8487
|
+
}
|
|
8488
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsToastHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8489
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsToastHost, isStandalone: true, selector: "sds-toast-host", inputs: { closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
8490
|
+
<div class="pointer-events-none fixed inset-x-0 bottom-6 z-[100] flex flex-col items-center gap-2 px-4">
|
|
8491
|
+
<div role="alert" aria-live="assertive" class="flex w-full flex-col items-center gap-2">
|
|
8492
|
+
@for (t of errors(); track t.id) {
|
|
8493
|
+
<ng-container [ngTemplateOutlet]="toast" [ngTemplateOutletContext]="{ t }" />
|
|
8494
|
+
}
|
|
8495
|
+
</div>
|
|
8496
|
+
<div aria-live="polite" class="flex w-full flex-col items-center gap-2">
|
|
8497
|
+
@for (t of notices(); track t.id) {
|
|
8498
|
+
<ng-container [ngTemplateOutlet]="toast" [ngTemplateOutletContext]="{ t }" />
|
|
8499
|
+
}
|
|
8500
|
+
</div>
|
|
8501
|
+
</div>
|
|
8502
|
+
|
|
8503
|
+
<ng-template #toast let-t="t">
|
|
8504
|
+
<div
|
|
8505
|
+
class="pointer-events-auto flex max-w-md items-start gap-2.5 rounded-card border bg-surface px-4 py-3 text-sm shadow-lg"
|
|
8506
|
+
[class]="border(t.kind)"
|
|
8507
|
+
>
|
|
8508
|
+
<sds-icon [name]="icon(t.kind)" [size]="18" class="mt-0.5 shrink-0" [class]="tone(t.kind)" />
|
|
8509
|
+
<span class="text-ink">{{ t.text }}</span>
|
|
8510
|
+
<button
|
|
8511
|
+
type="button"
|
|
8512
|
+
(click)="toasts.dismiss(t.id)"
|
|
8513
|
+
[attr.aria-label]="closeLabel()"
|
|
8514
|
+
class="ml-1 shrink-0 rounded p-0.5 text-ink-faint hover:bg-muted hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500"
|
|
8515
|
+
>
|
|
8516
|
+
<sds-icon name="x" [size]="15" />
|
|
8517
|
+
</button>
|
|
8518
|
+
</div>
|
|
8519
|
+
</ng-template>
|
|
8520
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8521
|
+
}
|
|
8522
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsToastHost, decorators: [{
|
|
8523
|
+
type: Component,
|
|
8524
|
+
args: [{
|
|
8525
|
+
selector: 'sds-toast-host',
|
|
8526
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8527
|
+
imports: [NgTemplateOutlet, SdsIcon],
|
|
8528
|
+
template: `
|
|
8529
|
+
<div class="pointer-events-none fixed inset-x-0 bottom-6 z-[100] flex flex-col items-center gap-2 px-4">
|
|
8530
|
+
<div role="alert" aria-live="assertive" class="flex w-full flex-col items-center gap-2">
|
|
8531
|
+
@for (t of errors(); track t.id) {
|
|
8532
|
+
<ng-container [ngTemplateOutlet]="toast" [ngTemplateOutletContext]="{ t }" />
|
|
8533
|
+
}
|
|
8534
|
+
</div>
|
|
8535
|
+
<div aria-live="polite" class="flex w-full flex-col items-center gap-2">
|
|
8536
|
+
@for (t of notices(); track t.id) {
|
|
8537
|
+
<ng-container [ngTemplateOutlet]="toast" [ngTemplateOutletContext]="{ t }" />
|
|
8538
|
+
}
|
|
8539
|
+
</div>
|
|
8540
|
+
</div>
|
|
8541
|
+
|
|
8542
|
+
<ng-template #toast let-t="t">
|
|
8543
|
+
<div
|
|
8544
|
+
class="pointer-events-auto flex max-w-md items-start gap-2.5 rounded-card border bg-surface px-4 py-3 text-sm shadow-lg"
|
|
8545
|
+
[class]="border(t.kind)"
|
|
8546
|
+
>
|
|
8547
|
+
<sds-icon [name]="icon(t.kind)" [size]="18" class="mt-0.5 shrink-0" [class]="tone(t.kind)" />
|
|
8548
|
+
<span class="text-ink">{{ t.text }}</span>
|
|
8549
|
+
<button
|
|
8550
|
+
type="button"
|
|
8551
|
+
(click)="toasts.dismiss(t.id)"
|
|
8552
|
+
[attr.aria-label]="closeLabel()"
|
|
8553
|
+
class="ml-1 shrink-0 rounded p-0.5 text-ink-faint hover:bg-muted hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500"
|
|
8554
|
+
>
|
|
8555
|
+
<sds-icon name="x" [size]="15" />
|
|
8556
|
+
</button>
|
|
8557
|
+
</div>
|
|
8558
|
+
</ng-template>
|
|
8559
|
+
`,
|
|
8560
|
+
}]
|
|
8561
|
+
}], propDecorators: { closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }] } });
|
|
8562
|
+
|
|
7401
8563
|
/**
|
|
7402
8564
|
* Shared collapse state for the sidebar and its nav items.
|
|
7403
8565
|
*
|
|
@@ -8213,5 +9375,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
8213
9375
|
* Generated bundle index. Do not edit.
|
|
8214
9376
|
*/
|
|
8215
9377
|
|
|
8216
|
-
export { SDS_ICONS, SDS_PHONE_COUNTRIES, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAvatar, SdsBadge, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsDatePicker, SdsIcon, SdsInput, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsSelect, SdsShell, SdsSidebar, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsToggleSwitch, SdsTopbar, SidebarStore, addDays, addMonths, addMonthsKeepDay, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isoDay, pad2, sdsFormatNational, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth };
|
|
9378
|
+
export { SDS_ICONS, SDS_PHONE_COUNTRIES, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAvatar, SdsBadge, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsDatePicker, SdsIcon, SdsInput, SdsInputNumber, SdsLocalizedInput, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsSelect, SdsShell, SdsSidebar, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SidebarStore, addDays, addMonths, addMonthsKeepDay, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isoDay, pad2, sdsFormatNational, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth };
|
|
8217
9379
|
//# sourceMappingURL=besaitech-ng-design-system.mjs.map
|