@basis-ng/primitives 0.0.1-alpha.144 → 0.0.1-alpha.146

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, Component, inject, ElementRef, signal, model, computed, viewChildren, booleanAttribute, ChangeDetectionStrategy, contentChildren, effect, Injectable, contentChild, ViewEncapsulation, HostListener, Directive, linkedSignal, TemplateRef, ChangeDetectorRef, Pipe, afterRenderEffect, RendererFactory2, PLATFORM_ID } from '@angular/core';
2
+ import { input, output, Component, inject, ElementRef, signal, model, computed, viewChildren, booleanAttribute, ChangeDetectionStrategy, contentChildren, effect, Injectable, contentChild, ViewEncapsulation, viewChild, Directive, linkedSignal, TemplateRef, ChangeDetectorRef, Pipe, afterRenderEffect, RendererFactory2, PLATFORM_ID } from '@angular/core';
3
3
  import { NgIcon, provideIcons } from '@ng-icons/core';
4
4
  import { lucideX, lucideChevronRight, lucideChevronLeft, lucideLoaderCircle, lucideLoader, lucideGripVertical } from '@ng-icons/lucide';
5
5
  import { GridCellWidget, Grid, GridRow, GridCell } from '@angular/aria/grid';
@@ -1465,13 +1465,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1465
1465
  }], ctorParameters: () => [] });
1466
1466
 
1467
1467
  /**
1468
- * A draggable bottom sheet drawer with open/close and drag-to-dismiss behavior.
1468
+ * A draggable floating drawer that can slide from any side.
1469
1469
  */
1470
1470
  class Drawer {
1471
+ panel = viewChild.required('panel');
1471
1472
  /**
1472
1473
  * Model indicating whether the drawer is open.
1473
1474
  */
1474
1475
  isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1476
+ /**
1477
+ * Side of the viewport the drawer appears from.
1478
+ */
1479
+ side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : []));
1480
+ /**
1481
+ * Whether the drawer can be dragged closed.
1482
+ */
1483
+ draggable = input(true, { ...(ngDevMode ? { debugName: "draggable" } : {}), transform: booleanAttribute });
1475
1484
  /**
1476
1485
  * Emitted when the sheet is closed.
1477
1486
  */
