@acorex/cdk 22.2.0-next.0 → 22.2.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/acorex-cdk-common.mjs +77 -16
- package/fesm2022/acorex-cdk-common.mjs.map +1 -1
- package/fesm2022/acorex-cdk-drawer.mjs +4 -2
- package/fesm2022/acorex-cdk-drawer.mjs.map +1 -1
- package/fesm2022/acorex-cdk-horizontal-scroll.mjs +11 -5
- package/fesm2022/acorex-cdk-horizontal-scroll.mjs.map +1 -1
- package/fesm2022/acorex-cdk-list-navigation.mjs +11 -0
- package/fesm2022/acorex-cdk-list-navigation.mjs.map +1 -1
- package/fesm2022/acorex-cdk-overlay-scrollbar.mjs +903 -0
- package/fesm2022/acorex-cdk-overlay-scrollbar.mjs.map +1 -0
- package/fesm2022/acorex-cdk-overlay.mjs +35 -4
- package/fesm2022/acorex-cdk-overlay.mjs.map +1 -1
- package/fesm2022/acorex-cdk-text-editor.mjs +942 -0
- package/fesm2022/acorex-cdk-text-editor.mjs.map +1 -0
- package/fesm2022/acorex-cdk-wysiwyg.mjs +5 -0
- package/fesm2022/acorex-cdk-wysiwyg.mjs.map +1 -1
- package/overlay-scrollbar/README.md +54 -0
- package/package.json +12 -2
- package/text-editor/README.md +5 -0
- package/types/acorex-cdk-common.d.ts +10 -0
- package/types/acorex-cdk-list-navigation.d.ts +5 -0
- package/types/acorex-cdk-overlay-scrollbar.d.ts +155 -0
- package/types/acorex-cdk-text-editor.d.ts +156 -0
- package/types/acorex-cdk-wysiwyg.d.ts +3 -0
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import { isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, inject, PLATFORM_ID, ElementRef, DestroyRef, input, afterRenderEffect, effect, afterNextRender, Directive } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
/** CSS custom properties for overlay scrollbar styling (set on the scroll host or in CSS). */
|
|
6
|
+
const AX_OVERLAY_SCROLLBAR_CSS_VARS = {
|
|
7
|
+
size: '--ax-comp-overlay-scrollbar-size',
|
|
8
|
+
thumbRadius: '--ax-comp-overlay-scrollbar-thumb-radius',
|
|
9
|
+
trackRadius: '--ax-comp-overlay-scrollbar-track-radius',
|
|
10
|
+
trackColor: '--ax-comp-overlay-scrollbar-track-color',
|
|
11
|
+
thumbColor: '--ax-comp-overlay-scrollbar-thumb-color',
|
|
12
|
+
thumbActiveColor: '--ax-comp-overlay-scrollbar-thumb-active-color',
|
|
13
|
+
thumbHoverColor: '--ax-comp-overlay-scrollbar-thumb-hover-color',
|
|
14
|
+
trackActiveColor: '--ax-comp-overlay-scrollbar-track-active-color',
|
|
15
|
+
thumbBorderWidth: '--ax-comp-overlay-scrollbar-thumb-border-width',
|
|
16
|
+
thumbBorderColor: '--ax-comp-overlay-scrollbar-thumb-border-color',
|
|
17
|
+
trackBorderWidth: '--ax-comp-overlay-scrollbar-track-border-width',
|
|
18
|
+
trackBorderColor: '--ax-comp-overlay-scrollbar-track-border-color',
|
|
19
|
+
fadeDuration: '--ax-comp-overlay-scrollbar-fade-duration',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Default delay (ms) before the overlay scrollbar fades out after scroll. */
|
|
23
|
+
const AX_OVERLAY_SCROLLBAR_DEFAULT_HIDE_DELAY_MS = 800;
|
|
24
|
+
/** Default pixels scrolled per navigation arrow click. */
|
|
25
|
+
const AX_OVERLAY_SCROLLBAR_DEFAULT_NAV_SCROLL_STEP_PX = 40;
|
|
26
|
+
const AXOverlayScrollbarDefaultConfig = {
|
|
27
|
+
hideDelay: AX_OVERLAY_SCROLLBAR_DEFAULT_HIDE_DELAY_MS,
|
|
28
|
+
navigationButtons: false,
|
|
29
|
+
navScrollStep: AX_OVERLAY_SCROLLBAR_DEFAULT_NAV_SCROLL_STEP_PX,
|
|
30
|
+
};
|
|
31
|
+
const AX_OVERLAY_SCROLLBAR_CONFIG = new InjectionToken('AX_OVERLAY_SCROLLBAR_CONFIG', {
|
|
32
|
+
providedIn: 'root',
|
|
33
|
+
factory: () => AXOverlayScrollbarDefaultConfig,
|
|
34
|
+
});
|
|
35
|
+
const DESIGN_TOKEN_KEYS = {
|
|
36
|
+
size: AX_OVERLAY_SCROLLBAR_CSS_VARS.size,
|
|
37
|
+
thumbRadius: AX_OVERLAY_SCROLLBAR_CSS_VARS.thumbRadius,
|
|
38
|
+
trackRadius: AX_OVERLAY_SCROLLBAR_CSS_VARS.trackRadius,
|
|
39
|
+
trackColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.trackColor,
|
|
40
|
+
thumbColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.thumbColor,
|
|
41
|
+
thumbActiveColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.thumbActiveColor,
|
|
42
|
+
thumbHoverColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.thumbHoverColor,
|
|
43
|
+
trackActiveColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.trackActiveColor,
|
|
44
|
+
thumbBorderWidth: AX_OVERLAY_SCROLLBAR_CSS_VARS.thumbBorderWidth,
|
|
45
|
+
thumbBorderColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.thumbBorderColor,
|
|
46
|
+
trackBorderWidth: AX_OVERLAY_SCROLLBAR_CSS_VARS.trackBorderWidth,
|
|
47
|
+
trackBorderColor: AX_OVERLAY_SCROLLBAR_CSS_VARS.trackBorderColor,
|
|
48
|
+
fadeDuration: AX_OVERLAY_SCROLLBAR_CSS_VARS.fadeDuration,
|
|
49
|
+
};
|
|
50
|
+
/** Merges partial config with {@link AXOverlayScrollbarDefaultConfig}. */
|
|
51
|
+
function overlayScrollbarConfig(config = {}) {
|
|
52
|
+
const { designTokens, ...rest } = config;
|
|
53
|
+
return {
|
|
54
|
+
...AXOverlayScrollbarDefaultConfig,
|
|
55
|
+
...rest,
|
|
56
|
+
designTokens: designTokens ? { ...designTokens } : undefined,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Sets overlay scrollbar CSS variables on the given element. */
|
|
60
|
+
function applyOverlayScrollbarDesignTokens(element, tokens) {
|
|
61
|
+
if (!tokens) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const key of Object.keys(DESIGN_TOKEN_KEYS)) {
|
|
65
|
+
const value = tokens[key];
|
|
66
|
+
if (value != null && value !== '') {
|
|
67
|
+
element.style.setProperty(DESIGN_TOKEN_KEYS[key], value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Registers global overlay scrollbar defaults for the application.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* export const appConfig: ApplicationConfig = {
|
|
77
|
+
* providers: [
|
|
78
|
+
* provideOverlayScrollbar({
|
|
79
|
+
* hideDelay: 1200,
|
|
80
|
+
* navigationButtons: true,
|
|
81
|
+
* navScrollStep: 48,
|
|
82
|
+
* designTokens: {
|
|
83
|
+
* size: '8px',
|
|
84
|
+
* thumbColor: 'var(--ax-sys-scroller-thumb-color)',
|
|
85
|
+
* },
|
|
86
|
+
* }),
|
|
87
|
+
* ],
|
|
88
|
+
* };
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
function provideOverlayScrollbar(config = {}) {
|
|
92
|
+
const merged = overlayScrollbarConfig(config);
|
|
93
|
+
return makeEnvironmentProviders([
|
|
94
|
+
{
|
|
95
|
+
provide: AX_OVERLAY_SCROLLBAR_CONFIG,
|
|
96
|
+
useValue: merged,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
100
|
+
multi: true,
|
|
101
|
+
useValue: () => {
|
|
102
|
+
if (!merged.designTokens) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const platformId = inject(PLATFORM_ID);
|
|
106
|
+
if (!isPlatformBrowser(platformId)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
applyOverlayScrollbarDesignTokens(inject(DOCUMENT).documentElement, merged.designTokens);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const OVERLAY_SCROLLBAR_TOKEN_NAMES = Object.values(AX_OVERLAY_SCROLLBAR_CSS_VARS);
|
|
116
|
+
const ROOT_CLASS = 'ax-cdk-overlay-scrollbar-root';
|
|
117
|
+
const RAIL_CLASS = 'ax-cdk-overlay-scrollbar__rail';
|
|
118
|
+
const TRACK_CLASS = 'ax-cdk-overlay-scrollbar__rail-track';
|
|
119
|
+
const THUMB_CLASS = 'ax-cdk-overlay-scrollbar__rail-thumb';
|
|
120
|
+
const NAV_BTN_CLASS = 'ax-cdk-overlay-scrollbar__nav-button';
|
|
121
|
+
const NAV_BTN_PRESSED_CLASS = 'ax-cdk-overlay-scrollbar__nav-button--pressed';
|
|
122
|
+
const NAV_ARROW_CLASS = 'ax-cdk-overlay-scrollbar__nav-arrow';
|
|
123
|
+
const RAIL_WITH_NAV_CLASS = 'ax-cdk-overlay-scrollbar__rail--with-nav';
|
|
124
|
+
const VISIBLE_CLASS = 'ax-cdk-overlay-scrollbar--active';
|
|
125
|
+
/** Applied while dragging the thumb (active fill; not used during wheel/content scroll). */
|
|
126
|
+
const ENGAGED_CLASS = 'ax-cdk-overlay-scrollbar--engaged';
|
|
127
|
+
/** Primary button down on the track (outside thumb); `:active` is unreliable with `preventDefault`. */
|
|
128
|
+
const TRACK_PRESSED_CLASS = 'ax-cdk-overlay-scrollbar--track-pressed';
|
|
129
|
+
const OVERLAY_SCROLLBAR_STYLES = `
|
|
130
|
+
.ax-cdk-overlay-scrollbar-root {
|
|
131
|
+
position: relative;
|
|
132
|
+
display: block;
|
|
133
|
+
min-height: 0;
|
|
134
|
+
--ax-comp-overlay-scrollbar-size: 6px;
|
|
135
|
+
--ax-comp-overlay-scrollbar-fade-duration: 0.35s;
|
|
136
|
+
--ax-comp-overlay-scrollbar-thumb-radius: 4px;
|
|
137
|
+
--ax-comp-overlay-scrollbar-track-radius: 4px;
|
|
138
|
+
--ax-comp-overlay-scrollbar-track-color: transparent;
|
|
139
|
+
--ax-comp-overlay-scrollbar-thumb-color: var(--ax-sys-scroller-thumb-color, #64748b);
|
|
140
|
+
--ax-comp-overlay-scrollbar-thumb-border-width: 0;
|
|
141
|
+
--ax-comp-overlay-scrollbar-thumb-border-color: transparent;
|
|
142
|
+
--ax-comp-overlay-scrollbar-track-border-width: 0;
|
|
143
|
+
--ax-comp-overlay-scrollbar-track-border-color: transparent;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ax-cdk-overlay-scrollbar {
|
|
147
|
+
position: relative;
|
|
148
|
+
--ax-comp-overlay-scrollbar-size: 6px;
|
|
149
|
+
--ax-comp-overlay-scrollbar-fade-duration: 0.35s;
|
|
150
|
+
--ax-comp-overlay-scrollbar-thumb-radius: 4px;
|
|
151
|
+
--ax-comp-overlay-scrollbar-track-radius: 4px;
|
|
152
|
+
--ax-comp-overlay-scrollbar-track-color: transparent;
|
|
153
|
+
--ax-comp-overlay-scrollbar-thumb-color: var(--ax-sys-scroller-thumb-color, #64748b);
|
|
154
|
+
--ax-comp-overlay-scrollbar-thumb-border-width: 0;
|
|
155
|
+
--ax-comp-overlay-scrollbar-thumb-border-color: transparent;
|
|
156
|
+
--ax-comp-overlay-scrollbar-track-border-width: 0;
|
|
157
|
+
--ax-comp-overlay-scrollbar-track-border-color: transparent;
|
|
158
|
+
|
|
159
|
+
scrollbar-width: none !important;
|
|
160
|
+
-ms-overflow-style: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.ax-cdk-overlay-scrollbar::-webkit-scrollbar {
|
|
164
|
+
display: none !important;
|
|
165
|
+
width: 0 !important;
|
|
166
|
+
height: 0 !important;
|
|
167
|
+
background: transparent !important;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.ax-cdk-overlay-scrollbar__rail {
|
|
171
|
+
position: absolute;
|
|
172
|
+
top: 0;
|
|
173
|
+
bottom: 0;
|
|
174
|
+
inset-inline-end: 0;
|
|
175
|
+
width: var(--ax-comp-overlay-scrollbar-size, 6px);
|
|
176
|
+
overflow: hidden;
|
|
177
|
+
pointer-events: auto;
|
|
178
|
+
opacity: 0;
|
|
179
|
+
z-index: 10;
|
|
180
|
+
box-sizing: border-box;
|
|
181
|
+
border-radius: var(--ax-comp-overlay-scrollbar-track-radius, 4px);
|
|
182
|
+
background-color: var(--ax-comp-overlay-scrollbar-track-color, transparent);
|
|
183
|
+
border-width: var(--ax-comp-overlay-scrollbar-track-border-width, 0);
|
|
184
|
+
border-style: solid;
|
|
185
|
+
border-color: var(--ax-comp-overlay-scrollbar-track-border-color, transparent);
|
|
186
|
+
transition:
|
|
187
|
+
opacity var(--ax-comp-overlay-scrollbar-fade-duration, 0.35s) ease,
|
|
188
|
+
background-color 0.15s ease;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.ax-cdk-overlay-scrollbar-root.ax-cdk-overlay-scrollbar--active .ax-cdk-overlay-scrollbar__rail,
|
|
192
|
+
.ax-cdk-overlay-scrollbar.ax-cdk-overlay-scrollbar--active .ax-cdk-overlay-scrollbar__rail {
|
|
193
|
+
opacity: 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ax-cdk-overlay-scrollbar__rail-track {
|
|
197
|
+
position: absolute;
|
|
198
|
+
inset: 0;
|
|
199
|
+
box-sizing: border-box;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ax-cdk-overlay-scrollbar-root.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--track-pressed
|
|
203
|
+
.ax-cdk-overlay-scrollbar__rail,
|
|
204
|
+
.ax-cdk-overlay-scrollbar.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--track-pressed
|
|
205
|
+
.ax-cdk-overlay-scrollbar__rail,
|
|
206
|
+
.ax-cdk-overlay-scrollbar-root.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--engaged
|
|
207
|
+
.ax-cdk-overlay-scrollbar__rail,
|
|
208
|
+
.ax-cdk-overlay-scrollbar.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--engaged
|
|
209
|
+
.ax-cdk-overlay-scrollbar__rail {
|
|
210
|
+
background-color: var(
|
|
211
|
+
--ax-comp-overlay-scrollbar-track-active-color,
|
|
212
|
+
var(--ax-comp-overlay-scrollbar-track-color, transparent)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.ax-cdk-overlay-scrollbar__rail--with-nav {
|
|
217
|
+
display: flex;
|
|
218
|
+
flex-direction: column;
|
|
219
|
+
overflow: hidden;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ax-cdk-overlay-scrollbar__rail--with-nav .ax-cdk-overlay-scrollbar__rail-track {
|
|
223
|
+
position: relative;
|
|
224
|
+
flex: 1 1 auto;
|
|
225
|
+
min-height: 0;
|
|
226
|
+
inset: auto;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ax-cdk-overlay-scrollbar__nav-button {
|
|
230
|
+
display: none;
|
|
231
|
+
flex: 0 0 var(--ax-comp-overlay-scrollbar-size, 6px);
|
|
232
|
+
width: 100%;
|
|
233
|
+
min-height: var(--ax-comp-overlay-scrollbar-size, 6px);
|
|
234
|
+
margin: 0;
|
|
235
|
+
padding: 0;
|
|
236
|
+
border: none;
|
|
237
|
+
box-sizing: border-box;
|
|
238
|
+
cursor: default;
|
|
239
|
+
align-items: center;
|
|
240
|
+
justify-content: center;
|
|
241
|
+
background-color: transparent;
|
|
242
|
+
color: var(--ax-comp-overlay-scrollbar-thumb-color, var(--ax-sys-scroller-thumb-color, #64748b));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.ax-cdk-overlay-scrollbar__rail--with-nav .ax-cdk-overlay-scrollbar__nav-button {
|
|
246
|
+
display: flex;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.ax-cdk-overlay-scrollbar__nav-arrow {
|
|
250
|
+
width: 0;
|
|
251
|
+
height: 0;
|
|
252
|
+
border-left: 3px solid transparent;
|
|
253
|
+
border-right: 3px solid transparent;
|
|
254
|
+
pointer-events: none;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.ax-cdk-overlay-scrollbar__nav-arrow--up {
|
|
258
|
+
border-bottom: 4px solid currentColor;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.ax-cdk-overlay-scrollbar__nav-arrow--down {
|
|
262
|
+
border-top: 4px solid currentColor;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.ax-cdk-overlay-scrollbar__rail-thumb {
|
|
266
|
+
position: absolute;
|
|
267
|
+
top: 0;
|
|
268
|
+
inset-inline-start: 0;
|
|
269
|
+
width: 100%;
|
|
270
|
+
box-sizing: border-box;
|
|
271
|
+
border-radius: var(--ax-comp-overlay-scrollbar-thumb-radius, 4px);
|
|
272
|
+
background-color: var(--ax-comp-overlay-scrollbar-thumb-color, var(--ax-sys-scroller-thumb-color, #64748b));
|
|
273
|
+
border-width: var(--ax-comp-overlay-scrollbar-thumb-border-width, 0);
|
|
274
|
+
border-style: solid;
|
|
275
|
+
border-color: var(--ax-comp-overlay-scrollbar-thumb-border-color, transparent);
|
|
276
|
+
transition: background-color 0.15s ease;
|
|
277
|
+
pointer-events: auto;
|
|
278
|
+
touch-action: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.ax-cdk-overlay-scrollbar__rail-thumb::before {
|
|
282
|
+
content: '';
|
|
283
|
+
position: absolute;
|
|
284
|
+
inset: -4px -8px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.ax-cdk-overlay-scrollbar-root.ax-cdk-overlay-scrollbar--active .ax-cdk-overlay-scrollbar__rail-thumb:hover,
|
|
288
|
+
.ax-cdk-overlay-scrollbar.ax-cdk-overlay-scrollbar--active .ax-cdk-overlay-scrollbar__rail-thumb:hover {
|
|
289
|
+
background-color: var(
|
|
290
|
+
--ax-comp-overlay-scrollbar-thumb-hover-color,
|
|
291
|
+
var(
|
|
292
|
+
--ax-comp-overlay-scrollbar-thumb-active-color,
|
|
293
|
+
var(--ax-comp-overlay-scrollbar-thumb-color, var(--ax-sys-scroller-thumb-color, #64748b))
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.ax-cdk-overlay-scrollbar-root.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--engaged
|
|
299
|
+
.ax-cdk-overlay-scrollbar__rail-thumb,
|
|
300
|
+
.ax-cdk-overlay-scrollbar.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--engaged
|
|
301
|
+
.ax-cdk-overlay-scrollbar__rail-thumb,
|
|
302
|
+
.ax-cdk-overlay-scrollbar-root.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--engaged
|
|
303
|
+
.ax-cdk-overlay-scrollbar__rail-thumb:hover,
|
|
304
|
+
.ax-cdk-overlay-scrollbar.ax-cdk-overlay-scrollbar--active.ax-cdk-overlay-scrollbar--engaged
|
|
305
|
+
.ax-cdk-overlay-scrollbar__rail-thumb:hover {
|
|
306
|
+
background-color: var(
|
|
307
|
+
--ax-comp-overlay-scrollbar-thumb-active-color,
|
|
308
|
+
var(--ax-comp-overlay-scrollbar-thumb-color, var(--ax-sys-scroller-thumb-color, #64748b))
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
`.trim();
|
|
312
|
+
const STYLE_ELEMENT_ID = 'ax-overlay-scrollbar-styles';
|
|
313
|
+
const DEFAULT_HIDE_DELAY_MS = AX_OVERLAY_SCROLLBAR_DEFAULT_HIDE_DELAY_MS;
|
|
314
|
+
const MIN_SCROLLBAR_WIDTH_PX = 2;
|
|
315
|
+
const MAX_SCROLLBAR_WIDTH_PX = 32;
|
|
316
|
+
const DEFAULT_SCROLLBAR_WIDTH_PX = 6;
|
|
317
|
+
const MIN_THUMB_HEIGHT_PX = 20;
|
|
318
|
+
const DEFAULT_NAV_SCROLL_STEP_PX = AX_OVERLAY_SCROLLBAR_DEFAULT_NAV_SCROLL_STEP_PX;
|
|
319
|
+
function clampOverlayScrollbarWidth(px) {
|
|
320
|
+
if (!Number.isFinite(px)) {
|
|
321
|
+
return DEFAULT_SCROLLBAR_WIDTH_PX;
|
|
322
|
+
}
|
|
323
|
+
return Math.min(MAX_SCROLLBAR_WIDTH_PX, Math.max(MIN_SCROLLBAR_WIDTH_PX, Math.round(px)));
|
|
324
|
+
}
|
|
325
|
+
function injectOverlayScrollbarStyles(document) {
|
|
326
|
+
const existing = document.getElementById(STYLE_ELEMENT_ID);
|
|
327
|
+
if (existing) {
|
|
328
|
+
existing.textContent = OVERLAY_SCROLLBAR_STYLES;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const style = document.createElement('style');
|
|
332
|
+
style.id = STYLE_ELEMENT_ID;
|
|
333
|
+
style.textContent = OVERLAY_SCROLLBAR_STYLES;
|
|
334
|
+
document.head.appendChild(style);
|
|
335
|
+
}
|
|
336
|
+
/** Rail is a sibling of the host; copy resolved tokens from the host so the rail can use them. */
|
|
337
|
+
function syncOverlayScrollbarDesignTokens(host, root) {
|
|
338
|
+
if (!root) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const computed = getComputedStyle(host);
|
|
342
|
+
for (const varName of OVERLAY_SCROLLBAR_TOKEN_NAMES) {
|
|
343
|
+
const value = computed.getPropertyValue(varName).trim();
|
|
344
|
+
if (value) {
|
|
345
|
+
root.style.setProperty(varName, value);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
root.style.removeProperty(varName);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function readRailWidthPx(rail) {
|
|
353
|
+
const width = rail.offsetWidth;
|
|
354
|
+
if (width > 0) {
|
|
355
|
+
return width;
|
|
356
|
+
}
|
|
357
|
+
const parsed = Number.parseFloat(getComputedStyle(rail).width);
|
|
358
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
359
|
+
return parsed;
|
|
360
|
+
}
|
|
361
|
+
const sizeToken = getComputedStyle(rail).getPropertyValue(AX_OVERLAY_SCROLLBAR_CSS_VARS.size).trim();
|
|
362
|
+
const fromToken = Number.parseFloat(sizeToken);
|
|
363
|
+
if (Number.isFinite(fromToken) && fromToken > 0) {
|
|
364
|
+
return fromToken;
|
|
365
|
+
}
|
|
366
|
+
return DEFAULT_SCROLLBAR_WIDTH_PX;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Custom overlay scrollbar: native scrollbars are hidden; a positioned thumb reflects scroll position.
|
|
370
|
+
* Styles are injected once by this directive.
|
|
371
|
+
*/
|
|
372
|
+
class AXOverlayScrollbarDirective {
|
|
373
|
+
constructor() {
|
|
374
|
+
this.host = inject(ElementRef);
|
|
375
|
+
this.destroyRef = inject(DestroyRef);
|
|
376
|
+
this.platformId = inject(PLATFORM_ID);
|
|
377
|
+
this.document = inject(DOCUMENT);
|
|
378
|
+
this.appConfig = inject(AX_OVERLAY_SCROLLBAR_CONFIG);
|
|
379
|
+
this.rootEl = null;
|
|
380
|
+
this.railEl = null;
|
|
381
|
+
this.trackEl = null;
|
|
382
|
+
this.navUpEl = null;
|
|
383
|
+
this.navDownEl = null;
|
|
384
|
+
this.thumbEl = null;
|
|
385
|
+
this.resizeObserver = null;
|
|
386
|
+
this.designTokenMutationObserver = null;
|
|
387
|
+
this.designTokenSyncFrame = null;
|
|
388
|
+
this.thumbDragPointerId = null;
|
|
389
|
+
this.pointerOverRail = false;
|
|
390
|
+
this.trackPressReleaseListener = null;
|
|
391
|
+
this.unbindRailInteraction = null;
|
|
392
|
+
/** Thumb size derived from layout; position updates on scroll without re-measuring scrollHeight. */
|
|
393
|
+
this.thumbLayout = null;
|
|
394
|
+
this.hideDelay = input(this.appConfig.hideDelay, /* @ts-ignore */
|
|
395
|
+
...(ngDevMode ? [{ debugName: "hideDelay" }] : /* istanbul ignore next */ []));
|
|
396
|
+
/**
|
|
397
|
+
* When true, shows browser-style up/down arrow buttons on the rail.
|
|
398
|
+
* Styling follows track/thumb CSS tokens.
|
|
399
|
+
*/
|
|
400
|
+
this.navigationButtons = input(this.appConfig.navigationButtons, /* @ts-ignore */
|
|
401
|
+
...(ngDevMode ? [{ debugName: "navigationButtons" }] : /* istanbul ignore next */ []));
|
|
402
|
+
/** Pixels to scroll per arrow click. */
|
|
403
|
+
this.navScrollStep = input(this.appConfig.navScrollStep, /* @ts-ignore */
|
|
404
|
+
...(ngDevMode ? [{ debugName: "navScrollStep" }] : /* istanbul ignore next */ []));
|
|
405
|
+
this.hideTimer = null;
|
|
406
|
+
afterRenderEffect(() => {
|
|
407
|
+
if (!isPlatformBrowser(this.platformId) || !this.rootEl) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
syncOverlayScrollbarDesignTokens(this.host.nativeElement, this.rootEl);
|
|
411
|
+
});
|
|
412
|
+
effect(() => {
|
|
413
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
this.navigationButtons();
|
|
417
|
+
this.remeasureCustomThumb();
|
|
418
|
+
});
|
|
419
|
+
afterNextRender(() => {
|
|
420
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
injectOverlayScrollbarStyles(this.document);
|
|
424
|
+
const element = this.host.nativeElement;
|
|
425
|
+
this.mountCustomScrollbar(element);
|
|
426
|
+
const interactionRoot = () => this.rootEl ?? element;
|
|
427
|
+
const onScroll = () => {
|
|
428
|
+
this.syncThumbPosition();
|
|
429
|
+
this.bumpVisibility();
|
|
430
|
+
};
|
|
431
|
+
const onScrollEnd = () => {
|
|
432
|
+
this.scheduleHide();
|
|
433
|
+
this.syncEngagedAfterScroll();
|
|
434
|
+
};
|
|
435
|
+
const onPointerEnter = () => this.bumpVisibility();
|
|
436
|
+
const onPointerMove = () => this.bumpVisibility();
|
|
437
|
+
const onPointerLeave = () => {
|
|
438
|
+
if (this.thumbDragPointerId === null) {
|
|
439
|
+
this.setScrollbarEngaged(false);
|
|
440
|
+
}
|
|
441
|
+
this.deactivateThumb();
|
|
442
|
+
};
|
|
443
|
+
element.addEventListener('scroll', onScroll, { passive: true });
|
|
444
|
+
element.addEventListener('scrollend', onScrollEnd);
|
|
445
|
+
const root = interactionRoot();
|
|
446
|
+
root.addEventListener('pointerenter', onPointerEnter);
|
|
447
|
+
root.addEventListener('pointermove', onPointerMove);
|
|
448
|
+
root.addEventListener('pointerleave', onPointerLeave);
|
|
449
|
+
this.bindRailInteraction(element);
|
|
450
|
+
this.resizeObserver = new ResizeObserver(() => this.remeasureCustomThumb());
|
|
451
|
+
this.resizeObserver.observe(element);
|
|
452
|
+
this.observeHostDesignTokenChanges(element);
|
|
453
|
+
this.queueDesignTokenSync();
|
|
454
|
+
this.remeasureCustomThumb();
|
|
455
|
+
this.destroyRef.onDestroy(() => {
|
|
456
|
+
this.clearHideTimer();
|
|
457
|
+
this.clearTrackPressed();
|
|
458
|
+
this.unbindRailInteraction?.();
|
|
459
|
+
this.unbindRailInteraction = null;
|
|
460
|
+
if (this.thumbEl && this.thumbDragPointerId !== null) {
|
|
461
|
+
try {
|
|
462
|
+
this.thumbEl.releasePointerCapture(this.thumbDragPointerId);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
/* already released */
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
this.thumbDragPointerId = null;
|
|
469
|
+
this.resizeObserver?.disconnect();
|
|
470
|
+
this.resizeObserver = null;
|
|
471
|
+
this.designTokenMutationObserver?.disconnect();
|
|
472
|
+
this.designTokenMutationObserver = null;
|
|
473
|
+
if (this.designTokenSyncFrame !== null) {
|
|
474
|
+
cancelAnimationFrame(this.designTokenSyncFrame);
|
|
475
|
+
this.designTokenSyncFrame = null;
|
|
476
|
+
}
|
|
477
|
+
element.removeEventListener('scroll', onScroll);
|
|
478
|
+
element.removeEventListener('scrollend', onScrollEnd);
|
|
479
|
+
root.removeEventListener('pointerenter', onPointerEnter);
|
|
480
|
+
root.removeEventListener('pointermove', onPointerMove);
|
|
481
|
+
root.removeEventListener('pointerleave', onPointerLeave);
|
|
482
|
+
this.unmountCustomScrollbar(element);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
/** Host and rail are siblings under the root; mirror CSS variables when the host style/class changes. */
|
|
487
|
+
observeHostDesignTokenChanges(host) {
|
|
488
|
+
this.designTokenMutationObserver?.disconnect();
|
|
489
|
+
this.designTokenMutationObserver = new MutationObserver(() => this.queueDesignTokenSync());
|
|
490
|
+
this.designTokenMutationObserver.observe(host, {
|
|
491
|
+
attributes: true,
|
|
492
|
+
attributeFilter: ['style', 'class'],
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
queueDesignTokenSync() {
|
|
496
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (this.designTokenSyncFrame !== null) {
|
|
500
|
+
cancelAnimationFrame(this.designTokenSyncFrame);
|
|
501
|
+
}
|
|
502
|
+
this.designTokenSyncFrame = requestAnimationFrame(() => {
|
|
503
|
+
this.designTokenSyncFrame = null;
|
|
504
|
+
syncOverlayScrollbarDesignTokens(this.host.nativeElement, this.rootEl);
|
|
505
|
+
this.remeasureCustomThumb();
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/** Rail lives on a wrapper so it stays fixed while the host scrolls. */
|
|
509
|
+
ensureScrollRoot(scroller) {
|
|
510
|
+
const parent = scroller.parentElement;
|
|
511
|
+
if (parent?.classList.contains(ROOT_CLASS)) {
|
|
512
|
+
this.rootEl = parent;
|
|
513
|
+
return parent;
|
|
514
|
+
}
|
|
515
|
+
const root = this.document.createElement('div');
|
|
516
|
+
root.className = ROOT_CLASS;
|
|
517
|
+
parent?.insertBefore(root, scroller);
|
|
518
|
+
root.appendChild(scroller);
|
|
519
|
+
this.rootEl = root;
|
|
520
|
+
return root;
|
|
521
|
+
}
|
|
522
|
+
mountCustomScrollbar(element) {
|
|
523
|
+
const root = this.ensureScrollRoot(element);
|
|
524
|
+
const existingRail = root.querySelector(`.${RAIL_CLASS}`);
|
|
525
|
+
if (existingRail) {
|
|
526
|
+
this.railEl = existingRail;
|
|
527
|
+
this.ensureRailStructure(existingRail);
|
|
528
|
+
this.bindRailInteraction(element);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const rail = this.document.createElement('div');
|
|
532
|
+
rail.className = RAIL_CLASS;
|
|
533
|
+
rail.setAttribute('aria-hidden', 'true');
|
|
534
|
+
this.ensureRailStructure(rail);
|
|
535
|
+
root.appendChild(rail);
|
|
536
|
+
this.railEl = rail;
|
|
537
|
+
this.bindRailInteraction(element);
|
|
538
|
+
}
|
|
539
|
+
ensureRailStructure(rail) {
|
|
540
|
+
let thumb = rail.querySelector(`.${THUMB_CLASS}`);
|
|
541
|
+
if (!thumb) {
|
|
542
|
+
thumb = this.document.createElement('div');
|
|
543
|
+
thumb.className = THUMB_CLASS;
|
|
544
|
+
thumb.addEventListener('pointerdown', (event) => this.onThumbPointerDown(event));
|
|
545
|
+
}
|
|
546
|
+
let track = rail.querySelector(`.${TRACK_CLASS}`);
|
|
547
|
+
if (!track) {
|
|
548
|
+
const navUp = this.createNavButton('up');
|
|
549
|
+
const navDown = this.createNavButton('down');
|
|
550
|
+
track = this.document.createElement('div');
|
|
551
|
+
track.className = TRACK_CLASS;
|
|
552
|
+
rail.replaceChildren(navUp, track, navDown);
|
|
553
|
+
track.appendChild(thumb);
|
|
554
|
+
this.navUpEl = navUp;
|
|
555
|
+
this.navDownEl = navDown;
|
|
556
|
+
}
|
|
557
|
+
else if (!track.contains(thumb)) {
|
|
558
|
+
track.appendChild(thumb);
|
|
559
|
+
this.navUpEl = rail.querySelector(`.${NAV_BTN_CLASS}--up`);
|
|
560
|
+
this.navDownEl = rail.querySelector(`.${NAV_BTN_CLASS}--down`);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
this.navUpEl = rail.querySelector(`.${NAV_BTN_CLASS}--up`);
|
|
564
|
+
this.navDownEl = rail.querySelector(`.${NAV_BTN_CLASS}--down`);
|
|
565
|
+
}
|
|
566
|
+
this.trackEl = track;
|
|
567
|
+
this.thumbEl = thumb;
|
|
568
|
+
}
|
|
569
|
+
createNavButton(direction) {
|
|
570
|
+
const button = this.document.createElement('button');
|
|
571
|
+
button.type = 'button';
|
|
572
|
+
button.className = `${NAV_BTN_CLASS} ${NAV_BTN_CLASS}--${direction}`;
|
|
573
|
+
button.setAttribute('aria-label', direction === 'up' ? 'Scroll up' : 'Scroll down');
|
|
574
|
+
const arrow = this.document.createElement('span');
|
|
575
|
+
arrow.className = `${NAV_ARROW_CLASS} ${NAV_ARROW_CLASS}--${direction}`;
|
|
576
|
+
arrow.setAttribute('aria-hidden', 'true');
|
|
577
|
+
button.appendChild(arrow);
|
|
578
|
+
return button;
|
|
579
|
+
}
|
|
580
|
+
resolveNavScrollStepPx() {
|
|
581
|
+
const step = this.navScrollStep();
|
|
582
|
+
if (Number.isFinite(step) && step > 0) {
|
|
583
|
+
return Math.round(step);
|
|
584
|
+
}
|
|
585
|
+
return DEFAULT_NAV_SCROLL_STEP_PX;
|
|
586
|
+
}
|
|
587
|
+
scrollByNavStep(scroller, direction) {
|
|
588
|
+
scroller.scrollTop += direction * this.resolveNavScrollStepPx();
|
|
589
|
+
this.syncThumbPosition();
|
|
590
|
+
this.bumpVisibility();
|
|
591
|
+
}
|
|
592
|
+
bindRailInteraction(scroller) {
|
|
593
|
+
const rail = this.railEl;
|
|
594
|
+
if (!rail) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
this.unbindRailInteraction?.();
|
|
598
|
+
const onRailPointerEnter = () => {
|
|
599
|
+
this.pointerOverRail = true;
|
|
600
|
+
this.showScrollbar();
|
|
601
|
+
this.clearHideTimer();
|
|
602
|
+
};
|
|
603
|
+
const onRailPointerLeave = () => {
|
|
604
|
+
this.pointerOverRail = false;
|
|
605
|
+
this.clearTrackPressed();
|
|
606
|
+
this.syncEngagedAfterScroll();
|
|
607
|
+
this.scheduleHide();
|
|
608
|
+
};
|
|
609
|
+
const onRailWheel = (event) => {
|
|
610
|
+
event.preventDefault();
|
|
611
|
+
event.stopPropagation();
|
|
612
|
+
scroller.scrollTop += event.deltaY;
|
|
613
|
+
this.bumpVisibility();
|
|
614
|
+
};
|
|
615
|
+
const onRailPointerDown = (event) => {
|
|
616
|
+
const thumb = this.thumbEl;
|
|
617
|
+
const target = event.target;
|
|
618
|
+
if (thumb && (event.target === thumb || thumb.contains(target))) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (this.navUpEl?.contains(target) ||
|
|
622
|
+
this.navDownEl?.contains(target) ||
|
|
623
|
+
event.target === this.navUpEl ||
|
|
624
|
+
event.target === this.navDownEl) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const track = this.trackEl;
|
|
628
|
+
if (!track || (event.target !== track && !track.contains(target))) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (this.isPointerOverThumbVisual(event.clientX, event.clientY)) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
event.preventDefault();
|
|
635
|
+
this.beginTrackPressed();
|
|
636
|
+
this.jumpScrollToTrackPointer(event, scroller);
|
|
637
|
+
};
|
|
638
|
+
const bindNavButton = (button, direction) => {
|
|
639
|
+
if (!button) {
|
|
640
|
+
return () => undefined;
|
|
641
|
+
}
|
|
642
|
+
const onNavPointerLeave = () => {
|
|
643
|
+
button.classList.remove(NAV_BTN_PRESSED_CLASS);
|
|
644
|
+
};
|
|
645
|
+
const onNavPointerDown = (event) => {
|
|
646
|
+
event.preventDefault();
|
|
647
|
+
event.stopPropagation();
|
|
648
|
+
button.classList.add(NAV_BTN_PRESSED_CLASS);
|
|
649
|
+
this.beginTrackPressed();
|
|
650
|
+
this.scrollByNavStep(scroller, direction);
|
|
651
|
+
};
|
|
652
|
+
const onNavPointerUp = () => {
|
|
653
|
+
button.classList.remove(NAV_BTN_PRESSED_CLASS);
|
|
654
|
+
};
|
|
655
|
+
button.addEventListener('pointerleave', onNavPointerLeave);
|
|
656
|
+
button.addEventListener('pointerdown', onNavPointerDown);
|
|
657
|
+
button.addEventListener('pointerup', onNavPointerUp);
|
|
658
|
+
button.addEventListener('pointercancel', onNavPointerUp);
|
|
659
|
+
return () => {
|
|
660
|
+
button.removeEventListener('pointerleave', onNavPointerLeave);
|
|
661
|
+
button.removeEventListener('pointerdown', onNavPointerDown);
|
|
662
|
+
button.removeEventListener('pointerup', onNavPointerUp);
|
|
663
|
+
button.removeEventListener('pointercancel', onNavPointerUp);
|
|
664
|
+
button.classList.remove(NAV_BTN_PRESSED_CLASS);
|
|
665
|
+
};
|
|
666
|
+
};
|
|
667
|
+
const unbindNavUp = bindNavButton(this.navUpEl, -1);
|
|
668
|
+
const unbindNavDown = bindNavButton(this.navDownEl, 1);
|
|
669
|
+
rail.addEventListener('pointerenter', onRailPointerEnter);
|
|
670
|
+
rail.addEventListener('pointerleave', onRailPointerLeave);
|
|
671
|
+
rail.addEventListener('wheel', onRailWheel, { passive: false });
|
|
672
|
+
rail.addEventListener('pointerdown', onRailPointerDown);
|
|
673
|
+
this.unbindRailInteraction = () => {
|
|
674
|
+
rail.removeEventListener('pointerenter', onRailPointerEnter);
|
|
675
|
+
rail.removeEventListener('pointerleave', onRailPointerLeave);
|
|
676
|
+
this.clearTrackPressed();
|
|
677
|
+
rail.removeEventListener('wheel', onRailWheel);
|
|
678
|
+
rail.removeEventListener('pointerdown', onRailPointerDown);
|
|
679
|
+
unbindNavUp?.();
|
|
680
|
+
unbindNavDown?.();
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
unmountCustomScrollbar(scroller) {
|
|
684
|
+
this.railEl?.remove();
|
|
685
|
+
this.railEl = null;
|
|
686
|
+
this.trackEl = null;
|
|
687
|
+
this.navUpEl = null;
|
|
688
|
+
this.navDownEl = null;
|
|
689
|
+
this.thumbEl = null;
|
|
690
|
+
this.thumbLayout = null;
|
|
691
|
+
const root = this.rootEl;
|
|
692
|
+
if (!root?.classList.contains(ROOT_CLASS)) {
|
|
693
|
+
this.rootEl = null;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const parent = root.parentElement;
|
|
697
|
+
if (parent) {
|
|
698
|
+
parent.insertBefore(scroller, root);
|
|
699
|
+
root.remove();
|
|
700
|
+
}
|
|
701
|
+
this.rootEl = null;
|
|
702
|
+
}
|
|
703
|
+
remeasureCustomThumb() {
|
|
704
|
+
const element = this.host.nativeElement;
|
|
705
|
+
const rail = this.railEl;
|
|
706
|
+
const thumb = this.thumbEl;
|
|
707
|
+
if (!rail || !thumb) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
syncOverlayScrollbarDesignTokens(element, this.rootEl);
|
|
711
|
+
const clientHeight = element.clientHeight;
|
|
712
|
+
rail.style.height = `${clientHeight}px`;
|
|
713
|
+
const widthPx = readRailWidthPx(rail);
|
|
714
|
+
const navigationEnabled = this.navigationButtons() === true;
|
|
715
|
+
rail.classList.toggle(RAIL_WITH_NAV_CLASS, navigationEnabled);
|
|
716
|
+
const track = this.trackEl;
|
|
717
|
+
let trackViewportHeight = track?.clientHeight ?? 0;
|
|
718
|
+
if (trackViewportHeight <= 0) {
|
|
719
|
+
trackViewportHeight = navigationEnabled ? Math.max(0, clientHeight - 2 * widthPx) : clientHeight;
|
|
720
|
+
}
|
|
721
|
+
const scrollHeight = element.scrollHeight;
|
|
722
|
+
const isScrollable = scrollHeight > clientHeight + 1;
|
|
723
|
+
rail.hidden = !isScrollable;
|
|
724
|
+
if (!isScrollable) {
|
|
725
|
+
this.thumbLayout = null;
|
|
726
|
+
thumb.style.height = '';
|
|
727
|
+
thumb.style.top = '';
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const thumbHeight = Math.max(MIN_THUMB_HEIGHT_PX, (trackViewportHeight / scrollHeight) * trackViewportHeight);
|
|
731
|
+
const maxScroll = scrollHeight - clientHeight;
|
|
732
|
+
const maxThumbOffset = Math.max(0, trackViewportHeight - thumbHeight);
|
|
733
|
+
this.thumbLayout = { thumbHeight, maxScroll, maxThumbOffset };
|
|
734
|
+
thumb.style.height = `${thumbHeight}px`;
|
|
735
|
+
this.syncThumbPosition();
|
|
736
|
+
}
|
|
737
|
+
syncThumbPosition() {
|
|
738
|
+
const element = this.host.nativeElement;
|
|
739
|
+
const thumb = this.thumbEl;
|
|
740
|
+
const layout = this.thumbLayout;
|
|
741
|
+
if (!thumb || !layout) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const { maxScroll, maxThumbOffset } = layout;
|
|
745
|
+
const scrollTop = element.scrollTop;
|
|
746
|
+
const thumbOffset = maxScroll > 0 ? (scrollTop / maxScroll) * maxThumbOffset : 0;
|
|
747
|
+
thumb.style.top = `${thumbOffset}px`;
|
|
748
|
+
}
|
|
749
|
+
/** Move thumb (and scroll position) to the pointer Y on the track. */
|
|
750
|
+
jumpScrollToTrackPointer(event, scroller) {
|
|
751
|
+
const rail = this.railEl;
|
|
752
|
+
const layout = this.thumbLayout;
|
|
753
|
+
if (!rail || !layout || layout.maxScroll <= 0 || layout.maxThumbOffset <= 0) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const track = this.trackEl ?? rail;
|
|
757
|
+
const rect = track.getBoundingClientRect();
|
|
758
|
+
const offsetY = event.clientY - rect.top;
|
|
759
|
+
const thumbTop = Math.min(layout.maxThumbOffset, Math.max(0, offsetY - layout.thumbHeight / 2));
|
|
760
|
+
scroller.scrollTop = (thumbTop / layout.maxThumbOffset) * layout.maxScroll;
|
|
761
|
+
this.syncThumbPosition();
|
|
762
|
+
this.bumpVisibility();
|
|
763
|
+
}
|
|
764
|
+
onThumbPointerDown(event) {
|
|
765
|
+
event.preventDefault();
|
|
766
|
+
event.stopPropagation();
|
|
767
|
+
const element = this.host.nativeElement;
|
|
768
|
+
const thumb = this.thumbEl;
|
|
769
|
+
if (!thumb) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
this.setScrollbarEngaged(true);
|
|
773
|
+
this.bumpVisibility();
|
|
774
|
+
thumb.setPointerCapture(event.pointerId);
|
|
775
|
+
const startY = event.clientY;
|
|
776
|
+
const startScrollTop = element.scrollTop;
|
|
777
|
+
const layout = this.thumbLayout;
|
|
778
|
+
const clientHeight = element.clientHeight;
|
|
779
|
+
const maxScroll = layout?.maxScroll ?? element.scrollHeight - clientHeight;
|
|
780
|
+
const maxThumbOffset = layout?.maxThumbOffset ?? clientHeight - thumb.offsetHeight;
|
|
781
|
+
const onPointerMove = (moveEvent) => {
|
|
782
|
+
if (maxThumbOffset <= 0 || maxScroll <= 0) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const deltaY = moveEvent.clientY - startY;
|
|
786
|
+
const scrollDelta = (deltaY / maxThumbOffset) * maxScroll;
|
|
787
|
+
element.scrollTop = startScrollTop + scrollDelta;
|
|
788
|
+
};
|
|
789
|
+
const onPointerUp = (upEvent) => {
|
|
790
|
+
thumb.releasePointerCapture(upEvent.pointerId);
|
|
791
|
+
thumb.removeEventListener('pointermove', onPointerMove);
|
|
792
|
+
thumb.removeEventListener('pointerup', onPointerUp);
|
|
793
|
+
thumb.removeEventListener('pointercancel', onPointerUp);
|
|
794
|
+
this.thumbDragPointerId = null;
|
|
795
|
+
this.setScrollbarEngaged(false);
|
|
796
|
+
};
|
|
797
|
+
this.thumbDragPointerId = event.pointerId;
|
|
798
|
+
thumb.addEventListener('pointermove', onPointerMove);
|
|
799
|
+
thumb.addEventListener('pointerup', onPointerUp);
|
|
800
|
+
thumb.addEventListener('pointercancel', onPointerUp);
|
|
801
|
+
}
|
|
802
|
+
showScrollbar() {
|
|
803
|
+
this.host.nativeElement.classList.add(VISIBLE_CLASS);
|
|
804
|
+
this.rootEl?.classList.add(VISIBLE_CLASS);
|
|
805
|
+
}
|
|
806
|
+
bumpVisibility() {
|
|
807
|
+
this.showScrollbar();
|
|
808
|
+
if (this.pointerOverRail) {
|
|
809
|
+
this.clearHideTimer();
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
this.scheduleHide();
|
|
813
|
+
}
|
|
814
|
+
deactivateThumb() {
|
|
815
|
+
if (this.pointerOverRail) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
this.clearHideTimer();
|
|
819
|
+
this.host.nativeElement.classList.remove(VISIBLE_CLASS);
|
|
820
|
+
this.rootEl?.classList.remove(VISIBLE_CLASS);
|
|
821
|
+
this.setScrollbarEngaged(false);
|
|
822
|
+
}
|
|
823
|
+
setScrollbarEngaged(engaged) {
|
|
824
|
+
this.host.nativeElement.classList.toggle(ENGAGED_CLASS, engaged);
|
|
825
|
+
this.rootEl?.classList.toggle(ENGAGED_CLASS, engaged);
|
|
826
|
+
}
|
|
827
|
+
setTrackPressed(pressed) {
|
|
828
|
+
this.host.nativeElement.classList.toggle(TRACK_PRESSED_CLASS, pressed);
|
|
829
|
+
this.rootEl?.classList.toggle(TRACK_PRESSED_CLASS, pressed);
|
|
830
|
+
}
|
|
831
|
+
isPointerOverThumbVisual(clientX, clientY) {
|
|
832
|
+
const thumb = this.thumbEl;
|
|
833
|
+
if (!thumb) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
const rect = thumb.getBoundingClientRect();
|
|
837
|
+
return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
|
|
838
|
+
}
|
|
839
|
+
beginTrackPressed() {
|
|
840
|
+
this.clearTrackPressed();
|
|
841
|
+
this.setTrackPressed(true);
|
|
842
|
+
const release = () => {
|
|
843
|
+
this.setTrackPressed(false);
|
|
844
|
+
this.document.removeEventListener('pointerup', release);
|
|
845
|
+
this.document.removeEventListener('pointercancel', release);
|
|
846
|
+
this.trackPressReleaseListener = null;
|
|
847
|
+
};
|
|
848
|
+
this.trackPressReleaseListener = release;
|
|
849
|
+
this.document.addEventListener('pointerup', release);
|
|
850
|
+
this.document.addEventListener('pointercancel', release);
|
|
851
|
+
}
|
|
852
|
+
clearTrackPressed() {
|
|
853
|
+
if (this.trackPressReleaseListener) {
|
|
854
|
+
this.document.removeEventListener('pointerup', this.trackPressReleaseListener);
|
|
855
|
+
this.document.removeEventListener('pointercancel', this.trackPressReleaseListener);
|
|
856
|
+
this.trackPressReleaseListener = null;
|
|
857
|
+
}
|
|
858
|
+
this.setTrackPressed(false);
|
|
859
|
+
}
|
|
860
|
+
syncEngagedAfterScroll() {
|
|
861
|
+
if (this.thumbDragPointerId !== null) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
this.setScrollbarEngaged(false);
|
|
865
|
+
}
|
|
866
|
+
scheduleHide() {
|
|
867
|
+
if (this.pointerOverRail) {
|
|
868
|
+
this.clearHideTimer();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
this.clearHideTimer();
|
|
872
|
+
const delay = Math.max(0, this.hideDelay());
|
|
873
|
+
this.hideTimer = setTimeout(() => {
|
|
874
|
+
this.deactivateThumb();
|
|
875
|
+
this.hideTimer = null;
|
|
876
|
+
}, delay);
|
|
877
|
+
}
|
|
878
|
+
clearHideTimer() {
|
|
879
|
+
if (this.hideTimer !== null) {
|
|
880
|
+
clearTimeout(this.hideTimer);
|
|
881
|
+
this.hideTimer = null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AXOverlayScrollbarDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
885
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: AXOverlayScrollbarDirective, isStandalone: true, selector: "[axOverlayScrollbar]", inputs: { hideDelay: { classPropertyName: "hideDelay", publicName: "hideDelay", isSignal: true, isRequired: false, transformFunction: null }, navigationButtons: { classPropertyName: "navigationButtons", publicName: "navigationButtons", isSignal: true, isRequired: false, transformFunction: null }, navScrollStep: { classPropertyName: "navScrollStep", publicName: "navScrollStep", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "ax-cdk-overlay-scrollbar" }, exportAs: ["axOverlayScrollbar"], ngImport: i0 }); }
|
|
886
|
+
}
|
|
887
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AXOverlayScrollbarDirective, decorators: [{
|
|
888
|
+
type: Directive,
|
|
889
|
+
args: [{
|
|
890
|
+
selector: '[axOverlayScrollbar]',
|
|
891
|
+
exportAs: 'axOverlayScrollbar',
|
|
892
|
+
host: {
|
|
893
|
+
class: 'ax-cdk-overlay-scrollbar',
|
|
894
|
+
},
|
|
895
|
+
}]
|
|
896
|
+
}], ctorParameters: () => [], propDecorators: { hideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideDelay", required: false }] }], navigationButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationButtons", required: false }] }], navScrollStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "navScrollStep", required: false }] }] } });
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Generated bundle index. Do not edit.
|
|
900
|
+
*/
|
|
901
|
+
|
|
902
|
+
export { AXOverlayScrollbarDefaultConfig, AXOverlayScrollbarDirective, AX_OVERLAY_SCROLLBAR_CONFIG, AX_OVERLAY_SCROLLBAR_CSS_VARS, AX_OVERLAY_SCROLLBAR_DEFAULT_HIDE_DELAY_MS, AX_OVERLAY_SCROLLBAR_DEFAULT_NAV_SCROLL_STEP_PX, applyOverlayScrollbarDesignTokens, clampOverlayScrollbarWidth, overlayScrollbarConfig, provideOverlayScrollbar };
|
|
903
|
+
//# sourceMappingURL=acorex-cdk-overlay-scrollbar.mjs.map
|