@@ -3,12 +3,21 @@ import "../components/expandable/expandable.js";
33
44window . activeLegendElement = null ;
55
6+ /**
7+ * @param {{x: number, y: number} } location
8+ * @param {{x: number, y: number} } pos
9+ * @returns {number }
10+ */
611export function vec2Distance ( location , pos ) {
712 return Math . sqrt (
813 Math . pow ( location . x - pos . x , 2 ) + Math . pow ( location . y - pos . y , 2 )
914 ) ;
1015}
1116
17+ /**
18+ * @param {string } strWithEmojis
19+ * @returns {string[] }
20+ */
1221export function extractEmojis ( strWithEmojis ) {
1322 const segmenter = new Intl . Segmenter ( "en" , {
1423 granularity : "grapheme"
@@ -62,25 +71,43 @@ export function createDOMElement(kind = "div", options = {}) {
6271 return el ;
6372}
6473
74+ /**
75+ * @param {string } href
76+ * @param {string|null } text
77+ * @returns {HTMLAnchorElement }
78+ */
6579export function createLink ( href , text = null ) {
6680 const attributes = {
6781 rel : "noopener" , target : "_blank" , href
6882 } ;
6983
70- return createDOMElement ( "a" , { text, attributes } ) ;
84+ const htmlAnchor =
85+ /** @type {HTMLAnchorElement } */
86+ ( createDOMElement ( "a" , { text, attributes } ) ) ;
87+
88+ return htmlAnchor ;
7189}
7290
91+ /**
92+ * @param {string } spec
93+ * @returns {{ name: string, version: string } }
94+ */
7395export function parseNpmSpec ( spec ) {
7496 const parts = spec . split ( "@" ) ;
75- const version = parts . at ( - 1 ) ;
97+ const version = parts . at ( - 1 ) ?? "" ;
7698
7799 return spec . startsWith ( "@" ) ?
78100 { name : `@${ parts [ 1 ] } ` , version } :
79101 { name : parts [ 0 ] , version } ;
80102}
81103
104+ /**
105+ * @param {{url?: string} } repository
106+ * @param {string | null } defaultValue
107+ * @returns {string | null } return repository url or defaultValue
108+ */
82109export function parseRepositoryUrl ( repository = { } , defaultValue = null ) {
83- if ( typeof repository !== "object" || ! ( "url" in repository ) ) {
110+ if ( ! repository || ! repository . url || typeof repository !== "object" || ! ( "url" in repository ) ) {
84111 return defaultValue ;
85112 }
86113
@@ -92,7 +119,7 @@ export function parseRepositoryUrl(repository = {}, defaultValue = null) {
92119 }
93120 if ( repository . url . startsWith ( "git@" ) ) {
94121 const execResult = / g i t @ (?< platform > [ a - z A - Z . ] + ) : (?< repo > .+ ) \. g i t / gm. exec ( repository . url ) ;
95- if ( execResult === null ) {
122+ if ( execResult === null || ! execResult . groups ) {
96123 return defaultValue ;
97124 }
98125
@@ -107,6 +134,12 @@ export function parseRepositoryUrl(repository = {}, defaultValue = null) {
107134 }
108135}
109136
137+ /**
138+ * @param {string } title
139+ * @param {string } value
140+ * @param {Record<string, any> } options
141+ * @returns {HTMLElement }
142+ */
110143export function createLiField ( title , value , options = { } ) {
111144 const { isLink = false } = options ;
112145
@@ -126,12 +159,20 @@ export function createLiField(title, value, options = {}) {
126159 return liElement ;
127160}
128161
162+ /**
163+ * @param {HTMLElement } node - The parent DOM element.
164+ * @param {string[] } items - Array of strings to display.
165+ * @param {Object } [options] - Optional configuration options.
166+ * @param {Function } [options.onclick] - Callback function (event, item).
167+ * @param {boolean } [options.hideItems] - Hide items if needed.
168+ * @param {number } [options.hideItemsLength] - Number of visible elements before masking.
169+ * @returns {void }
170+ */
129171export function createItemsList ( node , items = [ ] , options = { } ) {
130172 const { onclick = null , hideItems = false , hideItemsLength = 5 } = options ;
131-
132173 if ( items . length === 0 ) {
133174 const previousNode = node . previousElementSibling ;
134- if ( previousNode !== null ) {
175+ if ( previousNode !== null && previousNode instanceof HTMLElement ) {
135176 previousNode . style . display = "none" ;
136177 }
137178
@@ -157,8 +198,10 @@ export function createItemsList(node, items = [], options = {}) {
157198 }
158199
159200 if ( hideItems && items . length > hideItemsLength ) {
160- const expandableSpan = document . createElement ( "expandable-span" ) ;
161- expandableSpan . onToggle = ( expandable ) => toggle ( expandable , node , hideItemsLength ) ;
201+ const expandableSpan =
202+ /** @type {import("../components/expandable/expandable.js").ExpandableType } */
203+ ( document . createElement ( "expandable-span" ) ) ;
204+ expandableSpan . onToggle = ( ) => toggle ( expandableSpan , node , hideItemsLength ) ;
162205 fragment . appendChild ( expandableSpan ) ;
163206 }
164207 node . appendChild ( fragment ) ;
@@ -168,10 +211,16 @@ export function createItemsList(node, items = [], options = {}) {
168211TODO: this util function won't be necessary once the parents of the expandable component will be migrated to lit
169212becuase the parents will handle the filtering of their children themselves
170213*/
214+ /**
215+ * @param {import("../components/expandable/expandable.js").ExpandableType } expandable
216+ * @param {HTMLElement } parentNode
217+ * @param {number } hideItemsLength
218+ * @returns {void }
219+ */
171220export function toggle ( expandable , parentNode , hideItemsLength ) {
172221 expandable . isClosed = ! expandable . isClosed ;
173- for ( let id = 0 ; id < parentNode . childNodes . length ; id ++ ) {
174- const node = parentNode . childNodes [ id ] ;
222+ for ( let id = 0 ; id < parentNode . children . length ; id ++ ) {
223+ const node = parentNode . children [ id ] ;
175224 if ( node . tagName === "EXPANDABLE-SPAN" ) {
176225 continue ;
177226 }
@@ -185,26 +234,37 @@ export function toggle(expandable, parentNode, hideItemsLength) {
185234 }
186235}
187236
237+ /**
238+ * @param {string } str
239+ * @returns {void }
240+ */
188241export function copyToClipboard ( str ) {
189242 const el = document . createElement ( "textarea" ) ;
190243 el . value = str ;
191244 el . setAttribute ( "readonly" , "" ) ;
192245 el . style . position = "absolute" ;
193246 el . style . left = "-9999px" ;
194247 document . body . appendChild ( el ) ;
195- const selected =
196- document . getSelection ( ) . rangeCount > 0
197- ? document . getSelection ( ) . getRangeAt ( 0 )
198- : false ;
248+ const selection = document . getSelection ( ) ;
249+ const selected = selection && selection . rangeCount > 0 ? selection . getRangeAt ( 0 ) : false ;
199250 el . select ( ) ;
200251 document . execCommand ( "copy" ) ;
201252 document . body . removeChild ( el ) ;
202- if ( selected ) {
203- document . getSelection ( ) . removeAllRanges ( ) ;
204- document . getSelection ( ) . addRange ( selected ) ;
253+ if ( selected && selection ) {
254+ selection . removeAllRanges ( ) ;
255+ selection . addRange ( selected ) ;
205256 }
206257}
207258
259+ /**
260+ * @typedef {{reverse?: boolean, blacklist?: Node[], hiddenTarget?: HTMLElement, callback?: () => void} } hideOnClickOutsideOptions
261+ */
262+
263+ /**
264+ * @param {HTMLElement } element
265+ * @param {hideOnClickOutsideOptions } options
266+ * @returns {(event: Event) => void }
267+ */
208268export function hideOnClickOutside (
209269 element ,
210270 options = { }
@@ -216,8 +276,15 @@ export function hideOnClickOutside(
216276 callback = ( ) => void 0
217277 } = options ;
218278
279+ /** @param {Event } event */
219280 function outsideClickListener ( event ) {
220- if ( ! element . contains ( event . target ) && ! blacklist . includes ( event . target ) ) {
281+ const target = event . target ;
282+
283+ if ( ! ( target instanceof Node ) ) {
284+ return ;
285+ }
286+
287+ if ( ! element . contains ( target ) && ! blacklist . includes ( target ) ) {
221288 if ( hiddenTarget ) {
222289 if ( reverse ) {
223290 hiddenTarget . classList . remove ( "show" ) ;
@@ -240,13 +307,24 @@ export function hideOnClickOutside(
240307 return outsideClickListener ;
241308}
242309
310+ /** @returns {string } */
243311export function currentLang ( ) {
244- const detectedLang = document . getElementById ( "lang" ) . dataset . lang ;
312+ const detectedLang = document . getElementById ( "lang" ) ?. dataset . lang ;
313+ const defaultLanguage = "english" ;
314+ if ( ! detectedLang ) {
315+ return defaultLanguage ;
316+ }
245317
246- return detectedLang in window . i18n ? detectedLang : "english" ;
318+ return detectedLang in window . i18n ? detectedLang : defaultLanguage ;
247319}
248320
321+ /**
322+ * @param {Function } callback
323+ * @param {number } delay
324+ * @returns {() => void }
325+ */
249326export function debounce ( callback , delay ) {
327+ /** @type {ReturnType<typeof setTimeout> | undefined } */
250328 let timer ;
251329
252330 // eslint-disable-next-line func-names
0 commit comments