@@ -1481,66 +1490,49 @@ class Drawer {
1481
1490
  */
1482
1491
  isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
1483
1492
  /**
1484
- * Starting Y position of the pointer when drag begins.
1493
+ * Starting pointer coordinate for the active drag axis.
1485
1494
  */
1486
- startY = signal(0, ...(ngDevMode ? [{ debugName: "startY" }] : []));
1495
+ startOffset = signal(0, ...(ngDevMode ? [{ debugName: "startOffset" }] : []));
1487
1496
  /**
1488
- * Current vertical translation of the drawer.
1497
+ * Current close progress of the drawer, from 0 (open) to 100 (closed).
1489
1498
  */
1490
- translateY = signal(100, ...(ngDevMode ? [{ debugName: "translateY" }] : []));
1499
+ dragProgress = signal(100, ...(ngDevMode ? [{ debugName: "dragProgress" }] : []));
1491
1500
  /**
1492
- * Vertical drag threshold (percentage) to trigger close on release.
1501
+ * Drag threshold (percentage) to trigger close on release.
1493
1502
  */
1494
1503
  closeThreshold = input(30, ...(ngDevMode ? [{ debugName: "closeThreshold" }] : []));
1495
1504
  /**
1496
1505
  * Computed CSS transform for the drawer based on drag/open state.
1497
1506
  */
1498
- transform = computed(() => this.isDragging()
1499
- ? `translateY(${this.translateY()}%)`
1500
- : this.isOpen()
1501
- ? 'translateY(0%)'
1502
- : 'translateY(100%)', ...(ngDevMode ? [{ debugName: "transform" }] : []));
1507
+ transform = computed(() => this.getTransform(this.currentProgress()), ...(ngDevMode ? [{ debugName: "transform" }] : []));
1503
1508
  /**
1504
- * Element reference to the host component.
1509
+ * Backdrop opacity that tracks the drawer open progress.
1505
1510
  */
1506
- el = inject(ElementRef);
1511
+ backdropOpacity = computed(() => ((100 - this.currentProgress()) / 100) * 0.14, ...(ngDevMode ? [{ debugName: "backdropOpacity" }] : []));
1507
1512
  /**
1508
- * Close the drawer when clicking outside of it.
1509
- * The stopPropagation in the drawer-content prevents clicks inside from closing it.
1510
- * @param event - Click event.
1513
+ * Current progress to use for the rendered transform.
1511
1514
  */
1512
- closeOnOutsideClick(event) {
1513
- if (!this.isOpen())
1514
- return;
1515
- const target = event.target;
1516
- // Check if click is inside the drawer
1517
- if (this.el.nativeElement.contains(target)) {
1518
- return;
1519
- }
1520
- // Check if click is inside a CDK overlay (select dropdown, dialogs, etc.)
1521
- if (target.closest('.cdk-overlay-container')) {
1522
- return;
1523
- }
1524
- // Close the drawer
1525
- this.isOpen.set(false);
1526
- this.closeSheet.emit();
1527
- }
1515
+ currentProgress = computed(() => this.isDragging() ? this.dragProgress() : this.isOpen() ? 0 : 100, ...(ngDevMode ? [{ debugName: "currentProgress" }] : []));
1528
1516
  /**
1529
1517
  * Begin tracking pointer movement for drag-to-dismiss.
1530
1518
  * @param event - Pointer down event.
1531
1519
  */
1532
1520
  startDrag(event) {
1521
+ if (!this.draggable()) {
1522
+ return;
1523
+ }
1524
+ event.preventDefault();
1533
1525
  this.isDragging.set(true);
1534
- this.startY.set(event.clientY);
1535
- // Initialize translateY based on the current state:
1536
- this.translateY.set(this.isOpen() ? 0 : 100);
1537
- // Disable text selection for better UX
1526
+ this.startOffset.set(this.getPointerOffset(event));
1527
+ this.dragProgress.set(this.isOpen() ? 0 : 100);
1538
1528
  document.body.style.userSelect = 'none';
1539
- const move = (e) => this.updateDrag(e.clientY);
1529
+ const move = (e) => {
1530
+ e.preventDefault();
1531
+ this.updateDrag(e);
1532
+ };
1540
1533
  const end = () => {
1541
1534
  this.isDragging.set(false);
1542
1535
  this.snapToOpenOrClose();
1543
- // Restore text selection
1544
1536
  document.body.style.userSelect = '';
1545
1537
  window.removeEventListener('pointermove', move);
1546
1538
  window.removeEventListener('pointerup', end);
@@ -1550,34 +1542,97 @@ class Drawer {
1550
1542
  }
1551
1543
  /**
1552
1544
  * Update drawer position during drag.
1553
- * @param clientY - Current pointer Y position.
1554
- */
1555
- updateDrag(clientY) {
1556
- const deltaPx = clientY - this.startY();
1557
- const sheetHeight = this.el.nativeElement.offsetHeight;
1558
- // Convert the pixel delta to a percentage relative to the sheet height
1559
- const deltaPercent = (deltaPx / sheetHeight) * 100;
1560
- // If open, the initial position is 0%; if closed, it is 100%
1561
- const newPos = Math.min(100, Math.max(0, this.isOpen() ? 0 + deltaPercent : 100 + deltaPercent));
1562
- this.translateY.set(newPos);
1563
- } /**
1545
+ * @param event - Current pointer event.
1546
+ */
1547
+ updateDrag(event) {
1548
+ const host = this.panel().nativeElement;
1549
+ const pointerOffset = this.getPointerOffset(event);
1550
+ const deltaPx = (pointerOffset - this.startOffset()) * this.getCloseDirection();
1551
+ const size = this.isHorizontal() ? host.offsetWidth : host.offsetHeight;
1552
+ if (!size) {
1553
+ return;
1554
+ }
1555
+ const deltaPercent = (deltaPx / size) * 100;
1556
+ const nextProgress = Math.min(100, Math.max(0, deltaPercent));
1557
+ this.dragProgress.set(nextProgress);
1558
+ }
1559
+ /**
1564
1560
  * Snap the drawer to open or closed based on threshold.
1565
1561
  */
1566
1562
  snapToOpenOrClose() {
1567
- if (this.translateY() > this.closeThreshold()) {
1568
- this.isOpen.set(false);
1563
+ if (this.dragProgress() > this.closeThreshold()) {
1564
+ this.requestClose();
1569
1565
  }
1570
1566
  else {
1571
1567
  this.isOpen.set(true);
1572
1568
  }
1573
1569
  }
1570
+ /**
1571
+ * Closes the drawer and emits the close event.
1572
+ */
1573
+ requestClose() {
1574
+ this.isOpen.set(false);
1575
+ this.closeSheet.emit();
1576
+ }
1577
+ /**
1578
+ * Maps the current side to the transform axis and sign.
1579
+ */
1580
+ getTransform(progress) {
1581
+ switch (this.side()) {
1582
+ case 'top':
1583
+ return `translateY(-${progress}%)`;
1584
+ case 'left':
1585
+ return `translateX(-${progress}%)`;
1586
+ case 'right':
1587
+ return `translateX(${progress}%)`;
1588
+ case 'bottom':
1589
+ default:
1590
+ return `translateY(${progress}%)`;
1591
+ }
1592
+ }
1593
+ /**
1594
+ * Returns the pointer coordinate relevant to the active drag axis.
1595
+ */
1596
+ getPointerOffset(event) {
1597
+ return this.isHorizontal() ? event.clientX : event.clientY;
1598
+ }
1599
+ /**
1600
+ * Indicates whether the drawer moves horizontally.
1601
+ */
1602
+ isHorizontal() {
1603
+ return this.side() === 'left' || this.side() === 'right';
1604
+ }
1605
+ /**
1606
+ * Returns the positive pointer direction that closes the drawer.
1607
+ */
1608
+ getCloseDirection() {
1609
+ return this.side() === 'top' || this.side() === 'left' ? -1 : 1;
1610
+ }
1574
1611
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: Drawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1575
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.8", type: Drawer, isStandalone: true, selector: "b-drawer", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, closeThreshold: { classPropertyName: "closeThreshold", publicName: "closeThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closeSheet: "closeSheet" }, host: { listeners: { "document:click": "closeOnOutsideClick($event)" }, properties: { "class.dragging": "isDragging()", "class.open": "isOpen()", "style.transform": "transform()" } }, ngImport: i0, template: `
1576
- <div class="drag-section" (pointerdown)="startDrag($event)">
1577
- <div class="drag-indicator"></div>
1578
- </div>
1579
- <div class="drawer-content" (click)="$event.stopPropagation()">
1580
- <ng-content />
1612
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: Drawer, isStandalone: true, selector: "b-drawer", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, draggable: { classPropertyName: "draggable", publicName: "draggable", isSignal: true, isRequired: false, transformFunction: null }, closeThreshold: { classPropertyName: "closeThreshold", publicName: "closeThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closeSheet: "closeSheet" }, host: { properties: { "class.bottom": "side() === \"bottom\"", "class.dragging": "isDragging()", "class.left": "side() === \"left\"", "class.open": "isOpen()", "class.right": "side() === \"right\"", "class.top": "side() === \"top\"" } }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
1613
+ @if (isOpen() || isDragging()) {
1614
+ <div
1615
+ class="drawer-backdrop"
1616
+ [style.opacity]="backdropOpacity()"
1617
+ (click)="requestClose()"
1618
+ ></div>
1619
+ }
1620
+
1621
+ <div
1622
+ #panel
1623
+ class="drawer-panel"
1624
+ [style.transform]="transform()"
1625
+ (click)="$event.stopPropagation()"
1626
+ >
1627
+ @if (draggable()) {
1628
+ <div class="drag-section" (pointerdown)="startDrag($event)">
1629
+ <div class="drag-indicator"></div>
1630
+ </div>
1631
+ }
1632
+
1633
+ <div class="drawer-content" (click)="$event.stopPropagation()">
1634
+ <ng-content />
1635
+ </div>
1581
1636
  </div>
1582
1637
  `, isInline: true });
1583
1638
  }
