@bitforgehq/ui 0.4.0 → 0.5.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +350 -0
  2. package/README.md +12 -12
  3. package/ai/MANIFEST.index.json +74 -2
  4. package/ai/MANIFEST.json +3507 -1763
  5. package/ai/REFERENCE.index.json +189 -0
  6. package/ai/REFERENCE.json +1617 -139
  7. package/ai/UI_GUIDE.md +70 -20
  8. package/ai/components/app-frame.md +1 -1
  9. package/ai/components/badge.md +41 -1
  10. package/ai/components/chat-composer.md +357 -0
  11. package/ai/components/chat-message.md +251 -0
  12. package/ai/components/chat-thread.md +521 -0
  13. package/ai/components/checkbox.md +7 -0
  14. package/ai/components/combobox.md +12 -0
  15. package/ai/components/date-input.md +9 -0
  16. package/ai/components/datetime-input.md +2 -0
  17. package/ai/components/field.md +10 -0
  18. package/ai/components/file-input.md +498 -0
  19. package/ai/components/header.md +1 -1
  20. package/ai/components/image-input.md +368 -0
  21. package/ai/components/image-tile.md +452 -0
  22. package/ai/components/image-viewer.md +309 -0
  23. package/ai/components/media-card.md +478 -0
  24. package/ai/components/menu-button.md +67 -9
  25. package/ai/components/menu.md +10 -0
  26. package/ai/components/multi-combobox.md +7 -0
  27. package/ai/components/nav-group.md +10 -3
  28. package/ai/components/nav-item.md +11 -2
  29. package/ai/components/number-input.md +2 -7
  30. package/ai/components/pagination.md +59 -11
  31. package/ai/components/popover.md +13 -0
  32. package/ai/components/radio-group.md +6 -0
  33. package/ai/components/segmented.md +14 -2
  34. package/ai/components/select.md +100 -42
  35. package/ai/components/sidebar-toggle.md +16 -9
  36. package/ai/components/sidebar.md +41 -13
  37. package/ai/components/tab-group.md +18 -2
  38. package/ai/components/table.md +17 -2
  39. package/ai/components/theme-menu-button.md +1 -0
  40. package/ai/components/time-input.md +2 -0
  41. package/ai/components/toolbar.md +7 -18
  42. package/ai/reference/bf-chat-composer-harness.md +64 -0
  43. package/ai/reference/bf-chat-message-role.md +27 -0
  44. package/ai/reference/bf-chat-message-status.md +28 -0
  45. package/ai/reference/bf-fade-in-directive.md +18 -0
  46. package/ai/reference/bf-file-input-harness.md +67 -0
  47. package/ai/reference/bf-file-input-variant.md +27 -0
  48. package/ai/reference/bf-image-input-harness.md +67 -0
  49. package/ai/reference/bf-image-input-shape.md +27 -0
  50. package/ai/reference/bf-image-tile-aspect.md +28 -0
  51. package/ai/reference/bf-image-tile-caption-placement.md +27 -0
  52. package/ai/reference/bf-image-tile-harness.md +70 -0
  53. package/ai/reference/bf-image-viewer-actions-context.md +29 -0
  54. package/ai/reference/bf-image-viewer-harness.md +87 -0
  55. package/ai/reference/bf-image-viewer-image.md +25 -0
  56. package/ai/reference/bf-image-viewer-options.md +33 -0
  57. package/ai/reference/bf-image-viewer-origin.md +23 -0
  58. package/ai/reference/bf-image-viewer-ref.md +55 -0
  59. package/ai/reference/bf-image-viewer-service.md +32 -0
  60. package/ai/reference/bf-labels.md +107 -75
  61. package/ai/reference/bf-media-card-aspect.md +28 -0
  62. package/ai/reference/bf-media-card-harness.md +66 -0
  63. package/ai/reference/bf-motion-list-directive.md +22 -0
  64. package/ai/reference/bf-pagination-position-label.md +22 -0
  65. package/ai/reference/bf-prose-directive.md +16 -0
  66. package/ai/reference/bf-rejected-file-reason.md +28 -0
  67. package/ai/reference/bf-rejected-file.md +28 -0
  68. package/ai/reference/bf-select-harness.md +14 -5
  69. package/ai/reference/bf-select-option.md +2 -0
  70. package/ai/reference/bf-size.md +3 -0
  71. package/ai/reference/bf_table_sort_select.md +23 -0
  72. package/ai/reference/provide-bf-labels.md +11 -3
  73. package/ai/reference/provide-bf-select.md +20 -0
  74. package/bin/bf-ui.js +1 -0
  75. package/fesm2022/bitforgehq-ui-overlay.mjs +2660 -594
  76. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  77. package/fesm2022/bitforgehq-ui-testing.mjs +414 -10
  78. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  79. package/fesm2022/bitforgehq-ui.mjs +8056 -3993
  80. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  81. package/llms-full.txt +4923 -132
  82. package/llms.txt +36 -1
  83. package/package.json +1 -1
  84. package/theme.css +60 -0
  85. package/types/bitforgehq-ui-overlay.d.ts +695 -7
  86. package/types/bitforgehq-ui-testing.d.ts +517 -6
  87. package/types/bitforgehq-ui.d.ts +1699 -181
@@ -1,12 +1,12 @@
1
1
  import * as i0 from "@angular/core";
2
- import { Component, DOCUMENT, DestroyRef, Directive, ElementRef, Injectable, InjectionToken, Injector, SimpleChange, TemplateRef, ViewContainerRef, afterNextRender, afterRenderEffect, booleanAttribute, computed, effect, inject, input, inputBinding, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, signal, viewChild } from "@angular/core";
2
+ import { Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, Injectable, InjectionToken, Injector, SimpleChange, TemplateRef, ViewContainerRef, afterNextRender, afterRenderEffect, booleanAttribute, computed, effect, inject, input, inputBinding, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, signal, viewChild } from "@angular/core";
3
3
  import { FocusTrapFactory, LiveAnnouncer } from "@angular/cdk/a11y";
4
- import { Overlay, OverlayConfig } from "@angular/cdk/overlay";
4
+ import { OverlayConfig, createBlockScrollStrategy, createFlexibleConnectedPositionStrategy, createGlobalPositionStrategy, createNoopScrollStrategy, createOverlayRef, createRepositionScrollStrategy } from "@angular/cdk/overlay";
5
5
  import { ComponentPortal, TemplatePortal } from "@angular/cdk/portal";
6
6
  import { Location, NgTemplateOutlet } from "@angular/common";
7
- import { BF_DEFAULTS, BF_LABELS, BfButtonComponent, BfCalendarComponent, BfFieldComponent, BfIconButtonComponent, BfSpinnerComponent, bfFieldControlClass, ɵBF_BUTTON_CLASSES, ɵBF_ICON_BUTTON_CLASSES, ɵBF_NAV, ɵFIELD_BOX, ɵFIELD_PADDING_END, ɵFIELD_PADDING_START, ɵPILL_HIT, ɵdaysInMonth, ɵfieldDescribedBy, ɵfieldErrorMessageId, ɵfieldErrorText, ɵfieldShowsError, ɵformatIsoDate, ɵlocalIsoDate, ɵparseIsoDate, ɵuniqueId } from "@bitforgehq/ui";
7
+ import { BF_DEFAULTS, BF_LABELS, BF_TABLE_SORT_SELECT, BfButtonComponent, BfCalendarComponent, BfFadeInDirective, BfFieldComponent, BfIconButtonComponent, BfSpinnerComponent, BfTextareaComponent, bfFieldControlClass, ɵBF_BUTTON_CLASSES, ɵBF_ICON_BUTTON_CLASSES, ɵBF_NAV, ɵBfNativeSelectComponent, ɵFIELD_BOX, ɵFIELD_PADDING_END, ɵFIELD_PADDING_START, ɵPILL_HIT, ɵchoiceIndex, ɵchoiceKey, ɵdaysInMonth, ɵfieldDescribedBy, ɵfieldErrorMessageId, ɵfieldErrorText, ɵfieldShowsError, ɵformatIsoDate, ɵlocalIsoDate, ɵparseIsoDate, ɵuniqueId } from "@bitforgehq/ui";
8
8
  import { BfIconComponent } from "@bitforgehq/ui/icons";
9
- import { X } from "lucide";
9
+ import { ChevronLeft, ChevronRight, X } from "lucide";
10
10
  import { Directionality } from "@angular/cdk/bidi";
11
11
  import * as i1 from "@angular/cdk/menu";
12
12
  import { CDK_MENU, CdkMenu, CdkMenuGroup, CdkMenuItem, CdkMenuItemCheckbox, CdkMenuItemRadio, CdkMenuTrigger, CdkTargetMenuAim } from "@angular/cdk/menu";
@@ -437,7 +437,6 @@ function fromOtherPane(event, pane) {
437
437
  return false;
438
438
  }
