@uni-design-system/uni-angular 9.0.1 → 10.1.0

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 { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, ElementRef, Directive, model, afterRenderEffect, Renderer2, output, viewChild, ViewChild, viewChildren, contentChildren, effect, untracked, afterNextRender, booleanAttribute } from '@angular/core';
2
+ import { signal, inject, DestroyRef, computed, linkedSignal, resource, Injectable, InjectionToken, input, ChangeDetectionStrategy, Component, ElementRef, Directive, model, afterRenderEffect, Renderer2, output, viewChild, ViewChild, viewChildren, effect, untracked, isDevMode, contentChildren, afterNextRender, forwardRef, booleanAttribute } from '@angular/core';
3
3
  import { injectGlobal, css, keyframes } from '@emotion/css';
4
4
  import { UniThemes, LightTheme, toTypefaces, parseTheme, formatThemeIssues, hydrateTheme, createThemeFromPalette, Z_INDEX, removeInputPlatformStyling, fadeIn, fadeOut, expandDuration, expandFadeIn, collapseFadeOut, HOVER_OR_KEYBOARD_FOCUS, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
5
5
  import { NgTemplateOutlet, NgClass, CommonModule } from '@angular/common';
@@ -30,9 +30,23 @@ function resolveFocusTarget(element) {
30
30
  /**
31
31
  * Visually hides content while keeping it available to screen readers.
32
32
  * Use for text alternatives (e.g. badge counts, icon-only affordances).
33
+ *
34
+ * `fixed`, not `absolute`, and that is load-bearing. An absolutely positioned
35
+ * box resolves its containing block to the nearest *positioned* ancestor —
36
+ * which, since the controls emitting these spans are `position: static`, is
37
+ * whatever positioned box happens to be above them in the consumer's layout,
38
+ * often several scroll containers up. The span then skips every intervening
39
+ * `overflow: auto` and lands in that distant ancestor's scrollable overflow,
40
+ * turning 1x1 of invisible text into real scrollable distance in a box that
41
+ * never opted into scrolling. A fixed box's containing block is the viewport,
42
+ * so it joins no ancestor's scrollable overflow at all.
43
+ *
44
+ * Caveat: inside a `transform`ed (or `filter`ed/`contain`ed) ancestor a fixed
45
+ * box re-anchors to that ancestor. Harmless here — the element is 1x1 and
46
+ * clipped to nothing, so where it lands never matters, only what it overflows.
33
47
  */
34
48
  const visuallyHidden = {
35
- position: 'absolute',
49
+ position: 'fixed',
36
50
  width: 1,
37
51
  height: 1,
38
52
  padding: 0,
@@ -367,6 +381,694 @@ const splitDateTime = (value) => {
367
381
  /** One combined value only when both parts are present. */
368
382
  const joinDateTime = (date, time) => date && time ? `${date}T${time}` : undefined;
369
383
 
384
+ /**
385
+ * Canonical numeric value shapes shared by `uni-number-input`,
386
+ * `uni-quantity-stepper`, `uni-number-range-input` and `uni-slider`.
387
+ *
388
+ * The components' internal source of truth is a **canonical decimal string** —
389
+ * optional sign, digits, an optional `.`, no grouping and no affix:
390
+ * `'-1234.56'`. The bound `number` is its projection, emitted on commit.
391
+ *
392
+ * Nothing numeric passes through a float, because floats give wrong answers to
393
+ * questions people ask of money: `0.1 + 0.2` is `0.30000000000000004`, and
394
+ * `(1.15).toFixed(1)` is `'1.1'` — 1.15 is really 1.1499999999999999, so the
395
+ * platform rounds a tie that isn't there. See `decimal.helper.ts`.
396
+ */
397
+
398
+ /** Canonical decimal, permitting a leading `+` and a bare `.5` / `5.` form. */
399
+ const CANONICAL = /^[+-]?(\d+(\.\d*)?|\.\d+)$/;
400
+ /** `1.5e-7`, `1e21` — what `String(number)` produces outside 1e-7…1e21. */
401
+ const EXPONENTIAL = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/;
402
+ /** True when `text` is already a canonical decimal (leading/trailing space allowed). */
403
+ const isCanonicalDecimal = (text) => CANONICAL.test(text.trim());
404
+ /** Fraction-digit count. `'1.250'` → 3, `'12'` → 0, `'5.'` → 0. */
405
+ const decimalScale = (value) => {
406
+ const point = value.indexOf('.');
407
+ return point < 0 ? 0 : value.length - point - 1;
408
+ };
409
+ /**
410
+ * Canonical decimal → integer scaled by `10^scale`. Fraction digits beyond
411
+ * `scale` are truncated, so callers that must not lose them pass a `scale` at
412
+ * least `decimalScale(value)`.
413
+ */
414
+ const toScaled = (value, scale) => {
415
+ let text = value.trim();
416
+ const negative = text.startsWith('-');
417
+ if (negative || text.startsWith('+'))
418
+ text = text.slice(1);
419
+ const [integer, fraction = ''] = text.split('.');
420
+ const padded = (fraction + '0'.repeat(scale)).slice(0, scale);
421
+ const digits = (integer + padded).replace(/^0+(?=\d)/, '');
422
+ const scaled = BigInt(digits || '0');
423
+ return negative ? -scaled : scaled;
424
+ };
425
+ /** Scaled integer → canonical decimal, with trailing fraction zeros trimmed. */
426
+ const fromScaled = (scaled, scale) => {
427
+ const negative = scaled < 0n;
428
+ let digits = (negative ? -scaled : scaled).toString();
429
+ if (scale > 0) {
430
+ digits = digits.padStart(scale + 1, '0');
431
+ digits = `${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
432
+ // `0+$` stops at the point, so '10.00' loses only its fraction zeros.
433
+ digits = digits.replace(/0+$/, '').replace(/\.$/, '');
434
+ }
435
+ if (digits === '' || digits === '0')
436
+ return '0';
437
+ return (negative ? '-' : '') + digits;
438
+ };
439
+ /**
440
+ * Strip a leading `+`, leading zeros and trailing fraction zeros: `'+01.50'`
441
+ * → `'1.5'`. Requires a canonical decimal; guard with `isCanonicalDecimal`.
442
+ */
443
+ const normalizeDecimal = (value) => {
444
+ const scale = decimalScale(value);
445
+ return fromScaled(toScaled(value, scale), scale);
446
+ };
447
+ /**
448
+ * Any numeric input → canonical decimal, expanding the exponential notation
449
+ * `String(number)` produces outside 1e-7…1e21. A `step` of `1e-7` would
450
+ * otherwise reach the arithmetic as the literal text `'1e-7'`.
451
+ *
452
+ * Throws on text that is not numeric at all — every caller here passes either
453
+ * a `number` input or text already cleared by the parser.
454
+ */
455
+ const toDecimal = (value) => {
456
+ const text = String(value).trim();
457
+ if (CANONICAL.test(text))
458
+ return normalizeDecimal(text);
459
+ const match = EXPONENTIAL.exec(text);
460
+ if (!match)
461
+ throw new RangeError(`Not a decimal number: ${JSON.stringify(text)}`);
462
+ const [, sign, integer, fraction = '', exponent] = match;
463
+ const digits = integer + fraction;
464
+ const point = integer.length + Number(exponent);
465
+ let expanded;
466
+ if (point <= 0)
467
+ expanded = `0.${'0'.repeat(-point)}${digits}`;
468
+ else if (point >= digits.length)
469
+ expanded = digits + '0'.repeat(point - digits.length);
470
+ else
471
+ expanded = `${digits.slice(0, point)}.${digits.slice(point)}`;
472
+ return normalizeDecimal((sign === '-' ? '-' : '') + expanded);
473
+ };
474
+ /** `-1` when `a < b`, `1` when `a > b`, `0` when equal. `'1.50'` equals `'1.5'`. */
475
+ const compareDecimal = (a, b) => {
476
+ const scale = Math.max(decimalScale(a), decimalScale(b));
477
+ const left = toScaled(a, scale);
478
+ const right = toScaled(b, scale);
479
+ return left < right ? -1 : left > right ? 1 : 0;
480
+ };
481
+ /**
482
+ * Round to `fractionDigits`, breaking ties per `mode`. Exact where
483
+ * `Number.prototype.toFixed` is not — see the file header.
484
+ */
485
+ const roundDecimal = (value, fractionDigits, mode = 'half-up') => {
486
+ const digits = Math.max(0, Math.trunc(fractionDigits));
487
+ const scale = decimalScale(value);
488
+ if (scale <= digits)
489
+ return normalizeDecimal(value);
490
+ const scaled = toScaled(value, scale);
491
+ const divisor = 10n ** BigInt(scale - digits);
492
+ let quotient = scaled / divisor; // BigInt division truncates toward zero
493
+ const remainder = scaled % divisor;
494
+ if (remainder === 0n)
495
+ return fromScaled(quotient, digits);
496
+ const negative = scaled < 0n;
497
+ const twiceRemainder = (remainder < 0n ? -remainder : remainder) * 2n;
498
+ const away = () => {
499
+ quotient += negative ? -1n : 1n;
500
+ };
501
+ switch (mode) {
502
+ case 'trunc':
503
+ break;
504
+ case 'ceil':
505
+ if (!negative)
506
+ quotient += 1n;
507
+ break;
508
+ case 'floor':
509
+ if (negative)
510
+ quotient -= 1n;
511
+ break;
512
+ case 'half-even':
513
+ if (twiceRemainder > divisor || (twiceRemainder === divisor && quotient % 2n !== 0n))
514
+ away();
515
+ break;
516
+ default: // half-up — a tie goes away from zero
517
+ if (twiceRemainder >= divisor)
518
+ away();
519
+ }
520
+ return fromScaled(quotient, digits);
521
+ };
522
+ /**
523
+ * Multiply by `10^places`, exactly. Used for the percent preset's
524
+ * fraction ⇄ display shift (`0.15` ⇄ `15`) and for deriving a default
525
+ * large step of `step × 10` without touching a float.
526
+ */
527
+ const shiftDecimal = (value, places) => {
528
+ const currentScale = decimalScale(value);
529
+ let scaled = toScaled(value, currentScale);
530
+ let scale = currentScale - places;
531
+ if (scale < 0) {
532
+ scaled *= 10n ** BigInt(-scale);
533
+ scale = 0;
534
+ }
535
+ return fromScaled(scaled, scale);
536
+ };
537
+ /**
538
+ * Hold a value inside its fences, reporting which one it hit so the caller can
539
+ * announce it. Clamping belongs on commit, never per keystroke: a `min=10`
540
+ * field that clamps live can never be typed into, because the `1` becomes `10`
541
+ * before the `5` arrives.
542
+ */
543
+ const clampDecimal = (value, min, max) => {
544
+ if (min != null && compareDecimal(value, toDecimal(min)) < 0) {
545
+ return { value: toDecimal(min), hit: 'min' };
546
+ }
547
+ if (max != null && compareDecimal(value, toDecimal(max)) > 0) {
548
+ return { value: toDecimal(max), hit: 'max' };
549
+ }
550
+ return { value, hit: null };
551
+ };
552
+ /**
553
+ * One step from `current`, in `direction` (`1` up, `-1` down).
554
+ *
555
+ * Steps land on the grid `origin + n · step`, where `origin` is `min` by
556
+ * default. A value that is *off* the grid snaps to the nearest grid point **in
557
+ * the direction of travel** rather than jumping past it: with `min=5, step=10`
558
+ * the grid is 5, 15, 25, and stepping up from 7 gives 15, not 17.
559
+ *
560
+ * Fences stop the value; they never wrap unless `wrap` is set and both bounds
561
+ * are defined. Returns `current` unchanged when `step` is zero.
562
+ */
563
+ const stepDecimal = (current, direction, config = {}) => {
564
+ const step = toDecimal(config.step ?? 1);
565
+ const origin = config.stepOrigin === 'zero' || config.min == null ? '0' : toDecimal(config.min);
566
+ // One shared scale keeps every term an exact integer.
567
+ const scale = Math.max(decimalScale(current), decimalScale(step), decimalScale(origin), config.min == null ? 0 : decimalScale(toDecimal(config.min)), config.max == null ? 0 : decimalScale(toDecimal(config.max)));
568
+ const stepBy = toScaled(step, scale);
569
+ if (stepBy === 0n)
570
+ return current;
571
+ const value = toScaled(current, scale);
572
+ const anchor = toScaled(origin, scale);
573
+ let offset = (value - anchor) % stepBy;
574
+ if (offset < 0n)
575
+ offset += stepBy;
576
+ let next;
577
+ if (offset === 0n)
578
+ next = value + (direction > 0 ? stepBy : -stepBy);
579
+ else
580
+ next = direction > 0 ? value + (stepBy - offset) : value - offset;
581
+ const min = config.min == null ? null : toScaled(toDecimal(config.min), scale);
582
+ const max = config.max == null ? null : toScaled(toDecimal(config.max), scale);
583
+ if (config.wrap && min != null && max != null) {
584
+ // The cycle includes one step past `max` so 23 → 0 rather than 23 → 23.
585
+ const span = max - min + stepBy;
586
+ let position = (next - min) % span;
587
+ if (position < 0n)
588
+ position += span;
589
+ next = min + position;
590
+ }
591
+ else {
592
+ if (max != null && next > max)
593
+ next = max;
594
+ if (min != null && next < min)
595
+ next = min;
596
+ }
597
+ return fromScaled(next, scale);
598
+ };
599
+
600
+ /**
601
+ * Locale-aware number parsing and formatting, on top of the exact arithmetic
602
+ * in `decimal.helper.ts`. `Intl.NumberFormat` supplies both directions —
603
+ * separators, currency placement and digit systems all come from the locale
604
+ * and none of them is hardcoded per language. No number library.
605
+ *
606
+ * The reason this exists rather than `<input type="number">`: per the HTML
607
+ * value sanitization algorithm, a number input whose text is not a valid
608
+ * floating-point number reports `value === ''`. Type `12,50` as most of Europe
609
+ * does, or paste `1,234.56` from a spreadsheet, and the app reads an empty
610
+ * field with no way to tell that from a blank one.
611
+ */
612
+ /** Preset defaults. `null` decimals means "ask `Intl` about the currency". */
613
+ const PRESETS$1 = {
614
+ decimal: { decimals: [0, 3], grouping: 'min2', inputMode: 'decimal' },
615
+ integer: { decimals: [0, 0], grouping: 'min2', inputMode: 'numeric' },
616
+ currency: { decimals: null, grouping: 'always', inputMode: 'decimal' },
617
+ percent: { decimals: [0, 2], grouping: 'min2', inputMode: 'decimal' },
618
+ };
619
+ /**
620
+ * First code point of each localized digit run we map back to ASCII:
621
+ * Arabic-Indic, Extended Arabic-Indic (Persian/Urdu), Devanagari, Bengali,
622
+ * Thai. Anything outside these still parses in its ASCII form.
623
+ */
624
+ const DIGIT_ZEROS = [0x0660, 0x06f0, 0x0966, 0x09e6, 0x0e50];
625
+ /** Arabic decimal separator and thousands separator. */
626
+ const ARABIC_DECIMAL = '٫';
627
+ const ARABIC_GROUP = '٬';
628
+ /**
629
+ * Locale separators plus, when a currency is given, its symbol, side and
630
+ * fraction digits. Memoized: constructing an `Intl.NumberFormat` is expensive
631
+ * and a field re-resolves this on every keystroke.
632
+ */
633
+ const localeNumberParts = memoize((locale, currency) => {
634
+ const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
635
+ const group = parts.find((part) => part.type === 'group')?.value ?? ',';
636
+ const decimal = parts.find((part) => part.type === 'decimal')?.value ?? '.';
637
+ if (!currency) {
638
+ return {
639
+ group,
640
+ decimal,
641
+ currencySymbol: '',
642
+ currencyLeading: true,
643
+ currencyDecimals: 2,
644
+ };
645
+ }
646
+ const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency });
647
+ const currencyParts = formatter.formatToParts(1);
648
+ const symbolIndex = currencyParts.findIndex((part) => part.type === 'currency');
649
+ const integerIndex = currencyParts.findIndex((part) => part.type === 'integer');
650
+ return {
651
+ group,
652
+ decimal,
653
+ currencySymbol: currencyParts[symbolIndex]?.value ?? '',
654
+ currencyLeading: symbolIndex < integerIndex,
655
+ currencyDecimals: formatter.resolvedOptions().maximumFractionDigits ?? 2,
656
+ };
657
+ });
658
+ /**
659
+ * Map localized digits and Arabic separators to ASCII, so `١٢٣٤٫٥` parses in
660
+ * `ar` and `१२३४.५` in `hi`.
661
+ */
662
+ const toAsciiDigits = (text) => {
663
+ let out = '';
664
+ for (const char of text) {
665
+ const code = char.codePointAt(0) ?? 0;
666
+ const zero = DIGIT_ZEROS.find((start) => code >= start && code <= start + 9);
667
+ if (zero != null)
668
+ out += String(code - zero);
669
+ else if (char === ARABIC_DECIMAL)
670
+ out += '.';
671
+ else if (char === ARABIC_GROUP)
672
+ continue;
673
+ else
674
+ out += char;
675
+ }
676
+ return out;
677
+ };
678
+ /**
679
+ * Evaluate `+ − × ÷ ( )` over decimal literals — shunting-yard, roughly thirty
680
+ * lines, and **never `eval`**. Returns a canonical decimal, or `null` when the
681
+ * text is not a well-formed expression.
682
+ *
683
+ * Floats are acceptable here in a way they are not elsewhere: this is a
684
+ * convenience path for spreadsheet muscle memory (`12*3`, `100/4+5`), and the
685
+ * result is settled to ten decimals before re-entering exact arithmetic.
686
+ * Division is the only operation that can produce a non-terminating decimal,
687
+ * and no exact representation would help there either.
688
+ */
689
+ const evaluateExpression = (text) => {
690
+ const source = text.replace(/×/g, '*').replace(/÷/g, '/').replace(/−/g, '-');
691
+ if (!/^[\d.\s+\-*/()]+$/.test(source))
692
+ return null;
693
+ // A bare number is not an expression — it belongs on the ordinary path.
694
+ if (!/[\d)]\s*[+\-*/]\s*[\d(.]/.test(source) && !source.includes('('))
695
+ return null;
696
+ const tokens = source.match(/\d+\.?\d*|\.\d+|[+\-*/()]/g);
697
+ if (!tokens)
698
+ return null;
699
+ const precedence = { '+': 1, '-': 1, '*': 2, '/': 2 };
700
+ const output = [];
701
+ const operators = [];
702
+ let previous = null;
703
+ for (const token of tokens) {
704
+ if (/^[\d.]/.test(token)) {
705
+ output.push(Number(token));
706
+ }
707
+ else if (token === '(') {
708
+ operators.push(token);
709
+ }
710
+ else if (token === ')') {
711
+ while (operators.length && operators[operators.length - 1] !== '(') {
712
+ output.push(operators.pop());
713
+ }
714
+ if (!operators.length)
715
+ return null;
716
+ operators.pop();
717
+ }
718
+ else {
719
+ // Unary minus: push an implicit 0 so `-3` and `(2+-3)` both work.
720
+ if (token === '-' && (previous === null || previous in precedence || previous === '(')) {
721
+ output.push(0);
722
+ }
723
+ while (operators.length &&
724
+ precedence[operators[operators.length - 1]] >= precedence[token]) {
725
+ output.push(operators.pop());
726
+ }
727
+ operators.push(token);
728
+ }
729
+ previous = token;
730
+ }
731
+ while (operators.length) {
732
+ const operator = operators.pop();
733
+ if (operator === '(')
734
+ return null;
735
+ output.push(operator);
736
+ }
737
+ const stack = [];
738
+ for (const token of output) {
739
+ if (typeof token === 'number') {
740
+ stack.push(token);
741
+ continue;
742
+ }
743
+ const right = stack.pop();
744
+ const left = stack.pop();
745
+ if (left === undefined || right === undefined)
746
+ return null;
747
+ stack.push(token === '+'
748
+ ? left + right
749
+ : token === '-'
750
+ ? left - right
751
+ : token === '*'
752
+ ? left * right
753
+ : left / right);
754
+ }
755
+ if (stack.length !== 1 || !Number.isFinite(stack[0]))
756
+ return null;
757
+ return normalizeDecimal(Number(stack[0].toFixed(10)).toString());
758
+ };
759
+ /** Fill in every preset, locale and currency default. */
760
+ const resolveNumberFormat = (config = {}) => {
761
+ const presetName = config.currency ? 'currency' : (config.preset ?? 'decimal');
762
+ const preset = PRESETS$1[presetName];
763
+ const locale = config.locale || 'en-US';
764
+ const parts = localeNumberParts(locale, config.currency);
765
+ const [minimumFractionDigits, maximumFractionDigits] = Array.isArray(config.decimals)
766
+ ? config.decimals
767
+ : config.decimals != null
768
+ ? [config.decimals, config.decimals]
769
+ : (preset.decimals ?? [parts.currencyDecimals, parts.currencyDecimals]);
770
+ const isPercent = presetName === 'percent';
771
+ const isInteger = presetName === 'integer';
772
+ // Adornments live outside the editable text, so the caret never walks over
773
+ // them and `prefix`/`suffix` can be any string without becoming parseable.
774
+ const prefix = config.prefix || (config.currency && parts.currencyLeading ? parts.currencySymbol : '');
775
+ const suffix = config.suffix ||
776
+ (config.currency && !parts.currencyLeading ? parts.currencySymbol : '') ||
777
+ (isPercent ? '%' : '');
778
+ return {
779
+ locale,
780
+ parts,
781
+ prefix,
782
+ suffix,
783
+ minimumFractionDigits,
784
+ maximumFractionDigits,
785
+ grouping: config.grouping !== undefined ? config.grouping : preset.grouping,
786
+ compact: config.numberFormat?.notation === 'compact',
787
+ isInteger,
788
+ shift: config.valueIsFraction ? 2 : 0,
789
+ roundingMode: config.roundingMode ?? 'half-up',
790
+ inputMode: isInteger && (config.min == null || config.min < 0) ? 'decimal' : preset.inputMode,
791
+ unitAnnouncement: config.unitAnnouncement,
792
+ };
793
+ };
794
+ /**
795
+ * Read a user's text into a canonical decimal in **model units**.
796
+ *
797
+ * Accepted, in order: canonical/ASCII (always, whatever the locale — it is
798
+ * what agents and APIs write), locale-grouped, affixed, localized digits,
799
+ * compact (`1.5k`), accounting negatives (`(1,234.56)` → `-1234.56`), and
800
+ * expressions when `allowExpressions` is set.
801
+ */
802
+ const parseNumber = (raw, format, options = {}) => {
803
+ const original = toAsciiDigits(String(raw)).trim();
804
+ if (original === '')
805
+ return { status: 'empty' };
806
+ let text = original;
807
+ const { prefix, suffix, parts } = format;
808
+ // People paste from spreadsheets: strip this field's own affixes, the
809
+ // currency symbol and code, and any stray percent sign.
810
+ if (prefix)
811
+ text = text.split(prefix).join('');
812
+ if (suffix)
813
+ text = text.split(suffix).join('');
814
+ if (options.currency) {
815
+ text = text.split(parts.currencySymbol).join('');
816
+ text = text.replace(new RegExp(options.currency, 'i'), '');
817
+ }
818
+ text = text.replace(/%/g, '');
819
+ // `\s` covers NBSP and the narrow/thin spaces a French copy-paste carries.
820
+ text = text.replace(/\s/g, '');
821
+ if (text === '')
822
+ return { status: 'error', reason: 'unparseable' };
823
+ // Finance types parentheses for a negative; refusing them is a papercut.
824
+ let negative = false;
825
+ const accounting = /^\((.+)\)$/.exec(text);
826
+ if (accounting) {
827
+ negative = true;
828
+ text = accounting[1];
829
+ }
830
+ let magnitude = 0;
831
+ if (format.compact) {
832
+ const compact = /^(.+?)([kKmMbB])$/.exec(text);
833
+ if (compact) {
834
+ text = compact[1];
835
+ magnitude = { k: 3, m: 6, b: 9 }[compact[2].toLowerCase()];
836
+ }
837
+ }
838
+ let canonical = null;
839
+ if (isCanonicalDecimal(text)) {
840
+ canonical = text;
841
+ }
842
+ else {
843
+ const degrouped = text.split(parts.group).join('').split(parts.decimal).join('.');
844
+ if (isCanonicalDecimal(degrouped)) {
845
+ canonical = degrouped;
846
+ }
847
+ else if (options.allowExpressions) {
848
+ // Evaluate the ORIGINAL text: parentheses here are grouping, not the
849
+ // accounting negative stripped above.
850
+ const evaluated = evaluateExpression(original);
851
+ if (evaluated != null) {
852
+ return { status: 'ok', value: shiftDecimal(evaluated, -format.shift), viaExpression: true };
853
+ }
854
+ }
855
+ }
856
+ if (canonical == null)
857
+ return { status: 'error', reason: 'unparseable' };
858
+ canonical = normalizeDecimal(canonical);
859
+ if (magnitude)
860
+ canonical = shiftDecimal(canonical, magnitude);
861
+ if (negative && !canonical.startsWith('-') && canonical !== '0')
862
+ canonical = `-${canonical}`;
863
+ if (format.isInteger && decimalScale(canonical) > 0) {
864
+ return { status: 'error', reason: 'not-integer' };
865
+ }
866
+ return { status: 'ok', value: shiftDecimal(canonical, -format.shift), viaExpression: false };
867
+ };
868
+ /** Insert the locale's group separator every three integer digits. */
869
+ const applyGrouping = (integer, separator) => integer.replace(/\B(?=(\d{3})+(?!\d))/g, () => separator);
870
+ /**
871
+ * Canonical decimal (model units) → the display number, without affixes.
872
+ *
873
+ * `min2` grouping — the default — starts at five integer digits, so a year
874
+ * renders `2026` rather than `2,026` while a price still renders `10,000`.
875
+ */
876
+ const formatNumber = (canonical, format) => {
877
+ const display = shiftDecimal(canonical, format.shift);
878
+ if (format.compact) {
879
+ return new Intl.NumberFormat(format.locale, {
880
+ notation: 'compact',
881
+ maximumFractionDigits: 1,
882
+ }).format(Number(display));
883
+ }
884
+ const rounded = roundDecimal(display, format.maximumFractionDigits, format.roundingMode);
885
+ const negative = rounded.startsWith('-');
886
+ const [integerPart, fractionPart = ''] = (negative ? rounded.slice(1) : rounded).split('.');
887
+ const fraction = fractionPart.padEnd(format.minimumFractionDigits, '0');
888
+ const grouped = format.grouping === false
889
+ ? integerPart
890
+ : format.grouping === 'min2'
891
+ ? integerPart.length > 4
892
+ ? applyGrouping(integerPart, format.parts.group)
893
+ : integerPart
894
+ : integerPart.length > 3
895
+ ? applyGrouping(integerPart, format.parts.group)
896
+ : integerPart;
897
+ return ((negative ? '-' : '') + grouped + (fraction ? format.parts.decimal + fraction : ''));
898
+ };
899
+ /**
900
+ * The plain text the field shows while focused: the display number with no
901
+ * grouping and no affixes, so the caret never has to walk over a separator
902
+ * that appears and vanishes mid-word.
903
+ */
904
+ const rawNumberText = (canonical, format) => roundDecimal(shiftDecimal(canonical, format.shift), format.maximumFractionDigits, format.roundingMode);
905
+ /** Round a committed value to the field's precision, in model units. */
906
+ const settleNumber = (canonical, format) => shiftDecimal(rawNumberText(canonical, format), -format.shift);
907
+ /**
908
+ * The `aria-valuetext` string: the formatted number with its affixes spoken.
909
+ * `aria-valuenow` alone announces "1234.56", which is the one thing about a
910
+ * money field that is not the point. An empty field says "Empty" per APG.
911
+ */
912
+ const speakNumber = (canonical, format, emptyText = 'Empty') => {
913
+ if (canonical == null)
914
+ return emptyText;
915
+ const number = formatNumber(canonical, format);
916
+ const unit = format.unitAnnouncement || format.suffix;
917
+ const spoken = unit ? (unit === '%' ? ' percent' : ` ${unit}`) : '';
918
+ return format.prefix + number + spoken;
919
+ };
920
+ /** Model-units canonical decimal → the bound `number`. */
921
+ const toNumber = (canonical) => Number(canonical);
922
+ /**
923
+ * True when a value cannot survive the trip through `number` — the reason the
924
+ * components also expose an exact `valueAsString` model, and the trigger for
925
+ * the dev-mode warning. Silent precision loss is the whole point of that
926
+ * second model, so it is worth saying out loud once.
927
+ *
928
+ * A `number` can only be checked for magnitude, since it has already lost
929
+ * whatever it was going to lose. A canonical string can be checked properly:
930
+ * `'9007199254740993'` comes back as `'9007199254740992'`.
931
+ */
932
+ const losesPrecision = (value) => {
933
+ if (typeof value === 'number') {
934
+ return !Number.isFinite(value) || Math.abs(value) > Number.MAX_SAFE_INTEGER;
935
+ }
936
+ const canonical = toDecimal(value);
937
+ const projected = Number(canonical);
938
+ return !Number.isFinite(projected) || String(projected) !== canonical;
939
+ };
940
+
941
+ /**
942
+ * Hold-to-repeat for stepper buttons: press once to step once, hold to keep
943
+ * stepping, faster the longer you hold. Getting a quantity from 1 to 200 is
944
+ * otherwise 199 clicks.
945
+ *
946
+ * Like the other cdk helpers this owns **no DOM and attaches no listeners to
947
+ * an element** — the component's template hands it the events, which keeps the
948
+ * ARIA and the markup where they belong:
949
+ *
950
+ * ```html
951
+ * <button
952
+ * type="button"
953
+ * tabindex="-1"
954
+ * [disabled]="atMax()"
955
+ * (pointerdown)="increment.press($event)"
956
+ * (pointerup)="increment.release()"
957
+ * (pointercancel)="increment.cancel()"
958
+ * (lostpointercapture)="increment.release()"
959
+ * >
960
+ * ```
961
+ *
962
+ * It does register one `window` blur listener, because a hold that survives
963
+ * the window losing focus is a value that keeps climbing while the user is
964
+ * somewhere else. That listener is torn down with the injection context, so
965
+ * `createPressRepeat` must be called from one — a field initializer, as with
966
+ * `useTimer()`.
967
+ */
968
+ /** Milliseconds spent interpolating from `intervalMs` down to `fastIntervalMs`. */
969
+ const RAMP_WINDOW_MS = 500;
970
+ const DEFAULTS = {
971
+ /** Held this long before repeating starts, so a normal click steps once. */
972
+ delayMs: 500,
973
+ /** Repeat period once it starts — 10 steps a second. */
974
+ intervalMs: 100,
975
+ /** Repeat period at full speed — 40 steps a second. */
976
+ fastIntervalMs: 25,
977
+ /** Held this long before the acceleration begins. */
978
+ rampMs: 2000,
979
+ };
980
+ function createPressRepeat(config) {
981
+ const destroyRef = inject(DestroyRef);
982
+ const holding = signal(false, ...(ngDevMode ? [{ debugName: "holding" }] : /* istanbul ignore next */ []));
983
+ let timer = null;
984
+ let startedAt = 0;
985
+ let repeated = false;
986
+ const timing = () => ({ ...DEFAULTS, ...config.timing?.() });
987
+ /** Linear ramp: flat until `rampMs`, then down to `fastIntervalMs`. */
988
+ const intervalFor = (elapsed) => {
989
+ const { intervalMs, fastIntervalMs, rampMs } = timing();
990
+ if (elapsed <= rampMs)
991
+ return intervalMs;
992
+ const progress = Math.min(1, (elapsed - rampMs) / RAMP_WINDOW_MS);
993
+ return intervalMs + (fastIntervalMs - intervalMs) * progress;
994
+ };
995
+ const stopTimer = () => {
996
+ if (timer != null)
997
+ clearTimeout(timer);
998
+ timer = null;
999
+ };
1000
+ const tick = () => {
1001
+ repeated = true;
1002
+ config.onStep(true);
1003
+ // Re-armed rather than set on an interval, so the period can shorten
1004
+ // between ticks as the hold accelerates.
1005
+ timer = setTimeout(tick, intervalFor(Date.now() - startedAt));
1006
+ };
1007
+ const end = (notify) => {
1008
+ stopTimer();
1009
+ if (!holding())
1010
+ return;
1011
+ holding.set(false);
1012
+ const didRepeat = repeated;
1013
+ repeated = false;
1014
+ startedAt = 0;
1015
+ if (notify)
1016
+ config.onRelease?.(didRepeat);
1017
+ };
1018
+ const onWindowBlur = () => end(false);
1019
+ window.addEventListener('blur', onWindowBlur);
1020
+ destroyRef.onDestroy(() => {
1021
+ stopTimer();
1022
+ window.removeEventListener('blur', onWindowBlur);
1023
+ });
1024
+ return {
1025
+ holding: holding.asReadonly(),
1026
+ press(event) {
1027
+ if (config.disabled?.())
1028
+ return;
1029
+ if (holding())
1030
+ return;
1031
+ if (event) {
1032
+ // Keeps the pointer stream on the button even when the finger slides
1033
+ // off it, so `pointerup` still arrives and the run still ends.
1034
+ //
1035
+ // This also suppresses the browser's default focus handling, which is
1036
+ // why `focus` exists: a spinner button that leaves focus nowhere means
1037
+ // the arrow keys stop working the moment you click `+`, exactly when a
1038
+ // user is most likely to reach for them.
1039
+ event.preventDefault();
1040
+ const target = event.currentTarget;
1041
+ // The button is handed over as a fallback, for controls that have no
1042
+ // text field to focus (a read-only quantity stepper, where the buttons
1043
+ // are themselves the tab stops).
1044
+ config.focus?.(target instanceof HTMLElement ? target : null);
1045
+ // jsdom and older engines lack the method entirely.
1046
+ if (target instanceof Element && typeof target.setPointerCapture === 'function') {
1047
+ try {
1048
+ target.setPointerCapture(event.pointerId);
1049
+ }
1050
+ catch {
1051
+ // A synthetic or already-released pointer id; the run is still fine.
1052
+ }
1053
+ }
1054
+ }
1055
+ holding.set(true);
1056
+ repeated = false;
1057
+ startedAt = Date.now();
1058
+ config.onStep(false);
1059
+ if (config.repeat?.() === false)
1060
+ return;
1061
+ timer = setTimeout(tick, timing().delayMs);
1062
+ },
1063
+ release() {
1064
+ end(true);
1065
+ },
1066
+ cancel() {
1067
+ end(false);
1068
+ },
1069
+ };
1070
+ }
1071
+
370
1072
  /**
371
1073
  * The keyboard and ARIA bookkeeping shared by every combobox-style popup:
372
1074
  * open state, the active option index, and the `aria-activedescendant` id
@@ -1854,7 +2556,7 @@ class UniCheckboxComponent extends BaseComponent {
1854
2556
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
1855
2557
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
1856
2558
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
1857
- /** Synced from required() validators by the Signal Forms [field] directive. */
2559
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
1858
2560
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
1859
2561
  /**
1860
2562
  * Id(s) of external element(s) describing this control — typically your
@@ -2777,6 +3479,20 @@ class UniInputBoxComponent extends BaseComponent {
2777
3479
  width = input(undefined, ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
2778
3480
  fullWidth = input(false, ...(ngDevMode ? [{ debugName: "fullWidth" }] : /* istanbul ignore next */ []));
2779
3481
  grow = input(undefined, ...(ngDevMode ? [{ debugName: "grow" }] : /* istanbul ignore next */ []));
3482
+ /**
3483
+ * Stop applying the themed leading inset to the inner control, for fields
3484
+ * that place it themselves.
3485
+ *
3486
+ * The inset normally rides the `<input>`, which is right while the text is
3487
+ * the field's leading edge. It is wrong the moment an adornment sits in
3488
+ * front: a currency prefix would hug the border while the number it belongs
3489
+ * to is indented past it. A field with adornments takes the inset over and
3490
+ * puts it on whichever element is actually first.
3491
+ */
3492
+ managedInset = input(false, ...(ngDevMode ? [{ debugName: "managedInset" }] : /* istanbul ignore next */ []));
3493
+ /** Auto-height fields (tag input, textarea) still keep the themed height as
3494
+ a floor, so a single-line field lines up with every other input. */
3495
+ minHeight = computed(() => this.height() === 'auto' ? this.componentOptions().height : undefined, ...(ngDevMode ? [{ debugName: "minHeight" }] : /* istanbul ignore next */ []));
2780
3496
  color = computed(() => this.error() ? this.componentOptions().errorColor : this.componentOptions().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
2781
3497
  border = computed(() => this.error() ? this.componentOptions().errorBorder : this.componentOptions().border, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
2782
3498
  shadow = computed(() => this.error() ? this.componentOptions().errorShadow : this.componentOptions().shadow, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
@@ -2790,7 +3506,9 @@ class UniInputBoxComponent extends BaseComponent {
2790
3506
  '& input, select, textarea': {
2791
3507
  ...removeInputPlatformStyling,
2792
3508
  height: '100%',
2793
- ...this.theme.paddingLeft(this.componentOptions().paddingLeft),
3509
+ ...(this.managedInset()
3510
+ ? undefined
3511
+ : this.theme.paddingLeft(this.componentOptions().paddingLeft)),
2794
3512
  ...this.theme.color(this.componentOptions().textColor),
2795
3513
  ...this.theme.typeface(this.componentOptions().typeface),
2796
3514
  },
@@ -2824,12 +3542,12 @@ class UniInputBoxComponent extends BaseComponent {
2824
3542
  },
2825
3543
  ]), ...(ngDevMode ? [{ debugName: "inputBoxClass" }] : /* istanbul ignore next */ []));
2826
3544
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2827
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniInputBoxComponent, isStandalone: true, selector: "uni-input-box", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [containerColor]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [width]=\"width()\"\n [fullWidth]=\"fullWidth()\"\n [grow]=\"grow()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3545
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniInputBoxComponent, isStandalone: true, selector: "uni-input-box", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null }, managedInset: { classPropertyName: "managedInset", publicName: "managedInset", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [minHeight]=\"minHeight()\"\n [containerColor]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [width]=\"width()\"\n [fullWidth]=\"fullWidth()\"\n [grow]=\"grow()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2828
3546
  }
2829
3547
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, decorators: [{
2830
3548
  type: Component,
2831
- args: [{ selector: 'uni-input-box', imports: [UniRowDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [containerColor]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [width]=\"width()\"\n [fullWidth]=\"fullWidth()\"\n [grow]=\"grow()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n" }]
2832
- }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], grow: [{ type: i0.Input, args: [{ isSignal: true, alias: "grow", required: false }] }] } });
3549
+ args: [{ selector: 'uni-input-box', imports: [UniRowDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [minHeight]=\"minHeight()\"\n [containerColor]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [width]=\"width()\"\n [fullWidth]=\"fullWidth()\"\n [grow]=\"grow()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n" }]
3550
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], grow: [{ type: i0.Input, args: [{ isSignal: true, alias: "grow", required: false }] }], managedInset: [{ type: i0.Input, args: [{ isSignal: true, alias: "managedInset", required: false }] }] } });
2833
3551
 
2834
3552
  /**
2835
3553
  * Form-bound, closed-set, single-select autocomplete: `FormValueControl<T | null>`
@@ -3240,7 +3958,7 @@ class UniComboboxComponent extends BaseComponent {
3240
3958
  ]);
3241
3959
  }, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
3242
3960
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3243
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniComboboxComponent, isStandalone: true, selector: "uni-combobox, Combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, filterLocally: { classPropertyName: "filterLocally", publicName: "filterLocally", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", selected: "selected", cleared: "cleared", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3961
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniComboboxComponent, isStandalone: true, selector: "uni-combobox, Combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, filterLocally: { classPropertyName: "filterLocally", publicName: "filterLocally", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", selected: "selected", cleared: "cleared", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3244
3962
  }
3245
3963
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, decorators: [{
3246
3964
  type: Component,
@@ -4145,7 +4863,7 @@ class UniDateInputComponent extends BaseComponent {
4145
4863
  ]);
4146
4864
  }, ...(ngDevMode ? [{ debugName: "embeddedClass" }] : /* istanbul ignore next */ []));
4147
4865
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDateInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
4148
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDateInputComponent, isStandalone: true, selector: "uni-date-input, DateInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", opened: "opened", closed: "closed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dateInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "toggleRef", first: true, predicate: ["toggle"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "popupRef", first: true, predicate: ["popupDialog"], descendants: true, isSignal: true }, { propertyName: "dropdown", first: true, predicate: UniDropdownComponent, descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: UniCalendarComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so commits happen only when focus\n leaves the whole field, not while moving into the popup. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <!-- The dropdown wires aria-haspopup/aria-expanded/aria-controls onto\n this button; its name tracks the value (\"Change date, \u2026\"). -->\n <button\n #toggle\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'calendar_month'\"\n [disable]=\"disabled()\"\n >\n {{ toggleLabel() }}\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (toggleElement()) {\n <uni-dropdown\n [trigger]=\"toggleElement()!\"\n ariaHasPopup=\"dialog\"\n placement=\"bottom-end\"\n [containerColor]=\"componentOptions().popupColor ?? 'primary-surface'\"\n [shadow]=\"componentOptions().popupShadow ?? 'menu'\"\n [borderRadius]=\"componentOptions().popupBorderRadius ?? 'xs'\"\n paddingVertical=\"xs\"\n paddingHorizontal=\"xs\"\n (dropdownShowing)=\"onPopupShowing()\"\n (dropdownHiding)=\"onPopupHiding()\"\n >\n <!-- A focus-holding dialog per the APG date-picker pattern: Tab cycles\n inside, Escape closes and returns focus to the field. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div #popupDialog role=\"dialog\" aria-label=\"Choose date\" (keydown)=\"onPopupKeydown($event)\">\n <uni-calendar\n mode=\"single\"\n [value]=\"value()\"\n [minDate]=\"minDate()\"\n [maxDate]=\"maxDate()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n (selected)=\"onCalendarPick($event)\"\n />\n </div>\n </uni-dropdown>\n }\n\n <!-- Commits, steps and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniCalendarComponent, selector: "uni-calendar, Calendar", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "mode", "month", "minDate", "maxDate", "disabledDates", "markers", "locale", "weekStart", "ariaLabel", "size"], outputs: ["valueChange", "touchedChange", "monthChange", "selected"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "containerColor"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4866
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDateInputComponent, isStandalone: true, selector: "uni-date-input, DateInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", opened: "opened", closed: "closed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dateInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "toggleRef", first: true, predicate: ["toggle"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "popupRef", first: true, predicate: ["popupDialog"], descendants: true, isSignal: true }, { propertyName: "dropdown", first: true, predicate: UniDropdownComponent, descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: UniCalendarComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so commits happen only when focus\n leaves the whole field, not while moving into the popup. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <!-- The dropdown wires aria-haspopup/aria-expanded/aria-controls onto\n this button; its name tracks the value (\"Change date, \u2026\"). -->\n <button\n #toggle\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'calendar_month'\"\n [disable]=\"disabled()\"\n >\n {{ toggleLabel() }}\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (toggleElement()) {\n <uni-dropdown\n [trigger]=\"toggleElement()!\"\n ariaHasPopup=\"dialog\"\n placement=\"bottom-end\"\n [containerColor]=\"componentOptions().popupColor ?? 'primary-surface'\"\n [shadow]=\"componentOptions().popupShadow ?? 'menu'\"\n [borderRadius]=\"componentOptions().popupBorderRadius ?? 'xs'\"\n paddingVertical=\"xs\"\n paddingHorizontal=\"xs\"\n (dropdownShowing)=\"onPopupShowing()\"\n (dropdownHiding)=\"onPopupHiding()\"\n >\n <!-- A focus-holding dialog per the APG date-picker pattern: Tab cycles\n inside, Escape closes and returns focus to the field. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div #popupDialog role=\"dialog\" aria-label=\"Choose date\" (keydown)=\"onPopupKeydown($event)\">\n <uni-calendar\n mode=\"single\"\n [value]=\"value()\"\n [minDate]=\"minDate()\"\n [maxDate]=\"maxDate()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n (selected)=\"onCalendarPick($event)\"\n />\n </div>\n </uni-dropdown>\n }\n\n <!-- Commits, steps and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniCalendarComponent, selector: "uni-calendar, Calendar", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "mode", "month", "minDate", "maxDate", "disabledDates", "markers", "locale", "weekStart", "ariaLabel", "size"], outputs: ["valueChange", "touchedChange", "monthChange", "selected"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "containerColor"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4149
4867
  }
4150
4868
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDateInputComponent, decorators: [{
4151
4869
  type: Component,
@@ -4469,7 +5187,7 @@ class UniTimeInputComponent extends BaseComponent {
4469
5187
  }));
4470
5188
  }, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
4471
5189
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4472
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTimeInputComponent, isStandalone: true, selector: "uni-time-input, TimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5190
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTimeInputComponent, isStandalone: true, selector: "uni-time-input, TimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4473
5191
  }
4474
5192
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, decorators: [{
4475
5193
  type: Component,
@@ -4602,7 +5320,7 @@ class UniDateTimeInputComponent extends BaseComponent {
4602
5320
  });
4603
5321
  }, ...(ngDevMode ? [{ debugName: "dividerClass" }] : /* istanbul ignore next */ []));
4604
5322
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDateTimeInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
4605
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDateTimeInputComponent, isStandalone: true, selector: "uni-date-time-input, DateTimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, minDateTime: { classPropertyName: "minDateTime", publicName: "minDateTime", isSignal: true, isRequired: false, transformFunction: null }, maxDateTime: { classPropertyName: "maxDateTime", publicName: "maxDateTime", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, slotsFor: { classPropertyName: "slotsFor", publicName: "slotsFor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { listeners: { "focusout": "onHostFocusOut($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dateTimeInput' }], usesInheritance: true, ngImport: i0, template: "<!-- One field chrome for both parts; the parts render embedded (no box of\n their own), so error/disabled/focus states stay consistent with every\n other field. Tab order: date, then time \u2014 two honest tab stops. -->\n<uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div role=\"group\" [attr.aria-label]=\"label()\" [class]=\"groupClass()\">\n <div [class]=\"datePartClass()\">\n <uni-date-input\n [embedded]=\"true\"\n label=\"Date\"\n [value]=\"dateValue()\"\n [minDate]=\"dateMin()\"\n [maxDate]=\"dateMax()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n [disabled]=\"disabled()\"\n [ariaDescribedBy]=\"ariaDescribedBy()\"\n (valueChange)=\"onDatePartChange($event)\"\n />\n </div>\n <div [class]=\"dividerClass()\"></div>\n <div [class]=\"timePartClass()\">\n <uni-time-input\n [embedded]=\"true\"\n label=\"Time\"\n [value]=\"timeValue()\"\n [minTime]=\"timeMin()\"\n [maxTime]=\"timeMax()\"\n [slots]=\"effectiveSlots()\"\n [minuteStep]=\"minuteStep()\"\n [hour12]=\"hour12()\"\n [locale]=\"locale()\"\n [disabled]=\"timeDisabled()\"\n (valueChange)=\"onTimePartChange($event)\"\n />\n </div>\n </div>\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniDateInputComponent, selector: "uni-date-input, DateInput", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "placeholder", "displayFormat", "locale", "commitOnBlur", "parse", "embedded", "minDate", "maxDate", "disabledDates", "markers", "weekStart"], outputs: ["valueChange", "touchedChange", "opened", "closed", "rejected"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }, { kind: "component", type: UniTimeInputComponent, selector: "uni-time-input, TimeInput", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "placeholder", "minuteStep", "minTime", "maxTime", "slots", "hour12", "locale", "commitOnBlur", "embedded"], outputs: ["valueChange", "touchedChange", "rejected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5323
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDateTimeInputComponent, isStandalone: true, selector: "uni-date-time-input, DateTimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, minDateTime: { classPropertyName: "minDateTime", publicName: "minDateTime", isSignal: true, isRequired: false, transformFunction: null }, maxDateTime: { classPropertyName: "maxDateTime", publicName: "maxDateTime", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, slotsFor: { classPropertyName: "slotsFor", publicName: "slotsFor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { listeners: { "focusout": "onHostFocusOut($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dateTimeInput' }], usesInheritance: true, ngImport: i0, template: "<!-- One field chrome for both parts; the parts render embedded (no box of\n their own), so error/disabled/focus states stay consistent with every\n other field. Tab order: date, then time \u2014 two honest tab stops. -->\n<uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div role=\"group\" [attr.aria-label]=\"label()\" [class]=\"groupClass()\">\n <div [class]=\"datePartClass()\">\n <uni-date-input\n [embedded]=\"true\"\n label=\"Date\"\n [value]=\"dateValue()\"\n [minDate]=\"dateMin()\"\n [maxDate]=\"dateMax()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n [disabled]=\"disabled()\"\n [ariaDescribedBy]=\"ariaDescribedBy()\"\n (valueChange)=\"onDatePartChange($event)\"\n />\n </div>\n <div [class]=\"dividerClass()\"></div>\n <div [class]=\"timePartClass()\">\n <uni-time-input\n [embedded]=\"true\"\n label=\"Time\"\n [value]=\"timeValue()\"\n [minTime]=\"timeMin()\"\n [maxTime]=\"timeMax()\"\n [slots]=\"effectiveSlots()\"\n [minuteStep]=\"minuteStep()\"\n [hour12]=\"hour12()\"\n [locale]=\"locale()\"\n [disabled]=\"timeDisabled()\"\n (valueChange)=\"onTimePartChange($event)\"\n />\n </div>\n </div>\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniDateInputComponent, selector: "uni-date-input, DateInput", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "placeholder", "displayFormat", "locale", "commitOnBlur", "parse", "embedded", "minDate", "maxDate", "disabledDates", "markers", "weekStart"], outputs: ["valueChange", "touchedChange", "opened", "closed", "rejected"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }, { kind: "component", type: UniTimeInputComponent, selector: "uni-time-input, TimeInput", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "placeholder", "minuteStep", "minTime", "maxTime", "slots", "hour12", "locale", "commitOnBlur", "embedded"], outputs: ["valueChange", "touchedChange", "rejected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4606
5324
  }
4607
5325
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDateTimeInputComponent, decorators: [{
4608
5326
  type: Component,
@@ -4616,7 +5334,7 @@ class UniInputComponent {
4616
5334
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
4617
5335
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
4618
5336
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
4619
- /** Synced from required() validators by the Signal Forms [field] directive. */
5337
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
4620
5338
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
4621
5339
  /**
4622
5340
  * Id(s) of external element(s) describing this control — typically your
@@ -4632,7 +5350,7 @@ class UniInputComponent {
4632
5350
  */
4633
5351
  type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
4634
5352
  // --- CONSTRAINTS ---
4635
- // These are Signal Forms' own optional control inputs, so the `[field]`
5353
+ // These are Signal Forms' own optional control inputs, so the `[formField]`
4636
5354
  // directive syncs them from the field's validators the same way it syncs
4637
5355
  // `required` — and they are reflected onto the native element so the browser
4638
5356
  // can do its part (number steppers, length limits, on-screen keyboards).
@@ -4679,7 +5397,7 @@ class UniInputComponent {
4679
5397
  }
4680
5398
  inputClass = css({});
4681
5399
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4682
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniInputComponent, isStandalone: true, selector: "uni-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", 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 }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, pattern: { classPropertyName: "pattern", publicName: "pattern", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputMode: { classPropertyName: "inputMode", publicName: "inputMode", isSignal: true, isRequired: false, transformFunction: null }, list: { classPropertyName: "list", publicName: "list", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <ng-content select=\"pre-input, [pre-input]\" />\n <input\n [type]=\"type()\"\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (blur)=\"markAsTouched()\"\n [class]=\"inputClass\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.inputmode]=\"inputMode() || null\"\n [attr.list]=\"list() || null\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"nativePattern()\"\n [attr.spellcheck]=\"spellcheck() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <ng-content select=\"post-input, [post-input]\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5400
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniInputComponent, isStandalone: true, selector: "uni-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", 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 }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, pattern: { classPropertyName: "pattern", publicName: "pattern", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputMode: { classPropertyName: "inputMode", publicName: "inputMode", isSignal: true, isRequired: false, transformFunction: null }, list: { classPropertyName: "list", publicName: "list", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <ng-content select=\"pre-input, [pre-input]\" />\n <input\n [type]=\"type()\"\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (blur)=\"markAsTouched()\"\n [class]=\"inputClass\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.inputmode]=\"inputMode() || null\"\n [attr.list]=\"list() || null\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"nativePattern()\"\n [attr.spellcheck]=\"spellcheck() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <ng-content select=\"post-input, [post-input]\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4683
5401
  }
4684
5402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputComponent, decorators: [{
4685
5403
  type: Component,
@@ -4853,7 +5571,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
4853
5571
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
4854
5572
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
4855
5573
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
4856
- /** Synced from required() validators by the Signal Forms [field] directive. */
5574
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
4857
5575
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
4858
5576
  /**
4859
5577
  * Id(s) of external element(s) describing this control — typically your
@@ -5004,7 +5722,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
5004
5722
  });
5005
5723
  }
5006
5724
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5007
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled() || !!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "containerColor"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "directive", type: UniStackDirective, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5725
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled() || !!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "containerColor"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "directive", type: UniStackDirective, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5008
5726
  }
5009
5727
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, decorators: [{
5010
5728
  type: Component,
@@ -5084,7 +5802,7 @@ class UniRadioComponent extends BaseComponent {
5084
5802
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
5085
5803
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
5086
5804
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
5087
- /** Synced from required() validators by the Signal Forms [field] directive. */
5805
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
5088
5806
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
5089
5807
  /**
5090
5808
  * Id(s) of external element(s) describing this control — typically your
@@ -5274,7 +5992,7 @@ class UniDebounceInputComponent {
5274
5992
  minWidth: 0,
5275
5993
  });
5276
5994
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDebounceInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5277
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniDebounceInputComponent, isStandalone: true, selector: "uni-debounce-input", inputs: { inputName: { classPropertyName: "inputName", publicName: "inputName", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputMode: { classPropertyName: "inputMode", publicName: "inputMode", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaExpanded: { classPropertyName: "ariaExpanded", publicName: "ariaExpanded", isSignal: true, isRequired: false, transformFunction: null }, ariaControls: { classPropertyName: "ariaControls", publicName: "ariaControls", isSignal: true, isRequired: false, transformFunction: null }, ariaActivedescendant: { classPropertyName: "ariaActivedescendant", publicName: "ariaActivedescendant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: "<uni-input-box>\n <ng-content select=\"[pre-input]\" />\n <input\n #field\n [class]=\"inputClass\"\n [type]=\"type()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.inputmode]=\"inputMode() || null\"\n [name]=\"inputName()\"\n [id]=\"inputId()\"\n [value]=\"value() ?? ''\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"label() || null\"\n [attr.role]=\"role() || null\"\n [attr.aria-expanded]=\"ariaExpanded() ?? null\"\n [attr.aria-controls]=\"ariaControls() || null\"\n [attr.aria-activedescendant]=\"ariaActivedescendant() || null\"\n [attr.aria-autocomplete]=\"role() === 'combobox' ? 'list' : null\"\n />\n <ng-content select=\"[post-input]\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5995
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniDebounceInputComponent, isStandalone: true, selector: "uni-debounce-input", inputs: { inputName: { classPropertyName: "inputName", publicName: "inputName", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputMode: { classPropertyName: "inputMode", publicName: "inputMode", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaExpanded: { classPropertyName: "ariaExpanded", publicName: "ariaExpanded", isSignal: true, isRequired: false, transformFunction: null }, ariaControls: { classPropertyName: "ariaControls", publicName: "ariaControls", isSignal: true, isRequired: false, transformFunction: null }, ariaActivedescendant: { classPropertyName: "ariaActivedescendant", publicName: "ariaActivedescendant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: "<uni-input-box>\n <ng-content select=\"[pre-input]\" />\n <input\n #field\n [class]=\"inputClass\"\n [type]=\"type()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.inputmode]=\"inputMode() || null\"\n [name]=\"inputName()\"\n [id]=\"inputId()\"\n [value]=\"value() ?? ''\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"label() || null\"\n [attr.role]=\"role() || null\"\n [attr.aria-expanded]=\"ariaExpanded() ?? null\"\n [attr.aria-controls]=\"ariaControls() || null\"\n [attr.aria-activedescendant]=\"ariaActivedescendant() || null\"\n [attr.aria-autocomplete]=\"role() === 'combobox' ? 'list' : null\"\n />\n <ng-content select=\"[post-input]\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5278
5996
  }
5279
5997
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDebounceInputComponent, decorators: [{
5280
5998
  type: Component,
@@ -5395,7 +6113,7 @@ class UniSelectComponent {
5395
6113
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
5396
6114
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
5397
6115
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
5398
- /** Synced from required() validators by the Signal Forms [field] directive. */
6116
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
5399
6117
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
5400
6118
  /**
5401
6119
  * Id(s) of external element(s) describing this control — typically your
@@ -5461,7 +6179,7 @@ class UniSelectComponent {
5461
6179
  pointerEvents: 'none' /* Crucial for clicking through */,
5462
6180
  });
5463
6181
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5464
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option\n [value]=\"i\"\n [selected]=\"currentSelectedIndex() === i.toString()\"\n [disabled]=\"opt.disabled ?? false\"\n >\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6182
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option\n [value]=\"i\"\n [selected]=\"currentSelectedIndex() === i.toString()\"\n [disabled]=\"opt.disabled ?? false\"\n >\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5465
6183
  }
5466
6184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, decorators: [{
5467
6185
  type: Component,
@@ -5469,138 +6187,1981 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5469
6187
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], grow: [{ type: i0.Input, args: [{ isSignal: true, alias: "grow", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
5470
6188
 
5471
6189
  /**
5472
- * Range slider on a native `<input type="range">` keyboard interaction and
5473
- * the ARIA slider contract come from the platform. Fill, track, thumb and
5474
- * radii resolve from `slider` theme tokens; the fill percentage rides a CSS
5475
- * custom property so dragging never regenerates styles.
6190
+ * Numeric field with locale-aware parsing, `Intl` formatting on commit,
6191
+ * prefix/suffix adornments and steppers that hold to repeat.
6192
+ *
6193
+ * Not `<input type="number">`, and the first reason is a data-loss bug: per the
6194
+ * HTML value sanitization algorithm, a number input whose text is not a valid
6195
+ * floating-point number reports `value === ''`. Type `12,50` as most of Europe
6196
+ * does, or paste `1,234.56` from a spreadsheet, and the app reads an empty
6197
+ * field. This is `type="text"` with `role="spinbutton"`, which is the only way
6198
+ * to keep the user's malformed text on screen and tell them about it.
6199
+ *
6200
+ * Chrome comes from `uni-input-box`, so error, disabled and focus states match
6201
+ * every other field. All arithmetic runs on the cdk's exact decimal helpers.
5476
6202
  */
5477
- class UniSliderComponent extends BaseComponent {
5478
- // --- REQUIRED SIGNALS (populated by FormValueControl) ---
5479
- value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6203
+ class UniNumberInputComponent extends BaseComponent {
6204
+ // --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
6205
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5480
6206
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5481
6207
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
5482
6208
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
5483
6209
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
5484
- /** Synced from required() validators by the Signal Forms [field] directive. */
5485
6210
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
6211
+ ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
5486
6212
  /**
5487
- * Id(s) of external element(s) describing this control typically your
5488
- * app-rendered value or error text exposed as aria-describedby.
6213
+ * Exact binding, as a canonical decimal string. Bind this instead of `value`
6214
+ * where a cent in the fifth decimal place matters; both stay in sync, so it
6215
+ * is a one-word change from the ordinary case.
5489
6216
  */
5490
- ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
5491
- // --- CONFIGURATION ---
6217
+ valueAsString = model(null, ...(ngDevMode ? [{ debugName: "valueAsString" }] : /* istanbul ignore next */ []));
6218
+ // --- Configuration -------------------------------------------------------
6219
+ /** Accessible name, e.g. "Unit price". */
5492
6220
  label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
5493
- // `min`/`max` are part of the FormValueControl contract (synced from
5494
- // min()/max() validators), so their type must admit undefined.
5495
- min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
5496
- max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
6221
+ placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
6222
+ preset = input('decimal', ...(ngDevMode ? [{ debugName: "preset" }] : /* istanbul ignore next */ []));
6223
+ /** ISO 4217 code, e.g. `'USD'`. Implies `preset="currency"`. */
6224
+ currency = input(...(ngDevMode ? [undefined, { debugName: "currency" }] : /* istanbul ignore next */ []));
6225
+ /** BCP 47 tag. Defaults to the document language, then the browser's. */
6226
+ locale = input(...(ngDevMode ? [undefined, { debugName: "locale" }] : /* istanbul ignore next */ []));
6227
+ /** Static adornment before the number, e.g. `'$'`. Never parseable input. */
6228
+ prefix = input(...(ngDevMode ? [undefined, { debugName: "prefix" }] : /* istanbul ignore next */ []));
6229
+ /** Static adornment after the number, e.g. `'kg'`, `'/mo'`. */
6230
+ suffix = input(...(ngDevMode ? [undefined, { debugName: "suffix" }] : /* istanbul ignore next */ []));
6231
+ decimals = input(...(ngDevMode ? [undefined, { debugName: "decimals" }] : /* istanbul ignore next */ []));
6232
+ grouping = input(...(ngDevMode ? [undefined, { debugName: "grouping" }] : /* istanbul ignore next */ []));
6233
+ /** Escape hatch, merged over the preset. */
6234
+ numberFormat = input(...(ngDevMode ? [undefined, { debugName: "numberFormat" }] : /* istanbul ignore next */ []));
6235
+ roundingMode = input('half-up', ...(ngDevMode ? [{ debugName: "roundingMode" }] : /* istanbul ignore next */ []));
6236
+ align = input(...(ngDevMode ? [undefined, { debugName: "align" }] : /* istanbul ignore next */ []));
6237
+ /** The model is a fraction: `0.15` displays as `15%`. */
6238
+ valueIsFraction = input(false, ...(ngDevMode ? [{ debugName: "valueIsFraction" }] : /* istanbul ignore next */ []));
6239
+ /** Spoken long form of an abbreviated suffix, e.g. `'kilograms'` for `kg`. */
6240
+ unitAnnouncement = input(...(ngDevMode ? [undefined, { debugName: "unitAnnouncement" }] : /* istanbul ignore next */ []));
6241
+ readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
6242
+ /** Renders without its own input-box chrome, for composers like uni-slider. */
6243
+ embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
6244
+ // --- Range and stepping --------------------------------------------------
6245
+ min = input(...(ngDevMode ? [undefined, { debugName: "min" }] : /* istanbul ignore next */ []));
6246
+ max = input(...(ngDevMode ? [undefined, { debugName: "max" }] : /* istanbul ignore next */ []));
5497
6247
  step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
5498
- resolvedMin = computed(() => this.min() ?? 0, ...(ngDevMode ? [{ debugName: "resolvedMin" }] : /* istanbul ignore next */ []));
5499
- resolvedMax = computed(() => this.max() ?? 100, ...(ngDevMode ? [{ debugName: "resolvedMax" }] : /* istanbul ignore next */ []));
5500
- markAsTouched() {
5501
- this.touched.set(true);
6248
+ /** `PageUp`/`PageDown` and `Shift+Arrow`. Default: `step × 10`. */
6249
+ largeStep = input(...(ngDevMode ? [undefined, { debugName: "largeStep" }] : /* istanbul ignore next */ []));
6250
+ /** `Alt+Arrow`, Figma's fine-nudge convention. Unset disables it. */
6251
+ smallStep = input(...(ngDevMode ? [undefined, { debugName: "smallStep" }] : /* istanbul ignore next */ []));
6252
+ stepOrigin = input('min', ...(ngDevMode ? [{ debugName: "stepOrigin" }] : /* istanbul ignore next */ []));
6253
+ /** Cyclic fields only — 23 → 0 hours, 359 → 0 degrees. */
6254
+ wrap = input(false, ...(ngDevMode ? [{ debugName: "wrap" }] : /* istanbul ignore next */ []));
6255
+ /** `false` refuses an out-of-range commit instead of clamping it. */
6256
+ clampOnCommit = input(true, ...(ngDevMode ? [{ debugName: "clampOnCommit" }] : /* istanbul ignore next */ []));
6257
+ /** What ↑ commits on an empty field. Default: `min ?? 0`. */
6258
+ emptyStepValue = input(...(ngDevMode ? [undefined, { debugName: "emptyStepValue" }] : /* istanbul ignore next */ []));
6259
+ // --- Entry behaviour -----------------------------------------------------
6260
+ commitOnBlur = input(true, ...(ngDevMode ? [{ debugName: "commitOnBlur" }] : /* istanbul ignore next */ []));
6261
+ selectOnFocus = input(false, ...(ngDevMode ? [{ debugName: "selectOnFocus" }] : /* istanbul ignore next */ []));
6262
+ /** `12*3` → 36. Off by default: a parser in a form field is a real cost. */
6263
+ allowExpressions = input(false, ...(ngDevMode ? [{ debugName: "allowExpressions" }] : /* istanbul ignore next */ []));
6264
+ /** Scroll-to-step. Off by default — see `onWheel`. */
6265
+ wheel = input(false, ...(ngDevMode ? [{ debugName: "wheel" }] : /* istanbul ignore next */ []));
6266
+ repeat = input(true, ...(ngDevMode ? [{ debugName: "repeat" }] : /* istanbul ignore next */ []));
6267
+ /** Custom parser, replacing the built-in locale parsing. */
6268
+ parse = input(...(ngDevMode ? [undefined, { debugName: "parse" }] : /* istanbul ignore next */ []));
6269
+ /** Overrides the themed layout for this instance. */
6270
+ stepperLayout = input(...(ngDevMode ? [undefined, { debugName: "stepperLayout" }] : /* istanbul ignore next */ []));
6271
+ // --- Events --------------------------------------------------------------
6272
+ stepped = output();
6273
+ /** A commit was refused; the raw text stays in the field. */
6274
+ rejected = output();
6275
+ inputRef = viewChild.required('field');
6276
+ srOnly = css(visuallyHidden);
6277
+ /** Clamps, fences, rejections and expression results are otherwise silent. */
6278
+ announcer = createAnnouncer();
6279
+ hintId = uniqueId('uni-number-input-hint');
6280
+ /** Uncommitted text. `null` means "show the committed value". */
6281
+ draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
6282
+ /** A commit that failed — styles the field until the text is edited. */
6283
+ draftInvalid = signal(false, ...(ngDevMode ? [{ debugName: "draftInvalid" }] : /* istanbul ignore next */ []));
6284
+ focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
6285
+ /**
6286
+ * The canonical decimal behind both models — the field's source of truth.
6287
+ *
6288
+ * Two models that each accept writes need a rule for which one won, and
6289
+ * "whichever the app touched last" is the only one that does not surprise
6290
+ * somebody. A `linkedSignal` over both gives us that: the model whose value
6291
+ * differs from the previous source is the one that changed.
6292
+ *
6293
+ * The subtlety is the echo. Committing writes both models, and the `value`
6294
+ * projection of a 17-digit exact string is lossy — so on the next pass
6295
+ * `value` looks changed, and naively adopting it would clobber the very
6296
+ * precision `valueAsString` exists to keep. A changed `value` that already
6297
+ * matches `Number(exact)` is our own projection coming back, not a write.
6298
+ */
6299
+ canonical = linkedSignal({ ...(ngDevMode ? { debugName: "canonical" } : /* istanbul ignore next */ {}), source: () => ({ value: this.value(), exact: this.valueAsString() }),
6300
+ computation: (source, previous) => {
6301
+ const prior = previous?.source;
6302
+ if (prior && source.exact !== prior.exact)
6303
+ return source.exact;
6304
+ if (prior && source.value !== prior.value) {
6305
+ if (source.exact != null && Number(source.exact) === source.value)
6306
+ return source.exact;
6307
+ return source.value == null ? null : toDecimal(source.value);
6308
+ }
6309
+ if (source.exact != null)
6310
+ return source.exact;
6311
+ return source.value == null ? null : toDecimal(source.value);
6312
+ } });
6313
+ constructor() {
6314
+ super();
6315
+ // A hybrid device can gain or lose a coarse pointer mid-session.
6316
+ if (typeof matchMedia === 'function') {
6317
+ const query = matchMedia('(pointer: coarse)');
6318
+ const onChange = () => this.coarsePointer.set(query.matches);
6319
+ query.addEventListener('change', onChange);
6320
+ inject(DestroyRef).onDestroy(() => query.removeEventListener('change', onChange));
6321
+ }
6322
+ // Keep both models reflecting the source of truth, so an app that binds
6323
+ // only one of them still reads a consistent value from the other.
6324
+ effect(() => {
6325
+ const canonical = this.canonical();
6326
+ untracked(() => {
6327
+ if (this.valueAsString() !== canonical)
6328
+ this.valueAsString.set(canonical);
6329
+ const projected = canonical == null ? null : Number(canonical);
6330
+ if (this.value() !== projected)
6331
+ this.value.set(projected);
6332
+ });
6333
+ });
6334
+ // Silent precision loss is the whole reason `valueAsString` exists, so it
6335
+ // is worth saying out loud — once, in dev, per offending value.
6336
+ if (isDevMode()) {
6337
+ let warned = null;
6338
+ effect(() => {
6339
+ const exact = this.valueAsString();
6340
+ const value = this.value();
6341
+ const subject = exact ?? value;
6342
+ if (subject == null)
6343
+ return;
6344
+ const key = String(subject);
6345
+ if (key === warned || !losesPrecision(subject))
6346
+ return;
6347
+ warned = key;
6348
+ console.warn(`[uni-number-input] "${this.label()}": ${key} cannot round-trip through a JavaScript number. ` +
6349
+ 'Bind [(valueAsString)] instead of [(value)] to keep it exact.');
6350
+ });
6351
+ }
5502
6352
  }
5503
- handleInput(event) {
5504
- this.value.set(Number(event.target.value));
6353
+ // --- Format resolution ----------------------------------------------------
6354
+ resolvedLocale = computed(() => this.locale() ?? (document.documentElement.lang || navigator.language || 'en-US'), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
6355
+ format = computed(() => resolveNumberFormat({
6356
+ preset: this.preset(),
6357
+ currency: this.currency(),
6358
+ locale: this.resolvedLocale(),
6359
+ decimals: this.decimals(),
6360
+ grouping: this.grouping(),
6361
+ prefix: this.prefix(),
6362
+ suffix: this.suffix(),
6363
+ roundingMode: this.roundingMode(),
6364
+ valueIsFraction: this.valueIsFraction(),
6365
+ numberFormat: this.numberFormat(),
6366
+ min: this.min(),
6367
+ unitAnnouncement: this.unitAnnouncement(),
6368
+ }), ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
6369
+ /**
6370
+ * Two stacked arrows cannot both be 24px tall inside a 32px field, so on a
6371
+ * coarse pointer the stacked layout becomes `split`, where each button is a
6372
+ * full-height square and clears the WCAG 2.2 SC 2.5.8 floor. Two 12px
6373
+ * targets under a fingertip is a coin toss.
6374
+ */
6375
+ coarsePointer = signal(typeof matchMedia === 'function' ? matchMedia('(pointer: coarse)').matches : false, ...(ngDevMode ? [{ debugName: "coarsePointer" }] : /* istanbul ignore next */ []));
6376
+ layout = computed(() => {
6377
+ const requested = this.stepperLayout() ?? this.componentOptions().stepperLayout ?? 'stacked';
6378
+ return requested === 'stacked' && this.coarsePointer() ? 'split' : requested;
6379
+ }, ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
6380
+ showSteppers = computed(() => this.layout() !== 'none' && !this.readOnly(), ...(ngDevMode ? [{ debugName: "showSteppers" }] : /* istanbul ignore next */ []));
6381
+ showError = computed(() => (this.invalid() && (this.touched() || this.dirty())) || this.draftInvalid(), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
6382
+ /** Raw while focused, formatted once committed — no caret arithmetic ever. */
6383
+ displayText = computed(() => {
6384
+ const draft = this.draft();
6385
+ if (draft != null)
6386
+ return draft;
6387
+ const canonical = this.canonical();
6388
+ if (canonical == null)
6389
+ return '';
6390
+ return this.focused()
6391
+ ? rawNumberText(canonical, this.format())
6392
+ : formatNumber(canonical, this.format());
6393
+ }, ...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
6394
+ valueTextForAria = computed(() => speakNumber(this.canonical(), this.format()), ...(ngDevMode ? [{ debugName: "valueTextForAria" }] : /* istanbul ignore next */ []));
6395
+ /**
6396
+ * `aria-valuenow` is omitted entirely on an empty field, per APG — a
6397
+ * spinbutton reporting 0 for "nothing yet" is a wrong answer, not a missing
6398
+ * one. `aria-valuetext` carries the localized "Empty" instead.
6399
+ */
6400
+ canonicalForAria = computed(() => this.canonical() ?? null, ...(ngDevMode ? [{ debugName: "canonicalForAria" }] : /* istanbul ignore next */ []));
6401
+ describedBy = computed(() => [this.ariaDescribedBy(), this.hintId].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
6402
+ // --- Fences ---------------------------------------------------------------
6403
+ atFence(which) {
6404
+ const bound = which === 'min' ? this.min() : this.max();
6405
+ const canonical = this.canonical();
6406
+ if (bound == null || canonical == null)
6407
+ return false;
6408
+ const clamped = clampDecimal(canonical, this.min(), this.max());
6409
+ if (clamped.hit === which)
6410
+ return true;
6411
+ return which === 'min'
6412
+ ? Number(canonical) <= bound
6413
+ : Number(canonical) >= bound;
5505
6414
  }
5506
- fillPercent = computed(() => {
5507
- const min = this.resolvedMin();
5508
- const range = this.resolvedMax() - min;
5509
- if (range <= 0)
5510
- return '0%';
5511
- const ratio = (this.value() - min) / range;
5512
- return `${Math.min(100, Math.max(0, ratio * 100))}%`;
5513
- }, ...(ngDevMode ? [{ debugName: "fillPercent" }] : /* istanbul ignore next */ []));
5514
- inputClass = computed(() => {
6415
+ atMin = computed(() => this.atFence('min') && !this.wrap(), ...(ngDevMode ? [{ debugName: "atMin" }] : /* istanbul ignore next */ []));
6416
+ atMax = computed(() => this.atFence('max') && !this.wrap(), ...(ngDevMode ? [{ debugName: "atMax" }] : /* istanbul ignore next */ []));
6417
+ // --- Committing -----------------------------------------------------------
6418
+ /** Set the source of truth; the constructor's effect pushes it to both models. */
6419
+ write(canonical) {
6420
+ this.canonical.set(canonical);
6421
+ }
6422
+ /**
6423
+ * Turn the draft into a value. Out-of-range either clamps (announced) or is
6424
+ * refused, per `clampOnCommit`; unreadable text stays in the field, flagged.
6425
+ */
6426
+ commitDraft() {
6427
+ const draft = this.draft();
6428
+ if (draft == null)
6429
+ return;
6430
+ const custom = this.parse();
6431
+ if (custom) {
6432
+ const parsed = custom(draft, this.resolvedLocale());
6433
+ if (parsed == null)
6434
+ return this.reject(draft, 'unparseable');
6435
+ return this.acceptValue(parsed, false);
6436
+ }
6437
+ const result = parseNumber(draft, this.format(), {
6438
+ allowExpressions: this.allowExpressions(),
6439
+ currency: this.currency(),
6440
+ });
6441
+ if (result.status === 'empty') {
6442
+ this.draft.set(null);
6443
+ this.draftInvalid.set(false);
6444
+ this.write(null);
6445
+ return;
6446
+ }
6447
+ if (result.status === 'error')
6448
+ return this.reject(draft, result.reason);
6449
+ this.acceptValue(result.value, result.viaExpression);
6450
+ }
6451
+ acceptValue(parsed, viaExpression) {
6452
+ const settled = settleNumber(parsed, this.format());
6453
+ const clamped = clampDecimal(settled, this.min(), this.max());
6454
+ if (clamped.hit && !this.clampOnCommit()) {
6455
+ return this.reject(this.draft() ?? settled, clamped.hit);
6456
+ }
6457
+ this.draft.set(null);
6458
+ this.draftInvalid.set(false);
6459
+ this.write(clamped.value);
6460
+ if (clamped.hit) {
6461
+ const bound = clamped.hit === 'min' ? this.min() : this.max();
6462
+ this.announcer.announce(`${clamped.hit === 'min' ? 'Minimum' : 'Maximum'} is ${bound}. Value set to ${bound}.`);
6463
+ }
6464
+ else if (viaExpression) {
6465
+ this.announcer.announce(`${formatNumber(clamped.value, this.format())}.`);
6466
+ }
6467
+ }
6468
+ reject(raw, reason) {
6469
+ this.draftInvalid.set(true);
6470
+ this.rejected.emit({ raw, reason });
6471
+ this.announcer.announce(this.rejectionMessage(raw, reason));
6472
+ }
6473
+ rejectionMessage(raw, reason) {
6474
+ switch (reason) {
6475
+ case 'min':
6476
+ return `${raw} is below the minimum of ${this.min()}.`;
6477
+ case 'max':
6478
+ return `${raw} is above the maximum of ${this.max()}.`;
6479
+ case 'not-integer':
6480
+ return `${raw} must be a whole number.`;
6481
+ default:
6482
+ return `${raw} is not a number.`;
6483
+ }
6484
+ }
6485
+ // --- Stepping -------------------------------------------------------------
6486
+ stepSize(magnitude) {
6487
+ if (magnitude === 'normal')
6488
+ return this.step();
6489
+ if (magnitude === 'large')
6490
+ return this.largeStep() ?? this.step() * 10;
6491
+ return this.smallStep() ?? null;
6492
+ }
6493
+ /**
6494
+ * Apply one step. An empty field commits `emptyStepValue ?? min ?? 0`, so ↑
6495
+ * on a blank quantity gives 1 rather than NaN.
6496
+ */
6497
+ applyStep(direction, magnitude = 'normal', announce = true) {
6498
+ if (this.disabled() || this.readOnly())
6499
+ return;
6500
+ const size = this.stepSize(magnitude);
6501
+ if (size == null)
6502
+ return;
6503
+ // Type-then-step should step from what is on screen, not what was committed.
6504
+ if (this.draft() != null)
6505
+ this.commitDraft();
6506
+ if (this.draftInvalid())
6507
+ return;
6508
+ const from = this.value();
6509
+ const current = this.canonical();
6510
+ if (current == null) {
6511
+ const seed = toDecimal(this.emptyStepValue() ?? this.min() ?? 0);
6512
+ this.write(seed);
6513
+ this.stepped.emit({ from, to: Number(seed), by: 0 });
6514
+ if (announce)
6515
+ this.announceValue();
6516
+ return;
6517
+ }
6518
+ const next = stepDecimal(current, direction, {
6519
+ step: size,
6520
+ min: this.min(),
6521
+ max: this.max(),
6522
+ stepOrigin: this.stepOrigin(),
6523
+ wrap: this.wrap(),
6524
+ });
6525
+ if (next === current) {
6526
+ if (announce)
6527
+ this.announceFence(direction);
6528
+ return;
6529
+ }
6530
+ this.write(next);
6531
+ this.stepped.emit({ from, to: Number(next), by: Number(next) - (from ?? 0) });
6532
+ if (announce)
6533
+ this.announceValue();
6534
+ }
6535
+ announceValue() {
6536
+ this.announcer.announce(`${speakNumber(this.canonical(), this.format())}.`);
6537
+ }
6538
+ announceFence(direction) {
6539
+ const bound = direction > 0 ? this.max() : this.min();
6540
+ if (bound == null)
6541
+ return;
6542
+ this.announcer.announce(`${direction > 0 ? 'Maximum' : 'Minimum'}, ${bound}.`);
6543
+ }
6544
+ // --- Hold to repeat -------------------------------------------------------
6545
+ repeatTiming = () => {
5515
6546
  const options = this.componentOptions();
5516
- const fill = this.theme.colors()[options.color ?? 'primary'];
5517
- const track = this.theme.colors()[options.trackColor ?? 'surface-variant'];
5518
- const radius = this.theme.radii()[options.borderRadius ?? 'max'];
5519
- const trackHeight = options.trackHeight ?? 4;
5520
- const thumbSize = options.thumbSize ?? 16;
5521
- // Webkit paints the fill via a gradient stopped at the custom property;
5522
- // Firefox has a real ::-moz-range-progress.
5523
- const trackFill = `linear-gradient(to right, ${fill} var(--uni-slider-fill), ${track} var(--uni-slider-fill))`;
5524
- const thumb = {
5525
- appearance: 'none',
5526
- width: thumbSize,
5527
- height: thumbSize,
5528
- borderRadius: radius,
5529
- border: 'none',
5530
- background: fill,
5531
- cursor: 'pointer',
6547
+ return {
6548
+ delayMs: options.repeatDelayMs,
6549
+ intervalMs: options.repeatIntervalMs,
6550
+ fastIntervalMs: options.repeatFastIntervalMs,
6551
+ rampMs: options.repeatRampMs,
5532
6552
  };
5533
- return css({
5534
- appearance: 'none',
5535
- width: '100%',
5536
- height: thumbSize,
5537
- margin: 0,
5538
- background: 'transparent',
5539
- cursor: 'pointer',
5540
- '&::-webkit-slider-runnable-track': {
5541
- height: trackHeight,
5542
- borderRadius: radius,
5543
- background: trackFill,
5544
- },
5545
- '&::-webkit-slider-thumb': {
5546
- ...thumb,
5547
- marginTop: (trackHeight - thumbSize) / 2,
6553
+ };
6554
+ /**
6555
+ * The live region announces on release only — a screen reader narrating two
6556
+ * hundred intermediate values is a denial of service.
6557
+ */
6558
+ increment = createPressRepeat({
6559
+ onStep: () => this.applyStep(1, 'normal', false),
6560
+ onRelease: () => this.announceValue(),
6561
+ disabled: () => this.disabled() || this.readOnly() || this.atMax(),
6562
+ repeat: () => this.repeat(),
6563
+ // A native spinner leaves focus in its field; without this the arrow keys
6564
+ // go dead the moment you click a stepper.
6565
+ focus: () => this.inputRef().nativeElement.focus(),
6566
+ timing: this.repeatTiming,
6567
+ });
6568
+ decrement = createPressRepeat({
6569
+ onStep: () => this.applyStep(-1, 'normal', false),
6570
+ onRelease: () => this.announceValue(),
6571
+ disabled: () => this.disabled() || this.readOnly() || this.atMin(),
6572
+ repeat: () => this.repeat(),
6573
+ // A native spinner leaves focus in its field; without this the arrow keys
6574
+ // go dead the moment you click a stepper.
6575
+ focus: () => this.inputRef().nativeElement.focus(),
6576
+ timing: this.repeatTiming,
6577
+ });
6578
+ // --- Input events ---------------------------------------------------------
6579
+ onInput(text) {
6580
+ this.draft.set(text);
6581
+ // The flag describes a *committed* failure; editing clears it.
6582
+ this.draftInvalid.set(false);
6583
+ }
6584
+ onFocus() {
6585
+ this.focused.set(true);
6586
+ if (this.selectOnFocus()) {
6587
+ queueMicrotask(() => this.inputRef().nativeElement.select());
6588
+ }
6589
+ }
6590
+ onBlur() {
6591
+ this.focused.set(false);
6592
+ this.touched.set(true);
6593
+ if (this.commitOnBlur())
6594
+ this.commitDraft();
6595
+ this.increment.cancel();
6596
+ this.decrement.cancel();
6597
+ }
6598
+ onKeydown(event) {
6599
+ if (this.readOnly())
6600
+ return;
6601
+ const magnitude = event.shiftKey ? 'large' : event.altKey ? 'small' : 'normal';
6602
+ switch (event.key) {
6603
+ case 'ArrowUp':
6604
+ event.preventDefault();
6605
+ this.applyStep(1, magnitude);
6606
+ break;
6607
+ case 'ArrowDown':
6608
+ event.preventDefault();
6609
+ this.applyStep(-1, magnitude);
6610
+ break;
6611
+ case 'PageUp':
6612
+ event.preventDefault();
6613
+ this.applyStep(1, 'large');
6614
+ break;
6615
+ case 'PageDown':
6616
+ event.preventDefault();
6617
+ this.applyStep(-1, 'large');
6618
+ break;
6619
+ case 'Home': {
6620
+ // No-op when unbounded: nothing sensible lives at an open fence.
6621
+ const min = this.min();
6622
+ if (min == null)
6623
+ return;
6624
+ event.preventDefault();
6625
+ this.draft.set(null);
6626
+ this.write(toDecimal(min));
6627
+ this.announceValue();
6628
+ break;
6629
+ }
6630
+ case 'End': {
6631
+ const max = this.max();
6632
+ if (max == null)
6633
+ return;
6634
+ event.preventDefault();
6635
+ this.draft.set(null);
6636
+ this.write(toDecimal(max));
6637
+ this.announceValue();
6638
+ break;
6639
+ }
6640
+ case 'Enter':
6641
+ // Never submit the form while an uncommitted draft is in the field.
6642
+ if (this.draft() != null)
6643
+ event.preventDefault();
6644
+ this.commitDraft();
6645
+ break;
6646
+ case 'Escape':
6647
+ event.preventDefault();
6648
+ this.draft.set(null);
6649
+ this.draftInvalid.set(false);
6650
+ this.increment.cancel();
6651
+ this.decrement.cancel();
6652
+ break;
6653
+ case 'Tab':
6654
+ // Never trap: commit what is typed, then let focus move on.
6655
+ this.commitDraft();
6656
+ break;
6657
+ }
6658
+ }
6659
+ /**
6660
+ * Scroll-to-step, off by default. A *focused* `<input type="number">` changes
6661
+ * value on the wheel, which silently corrupts forms people are merely
6662
+ * scrolling past. When enabled this needs focus **and** hover, and it only
6663
+ * calls `preventDefault` when the value actually moved, so a page does not
6664
+ * get scroll-trapped on a field sitting at its max.
6665
+ */
6666
+ onWheel(event) {
6667
+ if (!this.wheel() || !this.focused() || this.disabled() || this.readOnly())
6668
+ return;
6669
+ const before = this.canonical();
6670
+ this.applyStep(event.deltaY < 0 ? 1 : -1);
6671
+ if (this.canonical() !== before)
6672
+ event.preventDefault();
6673
+ }
6674
+ // --- Styling --------------------------------------------------------------
6675
+ className = computed(() => css({ display: 'block' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
6676
+ fieldRowClass = computed(() => {
6677
+ const options = this.componentOptions();
6678
+ return css([
6679
+ {
6680
+ display: 'flex',
6681
+ alignItems: 'center',
6682
+ width: '100%',
6683
+ // Full field height, so a stretched stepper column is the height of the
6684
+ // field rather than of the text inside it.
6685
+ height: '100%',
5548
6686
  },
5549
- '&::-moz-range-track': { height: trackHeight, borderRadius: radius, background: track },
5550
- '&::-moz-range-progress': { height: trackHeight, borderRadius: radius, background: fill },
5551
- '&::-moz-range-thumb': thumb,
5552
- // The shared, themable focus indicator, in the track's fill color.
5553
- '&:focus-visible': { ...this.theme.focusRingStyle(fill) },
5554
- '&:disabled': {
5555
- cursor: 'not-allowed',
5556
- opacity: 0.5,
5557
- '&::-webkit-slider-thumb': { cursor: 'not-allowed' },
5558
- '&::-moz-range-thumb': { cursor: 'not-allowed' },
6687
+ this.theme.gap(options.affixGap ?? 'xs'),
6688
+ // Trailing inset, matching the leading one, so a suffix — or the text
6689
+ // itself — does not sit against the border. It rides the row because
6690
+ // `removeInputPlatformStyling` zeroes padding on `& input` at a higher
6691
+ // specificity than this class. Skipped when a stepper holds the trailing
6692
+ // edge: a button is meant to reach the border.
6693
+ this.showSteppers() ? undefined : this.theme.paddingRight(this.trailingInset()),
6694
+ ]);
6695
+ }, ...(ngDevMode ? [{ debugName: "fieldRowClass" }] : /* istanbul ignore next */ []));
6696
+ /** The shared field inset, reused on the trailing side so the two match. */
6697
+ trailingInset = computed(() => this.fieldChrome().paddingLeft, ...(ngDevMode ? [{ debugName: "trailingInset" }] : /* istanbul ignore next */ []));
6698
+ inputClass = computed(() => {
6699
+ const options = this.componentOptions();
6700
+ return css({
6701
+ flex: 1,
6702
+ minWidth: 0,
6703
+ border: 0,
6704
+ outline: 'none',
6705
+ background: 'transparent',
6706
+ color: 'inherit',
6707
+ font: 'inherit',
6708
+ padding: 0,
6709
+ textAlign: this.align() ?? options.align ?? 'start',
6710
+ ...(options.tabularNumerals === false ? {} : { fontVariantNumeric: 'tabular-nums' }),
6711
+ // Colour alone cannot carry "this is not a number" (WCAG 1.4.1), and the
6712
+ // same underline already means the same thing on an invalid uni-tag.
6713
+ ...(this.draftInvalid()
6714
+ ? {
6715
+ textDecoration: 'underline dashed',
6716
+ textUnderlineOffset: 3,
6717
+ textDecorationColor: this.theme.colors()['warn'],
6718
+ }
6719
+ : {}),
6720
+ });
6721
+ }, ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
6722
+ /**
6723
+ * The shared field chrome, read from the `input` theme entry — the same entry
6724
+ * `uni-input-box` resolves. Not a duplicate token: the inset has to be the
6725
+ * one every other field uses, or a money field stops lining up with the text
6726
+ * field above it.
6727
+ */
6728
+ fieldChrome = this.theme.getComponentOptions('input');
6729
+ /**
6730
+ * The leading inset for a prefix adornment. When there is a prefix the field
6731
+ * tells the box to stop insetting the `<input>` (`managedInset`) and puts the
6732
+ * inset here instead, so the `$` sits at the field's leading edge with the
6733
+ * number right after it. With no prefix the box keeps doing it — the text is
6734
+ * the leading edge then, and the box's rule outranks this class anyway.
6735
+ * `embedded` fields have no chrome, so they get no inset either.
6736
+ */
6737
+ leadingInset = computed(() => this.embedded() ? undefined : this.theme.paddingLeft(this.fieldChrome().paddingLeft), ...(ngDevMode ? [{ debugName: "leadingInset" }] : /* istanbul ignore next */ []));
6738
+ affixBase() {
6739
+ const options = this.componentOptions();
6740
+ return {
6741
+ flex: 'none',
6742
+ userSelect: 'none',
6743
+ ...this.theme.color(options.affixColor ?? 'on-primary-surface-variant'),
6744
+ };
6745
+ }
6746
+ /** Carries the field's leading inset, being the first thing in the row. */
6747
+ prefixClass = computed(() => css([this.affixBase(), this.leadingInset()]), ...(ngDevMode ? [{ debugName: "prefixClass" }] : /* istanbul ignore next */ []));
6748
+ suffixClass = computed(() => css([this.affixBase()]), ...(ngDevMode ? [{ debugName: "suffixClass" }] : /* istanbul ignore next */ []));
6749
+ /** Shared chrome for every stepper button, in any layout. */
6750
+ stepperButton() {
6751
+ const options = this.componentOptions();
6752
+ const target = options.minTouchTarget ?? 24;
6753
+ return {
6754
+ display: 'grid',
6755
+ placeItems: 'center',
6756
+ flex: 'none',
6757
+ minWidth: target,
6758
+ minHeight: target,
6759
+ padding: 0,
6760
+ border: 0,
6761
+ background: 'transparent',
6762
+ color: 'inherit',
6763
+ cursor: 'pointer',
6764
+ touchAction: 'none',
6765
+ '&:disabled': { opacity: 0.4, cursor: 'not-allowed' },
6766
+ ...this.theme.focusRing(),
6767
+ };
6768
+ }
6769
+ /** Split and trailing layouts: one square button per direction. */
6770
+ stepperClass = computed(() => css({
6771
+ ...this.stepperButton(),
6772
+ width: this.componentOptions().stepperWidth ?? 32,
6773
+ alignSelf: 'stretch',
6774
+ }), ...(ngDevMode ? [{ debugName: "stepperClass" }] : /* istanbul ignore next */ []));
6775
+ /** Stacked layout: two half-height arrows sharing one column. */
6776
+ stackedColumnClass = computed(() => css({
6777
+ display: 'flex',
6778
+ flexDirection: 'column',
6779
+ flex: 'none',
6780
+ alignSelf: 'stretch',
6781
+ justifyContent: 'center',
6782
+ width: this.componentOptions().stepperWidth ?? 32,
6783
+ }), ...(ngDevMode ? [{ debugName: "stackedColumnClass" }] : /* istanbul ignore next */ []));
6784
+ stackedButtonClass = computed(() => css({
6785
+ ...this.stepperButton(),
6786
+ width: '100%',
6787
+ // The two arrows split the field height between them. They cannot each
6788
+ // reach `minTouchTarget` — 2 × 24 does not fit a 32px field — which is
6789
+ // why a coarse pointer gets the `split` layout instead; see `layout`.
6790
+ minHeight: 0,
6791
+ flex: 1,
6792
+ }), ...(ngDevMode ? [{ debugName: "stackedButtonClass" }] : /* istanbul ignore next */ []));
6793
+ glyphSize = computed(() => (this.layout() === 'stacked' ? 12 : 18), ...(ngDevMode ? [{ debugName: "glyphSize" }] : /* istanbul ignore next */ []));
6794
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniNumberInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6795
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniNumberInputComponent, isStandalone: true, selector: "uni-number-input, NumberInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, valueAsString: { classPropertyName: "valueAsString", publicName: "valueAsString", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", 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 }, decimals: { classPropertyName: "decimals", publicName: "decimals", isSignal: true, isRequired: false, transformFunction: null }, grouping: { classPropertyName: "grouping", publicName: "grouping", isSignal: true, isRequired: false, transformFunction: null }, numberFormat: { classPropertyName: "numberFormat", publicName: "numberFormat", isSignal: true, isRequired: false, transformFunction: null }, roundingMode: { classPropertyName: "roundingMode", publicName: "roundingMode", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, valueIsFraction: { classPropertyName: "valueIsFraction", publicName: "valueIsFraction", isSignal: true, isRequired: false, transformFunction: null }, unitAnnouncement: { classPropertyName: "unitAnnouncement", publicName: "unitAnnouncement", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", 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 }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, smallStep: { classPropertyName: "smallStep", publicName: "smallStep", isSignal: true, isRequired: false, transformFunction: null }, stepOrigin: { classPropertyName: "stepOrigin", publicName: "stepOrigin", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, clampOnCommit: { classPropertyName: "clampOnCommit", publicName: "clampOnCommit", isSignal: true, isRequired: false, transformFunction: null }, emptyStepValue: { classPropertyName: "emptyStepValue", publicName: "emptyStepValue", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, selectOnFocus: { classPropertyName: "selectOnFocus", publicName: "selectOnFocus", isSignal: true, isRequired: false, transformFunction: null }, allowExpressions: { classPropertyName: "allowExpressions", publicName: "allowExpressions", isSignal: true, isRequired: false, transformFunction: null }, wheel: { classPropertyName: "wheel", publicName: "wheel", isSignal: true, isRequired: false, transformFunction: null }, repeat: { classPropertyName: "repeat", publicName: "repeat", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, stepperLayout: { classPropertyName: "stepperLayout", publicName: "stepperLayout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", valueAsString: "valueAsStringChange", stepped: "stepped", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'numberInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Stepper buttons, shared by every layout. Real <button>s so they are\n announced and activated as buttons, but tabindex=\"-1\": they are pointer\n affordances, and the keyboard route is the arrow keys. The same reasoning\n governs uni-combobox's chevron and uni-tag-input's per-chip remove. -->\n<ng-template #decrementButton let-styleClass=\"styleClass\" let-icon=\"icon\">\n <button\n type=\"button\"\n tabindex=\"-1\"\n [class]=\"styleClass\"\n [disabled]=\"disabled() || atMin()\"\n [attr.aria-label]=\"'Decrease ' + label()\"\n (pointerdown)=\"decrement.press($event)\"\n (pointerup)=\"decrement.release()\"\n (pointercancel)=\"decrement.cancel()\"\n (lostpointercapture)=\"decrement.release()\"\n >\n <uni-icon [name]=\"icon\" [size]=\"glyphSize()\" />\n </button>\n</ng-template>\n\n<ng-template #incrementButton let-styleClass=\"styleClass\" let-icon=\"icon\">\n <button\n type=\"button\"\n tabindex=\"-1\"\n [class]=\"styleClass\"\n [disabled]=\"disabled() || atMax()\"\n [attr.aria-label]=\"'Increase ' + label()\"\n (pointerdown)=\"increment.press($event)\"\n (pointerup)=\"increment.release()\"\n (pointercancel)=\"increment.cancel()\"\n (lostpointercapture)=\"increment.release()\"\n >\n <uni-icon [name]=\"icon\" [size]=\"glyphSize()\" />\n </button>\n</ng-template>\n\n<ng-template #fieldContent>\n <div [class]=\"fieldRowClass()\">\n @if (showSteppers() && layout() === 'split') {\n <ng-container\n [ngTemplateOutlet]=\"decrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().decrementIcon ?? 'minus',\n }\"\n ></ng-container>\n }\n\n <!-- Affixes are adornments, not text: they live outside the <input>, so\n caret math, select-all and paste never step over them, and they are\n aria-hidden because aria-valuetext already speaks them. -->\n @if (format().prefix) {\n <span [class]=\"prefixClass()\" aria-hidden=\"true\">{{ format().prefix }}</span>\n }\n\n <input\n #field\n type=\"text\"\n role=\"spinbutton\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [value]=\"displayText()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readOnly()\"\n [attr.inputmode]=\"format().inputMode\"\n [attr.placeholder]=\"placeholder() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-valuenow]=\"canonicalForAria()\"\n [attr.aria-valuemin]=\"min() ?? null\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuetext]=\"valueTextForAria()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-readonly]=\"readOnly() ? true : null\"\n (input)=\"onInput($any($event.target).value)\"\n (keydown)=\"onKeydown($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (wheel)=\"onWheel($event)\"\n />\n\n @if (format().suffix) {\n <span [class]=\"suffixClass()\" aria-hidden=\"true\">{{ format().suffix }}</span>\n }\n\n @if (showSteppers()) {\n @switch (layout()) {\n @case ('split') {\n <ng-container\n [ngTemplateOutlet]=\"incrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().incrementIcon ?? 'plus',\n }\"\n ></ng-container>\n }\n @case ('trailing') {\n <ng-container\n [ngTemplateOutlet]=\"decrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().decrementIcon ?? 'minus',\n }\"\n ></ng-container>\n <ng-container\n [ngTemplateOutlet]=\"incrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().incrementIcon ?? 'plus',\n }\"\n ></ng-container>\n }\n @case ('stacked') {\n <span [class]=\"stackedColumnClass()\">\n <ng-container\n [ngTemplateOutlet]=\"incrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stackedButtonClass(),\n icon: componentOptions().stepUpIcon ?? 'chevronUp',\n }\"\n ></ng-container>\n <ng-container\n [ngTemplateOutlet]=\"decrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stackedButtonClass(),\n icon: componentOptions().stepDownIcon ?? 'chevronDown',\n }\"\n ></ng-container>\n </span>\n }\n }\n }\n </div>\n</ng-template>\n\n<!-- `embedded` drops the field chrome for composers that supply their own. -->\n@if (embedded()) {\n <ng-container [ngTemplateOutlet]=\"fieldContent\"></ng-container>\n} @else {\n <!-- The inset moves onto the prefix (or the input when there is none), so an\n adornment sits at the field's leading edge instead of hugging its border. -->\n <uni-input-box\n [error]=\"showError()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"true\"\n [managedInset]=\"!!format().prefix\"\n >\n <ng-container [ngTemplateOutlet]=\"fieldContent\"></ng-container>\n </uni-input-box>\n}\n\n<!-- Said once per field rather than repeated on both buttons. -->\n<span [id]=\"hintId\" [class]=\"srOnly\">\n Use the up and down arrow keys to change the value.\n</span>\n\n<!-- Clamps, fences, rejections and expression results are each a purely visual\n event otherwise. Held stepping announces on release only. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6796
+ }
6797
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniNumberInputComponent, decorators: [{
6798
+ type: Component,
6799
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-number-input, NumberInput', imports: [NgTemplateOutlet, UniIconComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'numberInput' }], host: { '[class]': 'className()' }, template: "<!-- Stepper buttons, shared by every layout. Real <button>s so they are\n announced and activated as buttons, but tabindex=\"-1\": they are pointer\n affordances, and the keyboard route is the arrow keys. The same reasoning\n governs uni-combobox's chevron and uni-tag-input's per-chip remove. -->\n<ng-template #decrementButton let-styleClass=\"styleClass\" let-icon=\"icon\">\n <button\n type=\"button\"\n tabindex=\"-1\"\n [class]=\"styleClass\"\n [disabled]=\"disabled() || atMin()\"\n [attr.aria-label]=\"'Decrease ' + label()\"\n (pointerdown)=\"decrement.press($event)\"\n (pointerup)=\"decrement.release()\"\n (pointercancel)=\"decrement.cancel()\"\n (lostpointercapture)=\"decrement.release()\"\n >\n <uni-icon [name]=\"icon\" [size]=\"glyphSize()\" />\n </button>\n</ng-template>\n\n<ng-template #incrementButton let-styleClass=\"styleClass\" let-icon=\"icon\">\n <button\n type=\"button\"\n tabindex=\"-1\"\n [class]=\"styleClass\"\n [disabled]=\"disabled() || atMax()\"\n [attr.aria-label]=\"'Increase ' + label()\"\n (pointerdown)=\"increment.press($event)\"\n (pointerup)=\"increment.release()\"\n (pointercancel)=\"increment.cancel()\"\n (lostpointercapture)=\"increment.release()\"\n >\n <uni-icon [name]=\"icon\" [size]=\"glyphSize()\" />\n </button>\n</ng-template>\n\n<ng-template #fieldContent>\n <div [class]=\"fieldRowClass()\">\n @if (showSteppers() && layout() === 'split') {\n <ng-container\n [ngTemplateOutlet]=\"decrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().decrementIcon ?? 'minus',\n }\"\n ></ng-container>\n }\n\n <!-- Affixes are adornments, not text: they live outside the <input>, so\n caret math, select-all and paste never step over them, and they are\n aria-hidden because aria-valuetext already speaks them. -->\n @if (format().prefix) {\n <span [class]=\"prefixClass()\" aria-hidden=\"true\">{{ format().prefix }}</span>\n }\n\n <input\n #field\n type=\"text\"\n role=\"spinbutton\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [value]=\"displayText()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readOnly()\"\n [attr.inputmode]=\"format().inputMode\"\n [attr.placeholder]=\"placeholder() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-valuenow]=\"canonicalForAria()\"\n [attr.aria-valuemin]=\"min() ?? null\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuetext]=\"valueTextForAria()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-readonly]=\"readOnly() ? true : null\"\n (input)=\"onInput($any($event.target).value)\"\n (keydown)=\"onKeydown($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (wheel)=\"onWheel($event)\"\n />\n\n @if (format().suffix) {\n <span [class]=\"suffixClass()\" aria-hidden=\"true\">{{ format().suffix }}</span>\n }\n\n @if (showSteppers()) {\n @switch (layout()) {\n @case ('split') {\n <ng-container\n [ngTemplateOutlet]=\"incrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().incrementIcon ?? 'plus',\n }\"\n ></ng-container>\n }\n @case ('trailing') {\n <ng-container\n [ngTemplateOutlet]=\"decrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().decrementIcon ?? 'minus',\n }\"\n ></ng-container>\n <ng-container\n [ngTemplateOutlet]=\"incrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stepperClass(),\n icon: componentOptions().incrementIcon ?? 'plus',\n }\"\n ></ng-container>\n }\n @case ('stacked') {\n <span [class]=\"stackedColumnClass()\">\n <ng-container\n [ngTemplateOutlet]=\"incrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stackedButtonClass(),\n icon: componentOptions().stepUpIcon ?? 'chevronUp',\n }\"\n ></ng-container>\n <ng-container\n [ngTemplateOutlet]=\"decrementButton\"\n [ngTemplateOutletContext]=\"{\n styleClass: stackedButtonClass(),\n icon: componentOptions().stepDownIcon ?? 'chevronDown',\n }\"\n ></ng-container>\n </span>\n }\n }\n }\n </div>\n</ng-template>\n\n<!-- `embedded` drops the field chrome for composers that supply their own. -->\n@if (embedded()) {\n <ng-container [ngTemplateOutlet]=\"fieldContent\"></ng-container>\n} @else {\n <!-- The inset moves onto the prefix (or the input when there is none), so an\n adornment sits at the field's leading edge instead of hugging its border. -->\n <uni-input-box\n [error]=\"showError()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"true\"\n [managedInset]=\"!!format().prefix\"\n >\n <ng-container [ngTemplateOutlet]=\"fieldContent\"></ng-container>\n </uni-input-box>\n}\n\n<!-- Said once per field rather than repeated on both buttons. -->\n<span [id]=\"hintId\" [class]=\"srOnly\">\n Use the up and down arrow keys to change the value.\n</span>\n\n<!-- Clamps, fences, rejections and expression results are each a purely visual\n event otherwise. Held stepping announces on release only. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n" }]
6800
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], valueAsString: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueAsString", required: false }] }, { type: i0.Output, args: ["valueAsStringChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], decimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimals", required: false }] }], grouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouping", required: false }] }], numberFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "numberFormat", required: false }] }], roundingMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "roundingMode", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], valueIsFraction: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueIsFraction", required: false }] }], unitAnnouncement: [{ type: i0.Input, args: [{ isSignal: true, alias: "unitAnnouncement", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", 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 }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "largeStep", required: false }] }], smallStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "smallStep", required: false }] }], stepOrigin: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepOrigin", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }], clampOnCommit: [{ type: i0.Input, args: [{ isSignal: true, alias: "clampOnCommit", required: false }] }], emptyStepValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyStepValue", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], selectOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectOnFocus", required: false }] }], allowExpressions: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowExpressions", required: false }] }], wheel: [{ type: i0.Input, args: [{ isSignal: true, alias: "wheel", required: false }] }], repeat: [{ type: i0.Input, args: [{ isSignal: true, alias: "repeat", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], stepperLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepperLayout", required: false }] }], stepped: [{ type: i0.Output, args: ["stepped"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
6801
+
6802
+ /**
6803
+ * Bounded numeric input by pointer, for values where the *position* is the
6804
+ * information: volume, opacity, weightings, price filters.
6805
+ *
6806
+ * Custom thumbs rather than `<input type="range">`, which the previous version
6807
+ * used: one native range input cannot carry two thumbs, marks or a tooltip, and
6808
+ * a second component for the range case would mean two keyboard maps to keep in
6809
+ * step. The step model and the keyboard map are the cdk's, shared with the
6810
+ * numeric fields, so nothing new is learned moving between them.
6811
+ *
6812
+ * All arithmetic on values runs through the cdk's exact decimal helpers —
6813
+ * stepping `0.1` never yields `0.30000000000000004`. Only pointer *positions*
6814
+ * use floats, and they are snapped to the grid before becoming a value.
6815
+ */
6816
+ class UniSliderComponent extends BaseComponent {
6817
+ // --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
6818
+ /** Shape follows `mode`: a number when `single`, a `UniNumberRange` when `range`. */
6819
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6820
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
6821
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
6822
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
6823
+ dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
6824
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
6825
+ ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
6826
+ // --- Configuration -------------------------------------------------------
6827
+ /** Accessible name, e.g. "Opacity". Names the group in range mode. */
6828
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
6829
+ mode = input('single', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
6830
+ // `min`/`max` are part of the FormValueControl contract — Signal Forms syncs
6831
+ // them from min()/max() validators — so their type must admit undefined.
6832
+ min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
6833
+ max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
6834
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
6835
+ /** `PageUp`/`PageDown` and `Shift+Arrow`. Default: a tenth of the range. */
6836
+ largeStep = input(...(ngDevMode ? [undefined, { debugName: "largeStep" }] : /* istanbul ignore next */ []));
6837
+ /** Fill anchor. Defaults to `min`; set `0` for a slider that spans ±. */
6838
+ origin = input(...(ngDevMode ? [undefined, { debugName: "origin" }] : /* istanbul ignore next */ []));
6839
+ marks = input([], ...(ngDevMode ? [{ debugName: "marks" }] : /* istanbul ignore next */ []));
6840
+ /** Marks become the only valid stops — t-shirt sizing, Likert scales. */
6841
+ snapToMarks = input(false, ...(ngDevMode ? [{ debugName: "snapToMarks" }] : /* istanbul ignore next */ []));
6842
+ /**
6843
+ * Where the current value is shown. `tooltip` appears on hover, focus and
6844
+ * drag; `inline` sits at the trailing edge of the track; `input` seats a
6845
+ * compact `uni-number-input` there, two-way bound to the same value — drag
6846
+ * for the ballpark, type for the exact figure, which is the pairing that
6847
+ * makes bounded numeric input actually usable.
6848
+ *
6849
+ * `input` applies to `single` mode only; a range would need two fields, and
6850
+ * `inline` already reads well for two ends.
6851
+ */
6852
+ valueDisplay = input('none', ...(ngDevMode ? [{ debugName: "valueDisplay" }] : /* istanbul ignore next */ []));
6853
+ /** Overrides how a value is rendered and spoken. */
6854
+ formatValue = input(...(ngDevMode ? [undefined, { debugName: "formatValue" }] : /* istanbul ignore next */ []));
6855
+ /** Enforced distance between the two ends, in range mode. */
6856
+ minGap = input(...(ngDevMode ? [undefined, { debugName: "minGap" }] : /* istanbul ignore next */ []));
6857
+ variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
6858
+ // --- Events --------------------------------------------------------------
6859
+ /** Continuous, during a drag or a held key. Bind this for a live preview. */
6860
+ sliding = output();
6861
+ /**
6862
+ * Committed — on pointer release and key-up. **A form should bind this**:
6863
+ * piping a 60 Hz stream into a model is how sliders get blamed for jank.
6864
+ */
6865
+ changed = output();
6866
+ trackRef = viewChild.required('track');
6867
+ thumbRefs = viewChildren('thumb', ...(ngDevMode ? [{ debugName: "thumbRefs" }] : /* istanbul ignore next */ []));
6868
+ srOnly = css(visuallyHidden);
6869
+ /** Fences and swaps only — `aria-valuetext` already narrates movement. */
6870
+ announcer = createAnnouncer();
6871
+ groupId = uniqueId('uni-slider');
6872
+ /** True from pointerdown until release, to suppress the jump transition. */
6873
+ dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
6874
+ draggingThumb = null;
6875
+ /** Set by keydown, consumed by keyup, so one commit follows a key run. */
6876
+ keyed = false;
6877
+ isRange = computed(() => this.mode() === 'range', ...(ngDevMode ? [{ debugName: "isRange" }] : /* istanbul ignore next */ []));
6878
+ resolvedMin = computed(() => this.min() ?? 0, ...(ngDevMode ? [{ debugName: "resolvedMin" }] : /* istanbul ignore next */ []));
6879
+ resolvedMax = computed(() => this.max() ?? 100, ...(ngDevMode ? [{ debugName: "resolvedMax" }] : /* istanbul ignore next */ []));
6880
+ derivePair(value) {
6881
+ const min = this.resolvedMin();
6882
+ const max = this.resolvedMax();
6883
+ if (this.isRange()) {
6884
+ const range = (value ?? {});
6885
+ return [range.start ?? min, range.end ?? max];
6886
+ }
6887
+ return [typeof value === 'number' ? value : min, max];
6888
+ }
6889
+ /**
6890
+ * The two thumb positions, by **identity** rather than by order: thumb 0 is
6891
+ * whichever thumb the user grabbed first, not necessarily the lower one.
6892
+ *
6893
+ * A `linkedSignal` so an external write to `value` resets them, while a drag
6894
+ * moves them without writing the model on every frame. The computation
6895
+ * deliberately keeps the existing order when the incoming value describes the
6896
+ * same two positions — a commit writes the range back *sorted*, and
6897
+ * re-deriving from that would un-cross a crossed pair and yank the dragged
6898
+ * thumb out from under the pointer mid-drag.
6899
+ */
6900
+ thumbs = linkedSignal({ ...(ngDevMode ? { debugName: "thumbs" } : /* istanbul ignore next */ {}), source: () => this.value(),
6901
+ computation: (value, previous) => {
6902
+ const next = this.derivePair(value);
6903
+ const prior = previous?.value;
6904
+ if (prior &&
6905
+ Math.min(prior[0], prior[1]) === Math.min(next[0], next[1]) &&
6906
+ Math.max(prior[0], prior[1]) === Math.max(next[0], next[1])) {
6907
+ return prior;
6908
+ }
6909
+ return next;
6910
+ } });
6911
+ thumbIndexes = computed(() => (this.isRange() ? [0, 1] : [0]), ...(ngDevMode ? [{ debugName: "thumbIndexes" }] : /* istanbul ignore next */ []));
6912
+ showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
6913
+ /** Default large step: a tenth of the range, snapped to the step grid. */
6914
+ resolvedLargeStep = computed(() => {
6915
+ const explicit = this.largeStep();
6916
+ if (explicit != null)
6917
+ return explicit;
6918
+ const step = this.step();
6919
+ const tenth = (this.resolvedMax() - this.resolvedMin()) / 10;
6920
+ const snapped = Math.round(tenth / step) * step;
6921
+ return snapped > 0 ? snapped : step;
6922
+ }, ...(ngDevMode ? [{ debugName: "resolvedLargeStep" }] : /* istanbul ignore next */ []));
6923
+ numberFormat = computed(() => resolveNumberFormat({ decimals: [0, Math.max(decimalScale(toDecimal(this.step())), 0)] }), ...(ngDevMode ? [{ debugName: "numberFormat" }] : /* istanbul ignore next */ []));
6924
+ formatted(value) {
6925
+ const custom = this.formatValue();
6926
+ if (custom)
6927
+ return custom(value);
6928
+ return formatNumber(toDecimal(value), this.numberFormat());
6929
+ }
6930
+ /** A mark's label speaks for its value, so a marks-only slider says "Medium". */
6931
+ markLabel(value) {
6932
+ return this.marks().find((mark) => mark.value === value && mark.label)?.label;
6933
+ }
6934
+ valueText(value) {
6935
+ return this.markLabel(value) ?? this.formatted(value);
6936
+ }
6937
+ // --- Geometry -------------------------------------------------------------
6938
+ percentOf(value) {
6939
+ const min = this.resolvedMin();
6940
+ const span = this.resolvedMax() - min;
6941
+ if (span <= 0)
6942
+ return 0;
6943
+ return Math.min(100, Math.max(0, ((value - min) / span) * 100));
6944
+ }
6945
+ lowValue = computed(() => {
6946
+ const [a, b] = this.thumbs();
6947
+ return this.isRange() ? Math.min(a, b) : a;
6948
+ }, ...(ngDevMode ? [{ debugName: "lowValue" }] : /* istanbul ignore next */ []));
6949
+ highValue = computed(() => {
6950
+ const [a, b] = this.thumbs();
6951
+ return this.isRange() ? Math.max(a, b) : a;
6952
+ }, ...(ngDevMode ? [{ debugName: "highValue" }] : /* istanbul ignore next */ []));
6953
+ /** The fill spans between the ends in range mode, or origin → value. */
6954
+ fillStart = computed(() => {
6955
+ if (this.isRange())
6956
+ return this.percentOf(this.lowValue());
6957
+ const origin = this.origin() ?? this.resolvedMin();
6958
+ return this.percentOf(Math.min(origin, this.thumbs()[0]));
6959
+ }, ...(ngDevMode ? [{ debugName: "fillStart" }] : /* istanbul ignore next */ []));
6960
+ fillEnd = computed(() => {
6961
+ if (this.isRange())
6962
+ return 100 - this.percentOf(this.highValue());
6963
+ const origin = this.origin() ?? this.resolvedMin();
6964
+ return 100 - this.percentOf(Math.max(origin, this.thumbs()[0]));
6965
+ }, ...(ngDevMode ? [{ debugName: "fillEnd" }] : /* istanbul ignore next */ []));
6966
+ hasMarkLabels = computed(() => this.marks().some((mark) => !!mark.label), ...(ngDevMode ? [{ debugName: "hasMarkLabels" }] : /* istanbul ignore next */ []));
6967
+ /** The number-field readout only makes sense for a single value. */
6968
+ showReadoutField = computed(() => this.valueDisplay() === 'input' && !this.isRange(), ...(ngDevMode ? [{ debugName: "showReadoutField" }] : /* istanbul ignore next */ []));
6969
+ /** Fraction digits the readout should accept, taken from the step. */
6970
+ readoutDecimals = computed(() => [
6971
+ 0,
6972
+ decimalScale(toDecimal(this.step())),
6973
+ ], ...(ngDevMode ? [{ debugName: "readoutDecimals" }] : /* istanbul ignore next */ []));
6974
+ /**
6975
+ * The readout drives the thumb. Guarded against the write-back cycle: the
6976
+ * field is fed from `value`, so a commit here would otherwise bounce.
6977
+ */
6978
+ onReadoutValue(next) {
6979
+ if (next == null)
6980
+ return;
6981
+ const snapped = this.snapToGrid(next);
6982
+ if (snapped === this.thumbs()[0])
6983
+ return;
6984
+ this.setThumb(0, snapped, true);
6985
+ }
6986
+ // --- ARIA per thumb -------------------------------------------------------
6987
+ thumbValue(index) {
6988
+ return this.thumbs()[index];
6989
+ }
6990
+ /**
6991
+ * Each thumb's bound is the *other thumb's* position, so a screen-reader user
6992
+ * is told where the wall actually is rather than where the track ends.
6993
+ */
6994
+ thumbMin(index) {
6995
+ if (!this.isRange())
6996
+ return this.resolvedMin();
6997
+ return this.isLower(index) ? this.resolvedMin() : this.lowValue();
6998
+ }
6999
+ thumbMax(index) {
7000
+ if (!this.isRange())
7001
+ return this.resolvedMax();
7002
+ return this.isLower(index) ? this.highValue() : this.resolvedMax();
7003
+ }
7004
+ /** Thumbs may cross; which one is "minimum" follows position, not identity. */
7005
+ isLower(index) {
7006
+ const [a, b] = this.thumbs();
7007
+ return index === 0 ? a <= b : b < a;
7008
+ }
7009
+ thumbLabel(index) {
7010
+ if (!this.isRange())
7011
+ return this.label();
7012
+ return `${this.label()}, ${this.isLower(index) ? 'minimum' : 'maximum'}`;
7013
+ }
7014
+ // --- Value plumbing -------------------------------------------------------
7015
+ currentValue() {
7016
+ if (!this.isRange())
7017
+ return this.thumbs()[0];
7018
+ return { start: this.lowValue(), end: this.highValue() };
7019
+ }
7020
+ /** Exact `min + n · step`, so a snapped position never carries float drift. */
7021
+ snapToGrid(raw) {
7022
+ const marks = this.marks();
7023
+ if (this.snapToMarks() && marks.length) {
7024
+ return marks.reduce((best, mark) => (Math.abs(mark.value - raw) < Math.abs(best - raw) ? mark.value : best), marks[0].value);
7025
+ }
7026
+ const min = toDecimal(this.resolvedMin());
7027
+ const step = toDecimal(this.step());
7028
+ if (Number(step) === 0)
7029
+ return this.resolvedMin();
7030
+ const steps = Math.round((raw - Number(min)) / Number(step));
7031
+ const scale = Math.max(decimalScale(min), decimalScale(step));
7032
+ const exact = fromScaled(toScaled(min, scale) + BigInt(steps) * toScaled(step, scale), scale);
7033
+ return this.clamp(Number(exact));
7034
+ }
7035
+ clamp(value) {
7036
+ return Math.min(this.resolvedMax(), Math.max(this.resolvedMin(), value));
7037
+ }
7038
+ /**
7039
+ * Move a thumb. `commit` writes the model and emits `changed`; without it the
7040
+ * move is visual and only emits `sliding`.
7041
+ */
7042
+ setThumb(index, next, commit) {
7043
+ let target = this.clamp(next);
7044
+ // With a minimum gap the ends fence each other instead of swapping — the
7045
+ // gap is the whole point of setting it.
7046
+ const gap = this.minGap();
7047
+ if (gap != null && this.isRange()) {
7048
+ const [a, b] = this.thumbs();
7049
+ const other = index === 0 ? b : a;
7050
+ const current = index === 0 ? a : b;
7051
+ if (current <= other)
7052
+ target = Math.min(target, other - gap);
7053
+ else
7054
+ target = Math.max(target, other + gap);
7055
+ target = this.clamp(target);
7056
+ }
7057
+ const previous = this.thumbs()[index];
7058
+ if (previous !== target) {
7059
+ this.thumbs.update((pair) => {
7060
+ const next = [...pair];
7061
+ next[index] = target;
7062
+ return next;
7063
+ });
7064
+ this.sliding.emit(this.currentValue());
7065
+ }
7066
+ if (commit)
7067
+ this.commit();
7068
+ }
7069
+ commit() {
7070
+ this.value.set(this.currentValue());
7071
+ this.changed.emit(this.currentValue());
7072
+ }
7073
+ announceValue(index) {
7074
+ this.announcer.announce(`${this.valueText(this.thumbs()[index])}.`);
7075
+ }
7076
+ // --- Pointer --------------------------------------------------------------
7077
+ /** True when the track is laid out right-to-left. */
7078
+ isRtl() {
7079
+ const track = this.trackRef().nativeElement;
7080
+ return getComputedStyle(track).direction === 'rtl';
7081
+ }
7082
+ /** Pointer x → a raw value. The track's visual direction flips in RTL; the value's does not. */
7083
+ valueFromPointer(event) {
7084
+ const rect = this.trackRef().nativeElement.getBoundingClientRect();
7085
+ if (rect.width <= 0)
7086
+ return this.resolvedMin();
7087
+ let ratio = (event.clientX - rect.left) / rect.width;
7088
+ if (this.isRtl())
7089
+ ratio = 1 - ratio;
7090
+ const min = this.resolvedMin();
7091
+ return min + Math.min(1, Math.max(0, ratio)) * (this.resolvedMax() - min);
7092
+ }
7093
+ nearestThumb(raw) {
7094
+ if (!this.isRange())
7095
+ return 0;
7096
+ const [a, b] = this.thumbs();
7097
+ return Math.abs(a - raw) <= Math.abs(b - raw) ? 0 : 1;
7098
+ }
7099
+ onTrackPointerDown(event) {
7100
+ if (this.disabled())
7101
+ return;
7102
+ event.preventDefault();
7103
+ const raw = this.valueFromPointer(event);
7104
+ const onThumb = event.target?.closest('[role="slider"]');
7105
+ const index = onThumb
7106
+ ? Number(onThumb.dataset['thumb'])
7107
+ : this.nearestThumb(raw);
7108
+ this.draggingThumb = index;
7109
+ this.thumbRefs()[index]?.nativeElement.focus();
7110
+ // Pressing the track jumps the nearest thumb there — no "grab the thumb
7111
+ // first" tax — and that jump animates. A drag never does.
7112
+ if (!onThumb)
7113
+ this.setThumb(index, this.snapToGrid(raw), false);
7114
+ this.dragging.set(true);
7115
+ const track = this.trackRef().nativeElement;
7116
+ if (typeof track.setPointerCapture === 'function') {
7117
+ try {
7118
+ track.setPointerCapture(event.pointerId);
7119
+ }
7120
+ catch {
7121
+ // Synthetic pointer id; the move/up listeners below still work.
7122
+ }
7123
+ }
7124
+ }
7125
+ onTrackPointerMove(event) {
7126
+ if (this.draggingThumb == null)
7127
+ return;
7128
+ this.setThumb(this.draggingThumb, this.snapToGrid(this.valueFromPointer(event)), false);
7129
+ }
7130
+ onTrackPointerUp() {
7131
+ const index = this.draggingThumb;
7132
+ this.dragging.set(false);
7133
+ if (index == null)
7134
+ return;
7135
+ this.draggingThumb = null;
7136
+ this.touched.set(true);
7137
+ this.commit();
7138
+ this.announceValue(index);
7139
+ }
7140
+ // --- Keyboard -------------------------------------------------------------
7141
+ /** Adjacent mark, when marks are the only stops. */
7142
+ markStep(current, direction) {
7143
+ const values = this.marks()
7144
+ .map((mark) => mark.value)
7145
+ .sort((a, b) => a - b);
7146
+ if (!values.length)
7147
+ return current;
7148
+ const at = values.indexOf(current);
7149
+ if (at < 0)
7150
+ return this.snapToGrid(current);
7151
+ return values[Math.min(values.length - 1, Math.max(0, at + direction))];
7152
+ }
7153
+ stepFrom(current, direction, large) {
7154
+ if (this.snapToMarks() && this.marks().length)
7155
+ return this.markStep(current, direction);
7156
+ const next = stepDecimal(toDecimal(current), direction, {
7157
+ step: large ? this.resolvedLargeStep() : this.step(),
7158
+ min: this.resolvedMin(),
7159
+ max: this.resolvedMax(),
7160
+ stepOrigin: 'min',
7161
+ });
7162
+ return Number(next);
7163
+ }
7164
+ onThumbKeydown(event, index) {
7165
+ if (this.disabled())
7166
+ return;
7167
+ const current = this.thumbs()[index];
7168
+ const large = event.shiftKey;
7169
+ // Horizontal arrows follow the picture, so they mirror in RTL; the vertical
7170
+ // ones follow the number and never do (APG's rule).
7171
+ const toward = this.isRtl() ? -1 : 1;
7172
+ // Every branch either assigns or returns, so this needs no initializer.
7173
+ let next;
7174
+ switch (event.key) {
7175
+ case 'ArrowUp':
7176
+ next = this.stepFrom(current, 1, large);
7177
+ break;
7178
+ case 'ArrowDown':
7179
+ next = this.stepFrom(current, -1, large);
7180
+ break;
7181
+ case 'ArrowRight':
7182
+ next = this.stepFrom(current, toward > 0 ? 1 : -1, large);
7183
+ break;
7184
+ case 'ArrowLeft':
7185
+ next = this.stepFrom(current, toward > 0 ? -1 : 1, large);
7186
+ break;
7187
+ case 'PageUp':
7188
+ next = this.stepFrom(current, 1, true);
7189
+ break;
7190
+ case 'PageDown':
7191
+ next = this.stepFrom(current, -1, true);
7192
+ break;
7193
+ case 'Home':
7194
+ next = this.resolvedMin();
7195
+ break;
7196
+ case 'End':
7197
+ next = this.resolvedMax();
7198
+ break;
7199
+ default:
7200
+ return;
7201
+ }
7202
+ event.preventDefault();
7203
+ this.keyed = true;
7204
+ this.setThumb(index, next, false);
7205
+ }
7206
+ /** One commit and one announcement per key run, not per repeat. */
7207
+ onThumbKeyup(index) {
7208
+ if (!this.keyed)
7209
+ return;
7210
+ this.keyed = false;
7211
+ this.touched.set(true);
7212
+ this.commit();
7213
+ this.announceValue(index);
7214
+ }
7215
+ onThumbBlur() {
7216
+ this.touched.set(true);
7217
+ }
7218
+ // --- Styling --------------------------------------------------------------
7219
+ className = computed(() => css({ display: 'block' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
7220
+ fillColor = computed(() => {
7221
+ const colors = this.theme.colors();
7222
+ return colors[this.variant()] ?? colors['primary'];
7223
+ }, ...(ngDevMode ? [{ debugName: "fillColor" }] : /* istanbul ignore next */ []));
7224
+ rootClass = computed(() => css({
7225
+ display: 'flex',
7226
+ flexDirection: 'column',
7227
+ width: '100%',
7228
+ ...(this.disabled() ? { cursor: 'not-allowed' } : {}),
7229
+ }), ...(ngDevMode ? [{ debugName: "rootClass" }] : /* istanbul ignore next */ []));
7230
+ rowClass = computed(() => {
7231
+ const options = this.componentOptions();
7232
+ return css({
7233
+ display: 'flex',
7234
+ alignItems: 'center',
7235
+ ...this.theme.gap(options.labelTypeface ? 'md' : 'md'),
7236
+ });
7237
+ }, ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
7238
+ trackClass = computed(() => {
7239
+ const options = this.componentOptions();
7240
+ const colors = this.theme.colors();
7241
+ const height = options.trackHeight ?? 4;
7242
+ const target = options.minTouchTarget ?? 24;
7243
+ const radius = this.theme.radii()[options.borderRadius ?? 'max'];
7244
+ return css({
7245
+ position: 'relative',
7246
+ flex: '1 1 auto',
7247
+ height,
7248
+ borderRadius: radius,
7249
+ backgroundColor: this.disabled()
7250
+ ? colors['disabled-container']
7251
+ : colors[options.trackColor ?? 'primary-container'],
7252
+ cursor: this.disabled() ? 'not-allowed' : 'pointer',
7253
+ // Vertical room for the hit areas, horizontal room so a thumb at either
7254
+ // fence is not clipped by the track's own box.
7255
+ marginBlock: Math.max(0, (target - height) / 2),
7256
+ marginInline: target / 2,
7257
+ // Only the track: a vertical page scroll starting here still scrolls.
7258
+ touchAction: 'none',
7259
+ });
7260
+ }, ...(ngDevMode ? [{ debugName: "trackClass" }] : /* istanbul ignore next */ []));
7261
+ fillClass = computed(() => {
7262
+ const options = this.componentOptions();
7263
+ const colors = this.theme.colors();
7264
+ const duration = options.transitionMs ?? 120;
7265
+ return css({
7266
+ position: 'absolute',
7267
+ insetBlock: 0,
7268
+ borderRadius: this.theme.radii()[options.borderRadius ?? 'max'],
7269
+ backgroundColor: this.disabled() ? colors['disabled'] : this.fillColor(),
7270
+ ...(this.dragging()
7271
+ ? {}
7272
+ : motionSafe({
7273
+ transitionProperty: 'inset-inline-start, inset-inline-end',
7274
+ transitionDuration: `${duration}ms`,
7275
+ transitionTimingFunction: 'ease',
7276
+ })),
7277
+ });
7278
+ }, ...(ngDevMode ? [{ debugName: "fillClass" }] : /* istanbul ignore next */ []));
7279
+ markClass = computed(() => {
7280
+ const options = this.componentOptions();
7281
+ const colors = this.theme.colors();
7282
+ const size = options.markSize ?? 3;
7283
+ return css({
7284
+ position: 'absolute',
7285
+ top: '50%',
7286
+ width: size,
7287
+ height: size,
7288
+ borderRadius: '50%',
7289
+ backgroundColor: colors[options.markColor ?? 'on-primary-container'],
7290
+ transform: 'translate(-50%, -50%)',
7291
+ opacity: 0.7,
7292
+ pointerEvents: 'none',
7293
+ });
7294
+ }, ...(ngDevMode ? [{ debugName: "markClass" }] : /* istanbul ignore next */ []));
7295
+ thumbClass = computed(() => {
7296
+ const options = this.componentOptions();
7297
+ const colors = this.theme.colors();
7298
+ const size = options.thumbSize ?? 16;
7299
+ const target = options.minTouchTarget ?? 24;
7300
+ const duration = options.transitionMs ?? 120;
7301
+ return css({
7302
+ position: 'absolute',
7303
+ top: '50%',
7304
+ // The hit area is the element; the visual dot is the pseudo-element, so
7305
+ // a 16px thumb still presents a 24px target (WCAG 2.5.8).
7306
+ width: target,
7307
+ height: target,
7308
+ transform: 'translate(-50%, -50%)',
7309
+ display: 'grid',
7310
+ placeItems: 'center',
7311
+ borderRadius: '50%',
7312
+ cursor: this.disabled() ? 'not-allowed' : 'grab',
7313
+ touchAction: 'none',
7314
+ pointerEvents: this.disabled() ? 'none' : 'auto',
7315
+ '&:active': { cursor: 'grabbing' },
7316
+ '&::after': {
7317
+ content: '""',
7318
+ width: size,
7319
+ height: size,
7320
+ borderRadius: this.theme.radii()[options.thumbBorderRadius ?? 'max'],
7321
+ backgroundColor: this.disabled() ? colors['on-disabled'] : this.fillColor(),
7322
+ border: `2px solid ${colors['background']}`,
7323
+ boxSizing: 'border-box',
7324
+ },
7325
+ ...this.theme.focusRing(),
7326
+ ...(this.dragging()
7327
+ ? {}
7328
+ : motionSafe({
7329
+ transitionProperty: 'inset-inline-start',
7330
+ transitionDuration: `${duration}ms`,
7331
+ transitionTimingFunction: 'ease',
7332
+ })),
7333
+ });
7334
+ }, ...(ngDevMode ? [{ debugName: "thumbClass" }] : /* istanbul ignore next */ []));
7335
+ tooltipClass = computed(() => {
7336
+ const options = this.componentOptions();
7337
+ const colors = this.theme.colors();
7338
+ return css({
7339
+ position: 'absolute',
7340
+ bottom: '100%',
7341
+ left: '50%',
7342
+ transform: 'translateX(-50%)',
7343
+ marginBottom: 4,
7344
+ padding: '2px 6px',
7345
+ whiteSpace: 'nowrap',
7346
+ pointerEvents: 'none',
7347
+ opacity: 0,
7348
+ backgroundColor: colors[options.tooltipColor ?? 'inverse-surface'],
7349
+ borderRadius: this.theme.radii()[options.tooltipBorderRadius ?? 'xs'],
7350
+ ...this.theme.color(options.tooltipTextColor ?? 'on-inverse-surface'),
7351
+ ...this.theme.typeface(options.labelTypeface ?? 'label'),
7352
+ ...this.theme.boxShadow(options.tooltipShadow ?? 'menu'),
7353
+ // Shown on hover and focus, and throughout a drag.
7354
+ '[role="slider"]:hover > &, [role="slider"]:focus-visible > &': { opacity: 1 },
7355
+ });
7356
+ }, ...(ngDevMode ? [{ debugName: "tooltipClass" }] : /* istanbul ignore next */ []));
7357
+ labelsClass = computed(() => {
7358
+ const options = this.componentOptions();
7359
+ const target = options.minTouchTarget ?? 24;
7360
+ return css({
7361
+ position: 'relative',
7362
+ height: 18,
7363
+ marginInline: target / 2,
7364
+ });
7365
+ }, ...(ngDevMode ? [{ debugName: "labelsClass" }] : /* istanbul ignore next */ []));
7366
+ labelClass = computed(() => {
7367
+ const options = this.componentOptions();
7368
+ return css({
7369
+ position: 'absolute',
7370
+ transform: 'translateX(-50%)',
7371
+ whiteSpace: 'nowrap',
7372
+ ...this.theme.color(options.labelColor ?? 'on-surface-variant'),
7373
+ ...this.theme.typeface(options.labelTypeface ?? 'label'),
7374
+ });
7375
+ }, ...(ngDevMode ? [{ debugName: "labelClass" }] : /* istanbul ignore next */ []));
7376
+ /** Narrow enough that the track keeps most of the row. */
7377
+ readoutFieldClass = computed(() => css({ flex: 'none', width: 130 }), ...(ngDevMode ? [{ debugName: "readoutFieldClass" }] : /* istanbul ignore next */ []));
7378
+ readoutClass = computed(() => {
7379
+ const options = this.componentOptions();
7380
+ return css({
7381
+ flex: 'none',
7382
+ minWidth: '4ch',
7383
+ textAlign: 'end',
7384
+ fontVariantNumeric: 'tabular-nums',
7385
+ ...this.theme.color(options.labelColor ?? 'on-surface-variant'),
7386
+ ...this.theme.typeface(options.labelTypeface ?? 'label'),
7387
+ });
7388
+ }, ...(ngDevMode ? [{ debugName: "readoutClass" }] : /* istanbul ignore next */ []));
7389
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSliderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7390
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSliderComponent, isStandalone: true, selector: "uni-slider, Slider", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", 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 }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, origin: { classPropertyName: "origin", publicName: "origin", isSignal: true, isRequired: false, transformFunction: null }, marks: { classPropertyName: "marks", publicName: "marks", isSignal: true, isRequired: false, transformFunction: null }, snapToMarks: { classPropertyName: "snapToMarks", publicName: "snapToMarks", isSignal: true, isRequired: false, transformFunction: null }, valueDisplay: { classPropertyName: "valueDisplay", publicName: "valueDisplay", isSignal: true, isRequired: false, transformFunction: null }, formatValue: { classPropertyName: "formatValue", publicName: "formatValue", isSignal: true, isRequired: false, transformFunction: null }, minGap: { classPropertyName: "minGap", publicName: "minGap", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", sliding: "sliding", changed: "changed" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'slider' }], viewQueries: [{ propertyName: "trackRef", first: true, predicate: ["track"], descendants: true, isSignal: true }, { propertyName: "thumbRefs", predicate: ["thumb"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Range mode is two questions, so it is a group with two tab stops; single\n mode needs no wrapper role \u2014 the thumb itself is the control. -->\n<div\n [class]=\"rootClass()\"\n [attr.role]=\"isRange() ? 'group' : null\"\n [attr.aria-label]=\"isRange() ? label() : null\"\n>\n <div [class]=\"rowClass()\">\n <!-- Pointer move/up ride the track because it holds the pointer capture,\n so a drag that leaves the element still tracks and still releases. -->\n <div\n #track\n [class]=\"trackClass()\"\n (pointerdown)=\"onTrackPointerDown($event)\"\n (pointermove)=\"onTrackPointerMove($event)\"\n (pointerup)=\"onTrackPointerUp()\"\n (pointercancel)=\"onTrackPointerUp()\"\n (lostpointercapture)=\"onTrackPointerUp()\"\n >\n <div\n [class]=\"fillClass()\"\n [style.inset-inline-start.%]=\"fillStart()\"\n [style.inset-inline-end.%]=\"fillEnd()\"\n ></div>\n\n @for (mark of marks(); track mark.value) {\n <div [class]=\"markClass()\" [style.inset-inline-start.%]=\"percentOf(mark.value)\"></div>\n }\n\n @for (index of thumbIndexes(); track index) {\n <div\n #thumb\n role=\"slider\"\n tabindex=\"0\"\n [attr.data-thumb]=\"index\"\n [class]=\"thumbClass()\"\n [style.inset-inline-start.%]=\"percentOf(thumbValue(index))\"\n [attr.aria-label]=\"thumbLabel(index)\"\n [attr.aria-valuenow]=\"thumbValue(index)\"\n [attr.aria-valuemin]=\"thumbMin(index)\"\n [attr.aria-valuemax]=\"thumbMax(index)\"\n [attr.aria-valuetext]=\"valueText(thumbValue(index))\"\n [attr.aria-orientation]=\"'horizontal'\"\n [attr.aria-disabled]=\"disabled() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (keydown)=\"onThumbKeydown($event, index)\"\n (keyup)=\"onThumbKeyup(index)\"\n (blur)=\"onThumbBlur()\"\n >\n @if (valueDisplay() === 'tooltip') {\n <span [class]=\"tooltipClass()\" aria-hidden=\"true\">\n {{ valueText(thumbValue(index)) }}\n </span>\n }\n </div>\n }\n </div>\n\n @if (showReadoutField()) {\n <!-- The field is the slider's precise-entry escape hatch: drag for the\n ballpark, type for the exact value. It carries its own label because\n it is a second tab stop, and the thumb's ARIA already names the\n slider itself. -->\n <uni-number-input\n [class]=\"readoutFieldClass()\"\n [label]=\"label() + ' value'\"\n [value]=\"thumbValue(0)\"\n [min]=\"resolvedMin()\"\n [max]=\"resolvedMax()\"\n [step]=\"step()\"\n [decimals]=\"readoutDecimals()\"\n [disabled]=\"disabled()\"\n align=\"end\"\n (valueChange)=\"onReadoutValue($event)\"\n />\n }\n\n @if (valueDisplay() === 'inline') {\n <!-- Duplicated from aria-valuetext, so it is hidden from the reader. -->\n <span [class]=\"readoutClass()\" aria-hidden=\"true\">\n @if (isRange()) {\n {{ valueText(lowValue()) }} \u2013 {{ valueText(highValue()) }}\n } @else {\n {{ valueText(thumbValue(0)) }}\n }\n </span>\n }\n </div>\n\n @if (hasMarkLabels()) {\n <!-- Presentational: the text is folded into aria-valuetext at the matching\n value rather than being separately focusable. -->\n <div [class]=\"labelsClass()\" role=\"presentation\">\n @for (mark of marks(); track mark.value) {\n @if (mark.label) {\n <span [class]=\"labelClass()\" [style.inset-inline-start.%]=\"percentOf(mark.value)\">\n {{ mark.label }}\n </span>\n }\n }\n </div>\n }\n\n <!-- Fences and swaps only; ordinary movement is already narrated by\n aria-valuetext, and doubling it is noise. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniNumberInputComponent, selector: "uni-number-input, NumberInput", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "valueAsString", "label", "placeholder", "preset", "currency", "locale", "prefix", "suffix", "decimals", "grouping", "numberFormat", "roundingMode", "align", "valueIsFraction", "unitAnnouncement", "readOnly", "embedded", "min", "max", "step", "largeStep", "smallStep", "stepOrigin", "wrap", "clampOnCommit", "emptyStepValue", "commitOnBlur", "selectOnFocus", "allowExpressions", "wheel", "repeat", "parse", "stepperLayout"], outputs: ["valueChange", "touchedChange", "valueAsStringChange", "stepped", "rejected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7391
+ }
7392
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSliderComponent, decorators: [{
7393
+ type: Component,
7394
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-slider, Slider', imports: [UniNumberInputComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'slider' }], host: { '[class]': 'className()' }, template: "<!-- Range mode is two questions, so it is a group with two tab stops; single\n mode needs no wrapper role \u2014 the thumb itself is the control. -->\n<div\n [class]=\"rootClass()\"\n [attr.role]=\"isRange() ? 'group' : null\"\n [attr.aria-label]=\"isRange() ? label() : null\"\n>\n <div [class]=\"rowClass()\">\n <!-- Pointer move/up ride the track because it holds the pointer capture,\n so a drag that leaves the element still tracks and still releases. -->\n <div\n #track\n [class]=\"trackClass()\"\n (pointerdown)=\"onTrackPointerDown($event)\"\n (pointermove)=\"onTrackPointerMove($event)\"\n (pointerup)=\"onTrackPointerUp()\"\n (pointercancel)=\"onTrackPointerUp()\"\n (lostpointercapture)=\"onTrackPointerUp()\"\n >\n <div\n [class]=\"fillClass()\"\n [style.inset-inline-start.%]=\"fillStart()\"\n [style.inset-inline-end.%]=\"fillEnd()\"\n ></div>\n\n @for (mark of marks(); track mark.value) {\n <div [class]=\"markClass()\" [style.inset-inline-start.%]=\"percentOf(mark.value)\"></div>\n }\n\n @for (index of thumbIndexes(); track index) {\n <div\n #thumb\n role=\"slider\"\n tabindex=\"0\"\n [attr.data-thumb]=\"index\"\n [class]=\"thumbClass()\"\n [style.inset-inline-start.%]=\"percentOf(thumbValue(index))\"\n [attr.aria-label]=\"thumbLabel(index)\"\n [attr.aria-valuenow]=\"thumbValue(index)\"\n [attr.aria-valuemin]=\"thumbMin(index)\"\n [attr.aria-valuemax]=\"thumbMax(index)\"\n [attr.aria-valuetext]=\"valueText(thumbValue(index))\"\n [attr.aria-orientation]=\"'horizontal'\"\n [attr.aria-disabled]=\"disabled() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (keydown)=\"onThumbKeydown($event, index)\"\n (keyup)=\"onThumbKeyup(index)\"\n (blur)=\"onThumbBlur()\"\n >\n @if (valueDisplay() === 'tooltip') {\n <span [class]=\"tooltipClass()\" aria-hidden=\"true\">\n {{ valueText(thumbValue(index)) }}\n </span>\n }\n </div>\n }\n </div>\n\n @if (showReadoutField()) {\n <!-- The field is the slider's precise-entry escape hatch: drag for the\n ballpark, type for the exact value. It carries its own label because\n it is a second tab stop, and the thumb's ARIA already names the\n slider itself. -->\n <uni-number-input\n [class]=\"readoutFieldClass()\"\n [label]=\"label() + ' value'\"\n [value]=\"thumbValue(0)\"\n [min]=\"resolvedMin()\"\n [max]=\"resolvedMax()\"\n [step]=\"step()\"\n [decimals]=\"readoutDecimals()\"\n [disabled]=\"disabled()\"\n align=\"end\"\n (valueChange)=\"onReadoutValue($event)\"\n />\n }\n\n @if (valueDisplay() === 'inline') {\n <!-- Duplicated from aria-valuetext, so it is hidden from the reader. -->\n <span [class]=\"readoutClass()\" aria-hidden=\"true\">\n @if (isRange()) {\n {{ valueText(lowValue()) }} \u2013 {{ valueText(highValue()) }}\n } @else {\n {{ valueText(thumbValue(0)) }}\n }\n </span>\n }\n </div>\n\n @if (hasMarkLabels()) {\n <!-- Presentational: the text is folded into aria-valuetext at the matching\n value rather than being separately focusable. -->\n <div [class]=\"labelsClass()\" role=\"presentation\">\n @for (mark of marks(); track mark.value) {\n @if (mark.label) {\n <span [class]=\"labelClass()\" [style.inset-inline-start.%]=\"percentOf(mark.value)\">\n {{ mark.label }}\n </span>\n }\n }\n </div>\n }\n\n <!-- Fences and swaps only; ordinary movement is already narrated by\n aria-valuetext, and doubling it is noise. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
7395
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", 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 }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "largeStep", required: false }] }], origin: [{ type: i0.Input, args: [{ isSignal: true, alias: "origin", required: false }] }], marks: [{ type: i0.Input, args: [{ isSignal: true, alias: "marks", required: false }] }], snapToMarks: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToMarks", required: false }] }], valueDisplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueDisplay", required: false }] }], formatValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatValue", required: false }] }], minGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "minGap", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], sliding: [{ type: i0.Output, args: ["sliding"] }], changed: [{ type: i0.Output, args: ["changed"] }], trackRef: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }], thumbRefs: [{ type: i0.ViewChildren, args: ['thumb', { isSignal: true }] }] } });
7396
+
7397
+ /**
7398
+ * Two linked numeric fields in one chrome, with one `{ start, end }` value —
7399
+ * price filters, thresholds, tolerances.
7400
+ *
7401
+ * `start`/`end` deliberately match `UniDateRange`, so the library has one range
7402
+ * vocabulary, and they avoid colliding with the `min`/`max` **inputs**, which
7403
+ * mean the fence rather than the value.
7404
+ *
7405
+ * It owns its commit path rather than nesting two `uni-number-input`s, because
7406
+ * the two behaviours the spec asks for need *different* bounds: a stepper must
7407
+ * be fenced at the other end, while a typed commit must reach the parent
7408
+ * un-clamped so a backwards range can be swapped instead of destroyed. A child
7409
+ * field applies one bound pair to both. The arithmetic, parsing and formatting
7410
+ * are still the cdk's, shared with every other numeric control.
7411
+ */
7412
+ class UniNumberRangeInputComponent extends BaseComponent {
7413
+ // --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
7414
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7415
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7416
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
7417
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
7418
+ dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
7419
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
7420
+ ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
7421
+ // --- Configuration -------------------------------------------------------
7422
+ /** Names the group, e.g. "Price range". */
7423
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
7424
+ startLabel = input('Minimum', ...(ngDevMode ? [{ debugName: "startLabel" }] : /* istanbul ignore next */ []));
7425
+ endLabel = input('Maximum', ...(ngDevMode ? [{ debugName: "endLabel" }] : /* istanbul ignore next */ []));
7426
+ // Forwarded to both parts, so the two ends always read alike.
7427
+ preset = input('decimal', ...(ngDevMode ? [{ debugName: "preset" }] : /* istanbul ignore next */ []));
7428
+ currency = input(...(ngDevMode ? [undefined, { debugName: "currency" }] : /* istanbul ignore next */ []));
7429
+ locale = input(...(ngDevMode ? [undefined, { debugName: "locale" }] : /* istanbul ignore next */ []));
7430
+ prefix = input(...(ngDevMode ? [undefined, { debugName: "prefix" }] : /* istanbul ignore next */ []));
7431
+ suffix = input(...(ngDevMode ? [undefined, { debugName: "suffix" }] : /* istanbul ignore next */ []));
7432
+ decimals = input(...(ngDevMode ? [undefined, { debugName: "decimals" }] : /* istanbul ignore next */ []));
7433
+ grouping = input(...(ngDevMode ? [undefined, { debugName: "grouping" }] : /* istanbul ignore next */ []));
7434
+ roundingMode = input('half-up', ...(ngDevMode ? [{ debugName: "roundingMode" }] : /* istanbul ignore next */ []));
7435
+ placeholderStart = input(...(ngDevMode ? [undefined, { debugName: "placeholderStart" }] : /* istanbul ignore next */ []));
7436
+ placeholderEnd = input(...(ngDevMode ? [undefined, { debugName: "placeholderEnd" }] : /* istanbul ignore next */ []));
7437
+ // `min`/`max` are part of the FormValueControl contract — Signal Forms syncs
7438
+ // them from min()/max() validators — so their type must admit undefined.
7439
+ min = input(...(ngDevMode ? [undefined, { debugName: "min" }] : /* istanbul ignore next */ []));
7440
+ max = input(...(ngDevMode ? [undefined, { debugName: "max" }] : /* istanbul ignore next */ []));
7441
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
7442
+ /** Enforced distance between the two ends. */
7443
+ minGap = input(...(ngDevMode ? [undefined, { debugName: "minGap" }] : /* istanbul ignore next */ []));
7444
+ // --- Events --------------------------------------------------------------
7445
+ /** The ends were entered backwards and have been exchanged. */
7446
+ swapped = output();
7447
+ /** A typed commit on one end could not be read; its text stays in place. */
7448
+ rejected = output();
7449
+ inputRefs = viewChildren('field', ...(ngDevMode ? [{ debugName: "inputRefs" }] : /* istanbul ignore next */ []));
7450
+ srOnly = css(visuallyHidden);
7451
+ announcer = createAnnouncer();
7452
+ hintId = uniqueId('uni-number-range-hint');
7453
+ groupId = uniqueId('uni-number-range');
7454
+ /** Uncommitted text per part. `null` means "show the committed value". */
7455
+ drafts = signal({
7456
+ start: null,
7457
+ end: null,
7458
+ }, ...(ngDevMode ? [{ debugName: "drafts" }] : /* istanbul ignore next */ []));
7459
+ focusedPart = signal(null, ...(ngDevMode ? [{ debugName: "focusedPart" }] : /* istanbul ignore next */ []));
7460
+ invalidPart = signal(null, ...(ngDevMode ? [{ debugName: "invalidPart" }] : /* istanbul ignore next */ []));
7461
+ parts = ['start', 'end'];
7462
+ fieldChrome = this.theme.getComponentOptions('input');
7463
+ format = computed(() => resolveNumberFormat({
7464
+ preset: this.preset(),
7465
+ currency: this.currency(),
7466
+ locale: this.locale() ?? (document.documentElement.lang || navigator.language || 'en-US'),
7467
+ decimals: this.decimals(),
7468
+ grouping: this.grouping(),
7469
+ prefix: this.prefix(),
7470
+ suffix: this.suffix(),
7471
+ roundingMode: this.roundingMode(),
7472
+ min: this.min(),
7473
+ }), ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
7474
+ /** Form-level error, which belongs to both ends. */
7475
+ formError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "formError" }] : /* istanbul ignore next */ []));
7476
+ /**
7477
+ * Box-level error. A refused draft in *one* end flags the shared chrome, but
7478
+ * must not flag the other end's input — that end is fine.
7479
+ */
7480
+ showError = computed(() => this.formError() || this.invalidPart() != null, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
7481
+ describedBy = computed(() => [this.ariaDescribedBy(), this.hintId].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
7482
+ // --- Per-part reads -------------------------------------------------------
7483
+ /** The committed canonical decimal for a part, or `null` when that end is open. */
7484
+ canonicalOf(part) {
7485
+ const range = this.value();
7486
+ const raw = part === 'start' ? range?.start : range?.end;
7487
+ return raw == null ? null : toDecimal(raw);
7488
+ }
7489
+ valueOf(part) {
7490
+ const range = this.value();
7491
+ return (part === 'start' ? range?.start : range?.end) ?? null;
7492
+ }
7493
+ displayText(part) {
7494
+ const draft = this.drafts()[part];
7495
+ if (draft != null)
7496
+ return draft;
7497
+ const canonical = this.canonicalOf(part);
7498
+ if (canonical == null)
7499
+ return '';
7500
+ return this.focusedPart() === part
7501
+ ? rawNumberText(canonical, this.format())
7502
+ : formatNumber(canonical, this.format());
7503
+ }
7504
+ partLabel(part) {
7505
+ return `${this.label()}, ${part === 'start' ? this.startLabel() : this.endLabel()}`;
7506
+ }
7507
+ valueTextOf(part) {
7508
+ return speakNumber(this.canonicalOf(part), this.format());
7509
+ }
7510
+ isInvalid(part) {
7511
+ return this.invalidPart() === part;
7512
+ }
7513
+ // --- Fences ---------------------------------------------------------------
7514
+ /** Exact `a ± b` without a float, for the gap arithmetic. */
7515
+ shiftBy(value, by, direction) {
7516
+ const scale = Math.max(decimalScale(value), decimalScale(by));
7517
+ const moved = toScaled(value, scale) + BigInt(direction) * toScaled(by, scale);
7518
+ return fromScaled(moved, scale);
7519
+ }
7520
+ /**
7521
+ * The fence a part's **stepping** and its ARIA see: the other end, held off
7522
+ * by `minGap`, intersected with the outer bounds. This is deliberately
7523
+ * tighter than what a typed commit is measured against — the steppers must
7524
+ * not walk one end through the other, while typing a backwards range should
7525
+ * be swapped rather than clamped away.
7526
+ */
7527
+ stepFence(part) {
7528
+ const gap = toDecimal(this.minGap() ?? 0);
7529
+ const outerMin = this.min();
7530
+ const outerMax = this.max();
7531
+ if (part === 'start') {
7532
+ const other = this.canonicalOf('end');
7533
+ if (other == null)
7534
+ return { min: outerMin, max: outerMax };
7535
+ const cap = this.shiftBy(other, gap, -1);
7536
+ const capped = outerMax != null && compareDecimal(cap, toDecimal(outerMax)) > 0 ? outerMax : Number(cap);
7537
+ return { min: outerMin, max: capped };
7538
+ }
7539
+ const other = this.canonicalOf('start');
7540
+ if (other == null)
7541
+ return { min: outerMin, max: outerMax };
7542
+ const floor = this.shiftBy(other, gap, 1);
7543
+ const floored = outerMin != null && compareDecimal(floor, toDecimal(outerMin)) < 0 ? outerMin : Number(floor);
7544
+ return { min: floored, max: outerMax };
7545
+ }
7546
+ // --- Writing --------------------------------------------------------------
7547
+ writeRange(start, end) {
7548
+ if (start == null && end == null) {
7549
+ this.value.set(null);
7550
+ return;
7551
+ }
7552
+ this.value.set({
7553
+ ...(start == null ? {} : { start: Number(start) }),
7554
+ ...(end == null ? {} : { end: Number(end) }),
7555
+ });
7556
+ }
7557
+ onInput(part, text) {
7558
+ this.drafts.update((drafts) => ({ ...drafts, [part]: text }));
7559
+ if (this.invalidPart() === part)
7560
+ this.invalidPart.set(null);
7561
+ }
7562
+ /**
7563
+ * Commit one part. Out-of-range clamps to the **outer** bounds only, so the
7564
+ * other end never destroys what was typed; the ends are then reconciled.
7565
+ */
7566
+ commitPart(part) {
7567
+ const draft = this.drafts()[part];
7568
+ if (draft == null)
7569
+ return;
7570
+ const result = parseNumber(draft, this.format(), { currency: this.currency() });
7571
+ if (result.status === 'error') {
7572
+ this.invalidPart.set(part);
7573
+ this.rejected.emit({ part, raw: draft, reason: result.reason });
7574
+ this.announcer.announce(`${draft} is not a number.`);
7575
+ return;
7576
+ }
7577
+ this.drafts.update((drafts) => ({ ...drafts, [part]: null }));
7578
+ this.invalidPart.set(null);
7579
+ let start = this.canonicalOf('start');
7580
+ let end = this.canonicalOf('end');
7581
+ if (result.status === 'empty') {
7582
+ if (part === 'start')
7583
+ start = null;
7584
+ else
7585
+ end = null;
7586
+ this.writeRange(start, end);
7587
+ return;
7588
+ }
7589
+ const settled = clampDecimal(settleNumber(result.value, this.format()), this.min(), this.max()).value;
7590
+ if (part === 'start')
7591
+ start = settled;
7592
+ else
7593
+ end = settled;
7594
+ this.reconcile(part, start, end);
7595
+ }
7596
+ /**
7597
+ * Put the two ends in order. A backwards pair is **swapped**, not refused —
7598
+ * the same rule `uni-calendar` applies to a backwards date range, because the
7599
+ * user pointed at the range they meant. Otherwise `minGap` is honoured by
7600
+ * pushing the end that was just edited back to the boundary, which is what
7601
+ * makes stepping behave as a fence rather than dragging the other end along.
7602
+ */
7603
+ reconcile(edited, start, end) {
7604
+ if (start != null && end != null) {
7605
+ if (compareDecimal(start, end) > 0) {
7606
+ const swapped = { start: Number(end), end: Number(start) };
7607
+ this.value.set(swapped);
7608
+ this.swapped.emit(swapped);
7609
+ this.announcer.announce(`Range ${formatNumber(end, this.format())} to ${formatNumber(start, this.format())}. Ends swapped.`);
7610
+ return;
7611
+ }
7612
+ const gap = toDecimal(this.minGap() ?? 0);
7613
+ if (Number(gap) > 0) {
7614
+ const distance = this.shiftBy(end, start, -1);
7615
+ if (compareDecimal(distance, gap) < 0) {
7616
+ if (edited === 'end')
7617
+ end = this.shiftBy(start, gap, 1);
7618
+ else
7619
+ start = this.shiftBy(end, gap, -1);
7620
+ this.announcer.announce(`Kept ${formatNumber(gap, this.format())} between the ends.`);
7621
+ }
7622
+ }
7623
+ }
7624
+ this.writeRange(start, end);
7625
+ }
7626
+ // --- Stepping -------------------------------------------------------------
7627
+ applyStep(part, direction, large = false) {
7628
+ if (this.disabled())
7629
+ return;
7630
+ if (this.drafts()[part] != null)
7631
+ this.commitPart(part);
7632
+ const fence = this.stepFence(part);
7633
+ const current = this.canonicalOf(part);
7634
+ if (current == null) {
7635
+ const seed = toDecimal(fence.min ?? this.min() ?? 0);
7636
+ this.reconcile(part, part === 'start' ? seed : this.canonicalOf('start'), part === 'end' ? seed : this.canonicalOf('end'));
7637
+ this.announceValue(part);
7638
+ return;
7639
+ }
7640
+ const next = stepDecimal(current, direction, {
7641
+ step: large ? this.step() * 10 : this.step(),
7642
+ min: fence.min,
7643
+ max: fence.max,
7644
+ stepOrigin: 'min',
7645
+ });
7646
+ if (next === current) {
7647
+ const bound = direction > 0 ? fence.max : fence.min;
7648
+ if (bound != null) {
7649
+ this.announcer.announce(`${direction > 0 ? 'Maximum' : 'Minimum'}, ${bound}.`);
7650
+ }
7651
+ return;
7652
+ }
7653
+ this.reconcile(part, part === 'start' ? next : this.canonicalOf('start'), part === 'end' ? next : this.canonicalOf('end'));
7654
+ this.announceValue(part);
7655
+ }
7656
+ announceValue(part) {
7657
+ this.announcer.announce(`${this.valueTextOf(part)}.`);
7658
+ }
7659
+ // --- Events ---------------------------------------------------------------
7660
+ onFocus(part) {
7661
+ this.focusedPart.set(part);
7662
+ }
7663
+ onBlur(part) {
7664
+ if (this.focusedPart() === part)
7665
+ this.focusedPart.set(null);
7666
+ this.touched.set(true);
7667
+ this.commitPart(part);
7668
+ }
7669
+ onKeydown(event, part) {
7670
+ const fence = this.stepFence(part);
7671
+ switch (event.key) {
7672
+ case 'ArrowUp':
7673
+ event.preventDefault();
7674
+ this.applyStep(part, 1, event.shiftKey);
7675
+ break;
7676
+ case 'ArrowDown':
7677
+ event.preventDefault();
7678
+ this.applyStep(part, -1, event.shiftKey);
7679
+ break;
7680
+ case 'PageUp':
7681
+ event.preventDefault();
7682
+ this.applyStep(part, 1, true);
7683
+ break;
7684
+ case 'PageDown':
7685
+ event.preventDefault();
7686
+ this.applyStep(part, -1, true);
7687
+ break;
7688
+ case 'Home':
7689
+ if (fence.min == null)
7690
+ return;
7691
+ event.preventDefault();
7692
+ this.drafts.update((d) => ({ ...d, [part]: null }));
7693
+ this.reconcile(part, part === 'start' ? toDecimal(fence.min) : this.canonicalOf('start'), part === 'end' ? toDecimal(fence.min) : this.canonicalOf('end'));
7694
+ break;
7695
+ case 'End':
7696
+ if (fence.max == null)
7697
+ return;
7698
+ event.preventDefault();
7699
+ this.drafts.update((d) => ({ ...d, [part]: null }));
7700
+ this.reconcile(part, part === 'start' ? toDecimal(fence.max) : this.canonicalOf('start'), part === 'end' ? toDecimal(fence.max) : this.canonicalOf('end'));
7701
+ break;
7702
+ case 'Enter':
7703
+ if (this.drafts()[part] != null)
7704
+ event.preventDefault();
7705
+ this.commitPart(part);
7706
+ break;
7707
+ case 'Escape':
7708
+ event.preventDefault();
7709
+ this.drafts.update((d) => ({ ...d, [part]: null }));
7710
+ this.invalidPart.set(null);
7711
+ break;
7712
+ case 'Tab':
7713
+ this.commitPart(part);
7714
+ break;
7715
+ }
7716
+ }
7717
+ // --- Styling --------------------------------------------------------------
7718
+ className = computed(() => css({ display: 'block' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
7719
+ rowClass = computed(() => {
7720
+ const options = this.componentOptions();
7721
+ return css([
7722
+ {
7723
+ display: 'flex',
7724
+ alignItems: 'center',
7725
+ width: '100%',
7726
+ height: '100%',
7727
+ },
7728
+ this.theme.gap(options.partGap ?? 'sm'),
7729
+ // Both insets ride the row, not the `<input>`s: uni-input-box styles
7730
+ // `& input` at a higher specificity than this class can reach, so an
7731
+ // inset set on an input is silently overridden. See `managedInset`,
7732
+ // which is why the box is not applying the leading one either. There are
7733
+ // never steppers here, so the trailing edge always gets one — otherwise
7734
+ // the upper end's suffix sits against the border.
7735
+ this.theme.paddingLeft(this.fieldChrome().paddingLeft),
7736
+ this.theme.paddingRight(this.fieldChrome().paddingLeft),
7737
+ ]);
7738
+ }, ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
7739
+ /** Each end is its own `[prefix][number][suffix]` group. */
7740
+ partWrapClass = computed(() => {
7741
+ const options = this.componentOptions();
7742
+ return css([
7743
+ { display: 'flex', alignItems: 'center', flex: '1 1 0', minWidth: 0 },
7744
+ this.theme.gap(options.affixGap ?? 'xs'),
7745
+ ]);
7746
+ }, ...(ngDevMode ? [{ debugName: "partWrapClass" }] : /* istanbul ignore next */ []));
7747
+ affixClass = computed(() => {
7748
+ const options = this.componentOptions();
7749
+ return css([
7750
+ { flex: 'none', userSelect: 'none' },
7751
+ this.theme.color(options.affixColor ?? 'on-primary-surface-variant'),
7752
+ ]);
7753
+ }, ...(ngDevMode ? [{ debugName: "affixClass" }] : /* istanbul ignore next */ []));
7754
+ partClass = computed(() => css([this.partBase()]), ...(ngDevMode ? [{ debugName: "partClass" }] : /* istanbul ignore next */ []));
7755
+ partBase() {
7756
+ return {
7757
+ flex: '1 1 0',
7758
+ minWidth: 0,
7759
+ border: 0,
7760
+ outline: 'none',
7761
+ background: 'transparent',
7762
+ color: 'inherit',
7763
+ font: 'inherit',
7764
+ padding: 0,
7765
+ fontVariantNumeric: 'tabular-nums',
7766
+ };
7767
+ }
7768
+ invalidClass = computed(() => {
7769
+ // Colour alone cannot carry "this is not a number" (WCAG 1.4.1).
7770
+ return css({
7771
+ textDecoration: 'underline dashed',
7772
+ textUnderlineOffset: 3,
7773
+ textDecorationColor: this.theme.colors()['warn'],
7774
+ });
7775
+ }, ...(ngDevMode ? [{ debugName: "invalidClass" }] : /* istanbul ignore next */ []));
7776
+ dividerClass = computed(() => {
7777
+ const options = this.componentOptions();
7778
+ return css({
7779
+ flex: 'none',
7780
+ userSelect: 'none',
7781
+ ...this.theme.color(options.dividerColor ?? 'outline'),
7782
+ });
7783
+ }, ...(ngDevMode ? [{ debugName: "dividerClass" }] : /* istanbul ignore next */ []));
7784
+ dividerText = computed(() => this.componentOptions().dividerText ?? '–', ...(ngDevMode ? [{ debugName: "dividerText" }] : /* istanbul ignore next */ []));
7785
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniNumberRangeInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7786
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniNumberRangeInputComponent, isStandalone: true, selector: "uni-number-range-input, NumberRangeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, startLabel: { classPropertyName: "startLabel", publicName: "startLabel", isSignal: true, isRequired: false, transformFunction: null }, endLabel: { classPropertyName: "endLabel", publicName: "endLabel", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", 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 }, decimals: { classPropertyName: "decimals", publicName: "decimals", isSignal: true, isRequired: false, transformFunction: null }, grouping: { classPropertyName: "grouping", publicName: "grouping", isSignal: true, isRequired: false, transformFunction: null }, roundingMode: { classPropertyName: "roundingMode", publicName: "roundingMode", isSignal: true, isRequired: false, transformFunction: null }, placeholderStart: { classPropertyName: "placeholderStart", publicName: "placeholderStart", isSignal: true, isRequired: false, transformFunction: null }, placeholderEnd: { classPropertyName: "placeholderEnd", publicName: "placeholderEnd", 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 }, minGap: { classPropertyName: "minGap", publicName: "minGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", swapped: "swapped", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'numberRangeInput' }], viewQueries: [{ propertyName: "inputRefs", predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- One label names the group; the parts are named \"{label}, Minimum\" and\n \"{label}, Maximum\". Two tab stops, honestly \u2014 it is two questions. -->\n<div role=\"group\" [attr.aria-label]=\"label()\" [attr.aria-describedby]=\"describedBy()\">\n <uni-input-box\n [error]=\"showError()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"true\"\n [managedInset]=\"true\"\n >\n <div [class]=\"rowClass()\">\n @for (part of parts; track part) {\n @if (part === 'end') {\n <!-- Punctuation between two numbers, not a glyph: aria-hidden, since\n the two parts are already named minimum and maximum. -->\n <span [class]=\"dividerClass()\" aria-hidden=\"true\">{{ dividerText() }}</span>\n }\n\n <span [class]=\"partWrapClass()\">\n <!-- Adornments, not text: outside the <input> so the caret,\n select-all and paste never step over them, and aria-hidden\n because aria-valuetext already speaks them. -->\n @if (format().prefix) {\n <span [class]=\"affixClass()\" aria-hidden=\"true\">{{ format().prefix }}</span>\n }\n\n <input\n #field\n type=\"text\"\n role=\"spinbutton\"\n autocomplete=\"off\"\n [class]=\"partClass() + (isInvalid(part) ? ' ' + invalidClass() : '')\"\n [value]=\"displayText(part)\"\n [disabled]=\"disabled()\"\n [attr.inputmode]=\"format().inputMode\"\n [attr.placeholder]=\"\n (part === 'start' ? placeholderStart() : placeholderEnd()) ?? null\n \"\n [attr.aria-label]=\"partLabel(part)\"\n [attr.aria-valuenow]=\"valueOf(part)\"\n [attr.aria-valuemin]=\"stepFence(part).min ?? null\"\n [attr.aria-valuemax]=\"stepFence(part).max ?? null\"\n [attr.aria-valuetext]=\"valueTextOf(part)\"\n [attr.aria-invalid]=\"isInvalid(part) || formError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (input)=\"onInput(part, $any($event.target).value)\"\n (keydown)=\"onKeydown($event, part)\"\n (focus)=\"onFocus(part)\"\n (blur)=\"onBlur(part)\"\n />\n\n @if (format().suffix) {\n <span [class]=\"affixClass()\" aria-hidden=\"true\">{{ format().suffix }}</span>\n }\n </span>\n }\n </div>\n </uni-input-box>\n</div>\n\n<!-- Said once for the whole field rather than on both ends. -->\n<span [id]=\"hintId\" [class]=\"srOnly\">\n Two values. Use the up and down arrow keys to change either end; entering them\n the wrong way round swaps them.\n</span>\n\n<!-- Swaps, gap corrections, fences and refusals are each otherwise silent. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7787
+ }
7788
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniNumberRangeInputComponent, decorators: [{
7789
+ type: Component,
7790
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-number-range-input, NumberRangeInput', imports: [UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'numberRangeInput' }], host: { '[class]': 'className()' }, template: "<!-- One label names the group; the parts are named \"{label}, Minimum\" and\n \"{label}, Maximum\". Two tab stops, honestly \u2014 it is two questions. -->\n<div role=\"group\" [attr.aria-label]=\"label()\" [attr.aria-describedby]=\"describedBy()\">\n <uni-input-box\n [error]=\"showError()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"true\"\n [managedInset]=\"true\"\n >\n <div [class]=\"rowClass()\">\n @for (part of parts; track part) {\n @if (part === 'end') {\n <!-- Punctuation between two numbers, not a glyph: aria-hidden, since\n the two parts are already named minimum and maximum. -->\n <span [class]=\"dividerClass()\" aria-hidden=\"true\">{{ dividerText() }}</span>\n }\n\n <span [class]=\"partWrapClass()\">\n <!-- Adornments, not text: outside the <input> so the caret,\n select-all and paste never step over them, and aria-hidden\n because aria-valuetext already speaks them. -->\n @if (format().prefix) {\n <span [class]=\"affixClass()\" aria-hidden=\"true\">{{ format().prefix }}</span>\n }\n\n <input\n #field\n type=\"text\"\n role=\"spinbutton\"\n autocomplete=\"off\"\n [class]=\"partClass() + (isInvalid(part) ? ' ' + invalidClass() : '')\"\n [value]=\"displayText(part)\"\n [disabled]=\"disabled()\"\n [attr.inputmode]=\"format().inputMode\"\n [attr.placeholder]=\"\n (part === 'start' ? placeholderStart() : placeholderEnd()) ?? null\n \"\n [attr.aria-label]=\"partLabel(part)\"\n [attr.aria-valuenow]=\"valueOf(part)\"\n [attr.aria-valuemin]=\"stepFence(part).min ?? null\"\n [attr.aria-valuemax]=\"stepFence(part).max ?? null\"\n [attr.aria-valuetext]=\"valueTextOf(part)\"\n [attr.aria-invalid]=\"isInvalid(part) || formError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (input)=\"onInput(part, $any($event.target).value)\"\n (keydown)=\"onKeydown($event, part)\"\n (focus)=\"onFocus(part)\"\n (blur)=\"onBlur(part)\"\n />\n\n @if (format().suffix) {\n <span [class]=\"affixClass()\" aria-hidden=\"true\">{{ format().suffix }}</span>\n }\n </span>\n }\n </div>\n </uni-input-box>\n</div>\n\n<!-- Said once for the whole field rather than on both ends. -->\n<span [id]=\"hintId\" [class]=\"srOnly\">\n Two values. Use the up and down arrow keys to change either end; entering them\n the wrong way round swaps them.\n</span>\n\n<!-- Swaps, gap corrections, fences and refusals are each otherwise silent. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n" }]
7791
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], endLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "endLabel", required: false }] }], preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], decimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimals", required: false }] }], grouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouping", required: false }] }], roundingMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "roundingMode", required: false }] }], placeholderStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderStart", required: false }] }], placeholderEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderEnd", 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 }] }], minGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "minGap", required: false }] }], swapped: [{ type: i0.Output, args: ["swapped"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRefs: [{ type: i0.ViewChildren, args: ['field', { isSignal: true }] }] } });
7792
+
7793
+ /**
7794
+ * `− 3 +` for cart lines, table cells and seat counts: the numeric core with no
7795
+ * field chrome, no label and no room for either.
7796
+ *
7797
+ * A separate component rather than a `chrome="bare"` flag on
7798
+ * `uni-number-input`, because this control is defined by what it does *not*
7799
+ * have — presets, affixes, expressions, four stepper layouts — and eight inputs
7800
+ * are easier to write correctly than forty with a list of which ones to leave
7801
+ * alone. The arithmetic, parsing and hold-to-repeat are the cdk's, shared with
7802
+ * the field, so `1,200` and the keyboard map behave identically in both.
7803
+ *
7804
+ * The middle stays a real input by default: typing `12` beats tapping `+`
7805
+ * eleven times. `editable=false` is for read-mostly tables.
7806
+ */
7807
+ class UniQuantityStepperComponent extends BaseComponent {
7808
+ // --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
7809
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7810
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7811
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
7812
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
7813
+ dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
7814
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
7815
+ ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
7816
+ // --- Configuration -------------------------------------------------------
7817
+ /**
7818
+ * Accessible name. Never visible and always needed — a cart with six of these
7819
+ * needs "Quantity, Blue T-shirt (M)", not six controls called "Quantity".
7820
+ */
7821
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
7822
+ // `min`/`max` are part of the FormValueControl contract — Signal Forms syncs
7823
+ // them from min()/max() validators — so their type must admit undefined.
7824
+ // Read `resolvedMin()` internally, never `min()`.
7825
+ min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
7826
+ max = input(...(ngDevMode ? [undefined, { debugName: "max" }] : /* istanbul ignore next */ []));
7827
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
7828
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
7829
+ /** `false` renders the number as text: read-mostly tables. */
7830
+ editable = input(true, ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
7831
+ /**
7832
+ * The cart pattern in one attribute: at `min` the decrement button becomes a
7833
+ * remove affordance and emits `removed` rather than stepping. Without it
7834
+ * every shop reimplements the same `value === 1 ? remove() : step(-1)` branch
7835
+ * outside the component.
7836
+ */
7837
+ deleteAtMin = input(false, ...(ngDevMode ? [{ debugName: "deleteAtMin" }] : /* istanbul ignore next */ []));
7838
+ // --- Events --------------------------------------------------------------
7839
+ /**
7840
+ * The remove affordance was activated — the row should come out.
7841
+ *
7842
+ * Named `removed`, not the spec's `emptied`: that word is a native
7843
+ * `HTMLMediaElement` event, which `@angular-eslint/no-output-native` bans for
7844
+ * good reason, and `removed` is already what `uni-tag` calls this same
7845
+ * request.
7846
+ */
7847
+ removed = output();
7848
+ inputRef = viewChild('field', ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
7849
+ srOnly = css(visuallyHidden);
7850
+ announcer = createAnnouncer();
7851
+ hintId = uniqueId('uni-quantity-stepper');
7852
+ /** Uncommitted text. `null` means "show the committed value". */
7853
+ draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
7854
+ canonical = computed(() => {
7855
+ const value = this.value();
7856
+ return value == null ? null : toDecimal(value);
7857
+ }, ...(ngDevMode ? [{ debugName: "canonical" }] : /* istanbul ignore next */ []));
7858
+ /** Quantities are plain numbers; precision follows the step. */
7859
+ format = computed(() => resolveNumberFormat({ decimals: [0, decimalScale(toDecimal(this.step()))] }), ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
7860
+ displayText = computed(() => {
7861
+ const draft = this.draft();
7862
+ if (draft != null)
7863
+ return draft;
7864
+ const canonical = this.canonical();
7865
+ return canonical == null ? '' : formatNumber(canonical, this.format());
7866
+ }, ...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
7867
+ showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
7868
+ describedBy = computed(() => [this.ariaDescribedBy(), this.hintId].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
7869
+ // --- Fences ---------------------------------------------------------------
7870
+ /** A quantity has a floor even when a validator has not supplied one. */
7871
+ resolvedMin = computed(() => this.min() ?? 0, ...(ngDevMode ? [{ debugName: "resolvedMin" }] : /* istanbul ignore next */ []));
7872
+ atMin = computed(() => {
7873
+ const canonical = this.canonical();
7874
+ return canonical != null && Number(canonical) <= this.resolvedMin();
7875
+ }, ...(ngDevMode ? [{ debugName: "atMin" }] : /* istanbul ignore next */ []));
7876
+ atMax = computed(() => {
7877
+ const max = this.max();
7878
+ const canonical = this.canonical();
7879
+ return max != null && canonical != null && Number(canonical) >= max;
7880
+ }, ...(ngDevMode ? [{ debugName: "atMax" }] : /* istanbul ignore next */ []));
7881
+ /** At the floor with `deleteAtMin`, the − is a remove control instead. */
7882
+ showDelete = computed(() => this.deleteAtMin() && this.atMin(), ...(ngDevMode ? [{ debugName: "showDelete" }] : /* istanbul ignore next */ []));
7883
+ decrementIcon = computed(() => {
7884
+ const options = this.componentOptions();
7885
+ return this.showDelete()
7886
+ ? (options.deleteIcon ?? 'delete')
7887
+ : (options.decrementIcon ?? 'minus');
7888
+ }, ...(ngDevMode ? [{ debugName: "decrementIcon" }] : /* istanbul ignore next */ []));
7889
+ decrementLabel = computed(() => this.showDelete() ? `Remove ${this.label()}` : `Decrease ${this.label()}`, ...(ngDevMode ? [{ debugName: "decrementLabel" }] : /* istanbul ignore next */ []));
7890
+ // --- Committing -----------------------------------------------------------
7891
+ onInput(text) {
7892
+ this.draft.set(text);
7893
+ }
7894
+ /**
7895
+ * The same parse path as the field, so `1,200` commits as 1200 here too.
7896
+ * Unreadable text reverts rather than being kept: this control has no room to
7897
+ * show an error, and no `rejected` output to report one through.
7898
+ */
7899
+ commitDraft() {
7900
+ const draft = this.draft();
7901
+ if (draft == null)
7902
+ return;
7903
+ const result = parseNumber(draft, this.format());
7904
+ if (result.status !== 'ok') {
7905
+ this.draft.set(null);
7906
+ if (result.status === 'empty')
7907
+ return;
7908
+ this.announcer.announce(`${draft} is not a number.`);
7909
+ return;
7910
+ }
7911
+ const settled = settleNumber(result.value, this.format());
7912
+ const clamped = clampDecimal(settled, this.resolvedMin(), this.max());
7913
+ this.draft.set(null);
7914
+ this.value.set(Number(clamped.value));
7915
+ if (clamped.hit) {
7916
+ const bound = clamped.hit === 'min' ? this.resolvedMin() : this.max();
7917
+ this.announcer.announce(`${clamped.hit === 'min' ? 'Minimum' : 'Maximum'} is ${bound}.`);
7918
+ }
7919
+ }
7920
+ // --- Stepping -------------------------------------------------------------
7921
+ applyStep(direction, announce = true) {
7922
+ if (this.disabled())
7923
+ return;
7924
+ if (this.draft() != null)
7925
+ this.commitDraft();
7926
+ const current = this.canonical();
7927
+ if (current == null) {
7928
+ const seed = toDecimal(this.resolvedMin());
7929
+ this.value.set(Number(seed));
7930
+ if (announce)
7931
+ this.announceValue();
7932
+ return;
7933
+ }
7934
+ const next = stepDecimal(current, direction, {
7935
+ step: this.step(),
7936
+ min: this.resolvedMin(),
7937
+ max: this.max(),
7938
+ stepOrigin: 'min',
7939
+ });
7940
+ if (next === current) {
7941
+ if (announce)
7942
+ this.announceFence(direction);
7943
+ return;
7944
+ }
7945
+ this.value.set(Number(next));
7946
+ if (announce)
7947
+ this.announceValue();
7948
+ }
7949
+ /**
7950
+ * The decrement button has two jobs. Below the floor with `deleteAtMin` it is
7951
+ * a remove control — a single click, with nothing to hold and repeat — so the
7952
+ * press/repeat machinery is skipped entirely in that state.
7953
+ */
7954
+ onDecrementPress(event) {
7955
+ if (this.showDelete())
7956
+ return;
7957
+ this.decrement.press(event);
7958
+ }
7959
+ onDecrementClick() {
7960
+ if (this.disabled() || !this.showDelete())
7961
+ return;
7962
+ this.removed.emit();
7963
+ this.announcer.announce(`${this.label()} removed.`);
7964
+ }
7965
+ announceValue() {
7966
+ this.announcer.announce(`${this.displayText()}.`);
7967
+ }
7968
+ announceFence(direction) {
7969
+ const bound = direction > 0 ? this.max() : this.resolvedMin();
7970
+ if (bound == null)
7971
+ return;
7972
+ this.announcer.announce(`${direction > 0 ? 'Maximum' : 'Minimum'}, ${bound}.`);
7973
+ }
7974
+ // --- Hold to repeat -------------------------------------------------------
7975
+ /** Announced on release only; narrating every intermediate value is noise. */
7976
+ increment = createPressRepeat({
7977
+ onStep: () => this.applyStep(1, false),
7978
+ onRelease: () => this.announceValue(),
7979
+ focus: (button) => this.focusField(button),
7980
+ disabled: () => this.disabled() || this.atMax(),
7981
+ });
7982
+ decrement = createPressRepeat({
7983
+ onStep: () => this.applyStep(-1, false),
7984
+ onRelease: () => this.announceValue(),
7985
+ focus: (button) => this.focusField(button),
7986
+ disabled: () => this.disabled() || this.atMin(),
7987
+ });
7988
+ // --- Keyboard -------------------------------------------------------------
7989
+ onKeydown(event) {
7990
+ switch (event.key) {
7991
+ case 'ArrowUp':
7992
+ event.preventDefault();
7993
+ this.applyStep(1);
7994
+ break;
7995
+ case 'ArrowDown':
7996
+ event.preventDefault();
7997
+ this.applyStep(-1);
7998
+ break;
7999
+ case 'Enter':
8000
+ if (this.draft() != null)
8001
+ event.preventDefault();
8002
+ this.commitDraft();
8003
+ break;
8004
+ case 'Escape':
8005
+ event.preventDefault();
8006
+ this.draft.set(null);
8007
+ break;
8008
+ case 'Tab':
8009
+ this.commitDraft();
8010
+ break;
8011
+ }
8012
+ }
8013
+ onBlur() {
8014
+ this.touched.set(true);
8015
+ this.commitDraft();
8016
+ this.increment.cancel();
8017
+ this.decrement.cancel();
8018
+ }
8019
+ /**
8020
+ * Focus the field a stepper press should land in. With `editable=false` there
8021
+ * is no field and the buttons are the tab stops, so the pressed button takes
8022
+ * it instead.
8023
+ */
8024
+ focusField(fallback) {
8025
+ const input = this.inputRef()?.nativeElement;
8026
+ if (input)
8027
+ input.focus();
8028
+ else
8029
+ fallback?.focus();
8030
+ }
8031
+ // --- Styling --------------------------------------------------------------
8032
+ className = computed(() => css({ display: 'inline-block' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
8033
+ /** Overall height, from the theme's `sizes` block. */
8034
+ height = computed(() => Number(this.style()['height'] ?? 32), ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
8035
+ /**
8036
+ * The shared field chrome, read from the same `input` theme entry
8037
+ * `uni-input-box` resolves. This control has its own container tokens, but the
8038
+ * **focus indicator** has to be the one every other field uses — a stepper
8039
+ * that highlights differently from the field beside it reads as a bug.
8040
+ */
8041
+ fieldChrome = this.theme.getComponentOptions('input');
8042
+ rootClass = computed(() => {
8043
+ const colors = this.theme.colors();
8044
+ const chrome = this.fieldChrome();
8045
+ return css({
8046
+ display: 'inline-flex',
8047
+ alignItems: 'stretch',
8048
+ height: this.height(),
8049
+ // The themed size is the *outer* height, so a md stepper is 32px like the
8050
+ // field beside it rather than 32 plus its border.
8051
+ boxSizing: 'border-box',
8052
+ overflow: 'hidden',
8053
+ ...this.theme.backgroundColor(this.disabled() ? chrome.disabledColor : this.containerColor()),
8054
+ ...this.theme.border(this.containerBorder()),
8055
+ ...this.theme.radius(this.containerRadius()),
8056
+ ...(this.showError() ? { borderColor: colors['warn'] } : {}),
8057
+ ...(this.disabled() ? { cursor: 'not-allowed' } : {}),
8058
+ // The middle input clears its own outline (removeInputPlatformStyling),
8059
+ // so the focus indicator belongs on the container — the same `:has()`
8060
+ // rule and the same `input` tokens uni-input-box uses, so a stepper
8061
+ // highlights exactly like the field next to it. Error state wins, to
8062
+ // keep a flagged control visibly flagged while it is being corrected.
8063
+ '&:has(input:focus)': {
8064
+ outline: chrome.focusOutline,
8065
+ outlineOffset: chrome.focusOutlineOffset,
8066
+ ...(this.showError()
8067
+ ? {}
8068
+ : {
8069
+ ...this.theme.border(chrome.focusBorder),
8070
+ ...this.theme.boxShadow(chrome.focusShadow),
8071
+ ...this.theme.backgroundColor(chrome.focusColor),
8072
+ }),
5559
8073
  },
8074
+ // The dividers move with the frame, so a focused control does not end up
8075
+ // amber on the outside and grey down the middle. Falls back to the resting
8076
+ // border, which is a no-op in themes that show focus as an outline.
8077
+ '&:has(input:focus) > input': this.showError()
8078
+ ? {}
8079
+ : {
8080
+ ...this.theme.borderLeft(chrome.focusBorder ?? this.dividerBorder()),
8081
+ ...this.theme.borderRight(chrome.focusBorder ?? this.dividerBorder()),
8082
+ },
5560
8083
  });
5561
- }, ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
5562
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSliderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5563
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniSliderComponent, isStandalone: true, selector: "uni-slider", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, 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 } }, outputs: { value: "valueChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'slider' }], usesInheritance: true, ngImport: i0, template: `
5564
- <input
5565
- type="range"
5566
- [class]="inputClass()"
5567
- [style.--uni-slider-fill]="fillPercent()"
5568
- [min]="resolvedMin()"
5569
- [max]="resolvedMax()"
5570
- [step]="step()"
5571
- [value]="value()"
5572
- [disabled]="disabled()"
5573
- (input)="handleInput($event)"
5574
- (blur)="markAsTouched()"
5575
- [attr.aria-label]="label()"
5576
- [attr.aria-describedby]="ariaDescribedBy() || null"
5577
- />
5578
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
8084
+ }, ...(ngDevMode ? [{ debugName: "rootClass" }] : /* istanbul ignore next */ []));
8085
+ /** Square at the field height, so the pointer target is legal at every size. */
8086
+ buttonClass = computed(() => {
8087
+ const colors = this.theme.colors();
8088
+ return css({
8089
+ display: 'grid',
8090
+ placeItems: 'center',
8091
+ flex: 'none',
8092
+ width: this.height(),
8093
+ padding: 0,
8094
+ border: 0,
8095
+ background: 'transparent',
8096
+ color: this.disabled() ? colors['on-disabled-surface'] : 'inherit',
8097
+ cursor: this.disabled() ? 'not-allowed' : 'pointer',
8098
+ touchAction: 'none',
8099
+ '&:disabled': { opacity: 0.4, cursor: 'not-allowed' },
8100
+ ...this.theme.focusRing(),
8101
+ });
8102
+ }, ...(ngDevMode ? [{ debugName: "buttonClass" }] : /* istanbul ignore next */ []));
8103
+ /**
8104
+ * Container chrome, defaulting to the shared `input` entry rather than to
8105
+ * hardcoded tokens. It is not a field, but it sits beside them in carts and
8106
+ * table rows, so a theme that restyles `input` must carry it along — the
8107
+ * options below stay as per-component overrides for a deliberately different
8108
+ * look.
8109
+ */
8110
+ containerColor = computed(() => this.componentOptions().color ?? this.fieldChrome().color, ...(ngDevMode ? [{ debugName: "containerColor" }] : /* istanbul ignore next */ []));
8111
+ containerBorder = computed(() => this.componentOptions().border ?? this.fieldChrome().border, ...(ngDevMode ? [{ debugName: "containerBorder" }] : /* istanbul ignore next */ []));
8112
+ containerRadius = computed(() => this.componentOptions().borderRadius ?? this.fieldChrome().borderRadius, ...(ngDevMode ? [{ debugName: "containerRadius" }] : /* istanbul ignore next */ []));
8113
+ /** The rules either side of the value, matching the frame around it. */
8114
+ dividerBorder = computed(() => this.containerBorder(), ...(ngDevMode ? [{ debugName: "dividerBorder" }] : /* istanbul ignore next */ []));
8115
+ /**
8116
+ * Characters the value cell asks the browser to size itself for.
8117
+ *
8118
+ * Load-bearing: a bare `<input>` defaults to `size="20"`, and `flex-basis:
8119
+ * auto` resolves to that intrinsic width — so the control claimed ~230px
8120
+ * instead of the ~100px its buttons and `valueWidth` need, and stole track
8121
+ * width from anything beside it in a grid (`1fr` is `minmax(auto, 1fr)`, and
8122
+ * the `auto` floor includes this). Tracking the content keeps the cell honest
8123
+ * while still letting it grow with the digits, which a fixed `width` would
8124
+ * not. `valueWidth` remains the floor, via `min-width`.
8125
+ */
8126
+ valueSize = computed(() => Math.max(this.displayText().length, 1), ...(ngDevMode ? [{ debugName: "valueSize" }] : /* istanbul ignore next */ []));
8127
+ valueBase() {
8128
+ const options = this.componentOptions();
8129
+ const colors = this.theme.colors();
8130
+ return {
8131
+ flex: '1 1 auto',
8132
+ minWidth: options.valueWidth ?? '3ch',
8133
+ textAlign: 'center',
8134
+ alignSelf: 'stretch',
8135
+ border: 0,
8136
+ background: 'transparent',
8137
+ color: 'inherit',
8138
+ font: 'inherit',
8139
+ padding: 0,
8140
+ outline: 'none',
8141
+ ...(options.tabularNumerals === false ? {} : { fontVariantNumeric: 'tabular-nums' }),
8142
+ // A rule either side, which is what makes the three parts read as one
8143
+ // control rather than three loose ones. It uses the **same token as the
8144
+ // outer border** so the frame reads as one weight — a heavier divider
8145
+ // makes the control look like three stuck together. A theme wanting a
8146
+ // distinct rule overrides just its colour.
8147
+ ...this.theme.borderLeft(this.dividerBorder()),
8148
+ ...this.theme.borderRight(this.dividerBorder()),
8149
+ ...(options.dividerColor
8150
+ ? { borderInlineColor: colors[options.dividerColor] }
8151
+ : {}),
8152
+ };
8153
+ }
8154
+ inputClass = computed(() => css([this.valueBase()]), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
8155
+ /** Read-only presentation: centred text on the same grid as the input. */
8156
+ readoutClass = computed(() => css([this.valueBase(), { display: 'grid', placeItems: 'center' }]), ...(ngDevMode ? [{ debugName: "readoutClass" }] : /* istanbul ignore next */ []));
8157
+ glyphSize = computed(() => Math.max(12, Math.round(this.height() / 2)), ...(ngDevMode ? [{ debugName: "glyphSize" }] : /* istanbul ignore next */ []));
8158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniQuantityStepperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniQuantityStepperComponent, isStandalone: true, selector: "uni-quantity-stepper, QuantityStepper", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, 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 }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, deleteAtMin: { classPropertyName: "deleteAtMin", publicName: "deleteAtMin", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", removed: "removed" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'quantityStepper' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- With an editable middle the input is the tab stop and the buttons are\n pointer affordances (tabindex=\"-1\"), exactly as in uni-number-input. With a\n read-only middle there is nothing else to focus, so the buttons become the\n tab stops and the group carries the name. -->\n<div\n [class]=\"rootClass()\"\n [attr.role]=\"editable() ? null : 'group'\"\n [attr.aria-label]=\"editable() ? null : label()\"\n>\n <button\n type=\"button\"\n [class]=\"buttonClass()\"\n [attr.tabindex]=\"editable() ? -1 : null\"\n [disabled]=\"disabled() || (atMin() && !showDelete())\"\n [attr.aria-label]=\"decrementLabel()\"\n (pointerdown)=\"onDecrementPress($event)\"\n (pointerup)=\"decrement.release()\"\n (pointercancel)=\"decrement.cancel()\"\n (lostpointercapture)=\"decrement.release()\"\n (click)=\"onDecrementClick()\"\n >\n <uni-icon [name]=\"decrementIcon()\" [size]=\"glyphSize()\" />\n </button>\n\n @if (editable()) {\n <input\n #field\n type=\"text\"\n role=\"spinbutton\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [attr.size]=\"valueSize()\"\n [value]=\"displayText()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-valuenow]=\"value() ?? null\"\n [attr.aria-valuemin]=\"resolvedMin()\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuetext]=\"displayText() || 'Empty'\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (input)=\"onInput($any($event.target).value)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n } @else {\n <!-- Text, not a control: no role and no tab stop, so a reader reads it as\n the group's content. -->\n <span [class]=\"readoutClass()\">{{ displayText() }}</span>\n }\n\n <button\n type=\"button\"\n [class]=\"buttonClass()\"\n [attr.tabindex]=\"editable() ? -1 : null\"\n [disabled]=\"disabled() || atMax()\"\n [attr.aria-label]=\"'Increase ' + label()\"\n (pointerdown)=\"increment.press($event)\"\n (pointerup)=\"increment.release()\"\n (pointercancel)=\"increment.cancel()\"\n (lostpointercapture)=\"increment.release()\"\n >\n <uni-icon [name]=\"componentOptions().incrementIcon ?? 'plus'\" [size]=\"glyphSize()\" />\n </button>\n</div>\n\n@if (editable()) {\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Use the up and down arrow keys to change the quantity.\n </span>\n}\n\n<!-- Fences, clamps and removals are otherwise silent. Held stepping announces\n once, on release. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n", dependencies: [{ kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5579
8160
  }
5580
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSliderComponent, decorators: [{
8161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniQuantityStepperComponent, decorators: [{
5581
8162
  type: Component,
5582
- args: [{
5583
- changeDetection: ChangeDetectionStrategy.OnPush,
5584
- selector: 'uni-slider',
5585
- providers: [{ provide: COMPONENT_NAME, useValue: 'slider' }],
5586
- template: `
5587
- <input
5588
- type="range"
5589
- [class]="inputClass()"
5590
- [style.--uni-slider-fill]="fillPercent()"
5591
- [min]="resolvedMin()"
5592
- [max]="resolvedMax()"
5593
- [step]="step()"
5594
- [value]="value()"
5595
- [disabled]="disabled()"
5596
- (input)="handleInput($event)"
5597
- (blur)="markAsTouched()"
5598
- [attr.aria-label]="label()"
5599
- [attr.aria-describedby]="ariaDescribedBy() || null"
5600
- />
5601
- `,
5602
- }]
5603
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], 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 }] }] } });
8163
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-quantity-stepper, QuantityStepper', imports: [UniIconComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'quantityStepper' }], host: { '[class]': 'className()' }, template: "<!-- With an editable middle the input is the tab stop and the buttons are\n pointer affordances (tabindex=\"-1\"), exactly as in uni-number-input. With a\n read-only middle there is nothing else to focus, so the buttons become the\n tab stops and the group carries the name. -->\n<div\n [class]=\"rootClass()\"\n [attr.role]=\"editable() ? null : 'group'\"\n [attr.aria-label]=\"editable() ? null : label()\"\n>\n <button\n type=\"button\"\n [class]=\"buttonClass()\"\n [attr.tabindex]=\"editable() ? -1 : null\"\n [disabled]=\"disabled() || (atMin() && !showDelete())\"\n [attr.aria-label]=\"decrementLabel()\"\n (pointerdown)=\"onDecrementPress($event)\"\n (pointerup)=\"decrement.release()\"\n (pointercancel)=\"decrement.cancel()\"\n (lostpointercapture)=\"decrement.release()\"\n (click)=\"onDecrementClick()\"\n >\n <uni-icon [name]=\"decrementIcon()\" [size]=\"glyphSize()\" />\n </button>\n\n @if (editable()) {\n <input\n #field\n type=\"text\"\n role=\"spinbutton\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [attr.size]=\"valueSize()\"\n [value]=\"displayText()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-valuenow]=\"value() ?? null\"\n [attr.aria-valuemin]=\"resolvedMin()\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuetext]=\"displayText() || 'Empty'\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (input)=\"onInput($any($event.target).value)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n } @else {\n <!-- Text, not a control: no role and no tab stop, so a reader reads it as\n the group's content. -->\n <span [class]=\"readoutClass()\">{{ displayText() }}</span>\n }\n\n <button\n type=\"button\"\n [class]=\"buttonClass()\"\n [attr.tabindex]=\"editable() ? -1 : null\"\n [disabled]=\"disabled() || atMax()\"\n [attr.aria-label]=\"'Increase ' + label()\"\n (pointerdown)=\"increment.press($event)\"\n (pointerup)=\"increment.release()\"\n (pointercancel)=\"increment.cancel()\"\n (lostpointercapture)=\"increment.release()\"\n >\n <uni-icon [name]=\"componentOptions().incrementIcon ?? 'plus'\" [size]=\"glyphSize()\" />\n </button>\n</div>\n\n@if (editable()) {\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Use the up and down arrow keys to change the quantity.\n </span>\n}\n\n<!-- Fences, clamps and removals are otherwise silent. Held stepping announces\n once, on release. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n" }]
8164
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], 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 }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], deleteAtMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "deleteAtMin", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
5604
8165
 