@@ -1588,23 +1643,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1588
1643
  standalone: true,
1589
1644
  imports: [],
1590
1645
  template: `
1591
- <div class="drag-section" (pointerdown)="startDrag($event)">
1592
- <div class="drag-indicator"></div>
1593
- </div>
1594
- <div class="drawer-content" (click)="$event.stopPropagation()">
1595
- <ng-content />
1646
+ @if (isOpen() || isDragging()) {
1647
+ <div
1648
+ class="drawer-backdrop"
1649
+ [style.opacity]="backdropOpacity()"
1650
+ (click)="requestClose()"
1651
+ ></div>
1652
+ }
1653
+
1654
+ <div
1655
+ #panel
1656
+ class="drawer-panel"
1657
+ [style.transform]="transform()"
1658
+ (click)="$event.stopPropagation()"
1659
+ >
1660
+ @if (draggable()) {
1661
+ <div class="drag-section" (pointerdown)="startDrag($event)">
1662
+ <div class="drag-indicator"></div>
1663
+ </div>
1664
+ }
1665
+
1666
+ <div class="drawer-content" (click)="$event.stopPropagation()">
1667
+ <ng-content />
1668
+ </div>
1596
1669
  </div>
1597
1670
  `,
1598
1671
  host: {
1672
+ '[class.bottom]': 'side() === "bottom"',
1599
1673
  '[class.dragging]': 'isDragging()',
1674
+ '[class.left]': 'side() === "left"',
1600
1675
  '[class.open]': 'isOpen()',
1601
- '[style.transform]': 'transform()',
1676
+ '[class.right]': 'side() === "right"',
1677
+ '[class.top]': 'side() === "top"',
1602
1678
  },
