@a11y-ngx/overlay-base 1.0.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/README.md +632 -0
- package/a11y-ngx-overlay-base.d.ts +5 -0
- package/bundles/a11y-ngx-overlay-base.umd.js +1477 -0
- package/bundles/a11y-ngx-overlay-base.umd.js.map +1 -0
- package/esm2015/a11y-ngx-overlay-base.js +5 -0
- package/esm2015/lib/overlay-base.abstract.js +1219 -0
- package/esm2015/lib/overlay-base.errors.js +9 -0
- package/esm2015/lib/overlay-base.type.js +30 -0
- package/esm2015/lib/overlay-base.type.private.js +2 -0
- package/esm2015/public-api.js +3 -0
- package/fesm2015/a11y-ngx-overlay-base.js +1263 -0
- package/fesm2015/a11y-ngx-overlay-base.js.map +1 -0
- package/lib/overlay-base.abstract.d.ts +446 -0
- package/lib/overlay-base.errors.d.ts +1 -0
- package/lib/overlay-base.type.d.ts +64 -0
- package/lib/overlay-base.type.private.d.ts +31 -0
- package/package.json +40 -0
- package/public-api.d.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
# Overlay Base
|
|
2
|
+
|
|
3
|
+
An overlay positioning system that allows them to remain within the visible area of the viewport or a given custom boundary (such as containers with overflow) and thus, prevent being out of the screen.
|
|
4
|
+
|
|
5
|
+
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
|
|
6
|
+
|
|
7
|
+
## Index
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [The Configuration Object](#the-configuration-object)
|
|
11
|
+
- [The Trigger Element](#the-trigger-element)
|
|
12
|
+
- [The Position Input](#the-position-input)
|
|
13
|
+
- [The Positions Allowed Input](#the-positions-allowed-input)
|
|
14
|
+
- [The Alignments Allowed Input](#the-alignments-allowed-input)
|
|
15
|
+
- [The Position Strategy](#the-position-strategy)
|
|
16
|
+
- [The Custom Boundary](#the-custom-boundary)
|
|
17
|
+
- [The Safe Space](#the-safe-space)
|
|
18
|
+
- [The Safe Space Off](#the-safe-space-off)
|
|
19
|
+
- [The Safe Space On](#the-safe-space-on)
|
|
20
|
+
- [The Offset Size](#the-offset-size)
|
|
21
|
+
- [The Fluid Alignment](#the-fluid-alignment)
|
|
22
|
+
- [The Fluid Alignment On or Off](#the-fluid-alignment-on-or-off)
|
|
23
|
+
- [The Fluid Size](#the-fluid-size)
|
|
24
|
+
- [The Fluid Size On or Off](#the-fluid-size-on-or-off)
|
|
25
|
+
- [The Viewport Size](#the-viewport-size)
|
|
26
|
+
- [The Viewport Safe Size](#the-viewport-safe-size)
|
|
27
|
+
- [The Viewport Safe Size Without a Boundary](#the-viewport-safe-size-without-a-boundary)
|
|
28
|
+
- [The Viewport Safe Size With a Boundary](#the-viewport-safe-size-with-a-boundary)
|
|
29
|
+
- [The Types](#the-types)
|
|
30
|
+
- [The Overlay Position](#the-overlay-position)
|
|
31
|
+
- [The Overlay Reposition](#the-overlay-reposition)
|
|
32
|
+
- [The Overlay Reposition by Square Areas](#the-overlay-reposition-by-square-areas)
|
|
33
|
+
- [The Overlay Alignment](#the-overlay-alignment)
|
|
34
|
+
- [The Overlay Alignment Horizontally](#the-overlay-alignment-horizontally)
|
|
35
|
+
- [The Overlay Alignment Vertically](#the-overlay-alignment-vertically)
|
|
36
|
+
- [The Overlay Realignment](#the-overlay-realignment)
|
|
37
|
+
- [The Overlay Base Class](#the-overlay-base-class)
|
|
38
|
+
- [The `setBaseConfig()` Method](#the-setbaseconfig-method)
|
|
39
|
+
- [The `attachOverlay()` Method](#the-attachoverlay-method)
|
|
40
|
+
- [The Calculated Position](#the-calculated-position)
|
|
41
|
+
- [The `detachOverlay()` Method](#the-detachoverlay-method)
|
|
42
|
+
- [The `updateOverlaySize()` Method](#the-updateoverlaysize-method)
|
|
43
|
+
- [The `recalculate()` Method](#the-recalculate-method)
|
|
44
|
+
- [The `triggerBoundaryDistance()` Method](#the-triggerboundarydistance-method)
|
|
45
|
+
- [The public Properties, Getters and Setters](#the-public-properties-getters-and-setters)
|
|
46
|
+
- [The Listeners](#the-listeners)
|
|
47
|
+
- [The Page Scroll Listener](#the-page-scroll-listener)
|
|
48
|
+
- [The Custom Boundary Scroll Listener](#the-custom-boundary-scroll-listener)
|
|
49
|
+
- [Example](#example)
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
1. Install npm package:
|
|
54
|
+
|
|
55
|
+
`npm install @a11y-ngx/overlay-base --save`
|
|
56
|
+
|
|
57
|
+
2. Use `OverlayBase` abstract class to extend your component/directive.
|
|
58
|
+
|
|
59
|
+
## The Configuration Object
|
|
60
|
+
|
|
61
|
+
- **Type:** `OverlayBaseConfig`.
|
|
62
|
+
- **Properties:**
|
|
63
|
+
- [`trigger`](#the-trigger-element)
|
|
64
|
+
- [`position`](#the-position-input)
|
|
65
|
+
- [`positionStrategy`](#the-position-strategy)
|
|
66
|
+
- [`boundary`](#the-custom-boundary)
|
|
67
|
+
- [`safeSpace`](#the-safe-space)
|
|
68
|
+
- [`offsetSize`](#the-offset-size)
|
|
69
|
+
- [`fluidAlignment`](#the-fluid-alignment)
|
|
70
|
+
- [`fluidSize`](#the-fluid-size)
|
|
71
|
+
- [`positionsAllowed`](#the-positions-allowed-input)
|
|
72
|
+
- [`alignmentsAllowed`](#the-alignments-allowed-input)
|
|
73
|
+
- [`allowScrollListener`](#the-page-scroll-listener)
|
|
74
|
+
|
|
75
|
+
### The Trigger Element
|
|
76
|
+
|
|
77
|
+
It is the _area_ from which the overlay will be relatively positioned.
|
|
78
|
+
|
|
79
|
+
- **Property:** `trigger`.
|
|
80
|
+
- **Type:** `HTMLElement` or `DOMRect`.
|
|
81
|
+
|
|
82
|
+
When `HTMLElement` is provided (such as a `<button>`), it will be used as the _base_ element to calculate where to position the overlay.
|
|
83
|
+
|
|
84
|
+
When `DOMRect` is provided (such as a `PointerEvent`), those `x` and `y` coordinates are the _base_ to calculate where to position the overlay.
|
|
85
|
+
|
|
86
|
+
### The Position Input
|
|
87
|
+
|
|
88
|
+
To input, in a quick-simple way, either position or position & alignment (hyphen separated if `string` is used).
|
|
89
|
+
|
|
90
|
+
- **Property:** `position`.
|
|
91
|
+
- **Type:** `OverlayBasePositionInput`.
|
|
92
|
+
- **From the `enum`:** `POSITION` and `ALIGNMENT`.
|
|
93
|
+
- **Default:** `['top', 'center']`.
|
|
94
|
+
- **You can use:**
|
|
95
|
+
- `OverlayBasePosition`: e.g.: `POSITION.BOTTOM`.
|
|
96
|
+
- `[OverlayBasePosition, OverlayBaseAlignment]`: e.g.: `[POSITION.RIGHT, ALIGNMENT.START]`.
|
|
97
|
+
- `string`: e.g.: `'left'` or `'left-start'`.
|
|
98
|
+
|
|
99
|
+
See [the Overlay Position](#the-overlay-position) and [the Overlay Alignment](#the-overlay-alignment).
|
|
100
|
+
|
|
101
|
+
> **NOTE:** In case [alignmentsAllowed](#the-alignments-allowed-input) is set to `edges` and no alignment is provided here, `'start'` will be established as default.
|
|
102
|
+
|
|
103
|
+
#### The Positions Allowed Input
|
|
104
|
+
|
|
105
|
+
To establish which positions are allowed.
|
|
106
|
+
|
|
107
|
+
- **Property:** `positionsAllowed`.
|
|
108
|
+
- **Type:** `OverlayBasePositionsAllowed`.
|
|
109
|
+
- **Default:** `'auto'`.
|
|
110
|
+
- **You can use:**
|
|
111
|
+
- `'auto'`: means all sides are allowed.
|
|
112
|
+
- `'opposite'`: means that the provided (or default) position and its opposite are only allowed. So if the overlay is set to the top, the allowed positions are `'top'` and `'bottom'`.
|
|
113
|
+
- `string`: accepts a comma separated values, e.g.: `'top, right'`.
|
|
114
|
+
- `OverlayBasePosition`: means that it will allow a single position, e.g.: `POSITION.RIGHT` or `'right'`.
|
|
115
|
+
- ⚠️ **IMPORTANT:** avoid using this option unless you'll be 100% sure the overlay won't need repositioning!
|
|
116
|
+
- `OverlayBasePosition[]`: an array of values, e.g.: `[POSITION.TOP, POSITION.RIGHT]` or `['top', 'right']`.
|
|
117
|
+
|
|
118
|
+
#### The Alignments Allowed Input
|
|
119
|
+
|
|
120
|
+
To establish which alignments are allowed.
|
|
121
|
+
|
|
122
|
+
- **Property:** `alignmentsAllowed`.
|
|
123
|
+
- **Type:** `OverlayBaseAlignmentsAllowed`.
|
|
124
|
+
- **Default:** `'auto'`.
|
|
125
|
+
- **You can use:**
|
|
126
|
+
- `'auto'`: means all alignments are allowed.
|
|
127
|
+
- `'center'`: means that only center alignment is allowed (and it will only work if [Fluid Alignment](#the-fluid-alignment) is set to `true`).
|
|
128
|
+
- `'edges'`: means that only `start` and `end` alignments are allowed.
|
|
129
|
+
- 📘 **NOTE:** if no alignment was provided, `start` will be set as default.
|
|
130
|
+
- `OverlayBaseAlignment`: means that it will allow a single alignment, e.g.: `ALIGNMENT.START` or `'start'`.
|
|
131
|
+
- ⚠️ **IMPORTANT:** avoid using this option unless you'll be 100% sure the overlay will be within the visible area at that alignment!
|
|
132
|
+
- `OverlayBaseAlignment[]`: an array of values, e.g.: `[ALIGNMENT.CENTER, ALIGNMENT.END]` or `['center', 'end']`.
|
|
133
|
+
|
|
134
|
+
### The Position Strategy
|
|
135
|
+
|
|
136
|
+
To establish whether a `fixed` or `absolute` strategy positioning is used in CSS.
|
|
137
|
+
|
|
138
|
+
- **Property:** `positionStrategy`.
|
|
139
|
+
- **Type:** `OverlayBasePositionStrategy`.
|
|
140
|
+
- **From the `enum`:** `POSITION_STRATEGY`.
|
|
141
|
+
- **Default:** `'fixed'`.
|
|
142
|
+
- **Values:**
|
|
143
|
+
- `'fixed'`.
|
|
144
|
+
- `'absolute'`.
|
|
145
|
+
|
|
146
|
+
The `absolute` strategy was designed mainly to be utilized inside containers (aka [the boundary](#the-custom-boundary)) with overflow and to avoid the overlay to be visible in case of scrolling and the trigger being visually hidden.
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
### The Custom Boundary
|
|
151
|
+
|
|
152
|
+
A custom boundary can be interpreted as a wrapper/container, and the overlay will consider that boundary as the new limits for its positioning.
|
|
153
|
+
|
|
154
|
+
- **Property:** `boundary`.
|
|
155
|
+
- **Type:** `HTMLElement`.
|
|
156
|
+
- **Default:** `<body>`.
|
|
157
|
+
|
|
158
|
+
See [the example](#example).
|
|
159
|
+
|
|
160
|
+
### The Safe Space
|
|
161
|
+
|
|
162
|
+
To establish an extra safe space to the viewport's edges in case some fixed areas are present, such as headers, side menus or footers.
|
|
163
|
+
|
|
164
|
+
This way, the overlay will consider this area as the _edge limit_ and reposition itself if reached. Most useful use cases are related to scroll events.
|
|
165
|
+
|
|
166
|
+
- **Property:** `safeSpace`.
|
|
167
|
+
- **Type:** `OverlayBaseSafeSpace`:
|
|
168
|
+
- `object` with each side as a property of type `number`.
|
|
169
|
+
- **Default:** `{ top: 0, bottom: 0, left: 0, right: 0 }`.
|
|
170
|
+
|
|
171
|
+
#### The Safe Space Off
|
|
172
|
+
|
|
173
|
+
In this scenario we can see two different overlays with the default position/alignment (`top-center`) when the safe space is not set at all.
|
|
174
|
+
|
|
175
|
+
Both overlays have enough space at the top and can be centered.
|
|
176
|
+
|
|
177
|
+

|
|
178
|
+
|
|
179
|
+
#### The Safe Space On
|
|
180
|
+
|
|
181
|
+
Let's say we have a header at the top (`65px`) and a left side menu (`50px`), both fixed to the page.
|
|
182
|
+
|
|
183
|
+
Now we set the safe space with the desired values for our fixed landmarks:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
this.setBaseConfig({
|
|
187
|
+
safeSpace: { top: 65, left: 50 },
|
|
188
|
+
...
|
|
189
|
+
}),
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
> **NOTE:** For the next examples, we have forced the right overlay to ignore the safe space.
|
|
193
|
+
|
|
194
|
+
The overlay at the left doesn't have enough space to be centered anymore and it will analyze where can be aligned, which will result at `start`.
|
|
195
|
+
|
|
196
|
+

|
|
197
|
+
|
|
198
|
+
Now we start scrolling down and, the moment the left overlay reaches the top safe space limit, it will need to check the best side to reposition itself, which will result at `bottom`.
|
|
199
|
+
|
|
200
|
+

|
|
201
|
+
|
|
202
|
+
We keep scrolling down and we can see the right overlay not repositioning and overlapping the header. This can be **an issue** depending on what `z-index` your landmarks are set.
|
|
203
|
+
|
|
204
|
+

|
|
205
|
+
|
|
206
|
+
### The Offset Size
|
|
207
|
+
|
|
208
|
+
It defines the space between the overlay and its trigger.
|
|
209
|
+
|
|
210
|
+
- **Property:** `offsetSize`.
|
|
211
|
+
- **Type:** `number`.
|
|
212
|
+
- **Default:** `5`.
|
|
213
|
+
- **Accepts:** positives and negatives.
|
|
214
|
+
- **Translated to:** _pixels_.
|
|
215
|
+
|
|
216
|
+

|
|
217
|
+
|
|
218
|
+
### The Fluid Alignment
|
|
219
|
+
|
|
220
|
+
To establish whether the overlay's alignment will stick to the edges of the viewport/boundary (if set to `true`) or make jumps between `start`, `center` or `end` (if set to `false`).
|
|
221
|
+
|
|
222
|
+
- **Property:** `fluidAlignment`.
|
|
223
|
+
- **Type:** `boolean`.
|
|
224
|
+
- **Default:** `false`.
|
|
225
|
+
|
|
226
|
+
#### The Fluid Alignment On or Off
|
|
227
|
+
|
|
228
|
+
If `fluidAlignment` is on, and the overlay width (or height) exceeds both, the trigger's size and the free space to be centered, it will stick to the closest viewport/boundary edge. If not (is off), it will use one of the edges of the trigger to align itself (`start` or `end`).
|
|
229
|
+
|
|
230
|
+
In the next example, both overlays can't be centered and they have to decide where to be aligned. The one on the left is the only one that has `fluidAlignment` set to `true`, which means that it's going to be aligned to the left side of the viewport, while the one on the right should respect either `start`, `center` or `end` (in this case) of its trigger.
|
|
231
|
+
|
|
232
|
+

|
|
233
|
+
|
|
234
|
+
When this option is on, and the overlay is sticked to one of the viewport's sides (the left one in this case), as shown in the example above, you can access the property `overlayOutside` with the side that it is _out_, as its value (`'left'`).
|
|
235
|
+
|
|
236
|
+
### The Fluid Size
|
|
237
|
+
|
|
238
|
+
To establish whether the overlay size should adjust to the free space (if set to `true`) or stay as its original size, with the possibility of being out of the visible area, if larger (if set to `false`).
|
|
239
|
+
|
|
240
|
+
- **Property:** `fluidSize`.
|
|
241
|
+
- **Type:** `boolean`.
|
|
242
|
+
- **Default:** `true`.
|
|
243
|
+
|
|
244
|
+
The size adjustment will depend of the overlay's position:
|
|
245
|
+
|
|
246
|
+
- if `top` or `bottom`, the `height` of the overlay will be adjusted to the free space of any of those sides.
|
|
247
|
+
- if `left` or `right`, the `width` of the overlay will be adjusted to the free space of any of those sides.
|
|
248
|
+
|
|
249
|
+
#### The Fluid Size On or Off
|
|
250
|
+
|
|
251
|
+
If `fluidSize` is on, and the overlay is bigger than the chosen side free space, it will return the max `width` or `height` within `maxSize` (as part of [the Calculated Position](#the-calculated-position)).
|
|
252
|
+
|
|
253
|
+
At this point, is up to you to force either the `width` or `height` of your overlay to those "max" values, and handle any possible overflow inside of it.
|
|
254
|
+
|
|
255
|
+
If is off, `maxSize` will return `null` for both, `width` and `height`.
|
|
256
|
+
|
|
257
|
+
Check also [the square areas](#the-overlay-reposition-by-square-areas) to better understand how the free space is calculated in case the overlay size exceeds it.
|
|
258
|
+
|
|
259
|
+
### The Viewport Size
|
|
260
|
+
|
|
261
|
+
The viewport size (without the scrollbars into consideration).
|
|
262
|
+
|
|
263
|
+
- **Type:** `ViewportSize`:
|
|
264
|
+
- `object` with `width` and `height` as a property of type `number`.
|
|
265
|
+
- **Default:** viewport's width and height.
|
|
266
|
+
|
|
267
|
+
### The Viewport Safe Size
|
|
268
|
+
|
|
269
|
+
The viewport safe size is the result of how many free space (`width` and `height`) the overlay can consider to be positioned.
|
|
270
|
+
|
|
271
|
+
It will be the calculation between the [viewport size](#the-viewport-size), a given [custom boundary](#the-custom-boundary) (also without the scrollbars into consideration, if any) and/or the [safe space](#the-safe-space).
|
|
272
|
+
|
|
273
|
+
- **Type:** `ViewportSize`.
|
|
274
|
+
|
|
275
|
+
In the following two examples, the green area is the so called _viewport **safe** size_, meaning that the overlay will consider only that area to establish its position and alignment.
|
|
276
|
+
|
|
277
|
+
#### The Viewport Safe Size Without a Boundary
|
|
278
|
+
|
|
279
|
+
Imagine having a viewport of `755px` of width and `415px` of height and two safe spaces, one at the `top` of `65px` and another at the `left` of `50px`.
|
|
280
|
+
|
|
281
|
+
In this case, the _safe size_ will be the result of:
|
|
282
|
+
|
|
283
|
+
- the viewport's `width` minus the `left` safe space: `755 - 50 = 705px`.
|
|
284
|
+
- the viewport's `height` minus the `top` safe space `415 - 65 = 350px`.
|
|
285
|
+
|
|
286
|
+

|
|
287
|
+
|
|
288
|
+
#### The Viewport Safe Size With a Boundary
|
|
289
|
+
|
|
290
|
+
Now imagine having the same viewport (`755px` by `415px`), the same safe spaces (`65px` and `50px`) and a [custom boundary](#the-custom-boundary) of `730px` of `width` and `240px` of `height`. This boundary is, in this examnple, by design, partially behind the left safe space.
|
|
291
|
+
|
|
292
|
+
Now, the _safe size_ will be the result of the custom boundary size minus the safe space area that is overlapping at its left side.
|
|
293
|
+
|
|
294
|
+

|
|
295
|
+
|
|
296
|
+
## The Types
|
|
297
|
+
|
|
298
|
+
### The Overlay Position
|
|
299
|
+
|
|
300
|
+
Means the relative position to the trigger.
|
|
301
|
+
|
|
302
|
+
- **Type:** `OverlayBasePosition`.
|
|
303
|
+
- **From the `enum`:** `POSITION`.
|
|
304
|
+
- **Default:** `'top'`.
|
|
305
|
+
- **Values:**
|
|
306
|
+
- `'top'`.
|
|
307
|
+
- `'bottom'`.
|
|
308
|
+
- `'left'`.
|
|
309
|
+
- `'right'`.
|
|
310
|
+
|
|
311
|
+

|
|
312
|
+
|
|
313
|
+
#### The Overlay Reposition
|
|
314
|
+
|
|
315
|
+
The repositioning will depend on the chosen `position`.
|
|
316
|
+
|
|
317
|
+
For instance, if the overlay position is set to `'top'`, it will check if there is enough space to be placed there. If not, it will try at `'bottom'`, then at `'left'` and finally at `'right'`.
|
|
318
|
+
|
|
319
|
+
The order would be:
|
|
320
|
+
|
|
321
|
+
- `'top'` -> `'bottom'` -> `'left'` -> `'right'`.
|
|
322
|
+
- `'bottom'` -> `'top'` -> `'left'` -> `'right'`.
|
|
323
|
+
- `'left'` -> `'right'` -> `'top'` -> `'bottom'`.
|
|
324
|
+
- `'right'` -> `'left'` -> `'top'` -> `'bottom'`.
|
|
325
|
+
|
|
326
|
+
> **NOTE:** If, at any point, the overlay (especially when `resize` event occurs on the page) doesn't have enough space at any of the allowed sides to fit its maximum size, it will choose the one with [more square area](#the-overlay-reposition-by-square-areas).
|
|
327
|
+
>
|
|
328
|
+
> Check also [the `fluidSize` On or Off](#the-fluid-size-on-or-off).
|
|
329
|
+
|
|
330
|
+
##### The Overlay Reposition by Square Areas
|
|
331
|
+
|
|
332
|
+
In the next scenario, the overlay has a `top` position with `positionsAllowed` set to `'opposite'`, which means that only `top` or `bottom` are allowed.
|
|
333
|
+
|
|
334
|
+
Given the current overlay's maximum size is set to `300px` by `262px`, its height exceeds both allowed sides (`205px` at top and `147px` at bottom), so the overlay will choose the one with more available square area:
|
|
335
|
+
|
|
336
|
+
- top: `770 x 205 = 157850` ✔️.
|
|
337
|
+
- bottom: `770 x 147 = 113190` ❌.
|
|
338
|
+
|
|
339
|
+

|
|
340
|
+

|
|
341
|
+
|
|
342
|
+
### The Overlay Alignment
|
|
343
|
+
|
|
344
|
+
Means the relative alignment to the trigger.
|
|
345
|
+
|
|
346
|
+
- **Type:** `OverlayBaseAlignment`.
|
|
347
|
+
- **From the `enum`:** `ALIGNMENT`.
|
|
348
|
+
- **Default:** `'center'` (or `'start'` if `alignmentsAllowed` is set to `'edges'`).
|
|
349
|
+
- **Values:**
|
|
350
|
+
- `'start'`.
|
|
351
|
+
- `'center'`.
|
|
352
|
+
- `'end'`.
|
|
353
|
+
|
|
354
|
+
#### The Overlay Alignment Horizontally
|
|
355
|
+
|
|
356
|
+
This applies for `top` and `bottom` positions.
|
|
357
|
+
|
|
358
|
+
- `start`: means aligned to the left side of the trigger.
|
|
359
|
+
- `center`: means aligned to the center of the trigger.
|
|
360
|
+
- `end`: means aligned to the right side of the trigger.
|
|
361
|
+
|
|
362
|
+

|
|
363
|
+
|
|
364
|
+
#### The Overlay Alignment Vertically
|
|
365
|
+
|
|
366
|
+
This applies for `left` and `right` positions.
|
|
367
|
+
|
|
368
|
+
- `start`: means aligned to the top side of the trigger.
|
|
369
|
+
- `center`: means aligned to the center of the trigger.
|
|
370
|
+
- `end`: means aligned to the bottom side of the trigger.
|
|
371
|
+
|
|
372
|
+

|
|
373
|
+
|
|
374
|
+
#### The Overlay Realignment
|
|
375
|
+
|
|
376
|
+
The realignment will depend on the chosen `alignment`.
|
|
377
|
+
|
|
378
|
+
For instance, if the overlay alignment is set to `'center'`, it will check if there is enough space to be placed there. If not, it will try at `'start'` and finally at `'end'`.
|
|
379
|
+
|
|
380
|
+
Now, if `alignmentsAllowed` is set to `'edges'`, checking for `'center'` alignment will be completelly ignored, checking first at `'start'` and finally at `'end'`.
|
|
381
|
+
|
|
382
|
+
## The Overlay Base Class
|
|
383
|
+
|
|
384
|
+
This is an abstract class, which is the base of all the main calculations for the overlay to be positioned in the most adequate space.
|
|
385
|
+
|
|
386
|
+
You can create your own components and/or directives and extend them from this class.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
import { OverlayBase } from '@a11y-ngx/overlay-base';
|
|
390
|
+
|
|
391
|
+
@Directive({ ... })
|
|
392
|
+
export class MyCustomOverlay extends OverlayBase {
|
|
393
|
+
constructor(private hostElement: ElementRef) {
|
|
394
|
+
super();
|
|
395
|
+
}
|
|
396
|
+
/* Implement your own logic... */
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### The `setBaseConfig()` Method
|
|
401
|
+
|
|
402
|
+
To set the main config, it accepts a single parameter `customConfig` of type [`OverlayBaseConfig`](#the-configuration-object).
|
|
403
|
+
|
|
404
|
+
### The `attachOverlay()` Method
|
|
405
|
+
|
|
406
|
+
To establish the overlay `HTMLElement` to base all calculations on, start the listeners and receive the final position and alignment for rendering (along with the `maxSize`).
|
|
407
|
+
|
|
408
|
+
If a custom boundary was set in the config, a `scroll` event will be additionally attached to it.
|
|
409
|
+
|
|
410
|
+
Accepts two parameters:
|
|
411
|
+
|
|
412
|
+
- `overlayElement` of type `HTMLElement`.
|
|
413
|
+
- `debounceTimeMs` (_optional_) of type `number` (set to `10` by default).
|
|
414
|
+
|
|
415
|
+
Returns an `Observable` of type [`OverlayBaseCalculatedPosition`](#the-calculated-position) which will be returning data every time [the listeners](#the-listeners) are triggered.
|
|
416
|
+
|
|
417
|
+
#### The Calculated Position
|
|
418
|
+
|
|
419
|
+
- **Type:** `OverlayBaseCalculatedPosition`.
|
|
420
|
+
- **Properties:**
|
|
421
|
+
|
|
422
|
+
| Property | Type | Description |
|
|
423
|
+
|:---------|:-----|:------------|
|
|
424
|
+
| `position` | [`OverlayBasePosition`](#the-overlay-position) | The final position for the overlay |
|
|
425
|
+
| `alignment` | [`OverlayBaseAlignment`](#the-overlay-alignment) | The final alignment for the overlay |
|
|
426
|
+
| `render` | `OverlayBaseRenderPosition` | The (top, bottom, left or right) coordinates depending on the chosen [Position Strategy](#the-position-strategy) |
|
|
427
|
+
| `maxSize` | `OverlayBaseMaxSize` | The possible max size (`width` or `height`) if [`fluidSize`](#the-fluid-size) is set to `true` |
|
|
428
|
+
|
|
429
|
+
### The `detachOverlay()` Method
|
|
430
|
+
|
|
431
|
+
To stop and complete all the listeners (`Subject()` and `BehaviorSubject()`).
|
|
432
|
+
|
|
433
|
+
This will also complete the `isDetached$` subject, ideally so you can listen and kill your `attachOverlay()` subscription.
|
|
434
|
+
|
|
435
|
+
### The `updateOverlaySize()` Method
|
|
436
|
+
|
|
437
|
+
> **IMPORTANT:** The overlay original size is calculated only once, the first time.
|
|
438
|
+
|
|
439
|
+
Since the overlay content could dynamically change (and so its size), in these cases you can make use of this method, to update the overlay `ClientRect` data and calculations will be more precise for its final position and alignment.
|
|
440
|
+
|
|
441
|
+
This will trigger [the `recalculate()` method](#the-recalculate-method).
|
|
442
|
+
|
|
443
|
+
### The `recalculate()` Method
|
|
444
|
+
|
|
445
|
+
To update on demand and return recalculated position/alignment data.
|
|
446
|
+
|
|
447
|
+
### The `triggerBoundaryDistance()` Method
|
|
448
|
+
|
|
449
|
+
Accepts a single parameter `position` of type [`OverlayBasePosition`](#the-overlay-position) and returns a `number` that means the distance (in pixels) between the trigger and the boundary/viewport for that given side.
|
|
450
|
+
|
|
451
|
+
This will take into consideration a given [Custom Boundary](#the-custom-boundary) and/or [Safe Space](#the-safe-space).
|
|
452
|
+
|
|
453
|
+
### The public Properties, Getters and Setters
|
|
454
|
+
|
|
455
|
+
| Name | Type | Of Type | Description |
|
|
456
|
+
|:-----|:-----|:--------|:------------|
|
|
457
|
+
| `uid` | `property` | `number` | A unique auto-incremented value for each overlay created |
|
|
458
|
+
| `getCurrentPosition` | `get` | `OverlayBasePosition` | The position after all calculations. See [the Overlay Position](#the-overlay-position) |
|
|
459
|
+
| `getCurrentAlignment` | `get` | `OverlayBaseAlignment` | The alignment after all calculations. See [the Overlay Alignment](#the-overlay-alignment) |
|
|
460
|
+
| `isTop` | `get` | `boolean` | The position is `top` |
|
|
461
|
+
| `isBottom` | `get` | `boolean` | The position is `bottom` |
|
|
462
|
+
| `isLeft` | `get` | `boolean` | The position is `left` |
|
|
463
|
+
| `isRight` | `get` | `boolean` | The position is `right` |
|
|
464
|
+
| `isTopBottom` | `get` | `boolean` | The position is `top` or `bottom` |
|
|
465
|
+
| `isStart` | `get` | `boolean` | The alignment is `start` |
|
|
466
|
+
| `isCenter` | `get` | `boolean` | The alignment is `center` |
|
|
467
|
+
| `isEnd` | `get` | `boolean` | The alignment is `end` |
|
|
468
|
+
| `viewportSize` | `get` | `ViewportSize` | See [the Viewport Size](#the-viewport-size) |
|
|
469
|
+
| `viewportSizeSafe` | `get` | `ViewportSize` | See [the Viewport Safe Size](#the-viewport-safe-size) |
|
|
470
|
+
| `positionStrategy` | `get`/`set` | `OverlayBasePositionStrategy` | See [the Position Strategy](#the-position-strategy) |
|
|
471
|
+
| `safeSpace` | `get`/`set` | `OverlayBaseSafeSpace` | See [the Safe Space](#the-safe-space) |
|
|
472
|
+
| `offsetSize` | `get`/`set` | `number` | See [the Offset Size](#the-offset-size) |
|
|
473
|
+
| `fluidSize` | `get`/`set` | `boolean` | See [the Fluid Size](#the-fluid-size) |
|
|
474
|
+
| `fluidAlignment` | `get`/`set` | `boolean` | See [the Fluid Alignment](#the-fluid-alignment) |
|
|
475
|
+
| `triggerElement` | `get`/`set` | `HTMLElement` | See [the Trigger element](#the-trigger-element) |
|
|
476
|
+
| `overlayElement` | `get`/`set` | `HTMLElement` | The overlay element |
|
|
477
|
+
| `boundaryElement` | `get`/`set` | `HTMLElement` | See [the Custom Boundary](#the-custom-boundary) |
|
|
478
|
+
| `overlayOutside` | `property` | `OverlayBasePosition` | Provides the side of the viewport/boundary where the overlay would be out of in case `fluidAlignment` is set to `true`. See [the Fluid Alignment On or Off](#the-fluid-alignment-on-or-off) |
|
|
479
|
+
| `maxSize` | `property` | `OverlayBaseMaxSize` | To save the overlay max size and provide a better calculation for the `maxSize` values returned in the [attachOverlay() Method](#the-attachoverlay-method) |
|
|
480
|
+
| `isDetached$` | `property` | `Subject<void>` | So you can use it to kill the `attachOverlay()` subscription. See [the example](#example) |
|
|
481
|
+
|
|
482
|
+
### The Listeners
|
|
483
|
+
|
|
484
|
+
These are the listeners that will be approperly triggered so the overlay can reposition and/or realign.
|
|
485
|
+
|
|
486
|
+
| Listener | Default | Description |
|
|
487
|
+
|:---------|:--------|:------------|
|
|
488
|
+
| Window resize | _Always listening_ | The window changes its size |
|
|
489
|
+
| Page scroll | `true` | See [allow scroll listener](#the-page-scroll-listener) |
|
|
490
|
+
| Custom Boundary scroll | _Always listening_ (if any) | See [the Custom Boundary Scroll listener](#the-custom-boundary-scroll-listener) |
|
|
491
|
+
| _Force update_ | _Always listening_ | If the [`recalculate()` method](#the-recalculate-method) is invoked |
|
|
492
|
+
|
|
493
|
+
#### The Page Scroll Listener
|
|
494
|
+
|
|
495
|
+
To allow listening for page scrolling.
|
|
496
|
+
|
|
497
|
+
- **Property:** `allowScrollListener`.
|
|
498
|
+
- **Type:** `boolean`.
|
|
499
|
+
- **Default:** `true`.
|
|
500
|
+
|
|
501
|
+
This option can be set to `false` (to avoid extra listeners) in case you are **absolutely sure** the trigger won't change its position, such as within a fixed landmark.
|
|
502
|
+
|
|
503
|
+
Whenever `allowScrollListener` is set to `true` and there is a [trigger](#the-trigger-element) of type `HTMLElement` present, the listener will execute the repositioning in case the page gets scrolled.
|
|
504
|
+
|
|
505
|
+
> **IMPORTANT:** If a `DOMRect` is established as the trigger, the page scroll will not be used as a listener.
|
|
506
|
+
|
|
507
|
+
#### The Custom Boundary Scroll Listener
|
|
508
|
+
|
|
509
|
+
If a [custom boundary](#the-custom-boundary) was set and, at some point, has overflow and gets scrolled, the listener will be executed to check if the overlay reached any of the boundary limits and will reposition itself, if necessary.
|
|
510
|
+
|
|
511
|
+
## Example
|
|
512
|
+
|
|
513
|
+
This is a quick example on how to crate a basic tooltip directive extending the `OverlayBase` class.
|
|
514
|
+
|
|
515
|
+
**Tooltip Directive:**
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import { Directive, Input, ElementRef, OnDestroy } from '@angular/core';
|
|
519
|
+
import { Subject, merge } from 'rxjs';
|
|
520
|
+
import { takeUntil } from 'rxjs/operators';
|
|
521
|
+
|
|
522
|
+
import { OverlayBase } from '@a11y-ngx/overlay-base';
|
|
523
|
+
|
|
524
|
+
@Directive({
|
|
525
|
+
selector: '[tooltip]',
|
|
526
|
+
standalone: true,
|
|
527
|
+
host: {
|
|
528
|
+
'(mouseenter)': 'createTooltip()',
|
|
529
|
+
'(mouseleave)': 'destroyTooltip()',
|
|
530
|
+
'[attr.aria-labelledby]': 'tooltipElement ? id : null',
|
|
531
|
+
},
|
|
532
|
+
})
|
|
533
|
+
export class MyCustomTooltip extends OverlayBase implements OnDestroy {
|
|
534
|
+
@Input() tooltip: string;
|
|
535
|
+
|
|
536
|
+
private get id(): string {
|
|
537
|
+
return `my-custom-tooltip-${this.uid}`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private readonly destroy$: Subject<void> = new Subject<void>();
|
|
541
|
+
|
|
542
|
+
private tooltipElement: HTMLDivElement | undefined = undefined;
|
|
543
|
+
|
|
544
|
+
constructor(private hostElement: ElementRef) {
|
|
545
|
+
super();
|
|
546
|
+
|
|
547
|
+
// 1. We set the basic config needed for our tooltip
|
|
548
|
+
this.setBaseConfig({
|
|
549
|
+
// Trigger: mandatory, it is where the overlay will calculate its position from
|
|
550
|
+
trigger: this.hostElement.nativeElement,
|
|
551
|
+
// Position: We want it to open at bottom
|
|
552
|
+
position: 'bottom',
|
|
553
|
+
// Offset: We want a 10px distance between trigger & overlay
|
|
554
|
+
offsetSize: 10,
|
|
555
|
+
// Boundary: We want the <main> element as the boundary
|
|
556
|
+
boundary: document.querySelector('main'),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
ngOnDestroy(): void {
|
|
561
|
+
this.destroy$.next();
|
|
562
|
+
this.destroy$.complete();
|
|
563
|
+
this.destroyTooltip();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 2. We create the tooltip on (mouseenter) event
|
|
567
|
+
private createTooltip(): void {
|
|
568
|
+
// We create a new <div> element and set the id and inner text
|
|
569
|
+
this.tooltipElement = document.createElement('div');
|
|
570
|
+
this.tooltipElement.id = this.id;
|
|
571
|
+
this.tooltipElement.innerText = this.tooltip;
|
|
572
|
+
|
|
573
|
+
// We use 'opacity' instead of 'display' so the Base class
|
|
574
|
+
// can check the dimensions for a proper positioning
|
|
575
|
+
this.tooltipElement.style.opacity = '0';
|
|
576
|
+
// 'positionStrategy' is already set to 'fixed' by default
|
|
577
|
+
this.tooltipElement.style.position = this.positionStrategy;
|
|
578
|
+
// We set some other basic styles
|
|
579
|
+
this.tooltipElement.style.border = '1px solid #0c0b46';
|
|
580
|
+
this.tooltipElement.style.backgroundColor = '#bab6ff';
|
|
581
|
+
this.tooltipElement.style.color = '#0c0b46';
|
|
582
|
+
this.tooltipElement.style.padding = '5px 10px';
|
|
583
|
+
this.tooltipElement.style.borderRadius = '5px';
|
|
584
|
+
|
|
585
|
+
// We append it to the body
|
|
586
|
+
document.body.appendChild(this.tooltipElement);
|
|
587
|
+
|
|
588
|
+
// We tell the Base class to attach the overlay and
|
|
589
|
+
// we will subscribe to handle set the position
|
|
590
|
+
this.attachOverlay(this.tooltipElement)
|
|
591
|
+
// We kill the subscription when the overlay is detached
|
|
592
|
+
// or this directive gets destroyed
|
|
593
|
+
.pipe(takeUntil(merge(this.destroy$, this.isDetached$)))
|
|
594
|
+
.subscribe(({ render: { top, bottom, left, right } }) => {
|
|
595
|
+
this.tooltipElement.style.top = top !== null ? `${top}px` : null;
|
|
596
|
+
this.tooltipElement.style.bottom = bottom !== null ? `${bottom}px` : null;
|
|
597
|
+
this.tooltipElement.style.left = left !== null ? `${left}px` : null;
|
|
598
|
+
this.tooltipElement.style.right = right !== null ? `${right}px` : null;
|
|
599
|
+
|
|
600
|
+
// We make the overlay fully visible now
|
|
601
|
+
this.tooltipElement.style.opacity = '1';
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// 3. We destroy the tooltip on (mouseleave) event
|
|
606
|
+
private destroyTooltip(): void {
|
|
607
|
+
this.detachOverlay();
|
|
608
|
+
this.tooltipElement.remove();
|
|
609
|
+
this.tooltipElement = undefined;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**On your component:**
|
|
615
|
+
|
|
616
|
+
Add `MyCustomTooltip` to your component or module `import` array, and add it to a `button`.
|
|
617
|
+
|
|
618
|
+
```html
|
|
619
|
+
<button type="button" class="btn btn-dark" tooltip="Something beautiful">
|
|
620
|
+
<i class="fa-solid fa-circle-question"></i>
|
|
621
|
+
</button>
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**Result:**
|
|
625
|
+
|
|
626
|
+
The tooltip will open with position 'bottom' (as it was defined in the config) and alignment 'start' because it's respecting the boundary limit.
|
|
627
|
+
|
|
628
|
+

|
|
629
|
+
|
|
630
|
+
If we had chosen not to set the boundary, the tooltip alignment would have been centered by default.
|
|
631
|
+
|
|
632
|
+

|