forty-cdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, input, numberAttribute, signal, DestroyRef, Directive, ElementRef, DOCUMENT, PLATFORM_ID, afterNextRender, effect, computed } from '@angular/core';
|
|
3
|
+
import { createDefaults, injectTextDirection, injectElementSize } from 'forty-cdk/core';
|
|
4
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
5
|
+
|
|
6
|
+
const FOR_SCROLL_AREA_CONTEXT = new InjectionToken('FOR_SCROLL_AREA_CONTEXT');
|
|
7
|
+
function injectScrollAreaContext(piece) {
|
|
8
|
+
const ctx = inject(FOR_SCROLL_AREA_CONTEXT, { optional: true });
|
|
9
|
+
if (!ctx) {
|
|
10
|
+
throw new Error(`[forty-cdk/scroll-area] ${piece} must be used inside a [forScrollArea] element.`);
|
|
11
|
+
}
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Library fallback for scroll area defaults, read at the root injector when no
|
|
17
|
+
* consumer has called `provideForScrollAreaDefaults`. Exported for the shared defaults
|
|
18
|
+
* contract spec; not re-exported from the primitive's public entry.
|
|
19
|
+
*/
|
|
20
|
+
const FOR_SCROLL_AREA_FALLBACK_DEFAULTS = {
|
|
21
|
+
scrollHideDelay: 600,
|
|
22
|
+
};
|
|
23
|
+
const { token, provideDefaults } = createDefaults('FOR_SCROLL_AREA_DEFAULTS', FOR_SCROLL_AREA_FALLBACK_DEFAULTS);
|
|
24
|
+
/** Token holding the resolved scroll-area defaults for the current scope. */
|
|
25
|
+
const FOR_SCROLL_AREA_DEFAULTS = token;
|
|
26
|
+
/**
|
|
27
|
+
* Configures forty-cdk scroll-area defaults for this injector scope. Partial
|
|
28
|
+
* overrides inherit unspecified keys from the parent scope (or library
|
|
29
|
+
* defaults at the root).
|
|
30
|
+
*/
|
|
31
|
+
function provideForScrollAreaDefaults(defaults = {}) {
|
|
32
|
+
return provideDefaults(defaults);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Root of the custom-scrollbar primitive. Owns the viewport reference,
|
|
37
|
+
* scroll geometry signals, and visibility state for the synthetic
|
|
38
|
+
* scrollbars.
|
|
39
|
+
*
|
|
40
|
+
* Native scrollbars on the inner viewport are hidden via styles injected
|
|
41
|
+
* by `[forScrollAreaViewport]` (the only place in forty-cdk that
|
|
42
|
+
* imperatively touches CSS); the consumer styles `[forScrollAreaThumb]`
|
|
43
|
+
* however they like.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```html
|
|
47
|
+
* <div forScrollArea>
|
|
48
|
+
* <div forScrollAreaViewport>
|
|
49
|
+
* <div forScrollAreaContent>… long content …</div>
|
|
50
|
+
* </div>
|
|
51
|
+
* <div forScrollAreaScrollbar orientation="vertical">
|
|
52
|
+
* <div forScrollAreaThumb></div>
|
|
53
|
+
* </div>
|
|
54
|
+
* <div forScrollAreaScrollbar orientation="horizontal">
|
|
55
|
+
* <div forScrollAreaThumb></div>
|
|
56
|
+
* </div>
|
|
57
|
+
* <div forScrollAreaCorner></div>
|
|
58
|
+
* </div>
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
class ForScrollArea {
|
|
62
|
+
#defaults = inject(FOR_SCROLL_AREA_DEFAULTS);
|
|
63
|
+
/**
|
|
64
|
+
* When the synthetic scrollbars are visible:
|
|
65
|
+
* - `auto`: shown whenever the axis overflows, regardless of interaction;
|
|
66
|
+
* self-hides when the content fits.
|
|
67
|
+
* - `always`: the scrollbar (and corner) stay mounted and
|
|
68
|
+
* `data-state="visible"` regardless of overflow — a stable, always-painted
|
|
69
|
+
* track. When the axis does not overflow the thumb fills
|
|
70
|
+
* the full track and dragging is a no-op. The library is headless and does
|
|
71
|
+
* not own layout, so to actually *reserve* the gutter (no content shift
|
|
72
|
+
* when crossing the overflow boundary) the consumer lays the scrollbar out
|
|
73
|
+
* in flow — see the ScrollArea README's grid example.
|
|
74
|
+
* - `scroll`: shown during scroll, faded after `scrollHideDelay` ms.
|
|
75
|
+
* - `hover`: shown while the cursor is over the area (and during scroll).
|
|
76
|
+
*/
|
|
77
|
+
type = input('hover', /* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
79
|
+
/**
|
|
80
|
+
* ms after the most recent scroll before scrollbars fade (only
|
|
81
|
+
* `type="scroll"` and `"hover"`). The default is read from
|
|
82
|
+
* `provideForScrollAreaDefaults` for the surrounding scope.
|
|
83
|
+
*/
|
|
84
|
+
scrollHideDelay = input(this.#defaults.scrollHideDelay, { ...(ngDevMode ? { debugName: "scrollHideDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
85
|
+
/**
|
|
86
|
+
* Writing direction. When unset (default `null`), the inherited ambient
|
|
87
|
+
* direction is resolved from the nearest ancestor carrying a `dir` attribute
|
|
88
|
+
* (or `<html dir>`), defaulting to `'ltr'`. An explicit `[dir]` always wins.
|
|
89
|
+
* The resolved value is reflected to the host `dir` attribute and positions
|
|
90
|
+
* the vertical scrollbar on the left in RTL.
|
|
91
|
+
*/
|
|
92
|
+
_dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
93
|
+
dir = injectTextDirection(this._dirInput);
|
|
94
|
+
#viewport = signal(null, /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "#viewport" }] : /* istanbul ignore next */ []));
|
|
96
|
+
viewport = this.#viewport.asReadonly();
|
|
97
|
+
#content = signal(null, /* @ts-ignore */
|
|
98
|
+
...(ngDevMode ? [{ debugName: "#content" }] : /* istanbul ignore next */ []));
|
|
99
|
+
content = this.#content.asReadonly();
|
|
100
|
+
#scrollLeft = signal(0, /* @ts-ignore */
|
|
101
|
+
...(ngDevMode ? [{ debugName: "#scrollLeft" }] : /* istanbul ignore next */ []));
|
|
102
|
+
#scrollTop = signal(0, /* @ts-ignore */
|
|
103
|
+
...(ngDevMode ? [{ debugName: "#scrollTop" }] : /* istanbul ignore next */ []));
|
|
104
|
+
scrollLeft = this.#scrollLeft.asReadonly();
|
|
105
|
+
scrollTop = this.#scrollTop.asReadonly();
|
|
106
|
+
#clientW = signal(0, /* @ts-ignore */
|
|
107
|
+
...(ngDevMode ? [{ debugName: "#clientW" }] : /* istanbul ignore next */ []));
|
|
108
|
+
#clientH = signal(0, /* @ts-ignore */
|
|
109
|
+
...(ngDevMode ? [{ debugName: "#clientH" }] : /* istanbul ignore next */ []));
|
|
110
|
+
#scrollW = signal(0, /* @ts-ignore */
|
|
111
|
+
...(ngDevMode ? [{ debugName: "#scrollW" }] : /* istanbul ignore next */ []));
|
|
112
|
+
#scrollH = signal(0, /* @ts-ignore */
|
|
113
|
+
...(ngDevMode ? [{ debugName: "#scrollH" }] : /* istanbul ignore next */ []));
|
|
114
|
+
clientWidth = this.#clientW.asReadonly();
|
|
115
|
+
clientHeight = this.#clientH.asReadonly();
|
|
116
|
+
scrollWidth = this.#scrollW.asReadonly();
|
|
117
|
+
scrollHeight = this.#scrollH.asReadonly();
|
|
118
|
+
#hovering = signal(false, /* @ts-ignore */
|
|
119
|
+
...(ngDevMode ? [{ debugName: "#hovering" }] : /* istanbul ignore next */ []));
|
|
120
|
+
hovering = this.#hovering.asReadonly();
|
|
121
|
+
#scrolling = signal(false, /* @ts-ignore */
|
|
122
|
+
...(ngDevMode ? [{ debugName: "#scrolling" }] : /* istanbul ignore next */ []));
|
|
123
|
+
scrolling = this.#scrolling.asReadonly();
|
|
124
|
+
#scrollTimer = null;
|
|
125
|
+
registerViewport(el) {
|
|
126
|
+
this.#viewport.set(el);
|
|
127
|
+
}
|
|
128
|
+
registerContent(el) {
|
|
129
|
+
this.#content.set(el);
|
|
130
|
+
}
|
|
131
|
+
unregisterContent(el) {
|
|
132
|
+
// Only clear if the unregistering element is the one we currently track,
|
|
133
|
+
// so a late teardown of an old content piece doesn't blow away a freshly
|
|
134
|
+
// registered replacement.
|
|
135
|
+
if (this.#content() === el) {
|
|
136
|
+
this.#content.set(null);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
reportScroll(left, top) {
|
|
140
|
+
this.#scrollLeft.set(left);
|
|
141
|
+
this.#scrollTop.set(top);
|
|
142
|
+
}
|
|
143
|
+
reportSize(clientW, clientH, scrollW, scrollH) {
|
|
144
|
+
this.#clientW.set(clientW);
|
|
145
|
+
this.#clientH.set(clientH);
|
|
146
|
+
this.#scrollW.set(scrollW);
|
|
147
|
+
this.#scrollH.set(scrollH);
|
|
148
|
+
}
|
|
149
|
+
noteUserScroll() {
|
|
150
|
+
this.#scrolling.set(true);
|
|
151
|
+
if (this.#scrollTimer !== null)
|
|
152
|
+
clearTimeout(this.#scrollTimer);
|
|
153
|
+
this.#scrollTimer = setTimeout(() => {
|
|
154
|
+
this.#scrollTimer = null;
|
|
155
|
+
this.#scrolling.set(false);
|
|
156
|
+
}, Math.max(0, this.scrollHideDelay()));
|
|
157
|
+
}
|
|
158
|
+
onPointerEnter() {
|
|
159
|
+
this.#hovering.set(true);
|
|
160
|
+
}
|
|
161
|
+
onPointerLeave() {
|
|
162
|
+
this.#hovering.set(false);
|
|
163
|
+
}
|
|
164
|
+
constructor() {
|
|
165
|
+
inject(DestroyRef).onDestroy(() => {
|
|
166
|
+
if (this.#scrollTimer !== null)
|
|
167
|
+
clearTimeout(this.#scrollTimer);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollArea, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
171
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForScrollArea, isStandalone: true, selector: "[forScrollArea]", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, scrollHideDelay: { classPropertyName: "scrollHideDelay", publicName: "scrollHideDelay", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "pointerenter": "onPointerEnter()", "pointerleave": "onPointerLeave()" }, properties: { "attr.data-type": "type()", "attr.dir": "dir()" } }, providers: [{ provide: FOR_SCROLL_AREA_CONTEXT, useExisting: ForScrollArea }], exportAs: ["forScrollArea"], ngImport: i0 });
|
|
172
|
+
}
|
|
173
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollArea, decorators: [{
|
|
174
|
+
type: Directive,
|
|
175
|
+
args: [{
|
|
176
|
+
selector: '[forScrollArea]',
|
|
177
|
+
exportAs: 'forScrollArea',
|
|
178
|
+
host: {
|
|
179
|
+
'[attr.data-type]': 'type()',
|
|
180
|
+
'[attr.dir]': 'dir()',
|
|
181
|
+
'(pointerenter)': 'onPointerEnter()',
|
|
182
|
+
'(pointerleave)': 'onPointerLeave()',
|
|
183
|
+
},
|
|
184
|
+
providers: [{ provide: FOR_SCROLL_AREA_CONTEXT, useExisting: ForScrollArea }],
|
|
185
|
+
}]
|
|
186
|
+
}], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], scrollHideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollHideDelay", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }] } });
|
|
187
|
+
|
|
188
|
+
const HIDE_NATIVE_SCROLLBARS_STYLE_ID = 'for-scroll-area-hide-native';
|
|
189
|
+
const HIDE_NATIVE_SCROLLBARS_CSS = `
|
|
190
|
+
[forScrollAreaViewport] {
|
|
191
|
+
overflow: scroll;
|
|
192
|
+
-ms-overflow-style: none;
|
|
193
|
+
scrollbar-width: none;
|
|
194
|
+
}
|
|
195
|
+
[forScrollAreaViewport]::-webkit-scrollbar {
|
|
196
|
+
display: none;
|
|
197
|
+
width: 0;
|
|
198
|
+
height: 0;
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
function injectHidingStylesOnce(doc) {
|
|
202
|
+
if (doc.getElementById(HIDE_NATIVE_SCROLLBARS_STYLE_ID))
|
|
203
|
+
return;
|
|
204
|
+
const style = doc.createElement('style');
|
|
205
|
+
style.id = HIDE_NATIVE_SCROLLBARS_STYLE_ID;
|
|
206
|
+
style.textContent = HIDE_NATIVE_SCROLLBARS_CSS;
|
|
207
|
+
doc.head.appendChild(style);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The actually-scrolling element. Hides native scrollbars (the only place
|
|
211
|
+
* in forty-cdk that ships CSS — see README), tracks scroll position, and
|
|
212
|
+
* reports geometry to the root so the synthetic scrollbar can render.
|
|
213
|
+
*/
|
|
214
|
+
class ForScrollAreaViewport {
|
|
215
|
+
#ctx = injectScrollAreaContext('ForScrollAreaViewport');
|
|
216
|
+
#host = inject(ElementRef).nativeElement;
|
|
217
|
+
#document = inject(DOCUMENT);
|
|
218
|
+
#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
219
|
+
constructor() {
|
|
220
|
+
if (this.#isBrowser) {
|
|
221
|
+
injectHidingStylesOnce(this.#document);
|
|
222
|
+
}
|
|
223
|
+
this.#ctx.registerViewport(this.#host);
|
|
224
|
+
inject(DestroyRef).onDestroy(() => this.#ctx.registerViewport(null));
|
|
225
|
+
afterNextRender(() => {
|
|
226
|
+
this.#syncScroll();
|
|
227
|
+
this.#syncSize();
|
|
228
|
+
});
|
|
229
|
+
// Track size changes via ResizeObserver — simpler / lighter than
|
|
230
|
+
// polling, and consistent with the rest of the library's reactive
|
|
231
|
+
// patterns. Browser-only API; no-op on server.
|
|
232
|
+
//
|
|
233
|
+
// The content element is observed only when the consumer explicitly
|
|
234
|
+
// marks it with `[forScrollAreaContent]`. Without that marker we skip
|
|
235
|
+
// content observation entirely (rather than guessing `firstElementChild`,
|
|
236
|
+
// which silently breaks on layered / multi-sibling layouts).
|
|
237
|
+
if (this.#isBrowser && typeof ResizeObserver !== 'undefined') {
|
|
238
|
+
const ro = new ResizeObserver(() => this.#syncSize());
|
|
239
|
+
ro.observe(this.#host);
|
|
240
|
+
inject(DestroyRef).onDestroy(() => ro.disconnect());
|
|
241
|
+
// Re-attach the observer whenever the registered content element
|
|
242
|
+
// changes (registers, unregisters, or swaps). `effect` is the right
|
|
243
|
+
// primitive here: this is a DOM imperative side effect, not derived
|
|
244
|
+
// state — see CLAUDE.md "Never propagate state inside `effect()`".
|
|
245
|
+
let observedContent = null;
|
|
246
|
+
effect(() => {
|
|
247
|
+
const next = this.#ctx.content();
|
|
248
|
+
if (next === observedContent)
|
|
249
|
+
return;
|
|
250
|
+
if (observedContent)
|
|
251
|
+
ro.unobserve(observedContent);
|
|
252
|
+
if (next)
|
|
253
|
+
ro.observe(next);
|
|
254
|
+
observedContent = next;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
onScroll() {
|
|
259
|
+
this.#syncScroll();
|
|
260
|
+
this.#ctx.noteUserScroll();
|
|
261
|
+
}
|
|
262
|
+
#syncScroll() {
|
|
263
|
+
this.#ctx.reportScroll(this.#host.scrollLeft, this.#host.scrollTop);
|
|
264
|
+
}
|
|
265
|
+
#syncSize() {
|
|
266
|
+
this.#ctx.reportSize(this.#host.clientWidth, this.#host.clientHeight, this.#host.scrollWidth, this.#host.scrollHeight);
|
|
267
|
+
this.#syncScroll();
|
|
268
|
+
}
|
|
269
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaViewport, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
270
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForScrollAreaViewport, isStandalone: true, selector: "[forScrollAreaViewport]", host: { listeners: { "scroll": "onScroll()" } }, exportAs: ["forScrollAreaViewport"], ngImport: i0 });
|
|
271
|
+
}
|
|
272
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaViewport, decorators: [{
|
|
273
|
+
type: Directive,
|
|
274
|
+
args: [{
|
|
275
|
+
selector: '[forScrollAreaViewport]',
|
|
276
|
+
exportAs: 'forScrollAreaViewport',
|
|
277
|
+
host: {
|
|
278
|
+
'(scroll)': 'onScroll()',
|
|
279
|
+
},
|
|
280
|
+
}]
|
|
281
|
+
}], ctorParameters: () => [] });
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Marks the content element inside `[forScrollAreaViewport]`. The viewport
|
|
285
|
+
* observes this element with a `ResizeObserver` so `scrollWidth` /
|
|
286
|
+
* `scrollHeight` updates when content reflows.
|
|
287
|
+
*
|
|
288
|
+
* Required for the synthetic scrollbar to react to content resizes — without
|
|
289
|
+
* it, the viewport falls back to no observation and scrollbars stay sized to
|
|
290
|
+
* whatever the layout reported on first render.
|
|
291
|
+
*/
|
|
292
|
+
class ForScrollAreaContent {
|
|
293
|
+
#ctx = injectScrollAreaContext('ForScrollAreaContent');
|
|
294
|
+
#host = inject(ElementRef).nativeElement;
|
|
295
|
+
constructor() {
|
|
296
|
+
this.#ctx.registerContent(this.#host);
|
|
297
|
+
inject(DestroyRef).onDestroy(() => this.#ctx.unregisterContent(this.#host));
|
|
298
|
+
}
|
|
299
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
300
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForScrollAreaContent, isStandalone: true, selector: "[forScrollAreaContent]", exportAs: ["forScrollAreaContent"], ngImport: i0 });
|
|
301
|
+
}
|
|
302
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaContent, decorators: [{
|
|
303
|
+
type: Directive,
|
|
304
|
+
args: [{
|
|
305
|
+
selector: '[forScrollAreaContent]',
|
|
306
|
+
exportAs: 'forScrollAreaContent',
|
|
307
|
+
}]
|
|
308
|
+
}], ctorParameters: () => [] });
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Synthetic scrollbar track. Reflects `data-orientation`, `data-state`
|
|
312
|
+
* (`visible` / `hidden`), and exposes its host element via the orientation
|
|
313
|
+
* input so the thumb can compute geometry.
|
|
314
|
+
*
|
|
315
|
+
* The element is fully removed (`hidden`) when the corresponding axis has
|
|
316
|
+
* no overflow *and* `type` is not `'always'` — there is no scrollbar to
|
|
317
|
+
* render. Under `type="always"` the track stays painted regardless of
|
|
318
|
+
* overflow, so the consumer's reserved gutter is never empty.
|
|
319
|
+
* Visibility is enforced with an inline `display: none` (which beats any
|
|
320
|
+
* author `display` rule a consumer applies via a class) in addition to the
|
|
321
|
+
* `hidden` attribute that removes it from the a11y tree.
|
|
322
|
+
*/
|
|
323
|
+
class ForScrollAreaScrollbar {
|
|
324
|
+
orientation = input.required(/* @ts-ignore */
|
|
325
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
326
|
+
ctx = injectScrollAreaContext('ForScrollAreaScrollbar');
|
|
327
|
+
host = inject(ElementRef).nativeElement;
|
|
328
|
+
/**
|
|
329
|
+
* Reactive size of the track itself, measured independently of the viewport.
|
|
330
|
+
* The thumb's size and travel are computed against the *track* length, which
|
|
331
|
+
* the consumer is free to lay out shorter than the viewport (e.g. insets, a
|
|
332
|
+
* reserved corner, padding), so the viewport's reported client dimensions are
|
|
333
|
+
* not a substitute — the track must be observed directly.
|
|
334
|
+
*
|
|
335
|
+
* The target is a constant (`this.host` never changes), so `injectElementSize`'s
|
|
336
|
+
* target-swap branch is dead weight here; it is reused only for its
|
|
337
|
+
* `ResizeObserver` plumbing and change-deduped emission.
|
|
338
|
+
*/
|
|
339
|
+
#hostRef = signal(this.host, /* @ts-ignore */
|
|
340
|
+
...(ngDevMode ? [{ debugName: "#hostRef" }] : /* istanbul ignore next */ []));
|
|
341
|
+
size = injectElementSize(this.#hostRef);
|
|
342
|
+
#dragging = signal(false, /* @ts-ignore */
|
|
343
|
+
...(ngDevMode ? [{ debugName: "#dragging" }] : /* istanbul ignore next */ []));
|
|
344
|
+
/** True while the thumb is being dragged. Pins the track visible / painted. */
|
|
345
|
+
dragging = this.#dragging.asReadonly();
|
|
346
|
+
/**
|
|
347
|
+
* Marks the track as actively dragged so an in-flight drag is never aborted
|
|
348
|
+
* by the scrollbar self-hiding (`type="hover"` / `"scroll"` fading the track,
|
|
349
|
+
* or a consumer `display:none` on `data-state="hidden"`). Called by the thumb
|
|
350
|
+
* on pointer-down / drag-end.
|
|
351
|
+
*/
|
|
352
|
+
setDragging(dragging) {
|
|
353
|
+
this.#dragging.set(dragging);
|
|
354
|
+
}
|
|
355
|
+
hasOverflow = computed(() => {
|
|
356
|
+
if (this.orientation() === 'horizontal') {
|
|
357
|
+
return this.ctx.scrollWidth() - this.ctx.clientWidth() > 1;
|
|
358
|
+
}
|
|
359
|
+
return this.ctx.scrollHeight() - this.ctx.clientHeight() > 1;
|
|
360
|
+
}, /* @ts-ignore */
|
|
361
|
+
...(ngDevMode ? [{ debugName: "hasOverflow" }] : /* istanbul ignore next */ []));
|
|
362
|
+
/**
|
|
363
|
+
* Whether the track is rendered at all. `'always'` keeps it painted
|
|
364
|
+
* unconditionally (a stable, always-present track); every
|
|
365
|
+
* other `type` paints only the axis that actually overflows. An in-flight
|
|
366
|
+
* thumb drag also pins it painted so the gesture is never aborted by the
|
|
367
|
+
* track self-removing. Gates both the `hidden` attribute and the inline
|
|
368
|
+
* `display: none` self-removal.
|
|
369
|
+
*/
|
|
370
|
+
painted = computed(() => this.dragging() || this.ctx.type() === 'always' || this.hasOverflow(), /* @ts-ignore */
|
|
371
|
+
...(ngDevMode ? [{ debugName: "painted" }] : /* istanbul ignore next */ []));
|
|
372
|
+
/**
|
|
373
|
+
* `'visible' | 'hidden'`. An in-flight thumb drag forces `'visible'` so a
|
|
374
|
+
* consumer fade on `data-state="hidden"` can't hide the track mid-gesture.
|
|
375
|
+
* Otherwise: `'always'` resolves to `'visible'` regardless of overflow — the
|
|
376
|
+
* track is permanently present. `'auto'` shows whenever the axis overflows;
|
|
377
|
+
* `'hover'` / `'scroll'` additionally gate on the interaction signals. A
|
|
378
|
+
* non-overflowing axis is `'hidden'` for every mode except `'always'`.
|
|
379
|
+
*/
|
|
380
|
+
state = computed(() => {
|
|
381
|
+
if (this.dragging())
|
|
382
|
+
return 'visible';
|
|
383
|
+
switch (this.ctx.type()) {
|
|
384
|
+
case 'always':
|
|
385
|
+
return 'visible';
|
|
386
|
+
case 'auto':
|
|
387
|
+
return this.hasOverflow() ? 'visible' : 'hidden';
|
|
388
|
+
case 'hover':
|
|
389
|
+
return this.hasOverflow() && (this.ctx.hovering() || this.ctx.scrolling())
|
|
390
|
+
? 'visible'
|
|
391
|
+
: 'hidden';
|
|
392
|
+
case 'scroll':
|
|
393
|
+
return this.hasOverflow() && this.ctx.scrolling() ? 'visible' : 'hidden';
|
|
394
|
+
}
|
|
395
|
+
}, /* @ts-ignore */
|
|
396
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
397
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaScrollbar, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
398
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForScrollAreaScrollbar, isStandalone: true, selector: "[forScrollAreaScrollbar]", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-orientation": "orientation()", "attr.data-state": "state()", "hidden": "!painted()", "style.display": "painted() ? null : \"none\"" } }, exportAs: ["forScrollAreaScrollbar"], ngImport: i0 });
|
|
399
|
+
}
|
|
400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaScrollbar, decorators: [{
|
|
401
|
+
type: Directive,
|
|
402
|
+
args: [{
|
|
403
|
+
selector: '[forScrollAreaScrollbar]',
|
|
404
|
+
exportAs: 'forScrollAreaScrollbar',
|
|
405
|
+
host: {
|
|
406
|
+
'[attr.data-orientation]': 'orientation()',
|
|
407
|
+
'[attr.data-state]': 'state()',
|
|
408
|
+
'[hidden]': '!painted()',
|
|
409
|
+
'[style.display]': 'painted() ? null : "none"',
|
|
410
|
+
},
|
|
411
|
+
}]
|
|
412
|
+
}], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: true }] }] } });
|
|
413
|
+
|
|
414
|
+
const MIN_THUMB_SIZE = 8;
|
|
415
|
+
function injectRequiredScrollbar() {
|
|
416
|
+
const scrollbar = inject(ForScrollAreaScrollbar, { optional: true });
|
|
417
|
+
if (!scrollbar) {
|
|
418
|
+
throw new Error('[forty-cdk/scroll-area] ForScrollAreaThumb must be used inside a [forScrollAreaScrollbar] element.');
|
|
419
|
+
}
|
|
420
|
+
return scrollbar;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* The draggable thumb inside a `[forScrollAreaScrollbar]`. Sizes itself
|
|
424
|
+
* proportionally to viewport / content and translates along the track
|
|
425
|
+
* based on `scrollLeft` / `scrollTop`. Pointer-drag scrolls the viewport
|
|
426
|
+
* proportionally.
|
|
427
|
+
*/
|
|
428
|
+
class ForScrollAreaThumb {
|
|
429
|
+
scrollbar = injectRequiredScrollbar();
|
|
430
|
+
#host = inject(ElementRef).nativeElement;
|
|
431
|
+
#document = inject(DOCUMENT);
|
|
432
|
+
#dragging = signal(false, /* @ts-ignore */
|
|
433
|
+
...(ngDevMode ? [{ debugName: "#dragging" }] : /* istanbul ignore next */ []));
|
|
434
|
+
#dragStartPointer = 0;
|
|
435
|
+
#dragStartScroll = 0;
|
|
436
|
+
constructor() {
|
|
437
|
+
inject(DestroyRef).onDestroy(() => this.#endDrag());
|
|
438
|
+
}
|
|
439
|
+
/** Track length in CSS pixels (reactive — derived from the scrollbar's measured size). */
|
|
440
|
+
#trackLength = computed(() => {
|
|
441
|
+
const size = this.scrollbar.size();
|
|
442
|
+
if (!size)
|
|
443
|
+
return 0;
|
|
444
|
+
return this.scrollbar.orientation() === 'horizontal' ? size.width : size.height;
|
|
445
|
+
}, /* @ts-ignore */
|
|
446
|
+
...(ngDevMode ? [{ debugName: "#trackLength" }] : /* istanbul ignore next */ []));
|
|
447
|
+
#ratio = computed(() => {
|
|
448
|
+
const ctx = this.scrollbar.ctx;
|
|
449
|
+
if (this.scrollbar.orientation() === 'horizontal') {
|
|
450
|
+
const sw = ctx.scrollWidth();
|
|
451
|
+
if (sw <= 0)
|
|
452
|
+
return 0;
|
|
453
|
+
return Math.min(1, ctx.clientWidth() / sw);
|
|
454
|
+
}
|
|
455
|
+
const sh = ctx.scrollHeight();
|
|
456
|
+
if (sh <= 0)
|
|
457
|
+
return 0;
|
|
458
|
+
return Math.min(1, ctx.clientHeight() / sh);
|
|
459
|
+
}, /* @ts-ignore */
|
|
460
|
+
...(ngDevMode ? [{ debugName: "#ratio" }] : /* istanbul ignore next */ []));
|
|
461
|
+
#thumbSize = computed(() => {
|
|
462
|
+
const tl = this.#trackLength();
|
|
463
|
+
const r = this.#ratio();
|
|
464
|
+
if (tl === 0 || r === 0)
|
|
465
|
+
return 0;
|
|
466
|
+
return Math.max(MIN_THUMB_SIZE, Math.floor(tl * r));
|
|
467
|
+
}, /* @ts-ignore */
|
|
468
|
+
...(ngDevMode ? [{ debugName: "#thumbSize" }] : /* istanbul ignore next */ []));
|
|
469
|
+
#thumbOffset = computed(() => {
|
|
470
|
+
const ctx = this.scrollbar.ctx;
|
|
471
|
+
const tl = this.#trackLength();
|
|
472
|
+
const tsz = this.#thumbSize();
|
|
473
|
+
if (this.scrollbar.orientation() === 'horizontal') {
|
|
474
|
+
const max = ctx.scrollWidth() - ctx.clientWidth();
|
|
475
|
+
if (max <= 0)
|
|
476
|
+
return 0;
|
|
477
|
+
const left = this.#normalizeScrollLeft(ctx.scrollLeft(), max);
|
|
478
|
+
return (left / max) * (tl - tsz) || 0;
|
|
479
|
+
}
|
|
480
|
+
const max = ctx.scrollHeight() - ctx.clientHeight();
|
|
481
|
+
if (max <= 0)
|
|
482
|
+
return 0;
|
|
483
|
+
return (ctx.scrollTop() / max) * (tl - tsz) || 0;
|
|
484
|
+
}, /* @ts-ignore */
|
|
485
|
+
...(ngDevMode ? [{ debugName: "#thumbOffset" }] : /* istanbul ignore next */ []));
|
|
486
|
+
/**
|
|
487
|
+
* Normalise the viewport's `scrollLeft` to a left-edge-origin value in
|
|
488
|
+
* `[0, max]`. In LTR `scrollLeft` is already left-origin. In RTL the browser
|
|
489
|
+
* reports `scrollLeft` in the negative model (`0` at rest with the content's
|
|
490
|
+
* right edge flush, `-max` when scrolled fully left), so a left-origin
|
|
491
|
+
* position is `scrollLeft + max` — `max` at rest (thumb pinned to the right)
|
|
492
|
+
* down to `0` when scrolled forward.
|
|
493
|
+
*/
|
|
494
|
+
#normalizeScrollLeft(scrollLeft, max) {
|
|
495
|
+
return this.scrollbar.ctx.dir() === 'rtl' ? scrollLeft + max : scrollLeft;
|
|
496
|
+
}
|
|
497
|
+
widthPx() {
|
|
498
|
+
return this.scrollbar.orientation() === 'horizontal' ? this.#thumbSize() : null;
|
|
499
|
+
}
|
|
500
|
+
heightPx() {
|
|
501
|
+
return this.scrollbar.orientation() === 'vertical' ? this.#thumbSize() : null;
|
|
502
|
+
}
|
|
503
|
+
leftPx() {
|
|
504
|
+
return this.scrollbar.orientation() === 'horizontal' ? 0 : null;
|
|
505
|
+
}
|
|
506
|
+
topPx() {
|
|
507
|
+
return this.scrollbar.orientation() === 'vertical' ? 0 : null;
|
|
508
|
+
}
|
|
509
|
+
transformValue() {
|
|
510
|
+
if (this.scrollbar.orientation() === 'horizontal') {
|
|
511
|
+
return `translateX(${this.#thumbOffset()}px)`;
|
|
512
|
+
}
|
|
513
|
+
return `translateY(${this.#thumbOffset()}px)`;
|
|
514
|
+
}
|
|
515
|
+
onPointerDown(event) {
|
|
516
|
+
if (event.button !== 0)
|
|
517
|
+
return;
|
|
518
|
+
event.preventDefault();
|
|
519
|
+
this.#dragging.set(true);
|
|
520
|
+
this.scrollbar.setDragging(true);
|
|
521
|
+
this.#host.setPointerCapture(event.pointerId);
|
|
522
|
+
this.#dragStartPointer =
|
|
523
|
+
this.scrollbar.orientation() === 'horizontal' ? event.clientX : event.clientY;
|
|
524
|
+
const ctx = this.scrollbar.ctx;
|
|
525
|
+
this.#dragStartScroll =
|
|
526
|
+
this.scrollbar.orientation() === 'horizontal' ? ctx.scrollLeft() : ctx.scrollTop();
|
|
527
|
+
// Listen on the owner document, not the thumb element: pointer capture is
|
|
528
|
+
// set on the thumb (so the gesture stays bound to this pointer), but the
|
|
529
|
+
// captured node can be removed mid-drag when the scrollbar self-hides
|
|
530
|
+
// (`type="hover"` / `"scroll"`). Document-level listeners keep firing after
|
|
531
|
+
// that removal, so the drag is not silently aborted. `lostpointercapture`
|
|
532
|
+
// tears the gesture down if capture is otherwise lost.
|
|
533
|
+
const doc = this.#document;
|
|
534
|
+
doc.addEventListener('pointermove', this.#onPointerMove);
|
|
535
|
+
doc.addEventListener('pointerup', this.#onPointerUp);
|
|
536
|
+
doc.addEventListener('pointercancel', this.#onPointerUp);
|
|
537
|
+
}
|
|
538
|
+
onLostPointerCapture() {
|
|
539
|
+
this.#endDrag();
|
|
540
|
+
}
|
|
541
|
+
#onPointerMove = (event) => {
|
|
542
|
+
if (!this.#dragging())
|
|
543
|
+
return;
|
|
544
|
+
const ctx = this.scrollbar.ctx;
|
|
545
|
+
const viewport = ctx.viewport();
|
|
546
|
+
if (!viewport)
|
|
547
|
+
return;
|
|
548
|
+
const tl = this.#trackLength();
|
|
549
|
+
const tsz = this.#thumbSize();
|
|
550
|
+
if (tl - tsz <= 0)
|
|
551
|
+
return;
|
|
552
|
+
if (this.scrollbar.orientation() === 'horizontal') {
|
|
553
|
+
const delta = event.clientX - this.#dragStartPointer;
|
|
554
|
+
const scrollMax = ctx.scrollWidth() - ctx.clientWidth();
|
|
555
|
+
const scrollDelta = (delta / (tl - tsz)) * scrollMax;
|
|
556
|
+
viewport.scrollLeft = this.#dragStartScroll + scrollDelta;
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const delta = event.clientY - this.#dragStartPointer;
|
|
560
|
+
const scrollMax = ctx.scrollHeight() - ctx.clientHeight();
|
|
561
|
+
const scrollDelta = (delta / (tl - tsz)) * scrollMax;
|
|
562
|
+
viewport.scrollTop = this.#dragStartScroll + scrollDelta;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
#onPointerUp = (event) => {
|
|
566
|
+
this.#endDrag();
|
|
567
|
+
if (this.#host.hasPointerCapture(event.pointerId)) {
|
|
568
|
+
this.#host.releasePointerCapture(event.pointerId);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
#endDrag() {
|
|
572
|
+
this.#dragging.set(false);
|
|
573
|
+
this.scrollbar.setDragging(false);
|
|
574
|
+
const doc = this.#document;
|
|
575
|
+
doc.removeEventListener('pointermove', this.#onPointerMove);
|
|
576
|
+
doc.removeEventListener('pointerup', this.#onPointerUp);
|
|
577
|
+
doc.removeEventListener('pointercancel', this.#onPointerUp);
|
|
578
|
+
}
|
|
579
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaThumb, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
580
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForScrollAreaThumb, isStandalone: true, selector: "[forScrollAreaThumb]", host: { listeners: { "pointerdown": "onPointerDown($event)", "lostpointercapture": "onLostPointerCapture()" }, properties: { "style.position": "\"absolute\"", "style.width.px": "widthPx()", "style.height.px": "heightPx()", "style.left.px": "leftPx()", "style.top.px": "topPx()", "style.transform": "transformValue()", "attr.data-orientation": "scrollbar.orientation()", "attr.data-state": "scrollbar.state()" } }, exportAs: ["forScrollAreaThumb"], ngImport: i0 });
|
|
581
|
+
}
|
|
582
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaThumb, decorators: [{
|
|
583
|
+
type: Directive,
|
|
584
|
+
args: [{
|
|
585
|
+
selector: '[forScrollAreaThumb]',
|
|
586
|
+
exportAs: 'forScrollAreaThumb',
|
|
587
|
+
host: {
|
|
588
|
+
'[style.position]': '"absolute"',
|
|
589
|
+
'[style.width.px]': 'widthPx()',
|
|
590
|
+
'[style.height.px]': 'heightPx()',
|
|
591
|
+
'[style.left.px]': 'leftPx()',
|
|
592
|
+
'[style.top.px]': 'topPx()',
|
|
593
|
+
'[style.transform]': 'transformValue()',
|
|
594
|
+
'[attr.data-orientation]': 'scrollbar.orientation()',
|
|
595
|
+
'[attr.data-state]': 'scrollbar.state()',
|
|
596
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
597
|
+
'(lostpointercapture)': 'onLostPointerCapture()',
|
|
598
|
+
},
|
|
599
|
+
}]
|
|
600
|
+
}], ctorParameters: () => [] });
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Filler in the corner where horizontal and vertical scrollbars meet.
|
|
604
|
+
* Shown when both axes overflow, or unconditionally under `type="always"`
|
|
605
|
+
* (both tracks are permanently present then). Visibility is enforced with an
|
|
606
|
+
* inline `display: none` (which beats any author `display` rule a consumer
|
|
607
|
+
* applies via a class) in addition to the `hidden` attribute that removes it
|
|
608
|
+
* from the a11y tree.
|
|
609
|
+
*/
|
|
610
|
+
class ForScrollAreaCorner {
|
|
611
|
+
ctx = injectScrollAreaContext('ForScrollAreaCorner');
|
|
612
|
+
visible = computed(() => {
|
|
613
|
+
if (this.ctx.type() === 'always')
|
|
614
|
+
return true;
|
|
615
|
+
const hOverflow = this.ctx.scrollWidth() - this.ctx.clientWidth() > 1;
|
|
616
|
+
const vOverflow = this.ctx.scrollHeight() - this.ctx.clientHeight() > 1;
|
|
617
|
+
return hOverflow && vOverflow;
|
|
618
|
+
}, /* @ts-ignore */
|
|
619
|
+
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
620
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaCorner, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
621
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForScrollAreaCorner, isStandalone: true, selector: "[forScrollAreaCorner]", host: { properties: { "hidden": "!visible()", "style.display": "visible() ? null : \"none\"" } }, exportAs: ["forScrollAreaCorner"], ngImport: i0 });
|
|
622
|
+
}
|
|
623
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForScrollAreaCorner, decorators: [{
|
|
624
|
+
type: Directive,
|
|
625
|
+
args: [{
|
|
626
|
+
selector: '[forScrollAreaCorner]',
|
|
627
|
+
exportAs: 'forScrollAreaCorner',
|
|
628
|
+
host: {
|
|
629
|
+
'[hidden]': '!visible()',
|
|
630
|
+
'[style.display]': 'visible() ? null : "none"',
|
|
631
|
+
},
|
|
632
|
+
}]
|
|
633
|
+
}] });
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Generated bundle index. Do not edit.
|
|
637
|
+
*/
|
|
638
|
+
|
|
639
|
+
export { FOR_SCROLL_AREA_CONTEXT, FOR_SCROLL_AREA_DEFAULTS, ForScrollArea, ForScrollAreaContent, ForScrollAreaCorner, ForScrollAreaScrollbar, ForScrollAreaThumb, ForScrollAreaViewport, provideForScrollAreaDefaults };
|
|
640
|
+
//# sourceMappingURL=forty-cdk-scroll-area.mjs.map
|