1603
1679
  }]
1604
- }], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], closeSheet: [{ type: i0.Output, args: ["closeSheet"] }], closeThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeThreshold", required: false }] }], closeOnOutsideClick: [{
1605
- type: HostListener,
1606
- args: ['document:click', ['$event']]
1607
- }] } });
1680
+ }], propDecorators: { panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], closeSheet: [{ type: i0.Output, args: ["closeSheet"] }], closeThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeThreshold", required: false }] }] } });
1608
1681
 
1609
1682
  /**
1610
1683
  * Wrapper for grouping input-related elements.
@@ -3049,97 +3122,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
3049
3122
  }]
3050
3123
  }] });
3051
3124
 
3052
- /**
3053
- * Slide-in sheet panel used for side or bottom panels with optional backdrop.
3054
- */
3055
- class Sheet {
3056
- /**
3057
- * Whether the sheet is open. Can be two-way bound.
3058
- */
3059
- isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
3060
- /**
3061
- * Side of the sheet panel.
3062
- */
3063
- side = input('right', ...(ngDevMode ? [{ debugName: "side" }] : []));
3064
- /**
3065
- * Whether the sheet is positioned on the right side.
3066
- */
3067
- isRight = computed(() => this.side() === 'right', ...(ngDevMode ? [{ debugName: "isRight" }] : []));
3068
- /**
3069
- * Emitted when the sheet is closed.
3070
- */
3071
- closeSheet = output();
3072
- /**
3073
- * Reference to the host element.
3074
- */
3075
- el = inject(ElementRef);
3076
- /**
3077
- * Closes the sheet when clicking outside of it.
3078
- */
3079
- closeOnOutsideClick(event) {
3080
- if (this.isOpen() && !this.el.nativeElement.contains(event.target)) {
3081
- this.isOpen.set(false);
3082
- }
3083
- }
3084
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: Sheet, deps: [], target: i0.ɵɵFactoryTarget.Component });
3085
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.8", type: Sheet, isStandalone: true, selector: "b-sheet", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closeSheet: "closeSheet" }, host: { listeners: { "document:click": "closeOnOutsideClick($event)" }, properties: { "class.left": "side() === \"left\"", "class.right": "side() === \"right\"", "class.open": "isOpen()" } }, ngImport: i0, template: `
3086
- <button class="close-button" (click)="isOpen.set(false)">
3087
- <svg
3088
- xmlns="http://www.w3.org/2000/svg"
3089
- width="20"
3090
- height="20"
3091
- viewBox="0 0 24 24"
3092
- fill="none"
3093
- stroke="currentColor"
3094
- stroke-width="0.094rem"
3095
- stroke-linecap="round"
3096
- stroke-linejoin="round"
3097
- class="lucide lucide-x"
3098
- >
3099
- <path d="M18 6 6 18" />
3100
- <path d="m6 6 12 12" />
3101
- </svg>
3102
- </button>
3103
- <ng-content />
3104
- `, isInline: true });
3105
- }
3106
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: Sheet, decorators: [{
3107
- type: Component,
3108
- args: [{
3109
- selector: 'b-sheet',
3110
- standalone: true,
3111
- imports: [],
3112
- template: `
3113
- <button class="close-button" (click)="isOpen.set(false)">
3114
- <svg
3115
- xmlns="http://www.w3.org/2000/svg"
3116
- width="20"
3117
- height="20"
3118
- viewBox="0 0 24 24"
3119
- fill="none"
3120
- stroke="currentColor"
3121
- stroke-width="0.094rem"
3122
- stroke-linecap="round"
3123
- stroke-linejoin="round"
3124
- class="lucide lucide-x"
3125
- >
3126
- <path d="M18 6 6 18" />
3127
- <path d="m6 6 12 12" />
3128
- </svg>
3129
- </button>
3130
- <ng-content />
3131
- `,
3132
- host: {
3133
- '[class.left]': 'side() === "left"',
3134
- '[class.right]': 'side() === "right"',
3135
- '[class.open]': 'isOpen()',
3136
- },
3137
- }]
3138
- }], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], closeSheet: [{ type: i0.Output, args: ["closeSheet"] }], closeOnOutsideClick: [{
3139
- type: HostListener,
3140
- args: ['document:click', ['$event']]
3141
- }] } });
3142
-
3143
3125
  /**
3144
3126
  * A spinner component to indicate loading states.
3145
3127
  */
