@uni-design-system/uni-angular 5.1.0 → 5.2.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,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, Renderer2, ElementRef, Directive, model, output, effect, viewChild, afterNextRender, ViewChild, viewChildren } from '@angular/core';
2
+ import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, effect, viewChild, afterNextRender, ViewChild, viewChildren } from '@angular/core';
3
3
  import { injectGlobal, css, keyframes } from '@emotion/css';
4
- import { UniThemes, LightTheme, toTypefaces, createThemeFromPalette, Z_INDEX, fadeIn, fadeOut, expandFadeIn, collapseFadeOut, removeInputPlatformStyling } from '@uni-design-system/uni-core';
4
+ import { UniThemes, LightTheme, toTypefaces, createThemeFromPalette, Z_INDEX, fadeIn, fadeOut, expandFadeIn, collapseFadeOut, removeInputPlatformStyling, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
5
5
  import { NgClass, NgTemplateOutlet, CommonModule } from '@angular/common';
6
6
 
7
7
  let nextUniqueId = 0;
@@ -901,6 +901,283 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
901
901
  }]
902
902
  }], ctorParameters: () => [] });
903
903
 
904
+ const COMPONENT_NAME = new InjectionToken('');
905
+ class BaseComponent {
906
+ componentName = inject(COMPONENT_NAME);
907
+ theme = inject(ThemeService);
908
+ variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ [])); // TODO: Make Variant support undefined
909
+ size = input('lg', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
910
+ componentTheme = computed(() => this.theme.getComponentTheme(this.componentName)(), ...(ngDevMode ? [{ debugName: "componentTheme" }] : /* istanbul ignore next */ []));
911
+ componentOptions = computed(() => this.theme.getComponentOptions(this.componentName)(), ...(ngDevMode ? [{ debugName: "componentOptions" }] : /* istanbul ignore next */ []));
912
+ style = computed(() => this.theme.componentStyle(this.componentName, this.variant(), this.size())(), ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
913
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
914
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: BaseComponent, isStandalone: true, selector: "ng-component", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
915
+ }
916
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BaseComponent, decorators: [{
917
+ type: Component,
918
+ args: [{
919
+ changeDetection: ChangeDetectionStrategy.OnPush,
920
+ imports: [],
921
+ template: ``,
922
+ }]
923
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
924
+
925
+ /**
926
+ * Horizontal application bar: project leading controls (menu button, logo)
927
+ * with the `leading` attribute, actions with `trailing` (pushed to the far
928
+ * edge), and either pass `title` or project custom center content. Surface,
929
+ * divider, height, typography and spacing all resolve from `appBar` tokens.
930
+ * Place it inside your page's `<header>` landmark.
931
+ */
932
+ class UniAppBarComponent extends BaseComponent {
933
+ /** Bar title; alternatively project your own center content. */
934
+ title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
935
+ /** Stick the bar to the top of its scroll container. */
936
+ sticky = input(false, ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
937
+ className = computed(() => {
938
+ const options = this.componentOptions();
939
+ return css({
940
+ display: 'flex',
941
+ alignItems: 'center',
942
+ boxSizing: 'border-box',
943
+ width: '100%',
944
+ height: options.height ?? 56,
945
+ flex: 'none',
946
+ ...this.theme.colorPair(options.color),
947
+ ...this.theme.gap(options.gap),
948
+ ...this.theme.paddingLeft(options.padding),
949
+ ...this.theme.paddingRight(options.padding),
950
+ ...this.theme.borderBottom(options.divider),
951
+ ...this.theme.boxShadow(options.elevation),
952
+ '& .uni-app-bar-title': {
953
+ ...this.theme.typeface(options.typeface),
954
+ whiteSpace: 'nowrap',
955
+ },
956
+ '& .uni-app-bar-spacer': { flexGrow: 1 },
957
+ ...(this.sticky() && { position: 'sticky', top: 0, zIndex: 10 }),
958
+ });
959
+ }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
960
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniAppBarComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
961
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniAppBarComponent, isStandalone: true, selector: "uni-app-bar, AppBar", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'appBar' }], usesInheritance: true, ngImport: i0, template: `
962
+ <ng-content select="[leading]" />
963
+ @if (title()) {
964
+ <span class="uni-app-bar-title">{{ title() }}</span>
965
+ }
966
+ <ng-content />
967
+ <span class="uni-app-bar-spacer"></span>
968
+ <ng-content select="[trailing]" />
969
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
970
+ }
971
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniAppBarComponent, decorators: [{
972
+ type: Component,
973
+ args: [{
974
+ changeDetection: ChangeDetectionStrategy.OnPush,
975
+ selector: 'uni-app-bar, AppBar',
976
+ providers: [{ provide: COMPONENT_NAME, useValue: 'appBar' }],
977
+ host: { '[class]': 'className()' },
978
+ template: `
979
+ <ng-content select="[leading]" />
980
+ @if (title()) {
981
+ <span class="uni-app-bar-title">{{ title() }}</span>
982
+ }
983
+ <ng-content />
984
+ <span class="uni-app-bar-spacer"></span>
985
+ <ng-content select="[trailing]" />
986
+ `,
987
+ }]
988
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }] } });
989
+
990
+ class UniSymbolComponent extends BaseComponent {
991
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
992
+ fill = input(...(ngDevMode ? [undefined, { debugName: "fill" }] : /* istanbul ignore next */ []));
993
+ weight = input(...(ngDevMode ? [undefined, { debugName: "weight" }] : /* istanbul ignore next */ []));
994
+ grade = input(...(ngDevMode ? [undefined, { debugName: "grade" }] : /* istanbul ignore next */ []));
995
+ opticalSize = input(...(ngDevMode ? [undefined, { debugName: "opticalSize" }] : /* istanbul ignore next */ []));
996
+ className = computed(() => {
997
+ const settings = `'FILL' ${this.fill() || this.componentOptions().fill || 0}, ` +
998
+ `'wght' ${this.weight() || this.componentOptions().weight || 400}, ` +
999
+ `'GRAD' ${this.grade() || this.componentOptions().grade || 0}, ` +
1000
+ `'opsz' ${this.opticalSize() || this.componentOptions().opticalSize || 24}`;
1001
+ return ('material-symbols-rounded ' +
1002
+ css({
1003
+ fontVariationSettings: settings,
1004
+ fontSize: `${this.opticalSize() || this.componentOptions().opticalSize || 24}px`,
1005
+ }));
1006
+ }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1007
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSymbolComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1008
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniSymbolComponent, isStandalone: true, selector: "uni-symbol, Symbol", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, grade: { classPropertyName: "grade", publicName: "grade", isSignal: true, isRequired: false, transformFunction: null }, opticalSize: { classPropertyName: "opticalSize", publicName: "opticalSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'symbol' }], usesInheritance: true, ngImport: i0, template: `{{ name() }}`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1009
+ }
1010
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSymbolComponent, decorators: [{
1011
+ type: Component,
1012
+ args: [{
1013
+ selector: 'uni-symbol, Symbol',
1014
+ imports: [],
1015
+ template: `{{ name() }}`,
1016
+ providers: [{ provide: COMPONENT_NAME, useValue: 'symbol' }],
1017
+ // Icons are decorative: hide the ligature text (e.g. "chevron_left") from
1018
+ // screen readers. Meaningful icons get their name from the parent control.
1019
+ host: {
1020
+ '[class]': 'className()',
1021
+ 'aria-hidden': 'true',
1022
+ },
1023
+ changeDetection: ChangeDetectionStrategy.OnPush,
1024
+ }]
1025
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }], grade: [{ type: i0.Input, args: [{ isSignal: true, alias: "grade", required: false }] }], opticalSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "opticalSize", required: false }] }] } });
1026
+
1027
+ /**
1028
+ * Identity avatar with a graceful fallback chain: image → initials from
1029
+ * `name` → themed symbol. Coloring, radius, typeface and sizes all resolve
1030
+ * from `avatar` theme tokens; `variant` picks the container palette.
1031
+ */
1032
+ class UniAvatarComponent extends BaseComponent {
1033
+ /** Image URL; on load error the avatar falls back to initials/symbol. */
1034
+ src = input(...(ngDevMode ? [undefined, { debugName: "src" }] : /* istanbul ignore next */ []));
1035
+ /** Person's name: drives the initials and the accessible label. */
1036
+ name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
1037
+ /** Verbatim text instead of derived initials (e.g. a group's “+3”). */
1038
+ text = input(...(ngDevMode ? [undefined, { debugName: "text" }] : /* istanbul ignore next */ []));
1039
+ imageFailed = signal(false, ...(ngDevMode ? [{ debugName: "imageFailed" }] : /* istanbul ignore next */ []));
1040
+ showImage = computed(() => !!this.src() && !this.imageFailed(), ...(ngDevMode ? [{ debugName: "showImage" }] : /* istanbul ignore next */ []));
1041
+ initials = computed(() => {
1042
+ const name = this.name()?.trim();
1043
+ if (!name)
1044
+ return '';
1045
+ const words = name.split(/\s+/);
1046
+ const first = words[0]?.[0] ?? '';
1047
+ const last = words.length > 1 ? (words[words.length - 1][0] ?? '') : '';
1048
+ return (first + last).toUpperCase();
1049
+ }, ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
1050
+ label = computed(() => this.name() ?? this.text(), ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1051
+ className = computed(() => css([
1052
+ // Typeface (family/weight) before the merged theme style, so the size
1053
+ // record's fontSize wins over the typeface's.
1054
+ {
1055
+ ...this.theme.typeface(this.componentOptions().typeface),
1056
+ ...this.theme.radius(this.componentOptions().borderRadius),
1057
+ },
1058
+ this.style(),
1059
+ {
1060
+ display: 'inline-flex',
1061
+ alignItems: 'center',
1062
+ justifyContent: 'center',
1063
+ flex: 'none',
1064
+ overflow: 'hidden',
1065
+ userSelect: 'none',
1066
+ lineHeight: 1,
1067
+ '& img': { width: '100%', height: '100%', objectFit: 'cover' },
1068
+ '& symbol': { fontSize: '1.4em' },
1069
+ },
1070
+ ]), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1071
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniAvatarComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1072
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniAvatarComponent, isStandalone: true, selector: "uni-avatar, Avatar", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "attr.role": "label() ? 'img' : null", "attr.aria-label": "label() || null", "attr.aria-hidden": "label() ? null : 'true'" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'avatar' }], usesInheritance: true, ngImport: i0, template: `
1073
+ @if (showImage()) {
1074
+ <img [src]="src()" alt="" (error)="imageFailed.set(true)" />
1075
+ } @else if (text()) {
1076
+ <span>{{ text() }}</span>
1077
+ } @else if (initials()) {
1078
+ <span>{{ initials() }}</span>
1079
+ } @else {
1080
+ <Symbol [name]="componentOptions().fallbackSymbol ?? 'person'" />
1081
+ }
1082
+ `, isInline: true, dependencies: [{ kind: "component", type: UniSymbolComponent, selector: "uni-symbol, Symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1083
+ }
1084
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniAvatarComponent, decorators: [{
1085
+ type: Component,
1086
+ args: [{
1087
+ changeDetection: ChangeDetectionStrategy.OnPush,
1088
+ selector: 'uni-avatar, Avatar',
1089
+ imports: [UniSymbolComponent],
1090
+ providers: [{ provide: COMPONENT_NAME, useValue: 'avatar' }],
1091
+ host: {
1092
+ '[class]': 'className()',
1093
+ '[attr.role]': "label() ? 'img' : null",
1094
+ '[attr.aria-label]': 'label() || null',
1095
+ '[attr.aria-hidden]': "label() ? null : 'true'",
1096
+ },
1097
+ template: `
1098
+ @if (showImage()) {
1099
+ <img [src]="src()" alt="" (error)="imageFailed.set(true)" />
1100
+ } @else if (text()) {
1101
+ <span>{{ text() }}</span>
1102
+ } @else if (initials()) {
1103
+ <span>{{ initials() }}</span>
1104
+ } @else {
1105
+ <Symbol [name]="componentOptions().fallbackSymbol ?? 'person'" />
1106
+ }
1107
+ `,
1108
+ }]
1109
+ }], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }] } });
1110
+
1111
+ /**
1112
+ * Overlapping stack of avatars. `max` collapses the overflow into a themed
1113
+ * “+N” chip (itself a `uni-avatar`). Overlap and separator ring resolve from
1114
+ * `avatarGroup` theme tokens.
1115
+ */
1116
+ class UniAvatarGroupComponent extends BaseComponent {
1117
+ /** Show at most this many avatars; the rest collapse into a “+N” chip. */
1118
+ max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1119
+ avatars = contentChildren(UniAvatarComponent, ...(ngDevMode ? [{ debugName: "avatars" }] : /* istanbul ignore next */ []));
1120
+ surplus = computed(() => {
1121
+ const max = this.max();
1122
+ if (max === undefined)
1123
+ return 0;
1124
+ return Math.max(0, this.avatars().length - max);
1125
+ }, ...(ngDevMode ? [{ debugName: "surplus" }] : /* istanbul ignore next */ []));
1126
+ className = computed(() => {
1127
+ const options = this.componentOptions();
1128
+ const overlap = this.theme.spacing()[options.overlap ?? 'sm'] ?? '8px';
1129
+ const ring = `${options.ringWidth ?? 2}px solid ${this.theme.colors()[options.ringColor ?? 'surface'] ?? 'transparent'}`;
1130
+ const max = this.max();
1131
+ return css({
1132
+ display: 'inline-flex',
1133
+ alignItems: 'center',
1134
+ '& uni-avatar': {
1135
+ border: ring,
1136
+ marginLeft: `calc(-1 * ${overlap})`,
1137
+ '&:first-child': { marginLeft: 0 },
1138
+ },
1139
+ // Collapse the overflow: hide projected avatars past `max`, but never
1140
+ // the surplus chip this component renders itself.
1141
+ ...(max !== undefined && {
1142
+ [`& uni-avatar:nth-of-type(n + ${max + 1}):not(.uni-surplus)`]: { display: 'none' },
1143
+ }),
1144
+ });
1145
+ }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1146
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniAvatarGroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1147
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniAvatarGroupComponent, isStandalone: true, selector: "uni-avatar-group, AvatarGroup", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'avatarGroup' }], queries: [{ propertyName: "avatars", predicate: UniAvatarComponent, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1148
+ <ng-content />
1149
+ @if (surplus() > 0) {
1150
+ <uni-avatar
1151
+ class="uni-surplus"
1152
+ [text]="'+' + surplus()"
1153
+ variant="quaternary"
1154
+ [size]="size()"
1155
+ />
1156
+ }
1157
+ `, isInline: true, dependencies: [{ kind: "component", type: UniAvatarComponent, selector: "uni-avatar, Avatar", inputs: ["src", "name", "text"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1158
+ }
1159
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniAvatarGroupComponent, decorators: [{
1160
+ type: Component,
1161
+ args: [{
1162
+ changeDetection: ChangeDetectionStrategy.OnPush,
1163
+ selector: 'uni-avatar-group, AvatarGroup',
1164
+ imports: [UniAvatarComponent],
1165
+ providers: [{ provide: COMPONENT_NAME, useValue: 'avatarGroup' }],
1166
+ host: { '[class]': 'className()', role: 'group' },
1167
+ template: `
1168
+ <ng-content />
1169
+ @if (surplus() > 0) {
1170
+ <uni-avatar
1171
+ class="uni-surplus"
1172
+ [text]="'+' + surplus()"
1173
+ variant="quaternary"
1174
+ [size]="size()"
1175
+ />
1176
+ }
1177
+ `,
1178
+ }]
1179
+ }], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], avatars: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => UniAvatarComponent), { isSignal: true }] }] } });
1180
+
904
1181
  class UniBackgroundComponent {
905
1182
  theme = inject(ThemeService);
906
1183
  image = input(...(ngDevMode ? [undefined, { debugName: "image" }] : /* istanbul ignore next */ []));
@@ -941,27 +1218,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
941
1218
  * This file exports all public-facing elements of the background component.
942
1219
  */
943
1220
 
944
- const COMPONENT_NAME = new InjectionToken('');
945
- class BaseComponent {
946
- componentName = inject(COMPONENT_NAME);
947
- theme = inject(ThemeService);
948
- variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ [])); // TODO: Make Variant support undefined
949
- size = input('lg', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
950
- componentTheme = computed(() => this.theme.getComponentTheme(this.componentName)(), ...(ngDevMode ? [{ debugName: "componentTheme" }] : /* istanbul ignore next */ []));
951
- componentOptions = computed(() => this.theme.getComponentOptions(this.componentName)(), ...(ngDevMode ? [{ debugName: "componentOptions" }] : /* istanbul ignore next */ []));
952
- style = computed(() => this.theme.componentStyle(this.componentName, this.variant(), this.size())(), ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
953
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
954
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: BaseComponent, isStandalone: true, selector: "ng-component", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
955
- }
956
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BaseComponent, decorators: [{
957
- type: Component,
958
- args: [{
959
- changeDetection: ChangeDetectionStrategy.OnPush,
960
- imports: [],
961
- template: ``,
962
- }]
963
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
964
-
965
1221
  class UniBadgeComponent extends BaseComponent {
966
1222
  color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
967
1223
  useVariant = input(false, ...(ngDevMode ? [{ debugName: "useVariant" }] : /* istanbul ignore next */ []));
@@ -998,6 +1254,116 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
998
1254
  }]
999
1255
  }], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], useVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "useVariant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }] } });
