@jack-henry/jh-elements 2.0.0-beta.10

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.
Files changed (39) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +26 -0
  3. package/README.md +13 -0
  4. package/components/badge/badge.js +73 -0
  5. package/components/button/button.js +953 -0
  6. package/components/card/card.js +343 -0
  7. package/components/checkbox/checkbox.js +601 -0
  8. package/components/checkbox-group/checkbox-group.js +241 -0
  9. package/components/divider/divider.js +91 -0
  10. package/components/icon/icon.js +96 -0
  11. package/components/input/input.js +1247 -0
  12. package/components/input-email/input-email.js +18 -0
  13. package/components/input-password/input-password.js +153 -0
  14. package/components/input-search/input-search.js +41 -0
  15. package/components/input-telephone/input-telephone.js +18 -0
  16. package/components/input-textarea/input-textarea.js +379 -0
  17. package/components/input-url/input-url.js +31 -0
  18. package/components/list-group/list-group.js +103 -0
  19. package/components/list-item/list-item.js +350 -0
  20. package/components/menu/menu.js +60 -0
  21. package/components/notification/notification.js +327 -0
  22. package/components/progress/progress.js +417 -0
  23. package/components/radio/radio.js +422 -0
  24. package/components/radio-group/radio-group.js +400 -0
  25. package/components/switch/switch.js +315 -0
  26. package/components/table/table.js +367 -0
  27. package/components/table-data-cell/table-data-cell.js +107 -0
  28. package/components/table-header-cell/table-header-cell.js +321 -0
  29. package/components/table-row/table-row.js +52 -0
  30. package/components/tag/tag.js +422 -0
  31. package/components/tag-group/tag-group.js +57 -0
  32. package/components/toast/toast.js +172 -0
  33. package/components/toast-controller/toast-controller.js +122 -0
  34. package/components/tooltip/tooltip.js +498 -0
  35. package/custom-elements.json +6892 -0
  36. package/index.d.ts +69 -0
  37. package/jsconfig.json +9 -0
  38. package/package.json +52 -0
  39. package/utils/themeProvider.js +32 -0
