@bitforgehq/ui 0.4.0 → 0.5.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +303 -0
  2. package/README.md +12 -12
  3. package/ai/MANIFEST.index.json +65 -2
  4. package/ai/MANIFEST.json +2221 -737
  5. package/ai/REFERENCE.index.json +161 -0
  6. package/ai/REFERENCE.json +1336 -27
  7. package/ai/UI_GUIDE.md +61 -20
  8. package/ai/components/app-frame.md +1 -1
  9. package/ai/components/chat-composer.md +357 -0
  10. package/ai/components/chat-message.md +251 -0
  11. package/ai/components/chat-thread.md +521 -0
  12. package/ai/components/checkbox.md +7 -0
  13. package/ai/components/combobox.md +12 -0
  14. package/ai/components/date-input.md +9 -0
  15. package/ai/components/datetime-input.md +2 -0
  16. package/ai/components/field.md +10 -0
  17. package/ai/components/file-input.md +498 -0
  18. package/ai/components/header.md +1 -1
  19. package/ai/components/image-input.md +368 -0
  20. package/ai/components/image-viewer.md +309 -0
  21. package/ai/components/media-card.md +478 -0
  22. package/ai/components/menu-button.md +2 -0
  23. package/ai/components/menu.md +10 -0
  24. package/ai/components/multi-combobox.md +7 -0
  25. package/ai/components/nav-group.md +10 -3
  26. package/ai/components/nav-item.md +11 -2
  27. package/ai/components/number-input.md +2 -7
  28. package/ai/components/popover.md +13 -0
  29. package/ai/components/radio-group.md +6 -0
  30. package/ai/components/segmented.md +14 -2
  31. package/ai/components/select.md +100 -42
  32. package/ai/components/sidebar-toggle.md +16 -9
  33. package/ai/components/sidebar.md +41 -13
  34. package/ai/components/tab-group.md +18 -2
  35. package/ai/components/table.md +17 -2
  36. package/ai/components/theme-menu-button.md +1 -0
  37. package/ai/components/time-input.md +2 -0
  38. package/ai/components/toolbar.md +7 -18
  39. package/ai/reference/bf-chat-composer-harness.md +64 -0
  40. package/ai/reference/bf-chat-message-role.md +27 -0
  41. package/ai/reference/bf-chat-message-status.md +28 -0
  42. package/ai/reference/bf-fade-in-directive.md +18 -0
  43. package/ai/reference/bf-file-input-harness.md +67 -0
  44. package/ai/reference/bf-file-input-variant.md +27 -0
  45. package/ai/reference/bf-image-input-harness.md +67 -0
  46. package/ai/reference/bf-image-input-shape.md +27 -0
  47. package/ai/reference/bf-image-viewer-actions-context.md +29 -0
  48. package/ai/reference/bf-image-viewer-harness.md +87 -0
  49. package/ai/reference/bf-image-viewer-image.md +25 -0
  50. package/ai/reference/bf-image-viewer-options.md +33 -0
  51. package/ai/reference/bf-image-viewer-origin.md +23 -0
  52. package/ai/reference/bf-image-viewer-ref.md +55 -0
  53. package/ai/reference/bf-image-viewer-service.md +32 -0
  54. package/ai/reference/bf-labels.md +105 -75
  55. package/ai/reference/bf-media-card-aspect.md +28 -0
  56. package/ai/reference/bf-media-card-harness.md +66 -0
  57. package/ai/reference/bf-motion-list-directive.md +22 -0
  58. package/ai/reference/bf-prose-directive.md +16 -0
  59. package/ai/reference/bf-rejected-file-reason.md +28 -0
  60. package/ai/reference/bf-rejected-file.md +28 -0
  61. package/ai/reference/bf-select-harness.md +14 -5
  62. package/ai/reference/bf-select-option.md +2 -0
  63. package/ai/reference/bf-size.md +3 -0
  64. package/ai/reference/bf_table_sort_select.md +23 -0
  65. package/ai/reference/provide-bf-labels.md +10 -3
  66. package/ai/reference/provide-bf-select.md +20 -0
  67. package/bin/bf-ui.js +1 -0
  68. package/fesm2022/bitforgehq-ui-overlay.mjs +2649 -606
  69. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  70. package/fesm2022/bitforgehq-ui-testing.mjs +356 -10
  71. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  72. package/fesm2022/bitforgehq-ui.mjs +7531 -3989
  73. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  74. package/llms-full.txt +4151 -130
  75. package/llms.txt +31 -1
  76. package/package.json +1 -1
  77. package/theme.css +60 -0
  78. package/types/bitforgehq-ui-overlay.d.ts +684 -7
  79. package/types/bitforgehq-ui-testing.d.ts +434 -6
  80. package/types/bitforgehq-ui.d.ts +1473 -182
@@ -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,156 @@ 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 and bf-image-viewer's message for an image that failed to load.
647
+ * @default 'The image could not be loaded'
648
+ */
649
+ readonly imageLoadFailed: string;
650
+ /**
651
+ * bf-image-viewer's name.
652
+ * @default 'Image viewer'
653
+ */
654
+ readonly imageViewer: string;
655
+ /**
656
+ * bf-image-viewer's button that shows the previous image.
657
+ * @default 'Previous image'
658
+ */
659
+ readonly imageViewerPrevious: string;
660
+ /**
661
+ * bf-image-viewer's button that shows the next image.
662
+ * @default 'Next image'
663
+ */
664
+ readonly imageViewerNext: string;
665
+ /**
666
+ * bf-image-viewer's counter: the shown image's position (1-based) among the images.
667
+ * @default (position, count) => `${position} of ${count}`
668
+ */
669
+ readonly imageViewerPosition: (position: number, count: number) => string;
520
670
  /** Default error text per validator kind (see `BfValidationMessages`). */
521
671
  readonly validation: BfValidationMessages;
522
672
  }
@@ -557,9 +707,16 @@ export declare const BF_LABELS: InjectionToken<BfLabels>;
557
707
  * `dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,
558
708
  * `timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,
559
709
  * `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:
710
+ * `chartValue`, `chartEmpty`, `chartError`, `listItemUnread`,
711
+ * `fileInputChoose`, `fileInputDropHint`, `fileInputDropzone`, `fileInputTapToChoose`, `fileInputRemove`,
712
+ * `fileInputDismiss`, `fileInputAdded`, `fileInputNotAdded`, `fileInputRemoved`, `fileInputCount`,
713
+ * `fileRejectedType`, `fileRejectedSize`, `fileRejectedCount`,
714
+ * `imageInputChoose`, `imageInputUpload`, `imageInputReplace`, `imageInputRemove`,
715
+ * `chatThread`, `chatJumpToLatest`, `chatMessageStopped`, `chatMessageFailed`, `chatComposer`,
716
+ * `chatSend`, `chatStop`, `mediaCardSelect`, `imageLoadFailed`, `validation`); the overlay
717
+ * services (`ok`, `cancel`, `actions`, `sheet`, `modal`, `close`, `expand`, `collapse`,
718
+ * `sheetPosition`, `dialog`, `notifications`, `imageViewer`, `imageViewerPrevious`,
719
+ * `imageViewerNext`, `imageViewerPosition`) are root singletons and pick it up only when the caller passes its injector:
563
720
  * `open(…, { injector: inject(Injector) })`.
564
721
  *
565
722
  * @param overrides The strings to replace; any key left out keeps the parent's.
@@ -568,6 +725,51 @@ export declare const BF_LABELS: InjectionToken<BfLabels>;
568
725
  * @since 0.3.0
569
726
  */
570
727
  export declare function provideBfLabels(overrides: BfLabelsOverrides): Provider;
728
+ /**
729
+ * The rule a refused file broke: `type` (not accepted), `size` (over `maxSize`) or `count` (over
730
+ * the limit).
731
+ *
732
+ * @summary The rule a refused file broke, its type, its size or the file count
733
+ * @since 0.5.0
734
+ */
735
+ type BfRejectedFileReason = 'type' | 'size' | 'count';
736
+ /**
737
+ * A picked or dropped file that broke a rule, with the rule and its message.
738
+ *
739
+ * @summary A picked or dropped file that broke a rule, with the rule and its message
740
+ * @since 0.5.0
741
+ */
742
+ interface BfRejectedFile {
743
+ /** The file that was not added. */
744
+ readonly file: File;
745
+ /** The rule it broke: `type` (not accepted), `size` (over `maxSize`) or `count` (over the limit). */
746
+ readonly reason: BfRejectedFileReason;
747
+ /** The user-facing text for the rule, from the labels. */
748
+ readonly message: string;
749
+ }
750
+ /** The rules a file picker applies to what the user picks or drops. */
751
+ interface FileRules {
752
+ readonly accept: string;
753
+ readonly maxSize: number | undefined;
754
+ readonly maxFiles: number | undefined;
755
+ readonly multiple: boolean;
756
+ }
757
+ /** Whether `file` satisfies an `accept` string (MIME types, `type/*` wildcards and `.ext` tokens). */
758
+ declare function matchesAccept(file: File, accept: string): boolean;
759
+ /**
760
+ * Formats a byte count with base 1024: whole KB, one decimal (no trailing `.0`) for MB and GB.
761
+ * The unit is chosen after rounding, so a value that rounds up to 1024 shows as 1 of the next unit.
762
+ */
763
+ declare function formatFileSize(bytes: number): string;
764
+ /**
765
+ * Applies the rules to `incoming` in order and returns the new value and what was rejected.
766
+ * A file is checked for type, then size, then count. In single mode the first valid file
767
+ * replaces `current`; later valid ones are rejected as `count` with a max of 1.
768
+ */
769
+ declare function applyFileRules(current: readonly File[], incoming: readonly File[], rules: FileRules, labels: BfLabels): {
770
+ value: File[];
771
+ rejected: BfRejectedFile[];
772
+ };
571
773
  /**
572
774
  * The app's colour scheme choice: follow the system, or force light or dark.
573
775
  *
@@ -1126,6 +1328,9 @@ export declare class BfFieldComponent {
1126
1328
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1127
1329
  /** The id of the control inside the default slot. */
1128
1330
  readonly controlId: import("@angular/core").InputSignal<string>;
1331
+ /** The label's id (`"<controlId>-label"`), for a control a `<label for>` cannot name, such as a
1332
+ * `div[role=combobox]` that names itself with `aria-labelledby`. */
1333
+ readonly labelId: import("@angular/core").Signal<string>;
1129
1334
  readonly helpId: import("@angular/core").Signal<string>;
1130
1335
  readonly errorId: import("@angular/core").Signal<string>;
1131
1336
  /** The error paragraph: `error`, visually hidden while a press holds the layout. */
@@ -1786,173 +1991,255 @@ export declare class BfTextareaComponent implements FormValueControl<string> {
1786
1991
  }, never, never, true, never>;
1787
1992
  }
1788
1993
  /**
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).
1994
+ * The look of a `bf-file-input`: a compact field with a pick button (`field`), or a drop zone
1995
+ * (`dropzone`). Both have the same API and behaviour.
1813
1996
  *
1814
- * @summary One choice of a select with its value, its label and a disabled flag
1815
- * @since 0.1.0
1997
+ * @summary The two looks of a file picker, a compact field or a drop zone
1998
+ * @since 0.5.0
1816
1999
  */
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;
2000
+ type BfFileInputVariant = 'field' | 'dropzone';
2001
+ /** A chosen file as listed: its preview URL (images) or extension, and its formatted size. */
2002
+ interface BfFileInputRow {
2003
+ readonly file: File;
2004
+ readonly preview: string | null;
2005
+ readonly extension: string;
2006
+ readonly size: string;
1824
2007
  }
