@blotch/dolan 22.2.4 → 22.2.6

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.
Files changed (124) hide show
  1. package/dist/commands/add.test.js +2 -0
  2. package/dist/commands/add.test.js.map +1 -1
  3. package/dist/commands/doctor.js +78 -21
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/doctor.test.js +167 -26
  6. package/dist/commands/doctor.test.js.map +1 -1
  7. package/dist/commands/init.test.js +4 -7
  8. package/dist/commands/init.test.js.map +1 -1
  9. package/dist/commands/update.d.ts +3 -1
  10. package/dist/commands/update.js +78 -15
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/commands/update.test.js +257 -2
  13. package/dist/commands/update.test.js.map +1 -1
  14. package/dist/lockfile.d.ts +10 -0
  15. package/dist/lockfile.js +20 -1
  16. package/dist/lockfile.js.map +1 -1
  17. package/dist/lockfile.test.js +43 -3
  18. package/dist/lockfile.test.js.map +1 -1
  19. package/dist/registry.js +73 -11
  20. package/dist/registry.js.map +1 -1
  21. package/dist/registry.test.js +16 -3
  22. package/dist/registry.test.js.map +1 -1
  23. package/dist/test-utils.d.ts +24 -2
  24. package/dist/test-utils.js +41 -2
  25. package/dist/test-utils.js.map +1 -1
  26. package/dist/ui/boolean-inputs.test.ts +76 -0
  27. package/dist/ui/components/action-sheet/action-sheet.test.ts +370 -1
  28. package/dist/ui/components/action-sheet/action-sheet.ts +306 -28
  29. package/dist/ui/components/action-sheet/index.ts +1 -0
  30. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +552 -8
  31. package/dist/ui/components/alert-dialog/alert-dialog.ts +644 -36
  32. package/dist/ui/components/alert-dialog/index.ts +5 -0
  33. package/dist/ui/components/area-chart/area-chart.ts +3 -2
  34. package/dist/ui/components/badge/badge.ts +2 -1
  35. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +968 -3
  36. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1021 -95
  37. package/dist/ui/components/bottom-sheet/index.ts +7 -1
  38. package/dist/ui/components/button/button.test.ts +36 -1
  39. package/dist/ui/components/button/button.ts +17 -7
  40. package/dist/ui/components/button-group/button-group.test.ts +53 -9
  41. package/dist/ui/components/button-group/button-group.ts +67 -21
  42. package/dist/ui/components/card/card.test.ts +16 -0
  43. package/dist/ui/components/card/card.ts +2 -1
  44. package/dist/ui/components/chart/chart-presets.ts +6 -6
  45. package/dist/ui/components/chart/chart.test.ts +14 -0
  46. package/dist/ui/components/chart/chart.ts +10 -3
  47. package/dist/ui/components/checkbox/checkbox.ts +2 -1
  48. package/dist/ui/components/collapsible/collapsible.ts +2 -1
  49. package/dist/ui/components/dialog/dialog.ts +6 -6
  50. package/dist/ui/components/dock/dock-scroll.ts +9 -0
  51. package/dist/ui/components/dock/dock-search.test.ts +594 -0
  52. package/dist/ui/components/dock/dock.test.ts +4 -1
  53. package/dist/ui/components/dock/dock.ts +529 -51
  54. package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
  55. package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
  56. package/dist/ui/components/dynamic-color/index.ts +5 -0
  57. package/dist/ui/components/glass/glass.test.ts +39 -1
  58. package/dist/ui/components/glass/glass.ts +87 -17
  59. package/dist/ui/components/glass/index.ts +1 -0
  60. package/dist/ui/components/icon/icon-theme.test.ts +28 -0
  61. package/dist/ui/components/icon/icon-theme.ts +38 -7
  62. package/dist/ui/components/input/input.ts +2 -1
  63. package/dist/ui/components/label/label.ts +8 -2
  64. package/dist/ui/components/line-chart/line-chart.ts +3 -2
  65. package/dist/ui/components/list/list.ts +5 -4
  66. package/dist/ui/components/menu/menu-popup.test.ts +18 -14
  67. package/dist/ui/components/menu/menu-popup.ts +24 -3
  68. package/dist/ui/components/menu/menu.test.ts +99 -5
  69. package/dist/ui/components/menu/menu.ts +74 -34
  70. package/dist/ui/components/nav-drawer/nav-drawer.ts +3 -2
  71. package/dist/ui/components/navigation-bar/index.ts +1 -0
  72. package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
  73. package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
  74. package/dist/ui/components/popover/index.ts +9 -0
  75. package/dist/ui/components/popover/popover-panel.test.ts +735 -0
  76. package/dist/ui/components/popover/popover-panel.ts +603 -0
  77. package/dist/ui/components/popover/popover.test.ts +286 -0
  78. package/dist/ui/components/popover/popover.ts +207 -0
  79. package/dist/ui/components/progress/progress.ts +2 -1
  80. package/dist/ui/components/radio-group/radio-group.ts +2 -1
  81. package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
  82. package/dist/ui/components/search-field/index.ts +15 -0
  83. package/dist/ui/components/search-field/keyboard.test.ts +147 -0
  84. package/dist/ui/components/search-field/keyboard.ts +123 -0
  85. package/dist/ui/components/search-field/search-field.test.ts +549 -0
  86. package/dist/ui/components/search-field/search-field.ts +628 -0
  87. package/dist/ui/components/segmented-control/index.ts +8 -0
  88. package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
  89. package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
  90. package/dist/ui/components/select/select.ts +2 -1
  91. package/dist/ui/components/sheet/sheet.test.ts +90 -3
  92. package/dist/ui/components/sheet/sheet.ts +59 -4
  93. package/dist/ui/components/slider/index.ts +1 -0
  94. package/dist/ui/components/slider/slider.test.ts +695 -0
  95. package/dist/ui/components/slider/slider.ts +901 -0
  96. package/dist/ui/components/switch/index.ts +1 -1
  97. package/dist/ui/components/switch/switch.test.ts +464 -48
  98. package/dist/ui/components/switch/switch.ts +613 -43
  99. package/dist/ui/components/tabs/index.ts +7 -1
  100. package/dist/ui/components/tabs/tabs.test.ts +278 -2
  101. package/dist/ui/components/tabs/tabs.ts +233 -30
  102. package/dist/ui/components/textarea/textarea.ts +2 -1
  103. package/dist/ui/components/toggle/index.ts +1 -0
  104. package/dist/ui/components/toggle/toggle.test.ts +310 -4
  105. package/dist/ui/components/toggle/toggle.ts +241 -26
  106. package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
  107. package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
  108. package/dist/ui/components/toolbar/index.ts +12 -0
  109. package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
  110. package/dist/ui/components/toolbar/toolbar.ts +535 -0
  111. package/dist/utils/analyze.d.ts +10 -5
  112. package/dist/utils/analyze.js +3 -2
  113. package/dist/utils/analyze.js.map +1 -1
  114. package/dist/utils/installed.d.ts +13 -1
  115. package/dist/utils/installed.js +17 -0
  116. package/dist/utils/installed.js.map +1 -1
  117. package/dist/utils/installed.test.js +39 -1
  118. package/dist/utils/installed.test.js.map +1 -1
  119. package/dist/utils/tailwind.d.ts +25 -5
  120. package/dist/utils/tailwind.js +53 -14
  121. package/dist/utils/tailwind.js.map +1 -1
  122. package/dist/utils/tailwind.test.js +85 -22
  123. package/dist/utils/tailwind.test.js.map +1 -1
  124. package/package.json +1 -1
