diff --git a/CHANGELOG.md b/CHANGELOG.md index e20b31d9..e00efddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 16.0.0 + +- fix: `asyncDetection` scrollbars rendering delay. + ## 16.0.0-beta.2 - feat: `scrollViewport` directive can be a descent child, not necessarily a direct child of ``. @@ -19,6 +23,9 @@ ### Breaking changes - Minimum compatibility is Angular v17.3.0 +- The directive `cdkVirtualScrollViewport` no longer accepts `auto` value, instead use `syncSpacer` + * Before `` + * After `` ## 15.1.3 diff --git a/README.md b/README.md index 4be67611..dc418fc5 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,14 @@ Custom overlay-scrollbars with native scrolling mechanism for Angular, it also provides a cross-browser smooth scroll directive. -___ - -### The documentation is available at the [wiki page](https://github.com/MurhafSousli/ngx-scrollbar/wiki) 📚 - -___ +*** ## Features - Exceptional performance, see [comparing test results](https://github.com/MurhafSousli/ngx-scrollbar/wiki/Performance-analysis). - Native scrolling mechanism. - Easily Customizable using CSS variables. -- Easy integration with other libraries. +- Easy integration with 3rd party libraries. - RTL support. - Mobile support. - SSR support. @@ -34,10 +30,40 @@ ___ #### Addons directives: - Smooth scroll functionality. -- Scroll reached events. +- Scroll reached & dropped events. - **MatSelect** integration. - **CdkVirtualScroll** integration. +*** + +## Documentations + +### Table of contents + +* [Usage](projects/ngx-scrollbar/docs/Usage.md) +* [Styling](projects/ngx-scrollbar/docs/Styling.md) +* [Global Options](projects/ngx-scrollbar/docs/Global-options.md) +* [Smooth Scroll Functions](projects/ngx-scrollbar/docs/Smooth-Scroll-Functions.md) +* [Scroll Timeline Polyfill](projects/ngx-scrollbar/docs/Scroll-timeline-polyfill.md) + +### Addons: + +* [Integration Examples](projects/ngx-scrollbar/docs/Integration.md) + * [Material Select](projects/ngx-scrollbar/docs/Integration.md#material-select) + * [Material Dialog](projects/ngx-scrollbar/docs/Integration.md#material-dialog) + * [CDK Virtual Scroll](projects/ngx-scrollbar/docs/Integration.md#cdk-virtual-scroll) + * [ngx-datatable table](projects/ngx-scrollbar/docs/Integration.md#ngx-datatable-table) + * [Ag-grid table](projects/ngx-scrollbar/docs/Integration.md#ag-grid-table) + * [PrimeNG Scroller](projects/ngx-scrollbar/docs/Integration.md#primeng-scroller) + * [PrimeNG Table](projects/ngx-scrollbar/docs/Integration.md#primeng-table) + * [PrimeNG Dropdown](projects/ngx-scrollbar/docs/Integration.md#primeng-dropdown) + * [PrimeNg Dropdown (Virtual Scroll)](projects/ngx-scrollbar/docs/Integration.md#primeng-dropdown-virtual-scroll) + * [Kendu UI Grid](projects/ngx-scrollbar/docs/Integration.md#kendu-ui-grid) +* [Reached & Dropped Events](projects/ngx-scrollbar/docs/Reached-&-Dropped-Events.md) +* [Smooth Scroll Functions (without NgScrollbar)](projects/ngx-scrollbar/docs/Smooth-Scroll.md) + +___ + ## Issues diff --git a/docs/v13/Global-Options-v13.md b/docs/v13/Global-Options-v13.md new file mode 100644 index 00000000..c1021285 --- /dev/null +++ b/docs/v13/Global-Options-v13.md @@ -0,0 +1,42 @@ +To set global options for all scrollbar components across the app, provide the token `NG_SCROLLBAR_OPTIONS` with your custom options like in the following example: + +```ts +import { NgScrollbarModule, NG_SCROLLBAR_OPTIONS } from 'ngx-scrollbar'; + +@NgModule({ + imports: [ + NgScrollbarModule + ], + providers: [ + { + provide: NG_SCROLLBAR_OPTIONS, + useValue: { + // ... options + } + } + ] +}) +``` + +#### Global options API (`NgScrollbarOptions`) + + +| Name | Default value | Description | +| --------------------------- | --------------- | -------------------------------------------------------------------- | +| **track** | `vertical` | Directions to track `horizontal`, `vertical`, `all` | +| **position** | `native` | Invert scrollbar position `native`,`invertX`,`invertY`, `invertAll` | +| **visibility** | `native` | Scrollbar visibility `native`, `hover`, `always` | +| **appearance** | `compact` | Scrollbar appearance `standard`, `compact`. | +| **viewClass** | *null* | Add custom class to the viewport element. | +| **trackClass** | *null* | Add custom class to scrollbar track elements. | +| **thumbClass** | *null* | Add custom class to scrollbar thumbnail elements. | +| **trackClickScrollDuration**| 300 | The smooth scroll duration when a scrollbar is clicked. | +| **minThumbSize** | 20 | The minimum scrollbar thumb size in px. | +| **scrollAuditTime** | 0 | Throttle scroll event in ms. | +| **windowResizeDebounce** | 0 | Debounce interval for detecting changes via `window.resize` event. | +| **sensorDebounce** | 0 | Debounce interval for detecting changes via `ResizeObserver`. | +| **sensorDisabled** | false | Whether `ResizeObserver` is disabled. | +| **pointerEventsMethod** | `viewport` | The method used to detect scrollbar pointer-events, [read more](pointer-events). | +| **pointerEventsDisabled** | false | Enable/disable the scrollbar track clicked and thumb dragged events. | +| **autoHeightDisabled** | false | Whether to set component height to content height. | +| **autoWidthDisabled** | true | Whether to set component width to content width. | diff --git a/docs/v13/Integration-v13.md b/docs/v13/Integration-v13.md new file mode 100644 index 00000000..c2e5295e --- /dev/null +++ b/docs/v13/Integration-v13.md @@ -0,0 +1,95 @@ +The `scrollViewport` directive allows you to use a custom viewport element in your template. It is used for integration with other libraries where the viewport can be another component or directive. + +### Basic usage + +```html + +
+
+ +
+
+
+``` + +> **NOTE**: `scrollViewport` directive must be used on the direct child of `` + +> **NOTE**: When `scrollViewport` directive is used on an element, It is recommended that its host element to have one and only one child element that acts as a content wrapper _(like `.scroll-content-wrapper` in the above example)_, this content wrapper will be used by the ResizeObserver API to observes content changes and updates the UI accordingly. + +> **IMPORTANT NOTE:** Some external libraries can cause the scrollbar to recalculate very frequently when it scrolls, which can result in flickering behaviour (like the CDK virtual scroll viewport). + To avoid this, set `[autoHeightDisabled]="true"` + +### Integrate with [ngx-infinite-scroll](https://github.com/orizens/ngx-infinite-scroll). + +```html + +
+ + + {{ i }} + + +
+
+``` + +### Integrate with [CDK Scrollable](https://material.angular.io/cdk/scrolling/overview#cdkscrollable-and-scrolldispatcher) + +If you need to get hold of the scrollable element of the component, you can add a child element and then assign it with `scrollViewport` directive + +```html + +
+ +
+
+``` + +### Integrate with [CDK VirtualScroll](https://material.angular.io/cdk/scrolling/overview#virtual-scrolling). + + +```html + + +
{{item}}
+
+
+``` + +### Integrate with [CDK Drag and Drop](https://material.angular.io/cdk/drag-drop/overview) + +```html + + +
+ +
+ {{ tab.label }} + +
+ +
+
+``` +```scss +ng-scrollbar { + height: 70px; + width: 100%; +} +``` + +Demo: https://stackblitz.com/edit/ngx-scrollbar-cdk-drag-drop \ No newline at end of file diff --git a/docs/v13/Performance-tweak-v13.md b/docs/v13/Performance-tweak-v13.md new file mode 100644 index 00000000..013e924e --- /dev/null +++ b/docs/v13/Performance-tweak-v13.md @@ -0,0 +1,49 @@ +# Regarding the scroll event + +By default, the scroll event is used to do the necessary calculation to display the custom scrollbars. + +You may want to tweak the scroll event if it is not performing well on low-end devices. + +You can throttle the scroll event using the `scrollAuditTime` option, `20` milliseconds seems to be a reasonable value. + +**Example:** + +Set the option `[scrollAuditTime]` for a specific scrollbar component. + +```html + + + +``` + +**Example:** + +Set the `scrollAuditTime` option for all custom scrollbars using the global options. + +```ts +import { NgScrollbarModule, NG_SCROLLBAR_OPTIONS } from 'ngx-scrollbar'; + +@NgModule({ + imports: [ + NgScrollbarModule + ], + providers: [ + { + provide: NG_SCROLLBAR_OPTIONS, + useValue: { + scrollAuditTime: 20 + } + } + ] +}) +``` + +_This option is available in `ngx-scrollbar >= v7.1.0`_ + +# Regarding the `auto-height` and `auto-width` features + +When any of these features is turned on, the scrollbar will re-calculate when the component size or the content size change. + +If the scrollbar is used with other library that frequently changes the content or the component size, it could cause a performance issue, therefore, you should turn them off. + +For example when using it with a virtual scroll library, you should set `[autoHeightDisabled]="true"` (which is already disabled by default). \ No newline at end of file diff --git a/docs/v13/Pointer-Events-v13.md b/docs/v13/Pointer-Events-v13.md new file mode 100644 index 00000000..2362abb7 --- /dev/null +++ b/docs/v13/Pointer-Events-v13.md @@ -0,0 +1,69 @@ +The pointer-events here means the method this library use to detect the dragging of the scrollbar and the hover effect on the scrollbar + +There are 2 methods to detect pointer events + +```html + +``` + +- `pointerEventsMethod="viewport"`, will detect pointer events using viewport's `mousemove`, `mousedown` and `mouseleave` events. + +- `pointerEventsMethod="scrollbar"`, will detect pointer events using scrollbar `mousedown`, `mouseenter` and `mouseleave` event. + +### In a nutshell, here are the pros and cons of each method with the `[appearance]` option: + + + + + + + + + + + + + + + + + + + + + +
Appearance ↓viewportscrollbar
Standard +
    +
  • Only if both scrollbars appear, the content may overlap under the scrollbar
  • +
  • Scrolling over scrollbar works
  • +
+
+
    +
  • The content never overlap under the scrollbars
  • +
  • Scrolling over scrollbar doesn't work
  • +
+
Compact +
    +
  • Content overlaps under the scrollbar as intended
  • +
  • Scrolling over scrollbar works
  • +
+
+
    +
  • The content overlaps under the scrollbar as intended
  • +
  • Scrolling over scrollbar doesn't work
  • +
+
+ +--- + +> **My recommendation:** If scrolling over scrollbar isn't a big deal, then go with `pointerEventsMethod="scrollbar"`. + +--- + +*Theoretically `scrollbar` method is more performant because `viewport` method checks if the pointer is over the scrollbars while the pointer is moving over the viewport.* + +*But most of the time, there won't be any difference in performance unless you have a complex design. try both and see what best fits your app.* + +### 📌 Note + +The pointer-events are disabled on mobile browsers by design. because there is no pointer 😄 \ No newline at end of file diff --git a/docs/v13/README.md b/docs/v13/README.md new file mode 100644 index 00000000..2f93d67e --- /dev/null +++ b/docs/v13/README.md @@ -0,0 +1,22 @@ +## Documentation for v13 + +- [Demo for v13](https://ngx-scrollbar-v13.netlify.app/) +- [Usage](Usage-v13.md) +- [Styling](Styling-v13.md) +- [Global Options](Global-Options-v13.md) +- [Pointer Events](Pointer-Events-v13.md) +- [Scroll Event](scroll-event.md) +- [Updated Event](Updated-event-v13.md) +- [Smooth Scroll Functions](Smooth-scroll-functions-v13.md) +- [Performance tweak](Performance-tweak-v13.md) +- [Integration](Integration-v13.md) + - [Infinite Scroll](Integration-v13.md#integrate-with-ngx-infinite-scroll) + - [CDK Scrollable](Integration-v13.md#integrate-with-cdk-scrollable) + - [CDK Virtual Scroll](Integration-v13.md#integrate-with-cdk-virtualscroll) + - [CDK Drag and Drop](Integration-v13.md#integrate-with-cdk-drag-and-drop) +- [Reached events](reached-events-v13.md) + +--- + +- [Smooth Scroll Module (without NgScrollbar)](Smooth-Scroll-v13.md) + diff --git a/docs/v13/Reached-Events-v13.md b/docs/v13/Reached-Events-v13.md new file mode 100644 index 00000000..d8282487 --- /dev/null +++ b/docs/v13/Reached-Events-v13.md @@ -0,0 +1,52 @@ +The reached event is an addon feature that extends `NgScrollbar` component via `NgScrollbarReachedModule`. + +Once imported, reached outputs become available to use + +### Usage + +`NgScrollbarReachedModule` provides 4 standalone directives: + +- `(reachedTop)` +- `(reachedBottom)` +- `(reachedStart)` +- `(reachedEnd)` + +When using any of the above directives, you will be able to set the offset using `[reachedOffset]` input. + +**Example** + +```ts +import { NgScrollbarModule } from 'ngx-scrollbar'; +import { NgScrollbarReachedModule } from 'ngx-scrollbar/reached-event'; + +@NgModule({ + imports: [ + NgScrollbarModule, + NgScrollbarReachedModule + ], +}) +export class AppModule { +} +``` +```html + +
{{scrollableContent}}
+
+``` +```ts +@Component({...}) +export class FooComponent { + onBottomReached() { + console.log('Reached!'); + } +} +``` + + +| Name | Description | +| --------------------------- | -------------------------------------------------------------------- | +| **(reachedTop)** | A stream that emits when scroll has reached the top. | +| **(reachedBottom)** | A stream that emits when scroll has reached the bottom. | +| **(reachedStart)** | A stream that emits when scroll has reached the left (right in RTL) | +| **(reachedEnd)** | A stream that emits when scroll has reached the right (left in RTL) | +| **[reachedOffset]** | reached offset, default 0 | \ No newline at end of file diff --git a/docs/v13/Scroll-Event-v13.md b/docs/v13/Scroll-Event-v13.md new file mode 100644 index 00000000..7370569c --- /dev/null +++ b/docs/v13/Scroll-Event-v13.md @@ -0,0 +1,61 @@ +It was intentional not to provide a scroll event output on `NgScrollbar` component because it would cause the change detection to fire very rapidly which would cause a performance issue. + +To use the scroll event, you will need to get the component reference from the template. this can be done using the `@ViewChild` decorator + +**Example:** Subscribe to the scroll event + +```ts +@ViewChild(NgScrollbar) scrollbarRef: NgScrollbar; + +ngAfterViewInit() { + this.scrollbarRef.scrolled.subscribe(e => console.log(e)); +} +``` + + > Note: in order to avoid hitting change detection for every scroll event, all of the events emitted from this stream will be run outside the Angular zone. If you need to update the UI using the scroll event, you should to run your code inside the Angular zone, `NgZone.run(() => )`. + + +### Example: Change the header font size on scroll event + + +```ts +@Component({ + selector: 'app-page-title', + template: ` + +
+

Hello World

+
+
{{ longContent }}
+
+ ` +}) +export class PageTitleComponent { + + // Stream that sets the title font size on scroll event + fontSize$ = new Subject(); + + // Unsubscriber for elementScrolled stream. + scrollSubscription = Subscription.EMPTY; + + // Get scrollbar component reference + @ViewChild(NgScrollbar) scrollbarRef: NgScrollbar; + + constructor(private zone: NgZone) { + } + + ngAfterViewInit() { + // Subscribe to scroll event + this.scrollSubscription = this.scrollbarRef.scrolled.pipe( + map((e: any) => e.target.scrollTop > 50 ? '0.75em' : '1em'), + tap((size: string) => this.zone.run(() => this.fontSize$.next(size))) + ).subscribe(); + } + + ngOnDestroy() { + this.scrollSubscription.unsubscribe(); + } +} +``` + +Here is the [example demo stackblitz](https://stackblitz.com/edit/ngx-scrollbar?file=src%2Fapp%2Fscroll-event%2Fscroll-event%2Fscroll-event.component.ts) diff --git a/docs/v13/Smooth-Scroll-v13.md b/docs/v13/Smooth-Scroll-v13.md new file mode 100644 index 00000000..18c7d401 --- /dev/null +++ b/docs/v13/Smooth-Scroll-v13.md @@ -0,0 +1,47 @@ +Smooth scroll feature is built-in `NgScrollbar` component, but if you like to use this feature outside `NgScrollbar` component, then use `SmoothScroll`directive which is available independently and it allows you to use smooth scroll functions on any scrollable element. + +The package provides: + +- `SmoothScroll` a directive for template usage +- `SmoothScrollManager` a service for code usage + + +### Directive Usage + +Import `SmoothScroll` in your component the use the directive on your scrollable element + +```ts +@Component({ + standalone: true, + selector: 'my-app', + imports: [SmoothScroll], + template: ` +
+
{{scrollableContent}}
+
+ + + ` +}) +``` + + +### Service Usage + +```ts +@Component({...}) +export class FooComponent { + + // A reference to the smooth scroll service + readonly smoothScroll: SmoothScrollManager = inject(SmoothScrollManager); + + // A reference to a scrollable element (in this example it is the host element) + readonly scrollableElement: HTMLElement = inject(ElementRef).nativeElement; + + scrollToTop(): void { + this.smoothScroll.scrollTo(this.scrollableElement, { top: 0, duration: 800 }); + } +} +``` + +See all [ScrollTo Functions](Smooth-Scroll-Functions.md). diff --git a/docs/v13/Smooth-scroll-functions-v13.md b/docs/v13/Smooth-scroll-functions-v13.md new file mode 100644 index 00000000..57e14a61 --- /dev/null +++ b/docs/v13/Smooth-scroll-functions-v13.md @@ -0,0 +1,113 @@ +The following smooth scroll function returns a promise that resolves when scrolling reaches the point or target + +## Scroll to position + +```ts +scrollable.scrollTo(options: SmoothScrollToOptions) +``` + +- **Element:** target HTMLElement, or ElementRef or the selector string. +- **Options:** + - **Top:** Set top offset, default `null`. + - **Bottom:** Set bottom offset, default `null`. + - **Start:** Set start offset (evaluates left in ltr, in right rtl), default `null`. + - **End:** Set end offset (evaluates right in ltr, in left rtl), default `null`. + - **Left:** Set left offset, default `null`. + - **Right:** Set right offset, default `null`. + - **Duration:** Time to reach the target in ms, default `null`. + - **Easing:** Smooth scroll animation can be customized using the bezier-easing properties `{ x1, y1, x2, y2 }`. + +Avoid setting `top` and `bottom`, or `start` and `end`, or `left` and `right` at the same time because that would obviously not work. + +## Scroll to element + +```ts +scrollable.scrollToElement(target: HTMLElement | ElementRef | string, options?) +``` + +- **Element:** target HTMLElement, or ElementRef or the selector string. +- **Options:** + - **Left:** Set left offset, default `null`. + - **Right:** Set right offset, default `null`. + - **Duration:** time to reach the position in milliseconds, default `null`. + - **Easing:** Smooth scroll animation can be customized using the bezier-easing properties `{ x1, y1, x2, y2 }`. + +## Scrolling examples + +### Scroll to top directly from the template + +```html + + + + + +``` + +### Scroll to a specific element by a selector + +```html + +
+
+
+
+
+
+ + + + +``` + +### Scroll to a specific element by element reference + +```html + +
+
+
+
+
+
+ + +``` + +The same can be done from component code using the `@ViewChild` decorator + +```ts +@ViewChild(NgScrollbar) scrollable: NgScrollbar; +@ViewChild('comments') commentsSection: ElementRef; + +scrollToCommentsSection() { + this.scrollable.scrollToElement(this.commentsSection); +} +``` + + +### Scroll to top on route change + +If you wrap the `` inside ``, you can scroll to the top on route changes. + +The following example scrolls to top whenever the user navigates to another page. + +```html + + + +``` +```ts +export class AppComponent { + + @ViewChild(NgScrollbar, { static: true }) scrollable: NgScrollbar; + + constructor(router: Router) { + router.events.pipe( + filter(event => event instanceof NavigationEnd)), + filter(() => !!this.scrollable)), + tap((event: NavigationEnd) => this.scrollable.scrollTo({ top: 0, duration: 500 })) + ).subscribe(); + } +} +``` \ No newline at end of file diff --git a/docs/v13/Styling-v13.md b/docs/v13/Styling-v13.md new file mode 100644 index 00000000..0bb0e0c1 --- /dev/null +++ b/docs/v13/Styling-v13.md @@ -0,0 +1,45 @@ +The scrollbars can be easily customized using the CSS variables + +```html + + + +``` + +Here are all available CSS variables: + +```scss +.my-scrollbar { + --scrollbar-border-radius: 7px; + --scrollbar-padding: 4px; + --scrollbar-viewport-margin: 0; + --scrollbar-track-color: transparent; + --scrollbar-wrapper-color: transparent; + --scrollbar-thumb-color: rgba(0, 0, 0, 0.2); + --scrollbar-thumb-hover-color: var(--scrollbar-thumb-color); + --scrollbar-size: 5px; + --scrollbar-hover-size: var(--scrollbar-size); + --scrollbar-thumb-transition: height ease-out 150ms, width ease-out 150ms; + --scrollbar-track-transition: height ease-out 150ms, width ease-out 150ms; +} +``` + +If the CSS variables are not enough, custom classes can be used to override the styles, here is an example: + +```html + + + +``` + +```scss +::ng-deep { + .scrollbar { + background-color: rgba(0, 0, 0, 0.4); + border-radius: 4px; + } + .scrollbar-thumb { + background-color: rgba(161, 27, 27, 0.4); + } +} +``` \ No newline at end of file diff --git a/docs/v13/Updated-event-v13.md b/docs/v13/Updated-event-v13.md new file mode 100644 index 00000000..69265b88 --- /dev/null +++ b/docs/v13/Updated-event-v13.md @@ -0,0 +1,23 @@ +The `NgScrollbar` component has an output `(updated)` which emits when: + +- After its view is initialized +- Setting new options (_e.g. an input is changed) +- Resize sensor emitted (_e.g. the content size is changed_) + +**Example:** Check when content becomes scrollable + +```html + +```` + +```ts +onScrollbarUpdated(state: NgScrollbarState) { + if (state.isVerticallyScrollable) { + console.log('Scrollable'); + } else { + console.log('Not Scrollable'); + } +} +``` + +Here is a [demo stacbklitz](https://stackblitz.com/edit/ngx-scrollbar-f2qto9?file=src%2Fapp%2Fhome%2Fhome.component.ts) \ No newline at end of file diff --git a/docs/v13/Usage-v13.md b/docs/v13/Usage-v13.md new file mode 100644 index 00000000..22b89980 --- /dev/null +++ b/docs/v13/Usage-v13.md @@ -0,0 +1,76 @@ + +## Installation + +Install with **npm** + +```bash +npm i ngx-scrollbar@13 @angular/cdk +``` + +## Usage + +Import `NgScrollbarModule` in your module + +```js +import { NgScrollbarModule } from 'ngx-scrollbar'; + +@NgModule({ + imports: [ + NgScrollbarModule + ] +}) +``` + +In your template + +```html + + + + + + + + + +``` + +- Try it online using this [ngx-scrollbar v13 stackblitz example](https://stackblitz.com/edit/ngx-scrollbar-v13) + +If you need to get hold of the scrollable element of the component, you can add a child element and then assign it with `scrollViewport` directive + +```html + +
+ +
+
+``` + +## Options + +> You can set global options for all scrollbars across your app, see the [global options](global-options) chapter. + +The following are the available inputs and outputs of `` + +| Name | Default value | Description | +| ------------------------------ | --------------- | ------------------------------------------------------------------- | +| **[track]** | `vertical` | Directions to track `horizontal`, `vertical`, `all` | +| **[position]** | `native` | Invert scrollbar position `native`,`invertX`,`invertY`, `invertAll` | +| **[visibility]** | `native` | Scrollbar visibility `native`, `hover`, `always` | +| **[appearance]** | `compact` | Scrollbar appearance `standard`, `compact`. | +| **[viewClass]** | *null* | Add custom class to the viewport. | +| **[trackClass]** | *null* | Add custom class to scrollbars' tracks. | +| **[thumbClass]** | *null* | Add custom class to scrollbars' thumbnails. | +| **[disabled]** | `false` | Disable the custom scrollbars and use the native ones instead. | +| **[trackClickScrollDuration]** | 300 | The smooth scroll duration when a scrollbar is clicked. | +| **[minThumbSize]** | 20 | The minimum scrollbar thumb size in px. | +| **[scrollAuditTime]** | 0 | Throttle scroll event in ms. | +| **[sensorDebounce]** | 0 | Debounce interval for detecting changes via `ResizeObserver`. | +| **[sensorDisabled]** | false | Whether `ResizeObserver` is disabled. | +| **[pointerEventsMethod]** | `viewport` | The method used to detect scrollbar pointer-events, [read more](https://github.com/MurhafSousli/ngx-scrollbar/wiki/pointer-events). | +| **[pointerEventsDisabled]** | false | Enable/disable the scrollbar track clicked and thumb dragged events.| +| **[autoHeightDisabled]** | true | Whether to set component height to content height. | +| **[autoWidthDisabled]** | true | Whether to set component width to content width. | +| **(updated)** | - | Output that emits when the scrollbar component is updated. | + diff --git a/docs/v15/Global-options.md b/docs/v15/Global-options.md new file mode 100644 index 00000000..873b45da --- /dev/null +++ b/docs/v15/Global-options.md @@ -0,0 +1,89 @@ +To override the default scrollbar options for all scrollbar components across the application, import the `provideScrollbarOptions` function in your providers, here is an example: + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideScrollbarOptions } from 'ngx-scrollbar'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideScrollbarOptions({ + visibility: 'hover', + appearance: 'compact' + }) + ] +}; +``` + +## NgScrollbarOptions API + + +| Name | Default value | Description | +| --------------------------- | --------------- | -------------------------------------------------------------------- | +| **orientation** | `auto` | The scroll axis of the viewport `auto`, `horizontal`, `vertical`. | +| **position** | `native` | Invert scrollbar position `native`,`invertX`,`invertY`, `invertAll`. | +| **visibility** | `native` | Scrollbar visibility `native`, `hover`, `always`. | +| **appearance** | `native` | Scrollbar appearance `native`, `compact`. | +| **trackClass** | *null* | Add a class to scrollbar track elements. | +| **thumbClass** | *null* | Add a class to scrollbar thumbnail elements. | +| **buttonClass** | *null* | Add a class to scrollbar button elements. | +| **buttons** | false | Show scrollbar buttons. | +| **hoverOffset** | false | Activate hover effect on the offset area around the scrollbar. | +| **trackClickDuration** | 50 | The smooth scroll step duration when a scrollbar is clicked in ms. | +| **minThumbSize** | 20 | The minimum scrollbar thumb size in px. | +| **sensorThrottleTime** | 0 | The throttle time used for detecting size changes. | +| **disableSensor** | false | Whether `ResizeObserver` is disabled. | +| **disableInteraction** | false | Disables scrollbar interaction like dragging thumb and jumping by track click.| + +## Types aliases + +### ScrollbarOrientation + +Sets the scroll axis of the viewport. + - `auto`: Scrollbars are displayed for both vertical and horizontal scrolling. + - `vertical`: Scrollbars are displayed for vertical scrolling. + - `horizontal`: Scrollbars are displayed for horizontal scrolling. + +Defaults to 'auto'. + +```ts +type ScrollbarOrientation = 'auto' | 'vertical' | 'horizontal'; +``` + +### ScrollbarAppearance + +Sets the appearance of the scrollbar. + - `native`: Scrollbar space is reserved within the viewport, similar to native scrollbars. + - `compact`: Scrollbars do not reserve any space and are placed over the viewport. + +Defaults to 'native'. + +```ts +type ScrollbarAppearance = 'native' | 'compact'; +``` + +### ScrollbarVisibility + +Determines when to show the scrollbar. + - `native`: Scrollbar is visible when the viewport is scrollable, similar to native scrollbars. + - `hover`: Scrollbars are hidden by default and become visible on scrolling or hovering. + - `visible`: Scrollbars are always visible, even if the viewport is not scrollable. + +Defaults to 'native'. + +```ts +type ScrollbarVisibility = 'native' | 'hover' | 'visible'; +``` + +### ScrollbarPosition + +Sets the position of each scrollbar. + - `native`: Uses the default position as in native scrollbars. + - `invertY`: Inverts the vertical scrollbar position. + - `invertX`: Inverts the horizontal scrollbar position. + - `invertAll`: Inverts the positions of both vertical and horizontal scrollbars. + +Defaults to 'native'. + +```ts +type ScrollbarPosition = 'native' | 'invertY' | 'invertX' | 'invertAll'; +``` diff --git a/docs/v15/Integration.md b/docs/v15/Integration.md new file mode 100644 index 00000000..ac599b66 --- /dev/null +++ b/docs/v15/Integration.md @@ -0,0 +1,172 @@ +## Material Select + +When integrating with Material select, it's important to note that unlike other integration examples, you can't simply wrap the control and assign an external viewport due to the nature of the dropdown menu being a CDK popup panel, which isn't directly nested within the component. + +However, you can still achieve the desired functionality by wrapping the content with a scrollbar component and utilizing the `matSelectViewport` addon directive. This directive ensures that the selected item is scrolled to as soon as the dropdown opens. + +Here's how you can implement it: + +```html + + + @for (item of items; track item) { + + {{ item }} + + } + + +``` + +## Material Dialog + +In a Material dialog, the `mat-dialog-content` component serves as the scrollable element with default padding. + +To integrate with `ng-scrollbar`, we ensure it doesn't exceed the `mat-dialog-content` height and remove the padding styles. Then, we apply padding to a content wrapper inside our scrollbar component. + +```html +

Dialog Title

+ + + +
+ +
+ +
+``` + +```scss +mat-dialog-content.mat-mdc-dialog-content { + display: flex; + flex-direction: column; + padding: 0 !important; +} + +.ng-scrollbar { + flex: 1; +} + +// Add the padding to the content wrapper inside the scrollbar +.content-wrapper { + padding: var(--mat-dialog-with-actions-content-padding, 20px 24px); +} +``` + +Here is an [integration with material dialog example](https://stackblitz.com/edit/ngx-scrollbar-tkzscf?file=package.json) + +## CDK Virtual Scroll + +```html + + +
+ {{ item }} +
+
+
+``` + +## ngx-datatable table + +```html + + + + + +``` + +## Ag-grid table + +```html + + + +``` + +## PrimeNg Table + +```html + + + + + @for (col of columns; track col.header) { + {{ col.header }} + } + + + + + @for (col of columns; track col.header) { + {{ rowData[col.field] }} + } + + + + +``` + +## PrimeNg Scroller + +```html + + + +
{{ item.vin }}
+
+
+
+``` + +## Kendu UI Grid + +```html + + + ... + + +``` + +```css +.k-height-container { + position: absolute !important; +} +``` + +Here is a [Kendu UI grid example stackblitz](https://stackblitz.com/edit/angular-vgzffw-ldgsyr?file=src%2Fapp%2Fapp.component.ts,src%2Fstyles.css) diff --git a/docs/v15/Performance-analysis (v14 vs v13).md b/docs/v15/Performance-analysis (v14 vs v13).md new file mode 100644 index 00000000..dfd73be6 --- /dev/null +++ b/docs/v15/Performance-analysis (v14 vs v13).md @@ -0,0 +1,37 @@ +The following are test results from Chrome dev tools, scrolling for 5 seconds in version 14 vs version 13. + +## Scrolling using the wheel / touchpad + +Performance using CSS scroll-timeline feature: + +

+ +

Scrolling using the wheel / touchpad (version >= 14)
+

+ +*** + +Performance using JS scroll event: + +

+ +

Scrolling using the wheel / touchpad (version < 14)
+

+ +## Scrolling using pointer dragging + + +Performance using CSS scroll-timeline feature + dragging using JS pointer events: + +

+ +

Dragging scrollbar using pointer (version >= 14)
+

+ + +Performance using JS scroll event + dragging using JS pointer events: + +

+ +

Dragging scrollbar using pointer (version < 14)
+

diff --git a/docs/v15/README.md b/docs/v15/README.md new file mode 100644 index 00000000..20bea035 --- /dev/null +++ b/docs/v15/README.md @@ -0,0 +1,14 @@ +## Documentation for v15 + +- [Usage](Usage.md) +- [Styling](Styling.md) +- [Global Options](Global-options.md) +- [Smooth Scroll Functions](Smooth-Scroll-Functions.md) +- [Scroll Timeline Polyfill](Scroll-timeline-polyfill.md) + +### Addons: + +- [Reached & Dropped Events](Reached-&-Dropped-Events.md) +- [Integration](Integration.md) +- [Smooth Scroll Module (without NgScrollbar)](Smooth-Scroll.md) + diff --git a/docs/v15/Reached-&-Dropped-Events.md b/docs/v15/Reached-&-Dropped-Events.md new file mode 100644 index 00000000..ba3a7041 --- /dev/null +++ b/docs/v15/Reached-&-Dropped-Events.md @@ -0,0 +1,95 @@ +## Reached Events + +The `NgScrollReached` directive is an addon feature for the `NgScrollbar` component in Angular. It emits events when the scroll position reaches specific points within the scrollable content. + +### Usage + +The `NgScrollReached` directive can be used by selecting one or more of the following output events: + +- `(reachedTop)` +- `(reachedBottom)` +- `(reachedStart)` +- `(reachedEnd)` + +**Example** + +```ts +import { NgScrollbar } from 'ngx-scrollbar'; +import { NgScrollReached } from 'ngx-scrollbar/reached-event'; + +@Component({ + standalone: true, + selector: 'example-component', + template: ` + + ... + + `, + imports: [NgScrollbar, NgScrollReached], +}) +export class ExampleComponent { +} +``` + +### API + +| Name | Description | +| --------------------------- | -------------------------------------------------------------------- | +| **(reachedTop)** | A stream that emits when scroll has reached the top. | +| **(reachedBottom)** | A stream that emits when scroll has reached the bottom. | +| **(reachedStart)** | A stream that emits when scroll has reached the left (right in RTL) | +| **(reachedEnd)** | A stream that emits when scroll has reached the right (left in RTL) | +| **[reachedOffset]** | Reached offset, default 0 | +| **[reachedTopOffset]** | Reached top offset, falls back to `reachedOffset` value | +| **[reachedBottomOffset]** | Reached bottom offset, falls back to `reachedOffset` value | +| **[reachedStartOffset]** | Reached start offset, falls back to `reachedOffset` value | +| **[reachedEndOffset]** | Reached end offset, falls back to `reachedOffset` value | +| **[disableReached]** | Disable the directive, default false | + +## Dropped Events + +The `NgScrollDropped` directive is an addon feature for the `NgScrollbar` component in Angular. It emits events when the scroll position drops from specific points within the scrollable content. + +### Usage + +The `NgScrollDropped` directive can be used by selecting one or more of the following output events: + +- `(droppedTop)` +- `(droppedBottom)` +- `(droppedStart)` +- `(droppedEnd)` + +**Example** + +```ts +import { NgScrollbar } from 'ngx-scrollbar'; +import { NgScrollDropped } from 'ngx-scrollbar/reached-event'; + +@Component({ + standalone: true, + selector: 'example-component', + template: ` + + ... + + `, + imports: [NgScrollbar, NgScrollDropped], +}) +export class ExampleComponent { +} +``` + +### API + +| Name | Description | +| --------------------------- | -------------------------------------------------------------------- | +| **(droppedTop)** | A stream that emits when scroll has dropped the top. | +| **(droppedBottom)** | A stream that emits when scroll has dropped the bottom. | +| **(droppedStart)** | A stream that emits when scroll has dropped the left (right in RTL) | +| **(droppedEnd)** | A stream that emits when scroll has dropped the right (left in RTL) | +| **[droppedOffset]** | Dropped offset, default 0 | +| **[droppedTopOffset]** | Dropped top offset, falls back to `droppedOffset` value | +| **[droppedBottomOffset]** | Dropped bottom offset, falls back to `droppedOffset` value | +| **[droppedStartOffset]** | Dropped start offset, falls back to `droppedOffset` value | +| **[droppedEndOffset]** | Dropped end offset, falls back to `droppedOffset` value | +| **[disableDropped]** | Disable the directive, default false | diff --git a/docs/v15/Scroll-timeline-polyfill.md b/docs/v15/Scroll-timeline-polyfill.md new file mode 100644 index 00000000..9755c4b0 --- /dev/null +++ b/docs/v15/Scroll-timeline-polyfill.md @@ -0,0 +1,43 @@ +This plugin leverages the new Scroll Timeline feature in CSS to enhance scrollbar performance, surpassing traditional JavaScript implementations. Currently, the Scroll Timeline feature is available in Chrome but not yet in Firefox and Safari ([See compatibility here](https://caniuse.com/mdn-css_properties_animation-timeline_scroll)). Therefore, a polyfill is necessary for cross-browser compatibility. + +By default, the polyfill script is sourced directly from the [flackr/scroll-timeline](https://github.com/flackr/scroll-timeline) repo on Github. Unfortunately, the polyfill is not available on NPM. + +#### Customizing the Polyfill Path + +You can use the `provideScrollbarPolyfill` function to specify a custom path for the polyfill. + +#### Hosting the Polyfill Locally + +As of version **v14.1.1**, the polyfill is included with `ngx-scrollbar`. To host and load the polyfill script from your server, add the following configuration to your `angular.json` file: + +```json +{ + "projects": { + "project-name": { + "architect": { + "build": { + "assets": [ + { + "glob": "**/*", + "input": "node_modules/ngx-scrollbar/assets/", + "output": "assets" + } + ] + } + } + } + } +} +``` + +Next, set the polyfill path in your `app.config` file: + +```ts +import { provideScrollbarPolyfill } from 'ngx-scrollbar'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideScrollbarPolyfill('assets/scroll-timeline-polyfill.js') + ] +}; +``` \ No newline at end of file diff --git a/docs/v15/Smooth-Scroll-Functions.md b/docs/v15/Smooth-Scroll-Functions.md new file mode 100644 index 00000000..06e56f0d --- /dev/null +++ b/docs/v15/Smooth-Scroll-Functions.md @@ -0,0 +1,117 @@ +The following smooth scroll function returns a promise that resolves when scrolling reaches the point or target + +## Scroll to position + +```ts +scrollable.scrollTo(options: SmoothScrollToOptions) +``` + +- **Element:** target HTMLElement, or ElementRef or the selector string. +- **Options:** + - **Top:** Set top offset, default `null`. + - **Bottom:** Set bottom offset, default `null`. + - **Start:** Set start offset (evaluates left in ltr, in right rtl), default `null`. + - **End:** Set end offset (evaluates right in ltr, in left rtl), default `null`. + - **Left:** Set left offset, default `null`. + - **Right:** Set right offset, default `null`. + - **Duration:** Time to reach the target in ms, default `null`. + - **Easing:** Smooth scroll animation can be customized using the bezier-easing properties `{ x1, y1, x2, y2 }`. + +Avoid setting `top` and `bottom`, or `start` and `end`, or `left` and `right` at the same time because that would obviously not work. + +## Scroll to element + +```ts +scrollable.scrollToElement(target: HTMLElement | ElementRef | string, options?) +``` + +- **Element:** target HTMLElement, or ElementRef or the selector string. +- **Options:** + - **Top:** Set top offset, default `null`. + - **Bottom:** Set bottom offset, default `null`. + - **Start:** Set start offset (evaluates left in ltr, in right rtl), default `null`. + - **End:** Set end offset (evaluates right in ltr, in left rtl), default `null`. + - **Left:** Set left offset, default `null`. + - **Right:** Set right offset, default `null`. + - **Duration:** time to reach the position in milliseconds, default `null`. + - **Easing:** Smooth scroll animation can be customized using the bezier-easing properties `{ x1, y1, x2, y2 }`. + +## Scrolling examples + +### Scroll to top directly from the template + +```html + + + + + +``` + +### Scroll to a specific element by a selector + +```html + +
+
+
+
+
+
+ + + + +``` + +### Scroll to a specific element by element reference + +```html + +
+
+
+
+
+
+ + +``` + +The same can be done from component code using the `@ViewChild` decorator + +```ts +@ViewChild(NgScrollbar) scrollable: NgScrollbar; +@ViewChild('comments') commentsSection: ElementRef; + +scrollToCommentsSection() { + this.scrollable.scrollToElement(this.commentsSection); +} +``` + + +### Scroll to top on route change + +If you wrap the `` inside ``, you can scroll to the top on route changes. + +The following example scrolls to top whenever the user navigates to another page. + +```html + + + +``` +```ts +export class AppComponent { + + @ViewChild(NgScrollbar, { static: true }) scrollable: NgScrollbar; + + constructor(router: Router) { + router.events.pipe( + filter(event => event instanceof NavigationEnd), + filter(() => !!this.scrollable), + tap((event: NavigationEnd) => this.scrollable.scrollTo({ top: 0, duration: 500 })) + ).subscribe(); + } +} +``` diff --git a/docs/v15/Smooth-Scroll.md b/docs/v15/Smooth-Scroll.md new file mode 100644 index 00000000..18c7d401 --- /dev/null +++ b/docs/v15/Smooth-Scroll.md @@ -0,0 +1,47 @@ +Smooth scroll feature is built-in `NgScrollbar` component, but if you like to use this feature outside `NgScrollbar` component, then use `SmoothScroll`directive which is available independently and it allows you to use smooth scroll functions on any scrollable element. + +The package provides: + +- `SmoothScroll` a directive for template usage +- `SmoothScrollManager` a service for code usage + + +### Directive Usage + +Import `SmoothScroll` in your component the use the directive on your scrollable element + +```ts +@Component({ + standalone: true, + selector: 'my-app', + imports: [SmoothScroll], + template: ` +
+
{{scrollableContent}}
+
+ + + ` +}) +``` + + +### Service Usage + +```ts +@Component({...}) +export class FooComponent { + + // A reference to the smooth scroll service + readonly smoothScroll: SmoothScrollManager = inject(SmoothScrollManager); + + // A reference to a scrollable element (in this example it is the host element) + readonly scrollableElement: HTMLElement = inject(ElementRef).nativeElement; + + scrollToTop(): void { + this.smoothScroll.scrollTo(this.scrollableElement, { top: 0, duration: 800 }); + } +} +``` + +See all [ScrollTo Functions](Smooth-Scroll-Functions.md). diff --git a/docs/v15/Styling.md b/docs/v15/Styling.md new file mode 100644 index 00000000..853f6c1a --- /dev/null +++ b/docs/v15/Styling.md @@ -0,0 +1,23 @@ +Customize the following CSS variables to tailor the appearance of your scrollbar + + +| CSS Variable Name | Value | Description | +|------------------------------------------|---------------------------------------|--------------------------------------------------------------------| +| `--scrollbar-border-radius` | 0px | Adjust the border radius of the scrollbar track. | +| `--scrollbar-thickness` | 5 | Set the thickness of the scrollbar track. | +| `--scrollbar-offset` | 0 | Define the space between the scrollbar track and its surroundings. | +| `--scrollbar-track-wrapper-transition` | width 60ms linear, height 60ms linear | Customize the transition effect for the scrollbar track wrapper. | +| `--scrollbar-track-color` | transparent | Define the color of the scrollbar track. | +| `--scrollbar-thumb-color` | rgb(0 0 0 / 20%) | Define the color of the scrollbar thumb. | +| `--scrollbar-thumb-hover-color` | var(--scrollbar-thumb-color) | Define the color of the scrollbar thumb when hovered. | +| `--scrollbar-hover-thickness` | var(--scrollbar-thickness) | Define the thickness of the scrollbar when hovered. | +| `--scrollbar-thumb-transition` | none | Customize the transition effect for the scrollbar thumb. | +| `--scrollbar-thumb-min-size` | 20 | Set the minimum size for the scrollbar thumb. | +| `--scrollbar-button-color` | var(--scrollbar-thumb-color) | Define the color of the scrollbar button. | +| `--scrollbar-button-hover-color` | var(--scrollbar-button-color) | Define the color of the scrollbar button when hovered. | +| `--scrollbar-button-active-color` | var(--scrollbar-button-hover-color) | Define the color of the scrollbar button when active. | +| `--scrollbar-button-fill` | white | Define the fill color for the scrollbar button arrow. | +| `--scrollbar-button-hover-fill` | var(--scrollbar-button-fill) | Define the fill color for the scrollbar button arrow when hovered. | +| `--scrollbar-button-active-fill` | var(--scrollbar-button-hover-fill) | Define the fill color for the scrollbar button arrow when active. | +| `--scrollbar-overscroll-behavior` | initial | Customize the overscroll behavior of the viewport. | +| `--scrollbar-mobile-overscroll-behavior` | none | Define the overscroll behavior for mobile devices. | diff --git a/docs/v15/Usage.md b/docs/v15/Usage.md new file mode 100644 index 00000000..1d19cd91 --- /dev/null +++ b/docs/v15/Usage.md @@ -0,0 +1,117 @@ +## Installation + +To install `ngx-scrollbar`, along with its dependency `@angular/cdk`, you can use **npm**: + +```bash +npm i ngx-scrollbar @angular/cdk +``` + +## Usage + +After installation, import `NgScrollbarModule` into your component imports: + +```js +import { NgScrollbarModule } from 'ngx-scrollbar'; + +@Component({ + standalone: true, + selector: 'app-root', + templateUrl: './app.html', + imports: [ + NgScrollbarModule + ] +}) +``` + +Then, in your component template, you can use the `` to wrap your content: + +```html + + + +``` + +### NgScrollbar API + +`` has the following are the inputs / outputs: + +| Name | Default value | Description | +| ------------------------------ | --------------- | ------------------------------------------------------------------- | +| **[orientation]** | `auto` | The scroll axis of the viewport `horizontal`, `vertical`, `auto`. | +| **[position]** | `native` | Invert scrollbar position `native`,`invertX`,`invertY`, `invertAll`.| +| **[visibility]** | `native` | Scrollbar visibility `native`, `hover`, `always`. | +| **[appearance]** | `standard` | Scrollbar appearance `standard`, `compact`. | +| **[trackClass]** | *null* | Add a class to scrollbars' tracks. | +| **[thumbClass]** | *null* | Add a class to scrollbars' thumbnails. | +| **[buttonClass]** | *null* | Add a class to scrollbar button elements. | +| **[buttons]** | false | Show scrollbar buttons. | +| **[hoverOffset]** | false | Activate hover effect on the offset area around the scrollbar. | +| **[trackClickDuration]** | 50 | The smooth scroll step duration when a scrollbar is clicked in ms. | +| **[minThumbSize]** | 20 | The minimum scrollbar thumb size in px. | +| **[sensorThrottleTime]** | 0 | The throttle time used for detecting size changes. | +| **[disableSensor]** | false | Whether `ResizeObserver` is disabled. | +| **[disableInteraction]** | false | Disables scrollbar interaction like dragging thumb and jumping by track click.| +| **(afterInit)** | - | Output that emits after the scrollbar component is initialized. | +| **(afterUpdate)** | - | Output that emits after the scrollbar component is updated. | +| **update()** | | Trigger a re-calculation to update the scrollbar. | +| **scrollTo(options)** | | Scroll function that returns a promise that resolves when scroll is reached. | +| **scrollToElement(target, options?)** | | Scroll function that returns a promise that resolves when scroll is reached. | + +--- + +## Advanced usage: select a custom viewport element + +The `externalViewport` directve allows you to designate another element as the viewport, you can select an external viewport using the `scrollViewport` directive. + +#### Example using `scrollViewport` directive: + +```html + +
+ +
+
+``` + +If viewport element is inaccessible from the template, you can pass its selector as the directive value `[externalViewport]=".my-custom-viewport"`. + +#### Example of passing viewport selector: + + +```html + +
+ +
+
+``` + + +By default a content wrapper element will be created inside the viewport to hold its content. optionally, you can select a custom content wrapper using the input `[externalContentWrapper]` + +#### Example of passing content wrapper selector: + +```html + +
+
+ +
+
+
+``` + +This capability enables integration of the scrollbar with 3rd-party libraries where the viewport or content wrapper elements are inaccessible from the template. + + +### NgScrollbarExt API + +`` extends `` with the following inputs: + + +| Name | Default value | Description | +| ------------------------------ | --------------- | ------------------------------------------------------------------- | +| **[externalViewport]** | *null* | External viewport selector. | +| **[externalContentWrapper]** | *null* | External content wrapper selector. | +| **[externalSpacer]** | *null* | External spacer selector used for calculating content dimensions instead of using the content wrapper (useful for virtual scroll libraries). | diff --git a/projects/ngx-scrollbar-demo/src/app/prime-ng-table/prime-ng.component.html b/projects/ngx-scrollbar-demo/src/app/prime-ng-table/prime-ng.component.html index d3935f01..3c79838c 100644 --- a/projects/ngx-scrollbar-demo/src/app/prime-ng-table/prime-ng.component.html +++ b/projects/ngx-scrollbar-demo/src/app/prime-ng-table/prime-ng.component.html @@ -41,7 +41,6 @@ -
- {{ item.vin }} -
+
{{ item.vin }}
diff --git a/projects/ngx-scrollbar/README.md b/projects/ngx-scrollbar/README.md index 4be67611..cc4303f2 100644 --- a/projects/ngx-scrollbar/README.md +++ b/projects/ngx-scrollbar/README.md @@ -15,18 +15,14 @@ Custom overlay-scrollbars with native scrolling mechanism for Angular, it also provides a cross-browser smooth scroll directive. -___ - -### The documentation is available at the [wiki page](https://github.com/MurhafSousli/ngx-scrollbar/wiki) 📚 - -___ +*** ## Features - Exceptional performance, see [comparing test results](https://github.com/MurhafSousli/ngx-scrollbar/wiki/Performance-analysis). - Native scrolling mechanism. - Easily Customizable using CSS variables. -- Easy integration with other libraries. +- Easy integration with 3rd party libraries. - RTL support. - Mobile support. - SSR support. @@ -34,10 +30,40 @@ ___ #### Addons directives: - Smooth scroll functionality. -- Scroll reached events. +- Scroll reached & dropped events. - **MatSelect** integration. - **CdkVirtualScroll** integration. +*** + +## Documentations + +### Table of contents + +* [Usage](docs/Usage.md) +* [Styling](docs/Styling.md) +* [Global Options](docs/Global-options.md) +* [Smooth Scroll Functions](docs/Smooth-Scroll-Functions.md) +* [Scroll Timeline Polyfill](docs/Scroll-timeline-polyfill.md) + +### Addons: + +* [Integration Examples](docs/Integration.md) + * [Material Select](docs/Integration.md#material-select) + * [Material Dialog](docs/Integration.md#material-dialog) + * [CDK Virtual Scroll](docs/Integration.md#cdk-virtual-scroll) + * [ngx-datatable table](docs/Integration.md#ngx-datatable-table) + * [Ag-grid table](docs/Integration.md#ag-grid-table) + * [PrimeNG Scroller](docs/Integration.md#primeng-scroller) + * [PrimeNG Table](docs/Integration.md#primeng-table) + * [PrimeNG Dropdown](docs/Integration.md#primeng-dropdown) + * [PrimeNg Dropdown (Virtual Scroll)](docs/Integration.md#primeng-dropdown-virtual-scroll) + * [Kendu UI Grid](docs/Integration.md#kendu-ui-grid) +* [Reached & Dropped Events](docs/Reached-&-Dropped-Events.md) +* [Smooth Scroll Functions (without NgScrollbar)](docs/Smooth-Scroll.md) + +___ + ## Issues diff --git a/projects/ngx-scrollbar/docs/Global-options.md b/projects/ngx-scrollbar/docs/Global-options.md new file mode 100644 index 00000000..873b45da --- /dev/null +++ b/projects/ngx-scrollbar/docs/Global-options.md @@ -0,0 +1,89 @@ +To override the default scrollbar options for all scrollbar components across the application, import the `provideScrollbarOptions` function in your providers, here is an example: + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideScrollbarOptions } from 'ngx-scrollbar'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideScrollbarOptions({ + visibility: 'hover', + appearance: 'compact' + }) + ] +}; +``` + +## NgScrollbarOptions API + + +| Name | Default value | Description | +| --------------------------- | --------------- | -------------------------------------------------------------------- | +| **orientation** | `auto` | The scroll axis of the viewport `auto`, `horizontal`, `vertical`. | +| **position** | `native` | Invert scrollbar position `native`,`invertX`,`invertY`, `invertAll`. | +| **visibility** | `native` | Scrollbar visibility `native`, `hover`, `always`. | +| **appearance** | `native` | Scrollbar appearance `native`, `compact`. | +| **trackClass** | *null* | Add a class to scrollbar track elements. | +| **thumbClass** | *null* | Add a class to scrollbar thumbnail elements. | +| **buttonClass** | *null* | Add a class to scrollbar button elements. | +| **buttons** | false | Show scrollbar buttons. | +| **hoverOffset** | false | Activate hover effect on the offset area around the scrollbar. | +| **trackClickDuration** | 50 | The smooth scroll step duration when a scrollbar is clicked in ms. | +| **minThumbSize** | 20 | The minimum scrollbar thumb size in px. | +| **sensorThrottleTime** | 0 | The throttle time used for detecting size changes. | +| **disableSensor** | false | Whether `ResizeObserver` is disabled. | +| **disableInteraction** | false | Disables scrollbar interaction like dragging thumb and jumping by track click.| + +## Types aliases + +### ScrollbarOrientation + +Sets the scroll axis of the viewport. + - `auto`: Scrollbars are displayed for both vertical and horizontal scrolling. + - `vertical`: Scrollbars are displayed for vertical scrolling. + - `horizontal`: Scrollbars are displayed for horizontal scrolling. + +Defaults to 'auto'. + +```ts +type ScrollbarOrientation = 'auto' | 'vertical' | 'horizontal'; +``` + +### ScrollbarAppearance + +Sets the appearance of the scrollbar. + - `native`: Scrollbar space is reserved within the viewport, similar to native scrollbars. + - `compact`: Scrollbars do not reserve any space and are placed over the viewport. + +Defaults to 'native'. + +```ts +type ScrollbarAppearance = 'native' | 'compact'; +``` + +### ScrollbarVisibility + +Determines when to show the scrollbar. + - `native`: Scrollbar is visible when the viewport is scrollable, similar to native scrollbars. + - `hover`: Scrollbars are hidden by default and become visible on scrolling or hovering. + - `visible`: Scrollbars are always visible, even if the viewport is not scrollable. + +Defaults to 'native'. + +```ts +type ScrollbarVisibility = 'native' | 'hover' | 'visible'; +``` + +### ScrollbarPosition + +Sets the position of each scrollbar. + - `native`: Uses the default position as in native scrollbars. + - `invertY`: Inverts the vertical scrollbar position. + - `invertX`: Inverts the horizontal scrollbar position. + - `invertAll`: Inverts the positions of both vertical and horizontal scrollbars. + +Defaults to 'native'. + +```ts +type ScrollbarPosition = 'native' | 'invertY' | 'invertX' | 'invertAll'; +``` diff --git a/projects/ngx-scrollbar/docs/Integration.md b/projects/ngx-scrollbar/docs/Integration.md new file mode 100644 index 00000000..2d428ec4 --- /dev/null +++ b/projects/ngx-scrollbar/docs/Integration.md @@ -0,0 +1,208 @@ +## Material Select + +When integrating with Material select, it's important to note that unlike other integration examples, you can't simply +wrap the control and assign an external viewport due to the nature of the dropdown menu being a CDK popup panel, which +isn't directly nested within the component. + +However, you can still achieve the desired functionality by wrapping the content with a scrollbar component and +utilizing the `matSelectViewport` addon directive. This directive ensures that the selected item is scrolled to as soon +as the dropdown opens. + +Here's how you can implement it: + +```html + + + @for (item of items; track item) { + + {{ item }} + + } + + +``` + +## Material Dialog + +In a Material dialog, the `mat-dialog-content` component serves as the scrollable element with default padding. + +To integrate with `ng-scrollbar`, we ensure it doesn't exceed the `mat-dialog-content` height and remove the padding +styles. Then, we apply padding to a content wrapper inside our scrollbar component. + +```html +

Dialog Title

+ + + +
+ +
+ +
+``` + +```scss +mat-dialog-content.mat-mdc-dialog-content { + display: flex; + flex-direction: column; + padding: 0 !important; +} + +.ng-scrollbar { + flex: 1; +} + +// Add the padding to the content wrapper inside the scrollbar +.content-wrapper { + padding: var(--mat-dialog-with-actions-content-padding, 20px 24px); +} +``` + +Here is an [integration with material dialog example](https://stackblitz.com/edit/ngx-scrollbar-tkzscf?file=package.json) + +## CDK Virtual Scroll + +```html + + +
+ {{ item }} +
+
+
+``` + +## ngx-datatable table + +```html + + + + + +``` + +## Ag-grid Table + +```html + + + +``` + +## PrimeNg Scroller + +```html + + + +
{{ item.vin }}
+
+
+
+``` + +## PrimeNg Table + +```html + + + + + @for (col of columns; track col.header) { + {{ col.header }} + } + + + + + @for (col of columns; track col.header) { + {{ rowData[col.field] }} + } + + + + +``` + +## PrimeNg Dropdown + +When utilizing the scrollbar with the PrimeNG dropdown, the scrollbar component initializes, but the dropdown menu is +generated only upon user interaction (i.e., clicking the dropdown). To ensure the scrollbar correctly detects when the +dropdown menu is rendered, use the `asyncDetection="auto"` directive. + +```html + + + +``` + +## PrimeNg Dropdown (Virtual Scroll) + +```html + + + +``` + +## Kendu UI Grid + +```html + + + ... + + +``` + +```scss +.k-height-container { + position: absolute !important; +} +``` + +Here is +a [Kendu UI grid example stackblitz](https://stackblitz.com/edit/angular-vgzffw-ldgsyr?file=src%2Fapp%2Fapp.component.ts,src%2Fstyles.css) diff --git a/projects/ngx-scrollbar/docs/Reached-&-Dropped-Events.md b/projects/ngx-scrollbar/docs/Reached-&-Dropped-Events.md new file mode 100644 index 00000000..ba3a7041 --- /dev/null +++ b/projects/ngx-scrollbar/docs/Reached-&-Dropped-Events.md @@ -0,0 +1,95 @@ +## Reached Events + +The `NgScrollReached` directive is an addon feature for the `NgScrollbar` component in Angular. It emits events when the scroll position reaches specific points within the scrollable content. + +### Usage + +The `NgScrollReached` directive can be used by selecting one or more of the following output events: + +- `(reachedTop)` +- `(reachedBottom)` +- `(reachedStart)` +- `(reachedEnd)` + +**Example** + +```ts +import { NgScrollbar } from 'ngx-scrollbar'; +import { NgScrollReached } from 'ngx-scrollbar/reached-event'; + +@Component({ + standalone: true, + selector: 'example-component', + template: ` + + ... + + `, + imports: [NgScrollbar, NgScrollReached], +}) +export class ExampleComponent { +} +``` + +### API + +| Name | Description | +| --------------------------- | -------------------------------------------------------------------- | +| **(reachedTop)** | A stream that emits when scroll has reached the top. | +| **(reachedBottom)** | A stream that emits when scroll has reached the bottom. | +| **(reachedStart)** | A stream that emits when scroll has reached the left (right in RTL) | +| **(reachedEnd)** | A stream that emits when scroll has reached the right (left in RTL) | +| **[reachedOffset]** | Reached offset, default 0 | +| **[reachedTopOffset]** | Reached top offset, falls back to `reachedOffset` value | +| **[reachedBottomOffset]** | Reached bottom offset, falls back to `reachedOffset` value | +| **[reachedStartOffset]** | Reached start offset, falls back to `reachedOffset` value | +| **[reachedEndOffset]** | Reached end offset, falls back to `reachedOffset` value | +| **[disableReached]** | Disable the directive, default false | + +## Dropped Events + +The `NgScrollDropped` directive is an addon feature for the `NgScrollbar` component in Angular. It emits events when the scroll position drops from specific points within the scrollable content. + +### Usage + +The `NgScrollDropped` directive can be used by selecting one or more of the following output events: + +- `(droppedTop)` +- `(droppedBottom)` +- `(droppedStart)` +- `(droppedEnd)` + +**Example** + +```ts +import { NgScrollbar } from 'ngx-scrollbar'; +import { NgScrollDropped } from 'ngx-scrollbar/reached-event'; + +@Component({ + standalone: true, + selector: 'example-component', + template: ` + + ... + + `, + imports: [NgScrollbar, NgScrollDropped], +}) +export class ExampleComponent { +} +``` + +### API + +| Name | Description | +| --------------------------- | -------------------------------------------------------------------- | +| **(droppedTop)** | A stream that emits when scroll has dropped the top. | +| **(droppedBottom)** | A stream that emits when scroll has dropped the bottom. | +| **(droppedStart)** | A stream that emits when scroll has dropped the left (right in RTL) | +| **(droppedEnd)** | A stream that emits when scroll has dropped the right (left in RTL) | +| **[droppedOffset]** | Dropped offset, default 0 | +| **[droppedTopOffset]** | Dropped top offset, falls back to `droppedOffset` value | +| **[droppedBottomOffset]** | Dropped bottom offset, falls back to `droppedOffset` value | +| **[droppedStartOffset]** | Dropped start offset, falls back to `droppedOffset` value | +| **[droppedEndOffset]** | Dropped end offset, falls back to `droppedOffset` value | +| **[disableDropped]** | Disable the directive, default false | diff --git a/projects/ngx-scrollbar/docs/Scroll-timeline-polyfill.md b/projects/ngx-scrollbar/docs/Scroll-timeline-polyfill.md new file mode 100644 index 00000000..9755c4b0 --- /dev/null +++ b/projects/ngx-scrollbar/docs/Scroll-timeline-polyfill.md @@ -0,0 +1,43 @@ +This plugin leverages the new Scroll Timeline feature in CSS to enhance scrollbar performance, surpassing traditional JavaScript implementations. Currently, the Scroll Timeline feature is available in Chrome but not yet in Firefox and Safari ([See compatibility here](https://caniuse.com/mdn-css_properties_animation-timeline_scroll)). Therefore, a polyfill is necessary for cross-browser compatibility. + +By default, the polyfill script is sourced directly from the [flackr/scroll-timeline](https://github.com/flackr/scroll-timeline) repo on Github. Unfortunately, the polyfill is not available on NPM. + +#### Customizing the Polyfill Path + +You can use the `provideScrollbarPolyfill` function to specify a custom path for the polyfill. + +#### Hosting the Polyfill Locally + +As of version **v14.1.1**, the polyfill is included with `ngx-scrollbar`. To host and load the polyfill script from your server, add the following configuration to your `angular.json` file: + +```json +{ + "projects": { + "project-name": { + "architect": { + "build": { + "assets": [ + { + "glob": "**/*", + "input": "node_modules/ngx-scrollbar/assets/", + "output": "assets" + } + ] + } + } + } + } +} +``` + +Next, set the polyfill path in your `app.config` file: + +```ts +import { provideScrollbarPolyfill } from 'ngx-scrollbar'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideScrollbarPolyfill('assets/scroll-timeline-polyfill.js') + ] +}; +``` \ No newline at end of file diff --git a/projects/ngx-scrollbar/docs/Smooth-Scroll-Functions.md b/projects/ngx-scrollbar/docs/Smooth-Scroll-Functions.md new file mode 100644 index 00000000..e8f546f7 --- /dev/null +++ b/projects/ngx-scrollbar/docs/Smooth-Scroll-Functions.md @@ -0,0 +1,131 @@ +The following smooth scroll function returns a promise that resolves when scrolling reaches the point or target + +## Scroll to position + +```ts +scrollable.scrollTo(options: SmoothScrollToOptions) +``` + +- **Element:** target HTMLElement, or ElementRef or the selector string. +- **Options:** + - **Top:** Set top offset, default `null`. + - **Bottom:** Set bottom offset, default `null`. + - **Start:** Set start offset (evaluates left in ltr, in right rtl), default `null`. + - **End:** Set end offset (evaluates right in ltr, in left rtl), default `null`. + - **Left:** Set left offset, default `null`. + - **Right:** Set right offset, default `null`. + - **Duration:** Time to reach the target in ms, default `null`. + - **Easing:** Smooth scroll animation can be customized using the bezier-easing properties `{ x1, y1, x2, y2 }`. + +Avoid setting `top` and `bottom`, or `start` and `end`, or `left` and `right` at the same time because that would obviously not work. + +## Scroll to element + +```ts +scrollable.scrollToElement(target: HTMLElement | ElementRef | string, options?) +``` + +- **Element:** target HTMLElement, or ElementRef or the selector string. +- **Options:** + - **Top:** Set top offset, default `null`. + - **Bottom:** Set bottom offset, default `null`. + - **Start:** Set start offset (evaluates left in ltr, in right rtl), default `null`. + - **End:** Set end offset (evaluates right in ltr, in left rtl), default `null`. + - **Left:** Set left offset, default `null`. + - **Right:** Set right offset, default `null`. + - **Center** Set the scroll position to the center of the target element, default: `false` + - **Duration:** time to reach the position in milliseconds, default `null`. + - **Easing:** Smooth scroll animation can be customized using the bezier-easing properties `{ x1, y1, x2, y2 }`. + +## Scrolling examples + +### Scroll to top directly from the template + +```html + + + + + +``` + +### Scroll to a specific element by a selector + +```html + +
+
+
+
+
+
+ + + + +``` + +### Scroll to a specific element by element reference + +```html + +
+
+
+
+
+
+ + +``` + +The same can be done from component code using the `@ViewChild` decorator + +```ts +@ViewChild(NgScrollbar) scrollable: NgScrollbar; +@ViewChild('comments') commentsSection: ElementRef; + +scrollToCommentsSection() { + this.scrollable.scrollToElement(this.commentsSection); +} +``` + + +### Scroll to top on route change + +If you wrap the `` inside ``, you can scroll to the top on route changes. + +The following example scrolls to top whenever the user navigates to another page. + +```html + + + +``` + +```ts +import { untracked } from '@angular/core'; +import { Subscription } from 'rxjs'; + +export class AppComponent { + + scrollableComponent: Signal = viewChild(NgScrollbar); + + constructor(router: Router) { + let sub$: Subscription; + + effect((onCleanup) => { + const scrollbar: NgScrollbar = this.scrollableComponent(); + untracked(() => { + if (scrollbar) { + sub$ = router.events.pipe( + filter(event => event instanceof NavigationEnd), + tap((event: NavigationEnd) => scrollable.scrollTo({ top: 0, duration: 500 })) + ).subscribe(); + } + onCleanup(() => sub$?.unsubscribe()) + }); + }); + } +} +``` diff --git a/projects/ngx-scrollbar/docs/Smooth-Scroll.md b/projects/ngx-scrollbar/docs/Smooth-Scroll.md new file mode 100644 index 00000000..18c7d401 --- /dev/null +++ b/projects/ngx-scrollbar/docs/Smooth-Scroll.md @@ -0,0 +1,47 @@ +Smooth scroll feature is built-in `NgScrollbar` component, but if you like to use this feature outside `NgScrollbar` component, then use `SmoothScroll`directive which is available independently and it allows you to use smooth scroll functions on any scrollable element. + +The package provides: + +- `SmoothScroll` a directive for template usage +- `SmoothScrollManager` a service for code usage + + +### Directive Usage + +Import `SmoothScroll` in your component the use the directive on your scrollable element + +```ts +@Component({ + standalone: true, + selector: 'my-app', + imports: [SmoothScroll], + template: ` +
+
{{scrollableContent}}
+
+ + + ` +}) +``` + + +### Service Usage + +```ts +@Component({...}) +export class FooComponent { + + // A reference to the smooth scroll service + readonly smoothScroll: SmoothScrollManager = inject(SmoothScrollManager); + + // A reference to a scrollable element (in this example it is the host element) + readonly scrollableElement: HTMLElement = inject(ElementRef).nativeElement; + + scrollToTop(): void { + this.smoothScroll.scrollTo(this.scrollableElement, { top: 0, duration: 800 }); + } +} +``` + +See all [ScrollTo Functions](Smooth-Scroll-Functions.md). diff --git a/projects/ngx-scrollbar/docs/Styling.md b/projects/ngx-scrollbar/docs/Styling.md new file mode 100644 index 00000000..853f6c1a --- /dev/null +++ b/projects/ngx-scrollbar/docs/Styling.md @@ -0,0 +1,23 @@ +Customize the following CSS variables to tailor the appearance of your scrollbar + + +| CSS Variable Name | Value | Description | +|------------------------------------------|---------------------------------------|--------------------------------------------------------------------| +| `--scrollbar-border-radius` | 0px | Adjust the border radius of the scrollbar track. | +| `--scrollbar-thickness` | 5 | Set the thickness of the scrollbar track. | +| `--scrollbar-offset` | 0 | Define the space between the scrollbar track and its surroundings. | +| `--scrollbar-track-wrapper-transition` | width 60ms linear, height 60ms linear | Customize the transition effect for the scrollbar track wrapper. | +| `--scrollbar-track-color` | transparent | Define the color of the scrollbar track. | +| `--scrollbar-thumb-color` | rgb(0 0 0 / 20%) | Define the color of the scrollbar thumb. | +| `--scrollbar-thumb-hover-color` | var(--scrollbar-thumb-color) | Define the color of the scrollbar thumb when hovered. | +| `--scrollbar-hover-thickness` | var(--scrollbar-thickness) | Define the thickness of the scrollbar when hovered. | +| `--scrollbar-thumb-transition` | none | Customize the transition effect for the scrollbar thumb. | +| `--scrollbar-thumb-min-size` | 20 | Set the minimum size for the scrollbar thumb. | +| `--scrollbar-button-color` | var(--scrollbar-thumb-color) | Define the color of the scrollbar button. | +| `--scrollbar-button-hover-color` | var(--scrollbar-button-color) | Define the color of the scrollbar button when hovered. | +| `--scrollbar-button-active-color` | var(--scrollbar-button-hover-color) | Define the color of the scrollbar button when active. | +| `--scrollbar-button-fill` | white | Define the fill color for the scrollbar button arrow. | +| `--scrollbar-button-hover-fill` | var(--scrollbar-button-fill) | Define the fill color for the scrollbar button arrow when hovered. | +| `--scrollbar-button-active-fill` | var(--scrollbar-button-hover-fill) | Define the fill color for the scrollbar button arrow when active. | +| `--scrollbar-overscroll-behavior` | initial | Customize the overscroll behavior of the viewport. | +| `--scrollbar-mobile-overscroll-behavior` | none | Define the overscroll behavior for mobile devices. | diff --git a/projects/ngx-scrollbar/docs/Usage.md b/projects/ngx-scrollbar/docs/Usage.md new file mode 100644 index 00000000..d2ce7bab --- /dev/null +++ b/projects/ngx-scrollbar/docs/Usage.md @@ -0,0 +1,132 @@ +## Installation + +To install `ngx-scrollbar`, along with its dependency `@angular/cdk`, you can use **npm**: + +```bash +npm i ngx-scrollbar @angular/cdk +``` + +## Usage + +After installation, import `NgScrollbarModule` into your component imports: + +```js +import { NgScrollbarModule } from 'ngx-scrollbar'; + +@Component({ + standalone: true, + selector: 'app-root', + templateUrl: './app.html', + imports: [ + NgScrollbarModule + ] +}) +``` + +Then, in your component template, you can use the `` to wrap your content: + +```html + + + +``` + +### NgScrollbar API + +`` has the following are the inputs / outputs: + +| Name | Default value | Description | +|---------------------------------------|---------------|--------------------------------------------------------------------------------| +| **[orientation]** | `auto` | The scroll axis of the viewport `horizontal`, `vertical`, `auto`. | +| **[position]** | `native` | Invert scrollbar position `native`,`invertX`,`invertY`, `invertAll`. | +| **[visibility]** | `native` | Scrollbar visibility `native`, `hover`, `always`. | +| **[appearance]** | `standard` | Scrollbar appearance `standard`, `compact`. | +| **[trackClass]** | *null* | Add a class to scrollbars' tracks. | +| **[thumbClass]** | *null* | Add a class to scrollbars' thumbnails. | +| **[buttonClass]** | *null* | Add a class to scrollbar button elements. | +| **[buttons]** | false | Show scrollbar buttons. | +| **[hoverOffset]** | false | Activate hover effect on the offset area around the scrollbar. | +| **[trackClickDuration]** | 50 | The smooth scroll step duration when a scrollbar is clicked in ms. | +| **[minThumbSize]** | 20 | The minimum scrollbar thumb size in px. | +| **[sensorThrottleTime]** | 0 | The throttle time used for detecting size changes. | +| **[disableSensor]** | false | Whether `ResizeObserver` is disabled. | +| **[disableInteraction]** | false | Disables scrollbar interaction like dragging thumb and jumping by track click. | +| **(afterInit)** | | Output that emits after the scrollbar component is initialized. | +| **(afterUpdate)** | | Output that emits after the scrollbar component is updated. | +| **update()** | | Trigger a re-calculation to update the scrollbar. | +| **scrollTo(options)** | | Scroll function that returns a promise that resolves when scroll is reached. | +| **scrollToElement(target, options?)** | | Scroll function that returns a promise that resolves when scroll is reached. | + +--- + +## Advanced usage: select a custom viewport element + +The `externalViewport` directve allows you to designate another element as the viewport, you can select an external +viewport using the `scrollViewport` directive. + +#### Example using `scrollViewport` directive: + +```html + +
+ +
+
+``` + +If viewport element is inaccessible from the template, you can pass its selector as the directive value +`[externalViewport]=".my-custom-viewport"`. + +#### Example of passing viewport selector: + +```html + +
+ +
+
+``` + +By default a content wrapper element will be created inside the viewport to hold its content. optionally, you can select +a custom content wrapper using the input `[externalContentWrapper]` + +#### Example of passing content wrapper selector: + +```html + +
+
+ +
+
+
+``` + +This capability enables integration of the scrollbar with 3rd-party libraries where the viewport or content wrapper +elements are inaccessible from the template. + +### NgScrollbarExt API + +`` extends `` with the following inputs: + +| Name | Default value | Description | +|------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------| +| **[externalViewport]** | *null* | External viewport selector. | +| **[externalContentWrapper]** | *null* | External content wrapper selector. | +| **[externalSpacer]** | *null* | External spacer selector used for calculating content dimensions instead of using the content wrapper (useful for virtual scroll libraries). | + +### NgScrollbarExt + Async Detection directive + +The `AsyncDetection` directive is an addon for the `` component. It is particularly +useful when integrating with third-party libraries where the viewport and its content may not be rendered at the time +the scrollbar component is created. This directive detects the viewport and content wrapper, attaching the scrollbar +as soon as they are rendered. + +In contrast, you should use `asyncDetection="auto"` when the target viewport may be destroyed and recreated, +such as in the case of a dropdown menu. + +### NgScrollbarExt + Sync Spacer directive + +The syncSpacer directive is an addon for the component. It is particularly useful when +integrating with a virtual scroll component, ensuring that both scrollbars are displayed correctly. diff --git a/projects/ngx-scrollbar/ng-package.json b/projects/ngx-scrollbar/ng-package.json index d1787ea4..b800b63c 100644 --- a/projects/ngx-scrollbar/ng-package.json +++ b/projects/ngx-scrollbar/ng-package.json @@ -6,7 +6,8 @@ "glob": "*", "input": "./src/assets/", "output": "assets" - } + }, + "docs/*.md" ], "lib": { "entryFile": "src/public-api.ts" diff --git a/projects/ngx-scrollbar/package.json b/projects/ngx-scrollbar/package.json index 9e97c9dd..a7fe28b4 100644 --- a/projects/ngx-scrollbar/package.json +++ b/projects/ngx-scrollbar/package.json @@ -1,6 +1,6 @@ { "name": "ngx-scrollbar", - "version": "16.0.0-beta.2", + "version": "16.0.0", "license": "MIT", "homepage": "https://ngx-scrollbar.netlify.app/", "author": { diff --git a/projects/ngx-scrollbar/src/lib/async-detection.ts b/projects/ngx-scrollbar/src/lib/async-detection.ts index 7b51a137..eb5c1490 100644 --- a/projects/ngx-scrollbar/src/lib/async-detection.ts +++ b/projects/ngx-scrollbar/src/lib/async-detection.ts @@ -9,9 +9,8 @@ import { EffectCleanupRegisterFn } from '@angular/core'; import { ContentObserver } from '@angular/cdk/observers'; -import { Subscription } from 'rxjs'; +import { Subscription, throttleTime } from 'rxjs'; import { NgScrollbarExt } from './ng-scrollbar-ext'; -import { getThrottledStream } from './utils/common'; @Directive({ standalone: true, @@ -42,7 +41,14 @@ export class AsyncDetection { let contentWrapperElement: HTMLElement; this.zone.runOutsideAngular(() => { - sub$ = getThrottledStream(this.contentObserver.observe(this.scrollbar.nativeElement), 100).subscribe(() => { + // The content observer should not be throttled using the same function we use for ResizeObserver, + // It should detect the content change asap to attach the scrollbar + sub$ = this.contentObserver.observe(this.scrollbar.nativeElement).pipe( + throttleTime(100, null, { + leading: true, + trailing: true + }) + ).subscribe(() => { // Search for external viewport viewportElement = this.scrollbar.nativeElement.querySelector(externalViewport); diff --git a/projects/ngx-scrollbar/src/lib/ng-scrollbar-ext.ts b/projects/ngx-scrollbar/src/lib/ng-scrollbar-ext.ts index 7f51c0f4..e9348b0f 100644 --- a/projects/ngx-scrollbar/src/lib/ng-scrollbar-ext.ts +++ b/projects/ngx-scrollbar/src/lib/ng-scrollbar-ext.ts @@ -169,7 +169,7 @@ export class NgScrollbarExt extends NgScrollbarCore { // Initialize viewport this.viewport.init(viewportElement, contentWrapperElement, spacerElement); // Attach scrollbars - this.attachScrollbars(); + this._attachScrollbars(); } } } @@ -185,7 +185,7 @@ export class NgScrollbarExt extends NgScrollbarCore { super(); } - attachScrollbars(): void { + _attachScrollbars(): void { // Create the scrollbars component this._scrollbarsRef = createComponent(Scrollbars, { environmentInjector: this.appRef.injector, diff --git a/projects/ngx-scrollbar/src/lib/tests/ext-viewport-class.spec.ts b/projects/ngx-scrollbar/src/lib/tests/ext-viewport-class.spec.ts index ae728f0c..3ead6a0a 100644 --- a/projects/ngx-scrollbar/src/lib/tests/ext-viewport-class.spec.ts +++ b/projects/ngx-scrollbar/src/lib/tests/ext-viewport-class.spec.ts @@ -79,7 +79,7 @@ describe('External viewport via classes', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); component.externalViewport = '.my-custom-viewport'; @@ -129,7 +129,7 @@ describe('External viewport via classes', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); component.externalViewport = '.my-custom-viewport'; component.externalContentWrapper = '.my-custom-content-wrapper'; @@ -182,7 +182,7 @@ describe('External viewport via classes', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); component.externalViewport = '.my-custom-viewport'; component.externalContentWrapper = '.my-custom-content-wrapper'; component.externalSpacer = '.my-custom-spacer'; @@ -237,7 +237,7 @@ describe('External viewport via classes', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); const consoleSpy: jasmine.Spy = spyOn(console, 'error').and.callThrough(); component.externalViewport = '.not-existing-viewport'; @@ -260,7 +260,7 @@ describe('External viewport via classes', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); const consoleSpy: jasmine.Spy = spyOn(console, 'error').and.callThrough(); component.externalViewport = '.my-custom-viewport'; @@ -284,7 +284,7 @@ describe('External viewport via classes', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); const consoleSpy: jasmine.Spy = spyOn(console, 'error').and.callThrough(); component.externalViewport = '.my-custom-viewport'; diff --git a/projects/ngx-scrollbar/src/lib/tests/ext-viewport-directive.spec.ts b/projects/ngx-scrollbar/src/lib/tests/ext-viewport-directive.spec.ts index e7f16f7b..9caf5cd5 100644 --- a/projects/ngx-scrollbar/src/lib/tests/ext-viewport-directive.spec.ts +++ b/projects/ngx-scrollbar/src/lib/tests/ext-viewport-directive.spec.ts @@ -53,7 +53,7 @@ describe('External viewport via scrollViewportDirective', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); fixture.detectChanges(); @@ -98,7 +98,7 @@ describe('External viewport via scrollViewportDirective', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); component.externalContentWrapper = '.my-custom-content-wrapper'; fixture.detectChanges(); @@ -149,7 +149,7 @@ describe('External viewport via scrollViewportDirective', () => { const scrollbarCmp: NgScrollbarExt = component.scrollbar(); const viewportInitSpy: jasmine.Spy = spyOn(scrollbarCmp.viewport, 'init').and.callThrough(); - const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, 'attachScrollbars').and.callThrough(); + const attachScrollbarSpy: jasmine.Spy = spyOn(scrollbarCmp, '_attachScrollbars').and.callThrough(); component.externalContentWrapper = '.my-custom-content-wrapper'; component.externalSpacer = '.my-custom-spacer';