@bitforgehq/ui 0.4.0 → 0.5.0-next.4

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 (87) hide show
  1. package/CHANGELOG.md +350 -0
  2. package/README.md +12 -12
  3. package/ai/MANIFEST.index.json +74 -2
  4. package/ai/MANIFEST.json +3507 -1763
  5. package/ai/REFERENCE.index.json +189 -0
  6. package/ai/REFERENCE.json +1617 -139
  7. package/ai/UI_GUIDE.md +70 -20
  8. package/ai/components/app-frame.md +1 -1
  9. package/ai/components/badge.md +41 -1
  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-tile.md +452 -0
  22. package/ai/components/image-viewer.md +309 -0
  23. package/ai/components/media-card.md +478 -0
  24. package/ai/components/menu-button.md +67 -9
  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/pagination.md +59 -11
  31. package/ai/components/popover.md +13 -0
  32. package/ai/components/radio-group.md +6 -0
  33. package/ai/components/segmented.md +14 -2
  34. package/ai/components/select.md +100 -42
  35. package/ai/components/sidebar-toggle.md +16 -9
  36. package/ai/components/sidebar.md +41 -13
  37. package/ai/components/tab-group.md +18 -2
  38. package/ai/components/table.md +17 -2
  39. package/ai/components/theme-menu-button.md +1 -0
  40. package/ai/components/time-input.md +2 -0
  41. package/ai/components/toolbar.md +7 -18
  42. package/ai/reference/bf-chat-composer-harness.md +64 -0
  43. package/ai/reference/bf-chat-message-role.md +27 -0
  44. package/ai/reference/bf-chat-message-status.md +28 -0
  45. package/ai/reference/bf-fade-in-directive.md +18 -0
  46. package/ai/reference/bf-file-input-harness.md +67 -0
  47. package/ai/reference/bf-file-input-variant.md +27 -0
  48. package/ai/reference/bf-image-input-harness.md +67 -0
  49. package/ai/reference/bf-image-input-shape.md +27 -0
  50. package/ai/reference/bf-image-tile-aspect.md +28 -0
  51. package/ai/reference/bf-image-tile-caption-placement.md +27 -0
  52. package/ai/reference/bf-image-tile-harness.md +70 -0
  53. package/ai/reference/bf-image-viewer-actions-context.md +29 -0
  54. package/ai/reference/bf-image-viewer-harness.md +87 -0
  55. package/ai/reference/bf-image-viewer-image.md +25 -0
  56. package/ai/reference/bf-image-viewer-options.md +33 -0
  57. package/ai/reference/bf-image-viewer-origin.md +23 -0
  58. package/ai/reference/bf-image-viewer-ref.md +55 -0
  59. package/ai/reference/bf-image-viewer-service.md +32 -0
  60. package/ai/reference/bf-labels.md +107 -75
  61. package/ai/reference/bf-media-card-aspect.md +28 -0
  62. package/ai/reference/bf-media-card-harness.md +66 -0
  63. package/ai/reference/bf-motion-list-directive.md +22 -0
  64. package/ai/reference/bf-pagination-position-label.md +22 -0
  65. package/ai/reference/bf-prose-directive.md +16 -0
  66. package/ai/reference/bf-rejected-file-reason.md +28 -0
  67. package/ai/reference/bf-rejected-file.md +28 -0
  68. package/ai/reference/bf-select-harness.md +14 -5
  69. package/ai/reference/bf-select-option.md +2 -0
  70. package/ai/reference/bf-size.md +3 -0
  71. package/ai/reference/bf_table_sort_select.md +23 -0
  72. package/ai/reference/provide-bf-labels.md +11 -3
  73. package/ai/reference/provide-bf-select.md +20 -0
  74. package/bin/bf-ui.js +1 -0
  75. package/fesm2022/bitforgehq-ui-overlay.mjs +2660 -594
  76. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  77. package/fesm2022/bitforgehq-ui-testing.mjs +414 -10
  78. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  79. package/fesm2022/bitforgehq-ui.mjs +8056 -3993
  80. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  81. package/llms-full.txt +4923 -132
  82. package/llms.txt +36 -1
  83. package/package.json +1 -1
  84. package/theme.css +60 -0
  85. package/types/bitforgehq-ui-overlay.d.ts +695 -7
  86. package/types/bitforgehq-ui-testing.d.ts +517 -6
  87. package/types/bitforgehq-ui.d.ts +1699 -181
@@ -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.
@@ -517,6 +517,166 @@ interface BfLabels {
517
517
  * @default 'Unread'
518
518
  */
519
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, bf-image-tile'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-tile's placeholder text while it has no image yet.
652
+ * @default 'No image yet'
653
+ */
654
+ readonly imageTileEmpty: string;
655
+ /**
656
+ * bf-image-tile's selected state, read before the tile's title ("Selected, Dragon").
657
+ * @default 'Selected'
658
+ */
659
+ readonly imageTileSelected: string;
660
+ /**
661
+ * bf-image-viewer's name.
662
+ * @default 'Image viewer'
663
+ */
664
+ readonly imageViewer: string;
665
+ /**
666
+ * bf-image-viewer's button that shows the previous image.
667
+ * @default 'Previous image'
668
+ */
669
+ readonly imageViewerPrevious: string;
670
+ /**
671
+ * bf-image-viewer's button that shows the next image.
672
+ * @default 'Next image'
673
+ */
674
+ readonly imageViewerNext: string;
675
+ /**
676
+ * bf-image-viewer's counter: the shown image's position (1-based) among the images.
677
+ * @default (position, count) => `${position} of ${count}`
678
+ */
679
+ readonly imageViewerPosition: (position: number, count: number) => string;
520
680
  /** Default error text per validator kind (see `BfValidationMessages`). */
521
681
  readonly validation: BfValidationMessages;
522
682
  }
@@ -557,9 +717,17 @@ export declare const BF_LABELS: InjectionToken<BfLabels>;
557
717
  * `dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,
558
718
  * `timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,
559
719
  * `chart`, `chartLegend`, `chartValues`, `chartShowData`, `chartHideData`, `chartCategory`,
560
- * `chartValue`, `chartEmpty`, `chartError`, `listItemUnread`, `validation`); the overlay services (`ok`, `cancel`,
561
- * `actions`, `sheet`, `modal`, `close`, `expand`, `collapse`, `sheetPosition`, `dialog`,
562
- * `notifications`) are root singletons and pick it up only when the caller passes its injector:
720
+ * `chartValue`, `chartEmpty`, `chartError`, `listItemUnread`,
721
+ * `fileInputChoose`, `fileInputDropHint`, `fileInputDropzone`, `fileInputTapToChoose`, `fileInputRemove`,
722
+ * `fileInputDismiss`, `fileInputAdded`, `fileInputNotAdded`, `fileInputRemoved`, `fileInputCount`,
723
+ * `fileRejectedType`, `fileRejectedSize`, `fileRejectedCount`,
724
+ * `imageInputChoose`, `imageInputUpload`, `imageInputReplace`, `imageInputRemove`,
725
+ * `chatThread`, `chatJumpToLatest`, `chatMessageStopped`, `chatMessageFailed`, `chatComposer`,
726
+ * `chatSend`, `chatStop`, `mediaCardSelect`, `imageLoadFailed`, `imageTileEmpty`,
727
+ * `imageTileSelected`, `validation`); the overlay
728
+ * services (`ok`, `cancel`, `actions`, `sheet`, `modal`, `close`, `expand`, `collapse`,
729
+ * `sheetPosition`, `dialog`, `notifications`, `imageViewer`, `imageViewerPrevious`,
730
+ * `imageViewerNext`, `imageViewerPosition`) are root singletons and pick it up only when the caller passes its injector:
563
731
  * `open(…, { injector: inject(Injector) })`.
564
732
  *
565
733
  * @param overrides The strings to replace; any key left out keeps the parent's.
@@ -568,6 +736,51 @@ export declare const BF_LABELS: InjectionToken<BfLabels>;
568
736
  * @since 0.3.0
569
737
  */
570
738
  export declare function provideBfLabels(overrides: BfLabelsOverrides): Provider;
739
+ /**
740
+ * The rule a refused file broke: `type` (not accepted), `size` (over `maxSize`) or `count` (over
741
+ * the limit).
742
+ *
743
+ * @summary The rule a refused file broke, its type, its size or the file count
744
+ * @since 0.5.0
745
+ */
746
+ type BfRejectedFileReason = 'type' | 'size' | 'count';
747
+ /**
748
+ * A picked or dropped file that broke a rule, with the rule and its message.
749
+ *
750
+ * @summary A picked or dropped file that broke a rule, with the rule and its message
751
+ * @since 0.5.0
752
+ */
753
+ interface BfRejectedFile {
754
+ /** The file that was not added. */
755
+ readonly file: File;
756
+ /** The rule it broke: `type` (not accepted), `size` (over `maxSize`) or `count` (over the limit). */
757
+ readonly reason: BfRejectedFileReason;
758
+ /** The user-facing text for the rule, from the labels. */
759
+ readonly message: string;
760
+ }
761
+ /** The rules a file picker applies to what the user picks or drops. */
762
+ interface FileRules {
763
+ readonly accept: string;
764
+ readonly maxSize: number | undefined;
765
+ readonly maxFiles: number | undefined;
766
+ readonly multiple: boolean;
767
+ }
768
+ /** Whether `file` satisfies an `accept` string (MIME types, `type/*` wildcards and `.ext` tokens). */
769
+ declare function matchesAccept(file: File, accept: string): boolean;
770
+ /**
771
+ * Formats a byte count with base 1024: whole KB, one decimal (no trailing `.0`) for MB and GB.
772
+ * The unit is chosen after rounding, so a value that rounds up to 1024 shows as 1 of the next unit.
773
+ */
774
+ declare function formatFileSize(bytes: number): string;
775
+ /**
776
+ * Applies the rules to `incoming` in order and returns the new value and what was rejected.
777
+ * A file is checked for type, then size, then count. In single mode the first valid file
778
+ * replaces `current`; later valid ones are rejected as `count` with a max of 1.
779
+ */
780
+ declare function applyFileRules(current: readonly File[], incoming: readonly File[], rules: FileRules, labels: BfLabels): {
781
+ value: File[];
782
+ rejected: BfRejectedFile[];
783
+ };
571
784
  /**
572
785
  * The app's colour scheme choice: follow the system, or force light or dark.
573
786
  *
@@ -1032,10 +1245,12 @@ type BfBadgeEmphasis = 'soft' | 'strong';
1032
1245
  * <bf-badge variant="warning">At risk</bf-badge>
1033
1246
  * <bf-badge variant="danger" emphasis="strong">Critical</bf-badge>
1034
1247
  * <bf-badge [count]="unread()" countLabel="unread messages" />
1248
+ * <bf-badge variant="success"><bf-icon bfSlot="icon-start" [icon]="Check" />Reference</bf-badge>
1035
1249
  * @summary Small status label in semantic tones, or a compact numeric pill for counts
1036
1250
  * @category Data display
1037
1251
  * @since 0.1.0
1038
1252
  * @slot default The badge text, shown when no `count` is set.
1253
+ * @slot icon-start An icon before the text, drawn at 12 px; not shown on a count pill.
1039
1254
  */
