44 * Uses LoadableScript pattern for optimized loading
55 */
66
7- import type { ShareData } from '@components/Social/common'
7+ import type { ShareData , SharePlatform } from '@components/Social/common'
88import { platforms , nativeShare } from '@components/Social/common'
99import { MastodonModal } from '@components/Social/Mastodon/client'
10+ import { initializeTooltipHost } from '@components/Tooltip/client'
1011import { addScriptBreadcrumb } from '@components/scripts/errors'
1112import { handleScriptError } from '@components/scripts/errors/handler'
1213import {
@@ -25,11 +26,16 @@ import {
2526 queryShareButtons ,
2627 queryShareDialog ,
2728 queryShareIcon ,
29+ queryShareTooltipHosts ,
2830} from './selectors'
2931
3032const SCRIPT_NAME = 'Highlighter'
3133const COMPONENT_TAG_NAME = 'highlighter-element'
3234const ICON_BANK_ID = 'highlighter-icon-bank'
35+ const TOOLTIP_TRIGGER_CLASSES =
36+ 'inline-flex items-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-spotlight'
37+ const TOOLTIP_POPUP_CLASSES =
38+ 'pointer-events-none absolute left-1/2 top-full z-(--z-content-floating) mt-2 hidden -translate-x-1/2 max-w-64 rounded-md border border-trim bg-page-inverse px-2 py-1 text-sm leading-tight text-page-base shadow-elevated whitespace-nowrap'
3339
3440let highlighterInstanceCounter = 0
3541const VISIBLE_SHARE_LABEL = 'Share Selection'
@@ -100,13 +106,15 @@ export class HighlighterElement extends LitElement {
100106 this . bindTriggerButton ( )
101107 this . bindWrapperListeners ( )
102108 this . bindShareButtons ( )
109+ this . initializeShareTooltips ( )
103110 this . applyThemeStyles ( )
104111 }
105112
106113 protected override updated ( ) : void {
107114 this . bindTriggerButton ( )
108115 this . bindWrapperListeners ( )
109116 this . bindShareButtons ( )
117+ this . initializeShareTooltips ( )
110118 this . applyThemeStyles ( )
111119 }
112120
@@ -144,19 +152,7 @@ export class HighlighterElement extends LitElement {
144152 >
145153 < span class ="share-dialog__text "> ${ VISIBLE_SHARE_LABEL } </ span >
146154 < span class ="share-dialog__buttons ">
147- ${ platforms . map (
148- platform => html `
149- < button
150- type ="button "
151- class ="share-button "
152- data-platform ="${ platform . id } "
153- aria-label ="${ platform . ariaLabel } "
154- title ="${ platform . ariaLabel } "
155- >
156- ${ this . renderPlatformIcon ( platform . id ) }
157- </ button >
158- `
159- ) }
155+ ${ platforms . map ( platform => this . renderShareButton ( platform ) ) }
160156 </ span >
161157 < span class ="share-dialog__arrow "> </ span >
162158 </ span >
@@ -268,6 +264,31 @@ export class HighlighterElement extends LitElement {
268264 return iconMarkup ? unsafeHTML ( iconMarkup ) : null
269265 }
270266
267+ private renderShareButton ( platform : SharePlatform ) {
268+ return html `
269+ < site-tooltip class ="relative inline-flex ">
270+ < span data-tooltip-trigger class ="${ TOOLTIP_TRIGGER_CLASSES } ">
271+ < button
272+ type ="button "
273+ class ="share-button "
274+ data-platform ="${ platform . id } "
275+ aria-label ="${ platform . ariaLabel } "
276+ >
277+ ${ this . renderPlatformIcon ( platform . id ) }
278+ </ button >
279+ </ span >
280+ < span
281+ data-tooltip-popup
282+ role ="tooltip "
283+ aria-hidden ="true "
284+ class ="${ TOOLTIP_POPUP_CLASSES } "
285+ >
286+ ${ platform . ariaLabel }
287+ </ span >
288+ </ site-tooltip >
289+ `
290+ }
291+
271292 private getIconMarkup ( iconName : string ) : string | null {
272293 if ( ! iconName ) {
273294 return null
@@ -306,6 +327,11 @@ export class HighlighterElement extends LitElement {
306327 }
307328 }
308329
330+ private initializeShareTooltips ( ) : void {
331+ const tooltipHosts = queryShareTooltipHosts ( this )
332+ tooltipHosts . forEach ( host => initializeTooltipHost ( host ) )
333+ }
334+
309335 private applyFocusVisibleStyles ( ) : void {
310336 if ( ! this . triggerButton ) {
311337 return
@@ -403,12 +429,28 @@ export class HighlighterElement extends LitElement {
403429 private getShareData ( ) : ShareData {
404430 const text = this . getHighlightedText ( )
405431 return {
406- text : `" ${ text } "` ,
432+ text,
407433 url : window . location . href ,
408434 title : document . title ,
409435 }
410436 }
411437
438+ private shouldUseNativeShare ( ) : boolean {
439+ if ( typeof window === 'undefined' || typeof navigator === 'undefined' ) {
440+ return false
441+ }
442+
443+ if ( typeof navigator . share !== 'function' ) {
444+ return false
445+ }
446+
447+ const hasTouchPoints = navigator . maxTouchPoints > 0
448+ const prefersCoarsePointer =
449+ typeof window . matchMedia === 'function' && window . matchMedia ( '(pointer: coarse)' ) . matches
450+
451+ return hasTouchPoints || prefersCoarsePointer
452+ }
453+
412454 /**
413455 * Handle share action for a specific platform
414456 */
@@ -428,7 +470,7 @@ export class HighlighterElement extends LitElement {
428470 }
429471
430472 // Try native share first on mobile
431- if ( typeof navigator . share === 'function' ) {
473+ if ( this . shouldUseNativeShare ( ) ) {
432474 const shared = await nativeShare ( data )
433475 if ( shared ) {
434476 this . emitShareEvent ( platformId , data )
0 commit comments