@bitforgehq/ui 0.3.3 → 0.5.0-next.3

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 (99) hide show
  1. package/CHANGELOG.md +394 -0
  2. package/README.md +14 -14
  3. package/ai/MANIFEST.index.json +101 -2
  4. package/ai/MANIFEST.json +2630 -746
  5. package/ai/REFERENCE.index.json +196 -0
  6. package/ai/REFERENCE.json +1625 -140
  7. package/ai/UI_GUIDE.md +83 -21
  8. package/ai/components/app-frame.md +1 -1
  9. package/ai/components/badge.md +39 -2
  10. package/ai/components/chat-composer.md +357 -0
  11. package/ai/components/chat-message.md +251 -0
  12. package/ai/components/chat-thread.md +521 -0
  13. package/ai/components/checkbox.md +7 -0
  14. package/ai/components/combobox.md +12 -0
  15. package/ai/components/date-input.md +9 -0
  16. package/ai/components/datetime-input.md +2 -0
  17. package/ai/components/field.md +10 -0
  18. package/ai/components/file-input.md +498 -0
  19. package/ai/components/header.md +1 -1
  20. package/ai/components/image-input.md +368 -0
  21. package/ai/components/image-viewer.md +309 -0
  22. package/ai/components/list-item.md +105 -9
  23. package/ai/components/media-card.md +478 -0
  24. package/ai/components/menu-button.md +2 -0
  25. package/ai/components/menu.md +10 -0
  26. package/ai/components/multi-combobox.md +7 -0
  27. package/ai/components/nav-group.md +10 -3
  28. package/ai/components/nav-item.md +11 -2
  29. package/ai/components/number-input.md +2 -7
  30. package/ai/components/popover.md +13 -0
  31. package/ai/components/radio-group.md +6 -0
  32. package/ai/components/segmented.md +14 -2
  33. package/ai/components/select.md +100 -42
  34. package/ai/components/sidebar-toggle.md +16 -9
  35. package/ai/components/sidebar.md +41 -13
  36. package/ai/components/step.md +112 -0
  37. package/ai/components/steps.md +124 -0
  38. package/ai/components/tab-group.md +18 -2
  39. package/ai/components/table.md +115 -4
  40. package/ai/components/theme-menu-button.md +1 -0
  41. package/ai/components/time-input.md +2 -0
  42. package/ai/components/timeline-event.md +144 -0
  43. package/ai/components/timeline.md +201 -0
  44. package/ai/components/toggle.md +11 -1
  45. package/ai/components/toolbar.md +7 -18
  46. package/ai/reference/bf-action-directive.md +3 -1
  47. package/ai/reference/bf-badge-emphasis.md +27 -0
  48. package/ai/reference/bf-chat-composer-harness.md +64 -0
  49. package/ai/reference/bf-chat-message-role.md +27 -0
  50. package/ai/reference/bf-chat-message-status.md +28 -0
  51. package/ai/reference/bf-fade-in-directive.md +18 -0
  52. package/ai/reference/bf-file-input-harness.md +67 -0
  53. package/ai/reference/bf-file-input-variant.md +27 -0
  54. package/ai/reference/bf-image-input-harness.md +67 -0
  55. package/ai/reference/bf-image-input-shape.md +27 -0
  56. package/ai/reference/bf-image-viewer-actions-context.md +29 -0
  57. package/ai/reference/bf-image-viewer-harness.md +87 -0
  58. package/ai/reference/bf-image-viewer-image.md +25 -0
  59. package/ai/reference/bf-image-viewer-options.md +33 -0
  60. package/ai/reference/bf-image-viewer-origin.md +23 -0
  61. package/ai/reference/bf-image-viewer-ref.md +55 -0
  62. package/ai/reference/bf-image-viewer-service.md +32 -0
  63. package/ai/reference/bf-labels.md +105 -74
  64. package/ai/reference/bf-media-card-aspect.md +28 -0
  65. package/ai/reference/bf-media-card-harness.md +66 -0
  66. package/ai/reference/bf-motion-list-directive.md +22 -0
  67. package/ai/reference/bf-prose-directive.md +16 -0
  68. package/ai/reference/bf-rejected-file-reason.md +28 -0
  69. package/ai/reference/bf-rejected-file.md +28 -0
  70. package/ai/reference/bf-select-harness.md +14 -5
  71. package/ai/reference/bf-select-option.md +2 -0
  72. package/ai/reference/bf-size.md +3 -0
  73. package/ai/reference/bf-step-heading-level.md +28 -0
  74. package/ai/reference/bf-table-align.md +1 -1
  75. package/ai/reference/bf-table-column-align.md +33 -0
  76. package/ai/reference/bf-table-column.md +7 -7
  77. package/ai/reference/bf-table-harness.md +12 -0
  78. package/ai/reference/bf-timeline-tone.md +31 -0
  79. package/ai/reference/bf-toggle-harness.md +55 -0
  80. package/ai/reference/bf_table_sort_select.md +23 -0
  81. package/ai/reference/provide-bf-labels.md +10 -3
  82. package/ai/reference/provide-bf-select.md +20 -0
  83. package/bin/bf-ui.js +1 -0
  84. package/dark-ink.css +124 -0
  85. package/fesm2022/bitforgehq-ui-mobile.mjs +2 -1
  86. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  87. package/fesm2022/bitforgehq-ui-overlay.mjs +2649 -606
  88. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  89. package/fesm2022/bitforgehq-ui-testing.mjs +406 -11
  90. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  91. package/fesm2022/bitforgehq-ui.mjs +11149 -6957
  92. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  93. package/llms-full.txt +5219 -141
  94. package/llms.txt +40 -1
  95. package/package.json +2 -1
  96. package/theme.css +84 -0
  97. package/types/bitforgehq-ui-overlay.d.ts +684 -7
  98. package/types/bitforgehq-ui-testing.d.ts +510 -7
  99. package/types/bitforgehq-ui.d.ts +1757 -186
@@ -1,5 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
- import { ElementRef, EnvironmentProviders, InjectionToken, Provider, Signal, TemplateRef } from "@angular/core";
2
+ import { ElementRef, EnvironmentProviders, InjectionToken, Provider, Signal, TemplateRef, Type } from "@angular/core";
3
3
  import { FormCheckboxControl, FormValueControl, ValidationError } from "@angular/forms/signals";
4
4
  /**
5
5
  * The one control-height scale: sm 32 px, md 40 px, lg 48 px.
@@ -512,6 +512,161 @@ interface BfLabels {
512
512
  * @default 'The data could not be loaded'
513
513
  */
514
514
  readonly chartError: string;
515
+ /**
516
+ * bf-list-item's visually hidden state text before the title of an `unread` row.
517
+ * @default 'Unread'
518
+ */
519
+ readonly listItemUnread: string;
520
+ /**
521
+ * bf-file-input's button text.
522
+ * @default (multiple) => (multiple ? 'Choose files' : 'Choose file')
523
+ */
524
+ readonly fileInputChoose: (multiple: boolean) => string;
525
+ /**
526
+ * bf-file-input's hint beside the button.
527
+ * @default (multiple) => (multiple ? 'or drop them here' : 'or drop it here')
528
+ */
529
+ readonly fileInputDropHint: (multiple: boolean) => string;
530
+ /**
531
+ * bf-file-input's drop zone text.
532
+ * @default (multiple) => (multiple ? 'Drag files here or browse' : 'Drag a file here or browse')
533
+ */
534
+ readonly fileInputDropzone: (multiple: boolean) => string;
535
+ /**
536
+ * bf-file-input's text under touch input.
537
+ * @default (multiple) => (multiple ? 'Tap to choose files' : 'Tap to choose a file')
538
+ */
539
+ readonly fileInputTapToChoose: (multiple: boolean) => string;
540
+ /**
541
+ * The accessible name of bf-file-input's remove button for a file.
542
+ * @default (name) => `Remove ${name}`
543
+ */
544
+ readonly fileInputRemove: (name: string) => string;
545
+ /**
546
+ * The accessible name of bf-file-input's button that dismisses a rejected file.
547
+ * @default (name) => `Dismiss ${name}`
548
+ */
549
+ readonly fileInputDismiss: (name: string) => string;
550
+ /**
551
+ * bf-file-input's live announcement after files are added.
552
+ * @default (count) => (count === 1 ? '1 file added' : `${count} files added`)
553
+ */
554
+ readonly fileInputAdded: (count: number) => string;
555
+ /**
556
+ * bf-file-input's live announcement for a rejected file.
557
+ * @default (name, reason) => `${name} was not added: ${reason}`
558
+ */
559
+ readonly fileInputNotAdded: (name: string, reason: string) => string;
560
+ /**
561
+ * bf-file-input's live announcement after a file is removed.
562
+ * @default (name) => `${name} removed`
563
+ */
564
+ readonly fileInputRemoved: (name: string) => string;
565
+ /**
566
+ * bf-file-input's read-only box text for the number of chosen files.
567
+ * @default (count) => (count === 1 ? '1 file' : `${count} files`)
568
+ */
569
+ readonly fileInputCount: (count: number) => string;
570
+ /**
571
+ * The message of a file rejected for its type.
572
+ * @default 'Not an accepted type'
573
+ */
574
+ readonly fileRejectedType: string;
575
+ /**
576
+ * The message of a file rejected for its size; `max` is the formatted limit.
577
+ * @default (max) => `Too large, max ${max}`
578
+ */
579
+ readonly fileRejectedSize: (max: string) => string;
580
+ /**
581
+ * The message of a file rejected because the file limit is reached.
582
+ * @default (max) => `Too many files, max ${max}`
583
+ */
584
+ readonly fileRejectedCount: (max: number) => string;
585
+ /**
586
+ * bf-image-input's accessible name for the empty drop target.
587
+ * @default 'Choose image'
588
+ */
589
+ readonly imageInputChoose: string;
590
+ /**
591
+ * bf-image-input's text for choosing the first image.
592
+ * @default 'Upload'
593
+ */
594
+ readonly imageInputUpload: string;
595
+ /**
596
+ * bf-image-input's text for replacing the image.
597
+ * @default 'Replace'
598
+ */
599
+ readonly imageInputReplace: string;
600
+ /**
601
+ * bf-image-input's text for removing the image.
602
+ * @default 'Remove'
603
+ */
604
+ readonly imageInputRemove: string;
605
+ /**
606
+ * bf-chat-thread's accessible name (its `role="log"`) when no `label` is set.
607
+ * @default 'Conversation'
608
+ */
609
+ readonly chatThread: string;
610
+ /**
611
+ * bf-chat-thread's button that scrolls back to the newest message and follows it again.
612
+ * @default 'Jump to latest'
613
+ */
614
+ readonly chatJumpToLatest: string;
615
+ /**
616
+ * bf-chat-message's status line for a reply the user stopped.
617
+ * @default 'Stopped'
618
+ */
619
+ readonly chatMessageStopped: string;
620
+ /**
621
+ * bf-chat-message's status line for a message that failed.
622
+ * @default 'Something went wrong'
623
+ */
624
+ readonly chatMessageFailed: string;
625
+ /**
626
+ * bf-chat-composer's text field name when no `label` or `ariaLabel` is set.
627
+ * @default 'Message'
628
+ */
629
+ readonly chatComposer: string;
630
+ /**
631
+ * bf-chat-composer's send button.
632
+ * @default 'Send'
633
+ */
634
+ readonly chatSend: string;
635
+ /**
636
+ * bf-chat-composer's stop button, shown in place of send while a reply runs.
637
+ * @default 'Stop'
638
+ */
639
+ readonly chatStop: string;
640
+ /**
641
+ * bf-media-card's select button, read before the card's name ("Select Starfall").
642
+ * @default 'Select'
643
+ */
644
+ readonly mediaCardSelect: string;
645
+ /**
646
+ * bf-media-card's and bf-image-viewer's message for an image that failed to load.
647
+ * @default 'The image could not be loaded'
648
+ */
649
+ readonly imageLoadFailed: string;
650
+ /**
651
+ * bf-image-viewer's name.
652
+ * @default 'Image viewer'
653
+ */
654
+ readonly imageViewer: string;
655
+ /**
656
+ * bf-image-viewer's button that shows the previous image.
657
+ * @default 'Previous image'
658
+ */
659
+ readonly imageViewerPrevious: string;
660
+ /**
661
+ * bf-image-viewer's button that shows the next image.
662
+ * @default 'Next image'
663
+ */
664
+ readonly imageViewerNext: string;
665
+ /**
666
+ * bf-image-viewer's counter: the shown image's position (1-based) among the images.
667
+ * @default (position, count) => `${position} of ${count}`
668
+ */
669
+ readonly imageViewerPosition: (position: number, count: number) => string;
515
670
  /** Default error text per validator kind (see `BfValidationMessages`). */
516
671
  readonly validation: BfValidationMessages;
517
672
  }
@@ -552,9 +707,16 @@ export declare const BF_LABELS: InjectionToken<BfLabels>;
552
707
  * `dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,
553
708
  * `timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,
554
709
  * `chart`, `chartLegend`, `chartValues`, `chartShowData`, `chartHideData`, `chartCategory`,
555
- * `chartValue`, `chartEmpty`, `chartError`, `validation`); the overlay services (`ok`, `cancel`,
556
- * `actions`, `sheet`, `modal`, `close`, `expand`, `collapse`, `sheetPosition`, `dialog`,
557
- * `notifications`) are root singletons and pick it up only when the caller passes its injector:
710
+ * `chartValue`, `chartEmpty`, `chartError`, `listItemUnread`,
711
+ * `fileInputChoose`, `fileInputDropHint`, `fileInputDropzone`, `fileInputTapToChoose`, `fileInputRemove`,
712
+ * `fileInputDismiss`, `fileInputAdded`, `fileInputNotAdded`, `fileInputRemoved`, `fileInputCount`,
713
+ * `fileRejectedType`, `fileRejectedSize`, `fileRejectedCount`,
714
+ * `imageInputChoose`, `imageInputUpload`, `imageInputReplace`, `imageInputRemove`,
715
+ * `chatThread`, `chatJumpToLatest`, `chatMessageStopped`, `chatMessageFailed`, `chatComposer`,
716
+ * `chatSend`, `chatStop`, `mediaCardSelect`, `imageLoadFailed`, `validation`); the overlay
717
+ * services (`ok`, `cancel`, `actions`, `sheet`, `modal`, `close`, `expand`, `collapse`,
718
+ * `sheetPosition`, `dialog`, `notifications`, `imageViewer`, `imageViewerPrevious`,
719
+ * `imageViewerNext`, `imageViewerPosition`) are root singletons and pick it up only when the caller passes its injector:
558
720
  * `open(…, { injector: inject(Injector) })`.
559
721
  *
560
722
  * @param overrides The strings to replace; any key left out keeps the parent's.
@@ -563,6 +725,51 @@ export declare const BF_LABELS: InjectionToken<BfLabels>;
563
725
  * @since 0.3.0
564
726
  */
565
727
  export declare function provideBfLabels(overrides: BfLabelsOverrides): Provider;
728
+ /**
729
+ * The rule a refused file broke: `type` (not accepted), `size` (over `maxSize`) or `count` (over
730
+ * the limit).
731
+ *
732
+ * @summary The rule a refused file broke, its type, its size or the file count
733
+ * @since 0.5.0
734
+ */
735
+ type BfRejectedFileReason = 'type' | 'size' | 'count';
736
+ /**
737
+ * A picked or dropped file that broke a rule, with the rule and its message.
738
+ *
739
+ * @summary A picked or dropped file that broke a rule, with the rule and its message
740
+ * @since 0.5.0
741
+ */
742
+ interface BfRejectedFile {
743
+ /** The file that was not added. */
744
+ readonly file: File;
745
+ /** The rule it broke: `type` (not accepted), `size` (over `maxSize`) or `count` (over the limit). */
746
+ readonly reason: BfRejectedFileReason;
747
+ /** The user-facing text for the rule, from the labels. */
748
+ readonly message: string;
749
+ }
750
+ /** The rules a file picker applies to what the user picks or drops. */
751
+ interface FileRules {
752
+ readonly accept: string;
753
+ readonly maxSize: number | undefined;
754
+ readonly maxFiles: number | undefined;
755
+ readonly multiple: boolean;
756
+ }
757
+ /** Whether `file` satisfies an `accept` string (MIME types, `type/*` wildcards and `.ext` tokens). */
758
+ declare function matchesAccept(file: File, accept: string): boolean;
759
+ /**
760
+ * Formats a byte count with base 1024: whole KB, one decimal (no trailing `.0`) for MB and GB.
761
+ * The unit is chosen after rounding, so a value that rounds up to 1024 shows as 1 of the next unit.
762
+ */
763
+ declare function formatFileSize(bytes: number): string;
764
+ /**
765
+ * Applies the rules to `incoming` in order and returns the new value and what was rejected.
766
+ * A file is checked for type, then size, then count. In single mode the first valid file
767
+ * replaces `current`; later valid ones are rejected as `count` with a max of 1.
768
+ */
769
+ declare function applyFileRules(current: readonly File[], incoming: readonly File[], rules: FileRules, labels: BfLabels): {
770
+ value: File[];
771
+ rejected: BfRejectedFile[];
772
+ };
566
773
  /**
567
774
  * The app's colour scheme choice: follow the system, or force light or dark.
568
775
  *
@@ -1012,11 +1219,20 @@ type BfBadgeVariant = 'neutral' | 'primary' | 'success' | 'warning' | 'danger';
1012
1219
  * @since 0.3.1
1013
1220
  */