5605
8166
  /**
5606
8167
  * Compact chip for categories, states, filters and tokens.
@@ -6112,6 +8673,13 @@ class UniTagInputComponent extends BaseComponent {
6112
8673
  has the same geometry but is only the fallback's positioning context. */
6113
8674
  className = computed(() => css({ display: 'block', ...this.anchor.style }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
6114
8675
  wrapperClass = computed(() => css({ position: 'relative' }), ...(ngDevMode ? [{ debugName: "wrapperClass" }] : /* istanbul ignore next */ []));
8676
+ /**
8677
+ * The shared field chrome, from the same `input` theme entry
8678
+ * `uni-input-box` resolves — not a duplicate token, because the inset has to
8679
+ * match every other field or a chip field stops lining up with the text
8680
+ * field above it.
8681
+ */
8682
+ fieldChrome = this.theme.getComponentOptions('input');
6115
8683
  fieldClass = computed(() => {
6116
8684
  const options = this.componentOptions();
6117
8685
  return css({
@@ -6122,6 +8690,14 @@ class UniTagInputComponent extends BaseComponent {
6122
8690
  listStyle: 'none',
6123
8691
  margin: 0,
6124
8692
  padding: 0,
8693
+ // Wrapped chip rows keep clear of the field border; one 24px chip row
8694
+ // plus this padding fills the themed 32px minimum exactly.
8695
+ ...this.theme.paddingTop('xs'),
8696
+ ...this.theme.paddingBottom('xs'),
8697
+ // The leading inset lives here rather than on the inner <input> (see
8698
+ // `managedInset`): the chips are this field's leading edge, and an inset
8699
+ // on the text alone leaves the first chip riding the border.
8700
+ ...this.theme.paddingLeft(this.fieldChrome().paddingLeft),
6125
8701
  ...this.theme.gap(options.chipGap),
6126
8702
  });
6127
8703
  }, ...(ngDevMode ? [{ debugName: "fieldClass" }] : /* istanbul ignore next */ []));
@@ -6137,11 +8713,11 @@ class UniTagInputComponent extends BaseComponent {
6137
8713
  }), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
6138
8714
  listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions(), { anchor: this.anchor.name })), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