@@ -0,0 +1,122 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { LitElement, css, html } from 'lit';
6
+ import '../toast/toast.js';
7
+
8
+ /**
9
+ * @cssprop --jh-toast-controller-z-index - The toast controller z-index. Defaults to `--jh-z-index-positive-1000`.
10
+ * @slot default - Use to insert `<jh-toast>` components if appending toasts manually.
11
+ * @event jh-dismiss - Dispatched when the toast controller dismisses the oldest toast, and when toasts are dismissed manually by the user.
12
+ *
13
+ * @customElement jh-toast-controller
14
+ */
15
+ export class JhToastController extends LitElement {
16
+ static get styles() {
17
+ return css`
18
+ :host {
19
+ z-index: var(--jh-toast-controller-z-index, var(--jh-z-index-positive-1000));
20
+ bottom: var(--jh-dimension-800);
21
+ left: var(--jh-dimension-800);
22
+ position: fixed;
23
+ }
24
+ ::slotted(jh-toast) {
25
+ margin-top: var(--jh-dimension-200);
26
+ }
27
+ `;
28
+ }
29
+
30
+ static get properties() {
31
+ return {
32
+ /** Sets the maximum number of toasts to be displayed at a time. */
33
+ maxCount: { type: Number, attribute: 'max-count' },
34
+ /** Sets the role of the toast controller and establishes a live region to expose changes to assistive technologies. */
35
+ role: { type: String },
36
+ };
37
+ }
38
+
39
+ constructor() {
40
+ super();
41
+ /** @type {number} */
42
+ this.maxCount = 3;
43
+ /** @type {'status'|'alert'} */
44
+ this.role = 'status';
45
+
46
+ window.addEventListener('jh-create-toast', this.#createToast.bind(this));
47
+ }
48
+
49
+ connectedCallback() {
50
+ super.connectedCallback();
51
+ if (!this.getAttribute('role')) {
52
+ this.setAttribute('role', this.role);
53
+ }
54
+ }
55
+
56
+ #handleSlotChange() {
57
+ let currentToasts = this.children.length;
58
+
59
+ if (currentToasts > this.maxCount) {
60
+ let extraToast = currentToasts - this.maxCount;
61
+ for (let i = 0; i < extraToast; i++) {
62
+ this.#dispatch('jh-dismiss', this.children[i]);
63
+ }
64
+ }
65
+ }
66
+
67
+ // controller dispatches jh-dismiss event and calls handleDismiss method
68
+ #dispatch(name, toast) {
69
+ this.dispatchEvent(
70
+ new CustomEvent(name, {
71
+ bubbles: true,
72
+ cancelable: true,
73
+ composed: true,
74
+ })
75
+ );
76
+ this.#handleDismiss(toast);
77
+ }
78
+
79
+ // adds dismiss animation to toast and removes from DOM once completed
80
+ #handleDismiss(toast) {
81
+ toast.classList.add('remove');
82
+ toast.addEventListener("animationend", () => {
83
+ toast.remove();
84
+ });
85
+ }
86
+
87
+ #createToast(e) {
88
+ const toast = document.createElement('jh-toast');
89
+ const {
90
+ toastAction,
91
+ toastDismissIcon,
92
+ text,
93
+ toastIcon,
94
+ dismissButtonAccessibleLabel,
95
+ appearance,
96
+ hideDismissButton,
97
+ timeout,
98
+ stacked,
99
+ } = e.detail;
100
+
101
+ toastAction ? toast.innerHTML += toastAction : null;
102
+ toastDismissIcon ? toast.innerHTML += toastDismissIcon : null;
103
+ text ? toast.innerHTML += text : null;
104
+ toastIcon ? toast.innerHTML += toastIcon : null;
105
+ dismissButtonAccessibleLabel ? toast.setAttribute('dismiss-button-accessible-label', dismissButtonAccessibleLabel) : null;
106
+ appearance ? toast.setAttribute('appearance', appearance) : null;
107
+ hideDismissButton ? toast.setAttribute('hide-dismiss-button', '') : null;
108
+ timeout >= 0 ? toast.setAttribute('timeout', timeout) : null;
109
+ stacked ? toast.setAttribute('stacked', '') : null;
110
+
111
+ this.appendChild(toast);
112
+ }
113
+
114
+ render() {
115
+ return html`
116
+ <slot @slotchange=${this.#handleSlotChange}></slot>
117
+ `;
118
+ }
119
+ }
120
+ customElements.define('jh-toast-controller', JhToastController);
121
+
122
+
@@ -0,0 +1,498 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { LitElement, css, html } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+
8
+ let id = 0;
9
+ const openAttr = 'open';
10
+
11
+ /**
12
+ * @cssprop --jh-tooltip-color-background - The tooltip and arrow background-color. Defaults to `--jh-color-content-primary-enabled`.
13
+ * @cssprop --jh-tooltip-color-text - The tooltip text color. Defaults to `--jh-color-content-on-primary-enabled`.
14
+ *
15
+ * @slot default - Use to insert the element that triggers the tooltip.
16
+ *
17
+ * @customElement jh-tooltip
18
+ */
19
+ export class JhTooltip extends LitElement {
20
+ /** @type {ElementInternals} */
21
+ #internals;
22
+
23
+ static get styles() {
24
+ return css`
25
+ :host {
26
+ display: inline-block;
27
+ position: relative;
28
+ }
29
+ :host span {
30
+ background-color: var(
31
+ --jh-tooltip-color-background,
32
+ var(--jh-color-content-primary-enabled)
33
+ );
34
+ color: var(
35
+ --jh-tooltip-color-text,
36
+ var(--jh-color-content-on-primary-enabled)
37
+ );
38
+ border-radius: var(--jh-border-radius-100);
39
+ padding: var(--jh-dimension-200);
40
+ font-family: var(--jh-font-helper-regular-font-family);
41
+ font-weight: var(--jh-font-helper-regular-font-weight);
42
+ font-size: var(--jh-font-helper-regular-font-size);
43
+ line-height: var(--jh-font-helper-regular-line-height);
44
+ max-width: 160px;
45
+ width: max-content;
46
+ position: absolute;
47
+ box-sizing: border-box;
48
+ z-index: 1061;
49
+ word-wrap: break-word;
50
+ display: inline-block;
51
+ transition: visibility cubic-bezier(0.1, 0.5, 0.1, 1) 0.09s;
52
+ visibility: hidden;
53
+ opacity: 0;
54
+ }
55
+ span::before {
56
+ position: absolute;
57
+ z-index: 1060;
58
+ width: 0;
59
+ height: 0;
60
+ pointer-events: none;
61
+ content: '';
62
+ border: 4px solid transparent;
63
+ }
64
+ :host span.show {
65
+ transition: opacity 0.3s cubic-bezier(0.1, 0.5, 0.1, 1) 0s;
66
+ visibility: visible;
67
+ opacity: 1;
68
+ }
69
+
70
+ /* padding and margin used to extend hover surface for tooltip persistence */
71
+ :host([position='bottom-center']),
72
+ :host([position='bottom-start']),
73
+ :host([position='bottom-end']) {
74
+ padding-bottom: 8px;
75
+ margin-bottom: -8px;
76
+ }
77
+ :host([position='top-center']),
78
+ :host([position='top-start']),
79
+ :host([position='top-end']) {
80
+ padding-top: 8px;
81
+ margin-top: -8px;
82
+ }
83
+ :host([position='left']) {
84
+ padding-left: 8px;
85
+ margin-left: -8px;
86
+ }
87
+ :host([position='right']) {
88
+ padding-right: 8px;
89
+ margin-right: -8px;
90
+ }
91
+
92
+ /* placement of tooltip and arrow. Default is top-center */
93
+ :host([position='bottom-center']) span,
94
+ :host([position='bottom-start']) span,
95
+ :host([position='bottom-end']) span {
96
+ top: 100%;
97
+ right: 50%;
98
+ }
99
+ :host([position='bottom-center']) span::before,
100
+ :host([position='bottom-start']) span::before,
101
+ :host([position='bottom-end']) span::before {
102
+ border-bottom-color: var(
103
+ --jh-tooltip-color-background,
104
+ var(--jh-color-content-primary-enabled)
105
+ );
106
+ top: auto;
107
+ bottom: 100%;
108
+ }
109
+ :host([position='bottom-center']) span::before {
110
+ margin-right: calc(var(--jh-dimension-200) * -0.5);
111
+ right: 50%;
112
+ }
113
+ :host([position='bottom-start']) span::before {
114
+ left: var(--jh-dimension-200);
115
+ }
116
+ :host([position='bottom-end']) span::before {
117
+ right: var(--jh-dimension-200);
118
+ }
119
+ :host([position='bottom-start']) span {
120
+ margin-left: calc(var(--jh-dimension-200) * -1.5);
121
+ right: auto;
122
+ left: 50%;
123
+ }
124
+ :host([position='bottom-end']) span {
125
+ margin-right: calc(var(--jh-dimension-200) * -1.5);
126
+ }
127
+ :host([position='top-center']) span,
128
+ :host([position='top-start']) span,
129
+ :host([position='top-end']) span {
130
+ right: 50%;
131
+ bottom: 100%;
132
+ }
133
+ :host([position='top-center']) span::before,
134
+ :host([position='top-start']) span::before,
135
+ :host([position='top-end']) span::before {
136
+ border-top-color: var(
137
+ --jh-tooltip-color-background,
138
+ var(--jh-color-content-primary-enabled)
139
+ );
140
+ top: 100%;
141
+ bottom: auto;
142
+ }
143
+ :host([position='top-center']) span::before {
144
+ margin-right: calc(var(--jh-dimension-200) * -0.5);
145
+ right: 50%;
146
+ }
147
+ :host([position='top-start']) span::before {
148
+ left: var(--jh-dimension-200);
149
+ }
150
+ :host([position='top-end']) span::before {
151
+ right: var(--jh-dimension-200);
152
+ }
153
+ :host([position='top-start']) span {
154
+ margin-left: calc(var(--jh-dimension-200) * -1.5);
155
+ right: auto;
156
+ left: 50%;
157
+ }
158
+ :host([position='top-end']) span {
159
+ margin-right: calc(var(--jh-dimension-200) * -1.5);
160
+ }
161
+ :host([position='bottom-center']) span,
162
+ :host([position='top-center']) span {
163
+ transform: translateX(50%);
164
+ }
165
+ :host([position='left']) span {
166
+ right: 100%;
167
+ bottom: 50%;
168
+ transform: translateY(50%);
169
+ }
170
+ :host([position='left']) span::before {
171
+ border-left-color: var(
172
+ --jh-tooltip-color-background,
173
+ var(--jh-color-content-primary-enabled)
174
+ );
175
+ margin-top: calc(var(--jh-dimension-200) * -0.5);
176
+ top: 50%;
177
+ bottom: 50%;
178
+ left: 100%;
179
+ }
180
+ :host([position='right']) span {
181
+ bottom: 50%;
182
+ left: 100%;
183
+ transform: translateY(50%);
184
+ }
185
+ :host([position='right']) span::before {
186
+ border-right-color: var(
187
+ --jh-tooltip-color-background,
188
+ var(--jh-color-content-primary-enabled)
189
+ );
190
+ margin-top: calc(var(--jh-dimension-200) * -0.5);
191
+ top: 50%;
192
+ right: 100%;
193
+ bottom: 50%;
194
+ }
195
+ `;
196
+ }
197
+
198
+ static get properties() {
199
+ return {
200
+ /**
201
+ * Determines whether the tooltip flips to a different position when it reaches the edge of the viewport.
202
+ */
203
+ flipDisabled: {
204
+ type: Boolean,
205
+ attribute: 'flip-disabled',
206
+ },
207
+ /**
208
+ * Provides information about the item which triggered the tooltip.
209
+ */
210
+ label: {
211
+ type: String,
212
+ },
213
+ /**
214
+ * Determines whether the tooltip is open or closed. Can be set on the tooltip to force it open.
215
+ */
216
+ open: {
217
+ type: Boolean,
218
+ reflect: true,
219
+ },
220
+ /**
221
+ * The position of the tooltip and its arrow.
222
+ */
223
+ position: {
224
+ type: String,
225
+ reflect: true,
226
+ },
227
+ };
228
+ }
229
+
230
+ constructor() {
231
+ super();
232
+ this.#internals = this.attachInternals();
233
+ /**@type {?Boolean} */
234
+ this.flipDisabled = false;
235
+ /** @type {?string} */
236
+ this.label = null;
237
+ /**@type {?Boolean} */
238
+ this.open = false;
239
+ /** @type {?string} */
240
+ this.position = 'top-center';
241
+ }
242
+
243
+ connectedCallback() {
244
+ super.connectedCallback();
245
+ /** @ignore */
246
+ this.id = `tooltip-describedby-${id++}`;
247
+ let observer = new MutationObserver(this.#handleEmptyLabel.bind(this));
248
+ let options = {
249
+ childList: true,
250
+ };
251
+ observer.observe(this.shadowRoot, options);
252
+ }
253
+
254
+ #handleEmptyLabel(mutations) {
255
+ for (let mutation of mutations) {
256
+ const addedNodes = Array.from(mutation.addedNodes);
257
+ //check if any of the added nodes is a span
258
+ const spanAdded = addedNodes.some(
259
+ (addedNode) => addedNode.tagName === 'SPAN'
260
+ );
261
+
262
+ if (spanAdded) {
263
+ this.#internals.role = 'tooltip';
264
+ this.addEventListener('focus', this.#handleOpenTooltip, true);
265
+ this.addEventListener('mouseenter', this.#handleOpenTooltip);
266
+ this.addEventListener('blur', this.#handleCloseTooltip, true);
267
+ this.addEventListener('mouseleave', this.#handleCloseTooltip);
268
+ this.addEventListener('keydown', this.#handleKeyDown);
269
+ }
270
+
271
+ if (mutation.removedNodes.length > 0) {
272
+ this.#internals.role = '';
273
+ this.removeEventListener('focus', this.#handleOpenTooltip);
274
+ this.removeEventListener('mouseenter', this.#handleOpenTooltip);
275
+ this.removeEventListener('blur', this.#handleCloseTooltip);
276
+ this.removeEventListener('mouseleave', this.#handleCloseTooltip);
277
+ this.removeEventListener('keydown', this.#handleKeyDown);
278
+ }
279
+ }
280
+ }
281
+
282
+ disconnectedCallback() {
283
+ super.disconnectedCallback();
284
+ this.removeEventListener('focus', this.#handleOpenTooltip);
285
+ this.removeEventListener('mouseenter', this.#handleOpenTooltip);
286
+ this.removeEventListener('blur', this.#handleCloseTooltip);
287
+ this.removeEventListener('mouseleave', this.#handleCloseTooltip);
288
+ this.removeEventListener('keydown', this.#handleKeyDown);
289
+ }
290
+
291
+ #handleSlotChange() {
292
+ //get id set previously in the constructor and set it to reference aria-describedby on the first element in the slot
293
+ const tooltipId = this.getAttribute('id');
294
+ this.firstElementChild.setAttribute('aria-describedby', tooltipId);
295
+
296
+ //get display property of element in slot and set it on the jh-tooltip.
297
+ const tooltipDisplay = window.getComputedStyle(this.firstElementChild).display;
298
+ this.style.setProperty('display', tooltipDisplay );
299
+ }
300
+
301
+ //calls flipTooltip whenever the tooltip is updated, including manually opened with 'open' property.
302
+ updated() {
303
+ if (this.open) this.#flipTooltip();
304
+ }
305
+
306
+ #handleKeyDown(e) {
307
+ if (e.key === 'Escape') this.#handleCloseTooltip();
308
+ }
309
+
310
+ #handleOpenTooltip() {
311
+ this.setAttribute(openAttr, '');
312
+ }
313
+
314
+ #handleCloseTooltip() {
315
+ this.removeAttribute(openAttr);
316
+ }
317
+
318
+ //method to flip the tooltip if it is not fully visible on the viewport
319
+ #flipTooltip() {
320
+ //get current position from attribute on hover
321
+ const currentPosition = this.getAttribute('position');
322
+
323
+ //check if current position is a valid position otherwise make it fail.
324
+ if (!['left', 'right', 'top-start', 'top-end', 'top-center', 'bottom-start', 'bottom-end', 'bottom-center'].includes(currentPosition)) return;
325
+
326
+ if (this.flipDisabled === false && this.label) {
327
+
328
+ //break current position into tooltop position and arrow position
329
+ const [currentTooltip, currentArrow] = currentPosition.split('-');
330
+
331
+ //get an array of the available positions
332
+ const availablePositions = this.#getValidPositions();
333
+
334
+ //check if position selected by developer is available
335
+ const currentPositionAvailable = (position) =>
336
+ currentPosition === position;
337
+
338
+ //check if there is an available position with the same tooltipPosition but a different arrowPosition (top-end -> top-center)
339
+ const hasSameTooltipDifferentArrow = (position) =>
340
+ currentTooltip === position.split('-')[0];
341
+
342
+ //check if the opposite position is available
343
+ const hasOppositePosition = (position) => {
344
+ const [newTooltip, newArrow] = position.split('-');
345
+
346
+ return (
347
+ (currentPosition === 'left' && position === 'right') ||
348
+ (currentPosition === 'right' && position === 'left') ||
349
+ (currentTooltip === 'bottom' &&
350
+ newTooltip === 'top' &&
351
+ currentArrow === newArrow) ||
352
+ (currentTooltip === 'top' &&
353
+ newTooltip === 'bottom' &&
354
+ currentArrow === newArrow)
355
+ );
356
+ };
357
+
358
+ //set the position attribute if the position selected by developer is valid but not available.
359
+ if (!availablePositions.some(currentPositionAvailable)) {
360
+ const newPosition =
361
+ availablePositions.find(hasSameTooltipDifferentArrow) ||
362
+ availablePositions.find(hasOppositePosition) ||
363
+ availablePositions[0];
364
+
365
+ this.setAttribute('position', newPosition);
366
+ }
367
+ }
368
+ }
369
+
370
+ //method to check if the tooltip is fully visible on the screen
371
+ #getValidPositions() {
372
+ const { tooltipWidth, tooltipHeight, elemHeight, elemWidth } =
373
+ this.#getDimensions();
374
+
375
+ const { elemLeft, elemRight, elemTop, elemBottom } = this.#getCoordinates();
376
+
377
+ //Returns false if tooltip with position top (start/center/end) is out of the screen on the top
378
+ const topOutTop = elemTop - tooltipHeight > 0;
379
+
380
+ //Returns false if tooltip with position bottom (start/center/end) is out of the screen on the bottom
381
+ const bottomOutBottom = elemBottom - tooltipHeight > 0;
382
+
383
+ //Returns false if tooltip with position right is out of the screen on the right
384
+ const rightOutRight = elemRight - tooltipWidth > 0;
385
+
386
+ //Returns false if tooltip with position left is out of the screen on the left
387
+ const leftOutLeft = elemLeft - tooltipWidth > 0;
388
+
389
+ //Returns false if tooltip with position left or right is out of the screen on the top
390
+ const sideOutTop = this.#calculateSimmetricPositions(
391
+ elemTop,
392
+ elemHeight,
393
+ tooltipHeight
394
+ );
395
+
396
+ //Returns false if tooltip with position left or right is out of the screen on the bottom
397
+ const sideOutBottom = this.#calculateSimmetricPositions(
398
+ elemBottom,
399
+ elemHeight,
400
+ tooltipHeight
401
+ );
402
+
403
+ //Returns false if tooltip with position top-start or bottom-start is out of the screen on the right
404
+ const startOutRight = window.innerWidth - elemLeft - tooltipWidth > 0;
405
+
406
+ //Returns false if tooltip with position top-start or bottom-start is out of the screen on the left
407
+ const startOutLeft = elemLeft > 0;
408
+
409
+ //Returns false if tooltip with position top-center or bottom-center is out of the screen on the right
410
+ const centerOutRight = this.#calculateSimmetricPositions(
411
+ elemRight,
412
+ elemWidth,
413
+ tooltipWidth
414
+ );
415
+
416
+ //Returns false if tooltip with position top-center or bottom-center is out of the screen on the left
417
+ const centerOutLeft = this.#calculateSimmetricPositions(
418
+ elemLeft,
419
+ elemWidth,
420
+ tooltipWidth
421
+ );
422
+
423
+ //Returns false if tooltip with position top-end or bottom-end is out of the screen on the right
424
+ const endOutRight = elemRight > 0;
425
+
426
+ //Returns false if tooltip with position top-end or bottom-end is out of the screen on the left
427
+ const endOutLeft = elemLeft + elemWidth - tooltipWidth > 0;
428
+
429
+ //returns true if the 3 conditions are met. Means the tooltip is fully visible on the screen in that position.
430
+ const allPositions = {
431
+ left: leftOutLeft && sideOutTop && sideOutBottom,
432
+ right: rightOutRight && sideOutTop && sideOutBottom,
433
+ 'top-start': topOutTop && startOutRight && startOutLeft,
434
+ 'bottom-start': bottomOutBottom && startOutRight && startOutLeft,
435
+ 'top-center': topOutTop && centerOutRight && centerOutLeft,
436
+ 'bottom-center': bottomOutBottom && centerOutRight && centerOutLeft,
437
+ 'top-end': topOutTop && endOutRight && endOutLeft,
438
+ 'bottom-end': bottomOutBottom && endOutRight && endOutLeft,
439
+ };
440
+
441
+ //add valid positions to an array and return it.
442
+ const validPositions = Object.entries(allPositions).reduce(
443
+ (positions, [key, value]) => (value ? [...positions, key] : positions),
444
+ []
445
+ );
446
+ return validPositions;
447
+ }
448
+
449
+ //get the dimensions of the tooltip and the originating element
450
+ #getDimensions() {
451
+ return {
452
+ tooltipWidth: this.shadowRoot
453
+ .querySelector('span')
454
+ .getBoundingClientRect().width,
455
+ tooltipHeight: this.shadowRoot
456
+ .querySelector('span')
457
+ .getBoundingClientRect().height,
458
+ elemHeight: this.getBoundingClientRect().height,
459
+ elemWidth: this.getBoundingClientRect().width,
460
+ };
461
+ }
462
+
463
+ //get the coordinates of the 4 edges of the originating element
464
+ #getCoordinates() {
465
+ return {
466
+ elemLeft: this.getBoundingClientRect().left,
467
+ elemRight: window.innerWidth - this.getBoundingClientRect().right,
468
+ elemTop: this.getBoundingClientRect().top,
469
+ elemBottom: window.innerHeight - this.getBoundingClientRect().bottom,
470
+ };
471
+ }
472
+
473
+ #calculateSimmetricPositions(elemEdge, elemDimension, tooltipDimension) {
474
+ return elemEdge + elemDimension / 2 - tooltipDimension / 2 > 0;
475
+ }
476
+
477
+ render() {
478
+ let label;
479
+
480
+ if (this.label) {
481
+ label = html`
482
+ <span
483
+ class=${ifDefined(this.open ? 'show' : null)}
484
+ aria-hidden=${this.open ? 'false' : 'true'}
485
+ >
486
+ ${this.label}
487
+ </span>
488
+ `;
489
+ }
490
+
491
+ return html`
492
+ <slot @slotchange=${this.#handleSlotChange}></slot>
493
+ ${label}
494
+ `;
495
+ }
496
+ }
497
+
498
+ customElements.define('jh-tooltip', JhTooltip);