1000
1256
 
1257
+ /**
1258
+ * WAI-ARIA breadcrumb: a `nav` landmark wrapping an ordered list; the last
1259
+ * item is the current page (`aria-current="page"`), separators are decorative.
1260
+ * Items with `href` render as real links; without one they render as
1261
+ * link-styled buttons emitting `itemClicked` — wire that to your router.
1262
+ */
1263
+ class UniBreadcrumbComponent extends BaseComponent {
1264
+ /** The trail, root first; the last item is the current page. */
1265
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1266
+ /** Landmark label distinguishing this nav from others on the page. */
1267
+ ariaLabel = input('Breadcrumb', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1268
+ /** Emits the clicked ancestor item (also fires for `href` links). */
1269
+ itemClicked = output();
1270
+ className = computed(() => {
1271
+ const options = this.componentOptions();
1272
+ const linkColor = this.theme.colors()[options.color ?? 'on-background-variant'];
1273
+ return css({
1274
+ '& ol': {
1275
+ display: 'flex',
1276
+ alignItems: 'center',
1277
+ flexWrap: 'wrap',
1278
+ listStyle: 'none',
1279
+ margin: 0,
1280
+ padding: 0,
1281
+ ...this.theme.typeface(options.typeface),
1282
+ },
1283
+ '& li': {
1284
+ display: 'inline-flex',
1285
+ alignItems: 'center',
1286
+ ...this.theme.gap(options.gap),
1287
+ },
1288
+ '& a, & button': {
1289
+ background: 'none',
1290
+ border: 0,
1291
+ padding: 0,
1292
+ font: 'inherit',
1293
+ cursor: 'pointer',
1294
+ textDecoration: 'none',
1295
+ color: linkColor,
1296
+ '&:hover': { textDecoration: 'underline' },
1297
+ '&:focus-visible': {
1298
+ outline: `2px solid ${this.theme.colors()['primary']}`,
1299
+ outlineOffset: 2,
1300
+ },
1301
+ },
1302
+ '& [aria-current]': {
1303
+ ...this.theme.color(options.currentColor),
1304
+ },
1305
+ '& symbol': { fontSize: '1.1em', ...this.theme.color(options.color) },
1306
+ ...this.theme.gap(options.gap),
1307
+ });
1308
+ }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1309
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniBreadcrumbComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1310
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniBreadcrumbComponent, isStandalone: true, selector: "uni-breadcrumb, Breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClicked: "itemClicked" }, providers: [{ provide: COMPONENT_NAME, useValue: 'breadcrumb' }], usesInheritance: true, ngImport: i0, template: `
1311
+ <nav [attr.aria-label]="ariaLabel()" [class]="className()">
1312
+ <ol>
1313
+ @for (item of items(); track item.label; let last = $last; let i = $index) {
1314
+ <li>
1315
+ @if (last) {
1316
+ <span aria-current="page">{{ item.label }}</span>
1317
+ } @else if (item.href) {
1318
+ <a [href]="item.href" (click)="itemClicked.emit(item)">{{ item.label }}</a>
1319
+ } @else {
1320
+ <button type="button" (click)="itemClicked.emit(item)">{{ item.label }}</button>
1321
+ }
1322
+ @if (!last) {
1323
+ <Symbol
1324
+ aria-hidden="true"
1325
+ [name]="componentOptions().separatorSymbol ?? 'chevron_right'"
1326
+ />
1327
+ }
1328
+ </li>
1329
+ }
1330
+ </ol>
1331
+ </nav>
1332
+ `, isInline: true, dependencies: [{ kind: "component", type: UniSymbolComponent, selector: "uni-symbol, Symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1333
+ }
1334
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniBreadcrumbComponent, decorators: [{
1335
+ type: Component,
1336
+ args: [{
1337
+ changeDetection: ChangeDetectionStrategy.OnPush,
1338
+ selector: 'uni-breadcrumb, Breadcrumb',
1339
+ imports: [UniSymbolComponent],
1340
+ providers: [{ provide: COMPONENT_NAME, useValue: 'breadcrumb' }],
1341
+ template: `
1342
+ <nav [attr.aria-label]="ariaLabel()" [class]="className()">
1343
+ <ol>
1344
+ @for (item of items(); track item.label; let last = $last; let i = $index) {
1345
+ <li>
1346
+ @if (last) {
1347
+ <span aria-current="page">{{ item.label }}</span>
1348
+ } @else if (item.href) {
1349
+ <a [href]="item.href" (click)="itemClicked.emit(item)">{{ item.label }}</a>
1350
+ } @else {
1351
+ <button type="button" (click)="itemClicked.emit(item)">{{ item.label }}</button>
1352
+ }
1353
+ @if (!last) {
1354
+ <Symbol
1355
+ aria-hidden="true"
1356
+ [name]="componentOptions().separatorSymbol ?? 'chevron_right'"
1357
+ />
1358
+ }
1359
+ </li>
1360
+ }
1361
+ </ol>
1362
+ </nav>
1363
+ `,
1364
+ }]
1365
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], itemClicked: [{ type: i0.Output, args: ["itemClicked"] }] } });
1366
+
1001
1367
  class UniIconComponent {
1002
1368
  themeService = inject(ThemeService);
1003
1369
  color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : /* istanbul ignore next */ []));
@@ -1289,43 +1655,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1289
1655
  }]
1290
1656
  }], ctorParameters: () => [], propDecorators: { display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], wrapItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapItems", required: false }] }] } });
1291
1657
 
1292
- class UniSymbolComponent extends BaseComponent {
1293
- name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
1294
- fill = input(...(ngDevMode ? [undefined, { debugName: "fill" }] : /* istanbul ignore next */ []));
1295
- weight = input(...(ngDevMode ? [undefined, { debugName: "weight" }] : /* istanbul ignore next */ []));
1296
- grade = input(...(ngDevMode ? [undefined, { debugName: "grade" }] : /* istanbul ignore next */ []));
1297
- opticalSize = input(...(ngDevMode ? [undefined, { debugName: "opticalSize" }] : /* istanbul ignore next */ []));
1298
- className = computed(() => {
1299
- const settings = `'FILL' ${this.fill() || this.componentOptions().fill || 0}, ` +
1300
- `'wght' ${this.weight() || this.componentOptions().weight || 400}, ` +
1301
- `'GRAD' ${this.grade() || this.componentOptions().grade || 0}, ` +
1302
- `'opsz' ${this.opticalSize() || this.componentOptions().opticalSize || 24}`;
1303
- return ('material-symbols-rounded ' +
1304
- css({
1305
- fontVariationSettings: settings,
1306
- fontSize: `${this.opticalSize() || this.componentOptions().opticalSize || 24}px`,
1307
- }));
1308
- }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1309
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSymbolComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1310
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniSymbolComponent, isStandalone: true, selector: "uni-symbol, Symbol", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, grade: { classPropertyName: "grade", publicName: "grade", isSignal: true, isRequired: false, transformFunction: null }, opticalSize: { classPropertyName: "opticalSize", publicName: "opticalSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'symbol' }], usesInheritance: true, ngImport: i0, template: `{{ name() }}`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1311
- }
1312
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSymbolComponent, decorators: [{
1313
- type: Component,
1314
- args: [{
1315
- selector: 'uni-symbol, Symbol',
1316
- imports: [],
1317
- template: `{{ name() }}`,
1318
- providers: [{ provide: COMPONENT_NAME, useValue: 'symbol' }],
1319
- // Icons are decorative: hide the ligature text (e.g. "chevron_left") from
1320
- // screen readers. Meaningful icons get their name from the parent control.
1321
- host: {
1322
- '[class]': 'className()',
1323
- 'aria-hidden': 'true',
1324
- },
1325
- changeDetection: ChangeDetectionStrategy.OnPush,
1326
- }]
1327
- }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }], grade: [{ type: i0.Input, args: [{ isSignal: true, alias: "grade", required: false }] }], opticalSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "opticalSize", required: false }] }] } });
1328
-
1329
1658
  // https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/
1330
1659
  class RippleDirective {
1331
1660
  renderer = inject(Renderer2);
@@ -1401,6 +1730,11 @@ class UniButtonComponent extends BaseComponent {
1401
1730
  paddingBottom: '6%',
1402
1731
  });
1403
1732
  className = computed(() => css([
1733
+ // Token-driven radius + typeface from component options. Applied before
1734
+ // `style()` so theme `sizes`/`fixed` (per-size fontSize, or hand-set
1735
+ // radii/families in older themes) keep winning.
1736
+ this.theme.radius(this.componentOptions().borderRadius),
1737
+ { ...this.theme.typeface(this.componentOptions().typeface) },
1404
1738
  this.style() && {
1405
1739
  ...this.style(), // TODO: Set priority on theme-defined styles
1406
1740
  },
@@ -1618,12 +1952,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1618
1952
  }], propDecorators: { typeface: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeface", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], nowrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "nowrap", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], ellipsis: [{ type: i0.Input, args: [{ isSignal: true, alias: "ellipsis", required: false }] }] } });
1619
1953
 
1620
1954
  class UniCardComponent extends BaseComponent {
1955
+ /**
1956
+ * Token-resolved frame (variant-named border primitive, radii, elevation)
1957
+ * under the merged theme style, so hand-authored fixed/variant styles from
1958
+ * older themes keep winning.
1959
+ */
1960
+ cardStyle = computed(() => ({
1961
+ ...this.theme.border(this.componentOptions().border ?? this.variant()),
1962
+ ...this.theme.radius(this.componentOptions().borderRadius),
1963
+ ...this.theme.boxShadow(this.componentOptions().elevation),
1964
+ ...this.style(),
1965
+ }), ...(ngDevMode ? [{ debugName: "cardStyle" }] : /* istanbul ignore next */ []));
1621
1966
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCardComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1622
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: UniCardComponent, isStandalone: true, selector: "uni-card, Card", providers: [{ provide: COMPONENT_NAME, useValue: 'card' }], usesInheritance: true, ngImport: i0, template: "<div [style]=\"style()\">\n <ng-content></ng-content>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
1967
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: UniCardComponent, isStandalone: true, selector: "uni-card, Card", providers: [{ provide: COMPONENT_NAME, useValue: 'card' }], usesInheritance: true, ngImport: i0, template: "<div [style]=\"cardStyle()\">\n <ng-content></ng-content>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
1623
1968
  }
1624
1969
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCardComponent, decorators: [{
1625
1970
  type: Component,
1626
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-card, Card', imports: [], providers: [{ provide: COMPONENT_NAME, useValue: 'card' }], template: "<div [style]=\"style()\">\n <ng-content></ng-content>\n</div>\n" }]
1971
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-card, Card', imports: [], providers: [{ provide: COMPONENT_NAME, useValue: 'card' }], template: "<div [style]=\"cardStyle()\">\n <ng-content></ng-content>\n</div>\n" }]
1627
1972
  }] });
1628
1973
 
1629
1974
  class UniCardHeaderComponent extends BaseComponent {
@@ -1868,7 +2213,9 @@ class UniIconButtonComponent {
1868
2213
  border: 0,
1869
2214
  cursor: 'pointer',
1870
2215
  transition: 'all 0.28s ease',
1871
- borderRadius: 999,
2216
+ // Token-driven radius (`max` = circle) with the legacy 999 fallback
2217
+ // for hand-authored themes that predate iconButton options.
2218
+ ...(this.theme.radius(this.config().options?.borderRadius) ?? { borderRadius: 999 }),
1872
2219
  display: 'block',
1873
2220
  '&:disabled': {
1874
2221
  cursor: 'not-allowed !important',
@@ -2652,6 +2999,158 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
2652
2999
  }]
2653
3000
  }], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], border: [{ type: i0.Input, args: [{ isSignal: true, alias: "border", required: false }] }] } });
2654
3001
 
3002
+ /**
3003
+ * Navigation drawer with two modes sharing one content slot:
3004
+ *
3005
+ * - `side` — an in-flow `<aside>` that pushes content (dashboard sidenav);
3006
+ * opening/closing animates its width, and the divider border primitive
3007
+ * separates it from the page.
3008
+ * - `over` — a native `<dialog>` in the top layer: focus trap, Escape and
3009
+ * scrim backdrop come from the platform (same machinery as `uni-dialog`),
3010
+ * sliding in from its edge.
3011
+ *
3012
+ * Surface, width, divider, elevation, padding and backdrop all resolve from
3013
+ * `drawer` theme tokens.
3014
+ */
3015
+ class UniDrawerComponent extends BaseComponent {
3016
+ /** Two-way bindable open state: [(open)]. */
3017
+ open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
3018
+ mode = input('side', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
3019
+ position = input('start', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
3020
+ /** Accessible name for the overlay mode's dialog. */
3021
+ ariaLabel = input('Navigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
3022
+ contentTemplate = viewChild.required('content');
3023
+ overlay = viewChild('overlay', ...(ngDevMode ? [{ debugName: "overlay" }] : /* istanbul ignore next */ []));
3024
+ constructor() {
3025
+ super();
3026
+ effect(() => {
3027
+ const dialog = this.overlay()?.nativeElement;
3028
+ if (!dialog)
3029
+ return;
3030
+ if (this.open()) {
3031
+ if (!dialog.open) {
3032
+ dialog.removeAttribute('closing');
3033
+ dialog.showModal();
3034
+ }
3035
+ }
3036
+ else if (dialog.open) {
3037
+ // Slide out first; the dialog closes when the animation reports done.
3038
+ dialog.setAttribute('closing', 'true');
3039
+ }
3040
+ });
3041
+ }
3042
+ onBackdropClick(event) {
3043
+ if (event.target.nodeName === 'DIALOG')
3044
+ this.open.set(false);
3045
+ }
3046
+ /** Route Escape through the animated close, keeping `open` in sync. */
3047
+ onCancel(event) {
3048
+ event.preventDefault();
3049
+ this.open.set(false);
3050
+ }
3051
+ onAnimationEnd(event) {
3052
+ const dialog = this.overlay()?.nativeElement;
3053
+ if (!dialog || !dialog.hasAttribute('closing'))
3054
+ return;
3055
+ if (event.animationName.includes(this.slideOut())) {
3056
+ dialog.close();
3057
+ dialog.removeAttribute('closing');
3058
+ }
3059
+ }
3060
+ edge = computed(() => (this.position() === 'start' ? '-100%' : '100%'), ...(ngDevMode ? [{ debugName: "edge" }] : /* istanbul ignore next */ []));
3061
+ slideIn = computed(() => keyframes({ from: { transform: `translateX(${this.edge()})` }, to: { transform: 'translateX(0)' } }), ...(ngDevMode ? [{ debugName: "slideIn" }] : /* istanbul ignore next */ []));
3062
+ slideOut = computed(() => keyframes({ from: { transform: 'translateX(0)' }, to: { transform: `translateX(${this.edge()})` } }), ...(ngDevMode ? [{ debugName: "slideOut" }] : /* istanbul ignore next */ []));
3063
+ sideClass = computed(() => {
3064
+ const options = this.componentOptions();
3065
+ const width = options.width ?? 280;
3066
+ const start = this.position() === 'start';
3067
+ return css({
3068
+ display: 'block',
3069
+ boxSizing: 'border-box',
3070
+ height: '100%',
3071
+ flex: 'none',
3072
+ overflowX: 'hidden',
3073
+ overflowY: 'auto',
3074
+ transition: 'width 0.25s ease, visibility 0.25s',
3075
+ ...this.theme.colorPair(options.color),
3076
+ ...(start
3077
+ ? this.theme.borderRight(options.divider)
3078
+ : this.theme.borderLeft(options.divider)),
3079
+ ...(this.open()
3080
+ ? { width, visibility: 'visible', ...this.theme.padding(options.padding) }
3081
+ : { width: 0, visibility: 'hidden', padding: 0, border: 'none' }),
3082
+ });
3083
+ }, ...(ngDevMode ? [{ debugName: "sideClass" }] : /* istanbul ignore next */ []));
3084
+ overClass = computed(() => {
3085
+ const options = this.componentOptions();
3086
+ const start = this.position() === 'start';
3087
+ return css({
3088
+ boxSizing: 'border-box',
3089
+ width: options.width ?? 280,
3090
+ maxWidth: '90vw',
3091
+ height: '100dvh',
3092
+ maxHeight: '100dvh',
3093
+ border: 'none',
3094
+ margin: start ? '0 auto 0 0' : '0 0 0 auto',
3095
+ overflowY: 'auto',
3096
+ ...this.theme.colorPair(options.color),
3097
+ ...this.theme.padding(options.padding),
3098
+ ...this.theme.boxShadow(options.elevation),
3099
+ '&::backdrop': { ...options.backdrop },
3100
+ '&[open]': { animation: `${this.slideIn()} 250ms ease-out` },
3101
+ '&[closing]': { animation: `${this.slideOut()} 250ms ease-in` },
3102
+ });
3103
+ }, ...(ngDevMode ? [{ debugName: "overClass" }] : /* istanbul ignore next */ []));
3104
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3105
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDrawerComponent, isStandalone: true, selector: "uni-drawer, Drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'drawer' }], viewQueries: [{ propertyName: "contentTemplate", first: true, predicate: ["content"], descendants: true, isSignal: true }, { propertyName: "overlay", first: true, predicate: ["overlay"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
3106
+ <ng-template #content><ng-content /></ng-template>
3107
+ @if (mode() === 'side') {
3108
+ <aside [class]="sideClass()" [attr.aria-hidden]="open() ? null : 'true'">
3109
+ <ng-container [ngTemplateOutlet]="contentTemplate()" />
3110
+ </aside>
3111
+ } @else {
3112
+ <dialog
3113
+ #overlay
3114
+ [class]="overClass()"
3115
+ [attr.aria-label]="ariaLabel()"
3116
+ (click)="onBackdropClick($event)"
3117
+ (cancel)="onCancel($event)"
3118
+ (animationend)="onAnimationEnd($event)"
3119
+ >
3120
+ <ng-container [ngTemplateOutlet]="contentTemplate()" />
3121
+ </dialog>
3122
+ }
3123
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3124
+ }
3125
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDrawerComponent, decorators: [{
3126
+ type: Component,
3127
+ args: [{
3128
+ changeDetection: ChangeDetectionStrategy.OnPush,
3129
+ selector: 'uni-drawer, Drawer',
3130
+ imports: [NgTemplateOutlet],
3131
+ providers: [{ provide: COMPONENT_NAME, useValue: 'drawer' }],
3132
+ template: `
3133
+ <ng-template #content><ng-content /></ng-template>
3134
+ @if (mode() === 'side') {
3135
+ <aside [class]="sideClass()" [attr.aria-hidden]="open() ? null : 'true'">
3136
+ <ng-container [ngTemplateOutlet]="contentTemplate()" />
3137
+ </aside>
3138
+ } @else {
3139
+ <dialog
3140
+ #overlay
3141
+ [class]="overClass()"
3142
+ [attr.aria-label]="ariaLabel()"
3143
+ (click)="onBackdropClick($event)"
3144
+ (cancel)="onCancel($event)"
3145
+ (animationend)="onAnimationEnd($event)"
3146
+ >
3147
+ <ng-container [ngTemplateOutlet]="contentTemplate()" />
3148
+ </dialog>
3149
+ }
3150
+ `,
3151
+ }]
3152
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], contentTemplate: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], overlay: [{ type: i0.ViewChild, args: ['overlay', { isSignal: true }] }] } });
3153
+
2655
3154
  class UniDropdownComponent extends BaseComponent {
2656
3155
  renderer = inject(Renderer2);
2657
3156
  delay = 100;
@@ -3387,6 +3886,8 @@ class UniInputBoxComponent extends BaseComponent {
3387
3886
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3388
3887
  error = input(false, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
3389
3888
  minWidth = input('0', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
3889
+ /** Override the themed field height, e.g. `'auto'` for multi-line fields. */
3890
+ height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
3390
3891
  color = computed(() => this.error() ? this.componentOptions().errorColor : this.componentOptions().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
3391
3892
  border = computed(() => this.error() ? this.componentOptions().errorBorder : this.componentOptions().border, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
3392
3893
  shadow = computed(() => this.error() ? this.componentOptions().errorShadow : this.componentOptions().shadow, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
@@ -3397,33 +3898,39 @@ class UniInputBoxComponent extends BaseComponent {
3397
3898
  cursor: 'not-allowed !important',
3398
3899
  },
3399
3900
  {
3400
- '& input, select': {
3901
+ '& input, select, textarea': {
3401
3902
  ...removeInputPlatformStyling,
3402
3903
  height: '100%',
3403
3904
  ...this.theme.paddingLeft(this.componentOptions().paddingLeft),
3404
3905
  ...this.theme.color(this.componentOptions().textColor),
3405
3906
  ...this.theme.typeface(this.componentOptions().typeFace),
3406
3907
  },
3407
- '&:has(input:disabled, select:disabled)': {
3908
+ // Multi-line fields size themselves (rows/resize), not from the box.
3909
+ '& textarea': {
3910
+ height: 'auto',
3911
+ ...this.theme.paddingTop('xs'),
3912
+ ...this.theme.paddingBottom('xs'),
3913
+ },
3914
+ '&:has(input:disabled, select:disabled, textarea:disabled)': {
3408
3915
  ...this.theme.color(this.componentOptions().disabledTextColor),
3409
3916
  ...this.theme.backgroundColor(this.componentOptions().disabledColor),
3410
3917
  },
3411
- '& input:disabled, select:disabled': {
3918
+ '& input:disabled, select:disabled, textarea:disabled': {
3412
3919
  cursor: 'not-allowed !important',
3413
3920
  },
3414
- '&:has(input:focus, select:focus)': {
3921
+ '&:has(input:focus, select:focus, textarea:focus)': {
3415
3922
  outline: this.componentOptions().focusOutline,
3416
3923
  outlineOffset: this.componentOptions().focusOutlineOffset,
3417
3924
  },
3418
3925
  },
3419
3926
  ]), ...(ngDevMode ? [{ debugName: "inputBoxClass" }] : /* istanbul ignore next */ []));
3420
3927
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3421
- 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 } }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"componentOptions().height\"\n [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "component", type: UniRowComponent, selector: "div[uni-row-layout], Row, div[row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3928
+ 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 } }, 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 [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "component", type: UniRowComponent, selector: "div[uni-row-layout], Row, div[row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3422
3929
  }
3423
3930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, decorators: [{
3424
3931
  type: Component,
3425
- args: [{ selector: 'uni-input-box', imports: [UniRowComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"componentOptions().height\"\n [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n" }]
3426
- }], 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 }] }] } });
3932
+ args: [{ selector: 'uni-input-box', imports: [UniRowComponent], 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 [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n" }]
3933
+ }], 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 }] }] } });
3427
3934
 
3428
3935
  class UniInputComponent {
3429
3936
  // --- REQUIRED SIGNALS (populated by FormValueControl) ---
@@ -3452,7 +3959,7 @@ class UniInputComponent {
3452
3959
  }
3453
3960
  inputClass = css({});
3454
3961
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3455
- 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 } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\">\n <ng-content select=\"pre-input\" />\n <input\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n (blur)=\"markAsTouched()\"\n [class]=\"inputClass\"\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\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3962
+ 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 } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\">\n <ng-content select=\"pre-input\" />\n <input\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n (blur)=\"markAsTouched()\"\n [class]=\"inputClass\"\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\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3456
3963
  }
3457
3964
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputComponent, decorators: [{
3458
3965
  type: Component,
@@ -3872,7 +4379,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
3872
4379
  });
3873
4380
  }
3874
4381
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3875
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown, MultiSelectDropdown", 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 } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], 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 <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 <Text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</Text\n >\n <Symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></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 <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 stack-layout gap=\"xs\" padding=\"xs\">\n @for (option of filteredOptions(); track option.value) {\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled()\"\n >\n </uni-checkbox>\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</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox, Checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "div[uni-box-layout], Box, div[box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown, Dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniStackComponent, selector: "div[uni-stack-layout], Stack, div[stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider, Divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], Button, button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniTextComponent, selector: "uni-text, Text", inputs: ["typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol, Symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniRowComponent, selector: "div[uni-row-layout], Row, div[row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4382
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown, MultiSelectDropdown", 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 } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], 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 <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 <Text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</Text\n >\n <Symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></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 <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 stack-layout gap=\"xs\" padding=\"xs\">\n @for (option of filteredOptions(); track option.value) {\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled()\"\n >\n </uni-checkbox>\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</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox, Checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "div[uni-box-layout], Box, div[box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown, Dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniStackComponent, selector: "div[uni-stack-layout], Stack, div[stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider, Divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], Button, button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniTextComponent, selector: "uni-text, Text", inputs: ["typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol, Symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniRowComponent, selector: "div[uni-row-layout], Row, div[row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3876
4383
  }
3877
4384
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, decorators: [{
3878
4385
  type: Component,
@@ -4723,13 +5230,475 @@ class UniSelectComponent {
4723
5230
  pointerEvents: 'none' /* Crucial for clicking through */,
4724
5231
  });
4725
5232
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4726
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select, SelectInput", 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 }, 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\">\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 <option [value]=\"i\">\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"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol, Symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5233
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select, SelectInput", 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 }, 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\">\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 <option [value]=\"i\">\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"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol, Symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4727
5234
  }
4728
5235
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, decorators: [{
4729
5236
  type: Component,
4730
5237
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-select, SelectInput', imports: [UniInputBoxComponent, UniSymbolComponent], template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\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 <option [value]=\"i\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n" }]
4731
5238
  }], 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 }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
4732
5239
 
5240
+ /**
5241
+ * Loading placeholder painted with surface tokens. `text` renders one or more
5242
+ * lines (the last line shortened, as real text would be), `rect` and `circle`
5243
+ * render fixed shapes. The shimmer only animates when the user allows motion;
5244
+ * it degrades to static blocks under `prefers-reduced-motion`.
5245
+ */
5246
+ class UniSkeletonComponent extends BaseComponent {
5247
+ shape = input('text', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
5248
+ /** CSS width; defaults to 100% for text/rect and `height` for circles. */
5249
+ width = input(undefined, ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
5250
+ /** CSS height per block; defaults to 1em (text), 96px (rect), 40px (circle). */
5251
+ height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
5252
+ /** Number of text lines (text shape only). */
5253
+ lines = input(1, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
5254
+ cssSize = (value) => typeof value === 'number' ? `${value}px` : value;
5255
+ resolvedHeight = computed(() => {
5256
+ const height = this.height();
5257
+ if (height !== undefined)
5258
+ return this.cssSize(height);
5259
+ return { text: '1em', rect: '96px', circle: '40px' }[this.shape()];
5260
+ }, ...(ngDevMode ? [{ debugName: "resolvedHeight" }] : /* istanbul ignore next */ []));
5261
+ lineWidths = computed(() => {
5262
+ const width = this.width() !== undefined ? this.cssSize(this.width()) : undefined;
5263
+ if (this.shape() === 'circle')
5264
+ return [width ?? this.resolvedHeight()];
5265
+ if (this.shape() === 'rect')
5266
+ return [width ?? '100%'];
5267
+ const lines = Math.max(1, this.lines());
5268
+ // Multi-line text blocks end on a short line, like real paragraphs do.
5269
+ return Array.from({ length: lines }, (_, i) => lines > 1 && i === lines - 1 ? (width ?? '60%') : (width ?? '100%'));
5270
+ }, ...(ngDevMode ? [{ debugName: "lineWidths" }] : /* istanbul ignore next */ []));
5271
+ sweep = keyframes({
5272
+ from: { backgroundPosition: '200% 0' },
5273
+ to: { backgroundPosition: '-200% 0' },
5274
+ });
5275
+ className = computed(() => {
5276
+ const options = this.componentOptions();
5277
+ const base = this.theme.colors()[options.color ?? 'surface-variant'];
5278
+ const highlight = this.theme.colors()[options.highlightColor ?? 'surface'];
5279
+ const animated = (options.animation ?? 'shimmer') === 'shimmer';
5280
+ return css({
5281
+ display: 'flex',
5282
+ flexDirection: 'column',
5283
+ ...this.theme.gap(options.gap),
5284
+ '& .uni-skeleton-block': {
5285
+ height: this.resolvedHeight(),
5286
+ backgroundColor: base,
5287
+ ...(this.shape() === 'circle'
5288
+ ? { borderRadius: '50%', flex: 'none' }
5289
+ : this.theme.radius(options.borderRadius)),
5290
+ ...(animated &&
5291
+ motionSafe({
5292
+ backgroundImage: `linear-gradient(90deg, ${base} 40%, ${highlight} 50%, ${base} 60%)`,
5293
+ backgroundSize: '200% 100%',
5294
+ animation: `${this.sweep} ${options.duration ?? 1.4}s ease-in-out infinite`,
5295
+ })),
5296
+ },
5297
+ });
5298
+ }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
5299
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSkeletonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5300
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSkeletonComponent, isStandalone: true, selector: "uni-skeleton, Skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'skeleton' }], usesInheritance: true, ngImport: i0, template: `
5301
+ @for (line of lineWidths(); track $index) {
5302
+ <div class="uni-skeleton-block" [style.width]="line"></div>
5303
+ }
5304
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
5305
+ }
5306
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSkeletonComponent, decorators: [{
5307
+ type: Component,
5308
+ args: [{
5309
+ changeDetection: ChangeDetectionStrategy.OnPush,
5310
+ selector: 'uni-skeleton, Skeleton',
5311
+ providers: [{ provide: COMPONENT_NAME, useValue: 'skeleton' }],
5312
+ host: { '[class]': 'className()', 'aria-hidden': 'true' },
5313
+ template: `
5314
+ @for (line of lineWidths(); track $index) {
5315
+ <div class="uni-skeleton-block" [style.width]="line"></div>
5316
+ }
5317
+ `,
5318
+ }]
5319
+ }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }] } });
5320
+
5321
+ /**
5322
+ * Range slider on a native `<input type="range">` — keyboard interaction and
5323
+ * the ARIA slider contract come from the platform. Fill, track, thumb and
5324
+ * radii resolve from `slider` theme tokens; the fill percentage rides a CSS
5325
+ * custom property so dragging never regenerates styles.
5326
+ */
5327
+ class UniSliderComponent extends BaseComponent {
5328
+ // --- REQUIRED SIGNALS (populated by FormValueControl) ---
5329
+ value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5330
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5331
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
5332
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
5333
+ dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
5334
+ /** Synced from required() validators by the Signal Forms [field] directive. */
5335
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
5336
+ /**
5337
+ * Id(s) of external element(s) describing this control — typically your
5338
+ * app-rendered value or error text — exposed as aria-describedby.
5339
+ */
5340
+ ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
5341
+ // --- CONFIGURATION ---
5342
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
5343
+ // `min`/`max` are part of the FormValueControl contract (synced from
5344
+ // min()/max() validators), so their type must admit undefined.
5345
+ min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
5346
+ max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
5347
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
5348
+ resolvedMin = computed(() => this.min() ?? 0, ...(ngDevMode ? [{ debugName: "resolvedMin" }] : /* istanbul ignore next */ []));
5349
+ resolvedMax = computed(() => this.max() ?? 100, ...(ngDevMode ? [{ debugName: "resolvedMax" }] : /* istanbul ignore next */ []));
5350
+ markAsTouched() {
5351
+ this.touched.set(true);
5352
+ }
5353
+ handleInput(event) {
5354
+ this.value.set(Number(event.target.value));
5355
+ }
5356
+ fillPercent = computed(() => {
5357
+ const min = this.resolvedMin();
5358
+ const range = this.resolvedMax() - min;
5359
+ if (range <= 0)
5360
+ return '0%';
5361
+ const ratio = (this.value() - min) / range;
5362
+ return `${Math.min(100, Math.max(0, ratio * 100))}%`;
5363
+ }, ...(ngDevMode ? [{ debugName: "fillPercent" }] : /* istanbul ignore next */ []));
5364
+ inputClass = computed(() => {
5365
+ const options = this.componentOptions();
5366
+ const fill = this.theme.colors()[options.color ?? 'primary'];
5367
+ const track = this.theme.colors()[options.trackColor ?? 'surface-variant'];
5368
+ const radius = this.theme.radii()[options.borderRadius ?? 'max'];
5369
+ const trackHeight = options.trackHeight ?? 4;
5370
+ const thumbSize = options.thumbSize ?? 16;
5371
+ // Webkit paints the fill via a gradient stopped at the custom property;
5372
+ // Firefox has a real ::-moz-range-progress.
5373
+ const trackFill = `linear-gradient(to right, ${fill} var(--uni-slider-fill), ${track} var(--uni-slider-fill))`;
5374
+ const thumb = {
5375
+ appearance: 'none',
5376
+ width: thumbSize,
5377
+ height: thumbSize,
5378
+ borderRadius: radius,
5379
+ border: 'none',
5380
+ background: fill,
5381
+ cursor: 'pointer',
5382
+ };
5383
+ return css({
5384
+ appearance: 'none',
5385
+ width: '100%',
5386
+ height: thumbSize,
5387
+ margin: 0,
5388
+ background: 'transparent',
5389
+ cursor: 'pointer',
5390
+ '&::-webkit-slider-runnable-track': {
5391
+ height: trackHeight,
5392
+ borderRadius: radius,
5393
+ background: trackFill,
5394
+ },
5395
+ '&::-webkit-slider-thumb': {
5396
+ ...thumb,
5397
+ marginTop: (trackHeight - thumbSize) / 2,
5398
+ },
5399
+ '&::-moz-range-track': { height: trackHeight, borderRadius: radius, background: track },
5400
+ '&::-moz-range-progress': { height: trackHeight, borderRadius: radius, background: fill },
5401
+ '&::-moz-range-thumb': thumb,
5402
+ '&:focus-visible': { outline: `2px solid ${fill}`, outlineOffset: 2 },
5403
+ '&:disabled': {
5404
+ cursor: 'not-allowed',
5405
+ opacity: 0.5,
5406
+ '&::-webkit-slider-thumb': { cursor: 'not-allowed' },
5407
+ '&::-moz-range-thumb': { cursor: 'not-allowed' },
5408
+ },
5409
+ });
5410
+ }, ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
5411
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSliderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5412
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.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 }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'slider' }], usesInheritance: true, ngImport: i0, template: `
5413
+ <input
5414
+ type="range"
5415
+ [class]="inputClass()"
5416
+ [style.--uni-slider-fill]="fillPercent()"
5417
+ [min]="resolvedMin()"
5418
+ [max]="resolvedMax()"
5419
+ [step]="step()"
5420
+ [value]="value()"
5421
+ [disabled]="disabled()"
5422
+ (input)="handleInput($event)"
5423
+ (blur)="markAsTouched()"
5424
+ [attr.aria-label]="label()"
5425
+ [attr.aria-describedby]="ariaDescribedBy() || null"
5426
+ />
5427
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
5428
+ }
5429
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSliderComponent, decorators: [{
5430
+ type: Component,
5431
+ args: [{
5432
+ changeDetection: ChangeDetectionStrategy.OnPush,
5433
+ selector: 'uni-slider, Slider',
5434
+ providers: [{ provide: COMPONENT_NAME, useValue: 'slider' }],
5435
+ template: `
5436
+ <input
5437
+ type="range"
5438
+ [class]="inputClass()"
5439
+ [style.--uni-slider-fill]="fillPercent()"
5440
+ [min]="resolvedMin()"
5441
+ [max]="resolvedMax()"
5442
+ [step]="step()"
5443
+ [value]="value()"
5444
+ [disabled]="disabled()"
5445
+ (input)="handleInput($event)"
5446
+ (blur)="markAsTouched()"
5447
+ [attr.aria-label]="label()"
5448
+ [attr.aria-describedby]="ariaDescribedBy() || null"
5449
+ />
5450
+ `,
5451
+ }]
5452
+ }], 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 }] }] } });
5453
+
5454
+ /**
5455
+ * One tab inside `uni-tabs`: a label for the tablist plus projected panel
5456
+ * content. Content is captured in a template and only instantiated while the
5457
+ * tab is selected, so inactive panels cost nothing.
5458
+ */
5459
+ class UniTabComponent {
5460
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
5461
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5462
+ /** Stable ids wiring tab ↔ panel ARIA relationships. */
5463
+ id = uniqueId('uni-tab');
5464
+ panelId = `${this.id}-panel`;
5465
+ content = viewChild.required('content');
5466
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5467
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniTabComponent, isStandalone: true, selector: "uni-tab, Tab", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template #content><ng-content /></ng-template>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
5468
+ }
5469
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTabComponent, decorators: [{
5470
+ type: Component,
5471
+ args: [{
5472
+ changeDetection: ChangeDetectionStrategy.OnPush,
5473
+ selector: 'uni-tab, Tab',
5474
+ template: `<ng-template #content><ng-content /></ng-template>`,
5475
+ }]
5476
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
5477
+
5478
+ /**
5479
+ * WAI-ARIA tabs: roving tabindex, automatic activation (arrow keys move focus
5480
+ * and select), Home/End, disabled tabs skipped. Only the selected panel is
5481
+ * instantiated. All styling resolves from `tabs` theme option tokens.
5482
+ */
5483
+ class UniTabsComponent extends BaseComponent {
5484
+ /** Index of the selected tab; two-way bindable. */
5485
+ selectedIndex = model(0, ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
5486
+ tabs = contentChildren(UniTabComponent, ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
5487
+ tabButtons = viewChildren('tabButton', ...(ngDevMode ? [{ debugName: "tabButtons" }] : /* istanbul ignore next */ []));
5488
+ /** The selection, snapped to the nearest enabled tab. */
5489
+ activeIndex = computed(() => {
5490
+ const tabs = this.tabs();
5491
+ const wanted = this.selectedIndex();
5492
+ if (tabs[wanted] && !tabs[wanted].disabled())
5493
+ return wanted;
5494
+ const firstEnabled = tabs.findIndex((tab) => !tab.disabled());
5495
+ return firstEnabled === -1 ? 0 : firstEnabled;
5496
+ }, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
5497
+ activeTab = computed(() => this.tabs()[this.activeIndex()], ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
5498
+ select(index) {
5499
+ if (this.tabs()[index]?.disabled())
5500
+ return;
5501
+ this.selectedIndex.set(index);
5502
+ }
5503
+ onKeydown(event) {
5504
+ const enabled = this.tabs()
5505
+ .map((tab, index) => ({ tab, index }))
5506
+ .filter(({ tab }) => !tab.disabled())
5507
+ .map(({ index }) => index);
5508
+ if (enabled.length === 0)
5509
+ return;
5510
+ const position = enabled.indexOf(this.activeIndex());
5511
+ let next;
5512
+ switch (event.key) {
5513
+ case 'ArrowRight':
5514
+ next = enabled[(position + 1) % enabled.length];
5515
+ break;
5516
+ case 'ArrowLeft':
5517
+ next = enabled[(position - 1 + enabled.length) % enabled.length];
5518
+ break;
5519
+ case 'Home':
5520
+ next = enabled[0];
5521
+ break;
5522
+ case 'End':
5523
+ next = enabled[enabled.length - 1];
5524
+ break;
5525
+ default:
5526
+ return;
5527
+ }
5528
+ event.preventDefault();
5529
+ this.select(next);
5530
+ this.tabButtons()[next]?.nativeElement.focus();
5531
+ }
5532
+ tablistClass = computed(() => {
5533
+ const options = this.componentOptions();
5534
+ return css({
5535
+ display: 'flex',
5536
+ alignItems: 'stretch',
5537
+ ...this.theme.gap(options.gap),
5538
+ ...this.theme.borderBottom(options.divider),
5539
+ });
5540
+ }, ...(ngDevMode ? [{ debugName: "tablistClass" }] : /* istanbul ignore next */ []));
5541
+ tabClass = computed(() => {
5542
+ const options = this.componentOptions();
5543
+ const thickness = options.indicatorThickness
5544
+ ? this.theme.getThickness(options.indicatorThickness)
5545
+ : 2;
5546
+ return css({
5547
+ border: 0,
5548
+ background: 'transparent',
5549
+ cursor: 'pointer',
5550
+ // The indicator overlays the divider: reserve its thickness on every
5551
+ // tab so labels don't shift when selection moves.
5552
+ borderBottom: `${thickness}px solid transparent`,
5553
+ marginBottom: -1,
5554
+ ...this.theme.typeface(options.typeface),
5555
+ ...this.theme.color(options.textColor),
5556
+ ...this.theme.paddingLeft(options.padding),
5557
+ ...this.theme.paddingRight(options.padding),
5558
+ ...this.theme.paddingTop('sm'),
5559
+ ...this.theme.paddingBottom('sm'),
5560
+ ...this.theme.radius(options.borderRadius),
5561
+ ...motionSafe({ transition: 'color 0.15s ease, border-color 0.15s ease' }),
5562
+ '&.active': {
5563
+ ...this.theme.color(options.activeTextColor),
5564
+ ...this.theme.backgroundColor(options.activeColor),
5565
+ borderBottomColor: this.theme.colors()[options.indicatorColor ?? 'primary'],
5566
+ },
5567
+ '&:disabled': {
5568
+ ...this.theme.color('on-disabled'),
5569
+ cursor: 'not-allowed',
5570
+ },
5571
+ '&:focus-visible': {
5572
+ outline: `2px solid ${this.theme.colors()[options.indicatorColor ?? 'primary']}`,
5573
+ outlineOffset: -2,
5574
+ },
5575
+ });
5576
+ }, ...(ngDevMode ? [{ debugName: "tabClass" }] : /* istanbul ignore next */ []));
5577
+ panelClass = computed(() => css({
5578
+ ...this.theme.paddingTop('md'),
5579
+ '&:focus-visible': {
5580
+ outline: `2px solid ${this.theme.colors()[this.componentOptions().indicatorColor ?? 'primary']}`,
5581
+ outlineOffset: 2,
5582
+ },
5583
+ }), ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
5584
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTabsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5585
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTabsComponent, isStandalone: true, selector: "uni-tabs, Tabs", inputs: { selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndex: "selectedIndexChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'tabs' }], queries: [{ propertyName: "tabs", predicate: UniTabComponent, isSignal: true }], viewQueries: [{ propertyName: "tabButtons", predicate: ["tabButton"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
5586
+ <div role="tablist" [class]="tablistClass()" (keydown)="onKeydown($event)">
5587
+ @for (tab of tabs(); track tab.id; let i = $index) {
5588
+ <button
5589
+ #tabButton
5590
+ type="button"
5591
+ role="tab"
5592
+ [id]="tab.id"
5593
+ [class]="tabClass()"
5594
+ [attr.aria-selected]="i === activeIndex()"
5595
+ [attr.aria-controls]="tab.panelId"
5596
+ [attr.tabindex]="i === activeIndex() ? 0 : -1"
5597
+ [disabled]="tab.disabled()"
5598
+ [class.active]="i === activeIndex()"
5599
+ (click)="select(i)"
5600
+ >
5601
+ {{ tab.label() }}
5602
+ </button>
5603
+ }
5604
+ </div>
5605
+ @if (activeTab(); as tab) {
5606
+ <div
5607
+ role="tabpanel"
5608
+ [id]="tab.panelId"
5609
+ [attr.aria-labelledby]="tab.id"
5610
+ tabindex="0"
5611
+ [class]="panelClass()"
5612
+ >
5613
+ <ng-container [ngTemplateOutlet]="tab.content()" />
5614
+ </div>
5615
+ }
5616
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5617
+ }
5618
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTabsComponent, decorators: [{
5619
+ type: Component,
5620
+ args: [{
5621
+ changeDetection: ChangeDetectionStrategy.OnPush,
5622
+ selector: 'uni-tabs, Tabs',
5623
+ imports: [NgTemplateOutlet],
5624
+ providers: [{ provide: COMPONENT_NAME, useValue: 'tabs' }],
5625
+ template: `
5626
+ <div role="tablist" [class]="tablistClass()" (keydown)="onKeydown($event)">
5627
+ @for (tab of tabs(); track tab.id; let i = $index) {
5628
+ <button
5629
+ #tabButton
5630
+ type="button"
5631
+ role="tab"
5632
+ [id]="tab.id"
5633
+ [class]="tabClass()"
5634
+ [attr.aria-selected]="i === activeIndex()"
5635
+ [attr.aria-controls]="tab.panelId"
5636
+ [attr.tabindex]="i === activeIndex() ? 0 : -1"
5637
+ [disabled]="tab.disabled()"
5638
+ [class.active]="i === activeIndex()"
5639
+ (click)="select(i)"
5640
+ >
5641
+ {{ tab.label() }}
5642
+ </button>
5643
+ }
5644
+ </div>
5645
+ @if (activeTab(); as tab) {
5646
+ <div
5647
+ role="tabpanel"
5648
+ [id]="tab.panelId"
5649
+ [attr.aria-labelledby]="tab.id"
5650
+ tabindex="0"
5651
+ [class]="panelClass()"
5652
+ >
5653
+ <ng-container [ngTemplateOutlet]="tab.content()" />
5654
+ </div>
5655
+ }
5656
+ `,
5657
+ }]
5658
+ }], propDecorators: { selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }, { type: i0.Output, args: ["selectedIndexChange"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => UniTabComponent), { isSignal: true }] }], tabButtons: [{ type: i0.ViewChildren, args: ['tabButton', { isSignal: true }] }] } });
5659
+
5660
+ class UniTextareaComponent {
5661
+ theme = inject(ThemeService);
5662
+ // --- REQUIRED SIGNALS (populated by FormValueControl) ---
5663
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5664
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5665
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
5666
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
5667
+ dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
5668
+ /** Synced from required() validators by the Signal Forms [field] directive. */
5669
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
5670
+ /**
5671
+ * Id(s) of external element(s) describing this control — typically your
5672
+ * app-rendered error message — exposed as aria-describedby.
5673
+ */
5674
+ ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
5675
+ // --- CONFIGURATION ---
5676
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
5677
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
5678
+ /** Visible text rows. Defaults to the theme's `textarea` options. */
5679
+ rows = input(undefined, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
5680
+ options = this.theme.getComponentOptions('textarea');
5681
+ resolvedRows = computed(() => this.rows() ?? this.options().rows ?? 3, ...(ngDevMode ? [{ debugName: "resolvedRows" }] : /* istanbul ignore next */ []));
5682
+ // Only show errors if the user has actually interacted with the field
5683
+ showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
5684
+ textareaClass = computed(() => css({
5685
+ width: '100%',
5686
+ resize: this.options().resize ?? 'vertical',
5687
+ }), ...(ngDevMode ? [{ debugName: "textareaClass" }] : /* istanbul ignore next */ []));
5688
+ markAsTouched() {
5689
+ this.touched.set(true);
5690
+ }
5691
+ handleInput(event) {
5692
+ this.value.set(event.target.value);
5693
+ }
5694
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5695
+ 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 } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" height=\"auto\">\n <textarea\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n (blur)=\"markAsTouched()\"\n [rows]=\"resolvedRows()\"\n [class]=\"textareaClass()\"\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5696
+ }
5697
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTextareaComponent, decorators: [{
5698
+ type: Component,
5699
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-textarea', imports: [UniInputBoxComponent], template: "<uni-input-box [error]=\"showError()\" height=\"auto\">\n <textarea\n [value]=\"value()\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n (blur)=\"markAsTouched()\"\n [rows]=\"resolvedRows()\"\n [class]=\"textareaClass()\"\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" }]
5700
+ }], 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 }] }] } });
5701
+
4733
5702
  /**
4734
5703
  * UniSortHeaderComponent Barrel File
4735
5704
  *
@@ -4769,13 +5738,14 @@ const SCHEMES = [
4769
5738
  'triadic',
4770
5739
  ];
4771
5740
  const CATEGORIES = ['jewel', 'pastel', 'earth', 'neutral', 'florescent', 'shades'];
5741
+ const SHAPES = ['sharp', 'modern', 'playful'];
4772
5742
  const VARIANTS = ['primary', 'secondary', 'tertiary', 'warn', 'ghost'];
4773
- // Representative tokens to show as swatches the brand-derived spine of the theme.
5743
+ // Representative tokens shown as swatches in each mode panel.
4774
5744
  const SWATCHES = [
4775
5745
  { label: 'primary', token: 'primary', on: 'on-primary' },
4776
5746
  { label: 'secondary', token: 'secondary', on: 'on-secondary' },
4777
5747
  { label: 'tertiary', token: 'tertiary', on: 'on-tertiary' },
4778
- { label: 'quaternary', token: 'quaternary', on: 'on-quaternary' },
5748
+ { label: 'container', token: 'primary-container', on: 'on-primary-container' },
4779
5749
  { label: 'error', token: 'error', on: 'on-error' },
4780
5750
  { label: 'warn', token: 'warn', on: 'on-warn' },
4781
5751
  { label: 'success', token: 'success', on: 'on-success' },
@@ -4842,6 +5812,7 @@ class UniThemeBuilderComponent {
4842
5812
  theme = inject(ThemeService);
4843
5813
  schemes = SCHEMES;
4844
5814
  categories = CATEGORIES;
5815
+ shapes = SHAPES;
4845
5816
  variants = VARIANTS;
4846
5817
  sizes = ['sm', 'md', 'lg', 'xl'];
4847
5818
  swatches = SWATCHES;
@@ -4849,6 +5820,7 @@ class UniThemeBuilderComponent {
4849
5820
  seed = signal('#4F46E5', ...(ngDevMode ? [{ debugName: "seed" }] : /* istanbul ignore next */ []));
