@db-ux/ngx-core-components 4.12.0 → 4.12.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @db-ux/ngx-core-components
2
2
 
3
+ ## 4.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fix(custom-select): dropdown with `dropdownWidth="auto"` now correctly sizes to content width and respects the trigger minimum width. Long option labels no longer get truncated: `auto` keeps them on a single line (dropdown grows to the longest option), while `fixed` and `full` wrap long labels onto new lines. - [see commit 68dedc3](https://github.com/db-ux-design-system/core-web/commit/68dedc33c324b48339d5bb73a85fdff3045ed059)
8
+
9
+ - fix(drawer): prevent backdrop drag-close when selection starts inside content - [see commit b53ff8a](https://github.com/db-ux-design-system/core-web/commit/b53ff8a4f0a5350c5be41fad072e14797676bba7)
10
+
3
11
  ## 4.12.0
4
12
 
5
13
  _version bump_
@@ -2074,24 +2074,69 @@ const handleDataOutside = (el) => {
2074
2074
  const handleFixedDropdown = (element, parent, placement) => {
2075
2075
  if (!element || !parent)
2076
2076
  return;
2077
- // We skip this if we are in mobile it's already fixed
2077
+ const fullWidth = element.dataset['width'] === 'full';
2078
+ const autoWidth = element.dataset['width'] === 'auto';
2079
+ // Reset width-specific inline styles first so a previous mode (e.g. "auto")
2080
+ // doesn't leave a stale minInlineSize/inlineSize behind when the dropdown
2081
+ // width changes at runtime. This must happen before getFloatingProps
2082
+ // measures the element, otherwise the dropdown would be measured with a
2083
+ // width it no longer has and positioned incorrectly. It also has to run
2084
+ // before the mobile bailout below: otherwise a desktop minInlineSize would
2085
+ // survive into the mobile sheet, where CSS min-inline-size beats the
2086
+ // mobile max-inline-size guard and overflows the viewport.
2087
+ element.style.inlineSize = '';
2088
+ element.style.minInlineSize = '';
2089
+ // We skip the rest if we are in mobile, it's already fixed via CSS.
2078
2090
  if (getComputedStyle(element).zIndex === '9999')
2079
2091
  return;
2080
- const { top, bottom, childHeight, childWidth, width, right, left, correctedPlacement } = getFloatingProps(element, parent, placement);
2081
- const fullWidth = element.dataset['width'] === 'full';
2092
+ const { top, bottom, childHeight, childWidth, width, right, left, correctedPlacement, innerWidth } = getFloatingProps(element, parent, placement);
2093
+ // For auto width the dropdown is forced to be at least as wide as the trigger,
2094
+ // but clamped to its own max-inline-size: CSS lets a min-inline-size override
2095
+ // the max when the minimum is larger, so a trigger wider than the viewport
2096
+ // limit would otherwise drop the side margins or overflow horizontally.
2097
+ let autoMinWidth = width;
2098
+ if (autoWidth) {
2099
+ const maxInlineSize = parseFloat(getComputedStyle(element).maxInlineSize);
2100
+ if (!isNaN(maxInlineSize) && maxInlineSize > 0) {
2101
+ autoMinWidth = Math.min(width, maxInlineSize);
2102
+ }
2103
+ }
2082
2104
  if (fullWidth) {
2083
2105
  element.style.inlineSize = `${width}px`;
2084
2106
  }
2085
- if (correctedPlacement === 'top' || correctedPlacement === 'bottom' || correctedPlacement === 'top-start' || correctedPlacement === 'bottom-start') {
2107
+ else if (autoWidth) {
2108
+ element.style.minInlineSize = `${autoMinWidth}px`;
2109
+ }
2110
+ // getFloatingProps measured childWidth before the inline styles were
2111
+ // (re)applied, so use the width the dropdown will actually have:
2112
+ // - auto: the clamped minimum, so end-aligned dropdowns don't extend past
2113
+ // the trigger's right edge.
2114
+ // - full: the trigger width (the reset above drops it to content width).
2115
+ let effectiveChildWidth = childWidth;
2116
+ if (autoWidth) {
2117
+ effectiveChildWidth = Math.max(childWidth, autoMinWidth);
2118
+ }
2119
+ else if (fullWidth) {
2120
+ effectiveChildWidth = width;
2121
+ }
2122
+ // getFloatingProps detects horizontal overflow assuming a centered element
2123
+ // (it halves childWidth). The dropdown is actually start-aligned (inset =
2124
+ // left), so for the wider auto dropdown re-check overflow against its full
2125
+ // width and flip to end-alignment when it would extend past the viewport.
2126
+ let dropdownPlacement = correctedPlacement;
2127
+ if (autoWidth && (dropdownPlacement === 'top' || dropdownPlacement === 'bottom' || dropdownPlacement === 'top-start' || dropdownPlacement === 'bottom-start') && left + effectiveChildWidth > innerWidth) {
2128
+ dropdownPlacement = dropdownPlacement.startsWith('top') ? 'top-end' : 'bottom-end';
2129
+ }
2130
+ if (dropdownPlacement === 'top' || dropdownPlacement === 'bottom' || dropdownPlacement === 'top-start' || dropdownPlacement === 'bottom-start') {
2086
2131
  element.style.insetInlineStart = `${left}px`;
2087
2132
  }
2088
- else if (correctedPlacement === 'top-end' || correctedPlacement === 'bottom-end') {
2089
- element.style.insetInlineStart = `${right - childWidth}px`;
2133
+ else if (dropdownPlacement === 'top-end' || dropdownPlacement === 'bottom-end') {
2134
+ element.style.insetInlineStart = `${Math.max(right - effectiveChildWidth, 0)}px`;
2090
2135
  }
2091
- if (correctedPlacement?.startsWith('top')) {
2136
+ if (dropdownPlacement?.startsWith('top')) {
2092
2137
  element.style.insetBlockStart = `${top - childHeight}px`;
2093
2138
  }
2094
- else if (correctedPlacement?.startsWith('bottom')) {
2139
+ else if (dropdownPlacement?.startsWith('bottom')) {
2095
2140
  element.style.insetBlockStart = `${bottom}px`;
2096
2141
  }
2097
2142
  element.style.position = 'fixed';
@@ -2638,7 +2683,8 @@ class DBCustomSelectListItem {
2638
2683
  [disabled]="getBoolean(disabled(), 'disabled')"
2639
2684
  [value]="value()"
2640
2685
  (change)="handleChange($event)" />
2641
- @if(label()){{{label()}}} <ng-content></ng-content
2686
+ <span class="db-custom-select-list-item-label"
2687
+ >@if(label()){{{label()}}} <ng-content></ng-content></span
2642
2688
  ></label>
2643
2689
  }@else{
2644
2690
  <span>{{groupTitle()}}</span>
@@ -2675,7 +2721,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
2675
2721
  [disabled]="getBoolean(disabled(), 'disabled')"
2676
2722
  [value]="value()"
2677
2723
  (change)="handleChange($event)" />
2678
- @if(label()){{{label()}}} <ng-content></ng-content
2724
+ <span class="db-custom-select-list-item-label"
2725
+ >@if(label()){{{label()}}} <ng-content></ng-content></span
2679
2726
  ></label>
2680
2727
  }@else{
2681
2728
  <span>{{groupTitle()}}</span>
@@ -4196,6 +4243,9 @@ class DBCustomSelect {
4196
4243
  .toLowerCase()
4197
4244
  .includes(filterText.toLowerCase())));
4198
4245
  }
4246
+ if (this.detailsRef()?.nativeElement?.open) {
4247
+ this.handleAutoPlacement();
4248
+ }
4199
4249
  }
4200
4250
  handleClearAll(event) {
4201
4251
  event.stopPropagation();
@@ -4531,6 +4581,10 @@ class DBCustomSelect {
4531
4581
  // ---
4532
4582
  this._options.set(this.options());
4533
4583
  this.amountOptions.set(this.options()?.filter((option) => !option.isGroupTitle).length ?? 0);
4584
+ // Reposition open auto-width dropdowns: replacing options can change their content width.
4585
+ if (this.detailsRef()?.nativeElement?.open) {
4586
+ this.handleAutoPlacement();
4587
+ }
4534
4588
  }, Number(VERSION.major) < 19
4535
4589
  ? { allowSignalWrites: true }
4536
4590
  : undefined);
@@ -5375,6 +5429,18 @@ const DividerVariantList = ['horizontal', 'vertical'];
5375
5429
 
5376
5430
  const defaultProps$q = {};
5377
5431
  class DBDrawer {
5432
+ isNotModal() {
5433
+ return (this.position() === "absolute" ||
5434
+ this.backdrop() === "none" ||
5435
+ this.variant() === "inside");
5436
+ }
5437
+ handleBackdropPointerDown(event) {
5438
+ // Remember whether the pointer interaction started on the backdrop
5439
+ // (the DIALOG element itself) so we only close on a real backdrop
5440
+ // click and not when a drag started inside the content and ended
5441
+ // on the backdrop.
5442
+ this.backdropPointerDown.set(event?.target?.nodeName === "DIALOG");
5443
+ }
5378
5444
  handleClose(event, forceClose) {
5379
5445
  if (!event)
5380
5446
  return;
@@ -5395,11 +5461,15 @@ class DBDrawer {
5395
5461
  }
5396
5462
  if (event.target?.nodeName === "DIALOG" &&
5397
5463
  event.type === "click" &&
5398
- this.backdrop() !== "none") {
5464
+ this.backdrop() !== "none" &&
5465
+ this.backdropPointerDown()) {
5399
5466
  if (this.close) {
5400
5467
  this.close.emit(event);
5401
5468
  }
5402
5469
  }
5470
+ // Reset after handling the click so the next interaction
5471
+ // starts from a clean state.
5472
+ this.backdropPointerDown.set(false);
5403
5473
  }
5404
5474
  }
5405
5475
  handleDialogOpen() {
@@ -5409,12 +5479,15 @@ class DBDrawer {
5409
5479
  if (this.dialogContainerRef()?.nativeElement) {
5410
5480
  (this.dialogContainerRef()?.nativeElement).removeAttribute("data-transition");
5411
5481
  }
5412
- if (this.position() === "absolute" ||
5413
- this.backdrop() === "none" ||
5414
- this.variant() === "inside") {
5482
+ if (this.isNotModal()) {
5415
5483
  this._ref()?.nativeElement.show();
5416
5484
  }
5417
5485
  else {
5486
+ // Set the closedby attribute imperatively: the JSX
5487
+ // dialog type does not know this attribute yet, and it
5488
+ // only applies to modal dialogs. "any" enables native
5489
+ // light dismiss (backdrop click / Esc).
5490
+ this._ref()?.nativeElement.setAttribute("closedby", "any");
5418
5491
  this._ref()?.nativeElement.showModal();
5419
5492
  }
5420
5493
  void delay(() => {
@@ -5473,6 +5546,7 @@ class DBDrawer {
5473
5546
  this._ref = viewChild("_ref", ...(ngDevMode ? [{ debugName: "_ref" }] : /* istanbul ignore next */ []));
5474
5547
  this.dialogContainerRef = viewChild("dialogContainerRef", ...(ngDevMode ? [{ debugName: "dialogContainerRef" }] : /* istanbul ignore next */ []));
5475
5548
  this.initialized = signal(false, ...(ngDevMode ? [{ debugName: "initialized" }] : /* istanbul ignore next */ []));
5549
+ this.backdropPointerDown = signal(false, ...(ngDevMode ? [{ debugName: "backdropPointerDown" }] : /* istanbul ignore next */ []));
5476
5550
  this.observer = signal(undefined, ...(ngDevMode ? [{ debugName: "observer" }] : /* istanbul ignore next */ []));
5477
5551
  if (typeof window !== "undefined") {
5478
5552
  effect(() => {
@@ -5562,6 +5636,7 @@ class DBDrawer {
5562
5636
  [attr.id]="id() ?? propOverrides()?.id"
5563
5637
  #_ref
5564
5638
  (click)="handleClose($event)"
5639
+ (mousedown)="handleBackdropPointerDown($event)"
5565
5640
  (keydown)="handleClose($event)"
5566
5641
  [attr.data-position]="position()"
5567
5642
  [attr.data-backdrop]="backdrop()"
@@ -5601,6 +5676,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
5601
5676
  [attr.id]="id() ?? propOverrides()?.id"
5602
5677
  #_ref
5603
5678
  (click)="handleClose($event)"
5679
+ (mousedown)="handleBackdropPointerDown($event)"
5604
5680
  (keydown)="handleClose($event)"
5605
5681
  [attr.data-position]="position()"
5606
5682
  [attr.data-backdrop]="backdrop()"