1014
1221
  type BfBadgeAppearance = BfBadgeVariant | 'info';
1222
+ /**
1223
+ * How loud a `bf-badge` is: `soft`, a pale tint with tinted text (the default), or `strong`, a
1224
+ * solid fill with contrasting text for the one state that must stand out, such as "Critical".
1225
+ *
1226
+ * @summary The two badge emphases, the soft tint or the solid strong fill
1227
+ * @since 0.4.0
1228
+ */
1229
+ type BfBadgeEmphasis = 'soft' | 'strong';
1015
1230
  /**
1016
1231
  * Status label ("On track", "At risk") or, with `count`, a compact numeric pill for unread counts.
1017
1232
  *
1018
1233
  * @example
1019
1234
  * <bf-badge variant="warning">At risk</bf-badge>
1235
+ * <bf-badge variant="danger" emphasis="strong">Critical</bf-badge>
1020
1236
  * <bf-badge [count]="unread()" countLabel="unread messages" />
1021
1237
  * @summary Small status label in semantic tones, or a compact numeric pill for counts
1022
1238
  * @category Data display
@@ -1024,9 +1240,12 @@ type BfBadgeAppearance = BfBadgeVariant | 'info';
1024
1240
  * @slot default The badge text, shown when no `count` is set.
1025
1241
  */
1026
1242
  export declare class BfBadgeComponent {
1027
- /** Tint for the label style: neutral, primary, success, warning, danger or info. Ignored when
1028
- * `count` is set. */
1243
+ /** Tint for the label style: neutral, primary, success, warning, danger or info. Ignored by a
1244
+ * soft count pill; a strong one takes its fill from it. */
1029
1245
  readonly variant: import("@angular/core").InputSignal<BfBadgeAppearance>;
1246
+ /** `soft` (a pale tint, the default) or `strong` (a solid fill with contrasting text, also for
1247
+ * the count pill). */
1248
+ readonly emphasis: import("@angular/core").InputSignal<BfBadgeEmphasis>;
1030
1249
  /** Switches to the count pill; values above 99 render as "99+". */
1031
1250
  readonly count: import("@angular/core").InputSignal<number | undefined>;
1032
1251
  /** What the count counts ("unread messages"), read by assistive tech after the number ("99+
@@ -1034,7 +1253,7 @@ export declare class BfBadgeComponent {
1034
1253
  readonly countLabel: import("@angular/core").InputSignal<string>;
1035
1254
  readonly classes: import("@angular/core").Signal<string>;
1036
1255
  readonly countText: import("@angular/core").Signal<string | null>;
1037
- readonly countClasses = "inline-flex h-box min-w-box items-center justify-center rounded-control bg-primary px-1.75 text-micro font-bold leading-none text-on-primary";
1256
+ readonly countClasses: import("@angular/core").Signal<string>;
1038
1257
  static ɵfac: i0.ɵɵFactoryDeclaration<BfBadgeComponent, never>;
1039
1258
  static ɵcmp: i0.ɵɵComponentDeclaration<BfBadgeComponent, "bf-badge", never, {
1040
1259
  "variant": {
@@ -1042,6 +1261,11 @@ export declare class BfBadgeComponent {
1042
1261
  "required": false;
1043
1262
  "isSignal": true;
1044
1263
  };
1264
+ "emphasis": {
1265
+ "alias": "emphasis";
1266
+ "required": false;
1267
+ "isSignal": true;
1268
+ };
1045
1269
  "count": {
1046
1270
  "alias": "count";
1047
1271
  "required": false;
@@ -1104,6 +1328,9 @@ export declare class BfFieldComponent {
1104
1328
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1105
1329
  /** The id of the control inside the default slot. */
1106
1330
  readonly controlId: import("@angular/core").InputSignal<string>;
1331
+ /** The label's id (`"<controlId>-label"`), for a control a `<label for>` cannot name, such as a
1332
+ * `div[role=combobox]` that names itself with `aria-labelledby`. */
1333
+ readonly labelId: import("@angular/core").Signal<string>;
1107
1334
  readonly helpId: import("@angular/core").Signal<string>;
1108
1335
  readonly errorId: import("@angular/core").Signal<string>;
1109
1336
  /** The error paragraph: `error`, visually hidden while a press holds the layout. */
@@ -1764,173 +1991,255 @@ export declare class BfTextareaComponent implements FormValueControl<string> {
1764
1991
  }, never, never, true, never>;
1765
1992
  }
