11import Checkbox from "components/checkbox" ;
22import tile from "components/tile" ;
3+ import DOMPurify from "dompurify" ;
34import actionStack from "lib/actionStack" ;
45import restoreTheme from "lib/restoreTheme" ;
56
@@ -20,6 +21,8 @@ import restoreTheme from "lib/restoreTheme";
2021 * @property {boolean } [disabled]
2122 * @property {string } [letters]
2223 * @property {boolean } [checkbox]
24+ * @property {HTMLElement } [tailElement]
25+ * @property {function(Event):void } [ontailclick]
2326 */
2427
2528/**
@@ -43,33 +46,45 @@ function select(title, items, options = {}) {
4346 // elements
4447 const $mask = < span className = "mask" onclick = { cancel } > </ span > ;
4548 const $list = tag ( "ul" , {
46- className : " scroll" + ! textTransform ? " no-text-transform" : "" ,
49+ className : ` scroll${ ! textTransform ? " no-text-transform" : "" } ` ,
4750 } ) ;
51+ const $titleSpan = title ? (
52+ < strong className = "title" > { title } </ strong >
53+ ) : null ;
4854 const $select = (
4955 < div className = "prompt select" >
50- { title ? < strong className = "title" > { title } </ strong > : "" }
51- { $list }
56+ { $titleSpan ? [ $titleSpan , $list ] : $list }
5257 </ div >
5358 ) ;
5459
60+ // Track tail click handlers for cleanup
61+ const tailClickHandlers = new Map ( ) ;
62+
5563 items . map ( ( item ) => {
5664 let lead ,
57- tail ,
65+ tail = null ,
5866 itemOptions = {
5967 value : null ,
6068 text : null ,
6169 icon : null ,
6270 disabled : false ,
6371 letters : "" ,
6472 checkbox : null ,
73+ tailElement : null ,
74+ ontailclick : null ,
6575 } ;
6676
6777 // init item options
6878 if ( typeof item === "object" ) {
6979 if ( Array . isArray ( item ) ) {
80+ // This format does NOT support custom tail or handlers so pass object :)
7081 Object . keys ( itemOptions ) . forEach (
7182 ( key , i ) => ( itemOptions [ key ] = item [ i ] ) ,
7283 ) ;
84+
85+ item . map ( ( o , i ) => {
86+ if ( typeof o === "boolean" && i > 1 ) itemOptions . disabled = ! o ;
87+ } ) ;
7388 } else {
7489 itemOptions = Object . assign ( { } , itemOptions , item ) ;
7590 }
@@ -78,7 +93,7 @@ function select(title, items, options = {}) {
7893 itemOptions . text = item ;
7994 }
8095
81- // handle icon
96+ // handle icon (lead)
8297 if ( itemOptions . icon ) {
8398 if ( itemOptions . icon === "letters" && ! ! itemOptions . letters ) {
8499 lead = (
@@ -89,8 +104,10 @@ function select(title, items, options = {}) {
89104 }
90105 }
91106
92- // handle checkbox
93- if ( itemOptions . checkbox != null ) {
107+ // handle tail (checkbox or custom element)
108+ if ( itemOptions . tailElement ) {
109+ tail = itemOptions . tailElement ;
110+ } else if ( itemOptions . checkbox != null ) {
94111 tail = Checkbox ( {
95112 checked : itemOptions . checkbox ,
96113 } ) ;
@@ -99,7 +116,12 @@ function select(title, items, options = {}) {
99116 const $item = tile ( {
100117 lead,
101118 tail,
102- text : < span className = "text" innerHTML = { itemOptions . text } > </ span > ,
119+ text : (
120+ < span
121+ className = "text"
122+ innerHTML = { DOMPurify . sanitize ( itemOptions . text ) }
123+ > </ span >
124+ ) ,
103125 } ) ;
104126
105127 $item . tabIndex = "0" ;
@@ -109,13 +131,39 @@ function select(title, items, options = {}) {
109131 $defaultVal = $item ;
110132 }
111133
112- // handle events
113- $item . onclick = function ( ) {
134+ $item . onclick = function ( e ) {
135+ // Check if clicked element or any parent up to the item has data-action
136+ let target = e . target ;
137+ while ( target && target !== $item ) {
138+ if ( target . hasAttribute ( "data-action" ) ) {
139+ // Stop propagation and prevent default
140+ e . stopPropagation ( ) ;
141+ e . preventDefault ( ) ;
142+ return false ;
143+ }
144+ target = target . parentElement ;
145+ }
146+
114147 if ( ! itemOptions . value ) return ;
115148 if ( hideOnSelect ) hide ( ) ;
116149 res ( itemOptions . value ) ;
117150 } ;
118151
152+ // Handle tail click event if a custom tail and handler are provided
153+ if ( itemOptions . tailElement && itemOptions . ontailclick && tail ) {
154+ // Apply the pointer-events: all directly to the tail element
155+ tail . style . pointerEvents = "all" ;
156+
157+ const tailClickHandler = function ( e ) {
158+ e . stopPropagation ( ) ;
159+ e . preventDefault ( ) ;
160+ itemOptions . ontailclick . call ( $item , e ) ;
161+ } ;
162+
163+ tail . addEventListener ( "click" , tailClickHandler ) ;
164+ tailClickHandlers . set ( tail , tailClickHandler ) ;
165+ }
166+
119167 $list . append ( $item ) ;
120168 } ) ;
121169
@@ -134,7 +182,7 @@ function select(title, items, options = {}) {
134182 function cancel ( ) {
135183 hide ( ) ;
136184 if ( typeof options . onCancel === "function" ) options . onCancel ( ) ;
137- if ( rejectOnCancel ) reject ( ) ;
185+ if ( rejectOnCancel ) rej ( ) ;
138186 }
139187
140188 function hideSelect ( ) {
@@ -152,6 +200,11 @@ function select(title, items, options = {}) {
152200 hideSelect ( ) ;
153201 let listItems = [ ...$list . children ] ;
154202 listItems . map ( ( item ) => ( item . onclick = null ) ) ;
203+ // Clean up tail click handlers
204+ tailClickHandlers . forEach ( ( handler , element ) => {
205+ element . removeEventListener ( "click" , handler ) ;
206+ } ) ;
207+ tailClickHandlers . clear ( ) ;
155208 }
156209 } ) ;
157210}
0 commit comments