@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,
|
|
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: '
|
|
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 [
|
|
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
|
-
...
|
|
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 [
|
|
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 `[
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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
|
-
*
|
|
5473
|
-
*
|
|
5474
|
-
*
|
|
5475
|
-
*
|
|
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
|
|
5478
|
-
// ---
|
|
5479
|
-
value = model(
|
|
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
|
-
*
|
|
5488
|
-
*
|
|
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
|
-
|
|
5491
|
-
// ---
|
|
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
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
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
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
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
|
-
|
|
5504
|
-
|
|
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
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
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
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
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
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
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
|
-
'
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
//
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
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: "
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
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:
|
|
8161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniQuantityStepperComponent, decorators: [{
|
|
5581
8162
|
type: Component,
|
|
5582
|
-
args: [{
|
|
5583
|
-
|
|
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 [
|
|
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 `[
|
|
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 [
|
|
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
|
-
|
|
6229
|
-
|
|
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
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
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: "
|
|
8860
|
+
}, ...(ngDevMode ? [{ debugName: "transitions" }] : /* istanbul ignore next */ []));
|
|
6237
8861
|
toggleLabel = computed(() => {
|
|
6238
|
-
const {
|
|
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
|
|
6249
|
-
height
|
|
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:
|
|
8877
|
+
borderRadius: radius,
|
|
6254
8878
|
position: 'relative',
|
|
6255
|
-
|
|
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:
|
|
6261
|
-
height:
|
|
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:
|
|
6266
|
-
left:
|
|
6267
|
-
transition:
|
|
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 {
|
|
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:
|
|
6288
|
-
borderColor:
|
|
8910
|
+
backgroundColor: accent,
|
|
8911
|
+
borderColor: accent,
|
|
6289
8912
|
},
|
|
6290
8913
|
'&:checked + .toggle-switch .toggle-slider': {
|
|
6291
|
-
transform: `translateX(${
|
|
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(
|
|
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
|
-
*
|
|
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
|
-
*
|
|
7957
|
-
* `drawer
|
|
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
|
-
/**
|
|
7965
|
-
|
|
7966
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
10844
|
+
const width = this.panelWidth();
|
|
8010
10845
|
const start = this.position() === 'start';
|
|
8011
10846
|
return css({
|
|
8012
|
-
|
|
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.
|
|
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'
|
|
8025
|
-
: { width: 0, visibility: 'hidden',
|
|
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
|
-
|
|
8033
|
-
width:
|
|
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
|
-
|
|
8040
|
-
...this.theme.colorPair(options.color),
|
|
8041
|
-
...this.theme.padding(options.padding),
|
|
10872
|
+
...this.surface(),
|
|
8042
10873
|
...this.theme.boxShadow(options.elevation),
|
|
8043
|
-
|
|
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 }
|
|
8050
|
-
|
|
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]="
|
|
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-
|
|
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]="
|
|
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: [
|
|
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
|
|
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]="
|
|
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-
|
|
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]="
|
|
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 }] }],
|
|
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
|