1766
1993
  /**
1767
- * Equality that matches a bound value to an option's value (`compareWith` of the choice controls).
1768
- * Called as `compareWith(optionValue, value)`: the option's value first, the bound value second.
1769
- *
1770
- * @summary Equality of choice values, for options that are objects rather than primitives
1771
- * @since 0.3.0
1772
- */
1773
- type BfCompareWith<T> = (optionValue: T, value: T) => boolean;
1774
- /**
1775
- * Index of the option whose value matches `value` under `compare`; `-1` for `null` (nothing
1776
- * selected) or a value no option matches. An option whose value is `null` therefore never shows as
1777
- * selected.
1778
- */
1779
- declare function choiceIndex<T>(options: readonly {
1780
- readonly value: T;
1781
- }[], value: T | null, compare: BfCompareWith<T>): number;
1782
- /**
1783
- * The native `value` of an option (`<option>`, radio): `String(value)` for a string, number or
1784
- * boolean, so native reads, form posts and browser tests see the real value; the option index for
1785
- * anything else (objects). The controls map a choice back by position, never by this key, so
1786
- * duplicate keys still resolve to the right option.
1787
- */
1788
- declare function choiceKey(value: unknown, index: number): string;
1789
- /**
1790
- * One choice of `bf-select`; `value` is any type (a string by default).
1994
+ * The look of a `bf-file-input`: a compact field with a pick button (`field`), or a drop zone
1995
+ * (`dropzone`). Both have the same API and behaviour.
1791
1996
  *
1792
- * @summary One choice of a select with its value, its label and a disabled flag
1793
- * @since 0.1.0
1794
- */
1795
- interface BfSelectOption<T = string> {
1796
- /** The value the select's `value` takes when this option is chosen. */
1797
- value: T;
1798
- /** The text of the native `option`. */
1799
- label: string;
1800
- /** Whether this option cannot be chosen. */
1801
- disabled?: boolean;
1997
+ * @summary The two looks of a file picker, a compact field or a drop zone
1998
+ * @since 0.5.0
1999
+ */
2000
+ type BfFileInputVariant = 'field' | 'dropzone';
2001
+ /** A chosen file as listed: its preview URL (images) or extension, and its formatted size. */
2002
+ interface BfFileInputRow {
2003
+ readonly file: File;
2004
+ readonly preview: string | null;
2005
+ readonly extension: string;
2006
+ readonly size: string;
1802
2007
  }
1803
- /**
1804
- * Native single-choice dropdown styled like the text field, with a placeholder option and
1805
- * Signal Forms binding. Generic over the option value (a string by default); `null` means nothing
1806
- * is selected.
2008
+ /** The upper-case extension of `name`, at most 4 characters; empty without one. */
2009
+ declare function extensionOf(name: string): string;
2010
+ /**
2011
+ * File picker field: a tonal "Choose file" button inside a field box that also takes dropped
2012
+ * files, with the chosen files listed under it (thumbnail, name, size, remove button). `accept`,
2013
+ * `maxSize` and `maxFiles` decide what is added; a file that breaks one is listed in red with the
2014
+ * reason until the next pick and emitted through `rejected`, and never enters the value. The value
2015
+ * is always a `File[]` (one file at most while `multiple` is off). It only picks: uploading the
2016
+ * files is the app's job. Bind `[(value)]` directly or `[formField]` from a signal form.
1807
2017
  *
1808
2018
  * @example
1809
- * <bf-select label="Project" placeholder="Pick a project" [options]="projects" [(value)]="projectId" />
1810
- * <bf-select label="Team" placeholder="Pick a team" [options]="teams" [formField]="task.teamId" />
1811
- * @summary Native single-choice dropdown styled like the text field, with Signal Forms binding
2019
+ * <bf-file-input label="Attachments" multiple accept=".pdf,image/*" [maxSize]="10485760" [(value)]="files" />
2020
+ * <bf-file-input label="Contract" accept=".pdf" [formField]="signup.contract" />
2021
+ * @summary File picker field that lists picked or dropped files and checks type, size and count
1812
2022
  * @category Forms
1813
- * @since 0.1.0
1814
- */
1815
- export declare class BfSelectComponent<T = string> implements FormValueControl<T | null> {
1816
- /** The selected option's value, `null` while nothing is selected; two-way bindable and bound by
1817
- * `[formField]`. */
1818
- readonly value: import("@angular/core").ModelSignal<T | null>;
1819
- /** The options to choose from. */
1820
- readonly options: import("@angular/core").InputSignal<readonly BfSelectOption<T>[]>;
1821
- /** Equality for matching `value` to an option (default `Object.is`); pass one for object values. */
1822
- readonly compareWith: import("@angular/core").InputSignal<BfCompareWith<T>>;
1823
- /** Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md`
1824
- * by default). */
2023
+ * @since 0.5.0
2024
+ */
2025
+ export declare class BfFileInputComponent implements FormValueControl<File[]> {
2026
+ /** The chosen files; two-way bindable and bound by `[formField]`. Empty while nothing is chosen,
2027
+ * at most one file while `multiple` is off. */
2028
+ readonly value: import("@angular/core").ModelSignal<File[]>;
2029
+ /** The look: `field` (a compact field with a pick button, the default) or `dropzone`. */
2030
+ readonly variant: import("@angular/core").InputSignal<BfFileInputVariant>;
2031
+ /** Allows several files: new picks and drops are added to the list. Off, a new file replaces
2032
+ * the one chosen. */
2033
+ readonly multiple: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2034
+ /** The accepted file types, in the native `accept` syntax: MIME types, `type/*` wildcards and
2035
+ * `.ext` extensions, comma-separated (`'.pdf,image/*'`). Empty accepts every file. Dropped files
2036
+ * are checked against it too. */
2037
+ readonly accept: import("@angular/core").InputSignal<string>;
2038
+ /** Largest file size in bytes; a larger file is rejected. Unset allows any size. */
2039
+ readonly maxSize: import("@angular/core").InputSignal<number | undefined>;
2040
+ /** Most files in the value with `multiple`, counting those already chosen; files past it are
2041
+ * rejected. Unset allows any number. */
2042
+ readonly maxFiles: import("@angular/core").InputSignal<number | undefined>;
2043
+ /** Height step of the field box: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()`
2044
+ * (`fieldSize`, `md` by default). */
1825
2045
  readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
1826
2046
  /** Visible label above the control. */
1827
2047
  readonly label: import("@angular/core").InputSignal<string>;
1828
2048
  /**
1829
- * Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label`
1830
- * is set.
2049
+ * Accessible name when no visible label is rendered, read before the control's visible text
2050
+ * ("Attachments, Choose files"). Ignored when `label` is set.
1831
2051
  */
1832
2052
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
1833
2053
  /**
1834
- * Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when
1835
- * `label` is set.
2054
+ * Id(s) of the element(s) that name the field when no visible label is rendered, read before the
2055
+ * control's visible text. Ignored when `label` is set.
1836
2056
  */
1837
2057
  readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
1838
- /** Disabled first option shown while nothing is selected. */
1839
- readonly placeholder: import("@angular/core").InputSignal<string>;
1840
2058
  /** Helper text under the control. */
1841
2059
  readonly helpText: import("@angular/core").InputSignal<string>;
1842
2060
  /** Explicit error text; takes precedence over Signal Forms errors. */
1843
2061
  readonly errorMessage: import("@angular/core").InputSignal<string>;
1844
- /** Disables the control. */
2062
+ /** Disables the control: no picking, dropping or removing, and the pick button is not focusable. */
1845
2063
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1846
- /** Shows the value but blocks changes; the select stays focusable and exposes `aria-readonly`. */
2064
+ /** Makes the control read-only: the files stay listed, with no pick button, drop or remove. */
1847
2065
  readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1848
- /** Marks the control required. */
2066
+ /** Marks the control required (label marker). */
1849
2067
  readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1850
2068
  /** Field validity from Signal Forms. */
1851
2069
  readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1852
- /** Whether the user has left the field. */
2070
+ /** Whether the user has left the field; errors show only once touched. */
1853
2071
  readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1854
- /** Validation errors from Signal Forms. */
2072
+ /** Validation errors from Signal Forms; the first message is shown once touched. */
1855
2073
  readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
1856
- /** Native name attribute. */
2074
+ /** Native name attribute; set, the hidden native file input carries the chosen files, so a
2075
+ * plain HTML form post includes them. */
1857
2076
  readonly name: import("@angular/core").InputSignal<string>;
1858
- /** Control id; generated when not set. */
2077
+ /** Control id (the pick button's); generated when not set. */
1859
2078
  readonly id: import("@angular/core").InputSignal<string>;
1860
- /** Emitted with the chosen value after a user change (never `null`). */
1861
- readonly changed: import("@angular/core").OutputEmitterRef<T>;
1862
- /** Emitted when the control gains focus. */
2079
+ /** Emitted with the new files right after the user adds or removes one, not on a model write. */
2080
+ readonly changed: import("@angular/core").OutputEmitterRef<File[]>;
2081
+ /** Emitted once per pick or drop in which at least one file broke a rule, with those files. */
2082
+ readonly rejected: import("@angular/core").OutputEmitterRef<BfRejectedFile[]>;
2083
+ /** Emitted when the pick button gains focus. */
1863
2084
  readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
1864
- /** Emitted when the control loses focus. */
2085
+ /** Emitted when the pick button loses focus. */
1865
2086
  readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
1866
2087
  /** Emitted on blur so Signal Forms marks the field touched. */
1867
2088
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
1868
- private readonly el;
2089
+ private readonly pick;
2090
+ private readonly native;
2091
+ private readonly removeButtons;
2092
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
1869
2093
  private readonly defaults;
2094
+ private readonly injector;
2095
+ private readonly document;
2096
+ /** Whether the primary pointer is coarse (touch), from `matchMedia`: the drop zone then says
2097
+ * "tap" and takes less padding. */
2098
+ protected readonly coarse: import("@angular/core").WritableSignal<boolean>;
2099
+ /** The files of the last pick or drop that broke a rule, until the next one or a dismiss. */
2100
+ protected readonly rejectedFiles: import("@angular/core").WritableSignal<readonly BfRejectedFile[]>;
2101
+ /** The polite live message after an add, a rejection or a remove. */
2102
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
2103
+ /** The pending write of the next live message. */
2104
+ private announceTimer;
2105
+ /** Whether a drag is over the box. */
2106
+ private readonly dragging;
2107
+ /** The object URL of each image file in the value, for its thumbnail. */
2108
+ private readonly previews;
2109
+ protected readonly thumbClasses: string;
2110
+ protected readonly removeClasses: string;
2111
+ protected readonly crossClasses: string;
2112
+ protected readonly rejectedRowClasses = "grid grid-cols-[2.5rem_minmax(0,1fr)_auto] items-center gap-3 rounded-tile border border-border bg-surface py-1.5 ps-2 pe-1.5";
2113
+ protected readonly extensionOf: typeof extensionOf;
2114
+ protected readonly badgeClasses = "grid size-11 place-items-center rounded-full bg-primary-soft text-primary-strong";
2115
+ protected readonly arrowClasses: string;
1870
2116
  /** The size in effect: `size`, else the app's `fieldSize` default. */
1871
2117
  protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
1872
- readonly wrapperClasses: import("@angular/core").Signal<string>;
1873
- /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
1874
- readonly selectedIndex: import("@angular/core").Signal<number>;
1875
- readonly showEmptyOption: import("@angular/core").Signal<boolean>;
1876
- private readonly labels;
2118
+ /** Whether the user can pick, drop and remove files. */
2119
+ protected readonly editable: import("@angular/core").Signal<boolean>;
1877
2120
  readonly errorText: import("@angular/core").Signal<string>;
1878
2121
  readonly showError: import("@angular/core").Signal<boolean>;
1879
- readonly describedBy: import("@angular/core").Signal<string | null>;
2122
+ /** `aria-describedby` of the pick button: the text beside it (hint or file name), then the help
2123
+ * and error paragraphs. */
2124
+ readonly describedBy: import("@angular/core").Signal<string>;
1880
2125
  /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
1881
2126
  readonly errorMessageId: import("@angular/core").Signal<string | null>;
1882
- /**
1883
- * `aria-label` value: only when there is no visible label, so the label stays the sole accessible
1884
- * name.
1885
- */
2127
+ /** `ariaLabel` in effect: only when there is no visible label. It names the control through a
2128
+ * hidden element, never `aria-label`, so the visible text stays part of the name. */
1886
2129
  readonly ariaLabelAttr: import("@angular/core").Signal<string | null>;
1887
- /** `aria-labelledby` value, under the same rule as `ariaLabelAttr`. */
1888
- readonly ariaLabelledbyAttr: import("@angular/core").Signal<string | null>;
1889
- readonly classes: import("@angular/core").Signal<string>;
1890
- /** Native `value` of an option: its value for a primitive, its index for an object. */
1891
- protected readonly key: typeof choiceKey;
2130
+ /** `aria-labelledby` of the pick button or drop zone: the field's name (the visible label, else
2131
+ * `ariaLabelledby`, else `ariaLabel`), then the control's visible text ("Contract, Choose file"),
2132
+ * so a speech-input user can say what they see (WCAG 2.5.3). */
2133
+ readonly ariaLabelledbyAttr: import("@angular/core").Signal<string>;
2134
+ /** The drop zone's `aria-describedby`: the help and error paragraphs (its text is its name). */
2135
+ protected readonly zoneDescribedBy: import("@angular/core").Signal<string | null>;
2136
+ /** The drop zone's text: the drop prompt, or the tap prompt under a coarse pointer. */
2137
+ protected readonly zoneText: import("@angular/core").Signal<string>;
2138
+ /** The text in the box: the file name (one file, single mode), the file count (read-only with
2139
+ * `multiple`), else the drop hint. */
2140
+ protected readonly boxText: import("@angular/core").Signal<string>;
2141
+ protected readonly rows: import("@angular/core").Signal<BfFileInputRow[]>;
2142
+ readonly boxClasses: import("@angular/core").Signal<string>;
2143
+ protected readonly zoneClasses: import("@angular/core").Signal<string>;
2144
+ protected readonly pickClasses: import("@angular/core").Signal<string>;
2145
+ protected readonly hintClasses: import("@angular/core").Signal<string>;
2146
+ protected readonly rowClasses: import("@angular/core").Signal<string>;
2147
+ constructor();
1892
2148
  /**
1893
- * Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
2149
+ * Focuses the pick button; Signal Forms calls it from `focusBoundControl()`. Read-only has no
2150
+ * pick button, so nothing is focused.
1894
2151
  * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
1895
2152
  */
1896
2153
  focus(options?: FocusOptions): void;
1897
2154
  /**
1898
- * Read-only: no picker on a mouse press. Cancelling the press also cancels its focus, so the
1899
- * select is focused explicitly, like any other read-only control. Touch screens open the
1900
- * platform picker without a cancellable press; a choice made there is reverted by `onChange`.
2155
+ * Opens the native picker from the drop zone while the control is editable.
1901
2156
  * @internal
1902
2157
  */
1903
- onMousedown(event: MouseEvent): void;
2158
+ openPicker(): void;
1904
2159
  /**
1905
- * Read-only: no picker or value change from the keyboard (arrows, Enter, Space, F4, type-ahead).
2160
+ * Enter and Space on the drop zone open the picker (and do not scroll the page).
1906
2161
  * @internal
1907
2162
  */
1908
- onKeydown(event: KeyboardEvent): void;
2163
+ onZoneKey(event: Event): void;
1909
2164
  /**
1910
- * Writes the chosen option's value to the model and emits `changed`; while read-only, restores
1911
- * the shown option instead.
2165
+ * Adds the native input's files, then empties it so picking the same file again fires `change`.
1912
2166
  * @internal
1913
2167
  */
1914
- onChange(el: HTMLSelectElement): void;
2168
+ onPicked(input: HTMLInputElement): void;
2169
+ /**
2170
+ * Allows a drop on the box and highlights it while the control is editable.
2171
+ * @internal
2172
+ */
2173
+ onDragOver(event: DragEvent): void;
2174
+ /**
2175
+ * Ends the highlight when the drag leaves the box (not when it moves onto a child).
2176
+ * @internal
2177
+ */
2178
+ onDragLeave(event: DragEvent, box: HTMLElement): void;
2179
+ /**
2180
+ * Adds the dropped files through the same rules as a pick; ignores them (but still keeps the
2181
+ * browser from opening them) while the control cannot be edited.
2182
+ * @internal
2183
+ */
2184
+ onDrop(event: DragEvent): void;
1915
2185
  /**
1916
2186
  * Marks the field touched and emits `blurred`.
1917
2187
  * @internal
1918
2188
  */
1919
2189
  onBlur(event: FocusEvent): void;
1920
- static ɵfac: i0.ɵɵFactoryDeclaration<BfSelectComponent<any>, never>;
1921
- static ɵcmp: i0.ɵɵComponentDeclaration<BfSelectComponent<any>, "bf-select", never, {
2190
+ /**
2191
+ * Removes the file at `index`, then focuses the next remove button, else the previous one,
2192
+ * else the pick button.
2193
+ * @internal
2194
+ */
2195
+ removeAt(index: number): void;
2196
+ /**
2197
+ * Dismisses the rejected file at `index` and returns focus to the pick button.
2198
+ * @internal
2199
+ */
2200
+ dismissAt(index: number): void;
2201
+ /** Runs `files` through the rules: accepted ones join the value, rejected ones are listed. */
2202
+ private add;
2203
+ /**
2204
+ * Puts `text` in the live region. The region is emptied first and the text follows after
2205
+ * `ANNOUNCE_DELAY`, so a message equal to the last one ("1 file added" twice) still changes the
2206
+ * DOM in a separate update and is announced again.
2207
+ */
2208
+ private announce;
2209
+ /** Keeps one object URL per image in `files`: creates the missing ones, revokes the rest. */
2210
+ private syncPreviews;
2211
+ /** With a `name`, puts the current files on the hidden native input, so a plain form post sends
2212
+ * them. */
2213
+ private writeNativeFiles;
2214
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfFileInputComponent, never>;
2215
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfFileInputComponent, "bf-file-input", never, {
1922
2216
  "value": {
1923
2217
  "alias": "value";
1924
2218
  "required": false;
1925
2219
  "isSignal": true;
1926
2220
  };
1927
- "options": {
1928
- "alias": "options";
1929
- "required": true;
2221
+ "variant": {
2222
+ "alias": "variant";
2223
+ "required": false;
1930
2224
  "isSignal": true;
1931
2225
  };
1932
- "compareWith": {
1933
- "alias": "compareWith";
2226
+ "multiple": {
2227
+ "alias": "multiple";
2228
+ "required": false;
2229
+ "isSignal": true;
2230
+ };
2231
+ "accept": {
2232
+ "alias": "accept";
2233
+ "required": false;
2234
+ "isSignal": true;
2235
+ };
2236
+ "maxSize": {
2237
+ "alias": "maxSize";
2238
+ "required": false;
2239
+ "isSignal": true;
2240
+ };
2241
+ "maxFiles": {
2242
+ "alias": "maxFiles";
1934
2243
  "required": false;
1935
2244
  "isSignal": true;
1936
2245
  };
@@ -1954,11 +2263,6 @@ export declare class BfSelectComponent<T = string> implements FormValueControl<T
1954
2263
  "required": false;
1955
2264
  "isSignal": true;
1956
2265
  };
1957
- "placeholder": {
1958
- "alias": "placeholder";
1959
- "required": false;
1960
- "isSignal": true;
1961
- };
1962
2266
  "helpText": {
1963
2267
  "alias": "helpText";
1964
2268
  "required": false;
@@ -2012,60 +2316,373 @@ export declare class BfSelectComponent<T = string> implements FormValueControl<T
2012
2316
  }, {
2013
2317
  "value": "valueChange";
2014
2318
  "changed": "changed";
2319
+ "rejected": "rejected";
2015
2320
  "focused": "focused";
2016
2321
  "blurred": "blurred";
2017
2322
  "touch": "touch";
2018
2323
  }, never, never, true, never>;
2019
2324
  }
2020
2325
  /**
2021
- * Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
2022
- * checkbox contract so `[formField]` binds `checked`.
2326
+ * The shape of a `bf-image-input` tile: a circle (`round`, for avatars) or a rounded square
2327
+ * (`square`, for logos and covers).
2328
+ *
2329
+ * @summary The two tile shapes of an image picker, a circle or a rounded square
2330
+ * @since 0.5.0
2331
+ */
2332
+ type BfImageInputShape = 'round' | 'square';
2333
+ /**
2334
+ * Single-image picker for an avatar, logo or cover: a round or square tile that shows the image
2335
+ * (or a camera glyph while empty), with the label, help text and Upload/Replace and Remove buttons
2336
+ * beside it. Clicking the tile or Upload opens the native picker; an image dropped on the tile is
2337
+ * taken too. `src` shows an image the app already has until the user picks a new one or removes
2338
+ * it. A pick that breaks `accept` or `maxSize` keeps the current image, shows the reason under the
2339
+ * buttons and is emitted through `rejected`. It only picks: uploading is the app's job. Bind
2340
+ * `[(value)]` directly or `[formField]` from a signal form.
2023
2341
  *
2024
2342
  * @example
2025
- * <bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
2026
- * <bf-checkbox label="Notify me" [formField]="prefs.notify" />
2027
- * @summary Rounded checkbox with an optional label that binds to Signal Forms
2343
+ * <bf-image-input label="Profile picture" [(value)]="avatar" [(src)]="avatarUrl" />
2344
+ * <bf-image-input label="Logo" shape="square" [maxSize]="2097152" [formField]="company.logo" />
2345
+ * @summary Single-image picker with a round or square preview tile and replace or remove
2028
2346
  * @category Forms
2029
- * @since 0.1.0
2030
- * @slot default Rich label content next to the box, before the `label` text.
2031
- */
2032
- export declare class BfCheckboxComponent implements FormCheckboxControl {
2033
- /** Whether the box is checked; two-way bindable and bound by `[formField]`. */
2034
- readonly checked: import("@angular/core").ModelSignal<boolean>;
2035
- /** Label text; the default slot works too. */
2347
+ * @since 0.5.0
2348
+ */
2349
+ export declare class BfImageInputComponent implements FormValueControl<File | null> {
2350
+ /** The picked image; two-way bindable and bound by `[formField]`. Null while none is picked. */
2351
+ readonly value: import("@angular/core").ModelSignal<File | null>;
2352
+ /** URL of an image the app already has (the current avatar), shown while `value` is null. Remove
2353
+ * sets it to null; two-way bindable. */
2354
+ readonly src: import("@angular/core").ModelSignal<string | null>;
2355
+ /** The tile's shape: `round` (a circle, the default) or `square` (a rounded square). */
2356
+ readonly shape: import("@angular/core").InputSignal<BfImageInputShape>;
2357
+ /** Tile size: sm 64, md 96, lg 128 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by
2358
+ * default). */
2359
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2360
+ /** The accepted file types, in the native `accept` syntax (`'image/png,image/jpeg'`); dropped
2361
+ * files are checked against it too. */
2362
+ readonly accept: import("@angular/core").InputSignal<string>;
2363
+ /** Largest file size in bytes; a larger image is rejected. Unset allows any size. */
2364
+ readonly maxSize: import("@angular/core").InputSignal<number | undefined>;
2365
+ /** Visible label beside the tile. */
2036
2366
  readonly label: import("@angular/core").InputSignal<string>;
2037
- /** Accessible name when there is no visible `label`, e.g. a row-selection box. */
2367
+ /** Accessible name of the tile when no visible label is rendered, in place of "Choose image".
2368
+ * Ignored when `label` is set. */
2038
2369
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
2039
- /** Id(s) of the element(s) that name the control when no visible label is rendered (e.g. a
2040
- * table column header). */
2370
+ /** Id(s) of the element(s) that name the tile when no visible label is rendered. Ignored when
2371
+ * `label` is set. */
2041
2372
  readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
2042
- /** Height step of the row: sm 32, md 40, lg 48 px (44 px under a coarse pointer for sm and md);
2043
- * unset follows `provideBfDefaults()` (`controlSize`, `md` by default). */
2044
- readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2045
- /** Shows the mixed state (e.g. "select all" with some rows selected); two-way bindable. A user
2046
- * change clears it, like the native checkbox, so the box and `aria-checked` agree. */
2047
- readonly indeterminate: import("@angular/core").ModelSignal<boolean>;
2048
- /** Disables the control. */
2373
+ /** Helper text under the label. */
2374
+ readonly helpText: import("@angular/core").InputSignal<string>;
2375
+ /** Explicit error text; takes precedence over Signal Forms errors. */
2376
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
2377
+ /** Disables the control: the tile and buttons are disabled and drops are ignored. */
2049
2378
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2050
- /** Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. */
2379
+ /** Makes the control read-only: the image shows in a plain tile, with no buttons or drop. */
2051
2380
  readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2052
- /** Marks the control required. */
2381
+ /** Marks the control required (label marker). A `value` or a `src` fills it; see the docs for a
2382
+ * validator that counts `src`. */
2053
2383
  readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2054
2384
  /** Field validity from Signal Forms. */
2055
2385
  readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2056
- /** Whether the user has left the field. */
2386
+ /** Whether the user has left the field; errors show only once touched. */
2057
2387
  readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2058
- /** Validation errors from Signal Forms. */
2388
+ /** Validation errors from Signal Forms; the first message is shown once touched. */
2059
2389
  readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
2060
- /** Explicit error text; takes precedence over Signal Forms errors. */
2061
- readonly errorMessage: import("@angular/core").InputSignal<string>;
2062
- /** Native name attribute. */
2390
+ /** Native name attribute; set, the hidden native file input carries the picked image, so a plain
2391
+ * HTML form post includes it. */
2063
2392
  readonly name: import("@angular/core").InputSignal<string>;
2064
- /** Control id; generated when not set. */
2393
+ /** Control id (the tile's); generated when not set. */
2065
2394
  readonly id: import("@angular/core").InputSignal<string>;
2066
- /** Emitted with the new checked state after a user change. */
2067
- readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
2068
- /** Emitted on blur so Signal Forms marks the field touched. */
2395
+ /** Emitted with the new image (or null after Remove) right after a user change, not on a model
2396
+ * write. */
2397
+ readonly changed: import("@angular/core").OutputEmitterRef<File | null>;
2398
+ /** Emitted once per pick or drop in which a file broke a rule, with those files. */
2399
+ readonly rejected: import("@angular/core").OutputEmitterRef<BfRejectedFile[]>;
2400
+ /** Emitted when the tile gains focus. */
2401
+ readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
2402
+ /** Emitted when the tile loses focus. */
2403
+ readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
2404
+ /** Emitted on blur of the tile or a button so Signal Forms marks the field touched. */
2405
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
2406
+ private readonly tile;
2407
+ private readonly native;
2408
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
2409
+ private readonly defaults;
2410
+ /** Whether a drag is over the tile. */
2411
+ protected readonly dragging: import("@angular/core").WritableSignal<boolean>;
2412
+ /** The reason the last pick or drop was refused, until the next one or Remove. */
2413
+ protected readonly rejection: import("@angular/core").WritableSignal<string>;
2414
+ /** The polite live message: the rejection reason, written after a pause. */
2415
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
2416
+ /** The pending write of the live message. */
2417
+ private announceTimer;
2418
+ /** The object URL of the picked image, with the file it was made for. */
2419
+ private readonly objectUrl;
2420
+ protected readonly uploadClasses: string;
2421
+ protected readonly removeClasses: string;
2422
+ /** The size in effect: `size`, else the app's `fieldSize` default. */
2423
+ protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
2424
+ /** Whether the user can pick, drop and remove. */
2425
+ protected readonly editable: import("@angular/core").Signal<boolean>;
2426
+ /** What the tile shows: the picked image's object URL, else `src`, else nothing. */
2427
+ protected readonly preview: import("@angular/core").Signal<string | null>;
2428
+ /** Whether an image (picked or existing) is set: Upload reads Replace and Remove shows. */
2429
+ protected readonly shows: import("@angular/core").Signal<boolean>;
2430
+ readonly errorText: import("@angular/core").Signal<string>;
2431
+ readonly showError: import("@angular/core").Signal<boolean>;
2432
+ /** `aria-describedby` of the tile: the label (the tile's name is the action), then the help and
2433
+ * error paragraphs. */
2434
+ readonly describedBy: import("@angular/core").Signal<string | null>;
2435
+ /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
2436
+ readonly errorMessageId: import("@angular/core").Signal<string | null>;
2437
+ /** The tile's `aria-labelledby`: `ariaLabelledby` while there is no visible label. */
2438
+ protected readonly tileLabelledby: import("@angular/core").Signal<string | null>;
2439
+ /** The tile's `aria-label`: "Choose image", or `ariaLabel` while there is no visible label. */
2440
+ protected readonly tileLabel: import("@angular/core").Signal<string | null>;
2441
+ /** The buttons' `aria-describedby`: the label, so "Remove" says what it removes. */
2442
+ protected readonly actionDescribedBy: import("@angular/core").Signal<string | null>;
2443
+ protected readonly tileClasses: import("@angular/core").Signal<string>;
2444
+ protected readonly glyphClasses: import("@angular/core").Signal<string>;
2445
+ constructor();
2446
+ /**
2447
+ * Focuses the tile; Signal Forms calls it from `focusBoundControl()`. Read-only has no button, so
2448
+ * nothing is focused.
2449
+ * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
2450
+ */
2451
+ focus(options?: FocusOptions): void;
2452
+ /**
2453
+ * Takes the native input's file, then empties it so picking the same file again fires `change`.
2454
+ * @internal
2455
+ */
2456
+ onPicked(input: HTMLInputElement): void;
2457
+ /**
2458
+ * Allows a drop on the tile and highlights it while the control is editable. Otherwise it still
2459
+ * cancels the event, so the browser does not open a dropped file and leave the page.
2460
+ * @internal
2461
+ */
2462
+ onDragOver(event: DragEvent): void;
2463
+ /**
2464
+ * Takes the dropped file through the same rules as a pick; ignores it (but still keeps the
2465
+ * browser from opening it) while the control cannot be edited.
2466
+ * @internal
2467
+ */
2468
+ onDrop(event: DragEvent): void;
2469
+ /**
2470
+ * Marks the field touched and emits `blurred`.
2471
+ * @internal
2472
+ */
2473
+ onBlur(event: FocusEvent): void;
2474
+ /**
2475
+ * Clears the picked and the existing image, then focuses the tile.
2476
+ * @internal
2477
+ */
2478
+ remove(): void;
2479
+ /** Runs `files` through the rules: the first accepted one replaces the image; a refusal keeps it
2480
+ * and shows the reason. */
2481
+ private take;
2482
+ /**
2483
+ * Writes `text` to the rejection line at once and announces it through the live region. The
2484
+ * region is emptied first and the text follows after `ANNOUNCE_DELAY`, so the same reason twice
2485
+ * is still announced again.
2486
+ */
2487
+ private showRejection;
2488
+ /** Keeps one object URL for `file`: makes it when the file changes, revokes the previous one. */
2489
+ private syncObjectUrl;
2490
+ /** With a `name`, puts the picked image on the hidden native input, so a plain form post sends
2491
+ * it. */
2492
+ private writeNativeFiles;
2493
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfImageInputComponent, never>;
2494
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfImageInputComponent, "bf-image-input", never, {
2495
+ "value": {
2496
+ "alias": "value";
2497
+ "required": false;
2498
+ "isSignal": true;
2499
+ };
2500
+ "src": {
2501
+ "alias": "src";
2502
+ "required": false;
2503
+ "isSignal": true;
2504
+ };
2505
+ "shape": {
2506
+ "alias": "shape";
2507
+ "required": false;
2508
+ "isSignal": true;
2509
+ };
2510
+ "size": {
2511
+ "alias": "size";
2512
+ "required": false;
2513
+ "isSignal": true;
2514
+ };
2515
+ "accept": {
2516
+ "alias": "accept";
2517
+ "required": false;
2518
+ "isSignal": true;
2519
+ };
2520
+ "maxSize": {
2521
+ "alias": "maxSize";
2522
+ "required": false;
2523
+ "isSignal": true;
2524
+ };
2525
+ "label": {
2526
+ "alias": "label";
2527
+ "required": false;
2528
+ "isSignal": true;
2529
+ };
2530
+ "ariaLabel": {
2531
+ "alias": "ariaLabel";
2532
+ "required": false;
2533
+ "isSignal": true;
2534
+ };
2535
+ "ariaLabelledby": {
2536
+ "alias": "ariaLabelledby";
2537
+ "required": false;
2538
+ "isSignal": true;
2539
+ };
2540
+ "helpText": {
2541
+ "alias": "helpText";
2542
+ "required": false;
2543
+ "isSignal": true;
2544
+ };
2545
+ "errorMessage": {
2546
+ "alias": "errorMessage";
2547
+ "required": false;
2548
+ "isSignal": true;
2549
+ };
2550
+ "disabled": {
2551
+ "alias": "disabled";
2552
+ "required": false;
2553
+ "isSignal": true;
2554
+ };
2555
+ "readonly": {
2556
+ "alias": "readonly";
2557
+ "required": false;
2558
+ "isSignal": true;
2559
+ };
2560
+ "required": {
2561
+ "alias": "required";
2562
+ "required": false;
2563
+ "isSignal": true;
2564
+ };
2565
+ "invalid": {
2566
+ "alias": "invalid";
2567
+ "required": false;
2568
+ "isSignal": true;
2569
+ };
2570
+ "touched": {
2571
+ "alias": "touched";
2572
+ "required": false;
2573
+ "isSignal": true;
2574
+ };
2575
+ "errors": {
2576
+ "alias": "errors";
2577
+ "required": false;
2578
+ "isSignal": true;
2579
+ };
2580
+ "name": {
2581
+ "alias": "name";
2582
+ "required": false;
2583
+ "isSignal": true;
2584
+ };
2585
+ "id": {
2586
+ "alias": "id";
2587
+ "required": false;
2588
+ "isSignal": true;
2589
+ };
2590
+ }, {
2591
+ "value": "valueChange";
2592
+ "src": "srcChange";
2593
+ "changed": "changed";
2594
+ "rejected": "rejected";
2595
+ "focused": "focused";
2596
+ "blurred": "blurred";
2597
+ "touch": "touch";
2598
+ }, never, never, true, never>;
2599
+ }
2600
+ /**
2601
+ * Equality that matches a bound value to an option's value (`compareWith` of the choice controls).
2602
+ * Called as `compareWith(optionValue, value)`: the option's value first, the bound value second.
2603
+ *
2604
+ * @summary Equality of choice values, for options that are objects rather than primitives
2605
+ * @since 0.3.0
2606
+ */
2607
+ type BfCompareWith<T> = (optionValue: T, value: T) => boolean;
2608
+ /**
2609
+ * Index of the option whose value matches `value` under `compare`; `-1` for `null` (nothing
2610
+ * selected) or a value no option matches. An option whose value is `null` therefore never shows as
2611
+ * selected.
2612
+ */
2613
+ declare function choiceIndex<T>(options: readonly {
2614
+ readonly value: T;
2615
+ }[], value: T | null, compare: BfCompareWith<T>): number;
2616
+ /**
2617
+ * The native `value` of an option (`<option>`, radio): `String(value)` for a string, number or
2618
+ * boolean, so native reads, form posts and browser tests see the real value; the option index for
2619
+ * anything else (objects). The controls map a choice back by position, never by this key, so
2620
+ * duplicate keys still resolve to the right option.
2621
+ */
2622
+ declare function choiceKey(value: unknown, index: number): string;
2623
+ /**
2624
+ * One choice of `bf-select`; `value` is any type (a string by default).
2625
+ *
2626
+ * @summary One choice of a select with its value, its label and a disabled flag
2627
+ * @since 0.1.0
2628
+ */
2629
+ interface BfSelectOption<T = string> {
2630
+ /** The value the select's `value` takes when this option is chosen. */
2631
+ value: T;
2632
+ /** The text of the native `option`. */
2633
+ label: string;
2634
+ /** Whether this option cannot be chosen. */
2635
+ disabled?: boolean;
2636
+ }
2637
+ /**
2638
+ * Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
2639
+ * checkbox contract so `[formField]` binds `checked`.
2640
+ *
2641
+ * @example
2642
+ * <bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
2643
+ * <bf-checkbox label="Notify me" [formField]="prefs.notify" />
2644
+ * @summary Rounded checkbox with an optional label that binds to Signal Forms
2645
+ * @category Forms
2646
+ * @since 0.1.0
2647
+ * @slot default Rich label content next to the box, before the `label` text.
2648
+ */
2649
+ export declare class BfCheckboxComponent implements FormCheckboxControl {
2650
+ /** Whether the box is checked; two-way bindable and bound by `[formField]`. */
2651
+ readonly checked: import("@angular/core").ModelSignal<boolean>;
2652
+ /** Label text; the default slot works too. */
2653
+ readonly label: import("@angular/core").InputSignal<string>;
2654
+ /** Accessible name when there is no visible `label`, e.g. a row-selection box. */
2655
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
2656
+ /** Id(s) of the element(s) that name the control when no visible label is rendered (e.g. a
2657
+ * table column header). */
2658
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
2659
+ /** Height step of the row: sm 32, md 40, lg 48 px (44 px under a coarse pointer for sm and md);
2660
+ * unset follows `provideBfDefaults()` (`controlSize`, `md` by default). */
2661
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2662
+ /** Shows the mixed state (e.g. "select all" with some rows selected); two-way bindable. A user
2663
+ * change clears it, like the native checkbox, so the box and `aria-checked` agree. */
2664
+ readonly indeterminate: import("@angular/core").ModelSignal<boolean>;
2665
+ /** Disables the control. */
2666
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2667
+ /** Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. */
2668
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2669
+ /** Marks the control required. */
2670
+ readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2671
+ /** Field validity from Signal Forms. */
2672
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2673
+ /** Whether the user has left the field. */
2674
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2675
+ /** Validation errors from Signal Forms. */
2676
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
2677
+ /** Explicit error text; takes precedence over Signal Forms errors. */
2678
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
2679
+ /** Native name attribute. */
2680
+ readonly name: import("@angular/core").InputSignal<string>;
2681
+ /** Control id; generated when not set. */
2682
+ readonly id: import("@angular/core").InputSignal<string>;
2683
+ /** Emitted with the new checked state after a user change. */
2684
+ readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
2685
+ /** Emitted on blur so Signal Forms marks the field touched. */
2069
2686
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
2070
2687
  private readonly el;
2071
2688
  private readonly labels;
@@ -2917,6 +3534,8 @@ export declare class BfSegmentedComponent<T = string> implements FormValueContro
2917
3534
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
2918
3535
  /** The rendered radio buttons, in option order; used to move focus after keyboard selection. */
2919
3536
  readonly radios: import("@angular/core").Signal<readonly ElementRef<HTMLButtonElement>[]>;
3537
+ private readonly track;
3538
+ private readonly indicator;
2920
3539
  /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
2921
3540
  readonly selectedIndex: import("@angular/core").Signal<number>;
2922
3541
  /** Index of the option that owns the roving tab stop: the selected enabled option, or the first
@@ -2941,6 +3560,12 @@ export declare class BfSegmentedComponent<T = string> implements FormValueContro
2941
3560
  private readonly mode;
2942
3561
  /** The classes of the scroller around the track. */
2943
3562
  protected readonly scrollerClasses: import("@angular/core").Signal<string>;
3563
+ /** Whether the sliding pill has been measured and placed, so it, not the item, paints the
3564
+ * selection. */
3565
+ private readonly placed;
3566
+ /** The classes of the sliding pill: hidden until placed and while nothing is selected, the
3567
+ * disabled outline on a disabled selected segment. */
3568
+ protected readonly indicatorClasses: import("@angular/core").Signal<string>;
2944
3569
  readonly trackClasses: import("@angular/core").Signal<string>;
2945
3570
  /**
2946
3571
  * The classes of the segment at `index`.
@@ -3173,12 +3798,18 @@ interface BfActionHost {
3173
3798
  registerAction(): () => void;
3174
3799
  /** Whether the surface's action has keyboard focus (`:focus-visible`), for the focus ring. */
3175
3800
  setActionFocusVisible(visible: boolean): void;
3801
+ /**
3802
+ * The id of an element whose text starts the action's accessible name (a list row's visually
3803
+ * hidden "Unread"), or `null`; the action then names itself `aria-labelledby="<id> <own id>"`.
3804
+ */
3805
+ readonly actionLabelPrefix?: Signal<string | null>;
3176
3806
  }
3177
3807
  declare const BF_ACTION_HOST: InjectionToken<BfActionHost>;
3178
3808
  /**
3179
3809
  * One row of a list: leading slot (avatar, icon tile), title and subtitle, trailing slot
3180
3810
  * (time, toggle, badge) and an optional chevron for rows that navigate. A navigation row puts a
3181
3811
  * `bfAction` link in the `title` slot, so the row stays a list item and the link covers it.
3812
+ * `unread` marks a row not yet opened with a dot and a bolder title.
3182
3813
  *
3183
3814
  * @example
3184
3815
  * <bf-list-item subtitle="lena@studio.ch" chevron>
@@ -3210,13 +3841,26 @@ export declare class BfListItemComponent implements BfActionHost {
3210
3841
  * default they wrap, so nothing is lost at large text sizes.
3211
3842
  */
3212
3843
  readonly truncate: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
3844
+ /** Mark the row as not yet opened: a dot at the inline start, a bolder title and a visually
3845
+ * hidden state (the `listItemUnread` label) read before the title. */
3846
+ readonly unread: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
3213
3847
  /** Emitted when an interactive row is activated. */
3214
3848
  readonly clicked: import("@angular/core").OutputEmitterRef<Event>;
3215
3849
  /** `listitem` inside a `bf-list`, else no role. */
3216
3850
  protected readonly role: string | null;
3851
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
3852
+ protected readonly unreadDot = "absolute -start-2.5 mt-[7px] size-1.5 rounded-full bg-primary forced-colors:bg-[CanvasText]";
3217
3853
  private readonly actions;
3218
3854
  private readonly actionFocusVisible;
3219
3855
  private readonly actionState;
3856
+ protected readonly hasAction: import("@angular/core").Signal<boolean>;
3857
+ private unreadIdValue;
3858
+ /** The id of the unread state text, while `unread` is set. */
3859
+ protected readonly unreadId: import("@angular/core").Signal<string | null>;
3860
+ /** @internal The unread state starts the name of the row's `bfAction`. */
3861
+ readonly actionLabelPrefix: import("@angular/core").Signal<string | null>;
3862
+ /** The title weight: bolder on an unread row. */
3863
+ protected readonly titleWeight: import("@angular/core").Signal<"font-extrabold" | "font-bold">;
3220
3864
  /** Wrap or truncate classes of the title, the title slot and the subtitle. */
3221
3865
  protected readonly textClasses: import("@angular/core").Signal<string>;
3222
3866
  readonly classes: import("@angular/core").Signal<string>;
@@ -3256,6 +3900,11 @@ export declare class BfListItemComponent implements BfActionHost {
3256
3900
  "required": false;
3257
3901
  "isSignal": true;
3258
3902
  };
3903
+ "unread": {
3904
+ "alias": "unread";
3905
+ "required": false;
3906
+ "isSignal": true;
3907
+ };
3259
3908
  }, {
3260
3909
  "clicked": "clicked";
3261
3910
  }, never, ["[bfSlot=leading]", "[bfSlot=title]", "*", "[bfSlot=trailing]"], true, never>;
@@ -3333,41 +3982,205 @@ export declare class BfCardComponent implements BfActionHost {
3333
3982
  }, never, ["[bfSlot=header]", "*", "[bfSlot=footer]"], true, never>;
3334
3983
  }
3335
3984
  /**
3336
- * The track height of a `bf-progress`: sm or md.
3337
- *
3338
- * @summary The two track heights of a progress bar, thin and regular
3339
- * @since 0.1.0
3340
- */
3341
- type BfProgressSize = 'sm' | 'md';
3342
- /**
3343
- * The fill colour of a `bf-progress`: primary, success, warning or danger.
3985
+ * The aspect ratio of a `bf-media-card`'s image area: 16:9 (default), 4:3, square or 3:4.
3344
3986
  *
3345
- * @summary The four fill colours of a progress bar, from primary to danger
3346
- * @since 0.1.0
3347
- */
3348
- type BfProgressTone = 'primary' | 'success' | 'warning' | 'danger';
3349
- /**
3350
- * Progress bar for completion and quotas, determinate by default, or `indeterminate` while the
3351
- * amount of work is unknown.
3987
+ * @summary The four aspect ratios of a media card's image area, wide to portrait
3988
+ * @since 0.5.0
3989
+ */
3990
+ type BfMediaCardAspect = '16/9' | '4/3' | '1/1' | '3/4';
3991
+ /** What the image area shows. */
3992
+ type MediaState = 'empty' | 'loading' | 'loaded' | 'failed';
3993
+ /**
3994
+ * Card for a picture and its story: a game pitch, a generated image, a product. The top is an
3995
+ * image area at a fixed aspect ratio that shows a shimmering skeleton while the picture loads
3996
+ * (also while it does not exist yet: set `loading`), fades the picture in when it arrives, and
3997
+ * shows a message with a Retry slot when it fails. Below it sit a body and a footer. Make the card
3998
+ * clickable with a `bfAction` link or button in its title, as on `bf-card`, and selectable with
3999
+ * `selectable` and `[(selected)]`.
3352
4000
  *
3353
4001
  * @example
3354
- * <bf-progress [value]="68" label="Atlas rebrand" valueText="12 of 18 tasks" />
3355
- * <bf-progress indeterminate label="Uploading" />
3356
- * @summary Progress bar in two sizes and four tones for completion, quotas and unknown waits
4002
+ * <bf-media-card [src]="pitch.cover" alt="Neon city skyline" loadingLabel="Drawing…" selectable [(selected)]="picked">
4003
+ * <h3 class="text-heading"><a bfAction [routerLink]="['/pitches', pitch.id]">Starfall</a></h3>
4004
+ * <p class="text-body text-text-secondary">A roguelike in a falling city.</p>
4005
+ * <bf-button bfSlot="failed-action" size="sm" variant="tonal" (clicked)="retry()">Retry</bf-button>
4006
+ * </bf-media-card>
4007
+ * @summary Card with an image that loads, fades in or fails, plus a body and actions
3357
4008
  * @category Data display
3358
- * @since 0.1.0
4009
+ * @since 0.5.0
4010
+ * @slot default The card body: a title (with an optional bfAction link or button) and text.
4011
+ * @slot footer Content below the body, such as chips and buttons; it sits on the card's bottom edge.
4012
+ * @slot failed-action An action shown under the failure message, such as a Retry button.
3359
4013
  */
3360
- export declare class BfProgressComponent {
3361
- /** Completion in percent; clamped to 0–100. Without it (and without `indeterminate`) the bar
3362
- * shows 0 %. Ignored while `indeterminate`. */
3363
- readonly value: import("@angular/core").InputSignal<number | undefined>;
3364
- /** The amount of work is unknown: the native bar carries no value (assistive tech announces it
3365
- * as busy) and a stripe runs across the track, a static 40 % bar under reduced motion. */
3366
- readonly indeterminate: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
3367
- /** The value in words for assistive tech (`aria-valuetext`), such as "12 of 18 tasks"; the
3368
- * percentage is announced while it is empty. */
3369
- readonly valueText: import("@angular/core").InputSignal<string>;
3370
- /** Accessible name of the bar ("Atlas rebrand"). */
4014
+ export declare class BfMediaCardComponent implements BfActionHost {
4015
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
4016
+ /** URL of the image; empty shows the skeleton while `loading`, else an empty area. A new URL
4017
+ * starts over: skeleton, fade-in, and a failed load is forgotten. */
4018
+ readonly src: import("@angular/core").InputSignal<string>;
4019
+ /** Alternative text of the image; empty marks it decorative. */
4020
+ readonly alt: import("@angular/core").InputSignal<string>;
4021
+ /** Aspect ratio of the image area: `16/9` (default), `4/3`, `1/1` or `3/4`. */
4022
+ readonly aspect: import("@angular/core").InputSignal<BfMediaCardAspect>;
4023
+ /** Shows the skeleton while there is no `src` yet (a picture that is still being made). */
4024
+ readonly loading: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4025
+ /** Shows the failure message (the app could not make the picture); the app clears it. A load
4026
+ * error of `src` shows it too, until `src` changes. */
4027
+ readonly failed: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4028
+ /** Caption shown over the skeleton while loading ("Drawing…"); empty shows none and tells
4029
+ * screen readers `provideBfLabels()`'s `loading` ("Loading"). */
4030
+ readonly loadingLabel: import("@angular/core").InputSignal<string>;
4031
+ /** Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`. */
4032
+ readonly failedLabel: import("@angular/core").InputSignal<string>;
4033
+ /** Adds a select button (a toggle with `aria-pressed`): the whole card toggles `selected`,
4034
+ * or, with a `bfAction` in the card, a round check button in the image's corner does. */
4035
+ readonly selectable: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4036
+ /** Whether the card is selected (a ring around it and a filled check); two-way bindable. Drawn
4037
+ * only on a `selectable` card. */
4038
+ readonly selected: import("@angular/core").ModelSignal<boolean>;
4039
+ /** The card's name in its select button ("Select Starfall"); empty names it by the body's text. */
4040
+ readonly label: import("@angular/core").InputSignal<string>;
4041
+ /** The user selected or cleared the card; emits the new `selected`. */
4042
+ readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
4043
+ /** The image of `src` has loaded. */
4044
+ readonly loaded: import("@angular/core").OutputEmitterRef<void>;
4045
+ /** The image of `src` failed to load (the card shows the failure message). */
4046
+ readonly errored: import("@angular/core").OutputEmitterRef<void>;
4047
+ private readonly actions;
4048
+ private readonly actionFocusVisible;
4049
+ private readonly selectFocusVisible;
4050
+ protected readonly bodyId: string;
4051
+ protected readonly selectTextId: string;
4052
+ protected readonly labelId: string;
4053
+ protected readonly skeletonLayerClasses = "absolute inset-0 transition-opacity duration-slow ease-md motion-reduce:transition-none";
4054
+ /** The image's own progress, reset by every new `src`. */
4055
+ private readonly imageStatus;
4056
+ /** True once the skeleton has faded out under a loaded image; reset by every new `src`. */
4057
+ private readonly skeletonGone;
4058
+ protected readonly state: import("@angular/core").Signal<MediaState>;
4059
+ /** The image, keyed by URL so a new `src` gets a fresh element (and a fresh fade). */
4060
+ protected readonly imageSources: import("@angular/core").Signal<string[]>;
4061
+ protected readonly skeletonShown: import("@angular/core").Signal<boolean>;
4062
+ /** The body names the select button (no `label`): only then does it carry its id, so a plain
4063
+ * card renders no counter-made id. */
4064
+ protected readonly bodyNamesSelect: import("@angular/core").Signal<boolean>;
4065
+ protected readonly failedText: import("@angular/core").Signal<string>;
4066
+ /** The select button stretches over the card unless the card has its own bfAction. */
4067
+ private readonly selectMode;
4068
+ private readonly cue;
4069
+ protected readonly classes: import("@angular/core").Signal<string>;
4070
+ protected readonly mediaClasses: import("@angular/core").Signal<string>;
4071
+ protected readonly selectClasses: import("@angular/core").Signal<string>;
4072
+ protected readonly discClasses: import("@angular/core").Signal<string>;
4073
+ /** @internal Called by a `bfAction` in the card. */
4074
+ registerAction(): () => void;
4075
+ /** @internal Called by the card's `bfAction` on focus and blur. */
4076
+ setActionFocusVisible(visible: boolean): void;
4077
+ /** @internal The image's load event. */
4078
+ imageLoaded(): void;
4079
+ /** @internal The image's error event. */
4080
+ imageFailed(): void;
4081
+ /** @internal The skeleton layer's fade-out ended: it leaves the DOM (its shimmer stops). */
4082
+ skeletonFaded(event: TransitionEvent): void;
4083
+ /** @internal The select button was clicked. */
4084
+ toggle(): void;
4085
+ /** @internal The select button took focus. */
4086
+ selectFocused(event: FocusEvent): void;
4087
+ /** @internal The select button lost focus. */
4088
+ selectBlurred(): void;
4089
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfMediaCardComponent, never>;
4090
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfMediaCardComponent, "bf-media-card", never, {
4091
+ "src": {
4092
+ "alias": "src";
4093
+ "required": false;
4094
+ "isSignal": true;
4095
+ };
4096
+ "alt": {
4097
+ "alias": "alt";
4098
+ "required": false;
4099
+ "isSignal": true;
4100
+ };
4101
+ "aspect": {
4102
+ "alias": "aspect";
4103
+ "required": false;
4104
+ "isSignal": true;
4105
+ };
4106
+ "loading": {
4107
+ "alias": "loading";
4108
+ "required": false;
4109
+ "isSignal": true;
4110
+ };
4111
+ "failed": {
4112
+ "alias": "failed";
4113
+ "required": false;
4114
+ "isSignal": true;
4115
+ };
4116
+ "loadingLabel": {
4117
+ "alias": "loadingLabel";
4118
+ "required": false;
4119
+ "isSignal": true;
4120
+ };
4121
+ "failedLabel": {
4122
+ "alias": "failedLabel";
4123
+ "required": false;
4124
+ "isSignal": true;
4125
+ };
4126
+ "selectable": {
4127
+ "alias": "selectable";
4128
+ "required": false;
4129
+ "isSignal": true;
4130
+ };
4131
+ "selected": {
4132
+ "alias": "selected";
4133
+ "required": false;
4134
+ "isSignal": true;
4135
+ };
4136
+ "label": {
4137
+ "alias": "label";
4138
+ "required": false;
4139
+ "isSignal": true;
4140
+ };
4141
+ }, {
4142
+ "selected": "selectedChange";
4143
+ "changed": "changed";
4144
+ "loaded": "loaded";
4145
+ "errored": "errored";
4146
+ }, never, ["[bfSlot=failed-action]", "*", "[bfSlot=footer]"], true, never>;
4147
+ }
4148
+ /**
4149
+ * The track height of a `bf-progress`: sm or md.
4150
+ *
4151
+ * @summary The two track heights of a progress bar, thin and regular
4152
+ * @since 0.1.0
4153
+ */
4154
+ type BfProgressSize = 'sm' | 'md';
4155
+ /**
4156
+ * The fill colour of a `bf-progress`: primary, success, warning or danger.
4157
+ *
4158
+ * @summary The four fill colours of a progress bar, from primary to danger
4159
+ * @since 0.1.0
4160
+ */
4161
+ type BfProgressTone = 'primary' | 'success' | 'warning' | 'danger';
4162
+ /**
4163
+ * Progress bar for completion and quotas, determinate by default, or `indeterminate` while the
4164
+ * amount of work is unknown.
4165
+ *
4166
+ * @example
4167
+ * <bf-progress [value]="68" label="Atlas rebrand" valueText="12 of 18 tasks" />
4168
+ * <bf-progress indeterminate label="Uploading" />
4169
+ * @summary Progress bar in two sizes and four tones for completion, quotas and unknown waits
4170
+ * @category Data display
4171
+ * @since 0.1.0
4172
+ */
4173
+ export declare class BfProgressComponent {
4174
+ /** Completion in percent; clamped to 0–100. Without it (and without `indeterminate`) the bar
4175
+ * shows 0 %. Ignored while `indeterminate`. */
4176
+ readonly value: import("@angular/core").InputSignal<number | undefined>;
4177
+ /** The amount of work is unknown: the native bar carries no value (assistive tech announces it
4178
+ * as busy) and a stripe runs across the track, a static 40 % bar under reduced motion. */
4179
+ readonly indeterminate: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4180
+ /** The value in words for assistive tech (`aria-valuetext`), such as "12 of 18 tasks"; the
4181
+ * percentage is announced while it is empty. */
4182
+ readonly valueText: import("@angular/core").InputSignal<string>;
4183
+ /** Accessible name of the bar ("Atlas rebrand"). */
3371
4184
  readonly label: import("@angular/core").InputSignal<string>;
3372
4185
  /** Bar height: sm 8 px (default), md 10 px. */
3373
4186
  readonly size: import("@angular/core").InputSignal<BfProgressSize>;
@@ -3982,7 +4795,9 @@ export declare class BfTabBarComponent implements BfTabBarHost {
3982
4795
  * with the nearest card or row, which then draws the pointer, the hover and, while the action has
3983
4796
  * keyboard focus, the focus ring; a card is never lit by the actions of rows nested in it. Give
3984
4797
  * secondary controls inside the surface `class="relative z-1"` so they stay clickable above the
3985
- * stretched area.
4798
+ * stretched area. When the surface marks a state that belongs in the name (an `unread` list
4799
+ * row), the directive names the element `aria-labelledby="<state id> <own id>"`, so the link reads
4800
+ * "Unread, Account", and restores the element's own `aria-labelledby` when the state goes.
3986
4801
  *
3987
4802
  * @example
3988
4803
  * <bf-card>
@@ -4008,6 +4823,61 @@ export declare class BfActionDirective {
4008
4823
  static ɵfac: i0.ɵɵFactoryDeclaration<BfActionDirective, never>;
4009
4824
  static ɵdir: i0.ɵɵDirectiveDeclaration<BfActionDirective, "a[bfAction], button[bfAction]", never, {}, {}, never, never, true, never>;
4010
4825
  }
4826
+ /**
4827
+ * Fades an image in when it has loaded instead of letting it pop in line by line: put
4828
+ * `bfFadeIn` on the `<img>`. The image stays transparent until its `load` (or `error`) event,
4829
+ * then fades in over 260 ms (`duration-slow`), at once under `prefers-reduced-motion: reduce`. An
4830
+ * image that is already loaded when it renders (from the cache) shows straight away. Reserve its
4831
+ * space (`width`/`height` attributes or an aspect ratio) so the layout does not move when it
4832
+ * arrives; for a placeholder while it loads, put a `bf-skeleton` behind it.
4833
+ *
4834
+ * @example
4835
+ * <img bfFadeIn [src]="cover.url" alt="Game cover" width="640" height="360" />
4836
+ * @summary Fades an image in once it has loaded instead of popping in
4837
+ * @since 0.5.0
4838
+ */
4839
+ export declare class BfFadeInDirective {
4840
+ private readonly image;
4841
+ /** True once the image has loaded or failed: it is opaque from then on. */
4842
+ protected readonly shown: import("@angular/core").WritableSignal<boolean>;
4843
+ constructor();
4844
+ /**
4845
+ * Shows the image: its load or error event.
4846
+ *
4847
+ * @internal
4848
+ */
4849
+ show(): void;
4850
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfFadeInDirective, never>;
4851
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfFadeInDirective, "img[bfFadeIn]", never, {}, {}, never, never, true, never>;
4852
+ }
4853
+ /**
4854
+ * Glides the items of an app's list or grid to their new place when items are added, removed or
4855
+ * reordered, instead of letting them jump (the FLIP technique). Put `bfMotionList` on the element
4856
+ * whose direct children are the items, usually the one around an `@for`. Each item remembers
4857
+ * where it was; after the DOM changes, every item that moved slides from its old place to its new
4858
+ * one over 200 ms (`--bf-duration-layout`, on the settle curve). Pair it with Angular's
4859
+ * `animate.enter="motion-safe:animate-item-enter"` and
4860
+ * `animate.leave="motion-safe:animate-item-leave"` on the items, so new items fade and rise in and
4861
+ * removed ones fade out before the rest close the gap. Nothing moves under
4862
+ * `prefers-reduced-motion: reduce`, and nothing glides when the container is resized. It moves
4863
+ * items within one container: an item moved to another list leaves one and enters the other.
4864
+ *
4865
+ * @example
4866
+ * <ul bfMotionList class="grid grid-cols-3 gap-4">
4867
+ * @for (card of cards(); track card.id) {
4868
+ * <li animate.enter="motion-safe:animate-item-enter" animate.leave="motion-safe:animate-item-leave">
4869
+ * <app-card [card]="card" />
4870
+ * </li>
4871
+ * }
4872
+ * </ul>
4873
+ * @summary Glides the items of a list or grid to their new place when it changes
4874
+ * @since 0.5.0
4875
+ */
4876
+ export declare class BfMotionListDirective {
4877
+ constructor();
4878
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfMotionListDirective, never>;
4879
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfMotionListDirective, "[bfMotionList]", never, {}, {}, never, never, true, never>;
4880
+ }
4011
4881
  /** What a `bf-tab-panel` reads from its `bf-tab-group`; a token so the two files never import each
4012
4882
  * other. Internal: not exported from the package. */
4013
4883
  interface BfTabGroupHost {
@@ -4060,6 +4930,10 @@ export declare class BfTabPanelComponent {
4060
4930
  readonly isSelected: import("@angular/core").Signal<boolean>;
4061
4931
  /** Latches once the panel has been shown. */
4062
4932
  private readonly visited;
4933
+ /** True while this panel is shown because the selection moved to it (not the panel shown on
4934
+ * the first render): it fades in. */
4935
+ protected readonly switchedTo: import("@angular/core").WritableSignal<boolean>;
4936
+ protected readonly motionClasses: import("@angular/core").Signal<"" | "motion-safe:animate-panel-in">;
4063
4937
  /** Whether the `ng-template` content exists: while selected, or once visited with `keepAlive`.
4064
4938
  * `visited` is read first on every run, so the latch sees each selection. */
4065
4939
  protected readonly rendered: import("@angular/core").Signal<boolean>;
@@ -4125,6 +4999,8 @@ export declare class BfTabGroupComponent implements BfTabGroupHost {
4125
4999
  /** The `bf-tab-panel` children, in document order. */
4126
5000
  readonly panels: import("@angular/core").Signal<readonly BfTabPanelComponent[]>;
4127
5001
  private readonly tabs;
5002
+ private readonly list;
5003
+ private readonly indicator;
4128
5004
  private readonly labels;
4129
5005
  private readonly defaults;
4130
5006
  /** The size in effect: `size`, else the app's `controlSize` default. */
@@ -4139,8 +5015,14 @@ export declare class BfTabGroupComponent implements BfTabGroupHost {
4139
5015
  /** The panel whose tab owns the roving tab stop: the shown one, else (it is disabled) the first
4140
5016
  * enabled one. */
4141
5017
  protected readonly tabStop: import("@angular/core").Signal<BfTabPanelComponent | undefined>;
5018
+ /** Whether the sliding underline has been measured and placed, so it, not the tab, draws the
5019
+ * selection. */
5020
+ private readonly placed;
5021
+ /** The classes of the sliding underline: hidden until placed, `text-disabled` on a disabled
5022
+ * selected tab. */
5023
+ protected readonly indicatorClasses: import("@angular/core").Signal<string>;
4142
5024
  protected readonly scrollerClasses = "-m-1 overflow-x-auto p-1";
4143
- protected readonly listClasses = "flex w-max min-w-full border-b border-border";
5025
+ protected readonly listClasses = "relative flex w-max min-w-full border-b border-border";
4144
5026
  protected readonly badgeClasses = "flex h-5 min-w-5 shrink-0 items-center justify-center rounded-control bg-primary px-1.5 text-micro font-bold text-on-primary";
4145
5027
  /**
4146
5028
  * The classes of a panel's tab.
@@ -4254,7 +5136,7 @@ export declare class BfPageHeaderComponent {
4254
5136
  * @summary Dense row of search, filters, view switch, result count and actions
4255
5137
  * @category Actions
4256
5138
  * @since 0.3.1
4257
- * @slot default The search field and the filters (bf-input, bf-select, bf-chip-group), in order.
5139
+ * @slot default The search field and the filters (bf-input, bf-chip-group, bf-select from /overlay), in order.
4258
5140
  * @slot view A view switch after the filters, such as a bf-segmented of list and grid.
4259
5141
  * @slot actions The trailing actions, kept together at the inline end of the row, beside the status.
4260
5142
  */
@@ -4343,6 +5225,15 @@ export declare class BfTableCellDirective<T = unknown> {
4343
5225
  * @since 0.3.1
4344
5226
  */
4345
5227
  type BfTableAlign = 'start' | 'end';
5228
+ /**
5229
+ * Where a `bf-table` column lines up its header and cells: a `BfTableAlign`, or `center` for a
5230
+ * column of controls such as the toggles of a settings matrix. A new type, so `Record`s over the
5231
+ * released `BfTableAlign` keep compiling.
5232
+ *
5233
+ * @summary The inline alignment of a table column: start, end, or centred for controls
5234
+ * @since 0.4.0
5235
+ */
5236
+ type BfTableColumnAlign = BfTableAlign | 'center';
4346
5237
  /**
4347
5238
  * The direction of a sorted `bf-table` column, as `aria-sort` names it.
4348
5239
  *
@@ -4376,10 +5267,11 @@ interface BfTableColumn {
4376
5267
  */
4377
5268
  sortable?: boolean;
4378
5269
  /**
4379
- * The inline alignment of the header and cells; `end` also sets tabular figures.
5270
+ * The inline alignment of the header and cells; `end` also sets tabular figures, `center`
5271
+ * centres a cell's control (a toggle, a checkbox) in the column.
4380
5272
  * @default 'start'
4381
5273
  */
4382
- align?: BfTableAlign;
5274
+ align?: BfTableColumnAlign;
4383
5275
  }
4384
5276
  /**
4385
5277
  * The column a `bf-table` is sorted by and the direction; the consumer sorts the rows.
@@ -4450,6 +5342,11 @@ export declare class BfTableComponent<T = unknown> {
4450
5342
  protected readonly labels: import("@bitforgehq/ui").BfLabels;
4451
5343
  private readonly defaults;
4452
5344
  private readonly table;
5345
+ /** The Sort by select from `provideBfSelect()` (`BF_TABLE_SORT_SELECT`); null renders the
5346
+ * native one. */
5347
+ protected readonly sortSelectType: import("@angular/core").Type<unknown> | null;
5348
+ /** Where the provided Sort by select goes; set only while the stack bar shows it. */
5349
+ private readonly sortSelectAnchor;
4453
5350
  private readonly cells;
4454
5351
  private readonly id;
4455
5352
  /** The caption's id, which names the table while it can stack. */
@@ -4490,6 +5387,7 @@ export declare class BfTableComponent<T = unknown> {
4490
5387
  protected readonly selectBodyClasses: import("@angular/core").Signal<string>;
4491
5388
  /** A body cell's height step, with the coarse-pointer height unless the table stacks there. */
4492
5389
  private readonly bodyHeight;
5390
+ constructor();
4493
5391
  /**
4494
5392
  * The classes of a column's header cell.
4495
5393
  * @internal
@@ -4611,6 +5509,19 @@ export declare class BfTableComponent<T = unknown> {
4611
5509
  "selection": "selectionChange";
4612
5510
  }, ["cells"], ["[bfSlot=empty]"], true, never>;
4613
5511
  }
5512
+ /**
5513
+ * The component a stacked `bf-table` renders as its Sort by select; `null` (the default) keeps the
5514
+ * built-in native `<select>`. `provideBfSelect()` from `@bitforgehq/ui/overlay` sets it to the
5515
+ * styled `bf-select`, so the core entry never imports the overlay entry. A component given here
5516
+ * must take the inputs `label` (string), `options` (`BfSelectOption<string>[]`) and `value`
5517
+ * (string) and emit the picked value from the output `changed`. The table adds its layout classes
5518
+ * to the component's host with `classList`, so the component must not overwrite its host
5519
+ * `className` or `class` attribute directly.
5520
+ *
5521
+ * @summary The component a stacked table renders as its Sort by select
5522
+ * @since 0.5.0
5523
+ */
5524
+ export declare const BF_TABLE_SORT_SELECT: InjectionToken<Type<unknown> | null>;
4614
5525
  /**
4615
5526
  * The layout of a `bf-description-list`: `rows` (label beside value, divided) or `grid` (tiles).
4616
5527
  *
@@ -4790,6 +5701,132 @@ export declare class BfStatComponent {
4790
5701
  };
4791
5702
  }, {}, never, ["[bfSlot=icon]", "*"], true, never>;
4792
5703
  }
5704
+ /** The default format of an event's time: a medium date and a short time (12 Oct 2026, 14:30). */
5705
+ declare const TIMELINE_TIME_FORMAT: Intl.DateTimeFormatOptions;
5706
+ /** What a `bf-timeline-event` reads from its `bf-timeline`. */
5707
+ interface BfTimelineHost {
5708
+ /** The `Intl.DateTimeFormat` options of the events' times. */
5709
+ readonly timeFormat: Signal<Intl.DateTimeFormatOptions>;
5710
+ /** The locale of the events' times, `''` for the app's `LOCALE_ID`. */
5711
+ readonly locale: Signal<string>;
5712
+ }
5713
+ /**
5714
+ * Provided by `bf-timeline` so each `bf-timeline-event` claims `role="listitem"` and formats its
5715
+ * time with the timeline's options, without `timeline-event.component.ts` importing
5716
+ * `timeline.component.ts`.
5717
+ */
5718
+ declare const BF_TIMELINE: InjectionToken<BfTimelineHost>;
5719
+ /**
5720
+ * A dated history of events, newest first or oldest first as the consumer orders them: who did
5721
+ * what and when, each event a `bf-timeline-event` with a marker on a vertical line.
5722
+ *
5723
+ * @example
5724
+ * <bf-timeline label="History">
5725
+ * <bf-timeline-event actor="Lena Meier" time="2026-10-28T14:30:00Z" tone="success">
5726
+ * Marked the finding as fixed
5727
+ * </bf-timeline-event>
5728
+ * </bf-timeline>
5729
+ * @summary Dated list of events with who did what and when, on a vertical line
5730
+ * @category Data display
5731
+ * @since 0.4.0
5732
+ * @slot default The events, each a `bf-timeline-event`, in the order to show them.
5733
+ */
5734
+ export declare class BfTimelineComponent implements BfTimelineHost {
5735
+ /** The list's accessible name, such as "History"; empty leaves the list unnamed (a visible
5736
+ * heading above it already names the section). */
5737
+ readonly label: import("@angular/core").InputSignal<string>;
5738
+ /** `Intl.DateTimeFormat` options of every event's time; by default a medium date and a short
5739
+ * time (`dateStyle: 'medium'`, `timeStyle: 'short'`). */
5740
+ readonly timeFormat: import("@angular/core").InputSignal<Intl.DateTimeFormatOptions>;
5741
+ /** BCP 47 locale of the times, such as `de-CH`; empty follows the app's `LOCALE_ID`. */
5742
+ readonly locale: import("@angular/core").InputSignal<string>;
5743
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfTimelineComponent, never>;
5744
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfTimelineComponent, "bf-timeline", never, {
5745
+ "label": {
5746
+ "alias": "label";
5747
+ "required": false;
5748
+ "isSignal": true;
5749
+ };
5750
+ "timeFormat": {
5751
+ "alias": "timeFormat";
5752
+ "required": false;
5753
+ "isSignal": true;
5754
+ };
5755
+ "locale": {
5756
+ "alias": "locale";
5757
+ "required": false;
5758
+ "isSignal": true;
5759
+ };
5760
+ }, {}, never, ["*"], true, never>;
5761
+ }
5762
+ /**
5763
+ * The colour of a `bf-timeline-event`'s marker: `neutral` (the default), `primary`, or a status
5764
+ * (`success`, `warning`, `danger`, `info`), such as danger for a failed CI run.
5765
+ *
5766
+ * @summary The marker colour of a timeline event, neutral, primary or a status
5767
+ * @since 0.4.0
5768
+ */
5769
+ type BfTimelineTone = 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
5770
+ /**
5771
+ * One event of a `bf-timeline`: a marker coloured by `tone`, the event text (the default slot,
5772
+ * which may hold links), then who did it (`actor`) and when (`time`, in a `time` element).
5773
+ *
5774
+ * @example
5775
+ * <bf-timeline-event actor="CI" time="2026-10-28T09:12:00Z" tone="danger">
5776
+ * CI failed on <a href="/pulls/123">PR #123</a>
5777
+ * </bf-timeline-event>
5778
+ * @summary One timeline event with a coloured marker, its text, actor and time
5779
+ * @category Data display
5780
+ * @since 0.4.0
5781
+ * @slot default The event text; links in it take the link style.
5782
+ * @slot end Content at the inline end on a wide timeline and below the text on a narrow one, such as a button.
5783
+ */
5784
+ export declare class BfTimelineEventComponent {
5785
+ /** Who did it, such as a person or a bot; empty for a system event. */
5786
+ readonly actor: import("@angular/core").InputSignal<string>;
5787
+ /** When it happened: a Date or an ISO 8601 string; empty or invalid shows no time. */
5788
+ readonly time: import("@angular/core").InputSignal<string | Date | null>;
5789
+ /** Shown in place of the formatted time, such as "vor 2 Std."; the `time` element's `datetime`
5790
+ * keeps the exact value. */
5791
+ readonly timeText: import("@angular/core").InputSignal<string>;
5792
+ /** The marker's colour. */
5793
+ readonly tone: import("@angular/core").InputSignal<BfTimelineTone>;
5794
+ private readonly timeline;
5795
+ private readonly appLocale;
5796
+ /** `listitem` inside a `bf-timeline`, else no role. */
5797
+ protected readonly role: string | null;
5798
+ private readonly date;
5799
+ /** The `time` element's machine value: the ISO string as given, a Date's ISO string, else null. */
5800
+ protected readonly datetime: import("@angular/core").Signal<string | null>;
5801
+ /** The visible time: `timeText`, else the date formatted with the timeline's options. */
5802
+ protected readonly timeLabel: import("@angular/core").Signal<string>;
5803
+ protected readonly markerClasses: string;
5804
+ protected readonly textClasses: string;
5805
+ readonly dotClasses: import("@angular/core").Signal<string>;
5806
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfTimelineEventComponent, never>;
5807
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfTimelineEventComponent, "bf-timeline-event", never, {
5808
+ "actor": {
5809
+ "alias": "actor";
5810
+ "required": false;
5811
+ "isSignal": true;
5812
+ };
5813
+ "time": {
5814
+ "alias": "time";
5815
+ "required": false;
5816
+ "isSignal": true;
5817
+ };
5818
+ "timeText": {
5819
+ "alias": "timeText";
5820
+ "required": false;
5821
+ "isSignal": true;
5822
+ };
5823
+ "tone": {
5824
+ "alias": "tone";
5825
+ "required": false;
5826
+ "isSignal": true;
5827
+ };
5828
+ }, {}, never, ["*", "[bfSlot=end]"], true, never>;
5829
+ }
4793
5830
  /**
4794
5831
  * What a `li[bfBreadcrumbItem]` reads from its enclosing `bf-breadcrumb`, provided through an
4795
5832
  * injection token so `breadcrumb.component.ts` and `breadcrumb-item.component.ts` never import each
@@ -4948,7 +5985,11 @@ export declare class BfSidebarComponent {
4948
5985
  * screen readers and becomes a tooltip. A `bf-sidebar-toggle` inside flips it; bind
4949
5986
  * `[(collapsed)]` to keep it in your state. */
4950
5987
  readonly collapsed: import("@angular/core").ModelSignal<boolean>;
5988
+ private readonly defaults;
4951
5989
  private readonly labels;
5990
+ /** True after the first render: from then on collapsing and expanding animate.
5991
+ * @internal */
5992
+ readonly animated: import("@angular/core").WritableSignal<boolean>;
4952
5993
  /** The `nav` element's id, which `bf-sidebar-toggle` points `aria-controls` at.
4953
5994
  * @internal */
4954
5995
  readonly navId: string;
@@ -4956,7 +5997,7 @@ export declare class BfSidebarComponent {
4956
5997
  protected readonly name: import("@angular/core").Signal<string>;
4957
5998
  private readonly width;
4958
5999
  readonly hostClasses: import("@angular/core").Signal<string>;
4959
- protected readonly regionClasses: import("@angular/core").Signal<string>;
6000
+ constructor();
4960
6001
  static ɵfac: i0.ɵɵFactoryDeclaration<BfSidebarComponent, never>;
4961
6002
  static ɵcmp: i0.ɵɵComponentDeclaration<BfSidebarComponent, "bf-sidebar", never, {
4962
6003
  "label": {
@@ -5026,7 +6067,6 @@ export declare class BfNavGroupComponent {
5026
6067
  /** The heading's id, referenced by the group's `aria-labelledby`. */
5027
6068
  protected readonly headingId: string;
5028
6069
  private readonly form;
5029
- readonly hostClasses: import("@angular/core").Signal<string>;
5030
6070
  protected readonly headingClasses: import("@angular/core").Signal<string>;
5031
6071
  static ɵfac: i0.ɵɵFactoryDeclaration<BfNavGroupComponent, never>;
5032
6072
  static ɵcmp: i0.ɵɵComponentDeclaration<BfNavGroupComponent, "bf-nav-group", never, {
@@ -5073,11 +6113,17 @@ export declare class BfNavItemComponent {
5073
6113
  /** True while the enclosing sidebar is collapsed to an icon rail. */
5074
6114
  protected readonly collapsed: import("@angular/core").Signal<boolean>;
5075
6115
  private readonly form;
6116
+ /** True in a sidebar (or on its own), where the item can collapse to an icon rail. */
6117
+ protected readonly railable: import("@angular/core").Signal<boolean>;
6118
+ /** True once the enclosing sidebar has rendered: collapsing and expanding animate. */
6119
+ private readonly animated;
5076
6120
  /** The size in effect: `size`, else the app's `controlSize` default. */
5077
6121
  private readonly resolvedSize;
5078
6122
  readonly classes: import("@angular/core").Signal<string>;
6123
+ private readonly fade;
5079
6124
  protected readonly labelClasses: import("@angular/core").Signal<string>;
5080
6125
  protected readonly badgeClasses: import("@angular/core").Signal<string>;
6126
+ protected readonly dotClasses: import("@angular/core").Signal<string>;
5081
6127
  /** The badge as screen readers hear it after the label. */
5082
6128
  protected readonly badgeAnnouncement: import("@angular/core").Signal<string>;
5083
6129
  /** Collapsed, the label as the pointer tooltip; else the consumer's own `title`, if any. */
@@ -5734,6 +6780,311 @@ export declare class BfCalendarComponent implements FormValueControl<string | nu
5734
6780
  "touch": "touch";
5735
6781
  }, never, never, true, never>;
5736
6782
  }
6783
+ /**
6784
+ * Provided by `bf-steps` so each `bf-step` knows its number and may claim `role="listitem"`,
6785
+ * without `step.component.ts` importing `steps.component.ts`.
6786
+ */
6787
+ interface BfStepsHost {
6788
+ /** The number of the first step. */
6789
+ readonly start: Signal<number>;
6790
+ /** How many steps the list holds. */
6791
+ readonly count: Signal<number>;
6792
+ /** The zero-based position of a step among the list's steps, or -1. */
6793
+ indexOf(step: object): number;
6794
+ }
6795
+ declare const BF_STEPS: InjectionToken<BfStepsHost>;
6796
+ /**
6797
+ * Numbered instructions a user follows in order, such as setting up an app on a phone: an
6798
+ * ordered list of `bf-step` items, each with a numbered marker joined to the next by a line.
6799
+ *
6800
+ * @example
6801
+ * <bf-steps>
6802
+ * <bf-step title="Install the app">Download it from the App Store or Google Play.</bf-step>
6803
+ * <bf-step title="Sign in">Use your work email address.</bf-step>
6804
+ * <bf-step title="Turn on notifications">Allow them when the app asks.</bf-step>
6805
+ * </bf-steps>
6806
+ * @summary Numbered instruction steps a user follows in order, with joined markers
6807
+ * @category Data display
6808
+ * @since 0.4.0
6809
+ * @slot default The steps, each a `bf-step`.
6810
+ */
6811
+ export declare class BfStepsComponent implements BfStepsHost {
6812
+ /** The number of the first step, for a list continued from an earlier one. */
6813
+ readonly start: import("@angular/core").InputSignalWithTransform<number, unknown>;
6814
+ private readonly steps;
6815
+ /** @internal How many steps the list holds. */
6816
+ readonly count: import("@angular/core").Signal<number>;
6817
+ /** @internal The zero-based position of a step, read by the step for its number. */
6818
+ indexOf(step: object): number;
6819
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfStepsComponent, never>;
6820
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfStepsComponent, "bf-steps", never, {
6821
+ "start": {
6822
+ "alias": "start";
6823
+ "required": false;
6824
+ "isSignal": true;
6825
+ };
6826
+ }, {}, ["steps"], ["*"], true, never>;
6827
+ }
6828
+ /**
6829
+ * The heading level of a `bf-step` title, for a guide whose steps hold enough content (screenshots,
6830
+ * code) that a screen-reader user moves between them by heading.
6831
+ *
6832
+ * @summary The heading level a step title can take, two, three or four
6833
+ * @since 0.4.0
6834
+ */
6835
+ type BfStepHeadingLevel = 2 | 3 | 4;
6836
+ /**
6837
+ * One step of a `bf-steps` list: a numbered marker, a short instruction as its title and the
6838
+ * step's details in the content.
6839
+ *
6840
+ * @example
6841
+ * <bf-steps>
6842
+ * <bf-step title="Install the app">Download it from the App Store or Google Play.</bf-step>
6843
+ * <bf-step title="Sign in">Use your work email address.</bf-step>
6844
+ * </bf-steps>
6845
+ * @summary One numbered step of an instruction list, with a title and its details
6846
+ * @category Data display
6847
+ * @since 0.4.0
6848
+ * @slot default The step's details: text, a code snippet, an image or a button.
6849
+ */
6850
+ export declare class BfStepComponent {
6851
+ private readonly steps;
6852
+ /** The step's short instruction ("Install the app"), shown next to the number. */
6853
+ readonly title: import("@angular/core").InputSignal<string>;
6854
+ /** Renders the title as a heading of this level instead of text; leave it off for short steps. */
6855
+ readonly headingLevel: import("@angular/core").InputSignal<BfStepHeadingLevel | undefined>;
6856
+ /** `listitem` inside a `bf-steps`, else no role. */
6857
+ protected readonly role: string | null;
6858
+ protected readonly markerClasses = "flex size-6 shrink-0 items-center justify-center rounded-full bg-primary-soft text-label-sm font-bold text-primary-strong forced-colors:outline forced-colors:outline-[CanvasText]";
6859
+ protected readonly lineClasses = "my-1 w-0 flex-1 border-s border-border group-last/bf-step:hidden";
6860
+ protected readonly titleClasses = "pt-0.5 text-label font-bold text-text wrap-anywhere";
6861
+ /** The step's number: the list's `start` plus its position. */
6862
+ protected readonly number: import("@angular/core").Signal<number>;
6863
+ protected readonly positionInSet: import("@angular/core").Signal<number | null>;
6864
+ protected readonly setSize: import("@angular/core").Signal<number | null>;
6865
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfStepComponent, never>;
6866
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfStepComponent, "bf-step", never, {
6867
+ "title": {
6868
+ "alias": "title";
6869
+ "required": false;
6870
+ "isSignal": true;
6871
+ };
6872
+ "headingLevel": {
6873
+ "alias": "headingLevel";
6874
+ "required": false;
6875
+ "isSignal": true;
6876
+ };
6877
+ }, {}, never, ["*"], true, never>;
6878
+ }
6879
+ /** What a `bf-chat-message` asks of the `bf-chat-thread` around it. */
6880
+ interface BfChatThreadHost {
6881
+ /** True once the thread's first render is done: a message created after that animates in, the
6882
+ * ones the thread opened with do not. */
6883
+ hasSettled(): boolean;
6884
+ }
6885
+ /**
6886
+ * Provided by `bf-chat-thread` so a `bf-chat-message` knows whether it arrived after the first
6887
+ * render, without `chat-message.component.ts` importing `chat-thread.component.ts`.
6888
+ */
6889
+ declare const BF_CHAT_THREAD: InjectionToken<BfChatThreadHost>;
6890
+ /**
6891
+ * The scrolling message list of a chat: a `role="log"` around `bf-chat-message` elements that
6892
+ * stays pinned to the newest message while text arrives. When the user scrolls up it stops
6893
+ * following and shows a "Jump to latest" button, which scrolls back down (smoothly, at once under
6894
+ * reduced motion) and follows again. While a message streams the log is `aria-busy`, so a screen
6895
+ * reader announces the reply once it is complete, not piece by piece. Messages added after the
6896
+ * first render rise and fade in; the ones it opens with do not. Give it a height (or `flex-1` in a
6897
+ * column): it scrolls inside.
6898
+ *
6899
+ * @example
6900
+ * <bf-chat-thread class="h-[32rem]">
6901
+ * @for (message of messages(); track message.id) {
6902
+ * <bf-chat-message [role]="message.role" [streaming]="message.streaming">{{ message.text }}</bf-chat-message>
6903
+ * }
6904
+ * </bf-chat-thread>
6905
+ * @summary Scrolling chat message log that follows new text and offers a jump to latest
6906
+ * @category Data display
6907
+ * @since 0.5.0
6908
+ * @slot default The messages, each a `bf-chat-message` (in order, oldest first).
6909
+ * @slot jump-icon An icon before the "Jump to latest" text, such as an arrow down.
6910
+ */
6911
+ export declare class BfChatThreadComponent implements BfChatThreadHost {
6912
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
6913
+ private readonly document;
6914
+ /** The log's accessible name; empty uses the `chatThread` label ("Conversation"). */
6915
+ readonly label: import("@angular/core").InputSignal<string>;
6916
+ private readonly scroller;
6917
+ private readonly content;
6918
+ private readonly messages;
6919
+ /** Pinned to the newest message: new text scrolls the log down. */
6920
+ protected readonly following: import("@angular/core").WritableSignal<boolean>;
6921
+ /** Bumped when the content or the log changes size, so the follow scroll runs again. */
6922
+ private readonly resized;
6923
+ private settled;
6924
+ /** A smooth jump is under way: its own scroll events neither stop nor restart the follow. */
6925
+ private jumping;
6926
+ private jumpTimer;
6927
+ /** A finger is on the log: the follow does not move the content under it. */
6928
+ protected touching: boolean;
6929
+ protected readonly name: import("@angular/core").Signal<string>;
6930
+ /** A message is still streaming, so the log holds its announcement until it is done. */
6931
+ protected readonly busy: import("@angular/core").Signal<boolean>;
6932
+ protected readonly jumpClasses: string;
6933
+ constructor();
6934
+ /**
6935
+ * True once the first render is done, so only later messages animate in.
6936
+ * @internal
6937
+ */
6938
+ hasSettled(): boolean;
6939
+ /** Scrolls to the newest message (smoothly, at once under reduced motion) and follows it again. */
6940
+ jumpToLatest(): void;
6941
+ /** @internal */
6942
+ onScroll(): void;
6943
+ /** @internal */
6944
+ onWheel(event: WheelEvent): void;
6945
+ /** @internal */
6946
+ onKeydown(event: KeyboardEvent): void;
6947
+ /**
6948
+ * Ends a smooth jump and catches up with text that arrived during it.
6949
+ * @internal
6950
+ */
6951
+ endJump(): void;
6952
+ /** The user scrolls up: stop at once, so the next follow scroll cannot pull the log back. */
6953
+ private stopFollowing;
6954
+ private clearJumpTimer;
6955
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfChatThreadComponent, never>;
6956
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfChatThreadComponent, "bf-chat-thread", never, {
6957
+ "label": {
6958
+ "alias": "label";
6959
+ "required": false;
6960
+ "isSignal": true;
6961
+ };
6962
+ }, {}, ["messages"], ["*", "[bfSlot=jump-icon]"], true, never>;
6963
+ }
6964
+ /**
6965
+ * Who wrote a `bf-chat-message`: `user` (a tinted bubble at the inline end) or `assistant` (full
6966
+ * width, no bubble).
6967
+ *
6968
+ * @summary Who wrote a chat message, the user or the assistant
6969
+ * @since 0.5.0
6970
+ */
6971
+ type BfChatMessageRole = 'user' | 'assistant';
6972
+ /**
6973
+ * How a `bf-chat-message` ended: `done` (the default, no status line), `stopped` (the user
6974
+ * stopped the reply) or `failed` (it did not arrive or was not sent).
6975
+ *
6976
+ * @summary How a chat message ended: done, stopped by the user, or failed
6977
+ * @since 0.5.0
6978
+ */
6979
+ type BfChatMessageStatus = 'done' | 'stopped' | 'failed';
6980
+ /**
6981
+ * One message of a `bf-chat-thread`. A `user` message is a tinted bubble at the inline end; an
6982
+ * `assistant` message is full width with no bubble. The default slot is the message text, styled
6983
+ * as prose (`bfProse`) for markdown the app renders. An optional header names the `author` and
6984
+ * the `time`. While `streaming`, a caret pulses at the end of the text and the message is
6985
+ * `aria-busy`, so the thread announces it once, when it is done. A `stopped` or `failed` message
6986
+ * shows a short status line, with the `action` slot beside it (a Retry button).
6987
+ *
6988
+ * @example
6989
+ * <bf-chat-message role="assistant" author="Assistant" [streaming]="writing()">
6990
+ * <div [innerHTML]="replyHtml()"></div>
6991
+ * </bf-chat-message>
6992
+ * <bf-chat-message role="user" author="You" time="2026-11-06T09:30:00Z">Three names for a cafe?</bf-chat-message>
6993
+ * @summary One chat message, a user bubble or a full-width assistant reply that can stream
6994
+ * @category Data display
6995
+ * @since 0.5.0
6996
+ * @slot default The message text: plain text or the app's rendered markdown, styled as prose.
6997
+ * @slot content Inline content below the text, such as cards, images or chips (not styled as prose).
6998
+ * @slot action Beside the status line, such as a Retry button.
6999
+ */
7000
+ export declare class BfChatMessageComponent {
7001
+ private readonly labels;
7002
+ private readonly appLocale;
7003
+ private readonly thread;
7004
+ /** Who wrote it: `user` (a bubble at the inline end) or `assistant` (full width, no bubble). */
7005
+ readonly role: import("@angular/core").InputSignal<BfChatMessageRole>;
7006
+ /** Whose message it is, shown in the header, such as "You" or the assistant's name; empty
7007
+ * shows none. */
7008
+ readonly author: import("@angular/core").InputSignal<string>;
7009
+ /** When it was written: a Date or an ISO 8601 string, shown as hours and minutes; empty or
7010
+ * invalid shows no time. */
7011
+ readonly time: import("@angular/core").InputSignal<string | Date | null>;
7012
+ /** Shown in place of the formatted time, such as "just now"; the `time` element's `datetime`
7013
+ * keeps the exact value. */
7014
+ readonly timeText: import("@angular/core").InputSignal<string>;
7015
+ /** The text is still arriving: a caret pulses at its end and the message is `aria-busy`. */
7016
+ readonly streaming: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7017
+ /** How it ended: `done` (no status line), `stopped` or `failed`. */
7018
+ readonly status: import("@angular/core").InputSignal<BfChatMessageStatus>;
7019
+ /** Shown in place of the built-in status line (`chatMessageStopped`, `chatMessageFailed`),
7020
+ * such as the error the server returned. */
7021
+ readonly statusMessage: import("@angular/core").InputSignal<string>;
7022
+ /** Rises and fades in: only for a message added to a thread after its first render. */
7023
+ protected readonly entering: import("@angular/core").WritableSignal<boolean>;
7024
+ private readonly date;
7025
+ /** The `time` element's machine value: the ISO string as given, a Date's ISO string, else null. */
7026
+ protected readonly datetime: import("@angular/core").Signal<string | null>;
7027
+ /** The visible time: `timeText`, else the time formatted as hours and minutes. */
7028
+ protected readonly timeLabel: import("@angular/core").Signal<string>;
7029
+ /** The status line's text: `statusMessage`, else the label of `stopped` or `failed`. */
7030
+ protected readonly statusLine: import("@angular/core").Signal<string>;
7031
+ protected readonly statusClasses: import("@angular/core").Signal<string>;
7032
+ protected readonly hostClasses: import("@angular/core").Signal<string>;
7033
+ protected readonly bodyClasses: import("@angular/core").Signal<string>;
7034
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfChatMessageComponent, never>;
7035
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfChatMessageComponent, "bf-chat-message", never, {
7036
+ "role": {
7037
+ "alias": "role";
7038
+ "required": false;
7039
+ "isSignal": true;
7040
+ };
7041
+ "author": {
7042
+ "alias": "author";
7043
+ "required": false;
7044
+ "isSignal": true;
7045
+ };
7046
+ "time": {
7047
+ "alias": "time";
7048
+ "required": false;
7049
+ "isSignal": true;
7050
+ };
7051
+ "timeText": {
7052
+ "alias": "timeText";
7053
+ "required": false;
7054
+ "isSignal": true;
7055
+ };
7056
+ "streaming": {
7057
+ "alias": "streaming";
7058
+ "required": false;
7059
+ "isSignal": true;
7060
+ };
7061
+ "status": {
7062
+ "alias": "status";
7063
+ "required": false;
7064
+ "isSignal": true;
7065
+ };
7066
+ "statusMessage": {
7067
+ "alias": "statusMessage";
7068
+ "required": false;
7069
+ "isSignal": true;
7070
+ };
7071
+ }, {}, never, ["*", "[bfSlot=content]", "[bfSlot=action]"], true, never>;
7072
+ }
7073
+ /**
7074
+ * Styles rendered markdown inside its element: headings, paragraphs, lists, links, inline code,
7075
+ * code blocks, quotes and rules, through the semantic tokens only (so a theme override restyles
7076
+ * it). The app renders the markdown to HTML itself (and sanitises it); `bfProse` only styles it.
7077
+ * A `bf-chat-message` body is a `bfProse` element already.
7078
+ *
7079
+ * @example
7080
+ * <article bfProse [innerHTML]="renderedMarkdown()"></article>
7081
+ * @summary Styles rendered markdown with the library tokens: headings, lists, code and quotes
7082
+ * @since 0.5.0
7083
+ */
7084
+ export declare class BfProseDirective {
7085
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfProseDirective, never>;
7086
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfProseDirective, "[bfProse]", never, {}, {}, never, never, true, never>;
7087
+ }
5737
7088
  /** An error as a field reads it: its kind and optional message; limit fields (`minLength`,
5738
7089
  * `context.requiredLength`, …) are read through type guards by `bfValidationMessage`. */
5739
7090
  type FieldErrorLike = Pick<ValidationError, 'kind' | 'message'>;
@@ -5760,6 +7111,223 @@ declare function fieldErrorMessageId(id: string, errorText: string, showError: b
5760
7111
  * Hydration-stable ids are decided before 1.0.
5761
7112
  */
5762
7113
  declare function uniqueId(prefix: string): string;
7114
+ /**
7115
+ * Internal native single-choice dropdown styled like the text field, with a placeholder option and
7116
+ * Signal Forms binding. Generic over the option value (a string by default); `null` means nothing
7117
+ * is selected.
7118
+ *
7119
+ * @example
7120
+ * <bf-native-select label="Project" placeholder="Pick a project" [options]="projects" [(value)]="projectId" />
7121
+ * <bf-native-select label="Team" placeholder="Pick a team" [options]="teams" [formField]="task.teamId" />
7122
+ * @summary Native single-choice dropdown styled like the text field, with Signal Forms binding
7123
+ * @category Forms
7124
+ * @since 0.1.0
7125
+ */
7126
+ declare class BfNativeSelectComponent<T = string> implements FormValueControl<T | null> {
7127
+ /** The selected option's value, `null` while nothing is selected; two-way bindable and bound by
7128
+ * `[formField]`. */
7129
+ readonly value: import("@angular/core").ModelSignal<T | null>;
7130
+ /** The options to choose from. */
7131
+ readonly options: import("@angular/core").InputSignal<readonly BfSelectOption<T>[]>;
7132
+ /** Equality for matching `value` to an option (default `Object.is`); pass one for object values. */
7133
+ readonly compareWith: import("@angular/core").InputSignal<BfCompareWith<T>>;
7134
+ /** Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md`
7135
+ * by default). */
7136
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
7137
+ /** Visible label above the control. */
7138
+ readonly label: import("@angular/core").InputSignal<string>;
7139
+ /**
7140
+ * Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label`
7141
+ * is set.
7142
+ */
7143
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
7144
+ /**
7145
+ * Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when
7146
+ * `label` is set.
7147
+ */
7148
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
7149
+ /** Disabled first option shown while nothing is selected. */
7150
+ readonly placeholder: import("@angular/core").InputSignal<string>;
7151
+ /** Helper text under the control. */
7152
+ readonly helpText: import("@angular/core").InputSignal<string>;
7153
+ /** Explicit error text; takes precedence over Signal Forms errors. */
7154
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
7155
+ /** Disables the control. */
7156
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7157
+ /** Shows the value but blocks changes; the select stays focusable and exposes `aria-readonly`. */
7158
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7159
+ /** Marks the control required. */
7160
+ readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7161
+ /** Field validity from Signal Forms. */
7162
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7163
+ /** Whether the user has left the field. */
7164
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7165
+ /** Validation errors from Signal Forms. */
7166
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
7167
+ /** Native name attribute. */
7168
+ readonly name: import("@angular/core").InputSignal<string>;
7169
+ /** Control id; generated when not set. */
7170
+ readonly id: import("@angular/core").InputSignal<string>;
7171
+ /** Emitted with the chosen value after a user change (never `null`). */
7172
+ readonly changed: import("@angular/core").OutputEmitterRef<T>;
7173
+ /** Emitted when the control gains focus. */
7174
+ readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
7175
+ /** Emitted when the control loses focus. */
7176
+ readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
7177
+ /** Emitted on blur so Signal Forms marks the field touched. */
7178
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
7179
+ private readonly el;
7180
+ private readonly defaults;
7181
+ /** The size in effect: `size`, else the app's `fieldSize` default. */
7182
+ protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
7183
+ readonly wrapperClasses: import("@angular/core").Signal<string>;
7184
+ /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
7185
+ readonly selectedIndex: import("@angular/core").Signal<number>;
7186
+ readonly showEmptyOption: import("@angular/core").Signal<boolean>;
7187
+ private readonly labels;
7188
+ readonly errorText: import("@angular/core").Signal<string>;
7189
+ readonly showError: import("@angular/core").Signal<boolean>;
7190
+ readonly describedBy: import("@angular/core").Signal<string | null>;
7191
+ /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
7192
+ readonly errorMessageId: import("@angular/core").Signal<string | null>;
7193
+ /**
7194
+ * `aria-label` value: only when there is no visible label, so the label stays the sole accessible
7195
+ * name.
7196
+ */
7197
+ readonly ariaLabelAttr: import("@angular/core").Signal<string | null>;
7198
+ /** `aria-labelledby` value, under the same rule as `ariaLabelAttr`. */
7199
+ readonly ariaLabelledbyAttr: import("@angular/core").Signal<string | null>;
7200
+ readonly classes: import("@angular/core").Signal<string>;
7201
+ /** Native `value` of an option: its value for a primitive, its index for an object. */
7202
+ protected readonly key: typeof choiceKey;
7203
+ /**
7204
+ * Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
7205
+ * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
7206
+ */
7207
+ focus(options?: FocusOptions): void;
7208
+ /**
7209
+ * Read-only: no picker on a mouse press. Cancelling the press also cancels its focus, so the
7210
+ * select is focused explicitly, like any other read-only control. Touch screens open the
7211
+ * platform picker without a cancellable press; a choice made there is reverted by `onChange`.
7212
+ * @internal
7213
+ */
7214
+ onMousedown(event: MouseEvent): void;
7215
+ /**
7216
+ * Read-only: no picker or value change from the keyboard (arrows, Enter, Space, F4, type-ahead).
7217
+ * @internal
7218
+ */
7219
+ onKeydown(event: KeyboardEvent): void;
7220
+ /**
7221
+ * Writes the chosen option's value to the model and emits `changed`; while read-only, restores
7222
+ * the shown option instead.
7223
+ * @internal
7224
+ */
7225
+ onChange(el: HTMLSelectElement): void;
7226
+ /**
7227
+ * Marks the field touched and emits `blurred`.
7228
+ * @internal
7229
+ */
7230
+ onBlur(event: FocusEvent): void;
7231
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfNativeSelectComponent<any>, never>;
7232
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfNativeSelectComponent<any>, "bf-native-select", never, {
7233
+ "value": {
7234
+ "alias": "value";
7235
+ "required": false;
7236
+ "isSignal": true;
7237
+ };
7238
+ "options": {
7239
+ "alias": "options";
7240
+ "required": true;
7241
+ "isSignal": true;
7242
+ };
7243
+ "compareWith": {
7244
+ "alias": "compareWith";
7245
+ "required": false;
7246
+ "isSignal": true;
7247
+ };
7248
+ "size": {
7249
+ "alias": "size";
7250
+ "required": false;
7251
+ "isSignal": true;
7252
+ };
7253
+ "label": {
7254
+ "alias": "label";
7255
+ "required": false;
7256
+ "isSignal": true;
7257
+ };
7258
+ "ariaLabel": {
7259
+ "alias": "ariaLabel";
7260
+ "required": false;
7261
+ "isSignal": true;
7262
+ };
7263
+ "ariaLabelledby": {
7264
+ "alias": "ariaLabelledby";
7265
+ "required": false;
7266
+ "isSignal": true;
7267
+ };
7268
+ "placeholder": {
7269
+ "alias": "placeholder";
7270
+ "required": false;
7271
+ "isSignal": true;
7272
+ };
7273
+ "helpText": {
7274
+ "alias": "helpText";
7275
+ "required": false;
7276
+ "isSignal": true;
7277
+ };
7278
+ "errorMessage": {
7279
+ "alias": "errorMessage";
7280
+ "required": false;
7281
+ "isSignal": true;
7282
+ };
7283
+ "disabled": {
7284
+ "alias": "disabled";
7285
+ "required": false;
7286
+ "isSignal": true;
7287
+ };
7288
+ "readonly": {
7289
+ "alias": "readonly";
7290
+ "required": false;
7291
+ "isSignal": true;
7292
+ };
7293
+ "required": {
7294
+ "alias": "required";
7295
+ "required": false;
7296
+ "isSignal": true;
7297
+ };
7298
+ "invalid": {
7299
+ "alias": "invalid";
7300
+ "required": false;
7301
+ "isSignal": true;
7302
+ };
7303
+ "touched": {
7304
+ "alias": "touched";
7305
+ "required": false;
7306
+ "isSignal": true;
7307
+ };
7308
+ "errors": {
7309
+ "alias": "errors";
7310
+ "required": false;
7311
+ "isSignal": true;
7312
+ };
7313
+ "name": {
7314
+ "alias": "name";
7315
+ "required": false;
7316
+ "isSignal": true;
7317
+ };
7318
+ "id": {
7319
+ "alias": "id";
7320
+ "required": false;
7321
+ "isSignal": true;
7322
+ };
7323
+ }, {
7324
+ "value": "valueChange";
7325
+ "changed": "changed";
7326
+ "focused": "focused";
7327
+ "blurred": "blurred";
7328
+ "touch": "touch";
7329
+ }, never, never, true, never>;
7330
+ }
5763
7331
  /** One plain date's parts; `month` is 1–12. */
5764
7332
  interface CalendarDate {
5765
7333
  readonly year: number;
@@ -5832,6 +7400,9 @@ interface BfNavHost {
5832
7400
  readonly layout: BfNavLayout;
5833
7401
  /** True while the sidebar is collapsed to an icon rail; always false in a top nav. */
5834
7402
  readonly collapsed: Signal<boolean>;
7403
+ /** True once the host has rendered, so collapsing and expanding animate but the first render
7404
+ * (a restored collapsed state) does not; always false in a top nav. */
7405
+ readonly animated: Signal<boolean>;
5835
7406
  /** The id of the host's `nav` element (for a toggle's `aria-controls`); null in a top nav. */
5836
7407
  readonly navId: string | null;
5837
7408
  /** Writes the sidebar's `collapsed` model, as a user change; does nothing in a top nav. */
@@ -5852,4 +7423,4 @@ declare const BF_NAV: InjectionToken<BfNavHost>;
5852
7423
  declare function resetBfDocument(document: Document): void;
5853
7424
  /** Runs `resetBfDocument()` when the environment injector that holds it is destroyed. Internal. */
5854
7425
  declare function provideBfDocumentReset(): EnvironmentProviders;
5855
- export { type BfAlertVariant, type BfAppFrameBreakpoint, type BfAppFrameContentWidth, type BfAutocapitalize, type BfAvatarSize, type BfAvatarVariant, type BfBadgeAppearance, type BfBadgeVariant, type BfButtonAppearance, type BfButtonSize, type BfButtonType, type BfButtonVariant, type BfCalendarDisabledDate, type BfCalendarWeekday, type BfCardPadding, type BfCardVariant, type BfChipGroupMode, type BfChipVariant, type BfColorScheme, type BfCompareWith, type BfDefaults, type BfDefaultsOptions, type BfDescriptionListLayout, type BfEmptyStateHeading, type BfEmptyStateHeadingLevel, type BfEnterKeyHint, type BfFieldControlState, type BfFieldSize, type BfIconButtonVariant, type BfInputAutocapitalize, type BfInputEnterKeyHint, type BfInputInputMode, type BfInputMode, type BfInputType, type BfLabels, type BfLabelsOverrides, type BfPageHeaderHeadingLevel, type BfProgressSize, type BfProgressTone, type BfRadioGroupOrientation, type BfRadioOption, type BfSegmentOption, type BfSegmentedSize, type BfSelectOption, type BfSize, type BfSkeletonShape, type BfSpinnerSize, type BfStatAppearance, type BfStatTrend, type BfStatTrendIntent, type BfStatVariant, type BfTabBarController, type BfTabBarHost, type BfTabLinks, type BfTableAlign, type BfTableCellContext, type BfTableColumn, type BfTableRowKey, type BfTableSort, type BfTableSortDirection, type BfTableStack, type BfToggleLabelPosition, type BfValidationMessages, BUTTON_CLASSES as ɵBF_BUTTON_CLASSES, ICON_BUTTON_CLASSES as ɵBF_ICON_BUTTON_CLASSES, BF_NAV as ɵBF_NAV, type BfNavHost as ɵBfNavHost, FIELD_BOX as ɵFIELD_BOX, FIELD_ICON_END as ɵFIELD_ICON_END, FIELD_ICON_START as ɵFIELD_ICON_START, FIELD_PADDING_END as ɵFIELD_PADDING_END, FIELD_PADDING_START as ɵFIELD_PADDING_START, PILL_HIT as ɵPILL_HIT, compareIso as ɵcompareIso, createMemoryColorSchemeStorage as ɵcreateMemoryColorSchemeStorage, daysInMonth as ɵdaysInMonth, fieldDescribedBy as ɵfieldDescribedBy, fieldErrorMessageId as ɵfieldErrorMessageId, fieldErrorText as ɵfieldErrorText, fieldShowsError as ɵfieldShowsError, formatIsoDate as ɵformatIsoDate, localIsoDate as ɵlocalIsoDate, parseIsoDate as ɵparseIsoDate, provideBfDocumentReset as ɵprovideBfDocumentReset, uniqueId as ɵuniqueId, validIsoDate as ɵvalidIsoDate };
7426
+ export { type BfAlertVariant, type BfAppFrameBreakpoint, type BfAppFrameContentWidth, type BfAutocapitalize, type BfAvatarSize, type BfAvatarVariant, type BfBadgeAppearance, type BfBadgeEmphasis, type BfBadgeVariant, type BfButtonAppearance, type BfButtonSize, type BfButtonType, type BfButtonVariant, type BfCalendarDisabledDate, type BfCalendarWeekday, type BfCardPadding, type BfCardVariant, type BfChatMessageRole, type BfChatMessageStatus, type BfChipGroupMode, type BfChipVariant, type BfColorScheme, type BfCompareWith, type BfDefaults, type BfDefaultsOptions, type BfDescriptionListLayout, type BfEmptyStateHeading, type BfEmptyStateHeadingLevel, type BfEnterKeyHint, type BfFieldControlState, type BfFieldSize, type BfFileInputVariant, type BfIconButtonVariant, type BfImageInputShape, type BfInputAutocapitalize, type BfInputEnterKeyHint, type BfInputInputMode, type BfInputMode, type BfInputType, type BfLabels, type BfLabelsOverrides, type BfMediaCardAspect, type BfPageHeaderHeadingLevel, type BfProgressSize, type BfProgressTone, type BfRadioGroupOrientation, type BfRadioOption, type BfRejectedFile, type BfRejectedFileReason, type BfSegmentOption, type BfSegmentedSize, type BfSelectOption, type BfSize, type BfSkeletonShape, type BfSpinnerSize, type BfStatAppearance, type BfStatTrend, type BfStatTrendIntent, type BfStatVariant, type BfStepHeadingLevel, type BfTabBarController, type BfTabBarHost, type BfTabLinks, type BfTableAlign, type BfTableCellContext, type BfTableColumn, type BfTableColumnAlign, type BfTableRowKey, type BfTableSort, type BfTableSortDirection, type BfTableStack, type BfTimelineTone, type BfToggleLabelPosition, type BfValidationMessages, BUTTON_CLASSES as ɵBF_BUTTON_CLASSES, ICON_BUTTON_CLASSES as ɵBF_ICON_BUTTON_CLASSES, BF_NAV as ɵBF_NAV, BfNativeSelectComponent as ɵBfNativeSelectComponent, type BfNavHost as ɵBfNavHost, FIELD_BOX as ɵFIELD_BOX, FIELD_ICON_END as ɵFIELD_ICON_END, FIELD_ICON_START as ɵFIELD_ICON_START, FIELD_PADDING_END as ɵFIELD_PADDING_END, FIELD_PADDING_START as ɵFIELD_PADDING_START, PILL_HIT as ɵPILL_HIT, choiceIndex as ɵchoiceIndex, choiceKey as ɵchoiceKey, compareIso as ɵcompareIso, createMemoryColorSchemeStorage as ɵcreateMemoryColorSchemeStorage, daysInMonth as ɵdaysInMonth, fieldDescribedBy as ɵfieldDescribedBy, fieldErrorMessageId as ɵfieldErrorMessageId, fieldErrorText as ɵfieldErrorText, fieldShowsError as ɵfieldShowsError, formatIsoDate as ɵformatIsoDate, localIsoDate as ɵlocalIsoDate, parseIsoDate as ɵparseIsoDate, provideBfDocumentReset as ɵprovideBfDocumentReset, uniqueId as ɵuniqueId, validIsoDate as ɵvalidIsoDate };