6139
8715
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6140
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div [class]=\"wrapperClass()\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8716
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div [class]=\"wrapperClass()\" (focusout)=\"onFocusOut($event)\">\n <!-- The chip row owns the leading inset, so the first chip sits at the\n field's edge instead of riding its border. -->\n <uni-input-box [error]=\"showError()\" height=\"auto\" [managedInset]=\"true\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6141
8717
  }
6142
8718
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, decorators: [{
6143
8719
  type: Component,
6144
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag-input', imports: [UniInputBoxComponent, UniTagComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div [class]=\"wrapperClass()\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
8720
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag-input', imports: [UniInputBoxComponent, UniTagComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div [class]=\"wrapperClass()\" (focusout)=\"onFocusOut($event)\">\n <!-- The chip row owns the leading inset, so the first chip sits at the\n field's edge instead of riding its border. -->\n <uni-input-box [error]=\"showError()\" height=\"auto\" [managedInset]=\"true\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
6145
8721
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], tagVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagVariant", required: false }] }], tagTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagTone", required: false }] }], tagSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagSize", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], chipRefs: [{ type: i0.ViewChildren, args: ['chip', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
6146
8722
 
6147
8723
  class UniTextareaComponent {
@@ -6152,7 +8728,7 @@ class UniTextareaComponent {
6152
8728
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
6153
8729
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
6154
8730
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
6155
- /** Synced from required() validators by the Signal Forms [field] directive. */
8731
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
6156
8732
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
6157
8733
  /**
6158
8734
  * Id(s) of external element(s) describing this control — typically your
@@ -6164,7 +8740,7 @@ class UniTextareaComponent {
6164
8740
  placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
6165
8741
  /** Visible text rows. Defaults to the theme's `textarea` options. */
6166
8742
  rows = input(undefined, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
6167
- // Signal Forms' own optional control inputs: the `[field]` directive syncs
8743
+ // Signal Forms' own optional control inputs: the `[formField]` directive syncs
6168
8744
  // them from the field's validators, as it does `required`.
6169
8745
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
6170
8746
  name = input('', ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
@@ -6192,13 +8768,25 @@ class UniTextareaComponent {
6192
8768
  this.value.set(event.target.value);
6193
8769
  }
6194
8770
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6195
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniTextareaComponent, isStandalone: true, selector: "uni-textarea", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" height=\"auto\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <textarea\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (blur)=\"markAsTouched()\"\n [rows]=\"resolvedRows()\"\n [class]=\"textareaClass()\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.spellcheck]=\"spellcheck() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n ></textarea>\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8771
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniTextareaComponent, isStandalone: true, selector: "uni-textarea", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" height=\"auto\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <textarea\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (blur)=\"markAsTouched()\"\n [rows]=\"resolvedRows()\"\n [class]=\"textareaClass()\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.spellcheck]=\"spellcheck() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n ></textarea>\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6196
8772
  }
6197
8773
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTextareaComponent, decorators: [{
6198
8774
  type: Component,
6199
8775
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-textarea', imports: [UniInputBoxComponent], template: "<uni-input-box [error]=\"showError()\" height=\"auto\" [width]=\"width()\" [fullWidth]=\"fullWidth()\" [grow]=\"grow()\">\n <textarea\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (blur)=\"markAsTouched()\"\n [rows]=\"resolvedRows()\"\n [class]=\"textareaClass()\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.spellcheck]=\"spellcheck() ?? null\"\n [attr.aria-label]=\"label()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n ></textarea>\n</uni-input-box>\n" }]
6200
8776
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], minLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minLength", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], spellcheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellcheck", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], grow: [{ type: i0.Input, args: [{ isSignal: true, alias: "grow", required: false }] }] } });
6201
8777
 