@@ -4221,5 +4203,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
4221
4203
  * Generated bundle index. Do not edit.
4222
4204
  */
4223
4205
 
4224
- export { Alert, Badge, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CommandComponent, CommandOptionsComponent, ConnectedOverlay, Dialog, DialogContent, DialogManager, Drawer, Input, InputGroup, Menu, MenuGroup, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuLabel, MenuTriggerDirective, Option, Otp, OtpDigitDirective, Overlay, OverlayOrigin, OverlayTrigger, Popover, PopoverTrigger, Range, ResponsiveManager, Select, SelectContent, SelectFilter, SelectTrigger, SelectValue, Sheet, Spinner, SwitchComponent, Tab, Tabs, Textarea, TextareaGroup, ThemeManager, Tooltip, TooltipContent, TooltipTrigger, TranslatePipe, TranslationManager, Tree, TreeNode, Utils };
4206
+ export { Alert, Badge, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CommandComponent, CommandOptionsComponent, ConnectedOverlay, Dialog, DialogContent, DialogManager, Drawer, Input, InputGroup, Menu, MenuGroup, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuLabel, MenuTriggerDirective, Option, Otp, OtpDigitDirective, Overlay, OverlayOrigin, OverlayTrigger, Popover, PopoverTrigger, Range, ResponsiveManager, Select, SelectContent, SelectFilter, SelectTrigger, SelectValue, Spinner, SwitchComponent, Tab, Tabs, Textarea, TextareaGroup, ThemeManager, Tooltip, TooltipContent, TooltipTrigger, TranslatePipe, TranslationManager, Tree, TreeNode, Utils };
4225
4207
  //# sourceMappingURL=basis-ng-primitives.mjs.map