@@ -3,6 +3,7 @@ import {
3
3
  Component,
4
4
  ViewEncapsulation,
5
5
  computed,
6
+ contentChildren,
6
7
  effect,
7
8
  inject,
8
9
  input,
@@ -11,7 +12,7 @@ import {
11
12
  signal,
12
13
  viewChild,
13
14
  } from '@angular/core';
14
- import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
15
+ import { LYNX_ELEMENTS, LynxAccessibilitySettings } from '@blotch/angular-lynx';
15
16
 
16
17
  import {
17
18
  type AnimationHandle,
@@ -21,10 +22,37 @@ import {
21
22
  revealIn,
22
23
  } from '../../utils/animate';
23
24
  import { cn } from '../../utils/cn';
25
+ import {
26
+ type SegmentedHost,
27
+ segmentPlace,
28
+ UI_SEGMENTED_HOST,
29
+ UiSegmentedControl,
30
+ UiSegmentedIndicator,
31
+ } from '../segmented-control/segmented-control';
32
+
33
+ /**
34
+ * A tab list's look: shadcn's tabs, or `'segmented'` for the platform's
35
+ * segmented control (see UiTabsList).
36
+ */
37
+ export type TabsListVariant = 'default' | 'segmented';
38
+
39
+ /**
40
+ * `UiTabsTrigger`, for the list's content query. A call rather than the
41
+ * class itself: for a class, Angular's test build keeps an arrow function
42
+ * that nothing calls, which an app's coverage report would show as
43
+ * uncovered.
44
+ */
45
+ const triggerType = () => UiTabsTrigger;
46
+
47
+ /**
48
+ * The list, as the segmented control it provides reads it. Named, not
49
+ * inline: the component's metadata keeps its own copy of an inline factory,
50
+ * which nothing calls.
51
+ */
52
+ const tabsListHost = (): SegmentedHost => inject(UiTabsList).segmentedHost;
24
53
 
25
54
  @Component({
26
55
  selector: 'ui-tabs',
27
- standalone: true,
28
56
  imports: [LYNX_ELEMENTS],
29
57
  encapsulation: ViewEncapsulation.None,
30
58
  template: `
@@ -95,47 +123,133 @@ export class UiTabs {
95
123
  }
96
124
  }
97
125
 
126
+ /**
127
+ * The row of tab triggers.
128
+ *
129
+ * `variant="segmented"` draws the platform's segmented control instead (see
130
+ * UiSegmentedControl), Apple's control for switching between closely
131
+ * related views: iOS 26's capsule with a thumb that lifts into Liquid Glass
132
+ * as it slides between the tabs (it can be dragged across them too), iOS 13
133
+ * to 18's raised thumb before iOS 26, and Material 3 Expressive's connected
134
+ * button group on Android. It fills the width it's given, and the tabs
135
+ * share it.
136
+ *
137
+ * The row is a tab bar to VoiceOver (`tabbar`), which reads each trigger as
138
+ * a tab, and the selected one as selected.
139
+ */
98
140
  @Component({
99
141
  selector: 'ui-tabs-list',
100
- standalone: true,
101
- imports: [LYNX_ELEMENTS],
142
+ imports: [LYNX_ELEMENTS, UiSegmentedIndicator],
102
143
  encapsulation: ViewEncapsulation.None,
144
+ providers: [
145
+ UiSegmentedControl,
146
+ { provide: UI_SEGMENTED_HOST, useFactory: tabsListHost },
147
+ ],
148
+ // The mouse leaving the row lets go of an indicator dragged in the web
149
+ // preview, where a drag gets no touchcancel.
103
150
  template: `
104
- <view [class]="listClass()">
151
+ <view
152
+ accessibility-traits="tabbar"
153
+ [class]="listClass()"
154
+ (bindmouseleave)="segmentedControl.cancel()"
155
+ >
156
+ @if (segmentedControl.underlay()) {
157
+ <ui-segmented-indicator />
158
+ }
105
159
  <ng-content />
106
160
  </view>
107
161
  `,
108
162
  })
109
163
  export class UiTabsList {
164
+ /**
165
+ * `'default'`, or `'segmented'` for the platform's segmented control.
166
+ */
167
+ readonly variant = input<TabsListVariant>('default');
110
168
  readonly userClass = input<string>('', { alias: 'class' });
111
169
 
112
- protected readonly listClass = computed(() =>
113
- cn(
114
- // `self-start` keeps the tab bar hugging its triggers. The parent <ui-tabs>
115
- // is a `flex flex-col` column, and in Lynx's linear/flex layout a child with
116
- // no explicit width stretches to fill the parent's cross axis — so without
117
- // it the muted pill spans the full width, leaving empty space around the
118
- // tabs. Same lever as the badge fix: Lynx has no `inline-flex`, so
119
- // align-self is how you opt a child into shrink-to-fit.
120
- 'flex-row items-center self-start rounded-lg bg-muted p-1 flex',
121
- this.userClass(),
170
+ readonly #tabs = inject(UiTabs);
171
+ protected readonly segmentedControl = inject(UiSegmentedControl);
172
+ /** The triggers in the list, in order. */
173
+ readonly triggers = contentChildren(triggerType(), { descendants: true });
174
+
175
+ /** What the segmented control reads from the list. */
176
+ readonly segmentedHost: SegmentedHost = {
177
+ segmented: computed(() => this.variant() === 'segmented'),
178
+ segmentValues: computed(() =>
179
+ this.triggers().map((trigger) => trigger.triggerValue()),
122
180
  ),
181
+ selectedValues: computed(() => [this.#tabs.value()]),
182
+ // One tab is active at a time, and tabs are never disabled.
183
+ multiple: signal(false).asReadonly(),
184
+ disabled: signal(false).asReadonly(),
185
+ select: (value) => this.#tabs.select(value),
186
+ };
187
+
188
+ protected readonly listClass = computed(() =>
189
+ this.segmentedControl.enabled()
190
+ ? // A segmented control fills the width it's given, as UIKit's does
191
+ // in a form, and its tabs share it.
192
+ cn(this.segmentedControl.trackClass(), this.userClass())
193
+ : cn(
194
+ // `self-start` keeps the tab bar hugging its triggers. The parent <ui-tabs>
195
+ // is a `flex flex-col` column, and in Lynx's linear/flex layout a child with
196
+ // no explicit width stretches to fill the parent's cross axis — so without
197
+ // it the muted pill spans the full width, leaving empty space around the
198
+ // tabs. Same lever as the badge fix: Lynx has no `inline-flex`, so
199
+ // align-self is how you opt a child into shrink-to-fit.
200
+ 'flex-row items-center self-start rounded-lg bg-muted p-1 flex',
201
+ this.userClass(),
202
+ ),
123
203
  );
124
204
  }
125
205
 
126
206
  @Component({
127
207
  selector: 'ui-tabs-trigger',
128
- standalone: true,
129
208
  imports: [LYNX_ELEMENTS],
130
209
  encapsulation: ViewEncapsulation.None,
210
+ host: {
211
+ '[class]': 'hostClass()',
212
+ // The segmented control places its indicator from each tab's width.
213
+ '(bindlayoutchange)': 'measure($event)',
214
+ },
215
+ // The label is the accessibility element, a button (a tab, in the list's
216
+ // tab bar) named by its own text. A view would need a label of its own.
217
+ // The selected tab is `selected` to a screen reader, which iOS reads:
218
+ // lynxAccessibilityTraits adds the trait to `button` on iOS, and leaves
219
+ // Android, which reads one trait, with `button`.
131
220
  template: `
132
- <view #trigger [class]="triggerClass()" (bindtap)="select()">
133
- <text [class]="textClass()"><ng-content /></text>
221
+ <view
222
+ #trigger
223
+ [class]="triggerClass()"
224
+ [style]="triggerStyle()"
225
+ (bindtap)="onTap()"
226
+ (bindtouchstart)="press($event)"
227
+ (bindtouchmove)="move($event)"
228
+ (bindtouchend)="release()"
229
+ (bindtouchcancel)="cancel()"
230
+ (bindmousedown)="press($event)"
231
+ (bindmousemove)="move($event)"
232
+ (bindmouseup)="release()"
233
+ (bindmouseleave)="leave()"
234
+ >
235
+ @if (stateLayerClass(); as stateLayer) {
236
+ <view [class]="stateLayer"></view>
237
+ }
238
+ <text
239
+ accessibility-traits="button"
240
+ [lynxAccessibilityTraits]="{ selected: isActive() }"
241
+ [class]="textClass()"
242
+ ><ng-content
243
+ /></text>
134
244
  </view>
135
245
  `,
136
246
  })
137
247
  export class UiTabsTrigger {
138
248
  readonly #tabs = inject(UiTabs);
249
+ readonly #settings = inject(LynxAccessibilitySettings);
250
+ // Both absent for a trigger outside a <ui-tabs-list>.
251
+ readonly #list = inject(UiTabsList, { optional: true });
252
+ readonly #segmented = inject(UiSegmentedControl, { optional: true });
139
253
 
140
254
  readonly triggerValue = input.required<string>({ alias: 'value' });
141
255
  readonly userClass = input<string>('', { alias: 'class' });
@@ -148,6 +262,32 @@ export class UiTabsTrigger {
148
262
  () => this.#tabs.value() === this.triggerValue(),
149
263
  );
150
264
 
265
+ /**
266
+ * The segmented control this trigger is a segment of, or null when its
267
+ * list is the default tabs (or it has no list).
268
+ */
269
+ readonly #segment = computed(() =>
270
+ this.#segmented?.enabled() ? this.#segmented : null,
271
+ );
272
+
273
+ /**
274
+ * A segment's host is the track's flex item, an equal share of it. Angular
275
+ * puts the app's static `class` on the host as well as on the trigger, so
276
+ * the two are merged here: `class="flex-[2]"` makes a wider tab.
277
+ */
278
+ protected readonly hostClass = computed(() => {
279
+ const segment = this.#segment();
280
+ return segment ? cn(segment.segmentHostClass, this.userClass()) : '';
281
+ });
282
+
283
+ protected readonly triggerStyle = computed(
284
+ () => this.#segment()?.segmentStyle(this.triggerValue()) ?? null,
285
+ );
286
+
287
+ protected readonly stateLayerClass = computed(
288
+ () => this.#segment()?.stateLayerClass(this.triggerValue()) ?? null,
289
+ );
290
+
151
291
  /** Registers this trigger with the parent for direction detection. */
152
292
  protected readonly registering = effect(() => {
153
293
  this.#tabs.registerTab(this.triggerValue());
@@ -161,8 +301,17 @@ export class UiTabsTrigger {
161
301
  this.#previousActive = active;
162
302
  // The first run only records the starting state, and only a change has
163
303
  // anything to animate. (`isActive` is a computed, so the effect only
164
- // re-runs once it changes; the second check is a safeguard.)
165
- if (wasActive === undefined || wasActive === active) return;
304
+ // re-runs once it changes; the second check is a safeguard.) A segment
305
+ // doesn't animate itself: the segmented control's indicator moves. Under
306
+ // Reduce Motion the trigger changes at once.
307
+ if (
308
+ wasActive === undefined ||
309
+ wasActive === active ||
310
+ this.#segment() ||
311
+ this.#settings.reduceMotion()
312
+ ) {
313
+ return;
314
+ }
166
315
 
167
316
  this.#anim?.cancel();
168
317
 
@@ -195,8 +344,17 @@ export class UiTabsTrigger {
195
344
  }
196
345
  });
197
346
 
198
- protected readonly triggerClass = computed(() =>
199
- cn(
347
+ protected readonly triggerClass = computed(() => {
348
+ const segment = this.#segment();
349
+ if (segment) {
350
+ // The list provides the segmented control, so a segment is in one.
351
+ const place = segmentPlace(this.#list!.triggers(), this);
352
+ return cn(
353
+ segment.segmentClass(this.triggerValue(), place),
354
+ this.userClass(),
355
+ );
356
+ }
357
+ return cn(
200
358
  // No `flex-1` here: now that the list shrinks to fit its content (see
201
359
  // `self-start` in UiTabsList), stretching triggers to equal width is
202
360
  // pointless, and `flex-basis: 0` growth combined with Lynx treating
@@ -206,24 +364,62 @@ export class UiTabsTrigger {
206
364
  'items-center rounded-sm px-3 py-1.5 flex justify-center',
207
365
  this.isActive() ? 'bg-background' : 'bg-transparent',
208
366
  this.userClass(),
209
- ),
210
- );
367
+ );
368
+ });
211
369
 
212
- protected readonly textClass = computed(() =>
213
- cn(
214
- 'text-sm font-medium',
215
- this.isActive() ? 'text-foreground' : 'text-muted-foreground',
216
- ),
370
+ protected readonly textClass = computed(
371
+ () =>
372
+ this.#segment()?.labelClass(this.triggerValue()) ??
373
+ cn(
374
+ 'text-sm font-medium',
375
+ this.isActive() ? 'text-foreground' : 'text-muted-foreground',
376
+ ),
217
377
  );
218
378
 
219
379
  select(): void {
220
380
  this.#tabs.select(this.triggerValue());
221
381
  }
382
+
383
+ protected measure(event: unknown): void {
384
+ this.#segmented?.measure(this.triggerValue(), event);
385
+ }
386
+
387
+ /**
388
+ * A press. A segment's touches go to the segmented control, which draws
389
+ * their feedback and moves its indicator.
390
+ */
391
+ protected press(event: unknown): void {
392
+ this.#segment()?.press(this.triggerValue(), event);
393
+ }
394
+
395
+ protected move(event: unknown): void {
396
+ this.#segment()?.move(event);
397
+ }
398
+
399
+ protected release(): void {
400
+ this.#segment()?.release();
401
+ }
402
+
403
+ protected cancel(): void {
404
+ this.#segment()?.cancel();
405
+ }
406
+
407
+ protected leave(): void {
408
+ this.#segment()?.leave(this.triggerValue());
409
+ }
410
+
411
+ /**
412
+ * A tap that ends a drag of the indicator doesn't select this tab: the
413
+ * drag chose already.
414
+ */
415
+ protected onTap(): void {
416
+ if (this.#segment()?.tap(this.triggerValue()) === false) return;
417
+ this.select();
418
+ }
222
419
  }
223
420
 
224
421
  @Component({
225
422
  selector: 'ui-tabs-content',
226
- standalone: true,
227
423
  imports: [LYNX_ELEMENTS],
228
424
  encapsulation: ViewEncapsulation.None,
229
425
  host: {
@@ -242,6 +438,10 @@ export class UiTabsTrigger {
242
438
  // makes the host lay its content out vertically when shown.
243
439
  '[style.display]': "isActive() ? 'flex' : 'none'",
244
440
  '[style.flexDirection]': "'column'",
441
+ // A hidden panel's 0×0 doesn't stop iOS drawing its text: each line is
442
+ // laid out at 0×0 too, and its glyphs spill out from the panel's corner,
443
+ // over the tab list. Clipping the hidden host to its 0×0 hides them.
444
+ '[style.overflow]': "isActive() ? null : 'hidden'",
245
445
  },
246
446
  template: `
247
447
  <view #content [class]="contentClass()">
@@ -251,6 +451,7 @@ export class UiTabsTrigger {
251
451
  })
252
452
  export class UiTabsContent {
253
453
  readonly #tabs = inject(UiTabs);
454
+ readonly #settings = inject(LynxAccessibilitySettings);
254
455
 
255
456
  readonly contentValue = input.required<string>({ alias: 'value' });
256
457
  readonly userClass = input<string>('', { alias: 'class' });
@@ -282,6 +483,8 @@ export class UiTabsContent {
282
483
  if (!this.isActive()) return;
283
484
  // Defer one tick so the display flip to 'flex' has applied before we animate.
284
485
  setTimeout(() => {
486
+ // Under Reduce Motion the panel just appears.
487
+ if (this.#settings.reduceMotion()) return;
285
488
  const el = this.contentRef().nativeElement;
286
489
 
287
490
  this.#anim?.cancel();
@@ -2,6 +2,7 @@ import {
2
2
  type ElementRef,
3
3
  Component,
4
4
  ViewEncapsulation,
5
+ booleanAttribute,
5
6
  computed,
6
7
  effect,
7
8
  input,
@@ -95,7 +96,7 @@ export class UiTextarea implements FormValueControl<string> {
95
96
  readonly placeholder = input<string>('');
96
97
  readonly helperText = input<string>('');
97
98
  readonly error = input<string>('');
98
- readonly disabled = input(false);
99
+ readonly disabled = input(false, { transform: booleanAttribute });
99
100
  readonly userClass = input<string>('', { alias: 'class' });
100
101
 
101
102
  // SwiftUI-style lineLimit(min...max). The field opens at minLines, grows with
@@ -2,6 +2,7 @@ export {
2
2
  UiToggle,
3
3
  UiToggleGroup,
4
4
  UiToggleGroupItem,
5
+ type ToggleGroupVariant,
5
6
  type ToggleVariant,
6
7
  type ToggleSize,
7
8
  } from './toggle';