4850
5821
  scheme = signal('triadic', ...(ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []));
4851
5822
  category = signal('neutral', ...(ngDevMode ? [{ debugName: "category" }] : /* istanbul ignore next */ []));
5823
+ shape = signal('modern', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
4852
5824
  floor = signal(18, ...(ngDevMode ? [{ debugName: "floor" }] : /* istanbul ignore next */ []));
4853
5825
  mode = signal('light', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
4854
5826
  pinPrimary = signal(false, ...(ngDevMode ? [{ debugName: "pinPrimary" }] : /* istanbul ignore next */ []));
@@ -4857,6 +5829,8 @@ class UniThemeBuilderComponent {
4857
5829
  secondaryHex = signal('#D4A373', ...(ngDevMode ? [{ debugName: "secondaryHex" }] : /* istanbul ignore next */ []));
4858
5830
  /** Soft brand targets carried by the active preset (cleared on seed edits). */
4859
5831
  targets = signal(undefined, ...(ngDevMode ? [{ debugName: "targets" }] : /* istanbul ignore next */ []));
5832
+ copied = signal(null, ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
5833
+ showAllPairs = signal(false, ...(ngDevMode ? [{ debugName: "showAllPairs" }] : /* istanbul ignore next */ []));
4860
5834
  constructor() {
4861
5835
  // Seed the controls from an already-active custom palette, if any.
4862
5836
  const existing = this.theme.customPalette();
@@ -4883,19 +5857,88 @@ class UniThemeBuilderComponent {
4883
5857
  category: this.category(),
4884
5858
  mode: this.mode(),
4885
5859
  accentSaturationFloor: this.floor(),
5860
+ radii: ShapeRadii[this.shape()],
4886
5861
  ...(this.targets() ? { targets: this.targets() } : {}),
4887
5862
  brand: {
4888
5863
  ...(this.pinPrimary() ? { primary: this.primaryHex() } : {}),
4889
5864
  ...(this.pinSecondary() ? { secondary: this.secondaryHex() } : {}),
4890
5865
  },
4891
5866
  }), ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
4892
- snippet = computed(() => JSON.stringify(this.config()), ...(ngDevMode ? [{ debugName: "snippet" }] : /* istanbul ignore next */ []));
5867
+ /**
5868
+ * Both modes generated on every input change (< 15 ms each), independent of
5869
+ * the storybook-wide theme — the side-by-side preview and the contrast
5870
+ * report always show the full light+dark picture.
5871
+ */
5872
+ palettes = computed(() => {
5873
+ const base = { ...this.config() };
5874
+ const checks = [];
5875
+ const light = generatePalette({ ...base, mode: 'light', checks });
5876
+ const dark = generatePalette({ ...base, mode: 'dark', checks });
5877
+ return { light, dark, checks };
5878
+ }, ...(ngDevMode ? [{ debugName: "palettes" }] : /* istanbul ignore next */ []));
5879
+ modePanels = computed(() => [
5880
+ { label: 'light', colors: this.palettes().light },
5881
+ { label: 'dark', colors: this.palettes().dark },
5882
+ ], ...(ngDevMode ? [{ debugName: "modePanels" }] : /* istanbul ignore next */ []));
5883
+ report = computed(() => {
5884
+ const { checks } = this.palettes();
5885
+ return {
5886
+ checks,
5887
+ failing: checks.filter((c) => !c.pass),
5888
+ worst: checks.reduce((worst, c) => Math.min(worst, c.ratio), 21),
5889
+ };
5890
+ }, ...(ngDevMode ? [{ debugName: "report" }] : /* istanbul ignore next */ []));
5891
+ visibleChecks = computed(() => this.showAllPairs() ? this.report().checks : this.report().failing, ...(ngDevMode ? [{ debugName: "visibleChecks" }] : /* istanbul ignore next */ []));
5892
+ snippet = computed(() => {
5893
+ const { radii: _radii, ...rest } = this.config();
5894
+ return JSON.stringify(rest);
5895
+ }, ...(ngDevMode ? [{ debugName: "snippet" }] : /* istanbul ignore next */ []));
5896
+ /** The builder state expressed as engine input — drives all three exports. */
5897
+ generationInput = computed(() => {
5898
+ const targets = this.targets();
5899
+ const seeds = [this.pinPrimary() ? this.primaryHex() : (targets?.primary ?? this.seed())];
5900
+ const secondary = this.pinSecondary() ? this.secondaryHex() : targets?.secondary;
5901
+ if (secondary)
5902
+ seeds.push(secondary);
5903
+ if (targets?.tertiary)
5904
+ seeds.push(targets.tertiary);
5905
+ return {
5906
+ seed: seeds.length === 1 ? seeds[0] : seeds,
5907
+ scheme: this.scheme(),
5908
+ vibe: this.category(),
5909
+ shape: this.shape(),
5910
+ name: 'Brand',
5911
+ };
5912
+ }, ...(ngDevMode ? [{ debugName: "generationInput" }] : /* istanbul ignore next */ []));
4893
5913
  color(token) {
4894
5914
  return this.theme.colors()[token] ?? 'transparent';
4895
5915
  }
4896
5916
  apply() {
4897
5917
  this.theme.applyPalette(this.config());
4898
5918
  }
5919
+ copyToClipboard(kind, text) {
5920
+ void navigator.clipboard?.writeText(text).then(() => {
5921
+ this.copied.set(kind);
5922
+ setTimeout(() => this.copied.set(null), 1600);
5923
+ });
5924
+ }
5925
+ copyThemeFile() {
5926
+ this.copyToClipboard('file', emitThemeFile(this.generationInput()).content);
5927
+ }
5928
+ copyNgAdd() {
5929
+ const input = this.generationInput();
5930
+ const seeds = Array.isArray(input.seed) ? input.seed.join(',') : input.seed;
5931
+ this.copyToClipboard('ngadd', `ng add @uni-design-system/uni-angular --brand=${seeds} --shape=${input.shape}`);
5932
+ }
5933
+ copyDtcg() {
5934
+ const { light, dark } = this.palettes();
5935
+ const radii = ShapeRadii[this.shape()];
5936
+ const spacing = this.theme.spacing();
5937
+ this.copyToClipboard('dtcg', JSON.stringify({
5938
+ light: emitDtcgTokens({ colors: light, radii, spacing }),
5939
+ dark: emitDtcgTokens({ colors: dark, radii, spacing }),
5940
+ }, null, 2));
5941
+ }
4899
5942
  setSeed(v) {
4900
5943
  if (/^#?[0-9a-fA-F]{6}$/.test(v.replace('#', ''))) {
4901
5944
  this.seed.set(v.startsWith('#') ? v : `#${v}`);
@@ -4912,6 +5955,10 @@ class UniThemeBuilderComponent {
4912
5955
  this.category.set(v);
4913
5956
  this.apply();
4914
5957
  }
5958
+ setShape(v) {
5959
+ this.shape.set(v);
5960
+ this.apply();
5961
+ }
4915
5962
  setFloor(v) {
4916
5963
  this.floor.set(Number(v));
4917
5964
  this.apply();
@@ -4957,6 +6004,7 @@ class UniThemeBuilderComponent {
4957
6004
  this.seed.set('#4F46E5');
4958
6005
  this.scheme.set('triadic');
4959
6006
  this.category.set('neutral');
6007
+ this.shape.set('modern');
4960
6008
  this.floor.set(18);
4961
6009
  this.mode.set('light');
4962
6010
  this.targets.set(undefined);
@@ -5055,6 +6103,16 @@ class UniThemeBuilderComponent {
5055
6103
  color: this.c('on-primary'),
5056
6104
  borderColor: this.c('primary'),
5057
6105
  },
6106
+ '& .tb-exports': { display: 'flex', flexDirection: 'column', gap: 6 },
6107
+ '& .tb-exports button': {
6108
+ height: 32,
6109
+ borderRadius: 8,
6110
+ cursor: 'pointer',
6111
+ fontSize: 12.5,
6112
+ border: `1px solid ${this.c('primary')}`,
6113
+ background: this.c('primary-surface'),
6114
+ color: this.c('on-primary-surface'),
6115
+ },
5058
6116
  '& .tb-pin.dim input[type=color], & .tb-pin.dim input[type=text]': { opacity: 0.45 },
5059
6117
  '& .tb-pin em': {
5060
6118
  fontStyle: 'normal',
@@ -5087,34 +6145,86 @@ class UniThemeBuilderComponent {
5087
6145
  display: 'flex',
5088
6146
  flexDirection: 'column',
5089
6147
  gap: 10,
5090
- '& .tb-swatches': {
6148
+ '& .tb-modes': {
5091
6149
  display: 'grid',
5092
- gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
5093
- gap: 10,
6150
+ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
6151
+ gap: 14,
5094
6152
  marginBottom: 20,
5095
6153
  },
6154
+ '& .tb-panel': {
6155
+ border: '1px solid',
6156
+ borderRadius: 14,
6157
+ padding: 14,
6158
+ display: 'flex',
6159
+ flexDirection: 'column',
6160
+ gap: 12,
6161
+ },
6162
+ '& .tb-panel-tag': {
6163
+ fontSize: 10.5,
6164
+ letterSpacing: '0.12em',
6165
+ textTransform: 'uppercase',
6166
+ },
6167
+ '& .tb-mode-swatches': {
6168
+ display: 'grid',
6169
+ gridTemplateColumns: 'repeat(auto-fill, minmax(110px, 1fr))',
6170
+ gap: 8,
6171
+ },
5096
6172
  '& .tb-swatch': {
5097
- borderRadius: 12,
5098
- padding: '16px 14px',
5099
- minHeight: 74,
6173
+ borderRadius: 10,
6174
+ padding: '12px 12px',
6175
+ minHeight: 60,
5100
6176
  display: 'flex',
5101
6177
  flexDirection: 'column',
5102
6178
  justifyContent: 'space-between',
5103
6179
  },
5104
- '& .tb-swatch span': { fontSize: 13, fontWeight: 600 },
5105
- '& .tb-swatch code': { fontFamily: 'monospace', fontSize: 11, opacity: 0.85 },
6180
+ '& .tb-swatch span': { fontSize: 12.5, fontWeight: 600 },
6181
+ '& .tb-swatch code': { fontFamily: 'monospace', fontSize: 10.5, opacity: 0.85 },
6182
+ '& .tb-mini-card': { borderRadius: 10, overflow: 'hidden', border: '1px solid' },
6183
+ '& .tb-mini-head': { padding: '8px 14px', fontWeight: 600, fontSize: 13 },
6184
+ '& .tb-mini-body': { padding: '10px 14px', fontSize: 12.5, lineHeight: 1.5 },
5106
6185
  '& .tb-buttons': { display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center', marginBottom: 14 },
5107
- '& .tb-cards': { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: 14, marginBottom: 12 },
5108
- '& .tb-card': { borderRadius: 12, overflow: 'hidden', border: '1px solid' },
5109
- '& .tb-card-head': { padding: '10px 16px', fontWeight: 600 },
5110
- '& .tb-card-body': { padding: '14px 16px', fontSize: 13.5, lineHeight: 1.5 },
6186
+ '& .tb-pass': { color: this.c('success'), fontWeight: 700 },
6187
+ '& .tb-fail': { color: this.c('error'), fontWeight: 700 },
6188
+ '& .tb-link': {
6189
+ marginLeft: 10,
6190
+ background: 'none',
6191
+ border: 'none',
6192
+ cursor: 'pointer',
6193
+ color: this.c('primary'),
6194
+ fontSize: 11,
6195
+ textTransform: 'none',
6196
+ letterSpacing: 'normal',
6197
+ textDecoration: 'underline',
6198
+ },
6199
+ '& .tb-matrix': {
6200
+ maxHeight: 320,
6201
+ overflow: 'auto',
6202
+ border: `1px solid ${this.c('outline')}`,
6203
+ borderRadius: 10,
6204
+ marginBottom: 12,
6205
+ '& table': { width: '100%', borderCollapse: 'collapse', fontSize: 12 },
6206
+ '& th': {
6207
+ position: 'sticky',
6208
+ top: 0,
6209
+ textAlign: 'left',
6210
+ padding: '8px 10px',
6211
+ background: this.c('surface-variant'),
6212
+ color: this.c('on-surface-variant'),
6213
+ fontSize: 10.5,
6214
+ textTransform: 'uppercase',
6215
+ letterSpacing: '0.08em',
6216
+ },
6217
+ '& td': { padding: '6px 10px', borderTop: `1px solid ${this.c('outline')}` },
6218
+ '& td code': { fontFamily: 'monospace', fontSize: 11.5 },
6219
+ '& tr.bad td': { background: this.c('error-container'), color: this.c('on-error-container') },
6220
+ },
5111
6221
  '& .tb-hint': { fontSize: 12.5, opacity: 0.6, marginTop: 8 },
5112
6222
  }), ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
5113
6223
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniThemeBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5114
6224
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniThemeBuilderComponent, isStandalone: true, selector: "uni-theme-builder", host: { properties: { "class": "shell()" } }, ngImport: i0, template: `
5115
6225
  <aside [class]="controls()">
5116
6226
  <header class="tb-head">
5117
- <span class="tb-eyebrow">Theme builder · Color</span>
6227
+ <span class="tb-eyebrow">Theme builder · Playground</span>
5118
6228
  <h2 class="tb-title">Make Uni your brand</h2>
5119
6229
  <p class="tb-lede">
5120
6230
  Every token is derived in OKLCH from a seed color, a scheme and a category — or bring
@@ -5161,13 +6271,22 @@ class UniThemeBuilderComponent {
5161
6271
  </select>
5162
6272
  </label>
5163
6273
 
6274
+ <label class="tb-field">
6275
+ <span>Shape</span>
6276
+ <select (change)="setShape($any($event.target).value)">
6277
+ @for (s of shapes; track s) {
6278
+ <option [value]="s" [selected]="s === shape()">{{ s }}</option>
6279
+ }
6280
+ </select>
6281
+ </label>
6282
+
5164
6283
  <label class="tb-field">
5165
6284
  <span>Accent saturation floor · {{ floor() }}</span>
5166
6285
  <input type="range" min="0" max="45" [value]="floor()" (input)="setFloor($any($event.target).value)" />
5167
6286
  </label>
5168
6287
 
5169
6288
  <div class="tb-field">
5170
- <span>Mode</span>
6289
+ <span>Storybook mode</span>
5171
6290
  <div class="tb-toggle">
5172
6291
  <button type="button" [class.on]="mode() === 'light'" (click)="setMode('light')">Light</button>
5173
6292
  <button type="button" [class.on]="mode() === 'dark'" (click)="setMode('dark')">Dark</button>
@@ -5196,6 +6315,21 @@ class UniThemeBuilderComponent {
5196
6315
  </div>
5197
6316
  </label>
5198
6317
 
6318
+ <div class="tb-field">
6319
+ <span>Export</span>
6320
+ <div class="tb-exports">
6321
+ <button type="button" (click)="copyThemeFile()">
6322
+ {{ copied() === 'file' ? 'Copied ✓' : 'Copy uni-theme.ts' }}
6323
+ </button>
6324
+ <button type="button" (click)="copyNgAdd()">
6325
+ {{ copied() === 'ngadd' ? 'Copied ✓' : 'Copy ng add command' }}
6326
+ </button>
6327
+ <button type="button" (click)="copyDtcg()">
6328
+ {{ copied() === 'dtcg' ? 'Copied ✓' : 'Copy DTCG JSON' }}
6329
+ </button>
6330
+ </div>
6331
+ </div>
6332
+
5199
6333
  <div class="tb-actions">
5200
6334
  <button type="button" class="tb-reset" (click)="reset()">Reset to default</button>
5201
6335
  </div>
@@ -5203,17 +6337,35 @@ class UniThemeBuilderComponent {
5203
6337
  </aside>
5204
6338
 
5205
6339
  <section [class]="preview()">
5206
- <p class="tb-group">Palette</p>
5207
- <div class="tb-swatches">
5208
- @for (s of swatches; track s.label) {
5209
- <div class="tb-swatch" [style.background]="color(s.token)" [style.color]="color(s.on)">
5210
- <span>{{ s.label }}</span>
5211
- <code>{{ color(s.token) }}</code>
6340
+ <p class="tb-group">Light & dark — generated together, side by side</p>
6341
+ <div class="tb-modes">
6342
+ @for (m of modePanels(); track m.label) {
6343
+ <div class="tb-panel" [style.background]="m.colors['background']" [style.borderColor]="m.colors['outline']">
6344
+ <span class="tb-panel-tag" [style.color]="m.colors['on-background-variant']">{{ m.label }}</span>
6345
+ <div class="tb-mode-swatches">
6346
+ @for (s of swatches; track s.label) {
6347
+ <div class="tb-swatch" [style.background]="m.colors[s.token]" [style.color]="m.colors[s.on]">
6348
+ <span>{{ s.label }}</span>
6349
+ <code>{{ m.colors[s.token] }}</code>
6350
+ </div>
6351
+ }
6352
+ </div>
6353
+ <div class="tb-mini-card" [style.background]="m.colors['surface']" [style.borderColor]="m.colors['outline']">
6354
+ <div class="tb-mini-head" [style.background]="m.colors['primary']" [style.color]="m.colors['on-primary']">
6355
+ Card header
6356
+ </div>
6357
+ <div class="tb-mini-body" [style.color]="m.colors['on-surface']">
6358
+ Semantic inks stay legible:
6359
+ <b [style.color]="m.colors['error']">error</b> ·
6360
+ <b [style.color]="m.colors['warn']">warn</b> ·
6361
+ <b [style.color]="m.colors['success']">success</b>.
6362
+ </div>
6363
+ </div>
5212
6364
  </div>
5213
6365
  }
5214
6366
  </div>
5215
6367
 
5216
- <p class="tb-group">Buttons — your brand, our variants</p>
6368
+ <p class="tb-group">Real components — your brand, live ({{ mode() }})</p>
5217
6369
  <div class="tb-buttons">
5218
6370
  @for (v of variants; track v) {
5219
6371
  <button text-button [variant]="v" size="lg">{{ v }}</button>
@@ -5225,26 +6377,45 @@ class UniThemeBuilderComponent {
5225
6377
  }
5226
6378
  </div>
5227
6379
 
5228
- <p class="tb-group">Surfaces</p>
5229
- <div class="tb-cards">
5230
- <div class="tb-card" [style.background]="color('surface')" [style.borderColor]="color('outline')">
5231
- <div class="tb-card-head" [style.background]="color('primary')" [style.color]="color('on-primary')">
5232
- Card header
5233
- </div>
5234
- <div class="tb-card-body" [style.color]="color('on-surface')">
5235
- Body copy on a surface with the outline token as its border. Semantic states stay legible:
5236
- <b [style.color]="color('error')">error</b> ·
5237
- <b [style.color]="color('warn')">warn</b> ·
5238
- <b [style.color]="color('success')">success</b>.
5239
- </div>
5240
- </div>
5241
- <div class="tb-card" [style.background]="color('primary-container')" [style.borderColor]="color('primary')">
5242
- <div class="tb-card-body" [style.color]="color('on-primary-container')">
5243
- <b>primary-container</b> — a soft brand tint for chips, highlights and callouts, with its
5244
- own legible on-color.
5245
- </div>
6380
+ <p class="tb-group">
6381
+ Contrast report — {{ report().checks.length }} pairs ·
6382
+ worst {{ report().worst }}:1 ·
6383
+ @if (report().failing.length === 0) {
6384
+ <span class="tb-pass">all AA</span>
6385
+ } @else {
6386
+ <span class="tb-fail">{{ report().failing.length }} failing</span>
6387
+ }
6388
+ <button type="button" class="tb-link" (click)="showAllPairs.set(!showAllPairs())">
6389
+ {{ showAllPairs() ? 'hide detail' : 'show all pairs' }}
6390
+ </button>
6391
+ </p>
6392
+ @if (report().failing.length > 0 || showAllPairs()) {
6393
+ <div class="tb-matrix">
6394
+ <table>
6395
+ <thead>
6396
+ <tr><th>mode</th><th>foreground</th><th>on</th><th>ratio</th><th>needs</th><th>level</th></tr>
6397
+ </thead>
6398
+ <tbody>
6399
+ @for (c of visibleChecks(); track $index) {
6400
+ <tr [class.bad]="!c.pass">
6401
+ <td>{{ c.mode }}</td>
6402
+ <td><code>{{ c.foreground }}</code></td>
6403
+ <td><code>{{ c.background }}</code></td>
6404
+ <td>{{ c.ratio }}</td>
6405
+ <td>{{ c.required }}</td>
6406
+ <td>{{ c.level }}</td>
6407
+ </tr>
6408
+ }
6409
+ </tbody>
6410
+ </table>
5246
6411
  </div>
5247
- </div>
6412
+ }
6413
+ @if (report().failing.length > 0) {
6414
+ <p class="tb-hint">
6415
+ Failing pairs come from hard-pinned brand colors (pins are emitted verbatim). Un-pin, or
6416
+ use a preset — soft targets keep brand hue while the guard-rail restores AA.
6417
+ </p>
6418
+ }
5248
6419
 
5249
6420
  <p class="tb-hint">Tip: browse to any component story in the sidebar — it's wearing your brand now.</p>
5250
6421
  </section>
@@ -5260,7 +6431,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5260
6431
  template: `
5261
6432
  <aside [class]="controls()">
5262
6433
  <header class="tb-head">
5263
- <span class="tb-eyebrow">Theme builder · Color</span>
6434
+ <span class="tb-eyebrow">Theme builder · Playground</span>
5264
6435
  <h2 class="tb-title">Make Uni your brand</h2>
5265
6436
  <p class="tb-lede">
5266
6437
  Every token is derived in OKLCH from a seed color, a scheme and a category — or bring
@@ -5307,13 +6478,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5307
6478
  </select>
5308
6479
  </label>
5309
6480
 
6481
+ <label class="tb-field">
6482
+ <span>Shape</span>
6483
+ <select (change)="setShape($any($event.target).value)">
6484
+ @for (s of shapes; track s) {
6485
+ <option [value]="s" [selected]="s === shape()">{{ s }}</option>
6486
+ }
6487
+ </select>
6488
+ </label>
6489
+
5310
6490
  <label class="tb-field">
5311
6491
  <span>Accent saturation floor · {{ floor() }}</span>
5312
6492
  <input type="range" min="0" max="45" [value]="floor()" (input)="setFloor($any($event.target).value)" />
5313
6493
  </label>
5314
6494
 
5315
6495
  <div class="tb-field">
5316
- <span>Mode</span>
6496
+ <span>Storybook mode</span>
5317
6497
  <div class="tb-toggle">
5318
6498
  <button type="button" [class.on]="mode() === 'light'" (click)="setMode('light')">Light</button>
5319
6499
  <button type="button" [class.on]="mode() === 'dark'" (click)="setMode('dark')">Dark</button>
@@ -5342,6 +6522,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5342
6522
  </div>
5343
6523
  </label>
5344
6524
 
6525
+ <div class="tb-field">
6526
+ <span>Export</span>
6527
+ <div class="tb-exports">
6528
+ <button type="button" (click)="copyThemeFile()">
6529
+ {{ copied() === 'file' ? 'Copied ✓' : 'Copy uni-theme.ts' }}
6530
+ </button>
6531
+ <button type="button" (click)="copyNgAdd()">
6532
+ {{ copied() === 'ngadd' ? 'Copied ✓' : 'Copy ng add command' }}
6533
+ </button>
6534
+ <button type="button" (click)="copyDtcg()">
6535
+ {{ copied() === 'dtcg' ? 'Copied ✓' : 'Copy DTCG JSON' }}
6536
+ </button>
6537
+ </div>
6538
+ </div>
6539
+
5345
6540
  <div class="tb-actions">
5346
6541
  <button type="button" class="tb-reset" (click)="reset()">Reset to default</button>
5347
6542
  </div>
@@ -5349,17 +6544,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5349
6544
  </aside>
5350
6545
 
5351
6546
  <section [class]="preview()">
5352
- <p class="tb-group">Palette</p>
5353
- <div class="tb-swatches">
5354
- @for (s of swatches; track s.label) {
5355
- <div class="tb-swatch" [style.background]="color(s.token)" [style.color]="color(s.on)">
5356
- <span>{{ s.label }}</span>
5357
- <code>{{ color(s.token) }}</code>
6547
+ <p class="tb-group">Light & dark — generated together, side by side</p>
6548
+ <div class="tb-modes">
6549
+ @for (m of modePanels(); track m.label) {
6550
+ <div class="tb-panel" [style.background]="m.colors['background']" [style.borderColor]="m.colors['outline']">
6551
+ <span class="tb-panel-tag" [style.color]="m.colors['on-background-variant']">{{ m.label }}</span>
6552
+ <div class="tb-mode-swatches">
6553
+ @for (s of swatches; track s.label) {
6554
+ <div class="tb-swatch" [style.background]="m.colors[s.token]" [style.color]="m.colors[s.on]">
6555
+ <span>{{ s.label }}</span>
6556
+ <code>{{ m.colors[s.token] }}</code>
6557
+ </div>
6558
+ }
6559
+ </div>
6560
+ <div class="tb-mini-card" [style.background]="m.colors['surface']" [style.borderColor]="m.colors['outline']">
6561
+ <div class="tb-mini-head" [style.background]="m.colors['primary']" [style.color]="m.colors['on-primary']">
6562
+ Card header
6563
+ </div>
6564
+ <div class="tb-mini-body" [style.color]="m.colors['on-surface']">
6565
+ Semantic inks stay legible:
6566
+ <b [style.color]="m.colors['error']">error</b> ·
6567
+ <b [style.color]="m.colors['warn']">warn</b> ·
6568
+ <b [style.color]="m.colors['success']">success</b>.
6569
+ </div>
6570
+ </div>
5358
6571
  </div>
5359
6572
  }
5360
6573
  </div>
5361
6574
 
5362
- <p class="tb-group">Buttons — your brand, our variants</p>
6575
+ <p class="tb-group">Real components — your brand, live ({{ mode() }})</p>
5363
6576
  <div class="tb-buttons">
5364
6577
  @for (v of variants; track v) {
5365
6578
  <button text-button [variant]="v" size="lg">{{ v }}</button>
@@ -5371,26 +6584,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5371
6584
  }
5372
6585
  </div>
5373
6586
 
5374
- <p class="tb-group">Surfaces</p>
5375
- <div class="tb-cards">
5376
- <div class="tb-card" [style.background]="color('surface')" [style.borderColor]="color('outline')">
5377
- <div class="tb-card-head" [style.background]="color('primary')" [style.color]="color('on-primary')">
5378
- Card header
5379
- </div>
5380
- <div class="tb-card-body" [style.color]="color('on-surface')">
5381
- Body copy on a surface with the outline token as its border. Semantic states stay legible:
5382
- <b [style.color]="color('error')">error</b> ·
5383
- <b [style.color]="color('warn')">warn</b> ·
5384
- <b [style.color]="color('success')">success</b>.
5385
- </div>
5386
- </div>
5387
- <div class="tb-card" [style.background]="color('primary-container')" [style.borderColor]="color('primary')">
5388
- <div class="tb-card-body" [style.color]="color('on-primary-container')">
5389
- <b>primary-container</b> — a soft brand tint for chips, highlights and callouts, with its
5390
- own legible on-color.
5391
- </div>
6587
+ <p class="tb-group">
6588
+ Contrast report — {{ report().checks.length }} pairs ·
6589
+ worst {{ report().worst }}:1 ·
6590
+ @if (report().failing.length === 0) {
6591
+ <span class="tb-pass">all AA</span>
6592
+ } @else {
6593
+ <span class="tb-fail">{{ report().failing.length }} failing</span>
6594
+ }
6595
+ <button type="button" class="tb-link" (click)="showAllPairs.set(!showAllPairs())">
6596
+ {{ showAllPairs() ? 'hide detail' : 'show all pairs' }}
6597
+ </button>
6598
+ </p>
6599
+ @if (report().failing.length > 0 || showAllPairs()) {
6600
+ <div class="tb-matrix">
6601
+ <table>
6602
+ <thead>
6603
+ <tr><th>mode</th><th>foreground</th><th>on</th><th>ratio</th><th>needs</th><th>level</th></tr>
6604
+ </thead>
6605
+ <tbody>
6606
+ @for (c of visibleChecks(); track $index) {
6607
+ <tr [class.bad]="!c.pass">
6608
+ <td>{{ c.mode }}</td>
6609
+ <td><code>{{ c.foreground }}</code></td>
6610
+ <td><code>{{ c.background }}</code></td>
6611
+ <td>{{ c.ratio }}</td>
6612
+ <td>{{ c.required }}</td>
6613
+ <td>{{ c.level }}</td>
6614
+ </tr>
6615
+ }
6616
+ </tbody>
6617
+ </table>
5392
6618
  </div>
5393
- </div>
6619
+ }
6620
+ @if (report().failing.length > 0) {
6621
+ <p class="tb-hint">
6622
+ Failing pairs come from hard-pinned brand colors (pins are emitted verbatim). Un-pin, or
6623
+ use a preset — soft targets keep brand hue while the guard-rail restores AA.
6624
+ </p>
6625
+ }
5394
6626
 
5395
6627
  <p class="tb-hint">Tip: browse to any component story in the sidebar — it's wearing your brand now.</p>
5396
6628
  </section>
@@ -5509,5 +6741,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5509
6741
  * Generated bundle index. Do not edit.
5510
6742
  */
5511
6743
 
5512
- export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniButtonComponent, UniButtonGroupComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniSymbolComponent, UniTagComponent, UniTextComponent, UniThemeBuilderComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, anchorArrowStyles, anchorStyles, getFileExtension, motionSafe, newAnchorName, resolveFocusTarget, uniqueId, useTimer, visuallyHidden };
6744
+ export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, anchorArrowStyles, anchorStyles, getFileExtension, motionSafe, newAnchorName, resolveFocusTarget, uniqueId, useTimer, visuallyHidden };
5513
6745
  //# sourceMappingURL=uni-design-system-uni-angular.mjs.map