2008
+ /** The upper-case extension of `name`, at most 4 characters; empty without one. */
2009
+ declare function extensionOf(name: string): string;
1825
2010
  /**
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.
2011
+ * File picker field: a tonal "Choose file" button inside a field box that also takes dropped
2012
+ * files, with the chosen files listed under it (thumbnail, name, size, remove button). `accept`,
2013
+ * `maxSize` and `maxFiles` decide what is added; a file that breaks one is listed in red with the
2014
+ * reason until the next pick and emitted through `rejected`, and never enters the value. The value
2015
+ * is always a `File[]` (one file at most while `multiple` is off). It only picks: uploading the
2016
+ * files is the app's job. Bind `[(value)]` directly or `[formField]` from a signal form.
1829
2017
  *
1830
2018
  * @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
2019
+ * <bf-file-input label="Attachments" multiple accept=".pdf,image/*" [maxSize]="10485760" [(value)]="files" />
2020
+ * <bf-file-input label="Contract" accept=".pdf" [formField]="signup.contract" />
2021
+ * @summary File picker field that lists picked or dropped files and checks type, size and count
1834
2022
  * @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). */
2023
+ * @since 0.5.0
2024
+ */
2025
+ export declare class BfFileInputComponent implements FormValueControl<File[]> {
2026
+ /** The chosen files; two-way bindable and bound by `[formField]`. Empty while nothing is chosen,
2027
+ * at most one file while `multiple` is off. */
2028
+ readonly value: import("@angular/core").ModelSignal<File[]>;
2029
+ /** The look: `field` (a compact field with a pick button, the default) or `dropzone`. */
2030
+ readonly variant: import("@angular/core").InputSignal<BfFileInputVariant>;
2031
+ /** Allows several files: new picks and drops are added to the list. Off, a new file replaces
2032
+ * the one chosen. */
2033
+ readonly multiple: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2034
+ /** The accepted file types, in the native `accept` syntax: MIME types, `type/*` wildcards and
2035
+ * `.ext` extensions, comma-separated (`'.pdf,image/*'`). Empty accepts every file. Dropped files
2036
+ * are checked against it too. */
2037
+ readonly accept: import("@angular/core").InputSignal<string>;
2038
+ /** Largest file size in bytes; a larger file is rejected. Unset allows any size. */
2039
+ readonly maxSize: import("@angular/core").InputSignal<number | undefined>;
2040
+ /** Most files in the value with `multiple`, counting those already chosen; files past it are
2041
+ * rejected. Unset allows any number. */
2042
+ readonly maxFiles: import("@angular/core").InputSignal<number | undefined>;
2043
+ /** Height step of the field box: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()`
2044
+ * (`fieldSize`, `md` by default). */
1847
2045
  readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
1848
2046
  /** Visible label above the control. */
1849
2047
  readonly label: import("@angular/core").InputSignal<string>;
1850
2048
  /**
1851
- * Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label`
1852
- * is set.
2049
+ * Accessible name when no visible label is rendered, read before the control's visible text
2050
+ * ("Attachments, Choose files"). Ignored when `label` is set.
1853
2051
  */
1854
2052
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
1855
2053
  /**
1856
- * Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when
1857
- * `label` is set.
2054
+ * Id(s) of the element(s) that name the field when no visible label is rendered, read before the
2055
+ * control's visible text. Ignored when `label` is set.
1858
2056
  */
1859
2057
  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
2058
  /** Helper text under the control. */
1863
2059
  readonly helpText: import("@angular/core").InputSignal<string>;
1864
2060
  /** Explicit error text; takes precedence over Signal Forms errors. */
1865
2061
  readonly errorMessage: import("@angular/core").InputSignal<string>;
1866
- /** Disables the control. */
2062
+ /** Disables the control: no picking, dropping or removing, and the pick button is not focusable. */
1867
2063
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1868
- /** Shows the value but blocks changes; the select stays focusable and exposes `aria-readonly`. */
2064
+ /** Makes the control read-only: the files stay listed, with no pick button, drop or remove. */
1869
2065
  readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1870
- /** Marks the control required. */
2066
+ /** Marks the control required (label marker). */
1871
2067
  readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1872
2068
  /** Field validity from Signal Forms. */
1873
2069
  readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1874
- /** Whether the user has left the field. */
2070
+ /** Whether the user has left the field; errors show only once touched. */
1875
2071
  readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
1876
- /** Validation errors from Signal Forms. */
2072
+ /** Validation errors from Signal Forms; the first message is shown once touched. */
1877
2073
  readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
1878
- /** Native name attribute. */
2074
+ /** Native name attribute; set, the hidden native file input carries the chosen files, so a
2075
+ * plain HTML form post includes them. */
1879
2076
  readonly name: import("@angular/core").InputSignal<string>;
1880
- /** Control id; generated when not set. */
2077
+ /** Control id (the pick button's); generated when not set. */
1881
2078
  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. */
2079
+ /** Emitted with the new files right after the user adds or removes one, not on a model write. */
2080
+ readonly changed: import("@angular/core").OutputEmitterRef<File[]>;
2081
+ /** Emitted once per pick or drop in which at least one file broke a rule, with those files. */
2082
+ readonly rejected: import("@angular/core").OutputEmitterRef<BfRejectedFile[]>;
2083
+ /** Emitted when the pick button gains focus. */
1885
2084
  readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
1886
- /** Emitted when the control loses focus. */
2085
+ /** Emitted when the pick button loses focus. */
1887
2086
  readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
1888
2087
  /** Emitted on blur so Signal Forms marks the field touched. */
1889
2088
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
1890
- private readonly el;
2089
+ private readonly pick;
2090
+ private readonly native;
2091
+ private readonly removeButtons;
2092
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
1891
2093
  private readonly defaults;
2094
+ private readonly injector;
2095
+ private readonly document;
2096
+ /** Whether the primary pointer is coarse (touch), from `matchMedia`: the drop zone then says
2097
+ * "tap" and takes less padding. */
2098
+ protected readonly coarse: import("@angular/core").WritableSignal<boolean>;
2099
+ /** The files of the last pick or drop that broke a rule, until the next one or a dismiss. */
2100
+ protected readonly rejectedFiles: import("@angular/core").WritableSignal<readonly BfRejectedFile[]>;
2101
+ /** The polite live message after an add, a rejection or a remove. */
2102
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
2103
+ /** The pending write of the next live message. */
2104
+ private announceTimer;
2105
+ /** Whether a drag is over the box. */
2106
+ private readonly dragging;
2107
+ /** The object URL of each image file in the value, for its thumbnail. */
2108
+ private readonly previews;
2109
+ protected readonly thumbClasses: string;
2110
+ protected readonly removeClasses: string;
2111
+ protected readonly crossClasses: string;
2112
+ protected readonly rejectedRowClasses = "grid grid-cols-[2.5rem_minmax(0,1fr)_auto] items-center gap-3 rounded-tile border border-border bg-surface py-1.5 ps-2 pe-1.5";
2113
+ protected readonly extensionOf: typeof extensionOf;
2114
+ protected readonly badgeClasses = "grid size-11 place-items-center rounded-full bg-primary-soft text-primary-strong";
2115
+ protected readonly arrowClasses: string;
1892
2116
  /** The size in effect: `size`, else the app's `fieldSize` default. */
1893
2117
  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;
2118
+ /** Whether the user can pick, drop and remove files. */
2119
+ protected readonly editable: import("@angular/core").Signal<boolean>;
1899
2120
  readonly errorText: import("@angular/core").Signal<string>;
1900
2121
  readonly showError: import("@angular/core").Signal<boolean>;
1901
- readonly describedBy: import("@angular/core").Signal<string | null>;
2122
+ /** `aria-describedby` of the pick button: the text beside it (hint or file name), then the help
2123
+ * and error paragraphs. */
2124
+ readonly describedBy: import("@angular/core").Signal<string>;
1902
2125
  /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
1903
2126
  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
- */
2127
+ /** `ariaLabel` in effect: only when there is no visible label. It names the control through a
2128
+ * hidden element, never `aria-label`, so the visible text stays part of the name. */
1908
2129
  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;
2130
+ /** `aria-labelledby` of the pick button or drop zone: the field's name (the visible label, else
2131
+ * `ariaLabelledby`, else `ariaLabel`), then the control's visible text ("Contract, Choose file"),
2132
+ * so a speech-input user can say what they see (WCAG 2.5.3). */
2133
+ readonly ariaLabelledbyAttr: import("@angular/core").Signal<string>;
2134
+ /** The drop zone's `aria-describedby`: the help and error paragraphs (its text is its name). */
2135
+ protected readonly zoneDescribedBy: import("@angular/core").Signal<string | null>;
2136
+ /** The drop zone's text: the drop prompt, or the tap prompt under a coarse pointer. */
2137
+ protected readonly zoneText: import("@angular/core").Signal<string>;
2138
+ /** The text in the box: the file name (one file, single mode), the file count (read-only with
2139
+ * `multiple`), else the drop hint. */
2140
+ protected readonly boxText: import("@angular/core").Signal<string>;
2141
+ protected readonly rows: import("@angular/core").Signal<BfFileInputRow[]>;
2142
+ readonly boxClasses: import("@angular/core").Signal<string>;
2143
+ protected readonly zoneClasses: import("@angular/core").Signal<string>;
2144
+ protected readonly pickClasses: import("@angular/core").Signal<string>;
2145
+ protected readonly hintClasses: import("@angular/core").Signal<string>;
2146
+ protected readonly rowClasses: import("@angular/core").Signal<string>;
2147
+ constructor();
1914
2148
  /**
1915
- * Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
2149
+ * Focuses the pick button; Signal Forms calls it from `focusBoundControl()`. Read-only has no
2150
+ * pick button, so nothing is focused.
1916
2151
  * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
1917
2152
  */
1918
2153
  focus(options?: FocusOptions): void;
1919
2154
  /**
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`.
2155
+ * Opens the native picker from the drop zone while the control is editable.
1923
2156
  * @internal
1924
2157
  */
1925
- onMousedown(event: MouseEvent): void;
2158
+ openPicker(): void;
1926
2159
  /**
1927
- * Read-only: no picker or value change from the keyboard (arrows, Enter, Space, F4, type-ahead).
2160
+ * Enter and Space on the drop zone open the picker (and do not scroll the page).
1928
2161
  * @internal
1929
2162
  */
1930
- onKeydown(event: KeyboardEvent): void;
2163
+ onZoneKey(event: Event): void;
1931
2164
  /**
1932
- * Writes the chosen option's value to the model and emits `changed`; while read-only, restores
1933
- * the shown option instead.
2165
+ * Adds the native input's files, then empties it so picking the same file again fires `change`.
1934
2166
  * @internal
1935
2167
  */
1936
- onChange(el: HTMLSelectElement): void;
2168
+ onPicked(input: HTMLInputElement): void;
2169
+ /**
2170
+ * Allows a drop on the box and highlights it while the control is editable.
2171
+ * @internal
2172
+ */
2173
+ onDragOver(event: DragEvent): void;
2174
+ /**
2175
+ * Ends the highlight when the drag leaves the box (not when it moves onto a child).
2176
+ * @internal
2177
+ */
2178
+ onDragLeave(event: DragEvent, box: HTMLElement): void;
2179
+ /**
2180
+ * Adds the dropped files through the same rules as a pick; ignores them (but still keeps the
2181
+ * browser from opening them) while the control cannot be edited.
2182
+ * @internal
2183
+ */
2184
+ onDrop(event: DragEvent): void;
1937
2185
  /**
1938
2186
  * Marks the field touched and emits `blurred`.
1939
2187
  * @internal
1940
2188
  */
1941
2189
  onBlur(event: FocusEvent): void;
1942
- static ɵfac: i0.ɵɵFactoryDeclaration<BfSelectComponent<any>, never>;
1943
- static ɵcmp: i0.ɵɵComponentDeclaration<BfSelectComponent<any>, "bf-select", never, {
2190
+ /**
2191
+ * Removes the file at `index`, then focuses the next remove button, else the previous one,
2192
+ * else the pick button.
2193
+ * @internal
2194
+ */
2195
+ removeAt(index: number): void;
2196
+ /**
2197
+ * Dismisses the rejected file at `index` and returns focus to the pick button.
2198
+ * @internal
2199
+ */
2200
+ dismissAt(index: number): void;
2201
+ /** Runs `files` through the rules: accepted ones join the value, rejected ones are listed. */
2202
+ private add;
2203
+ /**
2204
+ * Puts `text` in the live region. The region is emptied first and the text follows after
2205
+ * `ANNOUNCE_DELAY`, so a message equal to the last one ("1 file added" twice) still changes the
2206
+ * DOM in a separate update and is announced again.
2207
+ */
2208
+ private announce;
2209
+ /** Keeps one object URL per image in `files`: creates the missing ones, revokes the rest. */
2210
+ private syncPreviews;
2211
+ /** With a `name`, puts the current files on the hidden native input, so a plain form post sends
2212
+ * them. */
2213
+ private writeNativeFiles;
2214
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfFileInputComponent, never>;
2215
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfFileInputComponent, "bf-file-input", never, {
1944
2216
  "value": {
1945
2217
  "alias": "value";
1946
2218
  "required": false;
1947
2219
  "isSignal": true;
1948
2220
  };
1949
- "options": {
1950
- "alias": "options";
1951
- "required": true;
2221
+ "variant": {
2222
+ "alias": "variant";
2223
+ "required": false;
1952
2224
  "isSignal": true;
1953
2225
  };
1954
- "compareWith": {
1955
- "alias": "compareWith";
2226
+ "multiple": {
2227
+ "alias": "multiple";
2228
+ "required": false;
2229
+ "isSignal": true;
2230
+ };
2231
+ "accept": {
2232
+ "alias": "accept";
2233
+ "required": false;
2234
+ "isSignal": true;
2235
+ };
2236
+ "maxSize": {
2237
+ "alias": "maxSize";
2238
+ "required": false;
2239
+ "isSignal": true;
2240
+ };
2241
+ "maxFiles": {
2242
+ "alias": "maxFiles";
1956
2243
  "required": false;
1957
2244
  "isSignal": true;
1958
2245
  };
@@ -1976,11 +2263,6 @@ export declare class BfSelectComponent<T = string> implements FormValueControl<T
1976
2263
  "required": false;
1977
2264
  "isSignal": true;
1978
2265
  };
1979
- "placeholder": {
1980
- "alias": "placeholder";
1981
- "required": false;
1982
- "isSignal": true;
1983
- };
1984
2266
  "helpText": {
1985
2267
  "alias": "helpText";
1986
2268
  "required": false;
@@ -2034,65 +2316,378 @@ export declare class BfSelectComponent<T = string> implements FormValueControl<T
2034
2316
  }, {
2035
2317
  "value": "valueChange";
2036
2318
  "changed": "changed";
2319
+ "rejected": "rejected";
2037
2320
  "focused": "focused";
2038
2321
  "blurred": "blurred";
2039
2322
  "touch": "touch";
2040
2323
  }, never, never, true, never>;
2041
2324
  }
2042
2325
  /**
2043
- * Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
2044
- * checkbox contract so `[formField]` binds `checked`.
2326
+ * The shape of a `bf-image-input` tile: a circle (`round`, for avatars) or a rounded square
2327
+ * (`square`, for logos and covers).
2328
+ *
2329
+ * @summary The two tile shapes of an image picker, a circle or a rounded square
2330
+ * @since 0.5.0
2331
+ */
2332
+ type BfImageInputShape = 'round' | 'square';
2333
+ /**
2334
+ * Single-image picker for an avatar, logo or cover: a round or square tile that shows the image
2335
+ * (or a camera glyph while empty), with the label, help text and Upload/Replace and Remove buttons
2336
+ * beside it. Clicking the tile or Upload opens the native picker; an image dropped on the tile is
2337
+ * taken too. `src` shows an image the app already has until the user picks a new one or removes
2338
+ * it. A pick that breaks `accept` or `maxSize` keeps the current image, shows the reason under the
2339
+ * buttons and is emitted through `rejected`. It only picks: uploading is the app's job. Bind
2340
+ * `[(value)]` directly or `[formField]` from a signal form.
2045
2341
  *
2046
2342
  * @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
2343
+ * <bf-image-input label="Profile picture" [(value)]="avatar" [(src)]="avatarUrl" />
2344
+ * <bf-image-input label="Logo" shape="square" [maxSize]="2097152" [formField]="company.logo" />
2345
+ * @summary Single-image picker with a round or square preview tile and replace or remove
2050
2346
  * @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. */
2347
+ * @since 0.5.0
2348
+ */
2349
+ export declare class BfImageInputComponent implements FormValueControl<File | null> {
2350
+ /** The picked image; two-way bindable and bound by `[formField]`. Null while none is picked. */
2351
+ readonly value: import("@angular/core").ModelSignal<File | null>;
2352
+ /** URL of an image the app already has (the current avatar), shown while `value` is null. Remove
2353
+ * sets it to null; two-way bindable. */
2354
+ readonly src: import("@angular/core").ModelSignal<string | null>;
2355
+ /** The tile's shape: `round` (a circle, the default) or `square` (a rounded square). */
2356
+ readonly shape: import("@angular/core").InputSignal<BfImageInputShape>;
2357
+ /** Tile size: sm 64, md 96, lg 128 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by
2358
+ * default). */
2359
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2360
+ /** The accepted file types, in the native `accept` syntax (`'image/png,image/jpeg'`); dropped
2361
+ * files are checked against it too. */
2362
+ readonly accept: import("@angular/core").InputSignal<string>;
2363
+ /** Largest file size in bytes; a larger image is rejected. Unset allows any size. */
2364
+ readonly maxSize: import("@angular/core").InputSignal<number | undefined>;
2365
+ /** Visible label beside the tile. */
2058
2366
  readonly label: import("@angular/core").InputSignal<string>;
2059
- /** Accessible name when there is no visible `label`, e.g. a row-selection box. */
2367
+ /** Accessible name of the tile when no visible label is rendered, in place of "Choose image".
2368
+ * Ignored when `label` is set. */
2060
2369
  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). */
2370
+ /** Id(s) of the element(s) that name the tile when no visible label is rendered. Ignored when
2371
+ * `label` is set. */
2063
2372
  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. */
2373
+ /** Helper text under the label. */
2374
+ readonly helpText: import("@angular/core").InputSignal<string>;
2375
+ /** Explicit error text; takes precedence over Signal Forms errors. */
2376
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
2377
+ /** Disables the control: the tile and buttons are disabled and drops are ignored. */
2071
2378
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2072
- /** Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. */
2379
+ /** Makes the control read-only: the image shows in a plain tile, with no buttons or drop. */
2073
2380
  readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2074
- /** Marks the control required. */
2381
+ /** Marks the control required (label marker). A `value` or a `src` fills it; see the docs for a
2382
+ * validator that counts `src`. */
2075
2383
  readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2076
2384
  /** Field validity from Signal Forms. */
2077
2385
  readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2078
- /** Whether the user has left the field. */
2386
+ /** Whether the user has left the field; errors show only once touched. */
2079
2387
  readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2080
- /** Validation errors from Signal Forms. */
2388
+ /** Validation errors from Signal Forms; the first message is shown once touched. */
2081
2389
  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. */
2390
+ /** Native name attribute; set, the hidden native file input carries the picked image, so a plain
2391
+ * HTML form post includes it. */
2085
2392
  readonly name: import("@angular/core").InputSignal<string>;
2086
- /** Control id; generated when not set. */
2393
+ /** Control id (the tile's); generated when not set. */
2087
2394
  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. */
2395
+ /** Emitted with the new image (or null after Remove) right after a user change, not on a model
2396
+ * write. */
2397
+ readonly changed: import("@angular/core").OutputEmitterRef<File | null>;
2398
+ /** Emitted once per pick or drop in which a file broke a rule, with those files. */
2399
+ readonly rejected: import("@angular/core").OutputEmitterRef<BfRejectedFile[]>;
2400
+ /** Emitted when the tile gains focus. */
2401
+ readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
2402
+ /** Emitted when the tile loses focus. */
2403
+ readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
2404
+ /** Emitted on blur of the tile or a button so Signal Forms marks the field touched. */
2091
2405
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
2092
- private readonly el;
2093
- private readonly labels;
2406
+ private readonly tile;
2407
+ private readonly native;
2408
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
2094
2409
  private readonly defaults;
2095
- /** The size in effect: `size`, else the app's `controlSize` default. */
2410
+ /** Whether a drag is over the tile. */
2411
+ protected readonly dragging: import("@angular/core").WritableSignal<boolean>;
2412
+ /** The reason the last pick or drop was refused, until the next one or Remove. */
2413
+ protected readonly rejection: import("@angular/core").WritableSignal<string>;
2414
+ /** The polite live message: the rejection reason, written after a pause. */
2415
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
2416
+ /** The pending write of the live message. */
2417
+ private announceTimer;
2418
+ /** The object URL of the picked image, with the file it was made for. */
2419
+ private readonly objectUrl;
2420
+ protected readonly uploadClasses: string;
2421
+ protected readonly removeClasses: string;
2422
+ /** The size in effect: `size`, else the app's `fieldSize` default. */
2423
+ protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
2424
+ /** Whether the user can pick, drop and remove. */
2425
+ protected readonly editable: import("@angular/core").Signal<boolean>;
2426
+ /** What the tile shows: the picked image's object URL, else `src`, else nothing. */
2427
+ protected readonly preview: import("@angular/core").Signal<string | null>;
2428
+ /** Whether an image (picked or existing) is set: Upload reads Replace and Remove shows. */
2429
+ protected readonly shows: import("@angular/core").Signal<boolean>;
2430
+ readonly errorText: import("@angular/core").Signal<string>;
2431
+ readonly showError: import("@angular/core").Signal<boolean>;
2432
+ /** `aria-describedby` of the tile: the label (the tile's name is the action), then the help and
2433
+ * error paragraphs. */
2434
+ readonly describedBy: import("@angular/core").Signal<string | null>;
2435
+ /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
2436
+ readonly errorMessageId: import("@angular/core").Signal<string | null>;
2437
+ /** The tile's `aria-labelledby`: `ariaLabelledby` while there is no visible label. */
2438
+ protected readonly tileLabelledby: import("@angular/core").Signal<string | null>;
2439
+ /** The tile's `aria-label`: "Choose image", or `ariaLabel` while there is no visible label. */
2440
+ protected readonly tileLabel: import("@angular/core").Signal<string | null>;
2441
+ /** The buttons' `aria-describedby`: the label, so "Remove" says what it removes. */
2442
+ protected readonly actionDescribedBy: import("@angular/core").Signal<string | null>;
2443
+ protected readonly tileClasses: import("@angular/core").Signal<string>;
2444
+ protected readonly glyphClasses: import("@angular/core").Signal<string>;
2445
+ constructor();
2446
+ /**
2447
+ * Focuses the tile; Signal Forms calls it from `focusBoundControl()`. Read-only has no button, so
2448
+ * nothing is focused.
2449
+ * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
2450
+ */
2451
+ focus(options?: FocusOptions): void;
2452
+ /**
2453
+ * Takes the native input's file, then empties it so picking the same file again fires `change`.
2454
+ * @internal
2455
+ */
2456
+ onPicked(input: HTMLInputElement): void;
2457
+ /**
2458
+ * Allows a drop on the tile and highlights it while the control is editable. Otherwise it still
2459
+ * cancels the event, so the browser does not open a dropped file and leave the page.
2460
+ * @internal
2461
+ */
2462
+ onDragOver(event: DragEvent): void;
2463
+ /**
2464
+ * Takes the dropped file through the same rules as a pick; ignores it (but still keeps the
2465
+ * browser from opening it) while the control cannot be edited.
2466
+ * @internal
2467
+ */
2468
+ onDrop(event: DragEvent): void;
2469
+ /**
2470
+ * Marks the field touched and emits `blurred`.
2471
+ * @internal
2472
+ */
2473
+ onBlur(event: FocusEvent): void;
2474
+ /**
2475
+ * Clears the picked and the existing image, then focuses the tile.
2476
+ * @internal
2477
+ */
2478
+ remove(): void;
2479
+ /** Runs `files` through the rules: the first accepted one replaces the image; a refusal keeps it
2480
+ * and shows the reason. */
2481
+ private take;
2482
+ /**
2483
+ * Writes `text` to the rejection line at once and announces it through the live region. The
2484
+ * region is emptied first and the text follows after `ANNOUNCE_DELAY`, so the same reason twice
2485
+ * is still announced again.
2486
+ */
2487
+ private showRejection;
2488
+ /** Keeps one object URL for `file`: makes it when the file changes, revokes the previous one. */
2489
+ private syncObjectUrl;
2490
+ /** With a `name`, puts the picked image on the hidden native input, so a plain form post sends
2491
+ * it. */
2492
+ private writeNativeFiles;
2493
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfImageInputComponent, never>;
2494
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfImageInputComponent, "bf-image-input", never, {
2495
+ "value": {
2496
+ "alias": "value";
2497
+ "required": false;
2498
+ "isSignal": true;
2499
+ };
2500
+ "src": {
2501
+ "alias": "src";
2502
+ "required": false;
2503
+ "isSignal": true;
2504
+ };
2505
+ "shape": {
2506
+ "alias": "shape";
2507
+ "required": false;
2508
+ "isSignal": true;
2509
+ };
2510
+ "size": {
2511
+ "alias": "size";
2512
+ "required": false;
2513
+ "isSignal": true;
2514
+ };
2515
+ "accept": {
2516
+ "alias": "accept";
2517
+ "required": false;
2518
+ "isSignal": true;
2519
+ };
2520
+ "maxSize": {
2521
+ "alias": "maxSize";
2522
+ "required": false;
2523
+ "isSignal": true;
2524
+ };
2525
+ "label": {
2526
+ "alias": "label";
2527
+ "required": false;
2528
+ "isSignal": true;
2529
+ };
2530
+ "ariaLabel": {
2531
+ "alias": "ariaLabel";
2532
+ "required": false;
2533
+ "isSignal": true;
2534
+ };
2535
+ "ariaLabelledby": {
2536
+ "alias": "ariaLabelledby";
2537
+ "required": false;
2538
+ "isSignal": true;
2539
+ };
2540
+ "helpText": {
2541
+ "alias": "helpText";
2542
+ "required": false;
2543
+ "isSignal": true;
2544
+ };
2545
+ "errorMessage": {
2546
+ "alias": "errorMessage";
2547
+ "required": false;
2548
+ "isSignal": true;
2549
+ };
2550
+ "disabled": {
2551
+ "alias": "disabled";
2552
+ "required": false;
2553
+ "isSignal": true;
2554
+ };
2555
+ "readonly": {
2556
+ "alias": "readonly";
2557
+ "required": false;
2558
+ "isSignal": true;
2559
+ };
2560
+ "required": {
2561
+ "alias": "required";
2562
+ "required": false;
2563
+ "isSignal": true;
2564
+ };
2565
+ "invalid": {
2566
+ "alias": "invalid";
2567
+ "required": false;
2568
+ "isSignal": true;
2569
+ };
2570
+ "touched": {
2571
+ "alias": "touched";
2572
+ "required": false;
2573
+ "isSignal": true;
2574
+ };
2575
+ "errors": {
2576
+ "alias": "errors";
2577
+ "required": false;
2578
+ "isSignal": true;
2579
+ };
2580
+ "name": {
2581
+ "alias": "name";
2582
+ "required": false;
2583
+ "isSignal": true;
2584
+ };
2585
+ "id": {
2586
+ "alias": "id";
2587
+ "required": false;
2588
+ "isSignal": true;
2589
+ };
2590
+ }, {
2591
+ "value": "valueChange";
2592
+ "src": "srcChange";
2593
+ "changed": "changed";
2594
+ "rejected": "rejected";
2595
+ "focused": "focused";
2596
+ "blurred": "blurred";
2597
+ "touch": "touch";
2598
+ }, never, never, true, never>;
2599
+ }
2600
+ /**
2601
+ * Equality that matches a bound value to an option's value (`compareWith` of the choice controls).
2602
+ * Called as `compareWith(optionValue, value)`: the option's value first, the bound value second.
2603
+ *
2604
+ * @summary Equality of choice values, for options that are objects rather than primitives
2605
+ * @since 0.3.0
2606
+ */
2607
+ type BfCompareWith<T> = (optionValue: T, value: T) => boolean;
2608
+ /**
2609
+ * Index of the option whose value matches `value` under `compare`; `-1` for `null` (nothing
2610
+ * selected) or a value no option matches. An option whose value is `null` therefore never shows as
2611
+ * selected.
2612
+ */
2613
+ declare function choiceIndex<T>(options: readonly {
2614
+ readonly value: T;
2615
+ }[], value: T | null, compare: BfCompareWith<T>): number;
2616
+ /**
2617
+ * The native `value` of an option (`<option>`, radio): `String(value)` for a string, number or
2618
+ * boolean, so native reads, form posts and browser tests see the real value; the option index for
2619
+ * anything else (objects). The controls map a choice back by position, never by this key, so
2620
+ * duplicate keys still resolve to the right option.
2621
+ */
2622
+ declare function choiceKey(value: unknown, index: number): string;
2623
+ /**
2624
+ * One choice of `bf-select`; `value` is any type (a string by default).
2625
+ *
2626
+ * @summary One choice of a select with its value, its label and a disabled flag
2627
+ * @since 0.1.0
2628
+ */
2629
+ interface BfSelectOption<T = string> {
2630
+ /** The value the select's `value` takes when this option is chosen. */
2631
+ value: T;
2632
+ /** The text of the native `option`. */
2633
+ label: string;
2634
+ /** Whether this option cannot be chosen. */
2635
+ disabled?: boolean;
2636
+ }
2637
+ /**
2638
+ * Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
2639
+ * checkbox contract so `[formField]` binds `checked`.
2640
+ *
2641
+ * @example
2642
+ * <bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
2643
+ * <bf-checkbox label="Notify me" [formField]="prefs.notify" />
2644
+ * @summary Rounded checkbox with an optional label that binds to Signal Forms
2645
+ * @category Forms
2646
+ * @since 0.1.0
2647
+ * @slot default Rich label content next to the box, before the `label` text.
2648
+ */
2649
+ export declare class BfCheckboxComponent implements FormCheckboxControl {
2650
+ /** Whether the box is checked; two-way bindable and bound by `[formField]`. */
2651
+ readonly checked: import("@angular/core").ModelSignal<boolean>;
2652
+ /** Label text; the default slot works too. */
2653
+ readonly label: import("@angular/core").InputSignal<string>;
2654
+ /** Accessible name when there is no visible `label`, e.g. a row-selection box. */
2655
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
2656
+ /** Id(s) of the element(s) that name the control when no visible label is rendered (e.g. a
2657
+ * table column header). */
2658
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
2659
+ /** Height step of the row: sm 32, md 40, lg 48 px (44 px under a coarse pointer for sm and md);
2660
+ * unset follows `provideBfDefaults()` (`controlSize`, `md` by default). */
2661
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
2662
+ /** Shows the mixed state (e.g. "select all" with some rows selected); two-way bindable. A user
2663
+ * change clears it, like the native checkbox, so the box and `aria-checked` agree. */
2664
+ readonly indeterminate: import("@angular/core").ModelSignal<boolean>;
2665
+ /** Disables the control. */
2666
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2667
+ /** Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. */
2668
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2669
+ /** Marks the control required. */
2670
+ readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2671
+ /** Field validity from Signal Forms. */
2672
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2673
+ /** Whether the user has left the field. */
2674
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
2675
+ /** Validation errors from Signal Forms. */
2676
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
2677
+ /** Explicit error text; takes precedence over Signal Forms errors. */
2678
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
2679
+ /** Native name attribute. */
2680
+ readonly name: import("@angular/core").InputSignal<string>;
2681
+ /** Control id; generated when not set. */
2682
+ readonly id: import("@angular/core").InputSignal<string>;
2683
+ /** Emitted with the new checked state after a user change. */
2684
+ readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
2685
+ /** Emitted on blur so Signal Forms marks the field touched. */
2686
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
2687
+ private readonly el;
2688
+ private readonly labels;
2689
+ private readonly defaults;
2690
+ /** The size in effect: `size`, else the app's `controlSize` default. */
2096
2691
  protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
2097
2692
  readonly errorText: import("@angular/core").Signal<string>;
2098
2693
  readonly showError: import("@angular/core").Signal<boolean>;
@@ -2939,6 +3534,8 @@ export declare class BfSegmentedComponent<T = string> implements FormValueContro
2939
3534
  readonly touch: import("@angular/core").OutputEmitterRef<void>;
2940
3535
  /** The rendered radio buttons, in option order; used to move focus after keyboard selection. */
2941
3536
  readonly radios: import("@angular/core").Signal<readonly ElementRef<HTMLButtonElement>[]>;
3537
+ private readonly track;
3538
+ private readonly indicator;
2942
3539
  /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
2943
3540
  readonly selectedIndex: import("@angular/core").Signal<number>;
2944
3541
  /** Index of the option that owns the roving tab stop: the selected enabled option, or the first
@@ -2963,6 +3560,12 @@ export declare class BfSegmentedComponent<T = string> implements FormValueContro
2963
3560
  private readonly mode;
2964
3561
  /** The classes of the scroller around the track. */
2965
3562
  protected readonly scrollerClasses: import("@angular/core").Signal<string>;
3563
+ /** Whether the sliding pill has been measured and placed, so it, not the item, paints the
3564
+ * selection. */
3565
+ private readonly placed;
3566
+ /** The classes of the sliding pill: hidden until placed and while nothing is selected, the
3567
+ * disabled outline on a disabled selected segment. */
3568
+ protected readonly indicatorClasses: import("@angular/core").Signal<string>;
2966
3569
  readonly trackClasses: import("@angular/core").Signal<string>;
2967
3570
  /**
2968
3571
  * The classes of the segment at `index`.
@@ -3379,45 +3982,209 @@ export declare class BfCardComponent implements BfActionHost {
3379
3982
  }, never, ["[bfSlot=header]", "*", "[bfSlot=footer]"], true, never>;
3380
3983
  }
3381
3984
  /**
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.
3985
+ * The aspect ratio of a `bf-media-card`'s image area: 16:9 (default), 4:3, square or 3:4.
3390
3986
  *
3391
- * @summary The four fill colours of a progress bar, from primary to danger
3392
- * @since 0.1.0
3987
+ * @summary The four aspect ratios of a media card's image area, wide to portrait
3988
+ * @since 0.5.0
3393
3989
  */
3394
- type BfProgressTone = 'primary' | 'success' | 'warning' | 'danger';
3990
+ type BfMediaCardAspect = '16/9' | '4/3' | '1/1' | '3/4';
3991
+ /** What the image area shows. */
3992
+ type MediaState = 'empty' | 'loading' | 'loaded' | 'failed';
3395
3993
  /**
3396
- * Progress bar for completion and quotas, determinate by default, or `indeterminate` while the
3397
- * amount of work is unknown.
3994
+ * Card for a picture and its story: a game pitch, a generated image, a product. The top is an
3995
+ * image area at a fixed aspect ratio that shows a shimmering skeleton while the picture loads
3996
+ * (also while it does not exist yet: set `loading`), fades the picture in when it arrives, and
3997
+ * shows a message with a Retry slot when it fails. Below it sit a body and a footer. Make the card
3998
+ * clickable with a `bfAction` link or button in its title, as on `bf-card`, and selectable with
3999
+ * `selectable` and `[(selected)]`.
3398
4000
  *
3399
4001
  * @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
4002
+ * <bf-media-card [src]="pitch.cover" alt="Neon city skyline" loadingLabel="Drawing…" selectable [(selected)]="picked">
4003
+ * <h3 class="text-heading"><a bfAction [routerLink]="['/pitches', pitch.id]">Starfall</a></h3>
4004
+ * <p class="text-body text-text-secondary">A roguelike in a falling city.</p>
4005
+ * <bf-button bfSlot="failed-action" size="sm" variant="tonal" (clicked)="retry()">Retry</bf-button>
4006
+ * </bf-media-card>
4007
+ * @summary Card with an image that loads, fades in or fails, plus a body and actions
3403
4008
  * @category Data display
3404
- * @since 0.1.0
4009
+ * @since 0.5.0
4010
+ * @slot default The card body: a title (with an optional bfAction link or button) and text.
4011
+ * @slot footer Content below the body, such as chips and buttons; it sits on the card's bottom edge.
4012
+ * @slot failed-action An action shown under the failure message, such as a Retry button.
3405
4013
  */
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"). */
4014
+ export declare class BfMediaCardComponent implements BfActionHost {
4015
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
4016
+ /** URL of the image; empty shows the skeleton while `loading`, else an empty area. A new URL
4017
+ * starts over: skeleton, fade-in, and a failed load is forgotten. */
4018
+ readonly src: import("@angular/core").InputSignal<string>;
4019
+ /** Alternative text of the image; empty marks it decorative. */
4020
+ readonly alt: import("@angular/core").InputSignal<string>;
4021
+ /** Aspect ratio of the image area: `16/9` (default), `4/3`, `1/1` or `3/4`. */
4022
+ readonly aspect: import("@angular/core").InputSignal<BfMediaCardAspect>;
4023
+ /** Shows the skeleton while there is no `src` yet (a picture that is still being made). */
4024
+ readonly loading: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4025
+ /** Shows the failure message (the app could not make the picture); the app clears it. A load
4026
+ * error of `src` shows it too, until `src` changes. */
4027
+ readonly failed: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4028
+ /** Caption shown over the skeleton while loading ("Drawing…"); empty shows none and tells
4029
+ * screen readers `provideBfLabels()`'s `loading` ("Loading"). */
4030
+ readonly loadingLabel: import("@angular/core").InputSignal<string>;
4031
+ /** Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`. */
4032
+ readonly failedLabel: import("@angular/core").InputSignal<string>;
4033
+ /** Adds a select button (a toggle with `aria-pressed`): the whole card toggles `selected`,
4034
+ * or, with a `bfAction` in the card, a round check button in the image's corner does. */
4035
+ readonly selectable: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4036
+ /** Whether the card is selected (a ring around it and a filled check); two-way bindable. Drawn
4037
+ * only on a `selectable` card. */
4038
+ readonly selected: import("@angular/core").ModelSignal<boolean>;
4039
+ /** The card's name in its select button ("Select Starfall"); empty names it by the body's text. */
3417
4040
  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. */
4041
+ /** The user selected or cleared the card; emits the new `selected`. */
4042
+ readonly changed: import("@angular/core").OutputEmitterRef<boolean>;
4043
+ /** The image of `src` has loaded. */
4044
+ readonly loaded: import("@angular/core").OutputEmitterRef<void>;
4045
+ /** The image of `src` failed to load (the card shows the failure message). */
4046
+ readonly errored: import("@angular/core").OutputEmitterRef<void>;
4047
+ private readonly actions;
4048
+ private readonly actionFocusVisible;
4049
+ private readonly selectFocusVisible;
4050
+ protected readonly bodyId: string;
4051
+ protected readonly selectTextId: string;
4052
+ protected readonly labelId: string;
4053
+ protected readonly skeletonLayerClasses = "absolute inset-0 transition-opacity duration-slow ease-md motion-reduce:transition-none";
4054
+ /** The image's own progress, reset by every new `src`. */
4055
+ private readonly imageStatus;
4056
+ /** True once the skeleton has faded out under a loaded image; reset by every new `src`. */
4057
+ private readonly skeletonGone;
4058
+ protected readonly state: import("@angular/core").Signal<MediaState>;
4059
+ /** The image, keyed by URL so a new `src` gets a fresh element (and a fresh fade). */
4060
+ protected readonly imageSources: import("@angular/core").Signal<string[]>;
4061
+ protected readonly skeletonShown: import("@angular/core").Signal<boolean>;
4062
+ /** The body names the select button (no `label`): only then does it carry its id, so a plain
4063
+ * card renders no counter-made id. */
4064
+ protected readonly bodyNamesSelect: import("@angular/core").Signal<boolean>;
4065
+ protected readonly failedText: import("@angular/core").Signal<string>;
4066
+ /** The select button stretches over the card unless the card has its own bfAction. */
4067
+ private readonly selectMode;
4068
+ private readonly cue;
4069
+ protected readonly classes: import("@angular/core").Signal<string>;
4070
+ protected readonly mediaClasses: import("@angular/core").Signal<string>;
4071
+ protected readonly selectClasses: import("@angular/core").Signal<string>;
4072
+ protected readonly discClasses: import("@angular/core").Signal<string>;
4073
+ /** @internal Called by a `bfAction` in the card. */
4074
+ registerAction(): () => void;
4075
+ /** @internal Called by the card's `bfAction` on focus and blur. */
4076
+ setActionFocusVisible(visible: boolean): void;
4077
+ /** @internal The image's load event. */
4078
+ imageLoaded(): void;
4079
+ /** @internal The image's error event. */
4080
+ imageFailed(): void;
4081
+ /** @internal The skeleton layer's fade-out ended: it leaves the DOM (its shimmer stops). */
4082
+ skeletonFaded(event: TransitionEvent): void;
4083
+ /** @internal The select button was clicked. */
4084
+ toggle(): void;
4085
+ /** @internal The select button took focus. */
4086
+ selectFocused(event: FocusEvent): void;
4087
+ /** @internal The select button lost focus. */
4088
+ selectBlurred(): void;
4089
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfMediaCardComponent, never>;
4090
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfMediaCardComponent, "bf-media-card", never, {
4091
+ "src": {
4092
+ "alias": "src";
4093
+ "required": false;
4094
+ "isSignal": true;
4095
+ };
4096
+ "alt": {
4097
+ "alias": "alt";
4098
+ "required": false;
4099
+ "isSignal": true;
4100
+ };
4101
+ "aspect": {
4102
+ "alias": "aspect";
4103
+ "required": false;
4104
+ "isSignal": true;
4105
+ };
4106
+ "loading": {
4107
+ "alias": "loading";
4108
+ "required": false;
4109
+ "isSignal": true;
4110
+ };
4111
+ "failed": {
4112
+ "alias": "failed";
4113
+ "required": false;
4114
+ "isSignal": true;
4115
+ };
4116
+ "loadingLabel": {
4117
+ "alias": "loadingLabel";
4118
+ "required": false;
4119
+ "isSignal": true;
4120
+ };
4121
+ "failedLabel": {
4122
+ "alias": "failedLabel";
4123
+ "required": false;
4124
+ "isSignal": true;
4125
+ };
4126
+ "selectable": {
4127
+ "alias": "selectable";
4128
+ "required": false;
4129
+ "isSignal": true;
4130
+ };
4131
+ "selected": {
4132
+ "alias": "selected";
4133
+ "required": false;
4134
+ "isSignal": true;
4135
+ };
4136
+ "label": {
4137
+ "alias": "label";
4138
+ "required": false;
4139
+ "isSignal": true;
4140
+ };
4141
+ }, {
4142
+ "selected": "selectedChange";
4143
+ "changed": "changed";
4144
+ "loaded": "loaded";
4145
+ "errored": "errored";
4146
+ }, never, ["[bfSlot=failed-action]", "*", "[bfSlot=footer]"], true, never>;
4147
+ }
4148
+ /**
4149
+ * The track height of a `bf-progress`: sm or md.
4150
+ *
4151
+ * @summary The two track heights of a progress bar, thin and regular
4152
+ * @since 0.1.0
4153
+ */
4154
+ type BfProgressSize = 'sm' | 'md';
4155
+ /**
4156
+ * The fill colour of a `bf-progress`: primary, success, warning or danger.
4157
+ *
4158
+ * @summary The four fill colours of a progress bar, from primary to danger
4159
+ * @since 0.1.0
4160
+ */
4161
+ type BfProgressTone = 'primary' | 'success' | 'warning' | 'danger';
4162
+ /**
4163
+ * Progress bar for completion and quotas, determinate by default, or `indeterminate` while the
4164
+ * amount of work is unknown.
4165
+ *
4166
+ * @example
4167
+ * <bf-progress [value]="68" label="Atlas rebrand" valueText="12 of 18 tasks" />
4168
+ * <bf-progress indeterminate label="Uploading" />
4169
+ * @summary Progress bar in two sizes and four tones for completion, quotas and unknown waits
4170
+ * @category Data display
4171
+ * @since 0.1.0
4172
+ */
4173
+ export declare class BfProgressComponent {
4174
+ /** Completion in percent; clamped to 0–100. Without it (and without `indeterminate`) the bar
4175
+ * shows 0 %. Ignored while `indeterminate`. */
4176
+ readonly value: import("@angular/core").InputSignal<number | undefined>;
4177
+ /** The amount of work is unknown: the native bar carries no value (assistive tech announces it
4178
+ * as busy) and a stripe runs across the track, a static 40 % bar under reduced motion. */
4179
+ readonly indeterminate: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
4180
+ /** The value in words for assistive tech (`aria-valuetext`), such as "12 of 18 tasks"; the
4181
+ * percentage is announced while it is empty. */
4182
+ readonly valueText: import("@angular/core").InputSignal<string>;
4183
+ /** Accessible name of the bar ("Atlas rebrand"). */
4184
+ readonly label: import("@angular/core").InputSignal<string>;
4185
+ /** Bar height: sm 8 px (default), md 10 px. */
4186
+ readonly size: import("@angular/core").InputSignal<BfProgressSize>;
4187
+ /** Fill color by meaning. */
3421
4188
  readonly tone: import("@angular/core").InputSignal<BfProgressTone>;
3422
4189
  readonly percent: import("@angular/core").Signal<number>;
3423
4190
  readonly classes: import("@angular/core").Signal<string>;
@@ -4056,6 +4823,61 @@ export declare class BfActionDirective {
4056
4823
  static ɵfac: i0.ɵɵFactoryDeclaration<BfActionDirective, never>;
4057
4824
  static ɵdir: i0.ɵɵDirectiveDeclaration<BfActionDirective, "a[bfAction], button[bfAction]", never, {}, {}, never, never, true, never>;
4058
4825
  }
4826
+ /**
4827
+ * Fades an image in when it has loaded instead of letting it pop in line by line: put
4828
+ * `bfFadeIn` on the `<img>`. The image stays transparent until its `load` (or `error`) event,
4829
+ * then fades in over 260 ms (`duration-slow`), at once under `prefers-reduced-motion: reduce`. An
4830
+ * image that is already loaded when it renders (from the cache) shows straight away. Reserve its
4831
+ * space (`width`/`height` attributes or an aspect ratio) so the layout does not move when it
4832
+ * arrives; for a placeholder while it loads, put a `bf-skeleton` behind it.
4833
+ *
4834
+ * @example
4835
+ * <img bfFadeIn [src]="cover.url" alt="Game cover" width="640" height="360" />
4836
+ * @summary Fades an image in once it has loaded instead of popping in
4837
+ * @since 0.5.0
4838
+ */
4839
+ export declare class BfFadeInDirective {
4840
+ private readonly image;
4841
+ /** True once the image has loaded or failed: it is opaque from then on. */
4842
+ protected readonly shown: import("@angular/core").WritableSignal<boolean>;
4843
+ constructor();
4844
+ /**
4845
+ * Shows the image: its load or error event.
4846
+ *
4847
+ * @internal
4848
+ */
4849
+ show(): void;
4850
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfFadeInDirective, never>;
4851
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfFadeInDirective, "img[bfFadeIn]", never, {}, {}, never, never, true, never>;
4852
+ }
4853
+ /**
4854
+ * Glides the items of an app's list or grid to their new place when items are added, removed or
4855
+ * reordered, instead of letting them jump (the FLIP technique). Put `bfMotionList` on the element
4856
+ * whose direct children are the items, usually the one around an `@for`. Each item remembers
4857
+ * where it was; after the DOM changes, every item that moved slides from its old place to its new
4858
+ * one over 200 ms (`--bf-duration-layout`, on the settle curve). Pair it with Angular's
4859
+ * `animate.enter="motion-safe:animate-item-enter"` and
4860
+ * `animate.leave="motion-safe:animate-item-leave"` on the items, so new items fade and rise in and
4861
+ * removed ones fade out before the rest close the gap. Nothing moves under
4862
+ * `prefers-reduced-motion: reduce`, and nothing glides when the container is resized. It moves
4863
+ * items within one container: an item moved to another list leaves one and enters the other.
4864
+ *
4865
+ * @example
4866
+ * <ul bfMotionList class="grid grid-cols-3 gap-4">
4867
+ * @for (card of cards(); track card.id) {
4868
+ * <li animate.enter="motion-safe:animate-item-enter" animate.leave="motion-safe:animate-item-leave">
4869
+ * <app-card [card]="card" />
4870
+ * </li>
4871
+ * }
4872
+ * </ul>
4873
+ * @summary Glides the items of a list or grid to their new place when it changes
4874
+ * @since 0.5.0
4875
+ */
4876
+ export declare class BfMotionListDirective {
4877
+ constructor();
4878
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfMotionListDirective, never>;
4879
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfMotionListDirective, "[bfMotionList]", never, {}, {}, never, never, true, never>;
4880
+ }
4059
4881
  /** What a `bf-tab-panel` reads from its `bf-tab-group`; a token so the two files never import each
4060
4882
  * other. Internal: not exported from the package. */
4061
4883
  interface BfTabGroupHost {
@@ -4108,6 +4930,10 @@ export declare class BfTabPanelComponent {
4108
4930
  readonly isSelected: import("@angular/core").Signal<boolean>;
4109
4931
  /** Latches once the panel has been shown. */
4110
4932
  private readonly visited;
4933
+ /** True while this panel is shown because the selection moved to it (not the panel shown on
4934
+ * the first render): it fades in. */
4935
+ protected readonly switchedTo: import("@angular/core").WritableSignal<boolean>;
4936
+ protected readonly motionClasses: import("@angular/core").Signal<"" | "motion-safe:animate-panel-in">;
4111
4937
  /** Whether the `ng-template` content exists: while selected, or once visited with `keepAlive`.
4112
4938
  * `visited` is read first on every run, so the latch sees each selection. */
4113
4939
  protected readonly rendered: import("@angular/core").Signal<boolean>;
@@ -4173,6 +4999,8 @@ export declare class BfTabGroupComponent implements BfTabGroupHost {
4173
4999
  /** The `bf-tab-panel` children, in document order. */
4174
5000
  readonly panels: import("@angular/core").Signal<readonly BfTabPanelComponent[]>;
4175
5001
  private readonly tabs;
5002
+ private readonly list;
5003
+ private readonly indicator;
4176
5004
  private readonly labels;
4177
5005
  private readonly defaults;
4178
5006
  /** The size in effect: `size`, else the app's `controlSize` default. */
@@ -4187,8 +5015,14 @@ export declare class BfTabGroupComponent implements BfTabGroupHost {
4187
5015
  /** The panel whose tab owns the roving tab stop: the shown one, else (it is disabled) the first
4188
5016
  * enabled one. */
4189
5017
  protected readonly tabStop: import("@angular/core").Signal<BfTabPanelComponent | undefined>;
5018
+ /** Whether the sliding underline has been measured and placed, so it, not the tab, draws the
5019
+ * selection. */
5020
+ private readonly placed;
5021
+ /** The classes of the sliding underline: hidden until placed, `text-disabled` on a disabled
5022
+ * selected tab. */
5023
+ protected readonly indicatorClasses: import("@angular/core").Signal<string>;
4190
5024
  protected readonly scrollerClasses = "-m-1 overflow-x-auto p-1";
4191
- protected readonly listClasses = "flex w-max min-w-full border-b border-border";
5025
+ protected readonly listClasses = "relative flex w-max min-w-full border-b border-border";
4192
5026
  protected readonly badgeClasses = "flex h-5 min-w-5 shrink-0 items-center justify-center rounded-control bg-primary px-1.5 text-micro font-bold text-on-primary";
4193
5027
  /**
4194
5028
  * The classes of a panel's tab.
@@ -4302,7 +5136,7 @@ export declare class BfPageHeaderComponent {
4302
5136
  * @summary Dense row of search, filters, view switch, result count and actions
4303
5137
  * @category Actions
4304
5138
  * @since 0.3.1
4305
- * @slot default The search field and the filters (bf-input, bf-select, bf-chip-group), in order.
5139
+ * @slot default The search field and the filters (bf-input, bf-chip-group, bf-select from /overlay), in order.
4306
5140
  * @slot view A view switch after the filters, such as a bf-segmented of list and grid.
4307
5141
  * @slot actions The trailing actions, kept together at the inline end of the row, beside the status.
4308
5142
  */
@@ -4508,6 +5342,11 @@ export declare class BfTableComponent<T = unknown> {
4508
5342
  protected readonly labels: import("@bitforgehq/ui").BfLabels;
4509
5343
  private readonly defaults;
4510
5344
  private readonly table;
5345
+ /** The Sort by select from `provideBfSelect()` (`BF_TABLE_SORT_SELECT`); null renders the
5346
+ * native one. */
5347
+ protected readonly sortSelectType: import("@angular/core").Type<unknown> | null;
5348
+ /** Where the provided Sort by select goes; set only while the stack bar shows it. */
5349
+ private readonly sortSelectAnchor;
4511
5350
  private readonly cells;
4512
5351
  private readonly id;
4513
5352
  /** The caption's id, which names the table while it can stack. */
@@ -4548,6 +5387,7 @@ export declare class BfTableComponent<T = unknown> {
4548
5387
  protected readonly selectBodyClasses: import("@angular/core").Signal<string>;
4549
5388
  /** A body cell's height step, with the coarse-pointer height unless the table stacks there. */
4550
5389
  private readonly bodyHeight;
5390
+ constructor();
4551
5391
  /**
4552
5392
  * The classes of a column's header cell.
4553
5393
  * @internal
@@ -4669,6 +5509,19 @@ export declare class BfTableComponent<T = unknown> {
4669
5509
  "selection": "selectionChange";
4670
5510
  }, ["cells"], ["[bfSlot=empty]"], true, never>;
4671
5511
  }
5512
+ /**
5513
+ * The component a stacked `bf-table` renders as its Sort by select; `null` (the default) keeps the
5514
+ * built-in native `<select>`. `provideBfSelect()` from `@bitforgehq/ui/overlay` sets it to the
5515
+ * styled `bf-select`, so the core entry never imports the overlay entry. A component given here
5516
+ * must take the inputs `label` (string), `options` (`BfSelectOption<string>[]`) and `value`
5517
+ * (string) and emit the picked value from the output `changed`. The table adds its layout classes
5518
+ * to the component's host with `classList`, so the component must not overwrite its host
5519
+ * `className` or `class` attribute directly.
5520
+ *
5521
+ * @summary The component a stacked table renders as its Sort by select
5522
+ * @since 0.5.0
5523
+ */
5524
+ export declare const BF_TABLE_SORT_SELECT: InjectionToken<Type<unknown> | null>;
4672
5525
  /**
4673
5526
  * The layout of a `bf-description-list`: `rows` (label beside value, divided) or `grid` (tiles).
4674
5527
  *
@@ -5132,7 +5985,11 @@ export declare class BfSidebarComponent {
5132
5985
  * screen readers and becomes a tooltip. A `bf-sidebar-toggle` inside flips it; bind
5133
5986
  * `[(collapsed)]` to keep it in your state. */
5134
5987
  readonly collapsed: import("@angular/core").ModelSignal<boolean>;
5988
+ private readonly defaults;
5135
5989
  private readonly labels;
5990
+ /** True after the first render: from then on collapsing and expanding animate.
5991
+ * @internal */
5992
+ readonly animated: import("@angular/core").WritableSignal<boolean>;
5136
5993
  /** The `nav` element's id, which `bf-sidebar-toggle` points `aria-controls` at.
5137
5994
  * @internal */
5138
5995
  readonly navId: string;
@@ -5140,7 +5997,7 @@ export declare class BfSidebarComponent {
5140
5997
  protected readonly name: import("@angular/core").Signal<string>;
5141
5998
  private readonly width;
5142
5999
  readonly hostClasses: import("@angular/core").Signal<string>;
5143
- protected readonly regionClasses: import("@angular/core").Signal<string>;
6000
+ constructor();
5144
6001
  static ɵfac: i0.ɵɵFactoryDeclaration<BfSidebarComponent, never>;
5145
6002
  static ɵcmp: i0.ɵɵComponentDeclaration<BfSidebarComponent, "bf-sidebar", never, {
5146
6003
  "label": {
@@ -5210,7 +6067,6 @@ export declare class BfNavGroupComponent {
5210
6067
  /** The heading's id, referenced by the group's `aria-labelledby`. */
5211
6068
  protected readonly headingId: string;
5212
6069
  private readonly form;
5213
- readonly hostClasses: import("@angular/core").Signal<string>;
5214
6070
  protected readonly headingClasses: import("@angular/core").Signal<string>;
5215
6071
  static ɵfac: i0.ɵɵFactoryDeclaration<BfNavGroupComponent, never>;
5216
6072
  static ɵcmp: i0.ɵɵComponentDeclaration<BfNavGroupComponent, "bf-nav-group", never, {
@@ -5257,11 +6113,17 @@ export declare class BfNavItemComponent {
5257
6113
  /** True while the enclosing sidebar is collapsed to an icon rail. */
5258
6114
  protected readonly collapsed: import("@angular/core").Signal<boolean>;
5259
6115
  private readonly form;
6116
+ /** True in a sidebar (or on its own), where the item can collapse to an icon rail. */
6117
+ protected readonly railable: import("@angular/core").Signal<boolean>;
6118
+ /** True once the enclosing sidebar has rendered: collapsing and expanding animate. */
6119
+ private readonly animated;
5260
6120
  /** The size in effect: `size`, else the app's `controlSize` default. */
5261
6121
  private readonly resolvedSize;
5262
6122
  readonly classes: import("@angular/core").Signal<string>;
6123
+ private readonly fade;
5263
6124
  protected readonly labelClasses: import("@angular/core").Signal<string>;
5264
6125
  protected readonly badgeClasses: import("@angular/core").Signal<string>;
6126
+ protected readonly dotClasses: import("@angular/core").Signal<string>;
5265
6127
  /** The badge as screen readers hear it after the label. */
5266
6128
  protected readonly badgeAnnouncement: import("@angular/core").Signal<string>;
5267
6129
  /** Collapsed, the label as the pointer tooltip; else the consumer's own `title`, if any. */
@@ -6014,6 +6876,215 @@ export declare class BfStepComponent {
6014
6876
  };
6015
6877
  }, {}, never, ["*"], true, never>;
6016
6878
  }
6879
+ /** What a `bf-chat-message` asks of the `bf-chat-thread` around it. */
6880
+ interface BfChatThreadHost {
6881
+ /** True once the thread's first render is done: a message created after that animates in, the
6882
+ * ones the thread opened with do not. */
6883
+ hasSettled(): boolean;
6884
+ }
6885
+ /**
6886
+ * Provided by `bf-chat-thread` so a `bf-chat-message` knows whether it arrived after the first
6887
+ * render, without `chat-message.component.ts` importing `chat-thread.component.ts`.
6888
+ */
6889
+ declare const BF_CHAT_THREAD: InjectionToken<BfChatThreadHost>;
6890
+ /**
6891
+ * The scrolling message list of a chat: a `role="log"` around `bf-chat-message` elements that
6892
+ * stays pinned to the newest message while text arrives. When the user scrolls up it stops
6893
+ * following and shows a "Jump to latest" button, which scrolls back down (smoothly, at once under
6894
+ * reduced motion) and follows again. While a message streams the log is `aria-busy`, so a screen
6895
+ * reader announces the reply once it is complete, not piece by piece. Messages added after the
6896
+ * first render rise and fade in; the ones it opens with do not. Give it a height (or `flex-1` in a
6897
+ * column): it scrolls inside.
6898
+ *
6899
+ * @example
6900
+ * <bf-chat-thread class="h-[32rem]">
6901
+ * @for (message of messages(); track message.id) {
6902
+ * <bf-chat-message [role]="message.role" [streaming]="message.streaming">{{ message.text }}</bf-chat-message>
6903
+ * }
6904
+ * </bf-chat-thread>
6905
+ * @summary Scrolling chat message log that follows new text and offers a jump to latest
6906
+ * @category Data display
6907
+ * @since 0.5.0
6908
+ * @slot default The messages, each a `bf-chat-message` (in order, oldest first).
6909
+ * @slot jump-icon An icon before the "Jump to latest" text, such as an arrow down.
6910
+ */
6911
+ export declare class BfChatThreadComponent implements BfChatThreadHost {
6912
+ protected readonly labels: import("@bitforgehq/ui").BfLabels;
6913
+ private readonly document;
6914
+ /** The log's accessible name; empty uses the `chatThread` label ("Conversation"). */
6915
+ readonly label: import("@angular/core").InputSignal<string>;
6916
+ private readonly scroller;
6917
+ private readonly content;
6918
+ private readonly messages;
6919
+ /** Pinned to the newest message: new text scrolls the log down. */
6920
+ protected readonly following: import("@angular/core").WritableSignal<boolean>;
6921
+ /** Bumped when the content or the log changes size, so the follow scroll runs again. */
6922
+ private readonly resized;
6923
+ private settled;
6924
+ /** A smooth jump is under way: its own scroll events neither stop nor restart the follow. */
6925
+ private jumping;
6926
+ private jumpTimer;
6927
+ /** A finger is on the log: the follow does not move the content under it. */
6928
+ protected touching: boolean;
6929
+ protected readonly name: import("@angular/core").Signal<string>;
6930
+ /** A message is still streaming, so the log holds its announcement until it is done. */
6931
+ protected readonly busy: import("@angular/core").Signal<boolean>;
6932
+ protected readonly jumpClasses: string;
6933
+ constructor();
6934
+ /**
6935
+ * True once the first render is done, so only later messages animate in.
6936
+ * @internal
6937
+ */
6938
+ hasSettled(): boolean;
6939
+ /** Scrolls to the newest message (smoothly, at once under reduced motion) and follows it again. */
6940
+ jumpToLatest(): void;
6941
+ /** @internal */
6942
+ onScroll(): void;
6943
+ /** @internal */
6944
+ onWheel(event: WheelEvent): void;
6945
+ /** @internal */
6946
+ onKeydown(event: KeyboardEvent): void;
6947
+ /**
6948
+ * Ends a smooth jump and catches up with text that arrived during it.
6949
+ * @internal
6950
+ */
6951
+ endJump(): void;
6952
+ /** The user scrolls up: stop at once, so the next follow scroll cannot pull the log back. */
6953
+ private stopFollowing;
6954
+ private clearJumpTimer;
6955
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfChatThreadComponent, never>;
6956
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfChatThreadComponent, "bf-chat-thread", never, {
6957
+ "label": {
6958
+ "alias": "label";
6959
+ "required": false;
6960
+ "isSignal": true;
6961
+ };
6962
+ }, {}, ["messages"], ["*", "[bfSlot=jump-icon]"], true, never>;
6963
+ }
6964
+ /**
6965
+ * Who wrote a `bf-chat-message`: `user` (a tinted bubble at the inline end) or `assistant` (full
6966
+ * width, no bubble).
6967
+ *
6968
+ * @summary Who wrote a chat message, the user or the assistant
6969
+ * @since 0.5.0
6970
+ */
6971
+ type BfChatMessageRole = 'user' | 'assistant';
6972
+ /**
6973
+ * How a `bf-chat-message` ended: `done` (the default, no status line), `stopped` (the user
6974
+ * stopped the reply) or `failed` (it did not arrive or was not sent).
6975
+ *
6976
+ * @summary How a chat message ended: done, stopped by the user, or failed
6977
+ * @since 0.5.0
6978
+ */
6979
+ type BfChatMessageStatus = 'done' | 'stopped' | 'failed';
6980
+ /**
6981
+ * One message of a `bf-chat-thread`. A `user` message is a tinted bubble at the inline end; an
6982
+ * `assistant` message is full width with no bubble. The default slot is the message text, styled
6983
+ * as prose (`bfProse`) for markdown the app renders. An optional header names the `author` and
6984
+ * the `time`. While `streaming`, a caret pulses at the end of the text and the message is
6985
+ * `aria-busy`, so the thread announces it once, when it is done. A `stopped` or `failed` message
6986
+ * shows a short status line, with the `action` slot beside it (a Retry button).
6987
+ *
6988
+ * @example
6989
+ * <bf-chat-message role="assistant" author="Assistant" [streaming]="writing()">
6990
+ * <div [innerHTML]="replyHtml()"></div>
6991
+ * </bf-chat-message>
6992
+ * <bf-chat-message role="user" author="You" time="2026-11-06T09:30:00Z">Three names for a cafe?</bf-chat-message>
6993
+ * @summary One chat message, a user bubble or a full-width assistant reply that can stream
6994
+ * @category Data display
6995
+ * @since 0.5.0
6996
+ * @slot default The message text: plain text or the app's rendered markdown, styled as prose.
6997
+ * @slot content Inline content below the text, such as cards, images or chips (not styled as prose).
6998
+ * @slot action Beside the status line, such as a Retry button.
6999
+ */
7000
+ export declare class BfChatMessageComponent {
7001
+ private readonly labels;
7002
+ private readonly appLocale;
7003
+ private readonly thread;
7004
+ /** Who wrote it: `user` (a bubble at the inline end) or `assistant` (full width, no bubble). */
7005
+ readonly role: import("@angular/core").InputSignal<BfChatMessageRole>;
7006
+ /** Whose message it is, shown in the header, such as "You" or the assistant's name; empty
7007
+ * shows none. */
7008
+ readonly author: import("@angular/core").InputSignal<string>;
7009
+ /** When it was written: a Date or an ISO 8601 string, shown as hours and minutes; empty or
7010
+ * invalid shows no time. */
7011
+ readonly time: import("@angular/core").InputSignal<string | Date | null>;
7012
+ /** Shown in place of the formatted time, such as "just now"; the `time` element's `datetime`
7013
+ * keeps the exact value. */
7014
+ readonly timeText: import("@angular/core").InputSignal<string>;
7015
+ /** The text is still arriving: a caret pulses at its end and the message is `aria-busy`. */
7016
+ readonly streaming: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7017
+ /** How it ended: `done` (no status line), `stopped` or `failed`. */
7018
+ readonly status: import("@angular/core").InputSignal<BfChatMessageStatus>;
7019
+ /** Shown in place of the built-in status line (`chatMessageStopped`, `chatMessageFailed`),
7020
+ * such as the error the server returned. */
7021
+ readonly statusMessage: import("@angular/core").InputSignal<string>;
7022
+ /** Rises and fades in: only for a message added to a thread after its first render. */
7023
+ protected readonly entering: import("@angular/core").WritableSignal<boolean>;
7024
+ private readonly date;
7025
+ /** The `time` element's machine value: the ISO string as given, a Date's ISO string, else null. */
7026
+ protected readonly datetime: import("@angular/core").Signal<string | null>;
7027
+ /** The visible time: `timeText`, else the time formatted as hours and minutes. */
7028
+ protected readonly timeLabel: import("@angular/core").Signal<string>;
7029
+ /** The status line's text: `statusMessage`, else the label of `stopped` or `failed`. */
7030
+ protected readonly statusLine: import("@angular/core").Signal<string>;
7031
+ protected readonly statusClasses: import("@angular/core").Signal<string>;
7032
+ protected readonly hostClasses: import("@angular/core").Signal<string>;
7033
+ protected readonly bodyClasses: import("@angular/core").Signal<string>;
7034
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfChatMessageComponent, never>;
7035
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfChatMessageComponent, "bf-chat-message", never, {
7036
+ "role": {
7037
+ "alias": "role";
7038
+ "required": false;
7039
+ "isSignal": true;
7040
+ };
7041
+ "author": {
7042
+ "alias": "author";
7043
+ "required": false;
7044
+ "isSignal": true;
7045
+ };
7046
+ "time": {
7047
+ "alias": "time";
7048
+ "required": false;
7049
+ "isSignal": true;
7050
+ };
7051
+ "timeText": {
7052
+ "alias": "timeText";
7053
+ "required": false;
7054
+ "isSignal": true;
7055
+ };
7056
+ "streaming": {
7057
+ "alias": "streaming";
7058
+ "required": false;
7059
+ "isSignal": true;
7060
+ };
7061
+ "status": {
7062
+ "alias": "status";
7063
+ "required": false;
7064
+ "isSignal": true;
7065
+ };
7066
+ "statusMessage": {
7067
+ "alias": "statusMessage";
7068
+ "required": false;
7069
+ "isSignal": true;
7070
+ };
7071
+ }, {}, never, ["*", "[bfSlot=content]", "[bfSlot=action]"], true, never>;
7072
+ }
7073
+ /**
7074
+ * Styles rendered markdown inside its element: headings, paragraphs, lists, links, inline code,
7075
+ * code blocks, quotes and rules, through the semantic tokens only (so a theme override restyles
7076
+ * it). The app renders the markdown to HTML itself (and sanitises it); `bfProse` only styles it.
7077
+ * A `bf-chat-message` body is a `bfProse` element already.
7078
+ *
7079
+ * @example
7080
+ * <article bfProse [innerHTML]="renderedMarkdown()"></article>
7081
+ * @summary Styles rendered markdown with the library tokens: headings, lists, code and quotes
7082
+ * @since 0.5.0
7083
+ */
7084
+ export declare class BfProseDirective {
7085
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfProseDirective, never>;
7086
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BfProseDirective, "[bfProse]", never, {}, {}, never, never, true, never>;
7087
+ }
6017
7088
  /** An error as a field reads it: its kind and optional message; limit fields (`minLength`,
6018
7089
  * `context.requiredLength`, …) are read through type guards by `bfValidationMessage`. */
6019
7090
  type FieldErrorLike = Pick<ValidationError, 'kind' | 'message'>;
@@ -6040,6 +7111,223 @@ declare function fieldErrorMessageId(id: string, errorText: string, showError: b
6040
7111
  * Hydration-stable ids are decided before 1.0.
6041
7112
  */
6042
7113
  declare function uniqueId(prefix: string): string;
7114
+ /**
7115
+ * Internal native single-choice dropdown styled like the text field, with a placeholder option and
7116
+ * Signal Forms binding. Generic over the option value (a string by default); `null` means nothing
7117
+ * is selected.
7118
+ *
7119
+ * @example
7120
+ * <bf-native-select label="Project" placeholder="Pick a project" [options]="projects" [(value)]="projectId" />
7121
+ * <bf-native-select label="Team" placeholder="Pick a team" [options]="teams" [formField]="task.teamId" />
7122
+ * @summary Native single-choice dropdown styled like the text field, with Signal Forms binding
7123
+ * @category Forms
7124
+ * @since 0.1.0
7125
+ */
7126
+ declare class BfNativeSelectComponent<T = string> implements FormValueControl<T | null> {
7127
+ /** The selected option's value, `null` while nothing is selected; two-way bindable and bound by
7128
+ * `[formField]`. */
7129
+ readonly value: import("@angular/core").ModelSignal<T | null>;
7130
+ /** The options to choose from. */
7131
+ readonly options: import("@angular/core").InputSignal<readonly BfSelectOption<T>[]>;
7132
+ /** Equality for matching `value` to an option (default `Object.is`); pass one for object values. */
7133
+ readonly compareWith: import("@angular/core").InputSignal<BfCompareWith<T>>;
7134
+ /** Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md`
7135
+ * by default). */
7136
+ readonly size: import("@angular/core").InputSignal<BfSize | undefined>;
7137
+ /** Visible label above the control. */
7138
+ readonly label: import("@angular/core").InputSignal<string>;
7139
+ /**
7140
+ * Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label`
7141
+ * is set.
7142
+ */
7143
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
7144
+ /**
7145
+ * Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when
7146
+ * `label` is set.
7147
+ */
7148
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
7149
+ /** Disabled first option shown while nothing is selected. */
7150
+ readonly placeholder: import("@angular/core").InputSignal<string>;
7151
+ /** Helper text under the control. */
7152
+ readonly helpText: import("@angular/core").InputSignal<string>;
7153
+ /** Explicit error text; takes precedence over Signal Forms errors. */
7154
+ readonly errorMessage: import("@angular/core").InputSignal<string>;
7155
+ /** Disables the control. */
7156
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7157
+ /** Shows the value but blocks changes; the select stays focusable and exposes `aria-readonly`. */
7158
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7159
+ /** Marks the control required. */
7160
+ readonly required: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7161
+ /** Field validity from Signal Forms. */
7162
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7163
+ /** Whether the user has left the field. */
7164
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
7165
+ /** Validation errors from Signal Forms. */
7166
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
7167
+ /** Native name attribute. */
7168
+ readonly name: import("@angular/core").InputSignal<string>;
7169
+ /** Control id; generated when not set. */
7170
+ readonly id: import("@angular/core").InputSignal<string>;
7171
+ /** Emitted with the chosen value after a user change (never `null`). */
7172
+ readonly changed: import("@angular/core").OutputEmitterRef<T>;
7173
+ /** Emitted when the control gains focus. */
7174
+ readonly focused: import("@angular/core").OutputEmitterRef<FocusEvent>;
7175
+ /** Emitted when the control loses focus. */
7176
+ readonly blurred: import("@angular/core").OutputEmitterRef<FocusEvent>;
7177
+ /** Emitted on blur so Signal Forms marks the field touched. */
7178
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
7179
+ private readonly el;
7180
+ private readonly defaults;
7181
+ /** The size in effect: `size`, else the app's `fieldSize` default. */
7182
+ protected readonly resolvedSize: import("@angular/core").Signal<BfSize>;
7183
+ readonly wrapperClasses: import("@angular/core").Signal<string>;
7184
+ /** Index of the option matching `value`, `-1` while nothing (or `null`) is selected. */
7185
+ readonly selectedIndex: import("@angular/core").Signal<number>;
7186
+ readonly showEmptyOption: import("@angular/core").Signal<boolean>;
7187
+ private readonly labels;
7188
+ readonly errorText: import("@angular/core").Signal<string>;
7189
+ readonly showError: import("@angular/core").Signal<boolean>;
7190
+ readonly describedBy: import("@angular/core").Signal<string | null>;
7191
+ /** `aria-errormessage`: the error paragraph while `aria-invalid` is set. */
7192
+ readonly errorMessageId: import("@angular/core").Signal<string | null>;
7193
+ /**
7194
+ * `aria-label` value: only when there is no visible label, so the label stays the sole accessible
7195
+ * name.
7196
+ */
7197
+ readonly ariaLabelAttr: import("@angular/core").Signal<string | null>;
7198
+ /** `aria-labelledby` value, under the same rule as `ariaLabelAttr`. */
7199
+ readonly ariaLabelledbyAttr: import("@angular/core").Signal<string | null>;
7200
+ readonly classes: import("@angular/core").Signal<string>;
7201
+ /** Native `value` of an option: its value for a primitive, its index for an object. */
7202
+ protected readonly key: typeof choiceKey;
7203
+ /**
7204
+ * Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
7205
+ * @param options Passed on to the native `focus()`, such as `{ preventScroll: true }`.
7206
+ */
7207
+ focus(options?: FocusOptions): void;
7208
+ /**
7209
+ * Read-only: no picker on a mouse press. Cancelling the press also cancels its focus, so the
7210
+ * select is focused explicitly, like any other read-only control. Touch screens open the
7211
+ * platform picker without a cancellable press; a choice made there is reverted by `onChange`.
7212
+ * @internal
7213
+ */
7214
+ onMousedown(event: MouseEvent): void;
7215
+ /**
7216
+ * Read-only: no picker or value change from the keyboard (arrows, Enter, Space, F4, type-ahead).
7217
+ * @internal
7218
+ */
7219
+ onKeydown(event: KeyboardEvent): void;
7220
+ /**
7221
+ * Writes the chosen option's value to the model and emits `changed`; while read-only, restores
7222
+ * the shown option instead.
7223
+ * @internal
7224
+ */
7225
+ onChange(el: HTMLSelectElement): void;
7226
+ /**
7227
+ * Marks the field touched and emits `blurred`.
7228
+ * @internal
7229
+ */
7230
+ onBlur(event: FocusEvent): void;
7231
+ static ɵfac: i0.ɵɵFactoryDeclaration<BfNativeSelectComponent<any>, never>;
7232
+ static ɵcmp: i0.ɵɵComponentDeclaration<BfNativeSelectComponent<any>, "bf-native-select", never, {
7233
+ "value": {
7234
+ "alias": "value";
7235
+ "required": false;
7236
+ "isSignal": true;
7237
+ };
7238
+ "options": {
7239
+ "alias": "options";
7240
+ "required": true;
7241
+ "isSignal": true;
7242
+ };
7243
+ "compareWith": {
7244
+ "alias": "compareWith";
7245
+ "required": false;
7246
+ "isSignal": true;
7247
+ };
7248
+ "size": {
7249
+ "alias": "size";
7250
+ "required": false;
7251
+ "isSignal": true;
7252
+ };
7253
+ "label": {
7254
+ "alias": "label";
7255
+ "required": false;
7256
+ "isSignal": true;
7257
+ };
7258
+ "ariaLabel": {
7259
+ "alias": "ariaLabel";
7260
+ "required": false;
7261
+ "isSignal": true;
7262
+ };
7263
+ "ariaLabelledby": {
7264
+ "alias": "ariaLabelledby";
7265
+ "required": false;
7266
+ "isSignal": true;
7267
+ };
7268
+ "placeholder": {
7269
+ "alias": "placeholder";
7270
+ "required": false;
7271
+ "isSignal": true;
7272
+ };
7273
+ "helpText": {
7274
+ "alias": "helpText";
7275
+ "required": false;
7276
+ "isSignal": true;
7277
+ };
7278
+ "errorMessage": {
7279
+ "alias": "errorMessage";
7280
+ "required": false;
7281
+ "isSignal": true;
7282
+ };
7283
+ "disabled": {
7284
+ "alias": "disabled";
7285
+ "required": false;
7286
+ "isSignal": true;
7287
+ };
7288
+ "readonly": {
7289
+ "alias": "readonly";
7290
+ "required": false;
7291
+ "isSignal": true;
7292
+ };
7293
+ "required": {
7294
+ "alias": "required";
7295
+ "required": false;
7296
+ "isSignal": true;
7297
+ };
7298
+ "invalid": {
7299
+ "alias": "invalid";
7300
+ "required": false;
7301
+ "isSignal": true;
7302
+ };
7303
+ "touched": {
7304
+ "alias": "touched";
7305
+ "required": false;
7306
+ "isSignal": true;
7307
+ };
7308
+ "errors": {
7309
+ "alias": "errors";
7310
+ "required": false;
7311
+ "isSignal": true;
7312
+ };
7313
+ "name": {
7314
+ "alias": "name";
7315
+ "required": false;
7316
+ "isSignal": true;
7317
+ };
7318
+ "id": {
7319
+ "alias": "id";
7320
+ "required": false;
7321
+ "isSignal": true;
7322
+ };
7323
+ }, {
7324
+ "value": "valueChange";
7325
+ "changed": "changed";
7326
+ "focused": "focused";
7327
+ "blurred": "blurred";
7328
+ "touch": "touch";
7329
+ }, never, never, true, never>;
7330
+ }
6043
7331
  /** One plain date's parts; `month` is 1–12. */
6044
7332
  interface CalendarDate {
6045
7333
  readonly year: number;
@@ -6112,6 +7400,9 @@ interface BfNavHost {
6112
7400
  readonly layout: BfNavLayout;
6113
7401
  /** True while the sidebar is collapsed to an icon rail; always false in a top nav. */
6114
7402
  readonly collapsed: Signal<boolean>;
7403
+ /** True once the host has rendered, so collapsing and expanding animate but the first render
7404
+ * (a restored collapsed state) does not; always false in a top nav. */
7405
+ readonly animated: Signal<boolean>;
6115
7406
  /** The id of the host's `nav` element (for a toggle's `aria-controls`); null in a top nav. */
6116
7407
  readonly navId: string | null;
6117
7408
  /** Writes the sidebar's `collapsed` model, as a user change; does nothing in a top nav. */
@@ -6132,4 +7423,4 @@ declare const BF_NAV: InjectionToken<BfNavHost>;
6132
7423
  declare function resetBfDocument(document: Document): void;
6133
7424
  /** Runs `resetBfDocument()` when the environment injector that holds it is destroyed. Internal. */
6134
7425
  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 };
7426
+ export { type BfAlertVariant, type BfAppFrameBreakpoint, type BfAppFrameContentWidth, type BfAutocapitalize, type BfAvatarSize, type BfAvatarVariant, type BfBadgeAppearance, type BfBadgeEmphasis, type BfBadgeVariant, type BfButtonAppearance, type BfButtonSize, type BfButtonType, type BfButtonVariant, type BfCalendarDisabledDate, type BfCalendarWeekday, type BfCardPadding, type BfCardVariant, type BfChatMessageRole, type BfChatMessageStatus, type BfChipGroupMode, type BfChipVariant, type BfColorScheme, type BfCompareWith, type BfDefaults, type BfDefaultsOptions, type BfDescriptionListLayout, type BfEmptyStateHeading, type BfEmptyStateHeadingLevel, type BfEnterKeyHint, type BfFieldControlState, type BfFieldSize, type BfFileInputVariant, type BfIconButtonVariant, type BfImageInputShape, type BfInputAutocapitalize, type BfInputEnterKeyHint, type BfInputInputMode, type BfInputMode, type BfInputType, type BfLabels, type BfLabelsOverrides, type BfMediaCardAspect, type BfPageHeaderHeadingLevel, type BfProgressSize, type BfProgressTone, type BfRadioGroupOrientation, type BfRadioOption, type BfRejectedFile, type BfRejectedFileReason, type BfSegmentOption, type BfSegmentedSize, type BfSelectOption, type BfSize, type BfSkeletonShape, type BfSpinnerSize, type BfStatAppearance, type BfStatTrend, type BfStatTrendIntent, type BfStatVariant, type BfStepHeadingLevel, type BfTabBarController, type BfTabBarHost, type BfTabLinks, type BfTableAlign, type BfTableCellContext, type BfTableColumn, type BfTableColumnAlign, type BfTableRowKey, type BfTableSort, type BfTableSortDirection, type BfTableStack, type BfTimelineTone, type BfToggleLabelPosition, type BfValidationMessages, BUTTON_CLASSES as ɵBF_BUTTON_CLASSES, ICON_BUTTON_CLASSES as ɵBF_ICON_BUTTON_CLASSES, BF_NAV as ɵBF_NAV, BfNativeSelectComponent as ɵBfNativeSelectComponent, type BfNavHost as ɵBfNavHost, FIELD_BOX as ɵFIELD_BOX, FIELD_ICON_END as ɵFIELD_ICON_END, FIELD_ICON_START as ɵFIELD_ICON_START, FIELD_PADDING_END as ɵFIELD_PADDING_END, FIELD_PADDING_START as ɵFIELD_PADDING_START, PILL_HIT as ɵPILL_HIT, choiceIndex as ɵchoiceIndex, choiceKey as ɵchoiceKey, compareIso as ɵcompareIso, createMemoryColorSchemeStorage as ɵcreateMemoryColorSchemeStorage, daysInMonth as ɵdaysInMonth, fieldDescribedBy as ɵfieldDescribedBy, fieldErrorMessageId as ɵfieldErrorMessageId, fieldErrorText as ɵfieldErrorText, fieldShowsError as ɵfieldShowsError, formatIsoDate as ɵformatIsoDate, localIsoDate as ɵlocalIsoDate, parseIsoDate as ɵparseIsoDate, provideBfDocumentReset as ɵprovideBfDocumentReset, uniqueId as ɵuniqueId, validIsoDate as ɵvalidIsoDate };