8778
+ /**
8779
+ * Everything a switch needs, from the three numbers a theme actually states.
8780
+ *
8781
+ * `travel` is why this is derived rather than written down: the knob starts at
8782
+ * `inset` and must end the same distance from the far edge, so it moves
8783
+ * `width - inset - knob - inset`, which reduces to `width - height`. The old
8784
+ * code hardcoded a translate of one track height, which was correct only while
8785
+ * the width was locked at 2x and the knob at 0.8x.
8786
+ */
8787
+ function geometry(width, height, inset) {
8788
+ return { width, height, inset, knob: height - inset * 2, travel: width - height, radius: height / 2 };
8789
+ }
6202
8790
  class UniToggleComponent extends BaseComponent {
6203
8791
  // --- REQUIRED SIGNALS (populated by FormCheckboxControl) ---
6204
8792
  checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
@@ -6206,7 +8794,7 @@ class UniToggleComponent extends BaseComponent {
6206
8794
  touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
6207
8795
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
6208
8796
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
6209
- /** Synced from required() validators by the Signal Forms [field] directive. */
8797
+ /** Synced from required() validators by the Signal Forms [formField] directive. */
6210
8798
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
6211
8799
  /**
6212
8800
  * Id(s) of external element(s) describing this control — typically your
@@ -6215,6 +8803,16 @@ class UniToggleComponent extends BaseComponent {
6215
8803
  ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
6216
8804
  // --- CONFIGURATION ---
6217
8805
  label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
8806
+ /**
8807
+ * Checked-state track color token, overriding the theme's
8808
+ * `toggle.behavior.checkedColor`.
8809
+ *
8810
+ * This exists alongside the theme option because `variant` — where this color
8811
+ * used to live exclusively — defaults to `'primary'`, so the component cannot
8812
+ * tell "set to primary" from "not set". Without an input, a theme-level
8813
+ * `checkedColor` would silently make per-instance `variant` inert.
8814
+ */
8815
+ checkedColor = input(...(ngDevMode ? [undefined, { debugName: "checkedColor" }] : /* istanbul ignore next */ []));
6218
8816
  // Only show errors if the user has actually interacted with the field
6219
8817
  showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
6220
8818
  markAsTouched() {
@@ -6224,18 +8822,44 @@ class UniToggleComponent extends BaseComponent {
6224
8822
  this.checked.set(event.target.checked);
6225
8823
  this.markAsTouched();
6226
8824
  }
8825
+ /**
8826
+ * Track and knob geometry for the active `size`, read out of the theme's
8827
+ * `sizes` block as data — `width`, `height` and the knob's inset `padding`.
8828
+ *
8829
+ * Read rather than spread: `padding` must not reach the track as real CSS or
8830
+ * it would double up with the knob's own `top`/`left` offsets. `uni-calendar`
8831
+ * treats its size block the same way.
8832
+ */
6227
8833
  metrics = computed(() => {
6228
- const toggleSize = this.componentOptions().size || 20;
6229
- const sliderSize = toggleSize * 0.8;
8834
+ // The legacy single-number token wins when a theme still sets it: that
8835
+ // theme opted into the old derived-ratio geometry before `sizes` existed,
8836
+ // and it applies to every instance regardless of the `size` input.
8837
+ const legacy = this.componentOptions().size;
8838
+ if (legacy != null) {
8839
+ const height = Number(legacy);
8840
+ return geometry(height * 2, height, (height - height * 0.8) / 2);
8841
+ }
8842
+ const size = this.style();
8843
+ const height = Number(size['height'] ?? 20);
8844
+ const width = Number(size['width'] ?? height * 2);
8845
+ const inset = Number(size['padding'] ?? (height - height * 0.8) / 2);
8846
+ return geometry(width, height, inset);
8847
+ }, ...(ngDevMode ? [{ debugName: "metrics" }] : /* istanbul ignore next */ []));
8848
+ /** The resolved checked/accent color: input, then theme option, then variant. */
8849
+ accent = computed(() => this.checkedColor() ?? this.componentOptions().checkedColor ?? this.variant(), ...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
8850
+ /** Knob slide and track color change, as a motion token — never `all`. */
8851
+ transitions = computed(() => {
8852
+ const motion = this.theme.motion(this.componentOptions().motion ?? 'control');
8853
+ const speed = motion.duration / 1000;
6230
8854
  return {
6231
- toggleSize,
6232
- toggleWidth: toggleSize * 2,
6233
- sliderSize,
6234
- sliderOffset: (toggleSize - sliderSize) / 2,
8855
+ // Scoped, never `all`: the focus ring must apply instantly rather than
8856
+ // interpolating its outline color from a stale value.
8857
+ track: `background-color ${speed}s ${motion.easing}, border-color ${speed}s ${motion.easing}`,
8858
+ knob: `transform ${speed}s ${motion.easing}, background-color ${speed}s ${motion.easing}`,
6235
8859
  };
6236
- }, ...(ngDevMode ? [{ debugName: "metrics" }] : /* istanbul ignore next */ []));
8860
+ }, ...(ngDevMode ? [{ debugName: "transitions" }] : /* istanbul ignore next */ []));
6237
8861
  toggleLabel = computed(() => {
6238
- const { toggleSize, toggleWidth, sliderSize, sliderOffset } = this.metrics();
8862
+ const { height, width, knob, inset, radius } = this.metrics();
6239
8863
  return css({
6240
8864
  userSelect: 'none',
6241
8865
  cursor: this.disabled() ? 'not-allowed' : 'pointer',
@@ -6245,26 +8869,24 @@ class UniToggleComponent extends BaseComponent {
6245
8869
  gap: 8,
6246
8870
  opacity: this.disabled() ? 0.6 : 1,
6247
8871
  '& .toggle-switch': {
6248
- width: toggleWidth,
6249
- height: toggleSize,
8872
+ width,
8873
+ height,
6250
8874
  backgroundColor: this.disabled()
6251
8875
  ? this.getThemeColor('disabled')
6252
8876
  : this.getThemeColor(this.componentOptions().trackColor ?? 'surface-variant'),
6253
- borderRadius: toggleSize / 2,
8877
+ borderRadius: radius,
6254
8878
  position: 'relative',
6255
- // Scoped, never `all`: the focus ring must apply instantly rather
6256
- // than interpolating its outline color from a stale value.
6257
- transition: 'background-color 0.3s ease, border-color 0.3s ease',
8879
+ transition: this.transitions().track,
6258
8880
  },
6259
8881
  '& .toggle-slider': {
6260
- width: sliderSize,
6261
- height: sliderSize,
8882
+ width: knob,
8883
+ height: knob,
6262
8884
  backgroundColor: this.getThemeColor(this.componentOptions().knobColor ?? 'surface'),
6263
8885
  borderRadius: '50%',
6264
8886
  position: 'absolute',
6265
- top: sliderOffset,
6266
- left: sliderOffset,
6267
- transition: 'transform 0.3s ease, background-color 0.3s ease',
8887
+ top: inset,
8888
+ left: inset,
8889
+ transition: this.transitions().knob,
6268
8890
  ...this.theme.boxShadow('raised'),
6269
8891
  },
6270
8892
  // Hover darkens whatever the token resolves to — the button convention.
@@ -6276,7 +8898,8 @@ class UniToggleComponent extends BaseComponent {
6276
8898
  });
6277
8899
  }, ...(ngDevMode ? [{ debugName: "toggleLabel" }] : /* istanbul ignore next */ []));
6278
8900
  toggleInput = computed(() => {
6279
- const { toggleSize } = this.metrics();
8901
+ const { travel } = this.metrics();
8902
+ const accent = this.getThemeColor(this.accent());
6280
8903
  return css({
6281
8904
  position: 'absolute',
6282
8905
  zIndex: -1,
@@ -6284,18 +8907,20 @@ class UniToggleComponent extends BaseComponent {
6284
8907
  height: 0,
6285
8908
  opacity: 0,
6286
8909
  '&:checked + .toggle-switch': {
6287
- backgroundColor: this.getThemeColor(this.variant()),
6288
- borderColor: this.getThemeColor(this.variant()),
8910
+ backgroundColor: accent,
8911
+ borderColor: accent,
6289
8912
  },
6290
8913
  '&:checked + .toggle-switch .toggle-slider': {
6291
- transform: `translateX(${toggleSize}px)`,
8914
+ transform: `translateX(${travel}px)`,
6292
8915
  },
6293
8916
  '&:disabled + .toggle-switch': {
6294
8917
  cursor: 'not-allowed',
6295
8918
  },
6296
- // The shared, themable focus indicator, keyed off the hidden input.
8919
+ // The shared, themable focus indicator, keyed off the hidden input. It
8920
+ // wears the checked color rather than the variant, so a themed on-state
8921
+ // is not paired with a ring in some other role's color.
6297
8922
  '&:focus + .toggle-switch': {
6298
- ...this.theme.focusRingStyle(this.getThemeColor(this.variant())),
8923
+ ...this.theme.focusRingStyle(accent),
6299
8924
  },
6300
8925
  });
6301
8926
  }, ...(ngDevMode ? [{ debugName: "toggleInput" }] : /* istanbul ignore next */ []));
@@ -6304,12 +8929,12 @@ class UniToggleComponent extends BaseComponent {
6304
8929
  return colors[token] ? colors[token] : colors['primary'];
6305
8930
  }
6306
8931
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniToggleComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
6307
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniToggleComponent, isStandalone: true, selector: "uni-toggle", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'toggle' }], usesInheritance: true, ngImport: i0, template: "<label [class]=\"toggleLabel()\">\n <input\n type=\"checkbox\"\n role=\"switch\"\n [class]=\"toggleInput()\"\n [checked]=\"checked()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"toggle-switch\">\n <div class=\"toggle-slider\"></div>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8932
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniToggleComponent, isStandalone: true, selector: "uni-toggle", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, checkedColor: { classPropertyName: "checkedColor", publicName: "checkedColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'toggle' }], usesInheritance: true, ngImport: i0, template: "<label [class]=\"toggleLabel()\">\n <input\n type=\"checkbox\"\n role=\"switch\"\n [class]=\"toggleInput()\"\n [checked]=\"checked()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"toggle-switch\">\n <div class=\"toggle-slider\"></div>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6308
8933
  }
6309
8934
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniToggleComponent, decorators: [{
6310
8935
  type: Component,
6311
8936
  args: [{ selector: 'uni-toggle', imports: [UniTextDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'toggle' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label [class]=\"toggleLabel()\">\n <input\n type=\"checkbox\"\n role=\"switch\"\n [class]=\"toggleInput()\"\n [checked]=\"checked()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"toggle-switch\">\n <div class=\"toggle-slider\"></div>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n" }]
6312
- }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
8937
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], checkedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkedColor", required: false }] }] } });
6313
8938
 
6314
8939
  /**
6315
8940
  * Every layout and typography attribute directive, for
@@ -6343,6 +8968,9 @@ const UNI_FORMS = [
6343
8968
  UniMultiSelectDropdownComponent,
6344
8969
  UniSearchInputComponent,
6345
8970
  UniTagInputComponent,
8971
+ UniNumberInputComponent,
8972
+ UniNumberRangeInputComponent,
8973
+ UniQuantityStepperComponent,
6346
8974
  UniSliderComponent,
6347
8975
  UniDateInputComponent,
6348
8976
  UniTimeInputComponent,
@@ -7943,8 +10571,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
7943
10571
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: '[uni-dialog-header]', imports: [UniBoxDirective, UniIconButtonComponent, UniTextDirective, UniRowDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'dialogHeader' }], template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n [paddingHorizontal]=\"componentOptions().paddingHorizontal ?? 'sm'\"\n>\n <!-- Balance spacer: only centered titles need to offset the close button. -->\n @if ((componentOptions().textAlign || 'center') === 'center') {\n <div box-layout [width]=\"26\"></div>\n }\n <div box-layout [grow]=\"1\" [attr.id]=\"titleId\">\n <span uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'center'\"\n ><ng-content></ng-content\n ></span>\n </div>\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDialog()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n</div>\n" }]
7944
10572
  }], ctorParameters: () => [] });
7945
10573
 
10574
+ const DRAWER_PANEL = new InjectionToken('uni-drawer-panel');
10575
+
10576
+ /**
10577
+ * The drawer's pinned footer action row — the save bar of an editor panel.
10578
+ *
10579
+ * Sits outside the scrolling body, so the actions stay reachable however long
10580
+ * the form is. Mirrors `[dialog-buttons]`; the difference is posture, which
10581
+ * lives in the `drawerButtons` theme options rather than here.
10582
+ */
10583
+ class UniDrawerButtonsComponent extends BaseComponent {
10584
+ drawer = inject(DRAWER_PANEL, { optional: true });
10585
+ confirmButtonText = input(...(ngDevMode ? [undefined, { debugName: "confirmButtonText" }] : /* istanbul ignore next */ []));
10586
+ confirmButtonVariant = input(...(ngDevMode ? [undefined, { debugName: "confirmButtonVariant" }] : /* istanbul ignore next */ []));
10587
+ cancelButtonText = input(...(ngDevMode ? [undefined, { debugName: "cancelButtonText" }] : /* istanbul ignore next */ []));
10588
+ cancelButtonVariant = input(...(ngDevMode ? [undefined, { debugName: "cancelButtonVariant" }] : /* istanbul ignore next */ []));
10589
+ disableConfirm = input(...(ngDevMode ? [undefined, { debugName: "disableConfirm" }] : /* istanbul ignore next */ []));
10590
+ padding = input(...(ngDevMode ? [undefined, { debugName: "padding" }] : /* istanbul ignore next */ []));
10591
+ justifyContent = input(...(ngDevMode ? [undefined, { debugName: "justifyContent" }] : /* istanbul ignore next */ []));
10592
+ confirmed = output();
10593
+ // Inputs win over theme options; the trailing literal is the fallback.
10594
+ confirmVariant = computed(() => this.confirmButtonVariant() ?? this.componentOptions().confirmButtonVariant ?? 'primary', ...(ngDevMode ? [{ debugName: "confirmVariant" }] : /* istanbul ignore next */ []));
10595
+ cancelVariant = computed(() => this.cancelButtonVariant() ?? this.componentOptions().cancelButtonVariant ?? 'quaternary', ...(ngDevMode ? [{ debugName: "cancelVariant" }] : /* istanbul ignore next */ []));
10596
+ paddingValue = computed(() => this.padding() ?? this.componentOptions().padding ?? 'md', ...(ngDevMode ? [{ debugName: "paddingValue" }] : /* istanbul ignore next */ []));
10597
+ justifyContentValue = computed(() => this.justifyContent() ?? this.componentOptions().justifyContent ?? 'flex-end', ...(ngDevMode ? [{ debugName: "justifyContentValue" }] : /* istanbul ignore next */ []));
10598
+ gapValue = computed(() => this.componentOptions().gap ?? 'sm', ...(ngDevMode ? [{ debugName: "gapValue" }] : /* istanbul ignore next */ []));
10599
+ buttonSize = computed(() => this.componentOptions().buttonSize ?? 'md', ...(ngDevMode ? [{ debugName: "buttonSize" }] : /* istanbul ignore next */ []));
10600
+ /** A pinned row, sized by its content rather than by the body beside it. */
10601
+ hostClass = computed(() => css({
10602
+ flex: 'none',
10603
+ ...this.theme.borderTop(this.componentOptions().divider),
10604
+ }), ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
10605
+ className = computed(() => css([
10606
+ this.componentTheme().fixed,
10607
+ this.componentOptions().stretch && {
10608
+ width: '100%',
10609
+ minWidth: '100%',
10610
+ '& > button': { flex: '1 1 50%', maxWidth: '50%' },
10611
+ },
10612
+ ]), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10613
+ /**
10614
+ * Cancel routes through the drawer's own close decision, so a panel with
10615
+ * unsaved changes can veto it exactly as it vetoes Escape.
10616
+ */
10617
+ closeDrawer() {
10618
+ this.drawer?.requestClose('close-button');
10619
+ }
10620
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerButtonsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
10621
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDrawerButtonsComponent, isStandalone: true, selector: "[uni-drawer-buttons], [drawer-buttons]", inputs: { confirmButtonText: { classPropertyName: "confirmButtonText", publicName: "confirmButtonText", isSignal: true, isRequired: false, transformFunction: null }, confirmButtonVariant: { classPropertyName: "confirmButtonVariant", publicName: "confirmButtonVariant", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonText: { classPropertyName: "cancelButtonText", publicName: "cancelButtonText", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonVariant: { classPropertyName: "cancelButtonVariant", publicName: "cancelButtonVariant", isSignal: true, isRequired: false, transformFunction: null }, disableConfirm: { classPropertyName: "disableConfirm", publicName: "disableConfirm", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, justifyContent: { classPropertyName: "justifyContent", publicName: "justifyContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed" }, host: { properties: { "class": "hostClass()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'drawerButtons' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n [gap]=\"gapValue()\"\n [padding]=\"paddingValue()\"\n [justifyContent]=\"justifyContentValue()\"\n [flexDirection]=\"componentOptions().reverseOrder ? 'row-reverse' : 'row'\"\n [class]=\"className()\"\n>\n <button text-button [variant]=\"cancelVariant()\" [size]=\"buttonSize()\" (click)=\"closeDrawer()\">\n {{ cancelButtonText() || 'Cancel' }}\n </button>\n <button\n text-button\n [variant]=\"confirmVariant()\"\n [size]=\"buttonSize()\"\n (click)=\"confirmed.emit()\"\n [disable]=\"disableConfirm()\"\n >\n {{ confirmButtonText() || 'Save' }}\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10622
+ }
10623
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerButtonsComponent, decorators: [{
10624
+ type: Component,
10625
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: '[uni-drawer-buttons], [drawer-buttons]', imports: [UniRowDirective, UniButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'drawerButtons' }], host: { '[class]': 'hostClass()' }, template: "<div\n row-layout\n [gap]=\"gapValue()\"\n [padding]=\"paddingValue()\"\n [justifyContent]=\"justifyContentValue()\"\n [flexDirection]=\"componentOptions().reverseOrder ? 'row-reverse' : 'row'\"\n [class]=\"className()\"\n>\n <button text-button [variant]=\"cancelVariant()\" [size]=\"buttonSize()\" (click)=\"closeDrawer()\">\n {{ cancelButtonText() || 'Cancel' }}\n </button>\n <button\n text-button\n [variant]=\"confirmVariant()\"\n [size]=\"buttonSize()\"\n (click)=\"confirmed.emit()\"\n [disable]=\"disableConfirm()\"\n >\n {{ confirmButtonText() || 'Save' }}\n </button>\n</div>\n" }]
10626
+ }], propDecorators: { confirmButtonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmButtonText", required: false }] }], confirmButtonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmButtonVariant", required: false }] }], cancelButtonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelButtonText", required: false }] }], cancelButtonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelButtonVariant", required: false }] }], disableConfirm: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableConfirm", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], justifyContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "justifyContent", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }] } });
10627
+
10628
+ /**
10629
+ * The drawer's pinned header row: a title, and optionally a close button.
10630
+ *
10631
+ * Sits outside the scrolling body, so it stays put while the form beneath it
10632
+ * moves. Reached either by projecting it — `<div uni-drawer-header>` — or
10633
+ * implicitly, by giving `uni-drawer` a `headline`, in which case the drawer
10634
+ * renders one of these itself.
10635
+ */
10636
+ class UniDrawerHeaderComponent extends BaseComponent {
10637
+ drawer = inject(DRAWER_PANEL, { optional: true });
10638
+ /** Title text. Falls back to the drawer's `headline`; projected content wins over both. */
10639
+ headline = input(...(ngDevMode ? [undefined, { debugName: "headline" }] : /* istanbul ignore next */ []));
10640
+ /** Attached to the title so the drawer is labelled by it. */
10641
+ titleId = this.drawer?.titleId ?? null;
10642
+ title = computed(() => this.headline() ?? this.drawer?.headline() ?? '', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
10643
+ showClose = computed(() => this.drawer?.defaultCloseButton() ?? true, ...(ngDevMode ? [{ debugName: "showClose" }] : /* istanbul ignore next */ []));
10644
+ constructor() {
10645
+ super();
10646
+ // Tells the drawer it is labelled by this row rather than by `ariaLabel`.
10647
+ this.drawer?.hasHeader.set(true);
10648
+ }
10649
+ /** Never a bare close: the drawer decides, so a veto is honoured here too. */
10650
+ closeDrawer() {
10651
+ this.drawer?.requestClose('close-button');
10652
+ }
10653
+ className = computed(() => css({
10654
+ // A pinned row: it is a flex child of the panel and must not be sized
10655
+ // by the scrolling body beside it.
10656
+ flex: 'none',
10657
+ ...this.theme.borderBottom(this.componentOptions().divider),
10658
+ }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10659
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDrawerHeaderComponent, isStandalone: true, selector: "[uni-drawer-header]", inputs: { headline: { classPropertyName: "headline", publicName: "headline", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'drawerHeader' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n gap=\"sm\"\n [padding]=\"componentOptions().padding ?? 'md'\"\n>\n <div box-layout [grow]=\"1\" [minWidth]=\"0\" [attr.id]=\"titleId\">\n <span\n uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'left'\"\n ><ng-content>{{ title() }}</ng-content></span\n >\n </div>\n @if (showClose()) {\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDrawer()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n }\n</div>\n", dependencies: [{ kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10661
+ }
10662
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerHeaderComponent, decorators: [{
10663
+ type: Component,
10664
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: '[uni-drawer-header]', imports: [UniBoxDirective, UniIconButtonComponent, UniTextDirective, UniRowDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'drawerHeader' }], host: { '[class]': 'className()' }, template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n gap=\"sm\"\n [padding]=\"componentOptions().padding ?? 'md'\"\n>\n <div box-layout [grow]=\"1\" [minWidth]=\"0\" [attr.id]=\"titleId\">\n <span\n uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'left'\"\n ><ng-content>{{ title() }}</ng-content></span\n >\n </div>\n @if (showClose()) {\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDrawer()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n }\n</div>\n" }]
10665
+ }], ctorParameters: () => [], propDecorators: { headline: [{ type: i0.Input, args: [{ isSignal: true, alias: "headline", required: false }] }] } });
10666
+
7946
10667
  /**
7947
- * Navigation drawer with two modes sharing one content slot:
10668
+ * Drawer with two modes sharing one three-row layout:
7948
10669
  *
7949
10670
  * - `side` — an in-flow `<aside>` that pushes content (dashboard sidenav);
7950
10671
  * opening/closing animates its width, and the divider border primitive
@@ -7953,17 +10674,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
7953
10674
  * scrim backdrop come from the platform (same machinery as `uni-dialog`),
7954
10675
  * sliding in from its edge.
7955
10676
  *
7956
- * Surface, width, divider, elevation, padding and backdrop all resolve from
7957
- * `drawer` theme tokens.
10677
+ * **The panel is never the scroll container.** It is a flex column of three
10678
+ * rows — an optional `[uni-drawer-header]`, the projected body, an optional
10679
+ * `[uni-drawer-buttons]` — and only the body scrolls. The panel itself is
10680
+ * `overflow: clip` on both axes. That is what lets a header and a save bar pin
10681
+ * while a long form scrolls between them, and it is why the theme's `padding`
10682
+ * option lands on the body row rather than the panel: padding on a scrolling
10683
+ * box scrolls away with its content.
10684
+ *
10685
+ * Surface, width, divider, elevation, padding, backdrop, scrim and background
10686
+ * all resolve from `drawer` theme tokens.
7958
10687
  */
7959
10688
  class UniDrawerComponent extends BaseComponent {
7960
10689
  /** Two-way bindable open state: [(open)]. */
7961
10690
  open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
7962
10691
  mode = input('side', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
7963
10692
  position = input('start', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
7964
- /** Accessible name for the overlay mode's dialog. */
7965
- ariaLabel = input('Navigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
7966
- contentTemplate = viewChild.required('content');
10693
+ /**
10694
+ * Accessible name for the overlay mode. Only consulted when the drawer has
10695
+ * no header to be labelled by.
10696
+ *
10697
+ * There is deliberately no default. A drawer used as an editor panel that
10698
+ * inherited the literal "Navigation" would announce itself as something it
10699
+ * is not, and a wrong accessible name is worse than a missing one — the
10700
+ * missing one is at least caught by any audit.
10701
+ */
10702
+ ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
10703
+ /**
10704
+ * Title for the drawer's header row. Shorthand for projecting a
10705
+ * `[uni-drawer-header]`; project one instead when the header needs more
10706
+ * than a title (a record counter, prev/next navigation).
10707
+ */
10708
+ headline = input(...(ngDevMode ? [undefined, { debugName: "headline" }] : /* istanbul ignore next */ []));
10709
+ /** Whether the header row renders a close button. */
10710
+ defaultCloseButton = input(true, ...(ngDevMode ? [{ debugName: "defaultCloseButton" }] : /* istanbul ignore next */ []));
10711
+ /** Panel width in px, overriding the theme's `drawer.behavior.width`. */
10712
+ width = input(...(ngDevMode ? [undefined, { debugName: "width" }] : /* istanbul ignore next */ []));
10713
+ /**
10714
+ * Whether the overlay dims the page behind it, overriding the theme's
10715
+ * `drawer.behavior.scrim`. False leaves the backdrop transparent so the page
10716
+ * stays legible while the panel is open — an editor panel beside a board the
10717
+ * user is still reading.
10718
+ *
10719
+ * This does not make the drawer non-modal: focus is still trapped and the
10720
+ * page behind is still inert. It is a visibility choice, not a modality one.
10721
+ */
10722
+ scrim = input(...(ngDevMode ? [undefined, { debugName: "scrim" }] : /* istanbul ignore next */ []));
10723
+ /**
10724
+ * CSS selector for the element to focus when the overlay opens. The native
10725
+ * default is the first focusable element, which in an editor panel is
10726
+ * usually the close button rather than the first field.
10727
+ */
10728
+ initialFocus = input(...(ngDevMode ? [undefined, { debugName: "initialFocus" }] : /* istanbul ignore next */ []));
10729
+ /**
10730
+ * The drawer is *asking* to close — Escape, the backdrop, or a close/cancel
10731
+ * button. Pair with `disableAutoClose` to hold the panel open while an async
10732
+ * confirmation runs.
10733
+ */
10734
+ closeRequest = output();
10735
+ /**
10736
+ * When true the drawer never closes itself; it only emits `closeRequest` and
10737
+ * waits for the consumer to set `open`. Off by default, so a drawer that
10738
+ * ignores `closeRequest` behaves exactly as it always has.
10739
+ */
10740
+ disableAutoClose = input(false, ...(ngDevMode ? [{ debugName: "disableAutoClose" }] : /* istanbul ignore next */ []));
10741
+ /** Set by a projected `[uni-drawer-header]` so it can pin flush to the top. */
10742
+ hasHeader = signal(false, ...(ngDevMode ? [{ debugName: "hasHeader" }] : /* istanbul ignore next */ []));
10743
+ /** Id referenced by aria-labelledby; the header row attaches it to its title. */
10744
+ titleId = uniqueId('uni-drawer-title');
10745
+ labelledBy = computed(() => (this.hasHeader() ? this.titleId : null), ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
10746
+ headerTemplate = viewChild.required('header');
10747
+ bodyTemplate = viewChild.required('body');
10748
+ footerTemplate = viewChild.required('footer');
7967
10749
  overlay = viewChild('overlay', ...(ngDevMode ? [{ debugName: "overlay" }] : /* istanbul ignore next */ []));
7968
10750
  constructor() {
7969
10751
  super();
@@ -7975,6 +10757,9 @@ class UniDrawerComponent extends BaseComponent {
7975
10757
  if (!dialog.open) {
7976
10758
  dialog.removeAttribute('closing');
7977
10759
  dialog.showModal();
10760
+ const selector = this.initialFocus();
10761
+ if (selector)
10762
+ dialog.querySelector(selector)?.focus();
7978
10763
  }
7979
10764
  }
7980
10765
  else if (dialog.open) {
@@ -7983,14 +10768,24 @@ class UniDrawerComponent extends BaseComponent {
7983
10768
  }
7984
10769
  });
7985
10770
  }
10771
+ /**
10772
+ * The one place a close is decided, so every route in — Escape, the
10773
+ * backdrop, the header's close button, the footer's cancel — behaves
10774
+ * identically and is equally vetoable.
10775
+ */
10776
+ requestClose(reason) {
10777
+ this.closeRequest.emit({ reason });
10778
+ if (!this.disableAutoClose())
10779
+ this.open.set(false);
10780
+ }
7986
10781
  onBackdropClick(event) {
7987
10782
  if (event.target.nodeName === 'DIALOG')
7988
- this.open.set(false);
10783
+ this.requestClose('backdrop');
7989
10784
  }
7990
10785
  /** Route Escape through the animated close, keeping `open` in sync. */
7991
10786
  onCancel(event) {
7992
10787
  event.preventDefault();
7993
- this.open.set(false);
10788
+ this.requestClose('escape');
7994
10789
  }
7995
10790
  onAnimationEnd(event) {
7996
10791
  const dialog = this.overlay()?.nativeElement;
@@ -8004,53 +10799,129 @@ class UniDrawerComponent extends BaseComponent {
8004
10799
  edge = computed(() => (this.position() === 'start' ? '-100%' : '100%'), ...(ngDevMode ? [{ debugName: "edge" }] : /* istanbul ignore next */ []));
8005
10800
  slideIn = computed(() => keyframes({ from: { transform: `translateX(${this.edge()})` }, to: { transform: 'translateX(0)' } }), ...(ngDevMode ? [{ debugName: "slideIn" }] : /* istanbul ignore next */ []));
8006
10801
  slideOut = computed(() => keyframes({ from: { transform: 'translateX(0)' }, to: { transform: `translateX(${this.edge()})` } }), ...(ngDevMode ? [{ debugName: "slideOut" }] : /* istanbul ignore next */ []));
10802
+ /** Input wins over the theme option; the literal is the last-resort default. */
10803
+ panelWidth = computed(() => this.width() ?? this.componentOptions().width ?? 280, ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
10804
+ showScrim = computed(() => this.scrim() ?? this.componentOptions().scrim ?? true, ...(ngDevMode ? [{ debugName: "showScrim" }] : /* istanbul ignore next */ []));
10805
+ /**
10806
+ * The panel's surface. `solid` is the plain color pair; `glass` and
10807
+ * `gradient` derive from it, so a theme swaps treatment without restating
10808
+ * the color.
10809
+ */
10810
+ surface = computed(() => {
10811
+ const options = this.componentOptions();
10812
+ const pair = this.theme.colorPair(options.color);
10813
+ const base = pair?.backgroundColor;
10814
+ const treatment = options.background ?? 'solid';
10815
+ if (!base || treatment === 'solid')
10816
+ return pair;
10817
+ if (treatment === 'glass') {
10818
+ return {
10819
+ ...pair,
10820
+ backgroundColor: `color-mix(in srgb, ${base} 72%, transparent)`,
10821
+ backdropFilter: 'blur(12px) saturate(1.4)',
10822
+ };
10823
+ }
10824
+ // A vertical tint toward the content color: always visible, and it never
10825
+ // lets the page show through the way a fade to transparent would.
10826
+ return {
10827
+ ...pair,
10828
+ backgroundImage: `linear-gradient(to bottom, ${base} 0%, color-mix(in srgb, ${base} 92%, ${pair?.color ?? 'transparent'} 8%) 100%)`,
10829
+ };
10830
+ }, ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
10831
+ /** The shared flex column: three rows, and never a scroll container itself. */
10832
+ shell = {
10833
+ boxSizing: 'border-box',
10834
+ display: 'flex',
10835
+ flexDirection: 'column',
10836
+ // Both axes, explicitly, and never the shorthand. Setting one axis alone
10837
+ // computes the other to `auto` — which is precisely how a panel becomes
10838
+ // an accidental scroll container.
10839
+ overflowX: 'clip',
10840
+ overflowY: 'clip',
10841
+ };
8007
10842
  sideClass = computed(() => {
8008
10843
  const options = this.componentOptions();
8009
- const width = options.width ?? 280;
10844
+ const width = this.panelWidth();
8010
10845
  const start = this.position() === 'start';
8011
10846
  return css({
8012
- display: 'block',
8013
- boxSizing: 'border-box',
10847
+ ...this.shell,
8014
10848
  height: '100%',
8015
10849
  flex: 'none',
8016
- overflowX: 'hidden',
8017
- overflowY: 'auto',
8018
10850
  transition: 'width 0.25s ease, visibility 0.25s',
8019
- ...this.theme.colorPair(options.color),
10851
+ ...this.surface(),
8020
10852
  ...(start
8021
10853
  ? this.theme.borderRight(options.divider)
8022
10854
  : this.theme.borderLeft(options.divider)),
8023
10855
  ...(this.open()
8024
- ? { width, visibility: 'visible', ...this.theme.padding(options.padding) }
8025
- : { width: 0, visibility: 'hidden', padding: 0, border: 'none' }),
10856
+ ? { width, visibility: 'visible' }
10857
+ : { width: 0, visibility: 'hidden', border: 'none' }),
8026
10858
  });
8027
10859
  }, ...(ngDevMode ? [{ debugName: "sideClass" }] : /* istanbul ignore next */ []));
8028
10860
  overClass = computed(() => {
8029
10861
  const options = this.componentOptions();
8030
10862
  const start = this.position() === 'start';
8031
10863
  return css({
8032
- boxSizing: 'border-box',
8033
- width: options.width ?? 280,
10864
+ ...this.shell,
10865
+ width: this.panelWidth(),
8034
10866
  maxWidth: '90vw',
8035
10867
  height: '100dvh',
8036
10868
  maxHeight: '100dvh',
8037
10869
  border: 'none',
10870
+ padding: 0,
8038
10871
  margin: start ? '0 auto 0 0' : '0 0 0 auto',
8039
- overflowY: 'auto',
8040
- ...this.theme.colorPair(options.color),
8041
- ...this.theme.padding(options.padding),
10872
+ ...this.surface(),
8042
10873
  ...this.theme.boxShadow(options.elevation),
8043
- '&::backdrop': { ...options.backdrop },
10874
+ // The UA stylesheet hides a closed dialog with `display: none`, which the
10875
+ // shell's `display: flex` would otherwise beat on specificity — leaving
10876
+ // the panel sitting in normal flow behind the page whenever it is shut.
10877
+ // The closing animation still runs: `open` is only removed after it ends.
10878
+ '&:not([open])': { display: 'none' },
10879
+ // `scrim: false` keeps the modality — focus trap, inert page — but stops
10880
+ // the drawer dimming what it covers.
10881
+ '&::backdrop': this.showScrim() ? { ...options.backdrop } : { background: 'transparent' },
8044
10882
  '&[open]': { animation: `${this.slideIn()} 250ms ease-out` },
8045
10883
  '&[closing]': { animation: `${this.slideOut()} 250ms ease-in` },
8046
10884
  });
8047
10885
  }, ...(ngDevMode ? [{ debugName: "overClass" }] : /* istanbul ignore next */ []));
10886
+ /** The only scrolling row, and the only padded one. */
10887
+ bodyClass = computed(() => css({
10888
+ flex: '1 1 auto',
10889
+ minHeight: 0,
10890
+ // Defence in depth: a positioned body is the containing block for any
10891
+ // stray absolute descendant, so nothing can re-home into an ancestor
10892
+ // and inflate its scrollHeight. Only safe because the shell above is
10893
+ // `overflow: clip` — on its own this would move the phantom overflow
10894
+ // into this scroller instead of out of the panel.
10895
+ position: 'relative',
10896
+ overflowX: 'hidden',
10897
+ overflowY: 'auto',
10898
+ overscrollBehavior: 'contain',
10899
+ ...this.theme.padding(this.componentOptions().padding),
10900
+ }), ...(ngDevMode ? [{ debugName: "bodyClass" }] : /* istanbul ignore next */ []));
8048
10901
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8049
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDrawerComponent, isStandalone: true, selector: "uni-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'drawer' }], viewQueries: [{ propertyName: "contentTemplate", first: true, predicate: ["content"], descendants: true, isSignal: true }, { propertyName: "overlay", first: true, predicate: ["overlay"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
8050
- <ng-template #content><ng-content /></ng-template>
10902
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDrawerComponent, isStandalone: true, selector: "uni-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, headline: { classPropertyName: "headline", publicName: "headline", isSignal: true, isRequired: false, transformFunction: null }, defaultCloseButton: { classPropertyName: "defaultCloseButton", publicName: "defaultCloseButton", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, scrim: { classPropertyName: "scrim", publicName: "scrim", isSignal: true, isRequired: false, transformFunction: null }, initialFocus: { classPropertyName: "initialFocus", publicName: "initialFocus", isSignal: true, isRequired: false, transformFunction: null }, disableAutoClose: { classPropertyName: "disableAutoClose", publicName: "disableAutoClose", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closeRequest: "closeRequest" }, providers: [
10903
+ { provide: COMPONENT_NAME, useValue: 'drawer' },
10904
+ { provide: DRAWER_PANEL, useExisting: forwardRef(() => UniDrawerComponent) },
10905
+ ], viewQueries: [{ propertyName: "headerTemplate", first: true, predicate: ["header"], descendants: true, isSignal: true }, { propertyName: "bodyTemplate", first: true, predicate: ["body"], descendants: true, isSignal: true }, { propertyName: "footerTemplate", first: true, predicate: ["footer"], descendants: true, isSignal: true }, { propertyName: "overlay", first: true, predicate: ["overlay"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
10906
+ <!-- One <ng-content> per slot, each parked in a template so both modes can
10907
+ render the same projected nodes. The catch-all is declared last so the
10908
+ two selective slots claim their content first. -->
10909
+ <ng-template #header>
10910
+ @if (headline()) {
10911
+ <div uni-drawer-header></div>
10912
+ }
10913
+ <ng-content select="[uni-drawer-header]" />
10914
+ </ng-template>
10915
+ <ng-template #footer>
10916
+ <ng-content select="[uni-drawer-buttons], [drawer-buttons]" />
10917
+ </ng-template>
10918
+ <ng-template #body><ng-content /></ng-template>
10919
+
8051
10920
  @if (mode() === 'side') {
8052
10921
  <aside [class]="sideClass()" [attr.aria-hidden]="open() ? null : 'true'">
8053
- <ng-container [ngTemplateOutlet]="contentTemplate()" />
10922
+ <ng-container [ngTemplateOutlet]="headerTemplate()" />
10923
+ <div [class]="bodyClass()"><ng-container [ngTemplateOutlet]="bodyTemplate()" /></div>
10924
+ <ng-container [ngTemplateOutlet]="footerTemplate()" />
8054
10925
  </aside>
8055
10926
  } @else {
8056
10927
  <!-- Click handles the ::backdrop only (target check); keyboard closing
@@ -8060,28 +10931,49 @@ class UniDrawerComponent extends BaseComponent {
8060
10931
  <dialog
8061
10932
  #overlay
8062
10933
  [class]="overClass()"
8063
- [attr.aria-label]="ariaLabel()"
10934
+ [attr.aria-labelledby]="labelledBy()"
10935
+ [attr.aria-label]="labelledBy() ? null : ariaLabel()"
8064
10936
  (click)="onBackdropClick($event)"
8065
10937
  (cancel)="onCancel($event)"
8066
10938
  (animationend)="onAnimationEnd($event)"
8067
10939
  >
8068
- <ng-container [ngTemplateOutlet]="contentTemplate()" />
10940
+ <ng-container [ngTemplateOutlet]="headerTemplate()" />
10941
+ <div [class]="bodyClass()"><ng-container [ngTemplateOutlet]="bodyTemplate()" /></div>
10942
+ <ng-container [ngTemplateOutlet]="footerTemplate()" />
8069
10943
  </dialog>
8070
10944
  }
8071
- `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10945
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniDrawerHeaderComponent, selector: "[uni-drawer-header]", inputs: ["headline"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8072
10946
  }
8073
10947
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerComponent, decorators: [{
8074
10948
  type: Component,
8075
10949
  args: [{
8076
10950
  changeDetection: ChangeDetectionStrategy.OnPush,
8077
10951
  selector: 'uni-drawer',
8078
- imports: [NgTemplateOutlet],
8079
- providers: [{ provide: COMPONENT_NAME, useValue: 'drawer' }],
10952
+ imports: [NgTemplateOutlet, UniDrawerHeaderComponent],
10953
+ providers: [
10954
+ { provide: COMPONENT_NAME, useValue: 'drawer' },
10955
+ { provide: DRAWER_PANEL, useExisting: forwardRef(() => UniDrawerComponent) },
10956
+ ],
8080
10957
  template: `
8081
- <ng-template #content><ng-content /></ng-template>
10958
+ <!-- One <ng-content> per slot, each parked in a template so both modes can
10959
+ render the same projected nodes. The catch-all is declared last so the
10960
+ two selective slots claim their content first. -->
10961
+ <ng-template #header>
10962
+ @if (headline()) {
10963
+ <div uni-drawer-header></div>
10964
+ }
10965
+ <ng-content select="[uni-drawer-header]" />
10966
+ </ng-template>
10967
+ <ng-template #footer>
10968
+ <ng-content select="[uni-drawer-buttons], [drawer-buttons]" />
10969
+ </ng-template>
10970
+ <ng-template #body><ng-content /></ng-template>
10971
+
8082
10972
  @if (mode() === 'side') {
8083
10973
  <aside [class]="sideClass()" [attr.aria-hidden]="open() ? null : 'true'">
8084
- <ng-container [ngTemplateOutlet]="contentTemplate()" />
10974
+ <ng-container [ngTemplateOutlet]="headerTemplate()" />
10975
+ <div [class]="bodyClass()"><ng-container [ngTemplateOutlet]="bodyTemplate()" /></div>
10976
+ <ng-container [ngTemplateOutlet]="footerTemplate()" />
8085
10977
  </aside>
8086
10978
  } @else {
8087
10979
  <!-- Click handles the ::backdrop only (target check); keyboard closing
@@ -8091,17 +10983,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
8091
10983
  <dialog
8092
10984
  #overlay
8093
10985
  [class]="overClass()"
8094
- [attr.aria-label]="ariaLabel()"
10986
+ [attr.aria-labelledby]="labelledBy()"
10987
+ [attr.aria-label]="labelledBy() ? null : ariaLabel()"
8095
10988
  (click)="onBackdropClick($event)"
8096
10989
  (cancel)="onCancel($event)"
8097
10990
  (animationend)="onAnimationEnd($event)"
8098
10991
  >
8099
- <ng-container [ngTemplateOutlet]="contentTemplate()" />
10992
+ <ng-container [ngTemplateOutlet]="headerTemplate()" />
10993
+ <div [class]="bodyClass()"><ng-container [ngTemplateOutlet]="bodyTemplate()" /></div>
10994
+ <ng-container [ngTemplateOutlet]="footerTemplate()" />
8100
10995
  </dialog>
8101
10996
  }
8102
10997
  `,
8103
10998
  }]
8104
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], contentTemplate: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], overlay: [{ type: i0.ViewChild, args: ['overlay', { isSignal: true }] }] } });
10999
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], headline: [{ type: i0.Input, args: [{ isSignal: true, alias: "headline", required: false }] }], defaultCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCloseButton", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], scrim: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrim", required: false }] }], initialFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFocus", required: false }] }], closeRequest: [{ type: i0.Output, args: ["closeRequest"] }], disableAutoClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableAutoClose", required: false }] }], headerTemplate: [{ type: i0.ViewChild, args: ['header', { isSignal: true }] }], bodyTemplate: [{ type: i0.ViewChild, args: ['body', { isSignal: true }] }], footerTemplate: [{ type: i0.ViewChild, args: ['footer', { isSignal: true }] }], overlay: [{ type: i0.ViewChild, args: ['overlay', { isSignal: true }] }] } });
8105
11000
 
8106
11001
  class UniExpandComponent extends BaseComponent {
8107
11002
  collapsed = model(true, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
@@ -11745,5 +14640,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
11745
14640
  * Generated bundle index. Do not edit.
11746
14641
  */
11747
14642
 
11748
- export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, TRANSFORM_ORIGINS, ThemeService, UNI_FORMS, UNI_LAYOUT, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxDirective, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCalloutComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterDirective, UniCheckboxComponent, UniComboboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaDirective, UniGridDirective, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowDirective, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackDirective, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextDirective, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniTourComponent, UniWrapDirective, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, clearAnchorName, createAnnouncer, createListboxNavigation, dayOfWeek, daysInMonth, discreteOverlayTransition, focusableElements, formatDate, formatMonthHeading, formatTime, getFileExtension, inclusiveDayCount, isDivider, isToggleOpen, isValidDate, isoDate, joinDateTime, listboxPopupAttr, listboxPopupStyles, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeWeekStart, monthOf, motionSafe, newAnchorName, newListboxAnchor, parseDateText, parseTimeText, promoteListboxPopup, resolveElement, resolveFocusTarget, restoreOverlayFocus, setAnchorName, splitDateTime, spotlightStyles, supportsAnchoredPopup, timeSlots, todayIso, transformOriginFor, uniqueId, useTimer, visuallyHidden, weekdayNames };
14643
+ export { BodyRenderDirective, ConfirmationDialogComponent, DRAWER_PANEL, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, TRANSFORM_ORIGINS, ThemeService, UNI_FORMS, UNI_LAYOUT, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxDirective, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCalloutComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterDirective, UniCheckboxComponent, UniComboboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerButtonsComponent, UniDrawerComponent, UniDrawerHeaderComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaDirective, UniGridDirective, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniNumberInputComponent, UniNumberRangeInputComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniQuantityStepperComponent, UniRadioComponent, UniRecordDatasource, UniRowDirective, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackDirective, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextDirective, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniTourComponent, UniWrapDirective, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, clampDecimal, clearAnchorName, compareDecimal, createAnnouncer, createListboxNavigation, createPressRepeat, dayOfWeek, daysInMonth, decimalScale, discreteOverlayTransition, evaluateExpression, focusableElements, formatDate, formatMonthHeading, formatNumber, formatTime, fromScaled, getFileExtension, inclusiveDayCount, isCanonicalDecimal, isDivider, isToggleOpen, isValidDate, isoDate, joinDateTime, listboxPopupAttr, listboxPopupStyles, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeNumberParts, localeWeekStart, losesPrecision, monthOf, motionSafe, newAnchorName, newListboxAnchor, normalizeDecimal, parseDateText, parseNumber, parseTimeText, promoteListboxPopup, rawNumberText, resolveElement, resolveFocusTarget, resolveNumberFormat, restoreOverlayFocus, roundDecimal, setAnchorName, settleNumber, shiftDecimal, speakNumber, splitDateTime, spotlightStyles, stepDecimal, supportsAnchoredPopup, timeSlots, toAsciiDigits, toDecimal, toNumber, toScaled, todayIso, transformOriginFor, uniqueId, useTimer, visuallyHidden, weekdayNames };
11749
14644
  //# sourceMappingURL=uni-design-system-uni-angular.mjs.map