1040
1255
  export declare class BfBadgeComponent {
1041
1256
  /** Tint for the label style: neutral, primary, success, warning, danger or info. Ignored by a
@@ -1074,7 +1289,7 @@ export declare class BfBadgeComponent {
1074
1289
  "required": false;
1075
1290
  "isSignal": true;
1076
1291
  };
1077
- }, {}, never, ["*"], true, never>;
1292
+ }, {}, never, ["[bfSlot=icon-start]", "*"], true, never>;
1078
1293
  }
1079
1294
  /** A message paragraph that never moves the layout under a pressed pointer. */
1080
1295
  interface HeldMessage {
@@ -1126,6 +1341,9 @@ export declare class BfFieldComponent {
1126
1341
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1127
1342
  /** The id of the control inside the default slot. */
1128
1343
  readonly controlId: import("@angular/core").InputSignal<string>;
1344
+ /** The label's id (`"<controlId>-label"`), for a control a `<label for>` cannot name, such as a
1345
+ * `div[role=combobox]` that names itself with `aria-labelledby`. */
1346
+ readonly labelId: import("@angular/core").Signal<string>;
1129
1347
  readonly helpId: import("@angular/core").Signal<string>;
1130
1348
  readonly errorId: import("@angular/core").Signal<string>;
1131
1349
  /** The error paragraph: `error`, visually hidden while a press holds the layout. */
@@ -1786,173 +2004,255 @@ export declare class BfTextareaComponent implements FormValueControl<string> {
1786
2004
  }, never, never, true, never>;
1787
2005
  }
1788
2006
  /**
1789
- * Equality that matches a bound value to an option's value (`compareWith` of the choice controls).
1790
- * Called as `compareWith(optionValue, value)`: the option's value first, the bound value second.
1791
- *
1792
- * @summary Equality of choice values, for options that are objects rather than primitives
1793
- * @since 0.3.0
1794
- */
1795
- type BfCompareWith<T> = (optionValue: T, value: T) => boolean;
1796
- /**
1797
- * Index of the option whose value matches `value` under `compare`; `-1` for `null` (nothing
1798
- * selected) or a value no option matches. An option whose value is `null` therefore never shows as
1799
- * selected.
1800
- */
1801
- declare function choiceIndex<T>(options: readonly {
1802
- readonly value: T;
1803
- }[], value: T | null, compare: BfCompareWith<T>): number;
1804
- /**
1805
- * The native `value` of an option (`<option>`, radio): `String(value)` for a string, number or
1806
- * boolean, so native reads, form posts and browser tests see the real value; the option index for
1807
- * anything else (objects). The controls map a choice back by position, never by this key, so
1808
- * duplicate keys still resolve to the right option.
1809
- */
1810
- declare function choiceKey(value: unknown, index: number): string;
1811
- /**
1812
- * One choice of `bf-select`; `value` is any type (a string by default).
2007
+ * The look of a `bf-file-input`: a compact field with a pick button (`field`), or a drop zone
2008
+ * (`dropzone`). Both have the same API and behaviour.
1813
2009
  *
1814
- * @summary One choice of a select with its value, its label and a disabled flag
1815
- * @since 0.1.0
2010
+ * @summary The two looks of a file picker, a compact field or a drop zone
2011
+ * @since 0.5.0
1816
2012
  */
1817
- interface BfSelectOption<T = string> {
1818
- /** The value the select's `value` takes when this option is chosen. */
1819
- value: T;
1820
- /** The text of the native `option`. */
1821
- label: string;
1822
- /** Whether this option cannot be chosen. */
1823
- disabled?: boolean;
2013
+ type BfFileInputVariant = 'field' | 'dropzone';
2014
+ /** A chosen file as listed: its preview URL (images) or extension, and its formatted size. */
2015
+ interface BfFileInputRow {
2016
+ readonly file: File;
2017
+ readonly preview: string | null;
2018
+ readonly extension: string;
2019
+ readonly size: string;
1824
2020
  }
2021
+ /** The upper-case extension of `name`, at most 4 characters; empty without one. */
2022
+ declare function extensionOf(name: string): string;
1825
2023
  /**
1826
- * Native single-choice dropdown styled like the text field, with a placeholder option and
1827
- * Signal Forms binding. Generic over the option value (a string by default); `null` means nothing
1828
- * is selected.
2024
+ * File picker field: a tonal "Choose file" button inside a field box that also takes dropped
2025
+ * files, with the chosen files listed under it (thumbnail, name, size, remove button). `accept`,
2026
+ * `maxSize` and `maxFiles` decide what is added; a file that breaks one is listed in red with the
2027
+ * reason until the next pick and emitted through `rejected`, and never enters the value. The value
2028
+ * is always a `File[]` (one file at most while `multiple` is off). It only picks: uploading the
2029
+ * files is the app's job. Bind `[(value)]` directly or `[formField]` from a signal form.
1829
2030
  *
1830
2031
  * @example
1831
- * <bf-select label="Project" placeholder="Pick a project" [options]="projects" [(value)]="projectId" />
1832
- * <bf-select label="Team" placeholder="Pick a team" [options]="teams" [formField]="task.teamId" />
1833
- * @summary Native single-choice dropdown styled like the text field, with Signal Forms binding
2032
+ * <bf-file-input label="Attachments" multiple accept=".pdf,image/*" [maxSize]="10485760" [(value)]="files" />
2033
+ * <bf-file-input label="Contract" accept=".pdf" [formField]="signup.contract" />
2034
+ * @summary File picker field that lists picked or dropped files and checks type, size and count
1834
2035
  * @category Forms
1835
- * @since 0.1.0
1836
- */
1837
- export declare class BfSelectComponent<T = string> implements FormValueControl<T | null> {
1838
- /** The selected option's value, `null` while nothing is selected; two-way bindable and bound by
1839
- * `[formField]`. */
1840
- readonly value: import("@angular/core").ModelSignal<T | null>;
1841
- /** The options to choose from. */
1842
- readonly options: import("@angular/core").InputSignal<readonly BfSelectOption<T>[]>;
1843
- /** Equality for matching `value` to an option (default `Object.is`); pass one for object values. */
1844
- readonly compareWith: import("@angular/core").InputSignal<BfCompareWith<T>>;
1845
- /** Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md`
1846
- * by default). */
2036
+ * @since 0.5.0
2037
+ */
2038
+ export declare class BfFileInputComponent implements FormValueControl<File[]> {
2039
+ /** The chosen files; two-way bindable and bound by `[formField]`. Empty while nothing is chosen,
2040
+ * at most one file while `multiple` is off. */
2041
+ readonly value: import("@angular/core").ModelSignal<File[]>;
2042
+ /** The look: `field` (a compact field with a pick button, the default) or `dropzone`. */
2043
+ readonly variant: import("@angular/core").InputSignal<BfFileInputVariant>;
2044
+ /** Allows several files: new picks and drops are added to the list. Off, a new file replaces
2045
+ * the one chosen. */
2046
+ readonly multiple: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2047
+ /** The accepted file types, in the native `accept` syntax: MIME types, `type/*` wildcards and
2048
+ * `.ext` extensions, comma-separated (`'.pdf,image/*'`). Empty accepts every file. Dropped files
2049
+ * are checked against it too. */
2050
+ readonly accept: import("@angular/core").InputSignal<string>;
2051
+ /** Largest file size in bytes; a larger file is rejected. Unset allows any size. */
2052
+ readonly maxSize: import("@angular/core").InputSignal<number | undefined>;
2053
+ /** Most files in the value with `multiple`, counting those already chosen; files past it are
2054
+ * rejected. Unset allows any number. */
2055
+ readonly maxFiles: import("@angular/core").InputSignal<number | undefined>;
2056
+ /** Height step of the field box: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()`
2057
+ * (`fieldSize`, `md` by default). */
1847
2058
  readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
1848
2059
  /** Visible label above the control. */
1849
2060
  readonly label: import("@angular/core").InputSignal<string>;
1850
2061
  /**
1851
- * Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label`
1852
- * is set.
2062
+ * Accessible name when no visible label is rendered, read before the control's visible text
2063
+ * ("Attachments, Choose files"). Ignored when `label` is set.
1853
2064
  */
1854
2065
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
1855
2066
  /**
1856
- * Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when
1857
- * `label` is set.
2067
+ * Id(s) of the element(s) that name the field when no visible label is rendered, read before the
2068
+ * control's visible text. Ignored when `label` is set.
1858
2069
  */
1859
2070
  readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
1860
- /** Disabled first option shown while nothing is selected. */
1861
- readonly placeholder: import("@angular/core").InputSignal<string>;
1862
2071
  /** Helper text under the control. */
1863
2072
  readonly helpText: import("@angular/core").InputSignal<string>;
1864
2073
  /** Explicit error text; takes precedence over Signal Forms errors. */
1865
2074
  readonly errorMessage: import("@angular/core").InputSignal<string>;
1866
- /** Disables the control. */
2075
+ /** Disables the control: no picking, dropping or removing, and the pick button is not focusable. */
1867
2076
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1868
- /** Shows the value but blocks changes; the select stays focusable and exposes `aria-readonly`. */
2077
+ /** Makes the control read-only: the files stay listed, with no pick button, drop or remove. */
1869
2078
  readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1870
- /** Marks the control required. */
2079
+ /** Marks the control required (label marker). */
1871
2080
  readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1872
2081
  /** Field validity from Signal Forms. */
1873
2082
  readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1874
- /** Whether the user has left the field. */
2083
+ /** Whether the user has left the field; errors show only once touched. */
1875
2084
  readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1876
- /** Validation errors from Signal Forms. */
2085
+ /** Validation errors from Signal Forms; the first message is shown once touched. */
1877
2086
  readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
1878
- /** Native name attribute. */
2087
+ /** Native name attribute; set, the hidden native file input carries the chosen files, so a
2088
+ * plain HTML form post includes them. */
1879
2089
  readonly name: import("@angular/core").InputSignal<string>;
1880
- /** Control id; generated when not set. */
2090
+ /** Control id (the pick button's); generated when not set. */
1881
2091
  readonly id: import("@angular/core").InputSignal<string>;
1882
- /** Emitted with the chosen value after a user change (never `null`). */
1883
- readonly changed: import("@angular/core").OutputEmitterRef<T>;
1884
- /** Emitted when the control gains focus. */
2092
+ /** Emitted with the new files right after the user adds or removes one, not on a model write. */
2093
+ readonly changed: import("@angular/core").OutputEmitterRef<File[]>;
2094
+ /** Emitted once per pick or drop in which at least one file broke a rule, with those files. */
2095
+ readonly rejected: import("@angular/core").OutputEmitterRef<BfRejectedFile[]>;
2096
+ /** Emitted when the pick button gains focus. */
1885
2097
  readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
1886
- /** Emitted when the control loses focus. */
2098
+ /** Emitted when the pick button loses focus. */
1887
2099
  readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
1888
2100
  /** Emitted on blur so Signal Forms marks the field touched. */
1889
2101
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
1890
- private readonly el;
2102
+ private readonly pick;
2103
+ private readonly native;
2104
+ private readonly removeButtons;
2105
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
1891
2106
  private readonly defaults;
2107
+ private readonly injector;
2108
+ private readonly document;
2109
+ /** Whether the primary pointer is coarse (touch), from `matchMedia`: the drop zone then says
2110
+ * "tap" and takes less padding. */
2111
+ protected readonly coarse: import("@angular/core").WritableSignal<boolean>;
2112
+ /** The files of the last pick or drop that broke a rule, until the next one or a dismiss. */
2113
+ protected readonly rejectedFiles: import("@angular/core").WritableSignal<readonly BfRejectedFile[]>;
2114
+ /** The polite live message after an add, a rejection or a remove. */
2115
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
2116
+ /** The pending write of the next live message. */
2117
+ private announceTimer;
2118
+ /** Whether a drag is over the box. */
2119
+ private readonly dragging;
2120
+ /** The object URL of each image file in the value, for its thumbnail. */
2121
+ private readonly previews;
2122
+ protected readonly thumbClasses: string;
2123
+ protected readonly removeClasses: string;
2124
+ protected readonly crossClasses: string;
2125
+ 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";
2126
+ protected readonly extensionOf: typeof extensionOf;
2127
+ protected readonly badgeClasses = "grid size-11 place-items-center rounded-full bg-primary-soft text-primary-strong";
2128
+ protected readonly arrowClasses: string;
1892
2129
  /** The size in effect: `size`, else the app's `fieldSize` default. */
1893
2130
  protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
1894
- readonly wrapperClasses: import("@angular/core").Signal<string>;
1895
- /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
1896
- readonly selectedIndex: import("@angular/core").Signal<number>;
1897
- readonly showEmptyOption: import("@angular/core").Signal<boolean>;
1898
- private readonly labels;
2131
+ /** Whether the user can pick, drop and remove files. */
2132
+ protected readonly editable: import("@angular/core").Signal<boolean>;
1899
2133
  readonly errorText: import("@angular/core").Signal<string>;
1900
2134
  readonly showError: import("@angular/core").Signal<boolean>;
1901
- readonly describedBy: import("@angular/core").Signal<string | null>;
2135
+ /** `aria-describedby` of the pick button: the text beside it (hint or file name), then the help
2136
+ * and error paragraphs. */
2137
+ readonly describedBy: import("@angular/core").Signal<string>;
1902
2138
  /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
1903
2139
  readonly errorMessageId: import("@angular/core").Signal<string | null>;
1904
- /**
1905
- * `aria-label` value: only when there is no visible label, so the label stays the sole accessible
1906
- * name.
1907
- */
2140
+ /** `ariaLabel` in effect: only when there is no visible label. It names the control through a
2141
+ * hidden element, never `aria-label`, so the visible text stays part of the name. */
1908
2142
  readonly ariaLabelAttr: import("@angular/core").Signal<string | null>;
1909
- /** `aria-labelledby` value, under the same rule as `ariaLabelAttr`. */
1910
- readonly ariaLabelledbyAttr: import("@angular/core").Signal<string | null>;
1911
- readonly classes: import("@angular/core").Signal<string>;
1912
- /** Native `value` of an option: its value for a primitive, its index for an object. */
1913
- protected readonly key: typeof choiceKey;
2143
+ /** `aria-labelledby` of the pick button or drop zone: the field's name (the visible label, else
2144
+ * `ariaLabelledby`, else `ariaLabel`), then the control's visible text ("Contract, Choose file"),
2145
+ * so a speech-input user can say what they see (WCAG 2.5.3). */
2146
+ readonly ariaLabelledbyAttr: import("@angular/core").Signal<string>;
2147
+ /** The drop zone's `aria-describedby`: the help and error paragraphs (its text is its name). */
2148
+ protected readonly zoneDescribedBy: import("@angular/core").Signal<string | null>;
2149
+ /** The drop zone's text: the drop prompt, or the tap prompt under a coarse pointer. */
2150
+ protected readonly zoneText: import("@angular/core").Signal<string>;
2151
+ /** The text in the box: the file name (one file, single mode), the file count (read-only with
2152
+ * `multiple`), else the drop hint. */
2153
+ protected readonly boxText: import("@angular/core").Signal<string>;
2154
+ protected readonly rows: import("@angular/core").Signal<BfFileInputRow[]>;
2155
+ readonly boxClasses: import("@angular/core").Signal<string>;
2156
+ protected readonly zoneClasses: import("@angular/core").Signal<string>;
2157
+ protected readonly pickClasses: import("@angular/core").Signal<string>;
2158
+ protected readonly hintClasses: import("@angular/core").Signal<string>;
2159
+ protected readonly rowClasses: import("@angular/core").Signal<string>;
2160
+ constructor();
1914
2161
  /**
1915
- * Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
2162
+ * Focuses the pick button; Signal Forms calls it from `focusBoundControl()`. Read-only has no
2163
+ * pick button, so nothing is focused.
1916
2164
  * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
1917
2165
  */
1918
2166
  focus(options?: FocusOptions): void;
1919
2167
  /**
1920
- * Read-only: no picker on a mouse press. Cancelling the press also cancels its focus, so the
1921
- * select is focused explicitly, like any other read-only control. Touch screens open the
1922
- * platform picker without a cancellable press; a choice made there is reverted by `onChange`.
2168
+ * Opens the native picker from the drop zone while the control is editable.
1923
2169
  * @internal
1924
2170
  */
1925
- onMousedown(event: MouseEvent): void;
2171
+ openPicker(): void;
1926
2172
  /**
1927
- * Read-only: no picker or value change from the keyboard (arrows, Enter, Space, F4, type-ahead).
2173
+ * Enter and Space on the drop zone open the picker (and do not scroll the page).
1928
2174
  * @internal
1929
2175
  */
1930
- onKeydown(event: KeyboardEvent): void;
2176
+ onZoneKey(event: Event): void;
1931
2177
  /**
1932
- * Writes the chosen option's value to the model and emits `changed`; while read-only, restores
1933
- * the shown option instead.
2178
+ * Adds the native input's files, then empties it so picking the same file again fires `change`.
1934
2179
  * @internal
1935
2180
  */
1936
- onChange(el: HTMLSelectElement): void;
2181
+ onPicked(input: HTMLInputElement): void;
2182
+ /**
2183
+ * Allows a drop on the box and highlights it while the control is editable.
2184
+ * @internal
2185
+ */
2186
+ onDragOver(event: DragEvent): void;
2187
+ /**
2188
+ * Ends the highlight when the drag leaves the box (not when it moves onto a child).
2189
+ * @internal
2190
+ */
2191
+ onDragLeave(event: DragEvent, box: HTMLElement): void;
2192
+ /**
2193
+ * Adds the dropped files through the same rules as a pick; ignores them (but still keeps the
2194
+ * browser from opening them) while the control cannot be edited.
2195
+ * @internal
2196
+ */
2197
+ onDrop(event: DragEvent): void;
1937
2198
  /**
1938
2199
  * Marks the field touched and emits `blurred`.
1939
2200
  * @internal
1940
2201
  */
1941
2202
  onBlur(event: FocusEvent): void;
1942
- static ɵfac: i0.ɵɵFactoryDeclaration<BfSelectComponent<any>, never>;
1943
- static ɵcmp: i0.ɵɵComponentDeclaration<BfSelectComponent<any>, "bf-select", never, {
2203
+ /**
2204
+ * Removes the file at `index`, then focuses the next remove button, else the previous one,
2205
+ * else the pick button.
2206
+ * @internal
2207
+ */
2208
+ removeAt(index: number): void;
2209
+ /**
2210
+ * Dismisses the rejected file at `index` and returns focus to the pick button.
2211
+ * @internal
2212
+ */
2213
+ dismissAt(index: number): void;
2214
+ /** Runs `files` through the rules: accepted ones join the value, rejected ones are listed. */
2215
+ private add;
2216
+ /**
2217
+ * Puts `text` in the live region. The region is emptied first and the text follows after
2218
+ * `ANNOUNCE_DELAY`, so a message equal to the last one ("1 file added" twice) still changes the
2219
+ * DOM in a separate update and is announced again.
2220
+ */
2221
+ private announce;
2222
+ /** Keeps one object URL per image in `files`: creates the missing ones, revokes the rest. */
2223
+ private syncPreviews;
2224
+ /** With a `name`, puts the current files on the hidden native input, so a plain form post sends
2225
+ * them. */
2226
+ private writeNativeFiles;
2227
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfFileInputComponent, never>;
2228
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfFileInputComponent, "bf-file-input", never, {
1944
2229
  "value": {
1945
2230
  "alias": "value";
1946
2231
  "required": false;
1947
2232
  "isSignal": true;
1948
2233
  };
1949
- "options": {
1950
- "alias": "options";
1951
- "required": true;
2234
+ "variant": {
2235
+ "alias": "variant";
2236
+ "required": false;
1952
2237
  "isSignal": true;
1953
2238
  };
1954
- "compareWith": {
1955
- "alias": "compareWith";
2239
+ "multiple": {
2240
+ "alias": "multiple";
2241
+ "required": false;
2242
+ "isSignal": true;
2243
+ };
2244
+ "accept": {
2245
+ "alias": "accept";
2246
+ "required": false;
2247
+ "isSignal": true;
2248
+ };
2249
+ "maxSize": {
2250
+ "alias": "maxSize";
2251
+ "required": false;
2252
+ "isSignal": true;
2253
+ };
2254
+ "maxFiles": {
2255
+ "alias": "maxFiles";
1956
2256
  "required": false;
1957
2257
  "isSignal": true;
1958
2258
  };
@@ -1976,11 +2276,6 @@ export declare class BfSelectComponent<T = string> implements FormValueControl<T
1976
2276
  "required": false;
1977
2277
  "isSignal": true;
1978
2278
  };
1979
- "placeholder": {
1980
- "alias": "placeholder";
1981
- "required": false;
1982
- "isSignal": true;
1983
- };
1984
2279
  "helpText": {
1985
2280
  "alias": "helpText";
1986
2281
  "required": false;
@@ -2034,60 +2329,373 @@ export declare class BfSelectComponent<T = string> implements FormValueControl<T
2034
2329
  }, {
2035
2330
  "value": "valueChange";
2036
2331
  "changed": "changed";
2332
+ "rejected": "rejected";
2037
2333
  "focused": "focused";
2038
2334
  "blurred": "blurred";
2039
2335
  "touch": "touch";
2040
2336
  }, never, never, true, never>;
2041
2337
  }
2042
2338
  /**
2043
- * Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
2044
- * checkbox contract so `[formField]` binds `checked`.
2339
+ * The shape of a `bf-image-input` tile: a circle (`round`, for avatars) or a rounded square
2340
+ * (`square`, for logos and covers).
2341
+ *
2342
+ * @summary The two tile shapes of an image picker, a circle or a rounded square
2343
+ * @since 0.5.0
2344
+ */
2345
+ type BfImageInputShape = 'round' | 'square';
2346
+ /**
2347
+ * Single-image picker for an avatar, logo or cover: a round or square tile that shows the image
2348
+ * (or a camera glyph while empty), with the label, help text and Upload/Replace and Remove buttons
2349
+ * beside it. Clicking the tile or Upload opens the native picker; an image dropped on the tile is
2350
+ * taken too. `src` shows an image the app already has until the user picks a new one or removes
2351
+ * it. A pick that breaks `accept` or `maxSize` keeps the current image, shows the reason under the
2352
+ * buttons and is emitted through `rejected`. It only picks: uploading is the app's job. Bind
2353
+ * `[(value)]` directly or `[formField]` from a signal form.
2045
2354
  *
2046
2355
  * @example
2047
- * <bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
2048
- * <bf-checkbox label="Notify me" [formField]="prefs.notify" />
2049
- * @summary Rounded checkbox with an optional label that binds to Signal Forms
2356
+ * <bf-image-input label="Profile picture" [(value)]="avatar" [(src)]="avatarUrl" />
2357
+ * <bf-image-input label="Logo" shape="square" [maxSize]="2097152" [formField]="company.logo" />
2358
+ * @summary Single-image picker with a round or square preview tile and replace or remove
2050
2359
  * @category Forms
2051
- * @since 0.1.0
2052
- * @slot default Rich label content next to the box, before the `label` text.
2053
- */
2054
- export declare class BfCheckboxComponent implements FormCheckboxControl {
2055
- /** Whether the box is checked; two-way bindable and bound by `[formField]`. */
2056
- readonly checked: import("@angular/core").ModelSignal<boolean>;
2057
- /** Label text; the default slot works too. */
2360
+ * @since 0.5.0
2361
+ */
2362
+ export declare class BfImageInputComponent implements FormValueControl<File | null> {
2363
+ /** The picked image; two-way bindable and bound by `[formField]`. Null while none is picked. */
2364
+ readonly value: import("@angular/core").ModelSignal<File | null>;
2365
+ /** URL of an image the app already has (the current avatar), shown while `value` is null. Remove
2366
+ * sets it to null; two-way bindable. */
2367
+ readonly src: import("@angular/core").ModelSignal<string | null>;
2368
+ /** The tile's shape: `round` (a circle, the default) or `square` (a rounded square). */
2369
+ readonly shape: import("@angular/core").InputSignal<BfImageInputShape>;
2370
+ /** Tile size: sm 64, md 96, lg 128 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by
2371
+ * default). */
2372
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2373
+ /** The accepted file types, in the native `accept` syntax (`'image/png,image/jpeg'`); dropped
2374
+ * files are checked against it too. */
2375
+ readonly accept: import("@angular/core").InputSignal<string>;
2376
+ /** Largest file size in bytes; a larger image is rejected. Unset allows any size. */
2377
+ readonly maxSize: import("@angular/core").InputSignal<number | undefined>;
2378
+ /** Visible label beside the tile. */
2058
2379
  readonly label: import("@angular/core").InputSignal<string>;
2059
- /** Accessible name when there is no visible `label`, e.g. a row-selection box. */
2380
+ /** Accessible name of the tile when no visible label is rendered, in place of "Choose image".
2381
+ * Ignored when `label` is set. */
2060
2382
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
2061
- /** Id(s) of the element(s) that name the control when no visible label is rendered (e.g. a
2062
- * table column header). */
2383
+ /** Id(s) of the element(s) that name the tile when no visible label is rendered. Ignored when
2384
+ * `label` is set. */
2063
2385
  readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
2064
- /** Height step of the row: sm 32, md 40, lg 48 px (44 px under a coarse pointer for sm and md);
2065
- * unset follows `provideBfDefaults()` (`controlSize`, `md` by default). */
2066
- readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2067
- /** Shows the mixed state (e.g. "select all" with some rows selected); two-way bindable. A user
2068
- * change clears it, like the native checkbox, so the box and `aria-checked` agree. */
2069
- readonly indeterminate: import("@angular/core").ModelSignal<boolean>;
2070
- /** Disables the control. */
2386
+ /** Helper text under the label. */
2387
+ readonly helpText: import("@angular/core").InputSignal<string>;
2388
+ /** Explicit error text; takes precedence over Signal Forms errors. */
2389
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
2390
+ /** Disables the control: the tile and buttons are disabled and drops are ignored. */
2071
2391
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2072
- /** Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. */
2392
+ /** Makes the control read-only: the image shows in a plain tile, with no buttons or drop. */
2073
2393
  readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2074
- /** Marks the control required. */
2394
+ /** Marks the control required (label marker). A `value` or a `src` fills it; see the docs for a
2395
+ * validator that counts `src`. */
2075
2396
  readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2076
2397
  /** Field validity from Signal Forms. */
2077
2398
  readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2078
- /** Whether the user has left the field. */
2399
+ /** Whether the user has left the field; errors show only once touched. */
2079
2400
  readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2080
- /** Validation errors from Signal Forms. */
2401
+ /** Validation errors from Signal Forms; the first message is shown once touched. */
2081
2402
  readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
2082
- /** Explicit error text; takes precedence over Signal Forms errors. */
2083
- readonly errorMessage: import("@angular/core").InputSignal<string>;
2084
- /** Native name attribute. */
2403
+ /** Native name attribute; set, the hidden native file input carries the picked image, so a plain
2404
+ * HTML form post includes it. */
2085
2405
  readonly name: import("@angular/core").InputSignal<string>;
2086
- /** Control id; generated when not set. */
2406
+ /** Control id (the tile's); generated when not set. */
2087
2407
  readonly id: import("@angular/core").InputSignal<string>;
2088
- /** Emitted with the new checked state after a user change. */
2089
- readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
2090
- /** Emitted on blur so Signal Forms marks the field touched. */
2408
+ /** Emitted with the new image (or null after Remove) right after a user change, not on a model
2409
+ * write. */
2410
+ readonly changed: import("@angular/core").OutputEmitterRef<File | null>;
2411
+ /** Emitted once per pick or drop in which a file broke a rule, with those files. */
2412
+ readonly rejected: import("@angular/core").OutputEmitterRef<BfRejectedFile[]>;
2413
+ /** Emitted when the tile gains focus. */
2414
+ readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
2415
+ /** Emitted when the tile loses focus. */
2416
+ readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
2417
+ /** Emitted on blur of the tile or a button so Signal Forms marks the field touched. */
2418
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
2419
+ private readonly tile;
2420
+ private readonly native;
2421
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
2422
+ private readonly defaults;
2423
+ /** Whether a drag is over the tile. */
2424
+ protected readonly dragging: import("@angular/core").WritableSignal<boolean>;
2425
+ /** The reason the last pick or drop was refused, until the next one or Remove. */
2426
+ protected readonly rejection: import("@angular/core").WritableSignal<string>;
2427
+ /** The polite live message: the rejection reason, written after a pause. */
2428
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
2429
+ /** The pending write of the live message. */
2430
+ private announceTimer;
2431
+ /** The object URL of the picked image, with the file it was made for. */
2432
+ private readonly objectUrl;
2433
+ protected readonly uploadClasses: string;
2434
+ protected readonly removeClasses: string;
2435
+ /** The size in effect: `size`, else the app's `fieldSize` default. */
2436
+ protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
2437
+ /** Whether the user can pick, drop and remove. */
2438
+ protected readonly editable: import("@angular/core").Signal<boolean>;
2439
+ /** What the tile shows: the picked image's object URL, else `src`, else nothing. */
2440
+ protected readonly preview: import("@angular/core").Signal<string | null>;
2441
+ /** Whether an image (picked or existing) is set: Upload reads Replace and Remove shows. */
2442
+ protected readonly shows: import("@angular/core").Signal<boolean>;
2443
+ readonly errorText: import("@angular/core").Signal<string>;
2444
+ readonly showError: import("@angular/core").Signal<boolean>;
2445
+ /** `aria-describedby` of the tile: the label (the tile's name is the action), then the help and
2446
+ * error paragraphs. */
2447
+ readonly describedBy: import("@angular/core").Signal<string | null>;
2448
+ /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
2449
+ readonly errorMessageId: import("@angular/core").Signal<string | null>;
2450
+ /** The tile's `aria-labelledby`: `ariaLabelledby` while there is no visible label. */
2451
+ protected readonly tileLabelledby: import("@angular/core").Signal<string | null>;
2452
+ /** The tile's `aria-label`: "Choose image", or `ariaLabel` while there is no visible label. */
2453
+ protected readonly tileLabel: import("@angular/core").Signal<string | null>;
2454
+ /** The buttons' `aria-describedby`: the label, so "Remove" says what it removes. */
2455
+ protected readonly actionDescribedBy: import("@angular/core").Signal<string | null>;
2456
+ protected readonly tileClasses: import("@angular/core").Signal<string>;
2457
+ protected readonly glyphClasses: import("@angular/core").Signal<string>;
2458
+ constructor();
2459
+ /**
2460
+ * Focuses the tile; Signal Forms calls it from `focusBoundControl()`. Read-only has no button, so
2461
+ * nothing is focused.
2462
+ * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
2463
+ */
2464
+ focus(options?: FocusOptions): void;
2465
+ /**
2466
+ * Takes the native input's file, then empties it so picking the same file again fires `change`.
2467
+ * @internal
2468
+ */
2469
+ onPicked(input: HTMLInputElement): void;
2470
+ /**
2471
+ * Allows a drop on the tile and highlights it while the control is editable. Otherwise it still
2472
+ * cancels the event, so the browser does not open a dropped file and leave the page.
2473
+ * @internal
2474
+ */
2475
+ onDragOver(event: DragEvent): void;
2476
+ /**
2477
+ * Takes the dropped file through the same rules as a pick; ignores it (but still keeps the
2478
+ * browser from opening it) while the control cannot be edited.
2479
+ * @internal
2480
+ */
2481
+ onDrop(event: DragEvent): void;
2482
+ /**
2483
+ * Marks the field touched and emits `blurred`.
2484
+ * @internal
2485
+ */
2486
+ onBlur(event: FocusEvent): void;
2487
+ /**
2488
+ * Clears the picked and the existing image, then focuses the tile.
2489
+ * @internal
2490
+ */
2491
+ remove(): void;
2492
+ /** Runs `files` through the rules: the first accepted one replaces the image; a refusal keeps it
2493
+ * and shows the reason. */
2494
+ private take;
2495
+ /**
2496
+ * Writes `text` to the rejection line at once and announces it through the live region. The
2497
+ * region is emptied first and the text follows after `ANNOUNCE_DELAY`, so the same reason twice
2498
+ * is still announced again.
2499
+ */
2500
+ private showRejection;
2501
+ /** Keeps one object URL for `file`: makes it when the file changes, revokes the previous one. */
2502
+ private syncObjectUrl;
2503
+ /** With a `name`, puts the picked image on the hidden native input, so a plain form post sends
2504
+ * it. */
2505
+ private writeNativeFiles;
2506
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfImageInputComponent, never>;
2507
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfImageInputComponent, "bf-image-input", never, {
2508
+ "value": {
2509
+ "alias": "value";
2510
+ "required": false;
2511
+ "isSignal": true;
2512
+ };
2513
+ "src": {
2514
+ "alias": "src";
2515
+ "required": false;
2516
+ "isSignal": true;
2517
+ };
2518
+ "shape": {
2519
+ "alias": "shape";
2520
+ "required": false;
2521
+ "isSignal": true;
2522
+ };
2523
+ "size": {
2524
+ "alias": "size";
2525
+ "required": false;
2526
+ "isSignal": true;
2527
+ };
2528
+ "accept": {
2529
+ "alias": "accept";
2530
+ "required": false;
2531
+ "isSignal": true;
2532
+ };
2533
+ "maxSize": {
2534
+ "alias": "maxSize";
2535
+ "required": false;
2536
+ "isSignal": true;
2537
+ };
2538
+ "label": {
2539
+ "alias": "label";
2540
+ "required": false;
2541
+ "isSignal": true;
2542
+ };
2543
+ "ariaLabel": {
2544
+ "alias": "ariaLabel";
2545
+ "required": false;
2546
+ "isSignal": true;
2547
+ };
2548
+ "ariaLabelledby": {
2549
+ "alias": "ariaLabelledby";
2550
+ "required": false;
2551
+ "isSignal": true;
2552
+ };
2553
+ "helpText": {
2554
+ "alias": "helpText";
2555
+ "required": false;
2556
+ "isSignal": true;
2557
+ };
2558
+ "errorMessage": {
2559
+ "alias": "errorMessage";
2560
+ "required": false;
2561
+ "isSignal": true;
2562
+ };
2563
+ "disabled": {
2564
+ "alias": "disabled";
2565
+ "required": false;
2566
+ "isSignal": true;
2567
+ };
2568
+ "readonly": {
2569
+ "alias": "readonly";
2570
+ "required": false;
2571
+ "isSignal": true;
2572
+ };
2573
+ "required": {
2574
+ "alias": "required";
2575
+ "required": false;
2576
+ "isSignal": true;
2577
+ };
2578
+ "invalid": {
2579
+ "alias": "invalid";
2580
+ "required": false;
2581
+ "isSignal": true;
2582
+ };
2583
+ "touched": {
2584
+ "alias": "touched";
2585
+ "required": false;
2586
+ "isSignal": true;
2587
+ };
2588
+ "errors": {
2589
+ "alias": "errors";
2590
+ "required": false;
2591
+ "isSignal": true;
2592
+ };
2593
+ "name": {
2594
+ "alias": "name";
2595
+ "required": false;
2596
+ "isSignal": true;
2597
+ };
2598
+ "id": {
2599
+ "alias": "id";
2600
+ "required": false;
2601
+ "isSignal": true;
2602
+ };
2603
+ }, {
2604
+ "value": "valueChange";
2605
+ "src": "srcChange";
2606
+ "changed": "changed";
2607
+ "rejected": "rejected";
2608
+ "focused": "focused";
2609
+ "blurred": "blurred";
2610
+ "touch": "touch";
2611
+ }, never, never, true, never>;
2612
+ }
2613
+ /**
2614
+ * Equality that matches a bound value to an option's value (`compareWith` of the choice controls).
2615
+ * Called as `compareWith(optionValue, value)`: the option's value first, the bound value second.
2616
+ *
2617
+ * @summary Equality of choice values, for options that are objects rather than primitives
2618
+ * @since 0.3.0
2619
+ */
2620
+ type BfCompareWith<T> = (optionValue: T, value: T) => boolean;
2621
+ /**
2622
+ * Index of the option whose value matches `value` under `compare`; `-1` for `null` (nothing
2623
+ * selected) or a value no option matches. An option whose value is `null` therefore never shows as
2624
+ * selected.
2625
+ */
2626
+ declare function choiceIndex<T>(options: readonly {
2627
+ readonly value: T;
2628
+ }[], value: T | null, compare: BfCompareWith<T>): number;
2629
+ /**
2630
+ * The native `value` of an option (`<option>`, radio): `String(value)` for a string, number or
2631
+ * boolean, so native reads, form posts and browser tests see the real value; the option index for
2632
+ * anything else (objects). The controls map a choice back by position, never by this key, so
2633
+ * duplicate keys still resolve to the right option.
2634
+ */
2635
+ declare function choiceKey(value: unknown, index: number): string;
2636
+ /**
2637
+ * One choice of `bf-select`; `value` is any type (a string by default).
2638
+ *
2639
+ * @summary One choice of a select with its value, its label and a disabled flag
2640
+ * @since 0.1.0
2641
+ */
2642
+ interface BfSelectOption<T = string> {
2643
+ /** The value the select's `value` takes when this option is chosen. */
2644
+ value: T;
2645
+ /** The text of the native `option`. */
2646
+ label: string;
2647
+ /** Whether this option cannot be chosen. */
2648
+ disabled?: boolean;
2649
+ }
2650
+ /**
2651
+ * Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
2652
+ * checkbox contract so `[formField]` binds `checked`.
2653
+ *
2654
+ * @example
2655
+ * <bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
2656
+ * <bf-checkbox label="Notify me" [formField]="prefs.notify" />
2657
+ * @summary Rounded checkbox with an optional label that binds to Signal Forms
2658
+ * @category Forms
2659
+ * @since 0.1.0
2660
+ * @slot default Rich label content next to the box, before the `label` text.
2661
+ */
2662
+ export declare class BfCheckboxComponent implements FormCheckboxControl {
2663
+ /** Whether the box is checked; two-way bindable and bound by `[formField]`. */
2664
+ readonly checked: import("@angular/core").ModelSignal<boolean>;
2665
+ /** Label text; the default slot works too. */
2666
+ readonly label: import("@angular/core").InputSignal<string>;
2667
+ /** Accessible name when there is no visible `label`, e.g. a row-selection box. */
2668
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
2669
+ /** Id(s) of the element(s) that name the control when no visible label is rendered (e.g. a
2670
+ * table column header). */
2671
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
2672
+ /** Height step of the row: sm 32, md 40, lg 48 px (44 px under a coarse pointer for sm and md);
2673
+ * unset follows `provideBfDefaults()` (`controlSize`, `md` by default). */
2674
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2675
+ /** Shows the mixed state (e.g. "select all" with some rows selected); two-way bindable. A user
2676
+ * change clears it, like the native checkbox, so the box and `aria-checked` agree. */
2677
+ readonly indeterminate: import("@angular/core").ModelSignal<boolean>;
2678
+ /** Disables the control. */
2679
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2680
+ /** Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. */
2681
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2682
+ /** Marks the control required. */
2683
+ readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2684
+ /** Field validity from Signal Forms. */
2685
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2686
+ /** Whether the user has left the field. */
2687
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2688
+ /** Validation errors from Signal Forms. */
2689
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
2690
+ /** Explicit error text; takes precedence over Signal Forms errors. */
2691
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
2692
+ /** Native name attribute. */
2693
+ readonly name: import("@angular/core").InputSignal<string>;
2694
+ /** Control id; generated when not set. */
2695
+ readonly id: import("@angular/core").InputSignal<string>;
2696
+ /** Emitted with the new checked state after a user change. */
2697
+ readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
2698
+ /** Emitted on blur so Signal Forms marks the field touched. */
2091
2699
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
2092
2700
  private readonly el;
2093
2701
  private readonly labels;
@@ -2939,6 +3547,8 @@ export declare class BfSegmentedComponent<T = string> implements FormValueContro
2939
3547
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
2940
3548
  /** The rendered radio buttons, in option order; used to move focus after keyboard selection. */
2941
3549
  readonly radios: import("@angular/core").Signal<readonly ElementRef<HTMLButtonElement>[]>;
3550
+ private readonly track;
3551
+ private readonly indicator;
2942
3552
  /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
2943
3553
  readonly selectedIndex: import("@angular/core").Signal<number>;
2944
3554
  /** Index of the option that owns the roving tab stop: the selected enabled option, or the first
@@ -2963,6 +3573,12 @@ export declare class BfSegmentedComponent<T = string> implements FormValueContro
2963
3573
  private readonly mode;
2964
3574
  /** The classes of the scroller around the track. */
2965
3575
  protected readonly scrollerClasses: import("@angular/core").Signal<string>;
3576
+ /** Whether the sliding pill has been measured and placed, so it, not the item, paints the
3577
+ * selection. */
3578
+ private readonly placed;
3579
+ /** The classes of the sliding pill: hidden until placed and while nothing is selected, the
3580
+ * disabled outline on a disabled selected segment. */
3581
+ protected readonly indicatorClasses: import("@angular/core").Signal<string>;
2966
3582
  readonly trackClasses: import("@angular/core").Signal<string>;
2967
3583
  /**
2968
3584
  * The classes of the segment at `index`.
@@ -3378,46 +3994,383 @@ export declare class BfCardComponent implements BfActionHost {
3378
3994
  "clicked": "clicked";
3379
3995
  }, never, ["[bfSlot=header]", "*", "[bfSlot=footer]"], true, never>;
3380
3996
  }
3997
+ /** What an image area shows: nothing yet, the skeleton, the picture, or the failure message. */
3998
+ type ImageLoadPhase = 'empty' | 'loading' | 'loaded' | 'failed';
3999
+ /**
4000
+ * The loading state of one picture in a fixed image area, shared by `bf-media-card` and
4001
+ * `bf-image-tile` so the two never drift: a skeleton while there is no `src` yet (with `loading`)
4002
+ * or while it loads, the picture fading in on top (`bfFadeIn`), the skeleton leaving the DOM once
4003
+ * it has faded out, and the failure message on `failed` or a load error. A new `src` starts over.
4004
+ */
4005
+ interface ImageLoadState {
4006
+ readonly phase: Signal<ImageLoadPhase>;
4007
+ /** The `src` to render as an `<img>`, keyed by URL in a `@for` so a new one fades in afresh. */
4008
+ readonly sources: Signal<string[]>;
4009
+ /** The skeleton layer is in the DOM: while loading, and while it fades out over the picture. */
4010
+ readonly skeletonShown: Signal<boolean>;
4011
+ /** The `<img>`'s load event. */
4012
+ loaded(): void;
4013
+ /** The `<img>`'s error event. */
4014
+ errored(): void;
4015
+ /** The skeleton layer's `transitionend`: once it has faded out over the picture it leaves. */
4016
+ skeletonFaded(event: TransitionEvent): void;
4017
+ }
4018
+ /** Creates the loading state of an image area from its `src`, `loading` and `failed` inputs. */
4019
+ declare function imageLoadState(src: Signal<string>, loading: Signal<boolean>, failed: Signal<boolean>): ImageLoadState;
3381
4020
  /**
3382
- * The track height of a `bf-progress`: sm or md.
3383
- *
3384
- * @summary The two track heights of a progress bar, thin and regular
3385
- * @since 0.1.0
3386
- */
3387
- type BfProgressSize = 'sm' | 'md';
3388
- /**
3389
- * The fill colour of a `bf-progress`: primary, success, warning or danger.
4021
+ * The aspect ratio of a `bf-media-card`'s image area: 16:9 (default), 4:3, square or 3:4.
3390
4022
  *
3391
- * @summary The four fill colours of a progress bar, from primary to danger
3392
- * @since 0.1.0
4023
+ * @summary The four aspect ratios of a media card's image area, wide to portrait
4024
+ * @since 0.5.0
3393
4025
  */
3394
- type BfProgressTone = 'primary' | 'success' | 'warning' | 'danger';
4026
+ type BfMediaCardAspect = '16/9' | '4/3' | '1/1' | '3/4';
3395
4027
  /**
3396
- * Progress bar for completion and quotas, determinate by default, or `indeterminate` while the
3397
- * amount of work is unknown.
4028
+ * Card for a picture and its story: a game pitch, a generated image, a product. The top is an
4029
+ * image area at a fixed aspect ratio that shows a shimmering skeleton while the picture loads
4030
+ * (also while it does not exist yet: set `loading`), fades the picture in when it arrives, and
4031
+ * shows a message with a Retry slot when it fails. Below it sit a body and a footer. Make the card
4032
+ * clickable with a `bfAction` link or button in its title, as on `bf-card`, and selectable with
4033
+ * `selectable` and `[(selected)]`.
3398
4034
  *
3399
4035
  * @example
3400
- * <bf-progress [value]="68" label="Atlas rebrand" valueText="12 of 18 tasks" />
3401
- * <bf-progress indeterminate label="Uploading" />
3402
- * @summary Progress bar in two sizes and four tones for completion, quotas and unknown waits
4036
+ * <bf-media-card [src]="pitch.cover" alt="Neon city skyline" loadingLabel="Drawing…" selectable [(selected)]="picked">
4037
+ * <h3 class="text-heading"><a bfAction [routerLink]="['/pitches', pitch.id]">Starfall</a></h3>
4038
+ * <p class="text-body text-text-secondary">A roguelike in a falling city.</p>
4039
+ * <bf-button bfSlot="failed-action" size="sm" variant="tonal" (clicked)="retry()">Retry</bf-button>
4040
+ * </bf-media-card>
4041
+ * @summary Card with an image that loads, fades in or fails, plus a body and actions
3403
4042
  * @category Data display
3404
- * @since 0.1.0
4043
+ * @since 0.5.0
4044
+ * @slot default The card body: a title (with an optional bfAction link or button) and text.
4045
+ * @slot footer Content below the body, such as chips and buttons; it sits on the card's bottom edge.
4046
+ * @slot failed-action An action shown under the failure message, such as a Retry button.
3405
4047
  */
3406
- export declare class BfProgressComponent {
3407
- /** Completion in percent; clamped to 0–100. Without it (and without `indeterminate`) the bar
3408
- * shows 0 %. Ignored while `indeterminate`. */
3409
- readonly value: import("@angular/core").InputSignal<number | undefined>;
3410
- /** The amount of work is unknown: the native bar carries no value (assistive tech announces it
3411
- * as busy) and a stripe runs across the track, a static 40 % bar under reduced motion. */
3412
- readonly indeterminate: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
3413
- /** The value in words for assistive tech (`aria-valuetext`), such as "12 of 18 tasks"; the
3414
- * percentage is announced while it is empty. */
3415
- readonly valueText: import("@angular/core").InputSignal<string>;
3416
- /** Accessible name of the bar ("Atlas rebrand"). */
4048
+ export declare class BfMediaCardComponent implements BfActionHost {
4049
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
4050
+ /** URL of the image; empty shows the skeleton while `loading`, else an empty area. A new URL
4051
+ * starts over: skeleton, fade-in, and a failed load is forgotten. */
4052
+ readonly src: import("@angular/core").InputSignal<string>;
4053
+ /** Alternative text of the image; empty marks it decorative. */
4054
+ readonly alt: import("@angular/core").InputSignal<string>;
4055
+ /** Aspect ratio of the image area: `16/9` (default), `4/3`, `1/1` or `3/4`. */
4056
+ readonly aspect: import("@angular/core").InputSignal<BfMediaCardAspect>;
4057
+ /** Shows the skeleton while there is no `src` yet (a picture that is still being made). */
4058
+ readonly loading: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4059
+ /** Shows the failure message (the app could not make the picture); the app clears it. A load
4060
+ * error of `src` shows it too, until `src` changes. */
4061
+ readonly failed: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4062
+ /** Caption shown over the skeleton while loading ("Drawing…"); empty shows none and tells
4063
+ * screen readers `provideBfLabels()`'s `loading` ("Loading"). */
4064
+ readonly loadingLabel: import("@angular/core").InputSignal<string>;
4065
+ /** Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`. */
4066
+ readonly failedLabel: import("@angular/core").InputSignal<string>;
4067
+ /** Adds a select button (a toggle with `aria-pressed`): the whole card toggles `selected`,
4068
+ * or, with a `bfAction` in the card, a round check button in the image's corner does. */
4069
+ readonly selectable: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4070
+ /** Whether the card is selected (a ring around it and a filled check); two-way bindable. Drawn
4071
+ * only on a `selectable` card. */
4072
+ readonly selected: import("@angular/core").ModelSignal<boolean>;
4073
+ /** The card's name in its select button ("Select Starfall"); empty names it by the body's text. */
3417
4074
  readonly label: import("@angular/core").InputSignal<string>;
3418
- /** Bar height: sm 8 px (default), md 10 px. */
3419
- readonly size: import("@angular/core").InputSignal<BfProgressSize>;
3420
- /** Fill color by meaning. */
4075
+ /** The user selected or cleared the card; emits the new `selected`. */
4076
+ readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
4077
+ /** The image of `src` has loaded. */
4078
+ readonly loaded: import("@angular/core").OutputEmitterRef<void>;
4079
+ /** The image of `src` failed to load (the card shows the failure message). */
4080
+ readonly errored: import("@angular/core").OutputEmitterRef<void>;
4081
+ private readonly actions;
4082
+ private readonly actionFocusVisible;
4083
+ private readonly selectFocusVisible;
4084
+ protected readonly bodyId: string;
4085
+ protected readonly selectTextId: string;
4086
+ protected readonly labelId: string;
4087
+ protected readonly skeletonLayerClasses = "absolute inset-0 transition-opacity duration-slow ease-md motion-reduce:transition-none";
4088
+ /** The image area's loading state, shared with bf-image-tile. */
4089
+ private readonly image;
4090
+ protected readonly state: import("@angular/core").Signal<ImageLoadPhase>;
4091
+ /** The image, keyed by URL so a new `src` gets a fresh element (and a fresh fade). */
4092
+ protected readonly imageSources: import("@angular/core").Signal<string[]>;
4093
+ protected readonly skeletonShown: import("@angular/core").Signal<boolean>;
4094
+ /** The body names the select button (no `label`): only then does it carry its id, so a plain
4095
+ * card renders no counter-made id. */
4096
+ protected readonly bodyNamesSelect: import("@angular/core").Signal<boolean>;
4097
+ protected readonly failedText: import("@angular/core").Signal<string>;
4098
+ /** The select button stretches over the card unless the card has its own bfAction. */
4099
+ private readonly selectMode;
4100
+ private readonly cue;
4101
+ protected readonly classes: import("@angular/core").Signal<string>;
4102
+ protected readonly mediaClasses: import("@angular/core").Signal<string>;
4103
+ protected readonly selectClasses: import("@angular/core").Signal<string>;
4104
+ protected readonly discClasses: import("@angular/core").Signal<string>;
4105
+ /** @internal Called by a `bfAction` in the card. */
4106
+ registerAction(): () => void;
4107
+ /** @internal Called by the card's `bfAction` on focus and blur. */
4108
+ setActionFocusVisible(visible: boolean): void;
4109
+ /** @internal The image's load event. */
4110
+ imageLoaded(): void;
4111
+ /** @internal The image's error event. */
4112
+ imageFailed(): void;
4113
+ /** @internal The skeleton layer's fade-out ended: it leaves the DOM (its shimmer stops). */
4114
+ skeletonFaded(event: TransitionEvent): void;
4115
+ /** @internal The select button was clicked. */
4116
+ toggle(): void;
4117
+ /** @internal The select button took focus. */
4118
+ selectFocused(event: FocusEvent): void;
4119
+ /** @internal The select button lost focus. */
4120
+ selectBlurred(): void;
4121
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfMediaCardComponent, never>;
4122
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfMediaCardComponent, "bf-media-card", never, {
4123
+ "src": {
4124
+ "alias": "src";
4125
+ "required": false;
4126
+ "isSignal": true;
4127
+ };
4128
+ "alt": {
4129
+ "alias": "alt";
4130
+ "required": false;
4131
+ "isSignal": true;
4132
+ };
4133
+ "aspect": {
4134
+ "alias": "aspect";
4135
+ "required": false;
4136
+ "isSignal": true;
4137
+ };
4138
+ "loading": {
4139
+ "alias": "loading";
4140
+ "required": false;
4141
+ "isSignal": true;
4142
+ };
4143
+ "failed": {
4144
+ "alias": "failed";
4145
+ "required": false;
4146
+ "isSignal": true;
4147
+ };
4148
+ "loadingLabel": {
4149
+ "alias": "loadingLabel";
4150
+ "required": false;
4151
+ "isSignal": true;
4152
+ };
4153
+ "failedLabel": {
4154
+ "alias": "failedLabel";
4155
+ "required": false;
4156
+ "isSignal": true;
4157
+ };
4158
+ "selectable": {
4159
+ "alias": "selectable";
4160
+ "required": false;
4161
+ "isSignal": true;
4162
+ };
4163
+ "selected": {
4164
+ "alias": "selected";
4165
+ "required": false;
4166
+ "isSignal": true;
4167
+ };
4168
+ "label": {
4169
+ "alias": "label";
4170
+ "required": false;
4171
+ "isSignal": true;
4172
+ };
4173
+ }, {
4174
+ "selected": "selectedChange";
4175
+ "changed": "changed";
4176
+ "loaded": "loaded";
4177
+ "errored": "errored";
4178
+ }, never, ["[bfSlot=failed-action]", "*", "[bfSlot=footer]"], true, never>;
4179
+ }
4180
+ /**
4181
+ * The aspect ratio of a `bf-image-tile`'s picture: square (default), 4:3, 16:9 or 3:4.
4182
+ *
4183
+ * @summary The four aspect ratios of an image tile's picture, square by default
4184
+ * @since 0.5.0
4185
+ */
4186
+ type BfImageTileAspect = '1/1' | '4/3' | '16/9' | '3/4';
4187
+ /**
4188
+ * Where a `bf-image-tile`'s title and meta line sit: `below` the picture (default), or `overlay`,
4189
+ * on a soft gradient over the bottom of the picture.
4190
+ *
4191
+ * @summary Where an image tile's caption sits, below the picture or over it
4192
+ * @since 0.5.0
4193
+ */
4194
+ type BfImageTileCaptionPlacement = 'below' | 'overlay';
4195
+ /**
4196
+ * Small card for a grid of thumbnails: mostly picture, at a fixed aspect ratio (square by
4197
+ * default), with a title and an optional meta line below it or, with `captionPlacement="overlay"`,
4198
+ * on a soft gradient over its bottom. Without a picture yet it shows a placeholder (an icon and a
4199
+ * text); while one loads a shimmering skeleton; the picture fades in when it arrives; a failed load
4200
+ * shows a short message. Make the whole tile one link or button with a `bfAction` in its title, as
4201
+ * on `bf-card`: it then lifts on hover, zooms the picture slightly and shows the focus ring.
4202
+ * `selected` draws a ring. The title clamps to two lines and always takes two, so tiles in a grid
4203
+ * row are as tall whatever their titles.
4204
+ *
4205
+ * @example
4206
+ * <bf-image-tile [src]="model.thumbnail" [selected]="model.id === current()">
4207
+ * <h3><a bfAction [routerLink]="['/models', model.id]">{{ model.name }}</a></h3>
4208
+ * <span bfSlot="meta">{{ model.updated }}</span>
4209
+ * <bf-badge bfSlot="badge" variant="info">3 versions</bf-badge>
4210
+ * </bf-image-tile>
4211
+ * @summary Small card for thumbnail grids: a picture and title that act as one target
4212
+ * @category Data display
4213
+ * @since 0.5.0
4214
+ * @slot default The title: a heading holding the tile's `bfAction` link or button.
4215
+ * @slot meta A short line under the title, such as a date or a count.
4216
+ * @slot badge A `bf-badge` in the picture's top start corner.
4217
+ * @slot placeholder-icon The placeholder's icon, shown with its text while there is no picture.
4218
+ */
4219
+ export declare class BfImageTileComponent implements BfActionHost {
4220
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
4221
+ /** URL of the picture; empty shows the placeholder, or the skeleton while `loading`. A new URL
4222
+ * starts over: skeleton, fade-in, and a failed load is forgotten. */
4223
+ readonly src: import("@angular/core").InputSignal<string>;
4224
+ /** Alternative text of the picture; empty marks it decorative (the title names the tile). */
4225
+ readonly alt: import("@angular/core").InputSignal<string>;
4226
+ /** Aspect ratio of the picture: `1/1` (default), `4/3`, `16/9` or `3/4`. */
4227
+ readonly aspect: import("@angular/core").InputSignal<BfImageTileAspect>;
4228
+ /** Where the title and the meta line sit: `below` the picture (default) or `overlay`, on a
4229
+ * gradient over its bottom. */
4230
+ readonly captionPlacement: import("@angular/core").InputSignal<BfImageTileCaptionPlacement>;
4231
+ /** Shows the skeleton while there is no `src` yet (a picture that is still being made). */
4232
+ readonly loading: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4233
+ /** Shows the failure message (the app could not make the picture); the app clears it. A load
4234
+ * error of `src` shows it too, until `src` changes. */
4235
+ readonly failed: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4236
+ /** Marks the tile as the selected one: a ring around it, and "Selected" read before its title. */
4237
+ readonly selected: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4238
+ /** Placeholder text while there is no picture; empty uses `provideBfLabels()`'s
4239
+ * `imageTileEmpty` ("No image yet"). */
4240
+ readonly placeholderLabel: import("@angular/core").InputSignal<string>;
4241
+ /** Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`. */
4242
+ readonly failedLabel: import("@angular/core").InputSignal<string>;
4243
+ /** The picture of `src` has loaded. */
4244
+ readonly loaded: import("@angular/core").OutputEmitterRef<void>;
4245
+ /** The picture of `src` failed to load (the tile shows the failure message). */
4246
+ readonly errored: import("@angular/core").OutputEmitterRef<void>;
4247
+ private readonly actions;
4248
+ private readonly actionFocusVisible;
4249
+ /** The picture's loading state, shared with bf-media-card. */
4250
+ private readonly image;
4251
+ protected readonly state: import("@angular/core").Signal<ImageLoadPhase>;
4252
+ /** The picture, keyed by URL so a new `src` gets a fresh element (and a fresh fade). */
4253
+ protected readonly imageSources: import("@angular/core").Signal<string[]>;
4254
+ protected readonly skeletonShown: import("@angular/core").Signal<boolean>;
4255
+ protected readonly skeletonLayerClasses = "absolute inset-0 transition-opacity duration-slow ease-md motion-reduce:transition-none";
4256
+ protected readonly titleClasses: string;
4257
+ protected readonly metaClasses = "text-caption text-text-muted empty:hidden";
4258
+ protected readonly hasAction: import("@angular/core").Signal<boolean>;
4259
+ private selectedIdValue;
4260
+ /** The id of the selected state text, while `selected` is set. */
4261
+ protected readonly selectedId: import("@angular/core").Signal<string | null>;
4262
+ /** @internal The selected state starts the name of the tile's `bfAction`. */
4263
+ readonly actionLabelPrefix: import("@angular/core").Signal<string | null>;
4264
+ protected readonly placeholderText: import("@angular/core").Signal<string>;
4265
+ protected readonly failedText: import("@angular/core").Signal<string>;
4266
+ private readonly cue;
4267
+ protected readonly classes: import("@angular/core").Signal<string>;
4268
+ protected readonly mediaClasses: import("@angular/core").Signal<string>;
4269
+ protected readonly zoomClasses: import("@angular/core").Signal<"absolute inset-0" | "absolute inset-0 motion-safe:transition-[scale] motion-safe:duration-slow motion-safe:ease-md motion-safe:group-hover/tile:scale-105">;
4270
+ protected readonly statusClasses: import("@angular/core").Signal<string>;
4271
+ protected readonly captionClasses: import("@angular/core").Signal<string>;
4272
+ /** @internal Called by a `bfAction` in the tile. */
4273
+ registerAction(): () => void;
4274
+ /** @internal Called by the tile's `bfAction` on focus and blur. */
4275
+ setActionFocusVisible(visible: boolean): void;
4276
+ /** @internal The picture's load event. */
4277
+ imageLoaded(): void;
4278
+ /** @internal The picture's error event. */
4279
+ imageFailed(): void;
4280
+ /** @internal The skeleton layer's fade-out ended: it leaves the DOM (its shimmer stops). */
4281
+ skeletonFaded(event: TransitionEvent): void;
4282
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfImageTileComponent, never>;
4283
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfImageTileComponent, "bf-image-tile", never, {
4284
+ "src": {
4285
+ "alias": "src";
4286
+ "required": false;
4287
+ "isSignal": true;
4288
+ };
4289
+ "alt": {
4290
+ "alias": "alt";
4291
+ "required": false;
4292
+ "isSignal": true;
4293
+ };
4294
+ "aspect": {
4295
+ "alias": "aspect";
4296
+ "required": false;
4297
+ "isSignal": true;
4298
+ };
4299
+ "captionPlacement": {
4300
+ "alias": "captionPlacement";
4301
+ "required": false;
4302
+ "isSignal": true;
4303
+ };
4304
+ "loading": {
4305
+ "alias": "loading";
4306
+ "required": false;
4307
+ "isSignal": true;
4308
+ };
4309
+ "failed": {
4310
+ "alias": "failed";
4311
+ "required": false;
4312
+ "isSignal": true;
4313
+ };
4314
+ "selected": {
4315
+ "alias": "selected";
4316
+ "required": false;
4317
+ "isSignal": true;
4318
+ };
4319
+ "placeholderLabel": {
4320
+ "alias": "placeholderLabel";
4321
+ "required": false;
4322
+ "isSignal": true;
4323
+ };
4324
+ "failedLabel": {
4325
+ "alias": "failedLabel";
4326
+ "required": false;
4327
+ "isSignal": true;
4328
+ };
4329
+ }, {
4330
+ "loaded": "loaded";
4331
+ "errored": "errored";
4332
+ }, never, ["[bfSlot=placeholder-icon]", "[bfSlot=badge]", "*", "[bfSlot=meta]"], true, never>;
4333
+ }
4334
+ /**
4335
+ * The track height of a `bf-progress`: sm or md.
4336
+ *
4337
+ * @summary The two track heights of a progress bar, thin and regular
4338
+ * @since 0.1.0
4339
+ */
4340
+ type BfProgressSize = 'sm' | 'md';
4341
+ /**
4342
+ * The fill colour of a `bf-progress`: primary, success, warning or danger.
4343
+ *
4344
+ * @summary The four fill colours of a progress bar, from primary to danger
4345
+ * @since 0.1.0
4346
+ */
4347
+ type BfProgressTone = 'primary' | 'success' | 'warning' | 'danger';
4348
+ /**
4349
+ * Progress bar for completion and quotas, determinate by default, or `indeterminate` while the
4350
+ * amount of work is unknown.
4351
+ *
4352
+ * @example
4353
+ * <bf-progress [value]="68" label="Atlas rebrand" valueText="12 of 18 tasks" />
4354
+ * <bf-progress indeterminate label="Uploading" />
4355
+ * @summary Progress bar in two sizes and four tones for completion, quotas and unknown waits
4356
+ * @category Data display
4357
+ * @since 0.1.0
4358
+ */
4359
+ export declare class BfProgressComponent {
4360
+ /** Completion in percent; clamped to 0–100. Without it (and without `indeterminate`) the bar
4361
+ * shows 0 %. Ignored while `indeterminate`. */
4362
+ readonly value: import("@angular/core").InputSignal<number | undefined>;
4363
+ /** The amount of work is unknown: the native bar carries no value (assistive tech announces it
4364
+ * as busy) and a stripe runs across the track, a static 40 % bar under reduced motion. */
4365
+ readonly indeterminate: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4366
+ /** The value in words for assistive tech (`aria-valuetext`), such as "12 of 18 tasks"; the
4367
+ * percentage is announced while it is empty. */
4368
+ readonly valueText: import("@angular/core").InputSignal<string>;
4369
+ /** Accessible name of the bar ("Atlas rebrand"). */
4370
+ readonly label: import("@angular/core").InputSignal<string>;
4371
+ /** Bar height: sm 8 px (default), md 10 px. */
4372
+ readonly size: import("@angular/core").InputSignal<BfProgressSize>;
4373
+ /** Fill color by meaning. */
3421
4374
  readonly tone: import("@angular/core").InputSignal<BfProgressTone>;
3422
4375
  readonly percent: import("@angular/core").Signal<number>;
3423
4376
  readonly classes: import("@angular/core").Signal<string>;
@@ -4056,6 +5009,61 @@ export declare class BfActionDirective {
4056
5009
  static ɵfac: i0.ɵɵFactoryDeclaration<BfActionDirective, never>;
4057
5010
  static ɵdir: i0.ɵɵDirectiveDeclaration<BfActionDirective, "a[bfAction], button[bfAction]", never, {}, {}, never, never, true, never>;
4058
5011
  }
5012
+ /**
5013
+ * Fades an image in when it has loaded instead of letting it pop in line by line: put
5014
+ * `bfFadeIn` on the `<img>`. The image stays transparent until its `load` (or `error`) event,
5015
+ * then fades in over 260 ms (`duration-slow`), at once under `prefers-reduced-motion: reduce`. An
5016
+ * image that is already loaded when it renders (from the cache) shows straight away. Reserve its
5017
+ * space (`width`/`height` attributes or an aspect ratio) so the layout does not move when it
5018
+ * arrives; for a placeholder while it loads, put a `bf-skeleton` behind it.
5019
+ *
5020
+ * @example
5021
+ * <img bfFadeIn [src]="cover.url" alt="Game cover" width="640" height="360" />
5022
+ * @summary Fades an image in once it has loaded instead of popping in
5023
+ * @since 0.5.0
5024
+ */
5025
+ export declare class BfFadeInDirective {
5026
+ private readonly image;
5027
+ /** True once the image has loaded or failed: it is opaque from then on. */
5028
+ protected readonly shown: import("@angular/core").WritableSignal<boolean>;
5029
+ constructor();
5030
+ /**
5031
+ * Shows the image: its load or error event.
5032
+ *
5033
+ * @internal
5034
+ */
5035
+ show(): void;
5036
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfFadeInDirective, never>;
5037
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfFadeInDirective, "img[bfFadeIn]", never, {}, {}, never, never, true, never>;
5038
+ }
5039
+ /**
5040
+ * Glides the items of an app's list or grid to their new place when items are added, removed or
5041
+ * reordered, instead of letting them jump (the FLIP technique). Put `bfMotionList` on the element
5042
+ * whose direct children are the items, usually the one around an `@for`. Each item remembers
5043
+ * where it was; after the DOM changes, every item that moved slides from its old place to its new
5044
+ * one over 200 ms (`--bf-duration-layout`, on the settle curve). Pair it with Angular's
5045
+ * `animate.enter="motion-safe:animate-item-enter"` and
5046
+ * `animate.leave="motion-safe:animate-item-leave"` on the items, so new items fade and rise in and
5047
+ * removed ones fade out before the rest close the gap. Nothing moves under
5048
+ * `prefers-reduced-motion: reduce`, and nothing glides when the container is resized. It moves
5049
+ * items within one container: an item moved to another list leaves one and enters the other.
5050
+ *
5051
+ * @example
5052
+ * <ul bfMotionList class="grid grid-cols-3 gap-4">
5053
+ * @for (card of cards(); track card.id) {
5054
+ * <li animate.enter="motion-safe:animate-item-enter" animate.leave="motion-safe:animate-item-leave">
5055
+ * <app-card [card]="card" />
5056
+ * </li>
5057
+ * }
5058
+ * </ul>
5059
+ * @summary Glides the items of a list or grid to their new place when it changes
5060
+ * @since 0.5.0
5061
+ */
5062
+ export declare class BfMotionListDirective {
5063
+ constructor();
5064
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfMotionListDirective, never>;
5065
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfMotionListDirective, "[bfMotionList]", never, {}, {}, never, never, true, never>;
5066
+ }
4059
5067
  /** What a `bf-tab-panel` reads from its `bf-tab-group`; a token so the two files never import each
4060
5068
  * other. Internal: not exported from the package. */
4061
5069
  interface BfTabGroupHost {
@@ -4108,6 +5116,10 @@ export declare class BfTabPanelComponent {
4108
5116
  readonly isSelected: import("@angular/core").Signal<boolean>;
4109
5117
  /** Latches once the panel has been shown. */
4110
5118
  private readonly visited;
5119
+ /** True while this panel is shown because the selection moved to it (not the panel shown on
5120
+ * the first render): it fades in. */
5121
+ protected readonly switchedTo: import("@angular/core").WritableSignal<boolean>;
5122
+ protected readonly motionClasses: import("@angular/core").Signal<"" | "motion-safe:animate-panel-in">;
4111
5123
  /** Whether the `ng-template` content exists: while selected, or once visited with `keepAlive`.
4112
5124
  * `visited` is read first on every run, so the latch sees each selection. */
4113
5125
  protected readonly rendered: import("@angular/core").Signal<boolean>;
@@ -4173,6 +5185,8 @@ export declare class BfTabGroupComponent implements BfTabGroupHost {
4173
5185
  /** The `bf-tab-panel` children, in document order. */
4174
5186
  readonly panels: import("@angular/core").Signal<readonly BfTabPanelComponent[]>;
4175
5187
  private readonly tabs;
5188
+ private readonly list;
5189
+ private readonly indicator;
4176
5190
  private readonly labels;
4177
5191
  private readonly defaults;
4178
5192
  /** The size in effect: `size`, else the app's `controlSize` default. */
@@ -4187,8 +5201,14 @@ export declare class BfTabGroupComponent implements BfTabGroupHost {
4187
5201
  /** The panel whose tab owns the roving tab stop: the shown one, else (it is disabled) the first
4188
5202
  * enabled one. */
4189
5203
  protected readonly tabStop: import("@angular/core").Signal<BfTabPanelComponent | undefined>;
5204
+ /** Whether the sliding underline has been measured and placed, so it, not the tab, draws the
5205
+ * selection. */
5206
+ private readonly placed;
5207
+ /** The classes of the sliding underline: hidden until placed, `text-disabled` on a disabled
5208
+ * selected tab. */
5209
+ protected readonly indicatorClasses: import("@angular/core").Signal<string>;
4190
5210
  protected readonly scrollerClasses = "-m-1 overflow-x-auto p-1";
4191
- protected readonly listClasses = "flex w-max min-w-full border-b border-border";
5211
+ protected readonly listClasses = "relative flex w-max min-w-full border-b border-border";
4192
5212
  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";
4193
5213
  /**
4194
5214
  * The classes of a panel's tab.
@@ -4302,7 +5322,7 @@ export declare class BfPageHeaderComponent {
4302
5322
  * @summary Dense row of search, filters, view switch, result count and actions
4303
5323
  * @category Actions
4304
5324
  * @since 0.3.1
4305
- * @slot default The search field and the filters (bf-input, bf-select, bf-chip-group), in order.
5325
+ * @slot default The search field and the filters (bf-input, bf-chip-group, bf-select from /overlay), in order.
4306
5326
  * @slot view A view switch after the filters, such as a bf-segmented of list and grid.
4307
5327
  * @slot actions The trailing actions, kept together at the inline end of the row, beside the status.
4308
5328
  */
@@ -4508,6 +5528,11 @@ export declare class BfTableComponent<T = unknown> {
4508
5528
  protected readonly labels: import("@bitforgehq/ui").BfLabels;
4509
5529
  private readonly defaults;
4510
5530
  private readonly table;
5531
+ /** The Sort by select from `provideBfSelect()` (`BF_TABLE_SORT_SELECT`); null renders the
5532
+ * native one. */
5533
+ protected readonly sortSelectType: import("@angular/core").Type<unknown> | null;
5534
+ /** Where the provided Sort by select goes; set only while the stack bar shows it. */
5535
+ private readonly sortSelectAnchor;
4511
5536
  private readonly cells;
4512
5537
  private readonly id;
4513
5538
  /** The caption's id, which names the table while it can stack. */
@@ -4548,6 +5573,7 @@ export declare class BfTableComponent<T = unknown> {
4548
5573
  protected readonly selectBodyClasses: import("@angular/core").Signal<string>;
4549
5574
  /** A body cell's height step, with the coarse-pointer height unless the table stacks there. */
4550
5575
  private readonly bodyHeight;
5576
+ constructor();
4551
5577
  /**
4552
5578
  * The classes of a column's header cell.
4553
5579
  * @internal
@@ -4669,6 +5695,19 @@ export declare class BfTableComponent<T = unknown> {
4669
5695
  "selection": "selectionChange";
4670
5696
  }, ["cells"], ["[bfSlot=empty]"], true, never>;
4671
5697
  }
5698
+ /**
5699
+ * The component a stacked `bf-table` renders as its Sort by select; `null` (the default) keeps the
5700
+ * built-in native `<select>`. `provideBfSelect()` from `@bitforgehq/ui/overlay` sets it to the
5701
+ * styled `bf-select`, so the core entry never imports the overlay entry. A component given here
5702
+ * must take the inputs `label` (string), `options` (`BfSelectOption<string>[]`) and `value`
5703
+ * (string) and emit the picked value from the output `changed`. The table adds its layout classes
5704
+ * to the component's host with `classList`, so the component must not overwrite its host
5705
+ * `className` or `class` attribute directly.
5706
+ *
5707
+ * @summary The component a stacked table renders as its Sort by select
5708
+ * @since 0.5.0
5709
+ */
5710
+ export declare const BF_TABLE_SORT_SELECT: InjectionToken<Type<unknown> | null>;
4672
5711
  /**
4673
5712
  * The layout of a `bf-description-list`: `rows` (label beside value, divided) or `grid` (tiles).
4674
5713
  *
@@ -5132,7 +6171,11 @@ export declare class BfSidebarComponent {
5132
6171
  * screen readers and becomes a tooltip. A `bf-sidebar-toggle` inside flips it; bind
5133
6172
  * `[(collapsed)]` to keep it in your state. */
5134
6173
  readonly collapsed: import("@angular/core").ModelSignal<boolean>;
6174
+ private readonly defaults;
5135
6175
  private readonly labels;
6176
+ /** True after the first render: from then on collapsing and expanding animate.
6177
+ * @internal */
6178
+ readonly animated: import("@angular/core").WritableSignal<boolean>;
5136
6179
  /** The `nav` element's id, which `bf-sidebar-toggle` points `aria-controls` at.
5137
6180
  * @internal */
5138
6181
  readonly navId: string;
@@ -5140,7 +6183,7 @@ export declare class BfSidebarComponent {
5140
6183
  protected readonly name: import("@angular/core").Signal<string>;
5141
6184
  private readonly width;
5142
6185
  readonly hostClasses: import("@angular/core").Signal<string>;
5143
- protected readonly regionClasses: import("@angular/core").Signal<string>;
6186
+ constructor();
5144
6187
  static ɵfac: i0.ɵɵFactoryDeclaration<BfSidebarComponent, never>;
5145
6188
  static ɵcmp: i0.ɵɵComponentDeclaration<BfSidebarComponent, "bf-sidebar", never, {
5146
6189
  "label": {
@@ -5210,7 +6253,6 @@ export declare class BfNavGroupComponent {
5210
6253
  /** The heading's id, referenced by the group's `aria-labelledby`. */
5211
6254
  protected readonly headingId: string;
5212
6255
  private readonly form;
5213
- readonly hostClasses: import("@angular/core").Signal<string>;
5214
6256
  protected readonly headingClasses: import("@angular/core").Signal<string>;
5215
6257
  static ɵfac: i0.ɵɵFactoryDeclaration<BfNavGroupComponent, never>;
5216
6258
  static ɵcmp: i0.ɵɵComponentDeclaration<BfNavGroupComponent, "bf-nav-group", never, {
@@ -5257,11 +6299,17 @@ export declare class BfNavItemComponent {
5257
6299
  /** True while the enclosing sidebar is collapsed to an icon rail. */
5258
6300
  protected readonly collapsed: import("@angular/core").Signal<boolean>;
5259
6301
  private readonly form;
6302
+ /** True in a sidebar (or on its own), where the item can collapse to an icon rail. */
6303
+ protected readonly railable: import("@angular/core").Signal<boolean>;
6304
+ /** True once the enclosing sidebar has rendered: collapsing and expanding animate. */
6305
+ private readonly animated;
5260
6306
  /** The size in effect: `size`, else the app's `controlSize` default. */
5261
6307
  private readonly resolvedSize;
5262
6308
  readonly classes: import("@angular/core").Signal<string>;
6309
+ private readonly fade;
5263
6310
  protected readonly labelClasses: import("@angular/core").Signal<string>;
5264
6311
  protected readonly badgeClasses: import("@angular/core").Signal<string>;
6312
+ protected readonly dotClasses: import("@angular/core").Signal<string>;
5265
6313
  /** The badge as screen readers hear it after the label. */
5266
6314
  protected readonly badgeAnnouncement: import("@angular/core").Signal<string>;
5267
6315
  /** Collapsed, the label as the pointer tooltip; else the consumer's own `title`, if any. */
@@ -5430,15 +6478,27 @@ export declare class BfSkeletonComponent {
5430
6478
  /** One slot of the page row: a page number, or a gap before or after the current page's run. */
5431
6479
  type BfPaginationItem = number | 'gap-start' | 'gap-end';
5432
6480
  type BfPaginationDirection = 'previous' | 'next';
6481
+ /**
6482
+ * Writes a compact `bf-pagination`'s position text from the current page and the page count, such
6483
+ * as "v3 of 7" for page 3 of 7.
6484
+ *
6485
+ * @summary Function that writes a compact pager's position text, such as "v3 of 7"
6486
+ * @since 0.5.0
6487
+ */
6488
+ type BfPaginationPositionLabel = (page: number, count: number) => string;
5433
6489
  /**
5434
6490
  * Page navigation for a long list or table: Previous and Next buttons around the page numbers,
5435
6491
  * the current one marked, the hidden runs shown as an ellipsis. `compact` shows "Page 3 of 12"
5436
6492
  * between the arrows instead, for narrow screens. The page count comes from `pageCount`, or from
5437
- * `total` and `pageSize`; the consumer shows the rows of `page`.
6493
+ * `total` and `pageSize`; the consumer shows the rows of `page`. A compact pagination with its own
6494
+ * `positionLabel`, `previousLabel` and `nextLabel` steps through anything numbered, such as the
6495
+ * versions of a model ("‹ v3 of 7 ›").
5438
6496
  *
5439
6497
  * @example
5440
6498
  * <bf-pagination [(page)]="page" [pageCount]="12" />
5441
6499
  * <bf-pagination [(page)]="page" [total]="invoices().length" [pageSize]="10" compact />
6500
+ * <bf-pagination [(page)]="version" [pageCount]="7" compact [positionLabel]="versionOf"
6501
+ * label="Versions" previousLabel="Previous version" nextLabel="Next version" />
5442
6502
  * @summary Previous, next and numbered page buttons for a long list or table
5443
6503
  * @category Navigation
5444
6504
  * @since 0.3.1
@@ -5456,6 +6516,16 @@ export declare class BfPaginationComponent {
5456
6516
  readonly siblings: import("@angular/core").InputSignalWithTransform<number, unknown>;
5457
6517
  /** Shows "Page 3 of 12" between the arrows instead of the page buttons, for narrow screens. */
5458
6518
  readonly compact: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
6519
+ /** Writes the compact position text from the page and the count, such as
6520
+ * "v3 of 7"; unset uses `provideBfLabels()`'s `pageOf`
6521
+ * ("Page 3 of 12"). Only used with `compact`. */
6522
+ readonly positionLabel: import("@angular/core").InputSignal<BfPaginationPositionLabel | undefined>;
6523
+ /** Accessible name of the previous button, such as "Previous version"; empty falls back to
6524
+ * `provideBfLabels()`'s `previousPage` ("Previous page"). */
6525
+ readonly previousLabel: import("@angular/core").InputSignal<string>;
6526
+ /** Accessible name of the next button, such as "Next version"; empty falls back to
6527
+ * `provideBfLabels()`'s `nextPage` ("Next page"). */
6528
+ readonly nextLabel: import("@angular/core").InputSignal<string>;
5459
6529
  /** Button height step: sm 32, md 40, lg 48 px (44 px hit area under a coarse pointer); unset
5460
6530
  * follows `provideBfDefaults()` (`controlSize`, `md` by default). */
5461
6531
  readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
@@ -5468,6 +6538,10 @@ export declare class BfPaginationComponent {
5468
6538
  protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
5469
6539
  /** The navigation's name: `label`, else the `pagination` label. */
5470
6540
  protected readonly name: import("@angular/core").Signal<string>;
6541
+ protected readonly previousName: import("@angular/core").Signal<string>;
6542
+ protected readonly nextName: import("@angular/core").Signal<string>;
6543
+ /** The compact position text: `positionLabel`, else the `pageOf` label. */
6544
+ protected readonly position: import("@angular/core").Signal<string>;
5471
6545
  /** The page count in effect, at least 1: `pageCount`, else `total` over `pageSize`. */
5472
6546
  protected readonly count: import("@angular/core").Signal<number>;
5473
6547
  /** The page shown as current: `page`, clamped to 1…count. */
@@ -5519,6 +6593,21 @@ export declare class BfPaginationComponent {
5519
6593
  "required": false;
5520
6594
  "isSignal": true;
5521
6595
  };
6596
+ "positionLabel": {
6597
+ "alias": "positionLabel";
6598
+ "required": false;
6599
+ "isSignal": true;
6600
+ };
6601
+ "previousLabel": {
6602
+ "alias": "previousLabel";
6603
+ "required": false;
6604
+ "isSignal": true;
6605
+ };
6606
+ "nextLabel": {
6607
+ "alias": "nextLabel";
6608
+ "required": false;
6609
+ "isSignal": true;
6610
+ };
5522
6611
  "size": {
5523
6612
  "alias": "size";
5524
6613
  "required": false;
@@ -6014,6 +7103,215 @@ export declare class BfStepComponent {
6014
7103
  };
6015
7104
  }, {}, never, ["*"], true, never>;
6016
7105
  }
7106
+ /** What a `bf-chat-message` asks of the `bf-chat-thread` around it. */
7107
+ interface BfChatThreadHost {
7108
+ /** True once the thread's first render is done: a message created after that animates in, the
7109
+ * ones the thread opened with do not. */
7110
+ hasSettled(): boolean;
7111
+ }
7112
+ /**
7113
+ * Provided by `bf-chat-thread` so a `bf-chat-message` knows whether it arrived after the first
7114
+ * render, without `chat-message.component.ts` importing `chat-thread.component.ts`.
7115
+ */
7116
+ declare const BF_CHAT_THREAD: InjectionToken<BfChatThreadHost>;
7117
+ /**
7118
+ * The scrolling message list of a chat: a `role="log"` around `bf-chat-message` elements that
7119
+ * stays pinned to the newest message while text arrives. When the user scrolls up it stops
7120
+ * following and shows a "Jump to latest" button, which scrolls back down (smoothly, at once under
7121
+ * reduced motion) and follows again. While a message streams the log is `aria-busy`, so a screen
7122
+ * reader announces the reply once it is complete, not piece by piece. Messages added after the
7123
+ * first render rise and fade in; the ones it opens with do not. Give it a height (or `flex-1` in a
7124
+ * column): it scrolls inside.
7125
+ *
7126
+ * @example
7127
+ * <bf-chat-thread class="h-[32rem]">
7128
+ * @for (message of messages(); track message.id) {
7129
+ * <bf-chat-message [role]="message.role" [streaming]="message.streaming">{{ message.text }}</bf-chat-message>
7130
+ * }
7131
+ * </bf-chat-thread>
7132
+ * @summary Scrolling chat message log that follows new text and offers a jump to latest
7133
+ * @category Data display
7134
+ * @since 0.5.0
7135
+ * @slot default The messages, each a `bf-chat-message` (in order, oldest first).
7136
+ * @slot jump-icon An icon before the "Jump to latest" text, such as an arrow down.
7137
+ */
7138
+ export declare class BfChatThreadComponent implements BfChatThreadHost {
7139
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
7140
+ private readonly document;
7141
+ /** The log's accessible name; empty uses the `chatThread` label ("Conversation"). */
7142
+ readonly label: import("@angular/core").InputSignal<string>;
7143
+ private readonly scroller;
7144
+ private readonly content;
7145
+ private readonly messages;
7146
+ /** Pinned to the newest message: new text scrolls the log down. */
7147
+ protected readonly following: import("@angular/core").WritableSignal<boolean>;
7148
+ /** Bumped when the content or the log changes size, so the follow scroll runs again. */
7149
+ private readonly resized;
7150
+ private settled;
7151
+ /** A smooth jump is under way: its own scroll events neither stop nor restart the follow. */
7152
+ private jumping;
7153
+ private jumpTimer;
7154
+ /** A finger is on the log: the follow does not move the content under it. */
7155
+ protected touching: boolean;
7156
+ protected readonly name: import("@angular/core").Signal<string>;
7157
+ /** A message is still streaming, so the log holds its announcement until it is done. */
7158
+ protected readonly busy: import("@angular/core").Signal<boolean>;
7159
+ protected readonly jumpClasses: string;
7160
+ constructor();
7161
+ /**
7162
+ * True once the first render is done, so only later messages animate in.
7163
+ * @internal
7164
+ */
7165
+ hasSettled(): boolean;
7166
+ /** Scrolls to the newest message (smoothly, at once under reduced motion) and follows it again. */
7167
+ jumpToLatest(): void;
7168
+ /** @internal */
7169
+ onScroll(): void;
7170
+ /** @internal */
7171
+ onWheel(event: WheelEvent): void;
7172
+ /** @internal */
7173
+ onKeydown(event: KeyboardEvent): void;
7174
+ /**
7175
+ * Ends a smooth jump and catches up with text that arrived during it.
7176
+ * @internal
7177
+ */
7178
+ endJump(): void;
7179
+ /** The user scrolls up: stop at once, so the next follow scroll cannot pull the log back. */
7180
+ private stopFollowing;
7181
+ private clearJumpTimer;
7182
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfChatThreadComponent, never>;
7183
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfChatThreadComponent, "bf-chat-thread", never, {
7184
+ "label": {
7185
+ "alias": "label";
7186
+ "required": false;
7187
+ "isSignal": true;
7188
+ };
7189
+ }, {}, ["messages"], ["*", "[bfSlot=jump-icon]"], true, never>;
7190
+ }
7191
+ /**
7192
+ * Who wrote a `bf-chat-message`: `user` (a tinted bubble at the inline end) or `assistant` (full
7193
+ * width, no bubble).
7194
+ *
7195
+ * @summary Who wrote a chat message, the user or the assistant
7196
+ * @since 0.5.0
7197
+ */
7198
+ type BfChatMessageRole = 'user' | 'assistant';
7199
+ /**
7200
+ * How a `bf-chat-message` ended: `done` (the default, no status line), `stopped` (the user
7201
+ * stopped the reply) or `failed` (it did not arrive or was not sent).
7202
+ *
7203
+ * @summary How a chat message ended: done, stopped by the user, or failed
7204
+ * @since 0.5.0
7205
+ */
7206
+ type BfChatMessageStatus = 'done' | 'stopped' | 'failed';
7207
+ /**
7208
+ * One message of a `bf-chat-thread`. A `user` message is a tinted bubble at the inline end; an
7209
+ * `assistant` message is full width with no bubble. The default slot is the message text, styled
7210
+ * as prose (`bfProse`) for markdown the app renders. An optional header names the `author` and
7211
+ * the `time`. While `streaming`, a caret pulses at the end of the text and the message is
7212
+ * `aria-busy`, so the thread announces it once, when it is done. A `stopped` or `failed` message
7213
+ * shows a short status line, with the `action` slot beside it (a Retry button).
7214
+ *
7215
+ * @example
7216
+ * <bf-chat-message role="assistant" author="Assistant" [streaming]="writing()">
7217
+ * <div [innerHTML]="replyHtml()"></div>
7218
+ * </bf-chat-message>
7219
+ * <bf-chat-message role="user" author="You" time="2026-11-06T09:30:00Z">Three names for a cafe?</bf-chat-message>
7220
+ * @summary One chat message, a user bubble or a full-width assistant reply that can stream
7221
+ * @category Data display
7222
+ * @since 0.5.0
7223
+ * @slot default The message text: plain text or the app's rendered markdown, styled as prose.
7224
+ * @slot content Inline content below the text, such as cards, images or chips (not styled as prose).
7225
+ * @slot action Beside the status line, such as a Retry button.
7226
+ */
7227
+ export declare class BfChatMessageComponent {
7228
+ private readonly labels;
7229
+ private readonly appLocale;
7230
+ private readonly thread;
7231
+ /** Who wrote it: `user` (a bubble at the inline end) or `assistant` (full width, no bubble). */
7232
+ readonly role: import("@angular/core").InputSignal<BfChatMessageRole>;
7233
+ /** Whose message it is, shown in the header, such as "You" or the assistant's name; empty
7234
+ * shows none. */
7235
+ readonly author: import("@angular/core").InputSignal<string>;
7236
+ /** When it was written: a Date or an ISO 8601 string, shown as hours and minutes; empty or
7237
+ * invalid shows no time. */
7238
+ readonly time: import("@angular/core").InputSignal<string | Date | null>;
7239
+ /** Shown in place of the formatted time, such as "just now"; the `time` element's `datetime`
7240
+ * keeps the exact value. */
7241
+ readonly timeText: import("@angular/core").InputSignal<string>;
7242
+ /** The text is still arriving: a caret pulses at its end and the message is `aria-busy`. */
7243
+ readonly streaming: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7244
+ /** How it ended: `done` (no status line), `stopped` or `failed`. */
7245
+ readonly status: import("@angular/core").InputSignal<BfChatMessageStatus>;
7246
+ /** Shown in place of the built-in status line (`chatMessageStopped`, `chatMessageFailed`),
7247
+ * such as the error the server returned. */
7248
+ readonly statusMessage: import("@angular/core").InputSignal<string>;
7249
+ /** Rises and fades in: only for a message added to a thread after its first render. */
7250
+ protected readonly entering: import("@angular/core").WritableSignal<boolean>;
7251
+ private readonly date;
7252
+ /** The `time` element's machine value: the ISO string as given, a Date's ISO string, else null. */
7253
+ protected readonly datetime: import("@angular/core").Signal<string | null>;
7254
+ /** The visible time: `timeText`, else the time formatted as hours and minutes. */
7255
+ protected readonly timeLabel: import("@angular/core").Signal<string>;
7256
+ /** The status line's text: `statusMessage`, else the label of `stopped` or `failed`. */
7257
+ protected readonly statusLine: import("@angular/core").Signal<string>;
7258
+ protected readonly statusClasses: import("@angular/core").Signal<string>;
7259
+ protected readonly hostClasses: import("@angular/core").Signal<string>;
7260
+ protected readonly bodyClasses: import("@angular/core").Signal<string>;
7261
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfChatMessageComponent, never>;
7262
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfChatMessageComponent, "bf-chat-message", never, {
7263
+ "role": {
7264
+ "alias": "role";
7265
+ "required": false;
7266
+ "isSignal": true;
7267
+ };
7268
+ "author": {
7269
+ "alias": "author";
7270
+ "required": false;
7271
+ "isSignal": true;
7272
+ };
7273
+ "time": {
7274
+ "alias": "time";
7275
+ "required": false;
7276
+ "isSignal": true;
7277
+ };
7278
+ "timeText": {
7279
+ "alias": "timeText";
7280
+ "required": false;
7281
+ "isSignal": true;
7282
+ };
7283
+ "streaming": {
7284
+ "alias": "streaming";
7285
+ "required": false;
7286
+ "isSignal": true;
7287
+ };
7288
+ "status": {
7289
+ "alias": "status";
7290
+ "required": false;
7291
+ "isSignal": true;
7292
+ };
7293
+ "statusMessage": {
7294
+ "alias": "statusMessage";
7295
+ "required": false;
7296
+ "isSignal": true;
7297
+ };
7298
+ }, {}, never, ["*", "[bfSlot=content]", "[bfSlot=action]"], true, never>;
7299
+ }
7300
+ /**
7301
+ * Styles rendered markdown inside its element: headings, paragraphs, lists, links, inline code,
7302
+ * code blocks, quotes and rules, through the semantic tokens only (so a theme override restyles
7303
+ * it). The app renders the markdown to HTML itself (and sanitises it); `bfProse` only styles it.
7304
+ * A `bf-chat-message` body is a `bfProse` element already.
7305
+ *
7306
+ * @example
7307
+ * <article bfProse [innerHTML]="renderedMarkdown()"></article>
7308
+ * @summary Styles rendered markdown with the library tokens: headings, lists, code and quotes
7309
+ * @since 0.5.0
7310
+ */
7311
+ export declare class BfProseDirective {
7312
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfProseDirective, never>;
7313
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfProseDirective, "[bfProse]", never, {}, {}, never, never, true, never>;
7314
+ }
6017
7315
  /** An error as a field reads it: its kind and optional message; limit fields (`minLength`,
6018
7316
  * `context.requiredLength`, …) are read through type guards by `bfValidationMessage`. */
6019
7317
  type FieldErrorLike = Pick<ValidationError, 'kind' | 'message'>;
@@ -6040,6 +7338,223 @@ declare function fieldErrorMessageId(id: string, errorText: string, showError: b
6040
7338
  * Hydration-stable ids are decided before 1.0.
6041
7339
  */
6042
7340
  declare function uniqueId(prefix: string): string;
7341
+ /**
7342
+ * Internal native single-choice dropdown styled like the text field, with a placeholder option and
7343
+ * Signal Forms binding. Generic over the option value (a string by default); `null` means nothing
7344
+ * is selected.
7345
+ *
7346
+ * @example
7347
+ * <bf-native-select label="Project" placeholder="Pick a project" [options]="projects" [(value)]="projectId" />
7348
+ * <bf-native-select label="Team" placeholder="Pick a team" [options]="teams" [formField]="task.teamId" />
7349
+ * @summary Native single-choice dropdown styled like the text field, with Signal Forms binding
7350
+ * @category Forms
7351
+ * @since 0.1.0
7352
+ */
7353
+ declare class BfNativeSelectComponent<T = string> implements FormValueControl<T | null> {
7354
+ /** The selected option's value, `null` while nothing is selected; two-way bindable and bound by
7355
+ * `[formField]`. */
7356
+ readonly value: import("@angular/core").ModelSignal<T | null>;
7357
+ /** The options to choose from. */
7358
+ readonly options: import("@angular/core").InputSignal<readonly BfSelectOption<T>[]>;
7359
+ /** Equality for matching `value` to an option (default `Object.is`); pass one for object values. */
7360
+ readonly compareWith: import("@angular/core").InputSignal<BfCompareWith<T>>;
7361
+ /** Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md`
7362
+ * by default). */
7363
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
7364
+ /** Visible label above the control. */
7365
+ readonly label: import("@angular/core").InputSignal<string>;
7366
+ /**
7367
+ * Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label`
7368
+ * is set.
7369
+ */
7370
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
7371
+ /**
7372
+ * Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when
7373
+ * `label` is set.
7374
+ */
7375
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
7376
+ /** Disabled first option shown while nothing is selected. */
7377
+ readonly placeholder: import("@angular/core").InputSignal<string>;
7378
+ /** Helper text under the control. */
7379
+ readonly helpText: import("@angular/core").InputSignal<string>;
7380
+ /** Explicit error text; takes precedence over Signal Forms errors. */
7381
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
7382
+ /** Disables the control. */
7383
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7384
+ /** Shows the value but blocks changes; the select stays focusable and exposes `aria-readonly`. */
7385
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7386
+ /** Marks the control required. */
7387
+ readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7388
+ /** Field validity from Signal Forms. */
7389
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7390
+ /** Whether the user has left the field. */
7391
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7392
+ /** Validation errors from Signal Forms. */
7393
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
7394
+ /** Native name attribute. */
7395
+ readonly name: import("@angular/core").InputSignal<string>;
7396
+ /** Control id; generated when not set. */
7397
+ readonly id: import("@angular/core").InputSignal<string>;
7398
+ /** Emitted with the chosen value after a user change (never `null`). */
7399
+ readonly changed: import("@angular/core").OutputEmitterRef<T>;
7400
+ /** Emitted when the control gains focus. */
7401
+ readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
7402
+ /** Emitted when the control loses focus. */
7403
+ readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
7404
+ /** Emitted on blur so Signal Forms marks the field touched. */
7405
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
7406
+ private readonly el;
7407
+ private readonly defaults;
7408
+ /** The size in effect: `size`, else the app's `fieldSize` default. */
7409
+ protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
7410
+ readonly wrapperClasses: import("@angular/core").Signal<string>;
7411
+ /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
7412
+ readonly selectedIndex: import("@angular/core").Signal<number>;
7413
+ readonly showEmptyOption: import("@angular/core").Signal<boolean>;
7414
+ private readonly labels;
7415
+ readonly errorText: import("@angular/core").Signal<string>;
7416
+ readonly showError: import("@angular/core").Signal<boolean>;
7417
+ readonly describedBy: import("@angular/core").Signal<string | null>;
7418
+ /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
7419
+ readonly errorMessageId: import("@angular/core").Signal<string | null>;
7420
+ /**
7421
+ * `aria-label` value: only when there is no visible label, so the label stays the sole accessible
7422
+ * name.
7423
+ */
7424
+ readonly ariaLabelAttr: import("@angular/core").Signal<string | null>;
7425
+ /** `aria-labelledby` value, under the same rule as `ariaLabelAttr`. */
7426
+ readonly ariaLabelledbyAttr: import("@angular/core").Signal<string | null>;
7427
+ readonly classes: import("@angular/core").Signal<string>;
7428
+ /** Native `value` of an option: its value for a primitive, its index for an object. */
7429
+ protected readonly key: typeof choiceKey;
7430
+ /**
7431
+ * Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
7432
+ * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
7433
+ */
7434
+ focus(options?: FocusOptions): void;
7435
+ /**
7436
+ * Read-only: no picker on a mouse press. Cancelling the press also cancels its focus, so the
7437
+ * select is focused explicitly, like any other read-only control. Touch screens open the
7438
+ * platform picker without a cancellable press; a choice made there is reverted by `onChange`.
7439
+ * @internal
7440
+ */
7441
+ onMousedown(event: MouseEvent): void;
7442
+ /**
7443
+ * Read-only: no picker or value change from the keyboard (arrows, Enter, Space, F4, type-ahead).
7444
+ * @internal
7445
+ */
7446
+ onKeydown(event: KeyboardEvent): void;
7447
+ /**
7448
+ * Writes the chosen option's value to the model and emits `changed`; while read-only, restores
7449
+ * the shown option instead.
7450
+ * @internal
7451
+ */
7452
+ onChange(el: HTMLSelectElement): void;
7453
+ /**
7454
+ * Marks the field touched and emits `blurred`.
7455
+ * @internal
7456
+ */
7457
+ onBlur(event: FocusEvent): void;
7458
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfNativeSelectComponent<any>, never>;
7459
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfNativeSelectComponent<any>, "bf-native-select", never, {
7460
+ "value": {
7461
+ "alias": "value";
7462
+ "required": false;
7463
+ "isSignal": true;
7464
+ };
7465
+ "options": {
7466
+ "alias": "options";
7467
+ "required": true;
7468
+ "isSignal": true;
7469
+ };
7470
+ "compareWith": {
7471
+ "alias": "compareWith";
7472
+ "required": false;
7473
+ "isSignal": true;
7474
+ };
7475
+ "size": {
7476
+ "alias": "size";
7477
+ "required": false;
7478
+ "isSignal": true;
7479
+ };
7480
+ "label": {
7481
+ "alias": "label";
7482
+ "required": false;
7483
+ "isSignal": true;
7484
+ };
7485
+ "ariaLabel": {
7486
+ "alias": "ariaLabel";
7487
+ "required": false;
7488
+ "isSignal": true;
7489
+ };
7490
+ "ariaLabelledby": {
7491
+ "alias": "ariaLabelledby";
7492
+ "required": false;
7493
+ "isSignal": true;
7494
+ };
7495
+ "placeholder": {
7496
+ "alias": "placeholder";
7497
+ "required": false;
7498
+ "isSignal": true;
7499
+ };
7500
+ "helpText": {
7501
+ "alias": "helpText";
7502
+ "required": false;
7503
+ "isSignal": true;
7504
+ };
7505
+ "errorMessage": {
7506
+ "alias": "errorMessage";
7507
+ "required": false;
7508
+ "isSignal": true;
7509
+ };
7510
+ "disabled": {
7511
+ "alias": "disabled";
7512
+ "required": false;
7513
+ "isSignal": true;
7514
+ };
7515
+ "readonly": {
7516
+ "alias": "readonly";
7517
+ "required": false;
7518
+ "isSignal": true;
7519
+ };
7520
+ "required": {
7521
+ "alias": "required";
7522
+ "required": false;
7523
+ "isSignal": true;
7524
+ };
7525
+ "invalid": {
7526
+ "alias": "invalid";
7527
+ "required": false;
7528
+ "isSignal": true;
7529
+ };
7530
+ "touched": {
7531
+ "alias": "touched";
7532
+ "required": false;
7533
+ "isSignal": true;
7534
+ };
7535
+ "errors": {
7536
+ "alias": "errors";
7537
+ "required": false;
7538
+ "isSignal": true;
7539
+ };
7540
+ "name": {
7541
+ "alias": "name";
7542
+ "required": false;
7543
+ "isSignal": true;
7544
+ };
7545
+ "id": {
7546
+ "alias": "id";
7547
+ "required": false;
7548
+ "isSignal": true;
7549
+ };
7550
+ }, {
7551
+ "value": "valueChange";
7552
+ "changed": "changed";
7553
+ "focused": "focused";
7554
+ "blurred": "blurred";
7555
+ "touch": "touch";
7556
+ }, never, never, true, never>;
7557
+ }
6043
7558
  /** One plain date's parts; `month` is 1–12. */
6044
7559
  interface CalendarDate {
6045
7560
  readonly year: number;
@@ -6112,6 +7627,9 @@ interface BfNavHost {
6112
7627
  readonly layout: BfNavLayout;
6113
7628
  /** True while the sidebar is collapsed to an icon rail; always false in a top nav. */
6114
7629
  readonly collapsed: Signal<boolean>;
7630
+ /** True once the host has rendered, so collapsing and expanding animate but the first render
7631
+ * (a restored collapsed state) does not; always false in a top nav. */
7632
+ readonly animated: Signal<boolean>;
6115
7633
  /** The id of the host's `nav` element (for a toggle's `aria-controls`); null in a top nav. */
6116
7634
  readonly navId: string | null;
6117
7635
  /** Writes the sidebar's `collapsed` model, as a user change; does nothing in a top nav. */
@@ -6132,4 +7650,4 @@ declare const BF_NAV: InjectionToken<BfNavHost>;
6132
7650
  declare function resetBfDocument(document: Document): void;
6133
7651
  /** Runs `resetBfDocument()` when the environment injector that holds it is destroyed. Internal. */
6134
7652
  declare function provideBfDocumentReset(): EnvironmentProviders;
6135
- 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 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 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, 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 };
7653
+ 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 BfImageTileAspect, type BfImageTileCaptionPlacement, type BfInputAutocapitalize, type BfInputEnterKeyHint, type BfInputInputMode, type BfInputMode, type BfInputType, type BfLabels, type BfLabelsOverrides, type BfMediaCardAspect, type BfPageHeaderHeadingLevel, type BfPaginationPositionLabel, 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 };