439
439
  var BfOverlayService = class BfOverlayService {
440
- overlay = inject(Overlay);
441
440
  injector = inject(Injector);
442
441
  document = inject(DOCUMENT);
443
442
  stack = inject(BfOverlayStack);
@@ -452,9 +451,9 @@ var BfOverlayService = class BfOverlayService {
452
451
  const previouslyFocused = this.document.activeElement;
453
452
  const hasBackdrop = modal && (options.backdrop ?? true);
454
453
  const position = options.position ?? "center";
455
- const overlayRef = this.overlay.create(new OverlayConfig({
454
+ const overlayRef = createOverlayRef(this.injector, new OverlayConfig({
456
455
  positionStrategy: this.positionStrategy(position),
457
- scrollStrategy: !modal || !this.documentScrolls ? this.overlay.scrollStrategies.noop() : this.overlay.scrollStrategies.block(),
456
+ scrollStrategy: !modal || !this.documentScrolls ? createNoopScrollStrategy() : createBlockScrollStrategy(this.injector),
458
457
  hasBackdrop,
459
458
  backdropClass: options.backdropClass ?? "bf-overlay-backdrop",
460
459
  panelClass: ["bf-overlay-panel", ...options.panelClass ?? []],
@@ -600,7 +599,7 @@ var BfOverlayService = class BfOverlayService {
600
599
  if (options.ariaDescribedBy) panel.setAttribute("aria-describedby", options.ariaDescribedBy);
601
600
  }
602
601
  positionStrategy(position) {
603
- const global = this.overlay.position().global();
602
+ const global = createGlobalPositionStrategy(this.injector);
604
603
  switch (position) {
605
604
  case "bottom": return global.centerHorizontally().bottom("0");
606
605
  case "top": return global.centerHorizontally().top("0");
@@ -1691,7 +1690,6 @@ const INSET = {
1691
1690
  endRtl: "calc(24px + var(--spacing-safe-left, 0px))"
1692
1691
  };
1693
1692
  var BfDesktopToastService = class BfDesktopToastService {
1694
- overlay = inject(Overlay);
1695
1693
  injector = inject(Injector);
1696
1694
  document = inject(DOCUMENT);
1697
1695
  config = inject(BF_DESKTOP_TOAST_CONFIG);
@@ -1761,9 +1759,9 @@ var BfDesktopToastService = class BfDesktopToastService {
1761
1759
  }
1762
1760
  ensurePane(injector) {
1763
1761
  if (this.pane) return this.pane;
1764
- const overlayRef = this.overlay.create({
1762
+ const overlayRef = createOverlayRef(this.injector, {
1765
1763
  positionStrategy: this.positionStrategy(),
1766
- scrollStrategy: this.overlay.scrollStrategies.noop(),
1764
+ scrollStrategy: createNoopScrollStrategy(),
1767
1765
  hasBackdrop: false,
1768
1766
  panelClass: "bf-desktop-toast-pane"
1769
1767
  });
@@ -1817,7 +1815,7 @@ var BfDesktopToastService = class BfDesktopToastService {
1817
1815
  return this.config.position.startsWith("top");
1818
1816
  }
1819
1817
  positionStrategy() {
1820
- const global = this.overlay.position().global();
1818
+ const global = createGlobalPositionStrategy(this.injector);
1821
1819
  const vertical = this.anchoredTop() ? global.top(INSET.top) : global.bottom(INSET.bottom);
1822
1820
  return this.config.position.endsWith("center") ? vertical.centerHorizontally() : vertical.end(this.directionality.value === "rtl" ? INSET.endRtl : INSET.endLtr);
1823
1821
  }
@@ -1848,6 +1846,96 @@ i0.ɵɵngDeclareClassMetadata({
1848
1846
  }],
1849
1847
  ctorParameters: () => []
1850
1848
  });
1849
+ const ANCHORED_ENTER_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
1850
+ const ANCHORED_EXIT_EASING = "cubic-bezier(0.4, 0, 1, 1)";
1851
+ const ANCHORED_SCALE = .96;
1852
+ const OPEN = {
1853
+ opacity: 1,
1854
+ transform: "none"
1855
+ };
1856
+ function hiddenFrame(panel, anchor) {
1857
+ if (prefersReducedMotion(panel.ownerDocument.defaultView)) return { opacity: 0 };
1858
+ const p = panel.getBoundingClientRect();
1859
+ const a = anchor.getBoundingClientRect();
1860
+ let x = 0;
1861
+ let y = 0;
1862
+ let originX = null;
1863
+ let originY = "top";
1864
+ if (p.top >= a.bottom - 1) y = -4;
1865
+ else if (p.bottom <= a.top + 1) {
1866
+ y = 4;
1867
+ originY = "bottom";
1868
+ } else if (p.left >= a.right - 1) {
1869
+ x = -4;
1870
+ originX = "left";
1871
+ } else {
1872
+ x = 4;
1873
+ originX = "right";
1874
+ }
1875
+ originX ??= Math.abs(p.left - a.left) <= Math.abs(p.right - a.right) ? "left" : "right";
1876
+ panel.style.transformOrigin = `${originX} ${originY}`;
1877
+ return {
1878
+ opacity: 0,
1879
+ transform: `translate3d(${x}px, ${y}px, 0) scale(${ANCHORED_SCALE})`
1880
+ };
1881
+ }
1882
+ function animateAnchoredIn(panel, anchor) {
1883
+ return animateOrSettle(panel, [hiddenFrame(panel, anchor), OPEN], {
1884
+ duration: 180,
1885
+ easing: ANCHORED_ENTER_EASING,
1886
+ fill: "backwards"
1887
+ });
1888
+ }
1889
+ function animateAnchoredOut(panel, anchor) {
1890
+ return animateOrSettle(panel, [hiddenFrame(panel, anchor)], {
1891
+ duration: 130,
1892
+ easing: ANCHORED_EXIT_EASING,
1893
+ fill: "forwards"
1894
+ });
1895
+ }
1896
+ var AnchoredMotion = class {
1897
+ leaving = null;
1898
+ enter(ref, anchor, view) {
1899
+ view?.detectChanges();
1900
+ ref.updatePosition();
1901
+ animateAnchoredIn(ref.overlayElement, anchor);
1902
+ }
1903
+ leave(ref, anchor, freeze) {
1904
+ this.flush();
1905
+ const pane = ref.overlayElement;
1906
+ pane.setAttribute("inert", "");
1907
+ pane.style.pointerEvents = "none";
1908
+ freeze?.detach();
1909
+ const animation = animateAnchoredOut(pane, anchor);
1910
+ if (animation.playState === "finished" || animation.effect === null) {
1911
+ ref.dispose();
1912
+ return;
1913
+ }
1914
+ const entry = {
1915
+ ref,
1916
+ animation
1917
+ };
1918
+ this.leaving = entry;
1919
+ const done = () => {
1920
+ if (this.leaving === entry) {
1921
+ this.leaving = null;
1922
+ ref.dispose();
1923
+ }
1924
+ };
1925
+ animation.finished.then(done, done);
1926
+ }
1927
+ flush() {
1928
+ const entry = this.leaving;
1929
+ if (entry) {
1930
+ this.leaving = null;
1931
+ entry.animation.cancel();
1932
+ entry.ref.dispose();
1933
+ }
1934
+ }
1935
+ isLeaving() {
1936
+ return this.leaving !== null;
1937
+ }
1938
+ };
1851
1939
  const BELOW_START = {
1852
1940
  originX: "start",
1853
1941
  originY: "bottom",
@@ -1965,11 +2053,11 @@ function sidePositions(placement, direction = "ltr") {
1965
2053
  offsetX: -position.offsetX
1966
2054
  } : position);
1967
2055
  }
1968
- function createAnchoredOverlay(overlay, anchor, options) {
1969
- const strategy = overlay.position().flexibleConnectedTo(anchor).withPositions(options.positions).withFlexibleDimensions(false).withPush(false).withViewportMargin(8);
1970
- const ref = overlay.create({
2056
+ function createAnchoredOverlay(injector, anchor, options) {
2057
+ const strategy = createFlexibleConnectedPositionStrategy(injector, anchor).withPositions(options.positions).withFlexibleDimensions(false).withPush(false).withViewportMargin(8);
2058
+ const ref = createOverlayRef(injector, {
1971
2059
  positionStrategy: strategy,
1972
- scrollStrategy: overlay.scrollStrategies.reposition(),
2060
+ scrollStrategy: createRepositionScrollStrategy(injector),
1973
2061
  direction: options.direction,
1974
2062
  hasBackdrop: false,
1975
2063
  panelClass: options.panelClass
@@ -2014,6 +2102,9 @@ var BfMenuTriggerDirective = class BfMenuTriggerDirective {
2014
2102
  previous = positions;
2015
2103
  });
2016
2104
  }
2105
+ anchorElement() {
2106
+ return this.el;
2107
+ }
2017
2108
  labelId() {
2018
2109
  if (!this.el.id) this.el.id = ɵuniqueId("bf-menu-trigger");
2019
2110
  return this.el.id;
@@ -2092,6 +2183,12 @@ var BfMenuComponent = class BfMenuComponent {
2092
2183
  resolvedSize = computed(() => this.size() ?? this.defaults.controlSize, ...ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []);
2093
2184
  triggerId = this.trigger?.labelId() ?? null;
2094
2185
  labelledBy = computed(() => this.ariaLabelledby() || (this.ariaLabel() ? null : this.triggerId), ...ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []);
2186
+ constructor() {
2187
+ const trigger = this.trigger;
2188
+ if (!trigger) return;
2189
+ const host = inject(ElementRef).nativeElement;
2190
+ afterNextRender({ read: () => animateAnchoredIn(host, trigger.anchorElement()) });
2191
+ }
2095
2192
  static ɵfac = i0.ɵɵngDeclareFactory({
2096
2193
  minVersion: "12.0.0",
2097
2194
  version: "22.1.7",
@@ -2160,6 +2257,7 @@ i0.ɵɵngDeclareClassMetadata({
2160
2257
  template: "<ng-content />"
2161
2258
  }]
2162
2259
  }],
2260
+ ctorParameters: () => [],
2163
2261
  propDecorators: {
2164
2262
  size: [{
2165
2263
  type: i0.Input,
@@ -2508,7 +2606,7 @@ i0.ɵɵngDeclareClassMetadata({
2508
2606
  }]
2509
2607
  }]
2510
2608
  });
2511
- const BUTTON = ɵBF_BUTTON_CLASSES;
2609
+ const BUTTON$1 = ɵBF_BUTTON_CLASSES;
2512
2610
  const ICON$1 = ɵBF_ICON_BUTTON_CLASSES;
2513
2611
  const CARET_PADDING = {
2514
2612
  sm: "ps-3.5 pe-3",
@@ -2526,6 +2624,10 @@ var BfMenuButtonComponent = class BfMenuButtonComponent {
2526
2624
  ...ngDevMode ? { debugName: "iconOnly" } : /* istanbul ignore next */ {},
2527
2625
  transform: booleanAttribute
2528
2626
  });
2627
+ fullWidth = input(false, {
2628
+ ...ngDevMode ? { debugName: "fullWidth" } : /* istanbul ignore next */ {},
2629
+ transform: booleanAttribute
2630
+ });
2529
2631
  disabled = input(false, {
2530
2632
  ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
2531
2633
  transform: booleanAttribute
@@ -2533,6 +2635,8 @@ var BfMenuButtonComponent = class BfMenuButtonComponent {
2533
2635
  opened = output();
2534
2636
  closed = output();
2535
2637
  resolvedSize = computed(() => this.size() ?? this.defaults.controlSize, ...ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []);
2638
+ stretched = computed(() => this.fullWidth() && !this.iconOnly(), ...ngDevMode ? [{ debugName: "stretched" }] : /* istanbul ignore next */ []);
2639
+ hostClasses = computed(() => this.stretched() ? "inline-flex max-w-full w-full" : "inline-flex max-w-full", ...ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []);
2536
2640
  labelClasses = computed(() => this.iconOnly() ? "contents" : "max-w-full min-w-0 truncate", ...ngDevMode ? [{ debugName: "labelClasses" }] : /* istanbul ignore next */ []);
2537
2641
  classes = computed(() => {
2538
2642
  const size = this.resolvedSize();
@@ -2548,16 +2652,17 @@ var BfMenuButtonComponent = class BfMenuButtonComponent {
2548
2652
  hit
2549
2653
  ].filter(Boolean).join(" ");
2550
2654
  }
2551
- const padding = variant === "text" ? BUTTON.textPadding[size] : CARET_PADDING[size];
2552
- const pressed = this.disabled() ? "" : BUTTON.pressed[variant];
2655
+ const padding = variant === "text" ? BUTTON$1.textPadding[size] : CARET_PADDING[size];
2656
+ const pressed = this.disabled() ? "" : BUTTON$1.pressed[variant];
2553
2657
  return [
2554
- BUTTON.base,
2658
+ BUTTON$1.base,
2555
2659
  "cursor-pointer",
2556
- BUTTON.variant[variant],
2660
+ BUTTON$1.variant[variant],
2557
2661
  pressed,
2558
- BUTTON.size[size],
2662
+ BUTTON$1.size[size],
2559
2663
  padding,
2560
- hit
2664
+ hit,
2665
+ this.stretched() ? "w-full" : ""
2561
2666
  ].filter(Boolean).join(" ");
2562
2667
  }, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
2563
2668
  static ɵfac = i0.ɵɵngDeclareFactory({
@@ -2617,6 +2722,13 @@ var BfMenuButtonComponent = class BfMenuButtonComponent {
2617
2722
  isRequired: false,
2618
2723
  transformFunction: null
2619
2724
  },
2725
+ fullWidth: {
2726
+ classPropertyName: "fullWidth",
2727
+ publicName: "fullWidth",
2728
+ isSignal: true,
2729
+ isRequired: false,
2730
+ transformFunction: null
2731
+ },
2620
2732
  disabled: {
2621
2733
  classPropertyName: "disabled",
2622
2734
  publicName: "disabled",
@@ -2629,7 +2741,7 @@ var BfMenuButtonComponent = class BfMenuButtonComponent {
2629
2741
  opened: "opened",
2630
2742
  closed: "closed"
2631
2743
  },
2632
- host: { classAttribute: "inline-flex max-w-full" },
2744
+ host: { properties: { "class": "hostClasses()" } },
2633
2745
  ngImport: i0,
2634
2746
  template: `
2635
2747
  <button
@@ -2672,7 +2784,7 @@ i0.ɵɵngDeclareClassMetadata({
2672
2784
  args: [{
2673
2785
  selector: "bf-menu-button",
2674
2786
  imports: [BfMenuTriggerDirective],
2675
- host: { class: "inline-flex max-w-full" },
2787
+ host: { "[class]": "hostClasses()" },
2676
2788
  template: `
2677
2789
  <button
2678
2790
  type="button"
@@ -2746,6 +2858,14 @@ i0.ɵɵngDeclareClassMetadata({
2746
2858
  required: false
2747
2859
  }]
2748
2860
  }],
2861
+ fullWidth: [{
2862
+ type: i0.Input,
2863
+ args: [{
2864
+ isSignal: true,
2865
+ alias: "fullWidth",
2866
+ required: false
2867
+ }]
2868
+ }],
2749
2869
  disabled: [{
2750
2870
  type: i0.Input,
2751
2871
  args: [{
@@ -2902,6 +3022,7 @@ var BfThemeMenuButtonComponent = class BfThemeMenuButtonComponent {
2902
3022
  "placement",
2903
3023
  "label",
2904
3024
  "iconOnly",
3025
+ "fullWidth",
2905
3026
  "disabled"
2906
3027
  ],
2907
3028
  outputs: ["opened", "closed"]
@@ -3089,7 +3210,7 @@ const SHOW_DELAY = 500;
3089
3210
  const HIDE_GRACE = 100;
3090
3211
  const FADE = 120;
3091
3212
  var BfTooltipDirective = class BfTooltipDirective {
3092
- overlay = inject(Overlay);
3213
+ environment = inject(EnvironmentInjector);
3093
3214
  document = inject(DOCUMENT);
3094
3215
  injector = inject(Injector);
3095
3216
  dir = inject(Directionality, { optional: true });
@@ -3112,7 +3233,7 @@ var BfTooltipDirective = class BfTooltipDirective {
3112
3233
  this.clearTimers();
3113
3234
  if (this.anchored || !this.bfTooltip() || this.coarsePointer()) return;
3114
3235
  const direction = this.dir?.value ?? "ltr";
3115
- const anchored = createAnchoredOverlay(this.overlay, this.host, {
3236
+ const anchored = createAnchoredOverlay(this.environment, this.host, {
3116
3237
  positions: sidePositions(this.bfTooltipPlacement(), direction),
3117
3238
  direction,
3118
3239
  panelClass: ["bf-tooltip-panel"]
@@ -3408,175 +3529,301 @@ i0.ɵɵngDeclareClassMetadata({
3408
3529
  }]
3409
3530
  }] }
3410
3531
  });
3411
- const BF_SIDE_PANEL_DATA = new InjectionToken("BF_SIDE_PANEL_DATA");
3412
- const BF_SIDE_PANEL_CONTEXT = new InjectionToken("BF_SIDE_PANEL_CONTEXT");
3413
- var BfSidePanelRef = class {
3414
- overlayRef;
3415
- instance;
3416
- id;
3417
- afterClosed;
3418
- closed;
3419
- constructor(overlayRef, instance = () => null) {
3420
- this.overlayRef = overlayRef;
3421
- this.instance = instance;
3422
- this.id = overlayRef.id;
3423
- this.afterClosed = overlayRef.afterClosed;
3424
- this.closed = overlayRef.closed;
3425
- }
3426
- get componentInstance() {
3427
- return this.instance();
3428
- }
3429
- close(result) {
3430
- this.overlayRef.close(result);
3431
- }
3532
+ const BUTTON = ɵBF_BUTTON_CLASSES;
3533
+ const VARIANT = {
3534
+ send: "primary",
3535
+ stop: "tonal"
3432
3536
  };
3433
- const HOST_BASE = "relative flex h-full flex-col border-s border-border bg-surface text-text shadow-floating";
3434
- const WIDTH = {
3435
- sm: "w-[min(22.5rem,100vw)]",
3436
- md: "w-[min(30rem,100vw)]",
3437
- lg: "w-[min(40rem,100vw)]"
3537
+ const SQUARE = {
3538
+ sm: "has-[[data-icon]>*]:w-8 has-[[data-icon]>*]:px-0",
3539
+ md: "has-[[data-icon]>*]:w-10 has-[[data-icon]>*]:px-0",
3540
+ lg: "has-[[data-icon]>*]:w-12 has-[[data-icon]>*]:px-0"
3438
3541
  };
3439
- var BfSidePanelComponent = class BfSidePanelComponent {
3542
+ const FIELD_HEIGHT = {
3543
+ sm: "[&_textarea]:min-h-8",
3544
+ md: "[&_textarea]:min-h-10",
3545
+ lg: "[&_textarea]:min-h-12 [&_textarea]:py-[11px]"
3546
+ };
3547
+ const BLOCKED = "cursor-not-allowed bg-surface-sunken text-text-disabled";
3548
+ var BfChatComposerComponent = class BfChatComposerComponent {
3549
+ defaults = inject(BF_DEFAULTS);
3440
3550
  labels = inject(BF_LABELS);
3441
- context = inject(BF_SIDE_PANEL_CONTEXT, { optional: true });
3442
- ref = inject(BfSidePanelRef, { optional: true });
3443
- title = input("", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
3444
- closeLabel = input("", ...ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []);
3445
- overflowing = signal(false, ...ngDevMode ? [{ debugName: "overflowing" }] : /* istanbul ignore next */ []);
3446
- titleId = this.context?.titleId ?? ɵuniqueId("bf-side-panel-title");
3447
- closeIcon = X;
3448
- width = this.context?.width ?? "md";
3449
- hostClasses = computed(() => `${HOST_BASE} ${WIDTH[this.width]}`, ...ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []);
3450
- closeName = computed(() => this.closeLabel() || this.labels.close, ...ngDevMode ? [{ debugName: "closeName" }] : /* istanbul ignore next */ []);
3451
- body = viewChild.required("body", ...ngDevMode ? [{ debugName: "body" }] : /* istanbul ignore next */ []);
3452
- content = viewChild.required("content", ...ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []);
3453
- constructor() {
3454
- let observer = null;
3455
- afterNextRender(() => {
3456
- const body = this.body().nativeElement;
3457
- const measure = () => this.overflowing.set(body.scrollHeight > body.clientHeight);
3458
- measure();
3459
- if (typeof ResizeObserver === "function") {
3460
- observer = new ResizeObserver(measure);
3461
- observer.observe(body);
3462
- observer.observe(this.content().nativeElement);
3463
- }
3464
- });
3465
- inject(DestroyRef).onDestroy(() => observer?.disconnect());
3551
+ value = model("", ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
3552
+ running = input(false, {
3553
+ ...ngDevMode ? { debugName: "running" } : /* istanbul ignore next */ {},
3554
+ transform: booleanAttribute
3555
+ });
3556
+ disabled = input(false, {
3557
+ ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
3558
+ transform: booleanAttribute
3559
+ });
3560
+ disabledReason = input("", ...ngDevMode ? [{ debugName: "disabledReason" }] : /* istanbul ignore next */ []);
3561
+ label = input("", ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
3562
+ ariaLabel = input("", ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
3563
+ ariaLabelledby = input("", ...ngDevMode ? [{ debugName: "ariaLabelledby" }] : /* istanbul ignore next */ []);
3564
+ placeholder = input("", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
3565
+ maxRows = input(8, ...ngDevMode ? [{ debugName: "maxRows" }] : /* istanbul ignore next */ []);
3566
+ maxLength = input(void 0, ...ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []);
3567
+ name = input("", ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
3568
+ size = input(void 0, ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
3569
+ changed = output();
3570
+ sent = output();
3571
+ stopped = output();
3572
+ field = viewChild.required(BfTextareaComponent, ...ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []);
3573
+ resolvedSize = computed(() => this.size() ?? this.defaults.fieldSize, ...ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []);
3574
+ fieldClasses = computed(() => `min-w-0 flex-1 ${FIELD_HEIGHT[this.resolvedSize()]}`, ...ngDevMode ? [{ debugName: "fieldClasses" }] : /* istanbul ignore next */ []);
3575
+ action = computed(() => this.running() ? "stop" : "send", ...ngDevMode ? [{ debugName: "action" }] : /* istanbul ignore next */ []);
3576
+ blocked = computed(() => this.disabled() && this.disabledReason() !== "", ...ngDevMode ? [{ debugName: "blocked" }] : /* istanbul ignore next */ []);
3577
+ nativeDisabled = computed(() => !this.blocked() && (this.disabled() || this.action() === "send" && this.value().trim() === ""), ...ngDevMode ? [{ debugName: "nativeDisabled" }] : /* istanbul ignore next */ []);
3578
+ buttonName = computed(() => this.action() === "stop" ? this.labels.chatStop : this.labels.chatSend, ...ngDevMode ? [{ debugName: "buttonName" }] : /* istanbul ignore next */ []);
3579
+ fieldName = computed(() => this.ariaLabel() || (this.ariaLabelledby() ? "" : this.labels.chatComposer), ...ngDevMode ? [{ debugName: "fieldName" }] : /* istanbul ignore next */ []);
3580
+ fieldPlaceholder = computed(() => this.disabled() && this.disabledReason() ? this.disabledReason() : this.placeholder(), ...ngDevMode ? [{ debugName: "fieldPlaceholder" }] : /* istanbul ignore next */ []);
3581
+ buttonClasses = computed(() => {
3582
+ const size = this.resolvedSize();
3583
+ const variant = VARIANT[this.action()];
3584
+ return [
3585
+ BUTTON.base,
3586
+ this.blocked() ? BLOCKED : `${BUTTON.variant[variant]} ${BUTTON.pressed[variant]} cursor-pointer`,
3587
+ BUTTON.size[size],
3588
+ BUTTON.padding[size],
3589
+ SQUARE[size],
3590
+ ɵPILL_HIT[size].plain
3591
+ ].join(" ");
3592
+ }, ...ngDevMode ? [{ debugName: "buttonClasses" }] : /* istanbul ignore next */ []);
3593
+ send() {
3594
+ const text = this.value().trim();
3595
+ if (this.disabled() || this.running() || text === "") return;
3596
+ this.sent.emit(text);
3597
+ this.value.set("");
3598
+ this.field().focus();
3599
+ }
3600
+ focus() {
3601
+ this.field().focus();
3602
+ }
3603
+ onChanged(text) {
3604
+ this.value.set(text);
3605
+ this.changed.emit(text);
3606
+ }
3607
+ onButton() {
3608
+ if (this.blocked()) return;
3609
+ if (this.action() === "stop") {
3610
+ this.stopped.emit();
3611
+ return;
3612
+ }
3613
+ this.send();
3466
3614
  }
3467
- close() {
3468
- this.ref?.close();
3615
+ onKeydown(event) {
3616
+ const composing = event.isComposing || event.keyCode === 229;
3617
+ if (event.key !== "Enter" || event.shiftKey || composing) return;
3618
+ event.preventDefault();
3619
+ this.send();
3469
3620
  }
3470
3621
  static ɵfac = i0.ɵɵngDeclareFactory({
3471
3622
  minVersion: "12.0.0",
3472
3623
  version: "22.1.7",
3473
3624
  ngImport: i0,
3474
- type: BfSidePanelComponent,
3625
+ type: BfChatComposerComponent,
3475
3626
  deps: [],
3476
3627
  target: i0.ɵɵFactoryTarget.Component
3477
3628
  });
3478
3629
  static ɵcmp = i0.ɵɵngDeclareComponent({
3479
- minVersion: "17.0.0",
3630
+ minVersion: "17.2.0",
3480
3631
  version: "22.1.7",
3481
- type: BfSidePanelComponent,
3632
+ type: BfChatComposerComponent,
3482
3633
  isStandalone: true,
3483
- selector: "bf-side-panel",
3634
+ selector: "bf-chat-composer",
3484
3635
  inputs: {
3485
- title: {
3486
- classPropertyName: "title",
3487
- publicName: "title",
3636
+ value: {
3637
+ classPropertyName: "value",
3638
+ publicName: "value",
3488
3639
  isSignal: true,
3489
3640
  isRequired: false,
3490
3641
  transformFunction: null
3491
3642
  },
3492
- closeLabel: {
3493
- classPropertyName: "closeLabel",
3494
- publicName: "closeLabel",
3643
+ running: {
3644
+ classPropertyName: "running",
3645
+ publicName: "running",
3646
+ isSignal: true,
3647
+ isRequired: false,
3648
+ transformFunction: null
3649
+ },
3650
+ disabled: {
3651
+ classPropertyName: "disabled",
3652
+ publicName: "disabled",
3653
+ isSignal: true,
3654
+ isRequired: false,
3655
+ transformFunction: null
3656
+ },
3657
+ disabledReason: {
3658
+ classPropertyName: "disabledReason",
3659
+ publicName: "disabledReason",
3660
+ isSignal: true,
3661
+ isRequired: false,
3662
+ transformFunction: null
3663
+ },
3664
+ label: {
3665
+ classPropertyName: "label",
3666
+ publicName: "label",
3667
+ isSignal: true,
3668
+ isRequired: false,
3669
+ transformFunction: null
3670
+ },
3671
+ ariaLabel: {
3672
+ classPropertyName: "ariaLabel",
3673
+ publicName: "ariaLabel",
3674
+ isSignal: true,
3675
+ isRequired: false,
3676
+ transformFunction: null
3677
+ },
3678
+ ariaLabelledby: {
3679
+ classPropertyName: "ariaLabelledby",
3680
+ publicName: "ariaLabelledby",
3681
+ isSignal: true,
3682
+ isRequired: false,
3683
+ transformFunction: null
3684
+ },
3685
+ placeholder: {
3686
+ classPropertyName: "placeholder",
3687
+ publicName: "placeholder",
3688
+ isSignal: true,
3689
+ isRequired: false,
3690
+ transformFunction: null
3691
+ },
3692
+ maxRows: {
3693
+ classPropertyName: "maxRows",
3694
+ publicName: "maxRows",
3695
+ isSignal: true,
3696
+ isRequired: false,
3697
+ transformFunction: null
3698
+ },
3699
+ maxLength: {
3700
+ classPropertyName: "maxLength",
3701
+ publicName: "maxLength",
3702
+ isSignal: true,
3703
+ isRequired: false,
3704
+ transformFunction: null
3705
+ },
3706
+ name: {
3707
+ classPropertyName: "name",
3708
+ publicName: "name",
3709
+ isSignal: true,
3710
+ isRequired: false,
3711
+ transformFunction: null
3712
+ },
3713
+ size: {
3714
+ classPropertyName: "size",
3715
+ publicName: "size",
3495
3716
  isSignal: true,
3496
3717
  isRequired: false,
3497
3718
  transformFunction: null
3498
3719
  }
3499
3720
  },
3500
- host: { properties: {
3501
- "class": "hostClasses()",
3502
- "attr.data-bf-side-panel-id": "titleId"
3503
- } },
3721
+ outputs: {
3722
+ value: "valueChange",
3723
+ changed: "changed",
3724
+ sent: "sent",
3725
+ stopped: "stopped"
3726
+ },
3727
+ host: {
3728
+ properties: { "attr.data-running": "running() ? '' : null" },
3729
+ classAttribute: "flex items-end gap-2"
3730
+ },
3504
3731
  viewQueries: [{
3505
- propertyName: "body",
3506
- first: true,
3507
- predicate: ["body"],
3508
- descendants: true,
3509
- isSignal: true
3510
- }, {
3511
- propertyName: "content",
3732
+ propertyName: "field",
3512
3733
  first: true,
3513
- predicate: ["content"],
3734
+ predicate: BfTextareaComponent,
3514
3735
  descendants: true,
3515
3736
  isSignal: true
3516
3737
  }],
3517
3738
  ngImport: i0,
3518
3739
  template: `
3519
- <div
3520
- data-side-panel-header
3521
- class="border-border flex min-h-16 min-w-0 shrink-0 items-center gap-3 border-b py-4 ps-6 pe-14"
3522
- >
3523
- @if (title()) {
3524
- <h2 [id]="titleId" class="text-heading min-w-0 flex-1 wrap-anywhere">{{ title() }}</h2>
3525
- }
3526
- <ng-content select="[bfSlot=header]" />
3740
+ <div data-start class="flex shrink-0 items-center gap-1 empty:hidden">
3741
+ <ng-content select="[bfSlot=start]" />
3527
3742
  </div>
3528
- <div
3529
- #body
3530
- data-side-panel-body
3531
- class="focus-visible:ring-ring min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4 focus-visible:ring-4 focus-visible:outline-hidden focus-visible:ring-inset"
3532
- [attr.tabindex]="overflowing() ? 0 : null"
3533
- [attr.role]="overflowing() && title() ? 'region' : null"
3534
- [attr.aria-labelledby]="overflowing() && title() ? titleId : null"
3535
- >
3536
- <div #content class="flex flex-col gap-4"><ng-content /></div>
3743
+ <bf-textarea
3744
+ [class]="fieldClasses()"
3745
+ autoResize
3746
+ [rows]="1"
3747
+ [maxRows]="maxRows()"
3748
+ [size]="resolvedSize()"
3749
+ [label]="label()"
3750
+ [ariaLabel]="fieldName()"
3751
+ [ariaLabelledby]="ariaLabelledby()"
3752
+ [placeholder]="fieldPlaceholder()"
3753
+ [disabled]="disabled()"
3754
+ [maxLength]="maxLength()"
3755
+ [name]="name()"
3756
+ [value]="value()"
3757
+ (changed)="onChanged($event)"
3758
+ (keydown)="onKeydown($event)"
3759
+ />
3760
+ <div data-end class="flex shrink-0 items-center gap-1 empty:hidden">
3761
+ <ng-content select="[bfSlot=end]" />
3537
3762
  </div>
3538
- <!-- A projected wrapper (<div bfSlot="footer">) lays out its buttons like the row itself. -->
3539
- <div
3540
- data-side-panel-footer
3541
- class="border-border flex shrink-0 flex-wrap justify-end gap-2 border-t px-6 py-4 *:flex *:min-w-0 *:flex-wrap *:justify-end *:gap-2 empty:hidden"
3763
+ <button
3764
+ type="button"
3765
+ data-action
3766
+ [attr.data-mode]="action()"
3767
+ [class]="buttonClasses()"
3768
+ [disabled]="nativeDisabled()"
3769
+ [attr.aria-disabled]="blocked() ? 'true' : null"
3770
+ [bfTooltip]="blocked() ? disabledReason() : ''"
3771
+ (click)="onButton()"
3542
3772
  >
3543
- <ng-content select="[bfSlot=footer]" />
3544
- </div>
3545
- <!-- Last in DOM order so first-tabbable focus lands on the content, not on the ×. -->
3546
- <bf-icon-button
3547
- data-side-panel-close
3548
- class="absolute end-3 top-4"
3549
- variant="text"
3550
- size="sm"
3551
- [label]="closeName()"
3552
- (clicked)="close()"
3553
- >
3554
- <bf-icon [icon]="closeIcon" />
3555
- </bf-icon-button>
3773
+ <!-- display: contents, so a projected icon is the button's own flex item. -->
3774
+ <span
3775
+ [class]="action() === 'send' ? 'contents' : 'hidden'"
3776
+ [attr.data-icon]="action() === 'send' ? '' : null"
3777
+ ><ng-content select="[bfSlot=send-icon]"
3778
+ /></span>
3779
+ <span
3780
+ [class]="action() === 'stop' ? 'contents' : 'hidden'"
3781
+ [attr.data-icon]="action() === 'stop' ? '' : null"
3782
+ ><ng-content select="[bfSlot=stop-icon]"
3783
+ /></span>
3784
+ <span data-text class="[[data-icon]:has(>*)~&]:sr-only">{{ buttonName() }}</span>
3785
+ </button>
3556
3786
  `,
3557
3787
  isInline: true,
3558
3788
  dependencies: [{
3559
3789
  kind: "component",
3560
- type: BfIconButtonComponent,
3561
- selector: "bf-icon-button, a[bfIconButton]",
3790
+ type: BfTextareaComponent,
3791
+ selector: "bf-textarea",
3562
3792
  inputs: [
3563
- "variant",
3793
+ "value",
3564
3794
  "size",
3565
- "type",
3795
+ "label",
3796
+ "ariaLabel",
3797
+ "ariaLabelledby",
3798
+ "placeholder",
3799
+ "helpText",
3800
+ "errorMessage",
3566
3801
  "disabled",
3567
- "label"
3802
+ "readonly",
3803
+ "required",
3804
+ "invalid",
3805
+ "touched",
3806
+ "errors",
3807
+ "name",
3808
+ "id",
3809
+ "rows",
3810
+ "autoResize",
3811
+ "maxRows",
3812
+ "maxLength",
3813
+ "minLength"
3568
3814
  ],
3569
- outputs: ["clicked"]
3570
- }, {
3571
- kind: "component",
3572
- type: BfIconComponent,
3573
- selector: "bf-icon",
3574
- inputs: [
3575
- "icon",
3576
- "size",
3577
- "label",
3578
- "filled"
3815
+ outputs: [
3816
+ "valueChange",
3817
+ "changed",
3818
+ "focused",
3819
+ "blurred",
3820
+ "touch"
3579
3821
  ]
3822
+ }, {
3823
+ kind: "directive",
3824
+ type: BfTooltipDirective,
3825
+ selector: "[bfTooltip]",
3826
+ inputs: ["bfTooltip", "bfTooltipPlacement"]
3580
3827
  }]
3581
3828
  });
3582
3829
  };
@@ -3584,105 +3831,459 @@ i0.ɵɵngDeclareClassMetadata({
3584
3831
  minVersion: "12.0.0",
3585
3832
  version: "22.1.7",
3586
3833
  ngImport: i0,
3587
- type: BfSidePanelComponent,
3834
+ type: BfChatComposerComponent,
3588
3835
  decorators: [{
3589
3836
  type: Component,
3590
3837
  args: [{
3591
- selector: "bf-side-panel",
3592
- imports: [BfIconButtonComponent, BfIconComponent],
3838
+ selector: "bf-chat-composer",
3839
+ imports: [BfTextareaComponent, BfTooltipDirective],
3593
3840
  host: {
3594
- "[class]": "hostClasses()",
3595
- "[attr.data-bf-side-panel-id]": "titleId"
3841
+ class: "flex items-end gap-2",
3842
+ "[attr.data-running]": "running() ? '' : null"
3596
3843
  },
3597
3844
  template: `
3598
- <div
3599
- data-side-panel-header
3600
- class="border-border flex min-h-16 min-w-0 shrink-0 items-center gap-3 border-b py-4 ps-6 pe-14"
3601
- >
3602
- @if (title()) {
3603
- <h2 [id]="titleId" class="text-heading min-w-0 flex-1 wrap-anywhere">{{ title() }}</h2>
3604
- }
3605
- <ng-content select="[bfSlot=header]" />
3606
- </div>
3607
- <div
3608
- #body
3609
- data-side-panel-body
3610
- class="focus-visible:ring-ring min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4 focus-visible:ring-4 focus-visible:outline-hidden focus-visible:ring-inset"
3611
- [attr.tabindex]="overflowing() ? 0 : null"
3612
- [attr.role]="overflowing() && title() ? 'region' : null"
3613
- [attr.aria-labelledby]="overflowing() && title() ? titleId : null"
3614
- >
3615
- <div #content class="flex flex-col gap-4"><ng-content /></div>
3845
+ <div data-start class="flex shrink-0 items-center gap-1 empty:hidden">
3846
+ <ng-content select="[bfSlot=start]" />
3616
3847
  </div>
3617
- <!-- A projected wrapper (<div bfSlot="footer">) lays out its buttons like the row itself. -->
3618
- <div
3619
- data-side-panel-footer
3620
- class="border-border flex shrink-0 flex-wrap justify-end gap-2 border-t px-6 py-4 *:flex *:min-w-0 *:flex-wrap *:justify-end *:gap-2 empty:hidden"
3621
- >
3622
- <ng-content select="[bfSlot=footer]" />
3848
+ <bf-textarea
3849
+ [class]="fieldClasses()"
3850
+ autoResize
3851
+ [rows]="1"
3852
+ [maxRows]="maxRows()"
3853
+ [size]="resolvedSize()"
3854
+ [label]="label()"
3855
+ [ariaLabel]="fieldName()"
3856
+ [ariaLabelledby]="ariaLabelledby()"
3857
+ [placeholder]="fieldPlaceholder()"
3858
+ [disabled]="disabled()"
3859
+ [maxLength]="maxLength()"
3860
+ [name]="name()"
3861
+ [value]="value()"
3862
+ (changed)="onChanged($event)"
3863
+ (keydown)="onKeydown($event)"
3864
+ />
3865
+ <div data-end class="flex shrink-0 items-center gap-1 empty:hidden">
3866
+ <ng-content select="[bfSlot=end]" />
3623
3867
  </div>
3624
- <!-- Last in DOM order so first-tabbable focus lands on the content, not on the ×. -->
3625
- <bf-icon-button
3626
- data-side-panel-close
3627
- class="absolute end-3 top-4"
3628
- variant="text"
3629
- size="sm"
3630
- [label]="closeName()"
3631
- (clicked)="close()"
3868
+ <button
3869
+ type="button"
3870
+ data-action
3871
+ [attr.data-mode]="action()"
3872
+ [class]="buttonClasses()"
3873
+ [disabled]="nativeDisabled()"
3874
+ [attr.aria-disabled]="blocked() ? 'true' : null"
3875
+ [bfTooltip]="blocked() ? disabledReason() : ''"
3876
+ (click)="onButton()"
3632
3877
  >
3633
- <bf-icon [icon]="closeIcon" />
3634
- </bf-icon-button>
3878
+ <!-- display: contents, so a projected icon is the button's own flex item. -->
3879
+ <span
3880
+ [class]="action() === 'send' ? 'contents' : 'hidden'"
3881
+ [attr.data-icon]="action() === 'send' ? '' : null"
3882
+ ><ng-content select="[bfSlot=send-icon]"
3883
+ /></span>
3884
+ <span
3885
+ [class]="action() === 'stop' ? 'contents' : 'hidden'"
3886
+ [attr.data-icon]="action() === 'stop' ? '' : null"
3887
+ ><ng-content select="[bfSlot=stop-icon]"
3888
+ /></span>
3889
+ <span data-text class="[[data-icon]:has(>*)~&]:sr-only">{{ buttonName() }}</span>
3890
+ </button>
3635
3891
  `
3636
3892
  }]
3637
3893
  }],
3638
- ctorParameters: () => [],
3639
3894
  propDecorators: {
3640
- title: [{
3895
+ value: [{
3641
3896
  type: i0.Input,
3642
3897
  args: [{
3643
3898
  isSignal: true,
3644
- alias: "title",
3899
+ alias: "value",
3645
3900
  required: false
3646
3901
  }]
3902
+ }, {
3903
+ type: i0.Output,
3904
+ args: ["valueChange"]
3647
3905
  }],
3648
- closeLabel: [{
3906
+ running: [{
3649
3907
  type: i0.Input,
3650
3908
  args: [{
3651
3909
  isSignal: true,
3652
- alias: "closeLabel",
3910
+ alias: "running",
3653
3911
  required: false
3654
3912
  }]
3655
3913
  }],
3656
- body: [{
3657
- type: i0.ViewChild,
3658
- args: ["body", { isSignal: true }]
3914
+ disabled: [{
3915
+ type: i0.Input,
3916
+ args: [{
3917
+ isSignal: true,
3918
+ alias: "disabled",
3919
+ required: false
3920
+ }]
3659
3921
  }],
3660
- content: [{
3922
+ disabledReason: [{
3923
+ type: i0.Input,
3924
+ args: [{
3925
+ isSignal: true,
3926
+ alias: "disabledReason",
3927
+ required: false
3928
+ }]
3929
+ }],
3930
+ label: [{
3931
+ type: i0.Input,
3932
+ args: [{
3933
+ isSignal: true,
3934
+ alias: "label",
3935
+ required: false
3936
+ }]
3937
+ }],
3938
+ ariaLabel: [{
3939
+ type: i0.Input,
3940
+ args: [{
3941
+ isSignal: true,
3942
+ alias: "ariaLabel",
3943
+ required: false
3944
+ }]
3945
+ }],
3946
+ ariaLabelledby: [{
3947
+ type: i0.Input,
3948
+ args: [{
3949
+ isSignal: true,
3950
+ alias: "ariaLabelledby",
3951
+ required: false
3952
+ }]
3953
+ }],
3954
+ placeholder: [{
3955
+ type: i0.Input,
3956
+ args: [{
3957
+ isSignal: true,
3958
+ alias: "placeholder",
3959
+ required: false
3960
+ }]
3961
+ }],
3962
+ maxRows: [{
3963
+ type: i0.Input,
3964
+ args: [{
3965
+ isSignal: true,
3966
+ alias: "maxRows",
3967
+ required: false
3968
+ }]
3969
+ }],
3970
+ maxLength: [{
3971
+ type: i0.Input,
3972
+ args: [{
3973
+ isSignal: true,
3974
+ alias: "maxLength",
3975
+ required: false
3976
+ }]
3977
+ }],
3978
+ name: [{
3979
+ type: i0.Input,
3980
+ args: [{
3981
+ isSignal: true,
3982
+ alias: "name",
3983
+ required: false
3984
+ }]
3985
+ }],
3986
+ size: [{
3987
+ type: i0.Input,
3988
+ args: [{
3989
+ isSignal: true,
3990
+ alias: "size",
3991
+ required: false
3992
+ }]
3993
+ }],
3994
+ changed: [{
3995
+ type: i0.Output,
3996
+ args: ["changed"]
3997
+ }],
3998
+ sent: [{
3999
+ type: i0.Output,
4000
+ args: ["sent"]
4001
+ }],
4002
+ stopped: [{
4003
+ type: i0.Output,
4004
+ args: ["stopped"]
4005
+ }],
4006
+ field: [{
3661
4007
  type: i0.ViewChild,
3662
- args: ["content", { isSignal: true }]
4008
+ args: [i0.forwardRef(() => BfTextareaComponent), { isSignal: true }]
3663
4009
  }]
3664
4010
  }
3665
4011
  });
3666
- const BF_SIDE_PANEL_TEMPLATE = new InjectionToken("BF_SIDE_PANEL_TEMPLATE");
3667
- var BfSidePanelTemplateHostComponent = class BfSidePanelTemplateHostComponent {
3668
- view = inject(BF_SIDE_PANEL_TEMPLATE);
3669
- injector = inject(Injector);
4012
+ const BF_SIDE_PANEL_DATA = new InjectionToken("BF_SIDE_PANEL_DATA");
4013
+ const BF_SIDE_PANEL_CONTEXT = new InjectionToken("BF_SIDE_PANEL_CONTEXT");
4014
+ var BfSidePanelRef = class {
4015
+ overlayRef;
4016
+ instance;
4017
+ id;
4018
+ afterClosed;
4019
+ closed;
4020
+ constructor(overlayRef, instance = () => null) {
4021
+ this.overlayRef = overlayRef;
4022
+ this.instance = instance;
4023
+ this.id = overlayRef.id;
4024
+ this.afterClosed = overlayRef.afterClosed;
4025
+ this.closed = overlayRef.closed;
4026
+ }
4027
+ get componentInstance() {
4028
+ return this.instance();
4029
+ }
4030
+ close(result) {
4031
+ this.overlayRef.close(result);
4032
+ }
4033
+ };
4034
+ const HOST_BASE = "relative flex h-full flex-col border-s border-border bg-surface text-text shadow-floating";
4035
+ const WIDTH = {
4036
+ sm: "w-[min(22.5rem,100vw)]",
4037
+ md: "w-[min(30rem,100vw)]",
4038
+ lg: "w-[min(40rem,100vw)]"
4039
+ };
4040
+ var BfSidePanelComponent = class BfSidePanelComponent {
4041
+ labels = inject(BF_LABELS);
4042
+ context = inject(BF_SIDE_PANEL_CONTEXT, { optional: true });
4043
+ ref = inject(BfSidePanelRef, { optional: true });
4044
+ title = input("", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
4045
+ closeLabel = input("", ...ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []);
4046
+ overflowing = signal(false, ...ngDevMode ? [{ debugName: "overflowing" }] : /* istanbul ignore next */ []);
4047
+ titleId = this.context?.titleId ?? ɵuniqueId("bf-side-panel-title");
4048
+ closeIcon = X;
4049
+ width = this.context?.width ?? "md";
4050
+ hostClasses = computed(() => `${HOST_BASE} ${WIDTH[this.width]}`, ...ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []);
4051
+ closeName = computed(() => this.closeLabel() || this.labels.close, ...ngDevMode ? [{ debugName: "closeName" }] : /* istanbul ignore next */ []);
4052
+ body = viewChild.required("body", ...ngDevMode ? [{ debugName: "body" }] : /* istanbul ignore next */ []);
4053
+ content = viewChild.required("content", ...ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []);
4054
+ constructor() {
4055
+ let observer = null;
4056
+ afterNextRender(() => {
4057
+ const body = this.body().nativeElement;
4058
+ const measure = () => this.overflowing.set(body.scrollHeight > body.clientHeight);
4059
+ measure();
4060
+ if (typeof ResizeObserver === "function") {
4061
+ observer = new ResizeObserver(measure);
4062
+ observer.observe(body);
4063
+ observer.observe(this.content().nativeElement);
4064
+ }
4065
+ });
4066
+ inject(DestroyRef).onDestroy(() => observer?.disconnect());
4067
+ }
4068
+ close() {
4069
+ this.ref?.close();
4070
+ }
3670
4071
  static ɵfac = i0.ɵɵngDeclareFactory({
3671
4072
  minVersion: "12.0.0",
3672
4073
  version: "22.1.7",
3673
4074
  ngImport: i0,
3674
- type: BfSidePanelTemplateHostComponent,
4075
+ type: BfSidePanelComponent,
3675
4076
  deps: [],
3676
4077
  target: i0.ɵɵFactoryTarget.Component
3677
4078
  });
3678
4079
  static ɵcmp = i0.ɵɵngDeclareComponent({
3679
- minVersion: "14.0.0",
4080
+ minVersion: "17.0.0",
3680
4081
  version: "22.1.7",
3681
- type: BfSidePanelTemplateHostComponent,
4082
+ type: BfSidePanelComponent,
3682
4083
  isStandalone: true,
3683
- selector: "bf-side-panel-template-host",
3684
- host: { styleAttribute: "display: contents" },
3685
- ngImport: i0,
4084
+ selector: "bf-side-panel",
4085
+ inputs: {
4086
+ title: {
4087
+ classPropertyName: "title",
4088
+ publicName: "title",
4089
+ isSignal: true,
4090
+ isRequired: false,
4091
+ transformFunction: null
4092
+ },
4093
+ closeLabel: {
4094
+ classPropertyName: "closeLabel",
4095
+ publicName: "closeLabel",
4096
+ isSignal: true,
4097
+ isRequired: false,
4098
+ transformFunction: null
4099
+ }
4100
+ },
4101
+ host: { properties: {
4102
+ "class": "hostClasses()",
4103
+ "attr.data-bf-side-panel-id": "titleId"
4104
+ } },
4105
+ viewQueries: [{
4106
+ propertyName: "body",
4107
+ first: true,
4108
+ predicate: ["body"],
4109
+ descendants: true,
4110
+ isSignal: true
4111
+ }, {
4112
+ propertyName: "content",
4113
+ first: true,
4114
+ predicate: ["content"],
4115
+ descendants: true,
4116
+ isSignal: true
4117
+ }],
4118
+ ngImport: i0,
4119
+ template: `
4120
+ <div
4121
+ data-side-panel-header
4122
+ class="border-border flex min-h-16 min-w-0 shrink-0 items-center gap-3 border-b py-4 ps-6 pe-14"
4123
+ >
4124
+ @if (title()) {
4125
+ <h2 [id]="titleId" class="text-heading min-w-0 flex-1 wrap-anywhere">{{ title() }}</h2>
4126
+ }
4127
+ <ng-content select="[bfSlot=header]" />
4128
+ </div>
4129
+ <div
4130
+ #body
4131
+ data-side-panel-body
4132
+ class="focus-visible:ring-ring min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4 focus-visible:ring-4 focus-visible:outline-hidden focus-visible:ring-inset"
4133
+ [attr.tabindex]="overflowing() ? 0 : null"
4134
+ [attr.role]="overflowing() && title() ? 'region' : null"
4135
+ [attr.aria-labelledby]="overflowing() && title() ? titleId : null"
4136
+ >
4137
+ <div #content class="flex flex-col gap-4"><ng-content /></div>
4138
+ </div>
4139
+ <!-- A projected wrapper (<div bfSlot="footer">) lays out its buttons like the row itself. -->
4140
+ <div
4141
+ data-side-panel-footer
4142
+ class="border-border flex shrink-0 flex-wrap justify-end gap-2 border-t px-6 py-4 *:flex *:min-w-0 *:flex-wrap *:justify-end *:gap-2 empty:hidden"
4143
+ >
4144
+ <ng-content select="[bfSlot=footer]" />
4145
+ </div>
4146
+ <!-- Last in DOM order so first-tabbable focus lands on the content, not on the ×. -->
4147
+ <bf-icon-button
4148
+ data-side-panel-close
4149
+ class="absolute end-3 top-4"
4150
+ variant="text"
4151
+ size="sm"
4152
+ [label]="closeName()"
4153
+ (clicked)="close()"
4154
+ >
4155
+ <bf-icon [icon]="closeIcon" />
4156
+ </bf-icon-button>
4157
+ `,
4158
+ isInline: true,
4159
+ dependencies: [{
4160
+ kind: "component",
4161
+ type: BfIconButtonComponent,
4162
+ selector: "bf-icon-button, a[bfIconButton]",
4163
+ inputs: [
4164
+ "variant",
4165
+ "size",
4166
+ "type",
4167
+ "disabled",
4168
+ "label"
4169
+ ],
4170
+ outputs: ["clicked"]
4171
+ }, {
4172
+ kind: "component",
4173
+ type: BfIconComponent,
4174
+ selector: "bf-icon",
4175
+ inputs: [
4176
+ "icon",
4177
+ "size",
4178
+ "label",
4179
+ "filled"
4180
+ ]
4181
+ }]
4182
+ });
4183
+ };
4184
+ i0.ɵɵngDeclareClassMetadata({
4185
+ minVersion: "12.0.0",
4186
+ version: "22.1.7",
4187
+ ngImport: i0,
4188
+ type: BfSidePanelComponent,
4189
+ decorators: [{
4190
+ type: Component,
4191
+ args: [{
4192
+ selector: "bf-side-panel",
4193
+ imports: [BfIconButtonComponent, BfIconComponent],
4194
+ host: {
4195
+ "[class]": "hostClasses()",
4196
+ "[attr.data-bf-side-panel-id]": "titleId"
4197
+ },
4198
+ template: `
4199
+ <div
4200
+ data-side-panel-header
4201
+ class="border-border flex min-h-16 min-w-0 shrink-0 items-center gap-3 border-b py-4 ps-6 pe-14"
4202
+ >
4203
+ @if (title()) {
4204
+ <h2 [id]="titleId" class="text-heading min-w-0 flex-1 wrap-anywhere">{{ title() }}</h2>
4205
+ }
4206
+ <ng-content select="[bfSlot=header]" />
4207
+ </div>
4208
+ <div
4209
+ #body
4210
+ data-side-panel-body
4211
+ class="focus-visible:ring-ring min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4 focus-visible:ring-4 focus-visible:outline-hidden focus-visible:ring-inset"
4212
+ [attr.tabindex]="overflowing() ? 0 : null"
4213
+ [attr.role]="overflowing() && title() ? 'region' : null"
4214
+ [attr.aria-labelledby]="overflowing() && title() ? titleId : null"
4215
+ >
4216
+ <div #content class="flex flex-col gap-4"><ng-content /></div>
4217
+ </div>
4218
+ <!-- A projected wrapper (<div bfSlot="footer">) lays out its buttons like the row itself. -->
4219
+ <div
4220
+ data-side-panel-footer
4221
+ class="border-border flex shrink-0 flex-wrap justify-end gap-2 border-t px-6 py-4 *:flex *:min-w-0 *:flex-wrap *:justify-end *:gap-2 empty:hidden"
4222
+ >
4223
+ <ng-content select="[bfSlot=footer]" />
4224
+ </div>
4225
+ <!-- Last in DOM order so first-tabbable focus lands on the content, not on the ×. -->
4226
+ <bf-icon-button
4227
+ data-side-panel-close
4228
+ class="absolute end-3 top-4"
4229
+ variant="text"
4230
+ size="sm"
4231
+ [label]="closeName()"
4232
+ (clicked)="close()"
4233
+ >
4234
+ <bf-icon [icon]="closeIcon" />
4235
+ </bf-icon-button>
4236
+ `
4237
+ }]
4238
+ }],
4239
+ ctorParameters: () => [],
4240
+ propDecorators: {
4241
+ title: [{
4242
+ type: i0.Input,
4243
+ args: [{
4244
+ isSignal: true,
4245
+ alias: "title",
4246
+ required: false
4247
+ }]
4248
+ }],
4249
+ closeLabel: [{
4250
+ type: i0.Input,
4251
+ args: [{
4252
+ isSignal: true,
4253
+ alias: "closeLabel",
4254
+ required: false
4255
+ }]
4256
+ }],
4257
+ body: [{
4258
+ type: i0.ViewChild,
4259
+ args: ["body", { isSignal: true }]
4260
+ }],
4261
+ content: [{
4262
+ type: i0.ViewChild,
4263
+ args: ["content", { isSignal: true }]
4264
+ }]
4265
+ }
4266
+ });
4267
+ const BF_SIDE_PANEL_TEMPLATE = new InjectionToken("BF_SIDE_PANEL_TEMPLATE");
4268
+ var BfSidePanelTemplateHostComponent = class BfSidePanelTemplateHostComponent {
4269
+ view = inject(BF_SIDE_PANEL_TEMPLATE);
4270
+ injector = inject(Injector);
4271
+ static ɵfac = i0.ɵɵngDeclareFactory({
4272
+ minVersion: "12.0.0",
4273
+ version: "22.1.7",
4274
+ ngImport: i0,
4275
+ type: BfSidePanelTemplateHostComponent,
4276
+ deps: [],
4277
+ target: i0.ɵɵFactoryTarget.Component
4278
+ });
4279
+ static ɵcmp = i0.ɵɵngDeclareComponent({
4280
+ minVersion: "14.0.0",
4281
+ version: "22.1.7",
4282
+ type: BfSidePanelTemplateHostComponent,
4283
+ isStandalone: true,
4284
+ selector: "bf-side-panel-template-host",
4285
+ host: { styleAttribute: "display: contents" },
4286
+ ngImport: i0,
3686
4287
  template: `<ng-container
3687
4288
  [ngTemplateOutlet]="view.template"
3688
4289
  [ngTemplateOutletContext]="view.context"
@@ -3825,7 +4426,7 @@ i0.ɵɵngDeclareClassMetadata({
3825
4426
  }]
3826
4427
  });
3827
4428
  var BfPopoverTriggerDirective = class BfPopoverTriggerDirective {
3828
- overlay = inject(Overlay);
4429
+ environment = inject(EnvironmentInjector);
3829
4430
  document = inject(DOCUMENT);
3830
4431
  injector = inject(Injector);
3831
4432
  viewContainer = inject(ViewContainerRef);
@@ -3841,6 +4442,7 @@ var BfPopoverTriggerDirective = class BfPopoverTriggerDirective {
3841
4442
  isOpen = this.openState.asReadonly();
3842
4443
  panelId = ɵuniqueId("bf-popover");
3843
4444
  anchored = null;
4445
+ motion = new AnchoredMotion();
3844
4446
  subscriptions = [];
3845
4447
  stopNavigation = null;
3846
4448
  stopHeading = null;
@@ -3853,18 +4455,22 @@ var BfPopoverTriggerDirective = class BfPopoverTriggerDirective {
3853
4455
  if (open) control.setAttribute("aria-controls", this.panelId);
3854
4456
  else control.removeAttribute("aria-controls");
3855
4457
  } });
3856
- inject(DestroyRef).onDestroy(() => this.teardown());
4458
+ inject(DestroyRef).onDestroy(() => {
4459
+ this.teardown(false);
4460
+ this.motion.flush();
4461
+ });
3857
4462
  }
3858
4463
  open() {
3859
4464
  if (this.anchored) return;
4465
+ this.motion.flush();
3860
4466
  const direction = this.dir?.value ?? "ltr";
3861
- const anchored = createAnchoredOverlay(this.overlay, this.host, {
4467
+ const anchored = createAnchoredOverlay(this.environment, this.host, {
3862
4468
  positions: blockPositions(this.bfPopoverPlacement()),
3863
4469
  direction,
3864
4470
  panelClass: ["bf-popover-panel"]
3865
4471
  });
3866
4472
  this.anchored = anchored;
3867
- anchored.ref.attach(new TemplatePortal(this.bfPopoverTriggerFor(), this.viewContainer, void 0, this.injector));
4473
+ const view = anchored.ref.attach(new TemplatePortal(this.bfPopoverTriggerFor(), this.viewContainer, void 0, this.injector));
3868
4474
  const pane = anchored.ref.overlayElement;
3869
4475
  this.subscriptions = [anchored.ref.keydownEvents().subscribe((event) => {
3870
4476
  if (event.key === "Escape") {
@@ -3879,6 +4485,7 @@ var BfPopoverTriggerDirective = class BfPopoverTriggerDirective {
3879
4485
  pane.addEventListener("keydown", (event) => this.tabbed(event, pane));
3880
4486
  this.stopNavigation = watchPathChange(this.location, () => this.close(false));
3881
4487
  this.openState.set(true);
4488
+ this.motion.enter(anchored.ref, this.host, view);
3882
4489
  const panel = pane.querySelector("bf-popover") ?? pane;
3883
4490
  this.name(panel);
3884
4491
  (firstTabbable(panel) ?? panel).focus({ preventScroll: true });
@@ -3886,7 +4493,7 @@ var BfPopoverTriggerDirective = class BfPopoverTriggerDirective {
3886
4493
  }
3887
4494
  close(restoreFocus = true) {
3888
4495
  if (!this.anchored) return;
3889
- this.teardown();
4496
+ this.teardown(true);
3890
4497
  this.openState.set(false);
3891
4498
  if (restoreFocus) this.control().focus({ preventScroll: true });
3892
4499
  this.bfPopoverClosed.emit();
@@ -3917,15 +4524,17 @@ var BfPopoverTriggerDirective = class BfPopoverTriggerDirective {
3917
4524
  panel.setAttribute("aria-labelledby", this.labelId());
3918
4525
  this.stopHeading = watchPanelHeading(panel);
3919
4526
  }
3920
- teardown() {
4527
+ teardown(animate) {
3921
4528
  this.stopHeading?.();
3922
4529
  this.stopHeading = null;
3923
4530
  this.subscriptions.forEach((s) => s.unsubscribe());
3924
4531
  this.subscriptions = [];
3925
4532
  this.stopNavigation?.();
3926
4533
  this.stopNavigation = null;
3927
- this.anchored?.ref.dispose();
4534
+ const ref = this.anchored?.ref;
3928
4535
  this.anchored = null;
4536
+ if (ref && animate) this.motion.leave(ref, this.host);
4537
+ else ref?.dispose();
3929
4538
  }
3930
4539
  static ɵfac = i0.ɵɵngDeclareFactory({
3931
4540
  minVersion: "12.0.0",
@@ -4091,7 +4700,7 @@ function labelContains(option, query) {
4091
4700
  }
4092
4701
  var ComboboxCore = class {
4093
4702
  host;
4094
- overlay = inject(Overlay);
4703
+ environment = inject(EnvironmentInjector);
4095
4704
  injector = inject(Injector);
4096
4705
  viewContainer = inject(ViewContainerRef);
4097
4706
  dir = inject(Directionality, { optional: true });
@@ -4141,10 +4750,15 @@ var ComboboxCore = class {
4141
4750
  }, ...ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []);
4142
4751
  controls = computed(() => this.isOpen() && this.items().length > 0 ? this.listboxId : null, ...ngDevMode ? [{ debugName: "controls" }] : /* istanbul ignore next */ []);
4143
4752
  anchored = null;
4753
+ panelView = null;
4754
+ motion = new AnchoredMotion();
4144
4755
  subscriptions = [];
4145
4756
  constructor(host) {
4146
4757
  this.host = host;
4147
- inject(DestroyRef).onDestroy(() => this.teardown());
4758
+ inject(DestroyRef).onDestroy(() => {
4759
+ this.teardown(false);
4760
+ this.motion.flush();
4761
+ });
4148
4762
  }
4149
4763
  setQuery(query) {
4150
4764
  const before = this.queryState() ?? "";
@@ -4158,15 +4772,18 @@ var ComboboxCore = class {
4158
4772
  }
4159
4773
  open() {
4160
4774
  if (this.anchored) return;
4775
+ this.motion.flush();
4161
4776
  const anchor = this.host.anchor();
4162
- const anchored = createAnchoredOverlay(this.overlay, anchor, {
4777
+ const anchored = createAnchoredOverlay(this.environment, anchor, {
4163
4778
  positions: blockPositions("below-start"),
4164
4779
  direction: this.dir?.value ?? "ltr",
4165
4780
  panelClass: ["bf-combobox-panel"]
4166
4781
  });
4167
4782
  this.anchored = anchored;
4168
4783
  anchored.ref.updateSize({ width: anchor.getBoundingClientRect().width || void 0 });
4169
- anchored.ref.attach(new ComponentPortal(this.host.panel, this.viewContainer, this.injector)).setInput("core", this);
4784
+ const panel = anchored.ref.attach(new ComponentPortal(this.host.panel, this.viewContainer, this.injector));
4785
+ panel.setInput("core", this);
4786
+ this.panelView = panel.changeDetectorRef;
4170
4787
  this.subscriptions = [anchored.ref.keydownEvents().subscribe((event) => {
4171
4788
  if (event.key === "Escape") {
4172
4789
  event.preventDefault();
@@ -4176,10 +4793,11 @@ var ComboboxCore = class {
4176
4793
  if (!(event.target instanceof Node && anchor.contains(event.target))) this.close();
4177
4794
  })];
4178
4795
  this.openState.set(true);
4796
+ this.motion.enter(anchored.ref, anchor, panel.changeDetectorRef);
4179
4797
  }
4180
4798
  close() {
4181
4799
  if (!this.anchored) return;
4182
- this.teardown();
4800
+ this.teardown(true);
4183
4801
  this.openState.set(false);
4184
4802
  this.activeIndex.set(null);
4185
4803
  }
@@ -4238,11 +4856,15 @@ var ComboboxCore = class {
4238
4856
  firstEnabled(items) {
4239
4857
  return items.find((item) => !item.option.disabled);
4240
4858
  }
4241
- teardown() {
4859
+ teardown(animate) {
4242
4860
  this.subscriptions.forEach((s) => s.unsubscribe());
4243
4861
  this.subscriptions = [];
4244
- this.anchored?.ref.dispose();
4862
+ const ref = this.anchored?.ref;
4863
+ const view = this.panelView;
4245
4864
  this.anchored = null;
4865
+ this.panelView = null;
4866
+ if (ref && animate) this.motion.leave(ref, this.host.anchor(), view ?? void 0);
4867
+ else ref?.dispose();
4246
4868
  }
4247
4869
  };
4248
4870
  function sameValue(compare, optionValue, value) {
@@ -4260,7 +4882,7 @@ const COMBOBOX_CHEVRON_INSET = {
4260
4882
  md: "after:end-4",
4261
4883
  lg: "after:end-4.5"
4262
4884
  };
4263
- const PANEL = "block bg-surface text-text border border-border rounded-card shadow-floating p-1 max-h-[min(18rem,calc(100dvh-2rem))] overflow-y-auto overscroll-contain";
4885
+ const PANEL = "block w-full min-w-0 bg-surface text-text border border-border rounded-card shadow-floating p-1 max-h-[min(18rem,calc(100dvh-2rem))] overflow-y-auto overscroll-contain";
4264
4886
  const OPTION$1 = "flex w-full items-start gap-2 rounded-field px-2 text-start cursor-pointer select-none data-active:bg-surface-muted aria-disabled:cursor-not-allowed aria-disabled:text-text-disabled before:mt-[0.3em] before:h-[11px] before:w-[6px] before:mx-[5px] before:shrink-0 before:rotate-45 before:border-current before:content-[''] aria-selected:before:border-r-2 aria-selected:before:border-b-2 aria-selected:text-primary-text forced-colors:data-active:forced-color-adjust-none forced-colors:data-active:bg-[Highlight] forced-colors:data-active:text-[HighlightText] forced-colors:aria-disabled:text-[GrayText]";
4265
4887
  const SIZE = {
4266
4888
  sm: "min-h-8 py-1.5 text-label-sm pointer-coarse:min-h-hit",
@@ -4472,10 +5094,664 @@ i0.ɵɵngDeclareClassMetadata({
4472
5094
  alias: "core",
4473
5095
  required: true
4474
5096
  }]
4475
- }] }
5097
+ }] }
5098
+ });
5099
+ var BfComboboxComponent = class BfComboboxComponent {
5100
+ value = model(null, ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
5101
+ options = input.required(...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
5102
+ compareWith = input(Object.is, ...ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []);
5103
+ filter = input(labelContains, ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
5104
+ size = input(void 0, ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
5105
+ label = input("", ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
5106
+ ariaLabel = input("", ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
5107
+ ariaLabelledby = input("", ...ngDevMode ? [{ debugName: "ariaLabelledby" }] : /* istanbul ignore next */ []);
5108
+ placeholder = input("", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
5109
+ helpText = input("", ...ngDevMode ? [{ debugName: "helpText" }] : /* istanbul ignore next */ []);
5110
+ errorMessage = input("", ...ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []);
5111
+ emptyLabel = input("", ...ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []);
5112
+ loading = input(false, {
5113
+ ...ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {},
5114
+ transform: booleanAttribute
5115
+ });
5116
+ disabled = input(false, {
5117
+ ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
5118
+ transform: booleanAttribute
5119
+ });
5120
+ readonly = input(false, {
5121
+ ...ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {},
5122
+ transform: booleanAttribute
5123
+ });
5124
+ required = input(false, {
5125
+ ...ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {},
5126
+ transform: booleanAttribute
5127
+ });
5128
+ invalid = input(false, {
5129
+ ...ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {},
5130
+ transform: booleanAttribute
5131
+ });
5132
+ touched = input(false, {
5133
+ ...ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {},
5134
+ transform: booleanAttribute
5135
+ });
5136
+ errors = input([], ...ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []);
5137
+ name = input("", ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
5138
+ id = input(ɵuniqueId("bf-combobox"), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
5139
+ changed = output();
5140
+ queried = output();
5141
+ focused = output();
5142
+ blurred = output();
5143
+ touch = output();
5144
+ el = viewChild.required("el", ...ngDevMode ? [{ debugName: "el" }] : /* istanbul ignore next */ []);
5145
+ anchorEl = viewChild.required("anchor", ...ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []);
5146
+ defaults = inject(BF_DEFAULTS);
5147
+ labels = inject(BF_LABELS);
5148
+ pickedLabel = signal(null, ...ngDevMode ? [{ debugName: "pickedLabel" }] : /* istanbul ignore next */ []);
5149
+ resolvedSize = computed(() => this.size() ?? this.defaults.fieldSize, ...ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []);
5150
+ core = new ComboboxCore({
5151
+ options: this.options,
5152
+ filter: this.filter,
5153
+ isSelected: (option) => sameValue(this.compareWith(), option.value, this.value()),
5154
+ pick: (item) => this.pick(item),
5155
+ anchor: () => this.anchorEl().nativeElement,
5156
+ panel: BfComboboxListboxComponent,
5157
+ listboxLabel: computed(() => this.label() || this.ariaLabel() || null),
5158
+ listboxLabelledby: computed(() => this.label() ? null : this.ariaLabelledby() || null),
5159
+ multiple: false,
5160
+ loading: this.loading,
5161
+ emptyText: computed(() => this.emptyLabel() || this.labels.comboboxEmpty),
5162
+ size: this.resolvedSize,
5163
+ queried: (query) => this.queried.emit(query)
5164
+ });
5165
+ valueLabel = computed(() => {
5166
+ const value = this.value();
5167
+ if (value === null) return "";
5168
+ const compare = this.compareWith();
5169
+ const option = this.options().find((o) => compare(o.value, value));
5170
+ const picked = this.pickedLabel();
5171
+ return option?.label ?? (picked && compare(picked.value, value) ? picked.label : "");
5172
+ }, ...ngDevMode ? [{ debugName: "valueLabel" }] : /* istanbul ignore next */ []);
5173
+ text = computed(() => this.core.query() ?? this.valueLabel(), ...ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []);
5174
+ errorText = computed(() => ɵfieldErrorText(this.errorMessage(), this.touched(), this.errors(), this.labels.validation), ...ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []);
5175
+ showError = computed(() => ɵfieldShowsError(this.errorText(), this.invalid(), this.touched()), ...ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []);
5176
+ describedBy = computed(() => ɵfieldDescribedBy(this.id(), this.errorText(), this.helpText()), ...ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []);
5177
+ errorMessageId = computed(() => ɵfieldErrorMessageId(this.id(), this.errorText(), this.showError()), ...ngDevMode ? [{ debugName: "errorMessageId" }] : /* istanbul ignore next */ []);
5178
+ ariaLabelAttr = computed(() => this.label() ? null : this.ariaLabel() || null, ...ngDevMode ? [{ debugName: "ariaLabelAttr" }] : /* istanbul ignore next */ []);
5179
+ ariaLabelledbyAttr = computed(() => this.label() ? null : this.ariaLabelledby() || null, ...ngDevMode ? [{ debugName: "ariaLabelledbyAttr" }] : /* istanbul ignore next */ []);
5180
+ wrapperClasses = computed(() => `${COMBOBOX_CHEVRON} ${COMBOBOX_CHEVRON_INSET[this.resolvedSize()]}`, ...ngDevMode ? [{ debugName: "wrapperClasses" }] : /* istanbul ignore next */ []);
5181
+ classes = computed(() => [
5182
+ COMBOBOX_FIELD_BASE,
5183
+ ɵFIELD_BOX[this.resolvedSize()],
5184
+ ɵFIELD_PADDING_START[this.resolvedSize()].text,
5185
+ COMBOBOX_PADDING_END[this.resolvedSize()],
5186
+ this.showError() ? "border-danger-border hover:border-danger" : "border-border-strong hover:border-text-muted"
5187
+ ].join(" "), ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
5188
+ focus(options) {
5189
+ this.el().nativeElement.focus(options);
5190
+ }
5191
+ onInput(el) {
5192
+ if (this.readonly()) return;
5193
+ this.core.typed(el.value);
5194
+ }
5195
+ onClick() {
5196
+ if (!this.readonly() && !this.disabled()) this.core.open();
5197
+ }
5198
+ onKeydown(event) {
5199
+ if (this.readonly()) return;
5200
+ if (this.core.keydown(event)) return;
5201
+ if (event.key === "Escape" && !this.core.isOpen() && this.core.query() !== null) this.resetQuery(null);
5202
+ }
5203
+ onBlur(event) {
5204
+ this.core.close();
5205
+ const query = this.core.query();
5206
+ if (query !== null) {
5207
+ const typed = foldText(query.trim());
5208
+ const match = this.options().find((o) => !o.disabled && foldText(o.label) === typed);
5209
+ if (!typed && this.value() !== null) this.set(null);
5210
+ else if (typed && match && !sameValue(this.compareWith(), match.value, this.value())) this.set(match.value, match.label);
5211
+ this.resetQuery(null);
5212
+ }
5213
+ this.blurred.emit(event);
5214
+ this.touch.emit();
5215
+ }
5216
+ pick(item) {
5217
+ if (!sameValue(this.compareWith(), item.option.value, this.value())) this.set(item.option.value, item.option.label);
5218
+ this.resetQuery(null);
5219
+ this.core.close();
5220
+ }
5221
+ set(value, label = "") {
5222
+ this.pickedLabel.set(value === null ? null : {
5223
+ value,
5224
+ label
5225
+ });
5226
+ this.value.set(value);
5227
+ this.changed.emit(value);
5228
+ }
5229
+ resetQuery(query) {
5230
+ this.core.setQuery(query);
5231
+ this.el().nativeElement.value = this.text();
5232
+ }
5233
+ static ɵfac = i0.ɵɵngDeclareFactory({
5234
+ minVersion: "12.0.0",
5235
+ version: "22.1.7",
5236
+ ngImport: i0,
5237
+ type: BfComboboxComponent,
5238
+ deps: [],
5239
+ target: i0.ɵɵFactoryTarget.Component
5240
+ });
5241
+ static ɵcmp = i0.ɵɵngDeclareComponent({
5242
+ minVersion: "17.2.0",
5243
+ version: "22.1.7",
5244
+ type: BfComboboxComponent,
5245
+ isStandalone: true,
5246
+ selector: "bf-combobox",
5247
+ inputs: {
5248
+ value: {
5249
+ classPropertyName: "value",
5250
+ publicName: "value",
5251
+ isSignal: true,
5252
+ isRequired: false,
5253
+ transformFunction: null
5254
+ },
5255
+ options: {
5256
+ classPropertyName: "options",
5257
+ publicName: "options",
5258
+ isSignal: true,
5259
+ isRequired: true,
5260
+ transformFunction: null
5261
+ },
5262
+ compareWith: {
5263
+ classPropertyName: "compareWith",
5264
+ publicName: "compareWith",
5265
+ isSignal: true,
5266
+ isRequired: false,
5267
+ transformFunction: null
5268
+ },
5269
+ filter: {
5270
+ classPropertyName: "filter",
5271
+ publicName: "filter",
5272
+ isSignal: true,
5273
+ isRequired: false,
5274
+ transformFunction: null
5275
+ },
5276
+ size: {
5277
+ classPropertyName: "size",
5278
+ publicName: "size",
5279
+ isSignal: true,
5280
+ isRequired: false,
5281
+ transformFunction: null
5282
+ },
5283
+ label: {
5284
+ classPropertyName: "label",
5285
+ publicName: "label",
5286
+ isSignal: true,
5287
+ isRequired: false,
5288
+ transformFunction: null
5289
+ },
5290
+ ariaLabel: {
5291
+ classPropertyName: "ariaLabel",
5292
+ publicName: "ariaLabel",
5293
+ isSignal: true,
5294
+ isRequired: false,
5295
+ transformFunction: null
5296
+ },
5297
+ ariaLabelledby: {
5298
+ classPropertyName: "ariaLabelledby",
5299
+ publicName: "ariaLabelledby",
5300
+ isSignal: true,
5301
+ isRequired: false,
5302
+ transformFunction: null
5303
+ },
5304
+ placeholder: {
5305
+ classPropertyName: "placeholder",
5306
+ publicName: "placeholder",
5307
+ isSignal: true,
5308
+ isRequired: false,
5309
+ transformFunction: null
5310
+ },
5311
+ helpText: {
5312
+ classPropertyName: "helpText",
5313
+ publicName: "helpText",
5314
+ isSignal: true,
5315
+ isRequired: false,
5316
+ transformFunction: null
5317
+ },
5318
+ errorMessage: {
5319
+ classPropertyName: "errorMessage",
5320
+ publicName: "errorMessage",
5321
+ isSignal: true,
5322
+ isRequired: false,
5323
+ transformFunction: null
5324
+ },
5325
+ emptyLabel: {
5326
+ classPropertyName: "emptyLabel",
5327
+ publicName: "emptyLabel",
5328
+ isSignal: true,
5329
+ isRequired: false,
5330
+ transformFunction: null
5331
+ },
5332
+ loading: {
5333
+ classPropertyName: "loading",
5334
+ publicName: "loading",
5335
+ isSignal: true,
5336
+ isRequired: false,
5337
+ transformFunction: null
5338
+ },
5339
+ disabled: {
5340
+ classPropertyName: "disabled",
5341
+ publicName: "disabled",
5342
+ isSignal: true,
5343
+ isRequired: false,
5344
+ transformFunction: null
5345
+ },
5346
+ readonly: {
5347
+ classPropertyName: "readonly",
5348
+ publicName: "readonly",
5349
+ isSignal: true,
5350
+ isRequired: false,
5351
+ transformFunction: null
5352
+ },
5353
+ required: {
5354
+ classPropertyName: "required",
5355
+ publicName: "required",
5356
+ isSignal: true,
5357
+ isRequired: false,
5358
+ transformFunction: null
5359
+ },
5360
+ invalid: {
5361
+ classPropertyName: "invalid",
5362
+ publicName: "invalid",
5363
+ isSignal: true,
5364
+ isRequired: false,
5365
+ transformFunction: null
5366
+ },
5367
+ touched: {
5368
+ classPropertyName: "touched",
5369
+ publicName: "touched",
5370
+ isSignal: true,
5371
+ isRequired: false,
5372
+ transformFunction: null
5373
+ },
5374
+ errors: {
5375
+ classPropertyName: "errors",
5376
+ publicName: "errors",
5377
+ isSignal: true,
5378
+ isRequired: false,
5379
+ transformFunction: null
5380
+ },
5381
+ name: {
5382
+ classPropertyName: "name",
5383
+ publicName: "name",
5384
+ isSignal: true,
5385
+ isRequired: false,
5386
+ transformFunction: null
5387
+ },
5388
+ id: {
5389
+ classPropertyName: "id",
5390
+ publicName: "id",
5391
+ isSignal: true,
5392
+ isRequired: false,
5393
+ transformFunction: null
5394
+ }
5395
+ },
5396
+ outputs: {
5397
+ value: "valueChange",
5398
+ changed: "changed",
5399
+ queried: "queried",
5400
+ focused: "focused",
5401
+ blurred: "blurred",
5402
+ touch: "touch"
5403
+ },
5404
+ host: {
5405
+ properties: { "attr.id": "null" },
5406
+ classAttribute: "block"
5407
+ },
5408
+ viewQueries: [{
5409
+ propertyName: "el",
5410
+ first: true,
5411
+ predicate: ["el"],
5412
+ descendants: true,
5413
+ isSignal: true
5414
+ }, {
5415
+ propertyName: "anchorEl",
5416
+ first: true,
5417
+ predicate: ["anchor"],
5418
+ descendants: true,
5419
+ isSignal: true
5420
+ }],
5421
+ ngImport: i0,
5422
+ template: `
5423
+ <bf-field
5424
+ [label]="label()"
5425
+ [helpText]="helpText()"
5426
+ [error]="errorText()"
5427
+ [required]="required()"
5428
+ [disabled]="disabled()"
5429
+ [controlId]="id()"
5430
+ >
5431
+ <div #anchor [class]="wrapperClasses()">
5432
+ <input
5433
+ #el
5434
+ type="text"
5435
+ role="combobox"
5436
+ autocomplete="off"
5437
+ aria-autocomplete="list"
5438
+ [id]="id()"
5439
+ [attr.name]="name() || null"
5440
+ [value]="text()"
5441
+ [placeholder]="placeholder()"
5442
+ [disabled]="disabled()"
5443
+ [readOnly]="readonly()"
5444
+ [required]="required()"
5445
+ [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
5446
+ [attr.aria-controls]="core.controls()"
5447
+ [attr.aria-activedescendant]="core.active()?.id ?? null"
5448
+ [attr.aria-label]="ariaLabelAttr()"
5449
+ [attr.aria-labelledby]="ariaLabelledbyAttr()"
5450
+ [attr.aria-invalid]="showError() ? 'true' : null"
5451
+ [attr.aria-describedby]="describedBy()"
5452
+ [attr.aria-errormessage]="errorMessageId()"
5453
+ [class]="classes()"
5454
+ (input)="onInput(el)"
5455
+ (keydown)="onKeydown($event)"
5456
+ (click)="onClick()"
5457
+ (focus)="focused.emit($event)"
5458
+ (blur)="onBlur($event)"
5459
+ />
5460
+ </div>
5461
+ </bf-field>
5462
+ `,
5463
+ isInline: true,
5464
+ dependencies: [{
5465
+ kind: "component",
5466
+ type: BfFieldComponent,
5467
+ selector: "bf-field",
5468
+ inputs: [
5469
+ "label",
5470
+ "helpText",
5471
+ "error",
5472
+ "required",
5473
+ "disabled",
5474
+ "controlId"
5475
+ ]
5476
+ }]
5477
+ });
5478
+ };
5479
+ i0.ɵɵngDeclareClassMetadata({
5480
+ minVersion: "12.0.0",
5481
+ version: "22.1.7",
5482
+ ngImport: i0,
5483
+ type: BfComboboxComponent,
5484
+ decorators: [{
5485
+ type: Component,
5486
+ args: [{
5487
+ selector: "bf-combobox",
5488
+ imports: [BfFieldComponent],
5489
+ host: {
5490
+ class: "block",
5491
+ "[attr.id]": "null"
5492
+ },
5493
+ template: `
5494
+ <bf-field
5495
+ [label]="label()"
5496
+ [helpText]="helpText()"
5497
+ [error]="errorText()"
5498
+ [required]="required()"
5499
+ [disabled]="disabled()"
5500
+ [controlId]="id()"
5501
+ >
5502
+ <div #anchor [class]="wrapperClasses()">
5503
+ <input
5504
+ #el
5505
+ type="text"
5506
+ role="combobox"
5507
+ autocomplete="off"
5508
+ aria-autocomplete="list"
5509
+ [id]="id()"
5510
+ [attr.name]="name() || null"
5511
+ [value]="text()"
5512
+ [placeholder]="placeholder()"
5513
+ [disabled]="disabled()"
5514
+ [readOnly]="readonly()"
5515
+ [required]="required()"
5516
+ [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
5517
+ [attr.aria-controls]="core.controls()"
5518
+ [attr.aria-activedescendant]="core.active()?.id ?? null"
5519
+ [attr.aria-label]="ariaLabelAttr()"
5520
+ [attr.aria-labelledby]="ariaLabelledbyAttr()"
5521
+ [attr.aria-invalid]="showError() ? 'true' : null"
5522
+ [attr.aria-describedby]="describedBy()"
5523
+ [attr.aria-errormessage]="errorMessageId()"
5524
+ [class]="classes()"
5525
+ (input)="onInput(el)"
5526
+ (keydown)="onKeydown($event)"
5527
+ (click)="onClick()"
5528
+ (focus)="focused.emit($event)"
5529
+ (blur)="onBlur($event)"
5530
+ />
5531
+ </div>
5532
+ </bf-field>
5533
+ `
5534
+ }]
5535
+ }],
5536
+ propDecorators: {
5537
+ value: [{
5538
+ type: i0.Input,
5539
+ args: [{
5540
+ isSignal: true,
5541
+ alias: "value",
5542
+ required: false
5543
+ }]
5544
+ }, {
5545
+ type: i0.Output,
5546
+ args: ["valueChange"]
5547
+ }],
5548
+ options: [{
5549
+ type: i0.Input,
5550
+ args: [{
5551
+ isSignal: true,
5552
+ alias: "options",
5553
+ required: true
5554
+ }]
5555
+ }],
5556
+ compareWith: [{
5557
+ type: i0.Input,
5558
+ args: [{
5559
+ isSignal: true,
5560
+ alias: "compareWith",
5561
+ required: false
5562
+ }]
5563
+ }],
5564
+ filter: [{
5565
+ type: i0.Input,
5566
+ args: [{
5567
+ isSignal: true,
5568
+ alias: "filter",
5569
+ required: false
5570
+ }]
5571
+ }],
5572
+ size: [{
5573
+ type: i0.Input,
5574
+ args: [{
5575
+ isSignal: true,
5576
+ alias: "size",
5577
+ required: false
5578
+ }]
5579
+ }],
5580
+ label: [{
5581
+ type: i0.Input,
5582
+ args: [{
5583
+ isSignal: true,
5584
+ alias: "label",
5585
+ required: false
5586
+ }]
5587
+ }],
5588
+ ariaLabel: [{
5589
+ type: i0.Input,
5590
+ args: [{
5591
+ isSignal: true,
5592
+ alias: "ariaLabel",
5593
+ required: false
5594
+ }]
5595
+ }],
5596
+ ariaLabelledby: [{
5597
+ type: i0.Input,
5598
+ args: [{
5599
+ isSignal: true,
5600
+ alias: "ariaLabelledby",
5601
+ required: false
5602
+ }]
5603
+ }],
5604
+ placeholder: [{
5605
+ type: i0.Input,
5606
+ args: [{
5607
+ isSignal: true,
5608
+ alias: "placeholder",
5609
+ required: false
5610
+ }]
5611
+ }],
5612
+ helpText: [{
5613
+ type: i0.Input,
5614
+ args: [{
5615
+ isSignal: true,
5616
+ alias: "helpText",
5617
+ required: false
5618
+ }]
5619
+ }],
5620
+ errorMessage: [{
5621
+ type: i0.Input,
5622
+ args: [{
5623
+ isSignal: true,
5624
+ alias: "errorMessage",
5625
+ required: false
5626
+ }]
5627
+ }],
5628
+ emptyLabel: [{
5629
+ type: i0.Input,
5630
+ args: [{
5631
+ isSignal: true,
5632
+ alias: "emptyLabel",
5633
+ required: false
5634
+ }]
5635
+ }],
5636
+ loading: [{
5637
+ type: i0.Input,
5638
+ args: [{
5639
+ isSignal: true,
5640
+ alias: "loading",
5641
+ required: false
5642
+ }]
5643
+ }],
5644
+ disabled: [{
5645
+ type: i0.Input,
5646
+ args: [{
5647
+ isSignal: true,
5648
+ alias: "disabled",
5649
+ required: false
5650
+ }]
5651
+ }],
5652
+ readonly: [{
5653
+ type: i0.Input,
5654
+ args: [{
5655
+ isSignal: true,
5656
+ alias: "readonly",
5657
+ required: false
5658
+ }]
5659
+ }],
5660
+ required: [{
5661
+ type: i0.Input,
5662
+ args: [{
5663
+ isSignal: true,
5664
+ alias: "required",
5665
+ required: false
5666
+ }]
5667
+ }],
5668
+ invalid: [{
5669
+ type: i0.Input,
5670
+ args: [{
5671
+ isSignal: true,
5672
+ alias: "invalid",
5673
+ required: false
5674
+ }]
5675
+ }],
5676
+ touched: [{
5677
+ type: i0.Input,
5678
+ args: [{
5679
+ isSignal: true,
5680
+ alias: "touched",
5681
+ required: false
5682
+ }]
5683
+ }],
5684
+ errors: [{
5685
+ type: i0.Input,
5686
+ args: [{
5687
+ isSignal: true,
5688
+ alias: "errors",
5689
+ required: false
5690
+ }]
5691
+ }],
5692
+ name: [{
5693
+ type: i0.Input,
5694
+ args: [{
5695
+ isSignal: true,
5696
+ alias: "name",
5697
+ required: false
5698
+ }]
5699
+ }],
5700
+ id: [{
5701
+ type: i0.Input,
5702
+ args: [{
5703
+ isSignal: true,
5704
+ alias: "id",
5705
+ required: false
5706
+ }]
5707
+ }],
5708
+ changed: [{
5709
+ type: i0.Output,
5710
+ args: ["changed"]
5711
+ }],
5712
+ queried: [{
5713
+ type: i0.Output,
5714
+ args: ["queried"]
5715
+ }],
5716
+ focused: [{
5717
+ type: i0.Output,
5718
+ args: ["focused"]
5719
+ }],
5720
+ blurred: [{
5721
+ type: i0.Output,
5722
+ args: ["blurred"]
5723
+ }],
5724
+ touch: [{
5725
+ type: i0.Output,
5726
+ args: ["touch"]
5727
+ }],
5728
+ el: [{
5729
+ type: i0.ViewChild,
5730
+ args: ["el", { isSignal: true }]
5731
+ }],
5732
+ anchorEl: [{
5733
+ type: i0.ViewChild,
5734
+ args: ["anchor", { isSignal: true }]
5735
+ }]
5736
+ }
4476
5737
  });
4477
- var BfComboboxComponent = class BfComboboxComponent {
4478
- value = model(null, ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
5738
+ const BOX = "flex w-full flex-wrap items-center gap-1 rounded-field border py-1 transition-colors focus-within:border-primary focus-within:ring-2 focus-within:ring-ring";
5739
+ const BOX_SIZE = {
5740
+ sm: "min-h-8 text-label-xs",
5741
+ md: "min-h-10 text-label-sm",
5742
+ lg: "min-h-12 text-label"
5743
+ };
5744
+ const MODE$1 = {
5745
+ editable: "cursor-text bg-surface text-text",
5746
+ readonly: "cursor-text bg-surface-muted text-text",
5747
+ disabled: "cursor-not-allowed border-surface-sunken bg-surface-muted text-text-disabled"
5748
+ };
5749
+ const INPUT = "h-6 min-w-16 flex-1 border-0 p-0 shadow-none focus:ring-0 focus:shadow-none bg-transparent outline-hidden placeholder:text-text-muted disabled:cursor-not-allowed";
5750
+ const TAG = "inline-flex h-6 max-w-full min-w-0 items-center rounded-control bg-primary-soft ps-2 text-label-xs font-bold text-primary-strong";
5751
+ const REMOVE = "relative inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-control hover:bg-primary-soft-hover active:bg-primary-soft-pressed focus-visible:outline-hidden focus-visible:ring-4 focus-visible:ring-ring pointer-coarse:after:absolute pointer-coarse:after:-inset-2.5 pointer-coarse:after:content-['']";
5752
+ const CROSS = "relative block size-2.5 before:absolute before:inset-x-0 before:top-1/2 before:h-[1.5px] before:-translate-y-1/2 before:rotate-45 before:bg-current before:content-[''] after:absolute after:inset-x-0 after:top-1/2 after:h-[1.5px] after:-translate-y-1/2 after:-rotate-45 after:bg-current after:content-['']";
5753
+ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5754
+ value = model([], ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
4479
5755
  options = input.required(...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
4480
5756
  compareWith = input(Object.is, ...ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []);
4481
5757
  filter = input(labelContains, ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
@@ -4513,7 +5789,7 @@ var BfComboboxComponent = class BfComboboxComponent {
4513
5789
  });
4514
5790
  errors = input([], ...ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []);
4515
5791
  name = input("", ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
4516
- id = input(ɵuniqueId("bf-combobox"), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
5792
+ id = input(ɵuniqueId("bf-multi-combobox"), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
4517
5793
  changed = output();
4518
5794
  queried = output();
4519
5795
  focused = output();
@@ -4523,105 +5799,137 @@ var BfComboboxComponent = class BfComboboxComponent {
4523
5799
  anchorEl = viewChild.required("anchor", ...ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []);
4524
5800
  defaults = inject(BF_DEFAULTS);
4525
5801
  labels = inject(BF_LABELS);
4526
- pickedLabel = signal(null, ...ngDevMode ? [{ debugName: "pickedLabel" }] : /* istanbul ignore next */ []);
5802
+ pickedLabels = signal([], ...ngDevMode ? [{ debugName: "pickedLabels" }] : /* istanbul ignore next */ []);
4527
5803
  resolvedSize = computed(() => this.size() ?? this.defaults.fieldSize, ...ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []);
4528
5804
  core = new ComboboxCore({
4529
5805
  options: this.options,
4530
5806
  filter: this.filter,
4531
- isSelected: (option) => sameValue(this.compareWith(), option.value, this.value()),
4532
- pick: (item) => this.pick(item),
5807
+ isSelected: (option) => this.indexOf(option.value) >= 0,
5808
+ pick: (item) => this.toggle(item),
4533
5809
  anchor: () => this.anchorEl().nativeElement,
4534
5810
  panel: BfComboboxListboxComponent,
4535
5811
  listboxLabel: computed(() => this.label() || this.ariaLabel() || null),
4536
5812
  listboxLabelledby: computed(() => this.label() ? null : this.ariaLabelledby() || null),
4537
- multiple: false,
5813
+ multiple: true,
4538
5814
  loading: this.loading,
4539
5815
  emptyText: computed(() => this.emptyLabel() || this.labels.comboboxEmpty),
4540
5816
  size: this.resolvedSize,
4541
5817
  queried: (query) => this.queried.emit(query)
4542
5818
  });
4543
- valueLabel = computed(() => {
4544
- const value = this.value();
4545
- if (value === null) return "";
5819
+ tags = computed(() => {
4546
5820
  const compare = this.compareWith();
4547
- const option = this.options().find((o) => compare(o.value, value));
4548
- const picked = this.pickedLabel();
4549
- return option?.label ?? (picked && compare(picked.value, value) ? picked.label : "");
4550
- }, ...ngDevMode ? [{ debugName: "valueLabel" }] : /* istanbul ignore next */ []);
4551
- text = computed(() => this.core.query() ?? this.valueLabel(), ...ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []);
5821
+ return this.value().map((value) => {
5822
+ return {
5823
+ value,
5824
+ label: this.options().find((o) => compare(o.value, value))?.label ?? this.pickedLabels().find((t) => compare(t.value, value))?.label ?? String(value)
5825
+ };
5826
+ });
5827
+ }, ...ngDevMode ? [{ debugName: "tags" }] : /* istanbul ignore next */ []);
5828
+ editable = computed(() => !this.disabled() && !this.readonly(), ...ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []);
4552
5829
  errorText = computed(() => ɵfieldErrorText(this.errorMessage(), this.touched(), this.errors(), this.labels.validation), ...ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []);
4553
5830
  showError = computed(() => ɵfieldShowsError(this.errorText(), this.invalid(), this.touched()), ...ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []);
4554
5831
  describedBy = computed(() => ɵfieldDescribedBy(this.id(), this.errorText(), this.helpText()), ...ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []);
4555
5832
  errorMessageId = computed(() => ɵfieldErrorMessageId(this.id(), this.errorText(), this.showError()), ...ngDevMode ? [{ debugName: "errorMessageId" }] : /* istanbul ignore next */ []);
4556
5833
  ariaLabelAttr = computed(() => this.label() ? null : this.ariaLabel() || null, ...ngDevMode ? [{ debugName: "ariaLabelAttr" }] : /* istanbul ignore next */ []);
4557
5834
  ariaLabelledbyAttr = computed(() => this.label() ? null : this.ariaLabelledby() || null, ...ngDevMode ? [{ debugName: "ariaLabelledbyAttr" }] : /* istanbul ignore next */ []);
4558
- wrapperClasses = computed(() => `${COMBOBOX_CHEVRON} ${COMBOBOX_CHEVRON_INSET[this.resolvedSize()]}`, ...ngDevMode ? [{ debugName: "wrapperClasses" }] : /* istanbul ignore next */ []);
4559
- classes = computed(() => [
4560
- COMBOBOX_FIELD_BASE,
4561
- ɵFIELD_BOX[this.resolvedSize()],
4562
- ɵFIELD_PADDING_START[this.resolvedSize()].text,
4563
- COMBOBOX_PADDING_END[this.resolvedSize()],
4564
- this.showError() ? "border-danger-border hover:border-danger" : "border-border-strong hover:border-text-muted"
4565
- ].join(" "), ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
5835
+ boxClasses = computed(() => {
5836
+ const size = this.resolvedSize();
5837
+ const mode = this.disabled() ? "disabled" : this.readonly() ? "readonly" : "editable";
5838
+ return [
5839
+ BOX,
5840
+ BOX_SIZE[size],
5841
+ ɵFIELD_PADDING_START[size].text,
5842
+ COMBOBOX_PADDING_END[size],
5843
+ COMBOBOX_CHEVRON,
5844
+ COMBOBOX_CHEVRON_INSET[size],
5845
+ MODE$1[mode],
5846
+ mode === "disabled" ? "" : this.showError() ? "border-danger-border hover:not-focus-within:border-danger" : "border-border-strong hover:not-focus-within:border-text-muted"
5847
+ ].filter(Boolean).join(" ");
5848
+ }, ...ngDevMode ? [{ debugName: "boxClasses" }] : /* istanbul ignore next */ []);
5849
+ inputClasses = INPUT;
5850
+ tagClasses = TAG;
5851
+ removeClasses = REMOVE;
5852
+ crossClasses = CROSS;
5853
+ constructor() {
5854
+ afterRenderEffect({ write: () => {
5855
+ this.tags();
5856
+ this.core.reposition();
5857
+ } });
5858
+ }
4566
5859
  focus(options) {
4567
5860
  this.el().nativeElement.focus(options);
4568
5861
  }
4569
5862
  onInput(el) {
4570
- if (this.readonly()) return;
4571
- this.core.typed(el.value);
5863
+ if (this.editable()) this.core.typed(el.value);
4572
5864
  }
4573
5865
  onClick() {
4574
- if (!this.readonly() && !this.disabled()) this.core.open();
5866
+ if (this.editable()) this.core.open();
5867
+ }
5868
+ onBoxMousedown(event) {
5869
+ const target = event.target;
5870
+ if (!(target instanceof Element) || target.closest("button, input") || this.disabled()) return;
5871
+ event.preventDefault();
5872
+ this.focus();
5873
+ this.onClick();
4575
5874
  }
4576
5875
  onKeydown(event) {
4577
- if (this.readonly()) return;
4578
- if (this.core.keydown(event)) return;
4579
- if (event.key === "Escape" && !this.core.isOpen() && this.core.query() !== null) this.resetQuery(null);
5876
+ if (!this.editable() || this.core.keydown(event)) return;
5877
+ const query = this.core.query() ?? "";
5878
+ if (event.key === "Backspace" && query === "" && this.value().length) {
5879
+ event.preventDefault();
5880
+ this.remove(this.value().length - 1);
5881
+ } else if (event.key === "Escape" && !this.core.isOpen() && query !== "") this.resetQuery(null);
4580
5882
  }
4581
5883
  onBlur(event) {
4582
5884
  this.core.close();
4583
- const query = this.core.query();
4584
- if (query !== null) {
4585
- const typed = foldText(query.trim());
4586
- const match = this.options().find((o) => !o.disabled && foldText(o.label) === typed);
4587
- if (!typed && this.value() !== null) this.set(null);
4588
- else if (typed && match && !sameValue(this.compareWith(), match.value, this.value())) this.set(match.value, match.label);
4589
- this.resetQuery(null);
4590
- }
5885
+ this.resetQuery(null);
4591
5886
  this.blurred.emit(event);
4592
5887
  this.touch.emit();
4593
5888
  }
4594
- pick(item) {
4595
- if (!sameValue(this.compareWith(), item.option.value, this.value())) this.set(item.option.value, item.option.label);
4596
- this.resetQuery(null);
4597
- this.core.close();
5889
+ remove(index) {
5890
+ const next = this.value().filter((_, i) => i !== index);
5891
+ this.value.set(next);
5892
+ this.changed.emit(next);
5893
+ this.focus();
4598
5894
  }
4599
- set(value, label = "") {
4600
- this.pickedLabel.set(value === null ? null : {
4601
- value,
4602
- label
4603
- });
4604
- this.value.set(value);
4605
- this.changed.emit(value);
5895
+ toggle(item) {
5896
+ const at = this.indexOf(item.option.value);
5897
+ let next;
5898
+ if (at >= 0) next = this.value().filter((_, i) => i !== at);
5899
+ else {
5900
+ next = [...this.value(), item.option.value];
5901
+ const tag = {
5902
+ value: item.option.value,
5903
+ label: item.option.label
5904
+ };
5905
+ this.pickedLabels.update((tags) => [...tags, tag]);
5906
+ }
5907
+ this.value.set(next);
5908
+ this.changed.emit(next);
5909
+ this.resetQuery("");
5910
+ }
5911
+ indexOf(value) {
5912
+ const compare = this.compareWith();
5913
+ return this.value().findIndex((v) => compare(value, v));
4606
5914
  }
4607
5915
  resetQuery(query) {
4608
5916
  this.core.setQuery(query);
4609
- this.el().nativeElement.value = this.text();
5917
+ this.el().nativeElement.value = "";
4610
5918
  }
4611
5919
  static ɵfac = i0.ɵɵngDeclareFactory({
4612
5920
  minVersion: "12.0.0",
4613
5921
  version: "22.1.7",
4614
5922
  ngImport: i0,
4615
- type: BfComboboxComponent,
5923
+ type: BfMultiComboboxComponent,
4616
5924
  deps: [],
4617
5925
  target: i0.ɵɵFactoryTarget.Component
4618
5926
  });
4619
5927
  static ɵcmp = i0.ɵɵngDeclareComponent({
4620
- minVersion: "17.2.0",
5928
+ minVersion: "17.0.0",
4621
5929
  version: "22.1.7",
4622
- type: BfComboboxComponent,
5930
+ type: BfMultiComboboxComponent,
4623
5931
  isStandalone: true,
4624
- selector: "bf-combobox",
5932
+ selector: "bf-multi-combobox",
4625
5933
  inputs: {
4626
5934
  value: {
4627
5935
  classPropertyName: "value",
@@ -4806,7 +6114,25 @@ var BfComboboxComponent = class BfComboboxComponent {
4806
6114
  [disabled]="disabled()"
4807
6115
  [controlId]="id()"
4808
6116
  >
4809
- <div #anchor [class]="wrapperClasses()">
6117
+ <!-- A press on the box outside the tags puts the caret in the input. -->
6118
+ <div #anchor [class]="boxClasses()" (mousedown)="onBoxMousedown($event)">
6119
+ @for (tag of tags(); track $index) {
6120
+ <span data-chip [class]="tagClasses">
6121
+ <span data-label class="min-w-0 truncate">{{ tag.label }}</span>
6122
+ @if (editable()) {
6123
+ <button
6124
+ type="button"
6125
+ [class]="removeClasses"
6126
+ [attr.aria-label]="labels.comboboxRemove(tag.label)"
6127
+ (click)="remove($index)"
6128
+ >
6129
+ <span aria-hidden="true" [class]="crossClasses"></span>
6130
+ </button>
6131
+ } @else {
6132
+ <span class="pe-2"></span>
6133
+ }
6134
+ </span>
6135
+ }
4810
6136
  <input
4811
6137
  #el
4812
6138
  type="text"
@@ -4815,11 +6141,11 @@ var BfComboboxComponent = class BfComboboxComponent {
4815
6141
  aria-autocomplete="list"
4816
6142
  [id]="id()"
4817
6143
  [attr.name]="name() || null"
4818
- [value]="text()"
4819
- [placeholder]="placeholder()"
6144
+ [value]="core.query() ?? ''"
6145
+ [placeholder]="tags().length ? '' : placeholder()"
4820
6146
  [disabled]="disabled()"
4821
6147
  [readOnly]="readonly()"
4822
- [required]="required()"
6148
+ [required]="required() && !tags().length"
4823
6149
  [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
4824
6150
  [attr.aria-controls]="core.controls()"
4825
6151
  [attr.aria-activedescendant]="core.active()?.id ?? null"
@@ -4828,7 +6154,7 @@ var BfComboboxComponent = class BfComboboxComponent {
4828
6154
  [attr.aria-invalid]="showError() ? 'true' : null"
4829
6155
  [attr.aria-describedby]="describedBy()"
4830
6156
  [attr.aria-errormessage]="errorMessageId()"
4831
- [class]="classes()"
6157
+ [class]="inputClasses"
4832
6158
  (input)="onInput(el)"
4833
6159
  (keydown)="onKeydown($event)"
4834
6160
  (click)="onClick()"
@@ -4858,11 +6184,11 @@ i0.ɵɵngDeclareClassMetadata({
4858
6184
  minVersion: "12.0.0",
4859
6185
  version: "22.1.7",
4860
6186
  ngImport: i0,
4861
- type: BfComboboxComponent,
6187
+ type: BfMultiComboboxComponent,
4862
6188
  decorators: [{
4863
6189
  type: Component,
4864
6190
  args: [{
4865
- selector: "bf-combobox",
6191
+ selector: "bf-multi-combobox",
4866
6192
  imports: [BfFieldComponent],
4867
6193
  host: {
4868
6194
  class: "block",
@@ -4877,7 +6203,25 @@ i0.ɵɵngDeclareClassMetadata({
4877
6203
  [disabled]="disabled()"
4878
6204
  [controlId]="id()"
4879
6205
  >
4880
- <div #anchor [class]="wrapperClasses()">
6206
+ <!-- A press on the box outside the tags puts the caret in the input. -->
6207
+ <div #anchor [class]="boxClasses()" (mousedown)="onBoxMousedown($event)">
6208
+ @for (tag of tags(); track $index) {
6209
+ <span data-chip [class]="tagClasses">
6210
+ <span data-label class="min-w-0 truncate">{{ tag.label }}</span>
6211
+ @if (editable()) {
6212
+ <button
6213
+ type="button"
6214
+ [class]="removeClasses"
6215
+ [attr.aria-label]="labels.comboboxRemove(tag.label)"
6216
+ (click)="remove($index)"
6217
+ >
6218
+ <span aria-hidden="true" [class]="crossClasses"></span>
6219
+ </button>
6220
+ } @else {
6221
+ <span class="pe-2"></span>
6222
+ }
6223
+ </span>
6224
+ }
4881
6225
  <input
4882
6226
  #el
4883
6227
  type="text"
@@ -4886,11 +6230,11 @@ i0.ɵɵngDeclareClassMetadata({
4886
6230
  aria-autocomplete="list"
4887
6231
  [id]="id()"
4888
6232
  [attr.name]="name() || null"
4889
- [value]="text()"
4890
- [placeholder]="placeholder()"
6233
+ [value]="core.query() ?? ''"
6234
+ [placeholder]="tags().length ? '' : placeholder()"
4891
6235
  [disabled]="disabled()"
4892
6236
  [readOnly]="readonly()"
4893
- [required]="required()"
6237
+ [required]="required() && !tags().length"
4894
6238
  [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
4895
6239
  [attr.aria-controls]="core.controls()"
4896
6240
  [attr.aria-activedescendant]="core.active()?.id ?? null"
@@ -4899,7 +6243,7 @@ i0.ɵɵngDeclareClassMetadata({
4899
6243
  [attr.aria-invalid]="showError() ? 'true' : null"
4900
6244
  [attr.aria-describedby]="describedBy()"
4901
6245
  [attr.aria-errormessage]="errorMessageId()"
4902
- [class]="classes()"
6246
+ [class]="inputClasses"
4903
6247
  (input)="onInput(el)"
4904
6248
  (keydown)="onKeydown($event)"
4905
6249
  (click)="onClick()"
@@ -4911,6 +6255,7 @@ i0.ɵɵngDeclareClassMetadata({
4911
6255
  `
4912
6256
  }]
4913
6257
  }],
6258
+ ctorParameters: () => [],
4914
6259
  propDecorators: {
4915
6260
  value: [{
4916
6261
  type: i0.Input,
@@ -5113,26 +6458,26 @@ i0.ɵɵngDeclareClassMetadata({
5113
6458
  }]
5114
6459
  }
5115
6460
  });
5116
- const BOX = "flex w-full flex-wrap items-center gap-1 rounded-field border py-1 transition-colors focus-within:border-primary focus-within:ring-2 focus-within:ring-ring";
5117
- const BOX_SIZE = {
5118
- sm: "min-h-8 text-label-xs",
5119
- md: "min-h-10 text-label-sm",
5120
- lg: "min-h-12 text-label"
5121
- };
6461
+ const BASE = "flex w-full items-center rounded-field border text-text select-none transition-colors focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-ring aria-disabled:cursor-not-allowed aria-disabled:border-surface-sunken aria-disabled:bg-surface-muted aria-disabled:text-text-disabled forced-colors:aria-disabled:border-[GrayText] forced-colors:aria-disabled:text-[GrayText]";
5122
6462
  const MODE = {
5123
- editable: "cursor-text bg-surface text-text",
5124
- readonly: "cursor-text bg-surface-muted text-text",
5125
- disabled: "cursor-not-allowed border-surface-sunken bg-surface-muted text-text-disabled"
6463
+ editable: "cursor-pointer bg-surface",
6464
+ readonly: "cursor-default bg-surface-muted"
5126
6465
  };
5127
- const INPUT = "h-6 min-w-16 flex-1 bg-transparent outline-hidden placeholder:text-text-muted disabled:cursor-not-allowed";
5128
- const TAG = "inline-flex h-6 max-w-full min-w-0 items-center rounded-control bg-primary-soft ps-2 text-label-xs font-bold text-primary-strong";
5129
- const REMOVE = "relative inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-control hover:bg-primary-soft-hover active:bg-primary-soft-pressed focus-visible:outline-hidden focus-visible:ring-4 focus-visible:ring-ring pointer-coarse:after:absolute pointer-coarse:after:-inset-2.5 pointer-coarse:after:content-['']";
5130
- const CROSS = "relative block size-2.5 before:absolute before:inset-x-0 before:top-1/2 before:h-[1.5px] before:-translate-y-1/2 before:rotate-45 before:bg-current before:content-[''] after:absolute after:inset-x-0 after:top-1/2 after:h-[1.5px] after:-translate-y-1/2 after:-rotate-45 after:bg-current after:content-['']";
5131
- var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5132
- value = model([], ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
6466
+ const SCROLL_KEYS = /* @__PURE__ */ new Set([
6467
+ " ",
6468
+ "ArrowUp",
6469
+ "ArrowDown",
6470
+ "Home",
6471
+ "End",
6472
+ "PageUp",
6473
+ "PageDown"
6474
+ ]);
6475
+ const PAGE = 10;
6476
+ const TYPEAHEAD_MS = 500;
6477
+ var BfSelectComponent = class BfSelectComponent {
6478
+ value = model(null, ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
5133
6479
  options = input.required(...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
5134
6480
  compareWith = input(Object.is, ...ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []);
5135
- filter = input(labelContains, ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
5136
6481
  size = input(void 0, ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
5137
6482
  label = input("", ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
5138
6483
  ariaLabel = input("", ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
@@ -5140,11 +6485,6 @@ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5140
6485
  placeholder = input("", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
5141
6486
  helpText = input("", ...ngDevMode ? [{ debugName: "helpText" }] : /* istanbul ignore next */ []);
5142
6487
  errorMessage = input("", ...ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []);
5143
- emptyLabel = input("", ...ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []);
5144
- loading = input(false, {
5145
- ...ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {},
5146
- transform: booleanAttribute
5147
- });
5148
6488
  disabled = input(false, {
5149
6489
  ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
5150
6490
  transform: booleanAttribute
@@ -5167,147 +6507,190 @@ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5167
6507
  });
5168
6508
  errors = input([], ...ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []);
5169
6509
  name = input("", ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
5170
- id = input(ɵuniqueId("bf-multi-combobox"), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
6510
+ id = input(ɵuniqueId("bf-select"), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
5171
6511
  changed = output();
5172
- queried = output();
5173
6512
  focused = output();
5174
6513
  blurred = output();
5175
6514
  touch = output();
5176
- el = viewChild.required("el", ...ngDevMode ? [{ debugName: "el" }] : /* istanbul ignore next */ []);
5177
- anchorEl = viewChild.required("anchor", ...ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []);
6515
+ fieldEl = viewChild("field", ...ngDevMode ? [{ debugName: "fieldEl" }] : /* istanbul ignore next */ []);
6516
+ anchorEl = viewChild("anchor", ...ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []);
6517
+ nativeSelect = viewChild("native", ...ngDevMode ? [{ debugName: "nativeSelect" }] : /* istanbul ignore next */ []);
5178
6518
  defaults = inject(BF_DEFAULTS);
5179
6519
  labels = inject(BF_LABELS);
5180
- pickedLabels = signal([], ...ngDevMode ? [{ debugName: "pickedLabels" }] : /* istanbul ignore next */ []);
6520
+ document = inject(DOCUMENT);
6521
+ coarse = signal(false, ...ngDevMode ? [{ debugName: "coarse" }] : /* istanbul ignore next */ []);
5181
6522
  resolvedSize = computed(() => this.size() ?? this.defaults.fieldSize, ...ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []);
6523
+ selectedIndex = computed(() => ɵchoiceIndex(this.options(), this.value(), this.compareWith()), ...ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []);
6524
+ selectedLabel = computed(() => this.options()[this.selectedIndex()]?.label ?? null, ...ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []);
6525
+ formKey = computed(() => {
6526
+ const index = this.selectedIndex();
6527
+ const option = this.options()[index];
6528
+ return option ? ɵchoiceKey(option.value, index) : "";
6529
+ }, ...ngDevMode ? [{ debugName: "formKey" }] : /* istanbul ignore next */ []);
6530
+ listOptions = computed(() => this.options().map(({ value, label, disabled }) => ({
6531
+ value,
6532
+ label,
6533
+ disabled
6534
+ })), ...ngDevMode ? [{ debugName: "listOptions" }] : /* istanbul ignore next */ []);
5182
6535
  core = new ComboboxCore({
5183
- options: this.options,
5184
- filter: this.filter,
5185
- isSelected: (option) => this.indexOf(option.value) >= 0,
5186
- pick: (item) => this.toggle(item),
6536
+ options: this.listOptions,
6537
+ filter: signal(null),
6538
+ isSelected: (option) => this.listOptions()[this.selectedIndex()] === option,
6539
+ pick: (item) => this.pick(item),
5187
6540
  anchor: () => this.anchorEl().nativeElement,
5188
6541
  panel: BfComboboxListboxComponent,
5189
6542
  listboxLabel: computed(() => this.label() || this.ariaLabel() || null),
5190
6543
  listboxLabelledby: computed(() => this.label() ? null : this.ariaLabelledby() || null),
5191
- multiple: true,
5192
- loading: this.loading,
5193
- emptyText: computed(() => this.emptyLabel() || this.labels.comboboxEmpty),
6544
+ multiple: false,
6545
+ loading: signal(false),
6546
+ emptyText: signal(""),
5194
6547
  size: this.resolvedSize,
5195
- queried: (query) => this.queried.emit(query)
6548
+ queried: () => void 0
5196
6549
  });
5197
- tags = computed(() => {
5198
- const compare = this.compareWith();
5199
- return this.value().map((value) => {
5200
- return {
5201
- value,
5202
- label: this.options().find((o) => compare(o.value, value))?.label ?? this.pickedLabels().find((t) => compare(t.value, value))?.label ?? String(value)
5203
- };
5204
- });
5205
- }, ...ngDevMode ? [{ debugName: "tags" }] : /* istanbul ignore next */ []);
5206
- editable = computed(() => !this.disabled() && !this.readonly(), ...ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []);
5207
6550
  errorText = computed(() => ɵfieldErrorText(this.errorMessage(), this.touched(), this.errors(), this.labels.validation), ...ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []);
5208
6551
  showError = computed(() => ɵfieldShowsError(this.errorText(), this.invalid(), this.touched()), ...ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []);
5209
6552
  describedBy = computed(() => ɵfieldDescribedBy(this.id(), this.errorText(), this.helpText()), ...ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []);
5210
6553
  errorMessageId = computed(() => ɵfieldErrorMessageId(this.id(), this.errorText(), this.showError()), ...ngDevMode ? [{ debugName: "errorMessageId" }] : /* istanbul ignore next */ []);
5211
6554
  ariaLabelAttr = computed(() => this.label() ? null : this.ariaLabel() || null, ...ngDevMode ? [{ debugName: "ariaLabelAttr" }] : /* istanbul ignore next */ []);
5212
6555
  ariaLabelledbyAttr = computed(() => this.label() ? null : this.ariaLabelledby() || null, ...ngDevMode ? [{ debugName: "ariaLabelledbyAttr" }] : /* istanbul ignore next */ []);
5213
- boxClasses = computed(() => {
5214
- const size = this.resolvedSize();
5215
- const mode = this.disabled() ? "disabled" : this.readonly() ? "readonly" : "editable";
5216
- return [
5217
- BOX,
5218
- BOX_SIZE[size],
5219
- ɵFIELD_PADDING_START[size].text,
5220
- COMBOBOX_PADDING_END[size],
5221
- COMBOBOX_CHEVRON,
5222
- COMBOBOX_CHEVRON_INSET[size],
5223
- MODE[mode],
5224
- mode === "disabled" ? "" : this.showError() ? "border-danger-border hover:not-focus-within:border-danger" : "border-border-strong hover:not-focus-within:border-text-muted"
5225
- ].filter(Boolean).join(" ");
5226
- }, ...ngDevMode ? [{ debugName: "boxClasses" }] : /* istanbul ignore next */ []);
5227
- inputClasses = INPUT;
5228
- tagClasses = TAG;
5229
- removeClasses = REMOVE;
5230
- crossClasses = CROSS;
6556
+ wrapperClasses = computed(() => `${COMBOBOX_CHEVRON} ${COMBOBOX_CHEVRON_INSET[this.resolvedSize()]}`, ...ngDevMode ? [{ debugName: "wrapperClasses" }] : /* istanbul ignore next */ []);
6557
+ classes = computed(() => [
6558
+ BASE,
6559
+ ɵFIELD_BOX[this.resolvedSize()],
6560
+ ɵFIELD_PADDING_START[this.resolvedSize()].text,
6561
+ COMBOBOX_PADDING_END[this.resolvedSize()],
6562
+ MODE[this.readonly() ? "readonly" : "editable"],
6563
+ this.showError() ? "border-danger-border hover:border-danger" : "border-border-strong hover:border-text-muted",
6564
+ this.selectedIndex() === -1 ? "text-text-muted" : ""
6565
+ ].filter(Boolean).join(" "), ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
6566
+ typeahead = "";
6567
+ typedAt = 0;
5231
6568
  constructor() {
5232
- afterRenderEffect({ write: () => {
5233
- this.tags();
5234
- this.core.reposition();
5235
- } });
6569
+ const media = this.document.defaultView?.matchMedia?.("(pointer: coarse)");
6570
+ if (media) {
6571
+ this.coarse.set(media.matches);
6572
+ const onChange = (event) => this.coarse.set(event.matches);
6573
+ media.addEventListener?.("change", onChange);
6574
+ inject(DestroyRef).onDestroy(() => media.removeEventListener?.("change", onChange));
6575
+ }
6576
+ effect(() => {
6577
+ if (this.coarse() || this.disabled() || this.readonly()) this.core.close();
6578
+ });
5236
6579
  }
5237
6580
  focus(options) {
5238
- this.el().nativeElement.focus(options);
6581
+ const native = this.nativeSelect();
6582
+ if (native) native.focus(options);
6583
+ else this.fieldEl()?.nativeElement.focus(options);
5239
6584
  }
5240
- onInput(el) {
5241
- if (this.editable()) this.core.typed(el.value);
6585
+ onHostClick(event) {
6586
+ if (!this.coarse() && !this.disabled() && event.target instanceof Element && event.target.closest("label")) this.focus();
5242
6587
  }
5243
6588
  onClick() {
5244
- if (this.editable()) this.core.open();
5245
- }
5246
- onBoxMousedown(event) {
5247
- const target = event.target;
5248
- if (!(target instanceof Element) || target.closest("button, input") || this.disabled()) return;
5249
- event.preventDefault();
5250
- this.focus();
5251
- this.onClick();
6589
+ if (this.core.isOpen()) this.core.close();
6590
+ else this.openList();
5252
6591
  }
5253
6592
  onKeydown(event) {
5254
- if (!this.editable() || this.core.keydown(event)) return;
5255
- const query = this.core.query() ?? "";
5256
- if (event.key === "Backspace" && query === "" && this.value().length) {
6593
+ if (this.disabled()) return;
6594
+ if (this.readonly()) {
6595
+ if (SCROLL_KEYS.has(event.key)) event.preventDefault();
6596
+ return;
6597
+ }
6598
+ const key = event.key;
6599
+ if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && (key !== " " || this.typing())) {
6600
+ this.search(key);
5257
6601
  event.preventDefault();
5258
- this.remove(this.value().length - 1);
5259
- } else if (event.key === "Escape" && !this.core.isOpen() && query !== "") this.resetQuery(null);
6602
+ return;
6603
+ }
6604
+ if (!this.core.isOpen()) {
6605
+ if (key === "Enter" || key === " " || key === "ArrowDown" || key === "ArrowUp") {
6606
+ this.openList();
6607
+ event.preventDefault();
6608
+ }
6609
+ return;
6610
+ }
6611
+ switch (key) {
6612
+ case " ":
6613
+ this.pickActive();
6614
+ event.preventDefault();
6615
+ return;
6616
+ case "Tab":
6617
+ this.pickActive();
6618
+ this.core.close();
6619
+ return;
6620
+ case "PageUp":
6621
+ case "PageDown":
6622
+ this.page(key === "PageDown" ? PAGE : -10);
6623
+ event.preventDefault();
6624
+ return;
6625
+ default: this.core.keydown(event);
6626
+ }
5260
6627
  }
5261
6628
  onBlur(event) {
5262
6629
  this.core.close();
5263
- this.resetQuery(null);
5264
6630
  this.blurred.emit(event);
5265
6631
  this.touch.emit();
5266
6632
  }
5267
- remove(index) {
5268
- const next = this.value().filter((_, i) => i !== index);
5269
- this.value.set(next);
5270
- this.changed.emit(next);
5271
- this.focus();
6633
+ openList() {
6634
+ if (this.disabled() || this.readonly() || this.core.isOpen()) return;
6635
+ const items = this.enabledItems();
6636
+ if (!items.length) return;
6637
+ this.core.open();
6638
+ const selected = items.find((item) => this.core.host.isSelected(item.option));
6639
+ this.core.activate(selected ?? items[0]);
5272
6640
  }
5273
- toggle(item) {
5274
- const at = this.indexOf(item.option.value);
5275
- let next;
5276
- if (at >= 0) next = this.value().filter((_, i) => i !== at);
5277
- else {
5278
- next = [...this.value(), item.option.value];
5279
- const tag = {
5280
- value: item.option.value,
5281
- label: item.option.label
5282
- };
5283
- this.pickedLabels.update((tags) => [...tags, tag]);
5284
- }
5285
- this.value.set(next);
5286
- this.changed.emit(next);
5287
- this.resetQuery("");
6641
+ pick(item) {
6642
+ if (this.listOptions().indexOf(item.option) !== this.selectedIndex()) {
6643
+ this.value.set(item.option.value);
6644
+ this.changed.emit(item.option.value);
6645
+ }
6646
+ this.core.close();
5288
6647
  }
5289
- indexOf(value) {
5290
- const compare = this.compareWith();
5291
- return this.value().findIndex((v) => compare(value, v));
6648
+ pickActive() {
6649
+ const active = this.core.active();
6650
+ if (active) this.pick(active);
6651
+ }
6652
+ page(step) {
6653
+ const items = this.enabledItems();
6654
+ const at = items.findIndex((item) => item.index === this.core.active()?.index);
6655
+ const item = items[Math.min(Math.max((at < 0 ? 0 : at) + step, 0), items.length - 1)];
6656
+ if (item) this.core.activate(item);
6657
+ }
6658
+ typing() {
6659
+ return this.typeahead !== "" && Date.now() - this.typedAt <= TYPEAHEAD_MS;
6660
+ }
6661
+ search(key) {
6662
+ this.typeahead = (this.typing() ? this.typeahead : "") + key;
6663
+ this.typedAt = Date.now();
6664
+ const typed = foldText(this.typeahead);
6665
+ const repeated = [...typed].every((char) => char === typed[0]);
6666
+ const query = repeated ? typed[0] : typed;
6667
+ const items = this.enabledItems();
6668
+ const from = this.core.isOpen() ? this.core.active() : items.find((item) => this.core.host.isSelected(item.option));
6669
+ const at = from ? items.findIndex((item) => item.index === from.index) : -1;
6670
+ const start = at < 0 ? 0 : repeated ? at + 1 : at;
6671
+ const match = [...items.slice(start), ...items.slice(0, start)].find((item) => foldText(item.option.label).startsWith(query));
6672
+ if (match) {
6673
+ this.openList();
6674
+ this.core.activate(match);
6675
+ }
5292
6676
  }
5293
- resetQuery(query) {
5294
- this.core.setQuery(query);
5295
- this.el().nativeElement.value = "";
6677
+ enabledItems() {
6678
+ return this.core.items().filter((item) => !item.option.disabled);
5296
6679
  }
5297
6680
  static ɵfac = i0.ɵɵngDeclareFactory({
5298
6681
  minVersion: "12.0.0",
5299
6682
  version: "22.1.7",
5300
6683
  ngImport: i0,
5301
- type: BfMultiComboboxComponent,
6684
+ type: BfSelectComponent,
5302
6685
  deps: [],
5303
6686
  target: i0.ɵɵFactoryTarget.Component
5304
6687
  });
5305
6688
  static ɵcmp = i0.ɵɵngDeclareComponent({
5306
6689
  minVersion: "17.0.0",
5307
6690
  version: "22.1.7",
5308
- type: BfMultiComboboxComponent,
6691
+ type: BfSelectComponent,
5309
6692
  isStandalone: true,
5310
- selector: "bf-multi-combobox",
6693
+ selector: "bf-select",
5311
6694
  inputs: {
5312
6695
  value: {
5313
6696
  classPropertyName: "value",
@@ -5330,13 +6713,6 @@ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5330
6713
  isRequired: false,
5331
6714
  transformFunction: null
5332
6715
  },
5333
- filter: {
5334
- classPropertyName: "filter",
5335
- publicName: "filter",
5336
- isSignal: true,
5337
- isRequired: false,
5338
- transformFunction: null
5339
- },
5340
6716
  size: {
5341
6717
  classPropertyName: "size",
5342
6718
  publicName: "size",
@@ -5386,20 +6762,6 @@ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5386
6762
  isRequired: false,
5387
6763
  transformFunction: null
5388
6764
  },
5389
- emptyLabel: {
5390
- classPropertyName: "emptyLabel",
5391
- publicName: "emptyLabel",
5392
- isSignal: true,
5393
- isRequired: false,
5394
- transformFunction: null
5395
- },
5396
- loading: {
5397
- classPropertyName: "loading",
5398
- publicName: "loading",
5399
- isSignal: true,
5400
- isRequired: false,
5401
- transformFunction: null
5402
- },
5403
6765
  disabled: {
5404
6766
  classPropertyName: "disabled",
5405
6767
  publicName: "disabled",
@@ -5460,87 +6822,116 @@ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5460
6822
  outputs: {
5461
6823
  value: "valueChange",
5462
6824
  changed: "changed",
5463
- queried: "queried",
5464
6825
  focused: "focused",
5465
6826
  blurred: "blurred",
5466
6827
  touch: "touch"
5467
6828
  },
5468
6829
  host: {
6830
+ listeners: { "click": "onHostClick($event)" },
5469
6831
  properties: { "attr.id": "null" },
5470
6832
  classAttribute: "block"
5471
6833
  },
5472
- viewQueries: [{
5473
- propertyName: "el",
5474
- first: true,
5475
- predicate: ["el"],
5476
- descendants: true,
5477
- isSignal: true
5478
- }, {
5479
- propertyName: "anchorEl",
5480
- first: true,
5481
- predicate: ["anchor"],
5482
- descendants: true,
5483
- isSignal: true
5484
- }],
6834
+ viewQueries: [
6835
+ {
6836
+ propertyName: "fieldEl",
6837
+ first: true,
6838
+ predicate: ["field"],
6839
+ descendants: true,
6840
+ isSignal: true
6841
+ },
6842
+ {
6843
+ propertyName: "anchorEl",
6844
+ first: true,
6845
+ predicate: ["anchor"],
6846
+ descendants: true,
6847
+ isSignal: true
6848
+ },
6849
+ {
6850
+ propertyName: "nativeSelect",
6851
+ first: true,
6852
+ predicate: ["native"],
6853
+ descendants: true,
6854
+ isSignal: true
6855
+ }
6856
+ ],
5485
6857
  ngImport: i0,
5486
6858
  template: `
5487
- <bf-field
5488
- [label]="label()"
5489
- [helpText]="helpText()"
5490
- [error]="errorText()"
5491
- [required]="required()"
5492
- [disabled]="disabled()"
5493
- [controlId]="id()"
5494
- >
5495
- <!-- A press on the box outside the tags puts the caret in the input. -->
5496
- <div #anchor [class]="boxClasses()" (mousedown)="onBoxMousedown($event)">
5497
- @for (tag of tags(); track $index) {
5498
- <span data-chip [class]="tagClasses">
5499
- <span data-label class="min-w-0 truncate">{{ tag.label }}</span>
5500
- @if (editable()) {
5501
- <button
5502
- type="button"
5503
- [class]="removeClasses"
5504
- [attr.aria-label]="labels.comboboxRemove(tag.label)"
5505
- (click)="remove($index)"
5506
- >
5507
- <span aria-hidden="true" [class]="crossClasses"></span>
5508
- </button>
5509
- } @else {
5510
- <span class="pe-2"></span>
5511
- }
5512
- </span>
6859
+ @if (coarse()) {
6860
+ <bf-native-select
6861
+ #native
6862
+ [options]="options()"
6863
+ [compareWith]="compareWith()"
6864
+ [size]="size()"
6865
+ [label]="label()"
6866
+ [ariaLabel]="ariaLabel()"
6867
+ [ariaLabelledby]="ariaLabelledby()"
6868
+ [placeholder]="placeholder()"
6869
+ [helpText]="helpText()"
6870
+ [errorMessage]="errorMessage()"
6871
+ [disabled]="disabled()"
6872
+ [readonly]="readonly()"
6873
+ [required]="required()"
6874
+ [invalid]="invalid()"
6875
+ [touched]="touched()"
6876
+ [errors]="errors()"
6877
+ [name]="name()"
6878
+ [id]="id()"
6879
+ [(value)]="value"
6880
+ (changed)="changed.emit($event)"
6881
+ (focused)="focused.emit($event)"
6882
+ (blurred)="blurred.emit($event)"
6883
+ (touch)="touch.emit()"
6884
+ />
6885
+ } @else {
6886
+ <bf-field
6887
+ #box
6888
+ [label]="label()"
6889
+ [helpText]="helpText()"
6890
+ [error]="errorText()"
6891
+ [required]="required()"
6892
+ [disabled]="disabled()"
6893
+ [controlId]="id()"
6894
+ >
6895
+ <div #anchor [class]="wrapperClasses()">
6896
+ <div
6897
+ #field
6898
+ role="combobox"
6899
+ aria-haspopup="listbox"
6900
+ [id]="id()"
6901
+ [attr.tabindex]="disabled() ? null : 0"
6902
+ [attr.aria-disabled]="disabled() ? 'true' : null"
6903
+ [attr.aria-readonly]="readonly() ? 'true' : null"
6904
+ [attr.aria-required]="required() ? 'true' : null"
6905
+ [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
6906
+ [attr.aria-controls]="core.controls()"
6907
+ [attr.aria-activedescendant]="core.active()?.id ?? null"
6908
+ [attr.aria-label]="ariaLabelAttr()"
6909
+ [attr.aria-labelledby]="label() ? box.labelId() : ariaLabelledbyAttr()"
6910
+ [attr.aria-invalid]="showError() ? 'true' : null"
6911
+ [attr.aria-describedby]="describedBy()"
6912
+ [attr.aria-errormessage]="errorMessageId()"
6913
+ [attr.data-placeholder]="selectedIndex() === -1 ? '' : null"
6914
+ [class]="classes()"
6915
+ (click)="onClick()"
6916
+ (keydown)="onKeydown($event)"
6917
+ (focus)="focused.emit($event)"
6918
+ (blur)="onBlur($event)"
6919
+ >
6920
+ <span class="min-w-0 truncate">{{ selectedLabel() ?? placeholder() }}</span>
6921
+ </div>
6922
+ </div>
6923
+ @if (name()) {
6924
+ <input type="hidden" [name]="name()" [value]="formKey()" [disabled]="disabled()" />
5513
6925
  }
5514
- <input
5515
- #el
5516
- type="text"
5517
- role="combobox"
5518
- autocomplete="off"
5519
- aria-autocomplete="list"
5520
- [id]="id()"
5521
- [attr.name]="name() || null"
5522
- [value]="core.query() ?? ''"
5523
- [placeholder]="tags().length ? '' : placeholder()"
5524
- [disabled]="disabled()"
5525
- [readOnly]="readonly()"
5526
- [required]="required() && !tags().length"
5527
- [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
5528
- [attr.aria-controls]="core.controls()"
5529
- [attr.aria-activedescendant]="core.active()?.id ?? null"
5530
- [attr.aria-label]="ariaLabelAttr()"
5531
- [attr.aria-labelledby]="ariaLabelledbyAttr()"
5532
- [attr.aria-invalid]="showError() ? 'true' : null"
5533
- [attr.aria-describedby]="describedBy()"
5534
- [attr.aria-errormessage]="errorMessageId()"
5535
- [class]="inputClasses"
5536
- (input)="onInput(el)"
5537
- (keydown)="onKeydown($event)"
5538
- (click)="onClick()"
5539
- (focus)="focused.emit($event)"
5540
- (blur)="onBlur($event)"
5541
- />
5542
- </div>
5543
- </bf-field>
6926
+ <!-- The option labels for BfSelectHarness, readable while the list cannot open (disabled,
6927
+ read-only); hidden, so never rendered or announced. -->
6928
+ <span hidden data-bf-select-options>
6929
+ @for (option of options(); track $index) {
6930
+ <span [attr.data-disabled]="option.disabled ? '' : null">{{ option.label }}</span>
6931
+ }
6932
+ </span>
6933
+ </bf-field>
6934
+ }
5544
6935
  `,
5545
6936
  isInline: true,
5546
6937
  dependencies: [{
@@ -5555,6 +6946,37 @@ var BfMultiComboboxComponent = class BfMultiComboboxComponent {
5555
6946
  "disabled",
5556
6947
  "controlId"
5557
6948
  ]
6949
+ }, {
6950
+ kind: "component",
6951
+ type: ɵBfNativeSelectComponent,
6952
+ selector: "bf-native-select",
6953
+ inputs: [
6954
+ "value",
6955
+ "options",
6956
+ "compareWith",
6957
+ "size",
6958
+ "label",
6959
+ "ariaLabel",
6960
+ "ariaLabelledby",
6961
+ "placeholder",
6962
+ "helpText",
6963
+ "errorMessage",
6964
+ "disabled",
6965
+ "readonly",
6966
+ "required",
6967
+ "invalid",
6968
+ "touched",
6969
+ "errors",
6970
+ "name",
6971
+ "id"
6972
+ ],
6973
+ outputs: [
6974
+ "valueChange",
6975
+ "changed",
6976
+ "focused",
6977
+ "blurred",
6978
+ "touch"
6979
+ ]
5558
6980
  }]
5559
6981
  });
5560
6982
  };
@@ -5562,74 +6984,94 @@ i0.ɵɵngDeclareClassMetadata({
5562
6984
  minVersion: "12.0.0",
5563
6985
  version: "22.1.7",
5564
6986
  ngImport: i0,
5565
- type: BfMultiComboboxComponent,
6987
+ type: BfSelectComponent,
5566
6988
  decorators: [{
5567
6989
  type: Component,
5568
6990
  args: [{
5569
- selector: "bf-multi-combobox",
5570
- imports: [BfFieldComponent],
6991
+ selector: "bf-select",
6992
+ imports: [BfFieldComponent, ɵBfNativeSelectComponent],
5571
6993
  host: {
5572
6994
  class: "block",
5573
- "[attr.id]": "null"
6995
+ "[attr.id]": "null",
6996
+ "(click)": "onHostClick($event)"
5574
6997
  },
5575
6998
  template: `
5576
- <bf-field
5577
- [label]="label()"
5578
- [helpText]="helpText()"
5579
- [error]="errorText()"
5580
- [required]="required()"
5581
- [disabled]="disabled()"
5582
- [controlId]="id()"
5583
- >
5584
- <!-- A press on the box outside the tags puts the caret in the input. -->
5585
- <div #anchor [class]="boxClasses()" (mousedown)="onBoxMousedown($event)">
5586
- @for (tag of tags(); track $index) {
5587
- <span data-chip [class]="tagClasses">
5588
- <span data-label class="min-w-0 truncate">{{ tag.label }}</span>
5589
- @if (editable()) {
5590
- <button
5591
- type="button"
5592
- [class]="removeClasses"
5593
- [attr.aria-label]="labels.comboboxRemove(tag.label)"
5594
- (click)="remove($index)"
5595
- >
5596
- <span aria-hidden="true" [class]="crossClasses"></span>
5597
- </button>
5598
- } @else {
5599
- <span class="pe-2"></span>
5600
- }
5601
- </span>
6999
+ @if (coarse()) {
7000
+ <bf-native-select
7001
+ #native
7002
+ [options]="options()"
7003
+ [compareWith]="compareWith()"
7004
+ [size]="size()"
7005
+ [label]="label()"
7006
+ [ariaLabel]="ariaLabel()"
7007
+ [ariaLabelledby]="ariaLabelledby()"
7008
+ [placeholder]="placeholder()"
7009
+ [helpText]="helpText()"
7010
+ [errorMessage]="errorMessage()"
7011
+ [disabled]="disabled()"
7012
+ [readonly]="readonly()"
7013
+ [required]="required()"
7014
+ [invalid]="invalid()"
7015
+ [touched]="touched()"
7016
+ [errors]="errors()"
7017
+ [name]="name()"
7018
+ [id]="id()"
7019
+ [(value)]="value"
7020
+ (changed)="changed.emit($event)"
7021
+ (focused)="focused.emit($event)"
7022
+ (blurred)="blurred.emit($event)"
7023
+ (touch)="touch.emit()"
7024
+ />
7025
+ } @else {
7026
+ <bf-field
7027
+ #box
7028
+ [label]="label()"
7029
+ [helpText]="helpText()"
7030
+ [error]="errorText()"
7031
+ [required]="required()"
7032
+ [disabled]="disabled()"
7033
+ [controlId]="id()"
7034
+ >
7035
+ <div #anchor [class]="wrapperClasses()">
7036
+ <div
7037
+ #field
7038
+ role="combobox"
7039
+ aria-haspopup="listbox"
7040
+ [id]="id()"
7041
+ [attr.tabindex]="disabled() ? null : 0"
7042
+ [attr.aria-disabled]="disabled() ? 'true' : null"
7043
+ [attr.aria-readonly]="readonly() ? 'true' : null"
7044
+ [attr.aria-required]="required() ? 'true' : null"
7045
+ [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
7046
+ [attr.aria-controls]="core.controls()"
7047
+ [attr.aria-activedescendant]="core.active()?.id ?? null"
7048
+ [attr.aria-label]="ariaLabelAttr()"
7049
+ [attr.aria-labelledby]="label() ? box.labelId() : ariaLabelledbyAttr()"
7050
+ [attr.aria-invalid]="showError() ? 'true' : null"
7051
+ [attr.aria-describedby]="describedBy()"
7052
+ [attr.aria-errormessage]="errorMessageId()"
7053
+ [attr.data-placeholder]="selectedIndex() === -1 ? '' : null"
7054
+ [class]="classes()"
7055
+ (click)="onClick()"
7056
+ (keydown)="onKeydown($event)"
7057
+ (focus)="focused.emit($event)"
7058
+ (blur)="onBlur($event)"
7059
+ >
7060
+ <span class="min-w-0 truncate">{{ selectedLabel() ?? placeholder() }}</span>
7061
+ </div>
7062
+ </div>
7063
+ @if (name()) {
7064
+ <input type="hidden" [name]="name()" [value]="formKey()" [disabled]="disabled()" />
5602
7065
  }
5603
- <input
5604
- #el
5605
- type="text"
5606
- role="combobox"
5607
- autocomplete="off"
5608
- aria-autocomplete="list"
5609
- [id]="id()"
5610
- [attr.name]="name() || null"
5611
- [value]="core.query() ?? ''"
5612
- [placeholder]="tags().length ? '' : placeholder()"
5613
- [disabled]="disabled()"
5614
- [readOnly]="readonly()"
5615
- [required]="required() && !tags().length"
5616
- [attr.aria-expanded]="core.isOpen() ? 'true' : 'false'"
5617
- [attr.aria-controls]="core.controls()"
5618
- [attr.aria-activedescendant]="core.active()?.id ?? null"
5619
- [attr.aria-label]="ariaLabelAttr()"
5620
- [attr.aria-labelledby]="ariaLabelledbyAttr()"
5621
- [attr.aria-invalid]="showError() ? 'true' : null"
5622
- [attr.aria-describedby]="describedBy()"
5623
- [attr.aria-errormessage]="errorMessageId()"
5624
- [class]="inputClasses"
5625
- (input)="onInput(el)"
5626
- (keydown)="onKeydown($event)"
5627
- (click)="onClick()"
5628
- (focus)="focused.emit($event)"
5629
- (blur)="onBlur($event)"
5630
- />
5631
- </div>
5632
- </bf-field>
7066
+ <!-- The option labels for BfSelectHarness, readable while the list cannot open (disabled,
7067
+ read-only); hidden, so never rendered or announced. -->
7068
+ <span hidden data-bf-select-options>
7069
+ @for (option of options(); track $index) {
7070
+ <span [attr.data-disabled]="option.disabled ? '' : null">{{ option.label }}</span>
7071
+ }
7072
+ </span>
7073
+ </bf-field>
7074
+ }
5633
7075
  `
5634
7076
  }]
5635
7077
  }],
@@ -5662,14 +7104,6 @@ i0.ɵɵngDeclareClassMetadata({
5662
7104
  required: false
5663
7105
  }]
5664
7106
  }],
5665
- filter: [{
5666
- type: i0.Input,
5667
- args: [{
5668
- isSignal: true,
5669
- alias: "filter",
5670
- required: false
5671
- }]
5672
- }],
5673
7107
  size: [{
5674
7108
  type: i0.Input,
5675
7109
  args: [{
@@ -5726,22 +7160,6 @@ i0.ɵɵngDeclareClassMetadata({
5726
7160
  required: false
5727
7161
  }]
5728
7162
  }],
5729
- emptyLabel: [{
5730
- type: i0.Input,
5731
- args: [{
5732
- isSignal: true,
5733
- alias: "emptyLabel",
5734
- required: false
5735
- }]
5736
- }],
5737
- loading: [{
5738
- type: i0.Input,
5739
- args: [{
5740
- isSignal: true,
5741
- alias: "loading",
5742
- required: false
5743
- }]
5744
- }],
5745
7163
  disabled: [{
5746
7164
  type: i0.Input,
5747
7165
  args: [{
@@ -5810,10 +7228,6 @@ i0.ɵɵngDeclareClassMetadata({
5810
7228
  type: i0.Output,
5811
7229
  args: ["changed"]
5812
7230
  }],
5813
- queried: [{
5814
- type: i0.Output,
5815
- args: ["queried"]
5816
- }],
5817
7231
  focused: [{
5818
7232
  type: i0.Output,
5819
7233
  args: ["focused"]
@@ -5826,16 +7240,26 @@ i0.ɵɵngDeclareClassMetadata({
5826
7240
  type: i0.Output,
5827
7241
  args: ["touch"]
5828
7242
  }],
5829
- el: [{
7243
+ fieldEl: [{
5830
7244
  type: i0.ViewChild,
5831
- args: ["el", { isSignal: true }]
7245
+ args: ["field", { isSignal: true }]
5832
7246
  }],
5833
7247
  anchorEl: [{
5834
7248
  type: i0.ViewChild,
5835
- args: ["anchor", { isSignal: true }]
7249
+ args: ["anchor", { isSignal: true }]
7250
+ }],
7251
+ nativeSelect: [{
7252
+ type: i0.ViewChild,
7253
+ args: ["native", { isSignal: true }]
5836
7254
  }]
5837
7255
  }
5838
7256
  });
7257
+ function provideBfSelect() {
7258
+ return makeEnvironmentProviders([{
7259
+ provide: BF_TABLE_SORT_SELECT,
7260
+ useValue: BfSelectComponent
7261
+ }]);
7262
+ }
5839
7263
  const DATE_KINDS = [
5840
7264
  "day",
5841
7265
  "month",
@@ -6171,7 +7595,7 @@ function localDate(iso) {
6171
7595
  return date;
6172
7596
  }
6173
7597
  var BfDateTimeFieldComponent = class BfDateTimeFieldComponent {
6174
- overlay = inject(Overlay);
7598
+ environment = inject(EnvironmentInjector);
6175
7599
  document = inject(DOCUMENT);
6176
7600
  injector = inject(Injector);
6177
7601
  viewContainer = inject(ViewContainerRef);
@@ -6379,6 +7803,7 @@ var BfDateTimeFieldComponent = class BfDateTimeFieldComponent {
6379
7803
  }, ...ngDevMode ? [{ debugName: "timeOptionViews" }] : /* istanbul ignore next */ []);
6380
7804
  activeOptionId = computed(() => this.timeOptionViews().find((option) => option.active)?.id ?? null, ...ngDevMode ? [{ debugName: "activeOptionId" }] : /* istanbul ignore next */ []);
6381
7805
  anchored = null;
7806
+ motion = new AnchoredMotion();
6382
7807
  subscriptions = [];
6383
7808
  stopNavigation = null;
6384
7809
  constructor() {
@@ -6389,7 +7814,10 @@ var BfDateTimeFieldComponent = class BfDateTimeFieldComponent {
6389
7814
  media.addEventListener?.("change", onChange);
6390
7815
  inject(DestroyRef).onDestroy(() => media.removeEventListener?.("change", onChange));
6391
7816
  }
6392
- inject(DestroyRef).onDestroy(() => this.teardown());
7817
+ inject(DestroyRef).onDestroy(() => {
7818
+ this.teardown(false);
7819
+ this.motion.flush();
7820
+ });
6393
7821
  }
6394
7822
  focus(options) {
6395
7823
  const native = this.hostEl.querySelector("input");
@@ -6404,15 +7832,16 @@ var BfDateTimeFieldComponent = class BfDateTimeFieldComponent {
6404
7832
  openPicker() {
6405
7833
  const box = this.box()?.nativeElement;
6406
7834
  if (this.anchored || !box || !this.picker() || this.disabled() || this.readonly()) return;
7835
+ this.motion.flush();
6407
7836
  const direction = getComputedStyle(box).direction === "rtl" ? "rtl" : "ltr";
6408
- const anchored = createAnchoredOverlay(this.overlay, box, {
7837
+ const anchored = createAnchoredOverlay(this.environment, box, {
6409
7838
  positions: blockPositions("below-start"),
6410
7839
  direction,
6411
7840
  panelClass: ["bf-date-time-panel"]
6412
7841
  });
6413
7842
  this.anchored = anchored;
6414
7843
  this.activeTime.set(this.initialTime());
6415
- anchored.ref.attach(new TemplatePortal(this.panel(), this.viewContainer, void 0, this.injector));
7844
+ const view = anchored.ref.attach(new TemplatePortal(this.panel(), this.viewContainer, void 0, this.injector));
6416
7845
  const pane = anchored.ref.overlayElement;
6417
7846
  this.subscriptions = [anchored.ref.keydownEvents().subscribe((event) => {
6418
7847
  if (event.key === "Escape") {
@@ -6428,11 +7857,12 @@ var BfDateTimeFieldComponent = class BfDateTimeFieldComponent {
6428
7857
  pane.addEventListener("keydown", (event) => this.tabbed(event, pane));
6429
7858
  this.stopNavigation = watchPathChange(this.location, () => this.closePicker(false));
6430
7859
  this.pickerOpen.set(true);
7860
+ this.motion.enter(anchored.ref, box, view);
6431
7861
  afterNextRender(() => this.focusPanel(pane), { injector: this.injector });
6432
7862
  }
6433
7863
  closePicker(restoreFocus = true) {
6434
7864
  if (!this.anchored) return;
6435
- this.teardown();
7865
+ this.teardown(true);
6436
7866
  this.pickerOpen.set(false);
6437
7867
  if (restoreFocus) this.trigger()?.nativeElement.focus({ preventScroll: true });
6438
7868
  else if (!this.hostEl.contains(this.document.activeElement)) this.markLeft();
@@ -6710,13 +8140,16 @@ var BfDateTimeFieldComponent = class BfDateTimeFieldComponent {
6710
8140
  if (event.shiftKey) event.preventDefault();
6711
8141
  this.closePicker(true);
6712
8142
  }
6713
- teardown() {
8143
+ teardown(animate) {
6714
8144
  this.subscriptions.forEach((s) => s.unsubscribe());
6715
8145
  this.subscriptions = [];
6716
8146
  this.stopNavigation?.();
6717
8147
  this.stopNavigation = null;
6718
- this.anchored?.ref.dispose();
8148
+ const ref = this.anchored?.ref;
6719
8149
  this.anchored = null;
8150
+ const box = this.box()?.nativeElement;
8151
+ if (ref && animate && box) this.motion.leave(ref, box);
8152
+ else ref?.dispose();
6720
8153
  }
6721
8154
  static ɵfac = i0.ɵɵngDeclareFactory({
6722
8155
  minVersion: "12.0.0",
@@ -9198,6 +10631,639 @@ i0.ɵɵngDeclareClassMetadata({
9198
10631
  }]
9199
10632
  }
9200
10633
  });
9201
- export { BF_DESKTOP_TOAST_DEFAULTS, BF_DIALOG_DATA, BF_OVERLAY_DATA, BF_OVERLAY_HOST, BF_SIDE_PANEL_DATA, BfComboboxComponent, BfComponentOverlayRef, BfDateInputComponent, BfDateTimeInputComponent, BfDesktopToastComponent, BfDesktopToastService, BfDialogComponent, BfDialogRef, BfDialogService, BfMenuButtonComponent, BfMenuComponent, BfMenuDividerDirective, BfMenuGroupDirective, BfMenuItemCheckboxDirective, BfMenuItemDirective, BfMenuItemRadioDirective, BfMenuTriggerDirective, BfMultiComboboxComponent, BfOverlayRef, BfOverlayService, BfOverlayStack, BfPopoverComponent, BfPopoverTriggerDirective, BfSidePanelComponent, BfSidePanelRef, BfSidePanelService, BfSidebarToggleComponent, BfThemeMenuButtonComponent, BfTimeInputComponent, BfToastRef, BfTooltipComponent, BfTooltipDirective, OVERLAY_DURATION, OVERLAY_EASING, OVERLAY_REDUCED_DURATION, fadeDown, fadeIn, fadeOut, fadeUp, prefersReducedMotion, provideBfDesktopToast, scaleIn, slideDown, slideInFromEnd, slideOutToEnd, slideUp, ɵBfToastQueue, labelPanelByHeading as ɵlabelPanelByHeading, watchPanelHeading as ɵwatchPanelHeading };
10634
+ function onScreen(rect, view) {
10635
+ if (!view || rect.width <= 0 || rect.height <= 0) return false;
10636
+ return rect.bottom > 0 && rect.right > 0 && rect.top < view.innerHeight && rect.left < view.innerWidth;
10637
+ }
10638
+ function zoomedFrame(frame, origin, radius) {
10639
+ if (frame.width <= 0 || frame.height <= 0 || origin.width <= 0 || origin.height <= 0) return null;
10640
+ const scale = Math.max(origin.width / frame.width, origin.height / frame.height);
10641
+ const dx = origin.left + origin.width / 2 - (frame.left + frame.width / 2);
10642
+ const dy = origin.top + origin.height / 2 - (frame.top + frame.height / 2);
10643
+ const insetX = Math.max(0, (frame.width - origin.width / scale) / 2);
10644
+ const insetY = Math.max(0, (frame.height - origin.height / scale) / 2);
10645
+ const round = radius > 0 ? radius / scale : 0;
10646
+ return {
10647
+ transform: `translate(${dx}px, ${dy}px) scale(${scale})`,
10648
+ clipPath: `inset(${insetY}px ${insetX}px round ${round}px)`
10649
+ };
10650
+ }
10651
+ const OPEN_FRAME = {
10652
+ transform: "none",
10653
+ clipPath: "inset(0px 0px round 0px)"
10654
+ };
10655
+ function fade(el, entering, duration, easing) {
10656
+ return animateOrSettle(el, entering ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 1 }, { opacity: 0 }], {
10657
+ duration,
10658
+ easing,
10659
+ fill: entering ? "backwards" : "forwards"
10660
+ });
10661
+ }
10662
+ function playViewerMotion(parts, origin, entering, reduced) {
10663
+ if (reduced) return fade(parts.host, entering, 150, "linear");
10664
+ const view = parts.host.ownerDocument.defaultView;
10665
+ const originRect = origin?.isConnected ? origin.getBoundingClientRect() : null;
10666
+ const zoomed = origin && originRect && onScreen(originRect, view) ? zoomedFrame(parts.frame.getBoundingClientRect(), originRect, radiusOf(origin, view)) : null;
10667
+ if (!zoomed) return fade(parts.host, entering, 260, OVERLAY_EASING);
10668
+ for (const el of parts.chrome) fade(el, entering, 260, OVERLAY_EASING);
10669
+ return animateOrSettle(parts.frame, entering ? [zoomed, OPEN_FRAME] : [OPEN_FRAME, zoomed], {
10670
+ duration: 260,
10671
+ easing: OVERLAY_EASING,
10672
+ fill: entering ? "backwards" : "forwards"
10673
+ });
10674
+ }
10675
+ function radiusOf(origin, view) {
10676
+ const value = view?.getComputedStyle(origin).borderTopLeftRadius ?? "";
10677
+ const parsed = Number.parseFloat(value);
10678
+ return Number.isFinite(parsed) ? parsed : 0;
10679
+ }
10680
+ var BfImageViewerRef = class {
10681
+ overlayRef;
10682
+ id;
10683
+ images;
10684
+ afterClosed;
10685
+ closed;
10686
+ current;
10687
+ index;
10688
+ constructor(overlayRef, images, start) {
10689
+ this.overlayRef = overlayRef;
10690
+ this.id = overlayRef.id;
10691
+ this.images = images;
10692
+ this.current = signal(start, ...ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []);
10693
+ this.index = this.current.asReadonly();
10694
+ this.afterClosed = overlayRef.afterClosed.then(() => void 0);
10695
+ this.closed = overlayRef.closed;
10696
+ }
10697
+ next() {
10698
+ this.goTo(this.current() + 1);
10699
+ }
10700
+ previous() {
10701
+ this.goTo(this.current() - 1);
10702
+ }
10703
+ goTo(index) {
10704
+ const count = this.images.length;
10705
+ if (count === 0 || !Number.isFinite(index)) return;
10706
+ this.current.set((Math.trunc(index) % count + count) % count);
10707
+ }
10708
+ close() {
10709
+ this.overlayRef.close();
10710
+ }
10711
+ };
10712
+ const BF_IMAGE_VIEWER_CONTEXT = new InjectionToken("BF_IMAGE_VIEWER_CONTEXT");
10713
+ const KEEP_OPEN = "[data-frame], button, a, input, select, textarea, label, [data-caption], [data-actions]";
10714
+ const FRAME = "relative max-h-full max-w-full";
10715
+ const FRAME_SIZED = "aspect-(--bf-image-viewer-ratio) w-[min(100cqw,calc(100cqh*var(--bf-image-viewer-ratio)))]";
10716
+ const NAV = "pointer-events-auto absolute top-1/2 -translate-y-1/2";
10717
+ var BfImageViewerComponent = class BfImageViewerComponent {
10718
+ labels = inject(BF_LABELS);
10719
+ ref = inject(BfImageViewerRef);
10720
+ context = inject(BF_IMAGE_VIEWER_CONTEXT);
10721
+ injector = inject(Injector);
10722
+ document = inject(DOCUMENT);
10723
+ host = inject(ElementRef);
10724
+ closeIcon = X;
10725
+ previousIcon = ChevronLeft;
10726
+ nextIcon = ChevronRight;
10727
+ previousClasses = `${NAV} start-3`;
10728
+ nextClasses = `${NAV} end-3`;
10729
+ frame = viewChild.required("frame", ...ngDevMode ? [{ debugName: "frame" }] : /* istanbul ignore next */ []);
10730
+ scrim = viewChild.required("scrim", ...ngDevMode ? [{ debugName: "scrim" }] : /* istanbul ignore next */ []);
10731
+ top = viewChild.required("top", ...ngDevMode ? [{ debugName: "top" }] : /* istanbul ignore next */ []);
10732
+ bottom = viewChild.required("bottom", ...ngDevMode ? [{ debugName: "bottom" }] : /* istanbul ignore next */ []);
10733
+ statuses = signal(/* @__PURE__ */ new Map(), ...ngDevMode ? [{ debugName: "statuses" }] : /* istanbul ignore next */ []);
10734
+ ratios = signal(/* @__PURE__ */ new Map(), ...ngDevMode ? [{ debugName: "ratios" }] : /* istanbul ignore next */ []);
10735
+ count = computed(() => this.ref.images.length, ...ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []);
10736
+ index = this.ref.index;
10737
+ image = computed(() => this.ref.images[this.index()] ?? {
10738
+ src: "",
10739
+ label: ""
10740
+ }, ...ngDevMode ? [{ debugName: "image" }] : /* istanbul ignore next */ []);
10741
+ status = computed(() => this.statuses().get(this.index()) ?? "pending", ...ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []);
10742
+ shown = computed(() => this.status() === "error" ? [] : [{
10743
+ index: this.index(),
10744
+ image: this.image()
10745
+ }], ...ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []);
10746
+ counter = computed(() => this.labels.imageViewerPosition(this.index() + 1, this.count()), ...ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ []);
10747
+ stepped = linkedSignal({
10748
+ ...ngDevMode ? { debugName: "stepped" } : /* istanbul ignore next */ {},
10749
+ source: this.index,
10750
+ computation: (_, previous) => previous !== void 0
10751
+ });
10752
+ announcement = computed(() => {
10753
+ if (!this.stepped()) return "";
10754
+ const alt = this.image().label;
10755
+ return alt ? `${this.counter()}, ${alt}` : this.counter();
10756
+ }, ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
10757
+ placeholder = computed(() => {
10758
+ if (this.status() === "loaded") return null;
10759
+ const img = thumbnailImage(this.originFor(this.index()));
10760
+ if (!img) return null;
10761
+ const src = img.currentSrc ? img.currentSrc : img.getAttribute("src");
10762
+ return src === "" ? null : src;
10763
+ }, ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
10764
+ ratio = computed(() => {
10765
+ const own = this.ratios().get(this.index());
10766
+ if (own) return own;
10767
+ const img = thumbnailImage(this.originFor(this.index()));
10768
+ return img && img.naturalWidth > 0 && img.naturalHeight > 0 ? img.naturalWidth / img.naturalHeight : null;
10769
+ }, ...ngDevMode ? [{ debugName: "ratio" }] : /* istanbul ignore next */ []);
10770
+ frameClasses = computed(() => this.ratio() ? `${FRAME} ${FRAME_SIZED}` : `${FRAME} size-full`, ...ngDevMode ? [{ debugName: "frameClasses" }] : /* istanbul ignore next */ []);
10771
+ actionsContext = computed(() => ({
10772
+ $implicit: this.image(),
10773
+ index: this.index(),
10774
+ ref: this.ref
10775
+ }), ...ngDevMode ? [{ debugName: "actionsContext" }] : /* istanbul ignore next */ []);
10776
+ constructor() {
10777
+ this.context.register(this);
10778
+ }
10779
+ enter() {
10780
+ return playViewerMotion(this.parts(), this.originFor(this.index()), true, this.reduced());
10781
+ }
10782
+ exit() {
10783
+ return playViewerMotion(this.parts(), this.originFor(this.index()), false, this.reduced());
10784
+ }
10785
+ imageLoaded(event, index) {
10786
+ const img = event.target;
10787
+ if (img instanceof HTMLImageElement && img.naturalWidth > 0 && img.naturalHeight > 0) {
10788
+ const ratio = img.naturalWidth / img.naturalHeight;
10789
+ this.ratios.update((map) => new Map(map).set(index, ratio));
10790
+ }
10791
+ this.statuses.update((map) => new Map(map).set(index, "loaded"));
10792
+ }
10793
+ imageFailed(index) {
10794
+ this.statuses.update((map) => new Map(map).set(index, "error"));
10795
+ }
10796
+ keydown(event) {
10797
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
10798
+ const target = event.target;
10799
+ if (target instanceof HTMLElement && (target.isContentEditable || target.matches("input, textarea, select"))) return;
10800
+ const rtl = this.document.defaultView?.getComputedStyle(this.host.nativeElement).direction === "rtl";
10801
+ const forward = rtl ? "ArrowLeft" : "ArrowRight";
10802
+ const backward = rtl ? "ArrowRight" : "ArrowLeft";
10803
+ switch (event.key) {
10804
+ case forward:
10805
+ this.ref.next();
10806
+ break;
10807
+ case backward:
10808
+ this.ref.previous();
10809
+ break;
10810
+ case "Home":
10811
+ this.ref.goTo(0);
10812
+ break;
10813
+ case "End":
10814
+ this.ref.goTo(this.count() - 1);
10815
+ break;
10816
+ default: return;
10817
+ }
10818
+ event.preventDefault();
10819
+ }
10820
+ clicked(event) {
10821
+ const target = event.target;
10822
+ if (target instanceof Element && !target.closest(KEEP_OPEN)) this.ref.close();
10823
+ }
10824
+ originFor(index) {
10825
+ const origin = this.context.origin;
10826
+ if (typeof origin === "function") return origin(index) ?? null;
10827
+ return index === this.context.start ? origin : null;
10828
+ }
10829
+ parts() {
10830
+ const host = this.host.nativeElement;
10831
+ const navigation = Array.from(host.querySelectorAll("bf-icon-button[data-chrome]"));
10832
+ return {
10833
+ host,
10834
+ frame: this.frame().nativeElement,
10835
+ chrome: [
10836
+ this.scrim().nativeElement,
10837
+ this.top().nativeElement,
10838
+ this.bottom().nativeElement,
10839
+ ...navigation
10840
+ ]
10841
+ };
10842
+ }
10843
+ reduced() {
10844
+ return prefersReducedMotion(this.document.defaultView);
10845
+ }
10846
+ static ɵfac = i0.ɵɵngDeclareFactory({
10847
+ minVersion: "12.0.0",
10848
+ version: "22.1.7",
10849
+ ngImport: i0,
10850
+ type: BfImageViewerComponent,
10851
+ deps: [],
10852
+ target: i0.ɵɵFactoryTarget.Component
10853
+ });
10854
+ static ɵcmp = i0.ɵɵngDeclareComponent({
10855
+ minVersion: "17.0.0",
10856
+ version: "22.1.7",
10857
+ type: BfImageViewerComponent,
10858
+ isStandalone: true,
10859
+ selector: "bf-image-viewer",
10860
+ host: {
10861
+ attributes: { "data-bf-image-viewer": "" },
10862
+ listeners: {
10863
+ "keydown": "keydown($event)",
10864
+ "click": "clicked($event)"
10865
+ },
10866
+ properties: {
10867
+ "style.--bf-image-viewer-ratio": "ratio()",
10868
+ "attr.data-index": "index()",
10869
+ "attr.data-count": "count()",
10870
+ "attr.data-label": "context.label"
10871
+ },
10872
+ classAttribute: "dark fixed inset-0 flex flex-col text-text"
10873
+ },
10874
+ viewQueries: [
10875
+ {
10876
+ propertyName: "frame",
10877
+ first: true,
10878
+ predicate: ["frame"],
10879
+ descendants: true,
10880
+ isSignal: true
10881
+ },
10882
+ {
10883
+ propertyName: "scrim",
10884
+ first: true,
10885
+ predicate: ["scrim"],
10886
+ descendants: true,
10887
+ isSignal: true
10888
+ },
10889
+ {
10890
+ propertyName: "top",
10891
+ first: true,
10892
+ predicate: ["top"],
10893
+ descendants: true,
10894
+ isSignal: true
10895
+ },
10896
+ {
10897
+ propertyName: "bottom",
10898
+ first: true,
10899
+ predicate: ["bottom"],
10900
+ descendants: true,
10901
+ isSignal: true
10902
+ }
10903
+ ],
10904
+ ngImport: i0,
10905
+ template: `
10906
+ <div #scrim data-scrim class="bg-scrim/90 absolute inset-0"></div>
10907
+ <div #top data-chrome class="relative flex shrink-0 items-center gap-3 p-3">
10908
+ <span data-counter class="text-label min-w-0 flex-1 ps-2" [class.invisible]="count() < 2">
10909
+ {{ counter() }}
10910
+ </span>
10911
+ <bf-icon-button data-close cdkFocusInitial [label]="labels.close" (clicked)="ref.close()">
10912
+ <bf-icon [icon]="closeIcon" />
10913
+ </bf-icon-button>
10914
+ </div>
10915
+ <div class="relative min-h-0 flex-1">
10916
+ <div
10917
+ data-stage
10918
+ class="[container-type:size] absolute inset-0 grid place-items-center px-3 sm:px-16"
10919
+ >
10920
+ <div #frame data-frame [attr.data-status]="status()" [class]="frameClasses()">
10921
+ @if (placeholder(); as thumb) {
10922
+ <img
10923
+ data-placeholder
10924
+ aria-hidden="true"
10925
+ alt=""
10926
+ class="absolute inset-0 size-full object-contain"
10927
+ [src]="thumb"
10928
+ />
10929
+ }
10930
+ @for (item of shown(); track item.index) {
10931
+ <img
10932
+ bfFadeIn
10933
+ data-image
10934
+ class="absolute inset-0 size-full object-contain"
10935
+ animate.leave="opacity-0 transition-opacity duration-slow ease-md motion-reduce:transition-none"
10936
+ [src]="item.image.src"
10937
+ [alt]="item.image.label"
10938
+ (load)="imageLoaded($event, item.index)"
10939
+ (error)="imageFailed(item.index)"
10940
+ />
10941
+ }
10942
+ @if (status() === 'pending') {
10943
+ <div class="absolute inset-0 grid place-items-center">
10944
+ <bf-spinner size="lg" />
10945
+ </div>
10946
+ } @else if (status() === 'error') {
10947
+ <p
10948
+ data-failed
10949
+ class="text-body text-text-secondary absolute inset-0 grid place-items-center p-4 text-center"
10950
+ >
10951
+ {{ labels.imageLoadFailed }}
10952
+ </p>
10953
+ }
10954
+ </div>
10955
+ </div>
10956
+ @if (count() > 1) {
10957
+ <bf-icon-button
10958
+ data-chrome
10959
+ data-previous
10960
+ [class]="previousClasses"
10961
+ [label]="labels.imageViewerPrevious"
10962
+ (clicked)="ref.previous()"
10963
+ >
10964
+ <bf-icon class="rtl:-scale-x-100" [icon]="previousIcon" />
10965
+ </bf-icon-button>
10966
+ <bf-icon-button
10967
+ data-chrome
10968
+ data-next
10969
+ [class]="nextClasses"
10970
+ [label]="labels.imageViewerNext"
10971
+ (clicked)="ref.next()"
10972
+ >
10973
+ <bf-icon class="rtl:-scale-x-100" [icon]="nextIcon" />
10974
+ </bf-icon-button>
10975
+ }
10976
+ </div>
10977
+ <div
10978
+ #bottom
10979
+ data-chrome
10980
+ class="relative flex shrink-0 flex-wrap items-center gap-x-4 gap-y-2 px-5 pt-2 pb-4 empty:hidden"
10981
+ >
10982
+ @if (image().caption; as caption) {
10983
+ <p data-caption class="text-body min-w-0 flex-1 wrap-anywhere">{{ caption }}</p>
10984
+ }
10985
+ @if (context.actions; as actions) {
10986
+ <div data-actions class="ms-auto flex flex-wrap items-center gap-2">
10987
+ <ng-container
10988
+ [ngTemplateOutlet]="actions"
10989
+ [ngTemplateOutletContext]="actionsContext()"
10990
+ [ngTemplateOutletInjector]="injector"
10991
+ />
10992
+ </div>
10993
+ }
10994
+ </div>
10995
+ <div data-announcement aria-live="polite" class="sr-only">{{ announcement() }}</div>
10996
+ `,
10997
+ isInline: true,
10998
+ dependencies: [
10999
+ {
11000
+ kind: "directive",
11001
+ type: NgTemplateOutlet,
11002
+ selector: "[ngTemplateOutlet]",
11003
+ inputs: [
11004
+ "ngTemplateOutletContext",
11005
+ "ngTemplateOutlet",
11006
+ "ngTemplateOutletInjector"
11007
+ ]
11008
+ },
11009
+ {
11010
+ kind: "directive",
11011
+ type: BfFadeInDirective,
11012
+ selector: "img[bfFadeIn]"
11013
+ },
11014
+ {
11015
+ kind: "component",
11016
+ type: BfIconButtonComponent,
11017
+ selector: "bf-icon-button, a[bfIconButton]",
11018
+ inputs: [
11019
+ "variant",
11020
+ "size",
11021
+ "type",
11022
+ "disabled",
11023
+ "label"
11024
+ ],
11025
+ outputs: ["clicked"]
11026
+ },
11027
+ {
11028
+ kind: "component",
11029
+ type: BfIconComponent,
11030
+ selector: "bf-icon",
11031
+ inputs: [
11032
+ "icon",
11033
+ "size",
11034
+ "label",
11035
+ "filled"
11036
+ ]
11037
+ },
11038
+ {
11039
+ kind: "component",
11040
+ type: BfSpinnerComponent,
11041
+ selector: "bf-spinner",
11042
+ inputs: [
11043
+ "size",
11044
+ "label",
11045
+ "decorative"
11046
+ ]
11047
+ }
11048
+ ]
11049
+ });
11050
+ };
11051
+ i0.ɵɵngDeclareClassMetadata({
11052
+ minVersion: "12.0.0",
11053
+ version: "22.1.7",
11054
+ ngImport: i0,
11055
+ type: BfImageViewerComponent,
11056
+ decorators: [{
11057
+ type: Component,
11058
+ args: [{
11059
+ selector: "bf-image-viewer",
11060
+ imports: [
11061
+ NgTemplateOutlet,
11062
+ BfFadeInDirective,
11063
+ BfIconButtonComponent,
11064
+ BfIconComponent,
11065
+ BfSpinnerComponent
11066
+ ],
11067
+ host: {
11068
+ class: "dark fixed inset-0 flex flex-col text-text",
11069
+ "data-bf-image-viewer": "",
11070
+ "[style.--bf-image-viewer-ratio]": "ratio()",
11071
+ "[attr.data-index]": "index()",
11072
+ "[attr.data-count]": "count()",
11073
+ "[attr.data-label]": "context.label",
11074
+ "(keydown)": "keydown($event)",
11075
+ "(click)": "clicked($event)"
11076
+ },
11077
+ template: `
11078
+ <div #scrim data-scrim class="bg-scrim/90 absolute inset-0"></div>
11079
+ <div #top data-chrome class="relative flex shrink-0 items-center gap-3 p-3">
11080
+ <span data-counter class="text-label min-w-0 flex-1 ps-2" [class.invisible]="count() < 2">
11081
+ {{ counter() }}
11082
+ </span>
11083
+ <bf-icon-button data-close cdkFocusInitial [label]="labels.close" (clicked)="ref.close()">
11084
+ <bf-icon [icon]="closeIcon" />
11085
+ </bf-icon-button>
11086
+ </div>
11087
+ <div class="relative min-h-0 flex-1">
11088
+ <div
11089
+ data-stage
11090
+ class="[container-type:size] absolute inset-0 grid place-items-center px-3 sm:px-16"
11091
+ >
11092
+ <div #frame data-frame [attr.data-status]="status()" [class]="frameClasses()">
11093
+ @if (placeholder(); as thumb) {
11094
+ <img
11095
+ data-placeholder
11096
+ aria-hidden="true"
11097
+ alt=""
11098
+ class="absolute inset-0 size-full object-contain"
11099
+ [src]="thumb"
11100
+ />
11101
+ }
11102
+ @for (item of shown(); track item.index) {
11103
+ <img
11104
+ bfFadeIn
11105
+ data-image
11106
+ class="absolute inset-0 size-full object-contain"
11107
+ animate.leave="opacity-0 transition-opacity duration-slow ease-md motion-reduce:transition-none"
11108
+ [src]="item.image.src"
11109
+ [alt]="item.image.label"
11110
+ (load)="imageLoaded($event, item.index)"
11111
+ (error)="imageFailed(item.index)"
11112
+ />
11113
+ }
11114
+ @if (status() === 'pending') {
11115
+ <div class="absolute inset-0 grid place-items-center">
11116
+ <bf-spinner size="lg" />
11117
+ </div>
11118
+ } @else if (status() === 'error') {
11119
+ <p
11120
+ data-failed
11121
+ class="text-body text-text-secondary absolute inset-0 grid place-items-center p-4 text-center"
11122
+ >
11123
+ {{ labels.imageLoadFailed }}
11124
+ </p>
11125
+ }
11126
+ </div>
11127
+ </div>
11128
+ @if (count() > 1) {
11129
+ <bf-icon-button
11130
+ data-chrome
11131
+ data-previous
11132
+ [class]="previousClasses"
11133
+ [label]="labels.imageViewerPrevious"
11134
+ (clicked)="ref.previous()"
11135
+ >
11136
+ <bf-icon class="rtl:-scale-x-100" [icon]="previousIcon" />
11137
+ </bf-icon-button>
11138
+ <bf-icon-button
11139
+ data-chrome
11140
+ data-next
11141
+ [class]="nextClasses"
11142
+ [label]="labels.imageViewerNext"
11143
+ (clicked)="ref.next()"
11144
+ >
11145
+ <bf-icon class="rtl:-scale-x-100" [icon]="nextIcon" />
11146
+ </bf-icon-button>
11147
+ }
11148
+ </div>
11149
+ <div
11150
+ #bottom
11151
+ data-chrome
11152
+ class="relative flex shrink-0 flex-wrap items-center gap-x-4 gap-y-2 px-5 pt-2 pb-4 empty:hidden"
11153
+ >
11154
+ @if (image().caption; as caption) {
11155
+ <p data-caption class="text-body min-w-0 flex-1 wrap-anywhere">{{ caption }}</p>
11156
+ }
11157
+ @if (context.actions; as actions) {
11158
+ <div data-actions class="ms-auto flex flex-wrap items-center gap-2">
11159
+ <ng-container
11160
+ [ngTemplateOutlet]="actions"
11161
+ [ngTemplateOutletContext]="actionsContext()"
11162
+ [ngTemplateOutletInjector]="injector"
11163
+ />
11164
+ </div>
11165
+ }
11166
+ </div>
11167
+ <div data-announcement aria-live="polite" class="sr-only">{{ announcement() }}</div>
11168
+ `
11169
+ }]
11170
+ }],
11171
+ ctorParameters: () => [],
11172
+ propDecorators: {
11173
+ frame: [{
11174
+ type: i0.ViewChild,
11175
+ args: ["frame", { isSignal: true }]
11176
+ }],
11177
+ scrim: [{
11178
+ type: i0.ViewChild,
11179
+ args: ["scrim", { isSignal: true }]
11180
+ }],
11181
+ top: [{
11182
+ type: i0.ViewChild,
11183
+ args: ["top", { isSignal: true }]
11184
+ }],
11185
+ bottom: [{
11186
+ type: i0.ViewChild,
11187
+ args: ["bottom", { isSignal: true }]
11188
+ }]
11189
+ }
11190
+ });
11191
+ function thumbnailImage(origin) {
11192
+ if (!origin) return null;
11193
+ return origin instanceof HTMLImageElement ? origin : origin.querySelector("img");
11194
+ }
11195
+ var BfImageViewerService = class BfImageViewerService {
11196
+ overlays = inject(BfOverlayService);
11197
+ injector = inject(Injector);
11198
+ open(options) {
11199
+ const images = [...options.images];
11200
+ if (images.length === 0) throw new Error("BfImageViewerService: open() needs at least one image");
11201
+ const requested = Math.trunc(options.index ?? 0);
11202
+ const start = Number.isFinite(requested) ? (requested % images.length + images.length) % images.length : 0;
11203
+ const injector = options.injector ?? this.injector;
11204
+ const label = options.ariaLabel?.length ? options.ariaLabel : injector.get(BF_LABELS).imageViewer;
11205
+ let viewerRef = null;
11206
+ let motion = null;
11207
+ const context = {
11208
+ start,
11209
+ label,
11210
+ origin: options.origin ?? null,
11211
+ actions: options.actions ?? null,
11212
+ register: (viewer) => {
11213
+ motion = viewer;
11214
+ }
11215
+ };
11216
+ const providers = (ref) => {
11217
+ viewerRef = new BfImageViewerRef(ref, images, start);
11218
+ return [{
11219
+ provide: BfImageViewerRef,
11220
+ useValue: viewerRef
11221
+ }, {
11222
+ provide: BF_IMAGE_VIEWER_CONTEXT,
11223
+ useValue: context
11224
+ }];
11225
+ };
11226
+ this.overlays.open(BfImageViewerComponent, {
11227
+ position: "fullscreen",
11228
+ backdrop: false,
11229
+ role: "dialog",
11230
+ ariaLabel: label,
11231
+ closeOnNavigation: options.closeOnNavigation,
11232
+ panelClass: ["bf-image-viewer-panel"],
11233
+ injector,
11234
+ providers,
11235
+ enter: (panel) => motion ? motion.enter() : fadeIn(panel),
11236
+ exit: (panel) => motion ? motion.exit() : fadeOut(panel)
11237
+ });
11238
+ if (!viewerRef) throw new Error("BfImageViewerService: the overlay core did not build the viewer providers");
11239
+ return viewerRef;
11240
+ }
11241
+ static ɵfac = i0.ɵɵngDeclareFactory({
11242
+ minVersion: "12.0.0",
11243
+ version: "22.1.7",
11244
+ ngImport: i0,
11245
+ type: BfImageViewerService,
11246
+ deps: [],
11247
+ target: i0.ɵɵFactoryTarget.Injectable
11248
+ });
11249
+ static ɵprov = i0.ɵɵngDeclareInjectable({
11250
+ minVersion: "12.0.0",
11251
+ version: "22.1.7",
11252
+ ngImport: i0,
11253
+ type: BfImageViewerService,
11254
+ providedIn: "root"
11255
+ });
11256
+ };
11257
+ i0.ɵɵngDeclareClassMetadata({
11258
+ minVersion: "12.0.0",
11259
+ version: "22.1.7",
11260
+ ngImport: i0,
11261
+ type: BfImageViewerService,
11262
+ decorators: [{
11263
+ type: Injectable,
11264
+ args: [{ providedIn: "root" }]
11265
+ }]
11266
+ });
11267
+ export { BF_DESKTOP_TOAST_DEFAULTS, BF_DIALOG_DATA, BF_OVERLAY_DATA, BF_OVERLAY_HOST, BF_SIDE_PANEL_DATA, BfChatComposerComponent, BfComboboxComponent, BfComponentOverlayRef, BfDateInputComponent, BfDateTimeInputComponent, BfDesktopToastComponent, BfDesktopToastService, BfDialogComponent, BfDialogRef, BfDialogService, BfImageViewerComponent, BfImageViewerRef, BfImageViewerService, BfMenuButtonComponent, BfMenuComponent, BfMenuDividerDirective, BfMenuGroupDirective, BfMenuItemCheckboxDirective, BfMenuItemDirective, BfMenuItemRadioDirective, BfMenuTriggerDirective, BfMultiComboboxComponent, BfOverlayRef, BfOverlayService, BfOverlayStack, BfPopoverComponent, BfPopoverTriggerDirective, BfSelectComponent, BfSidePanelComponent, BfSidePanelRef, BfSidePanelService, BfSidebarToggleComponent, BfThemeMenuButtonComponent, BfTimeInputComponent, BfToastRef, BfTooltipComponent, BfTooltipDirective, OVERLAY_DURATION, OVERLAY_EASING, OVERLAY_REDUCED_DURATION, fadeDown, fadeIn, fadeOut, fadeUp, prefersReducedMotion, provideBfDesktopToast, provideBfSelect, scaleIn, slideDown, slideInFromEnd, slideOutToEnd, slideUp, ɵBfToastQueue, labelPanelByHeading as ɵlabelPanelByHeading, watchPanelHeading as ɵwatchPanelHeading };
9202
11268
 
9203
11269
  //# sourceMappingURL=bitforgehq-ui-overlay.mjs.map