@@ -38,6 +38,8 @@ export type ActionHandler<N, T, D> = (node: CHTMLmaction<N, T, D>) => void;
3838export type ActionMap = Map < string , ActionHandler < any , any , any > > ;
3939export type ActionPair = [ string , ActionHandler < any , any , any > ] ;
4040
41+ export type EventHandler = ( event : Event ) => void ;
42+ export type EventList = [ any , string , EventHandler ] ;
4143
4244/*****************************************************************/
4345/*
@@ -80,6 +82,7 @@ export class CHTMLmaction<N, T, D> extends CHTMLWrapper<N, T, D> {
8082 public static Actions = new Map ( [
8183 [ 'toggle' , node => {
8284 node . adaptor . setAttribute ( node . chtml , 'toggle' , node . node . attributes . get ( 'selection' ) as string ) ;
85+ node . setEventHandler ( 'click' , node . toggleClick ) ;
8386 } ] ,
8487
8588 [ 'tooltip' , node => {
@@ -92,6 +95,8 @@ export class CHTMLmaction<N, T, D> extends CHTMLWrapper<N, T, D> {
9295 const box = node . html ( 'mjx-tip' ) ;
9396 tip . toCHTML ( box ) ;
9497 node . adaptor . append ( node . chtml , node . html ( 'mjx-tool' , { } , [ box ] ) ) ;
98+ node . setEventHandler ( 'mouseover' , node . tooltipOver ) ;
99+ node . setEventHandler ( 'mouseout' , node . tooltipOut ) ;
95100 }
96101 } ] ,
97102
@@ -101,13 +106,25 @@ export class CHTMLmaction<N, T, D> extends CHTMLWrapper<N, T, D> {
101106 if ( tip . node . isKind ( 'mtext' ) ) {
102107 const text = ( tip . node as TextNode ) . getText ( ) ;
103108 node . adaptor . setAttribute ( node . chtml , 'statusline' , text ) ;
109+ node . setEventHandler ( 'mouseover' , node . statusOver ) ;
110+ node . setEventHandler ( 'mouseout' , node . statusOut ) ;
104111 }
105112 } ]
106113
107114 ] as ActionPair [ ] ) ;
108115
116+ /*************************************************************/
117+
118+ /*
119+ * Delays before posting or clearing a math tooltip
120+ */
121+ public static postDelay = 600 ;
122+ public static clearDelay = 100 ;
123+
124+ /*************************************************************/
125+
109126 /*
110- * The handler for the specified actiontype
127+ * The handler for the specified actiontype
111128 */
112129 protected action : ActionHandler < N , T , D > = null ;
113130
@@ -120,6 +137,8 @@ export class CHTMLmaction<N, T, D> extends CHTMLWrapper<N, T, D> {
120137 return this . childNodes [ i ] || this . wrap ( ( this . node as MmlMaction ) . selected ) ;
121138 }
122139
140+ /*************************************************************/
141+
123142 /*
124143 * @override
125144 */
@@ -147,4 +166,147 @@ export class CHTMLmaction<N, T, D> extends CHTMLWrapper<N, T, D> {
147166 bbox . updateFrom ( this . selected . getBBox ( ) ) ;
148167 }
149168
169+ /*************************************************************/
170+ /*************************************************************/
171+ /*
172+ * Handle events for the actions
173+ */
174+
175+ /*
176+ * Add an event handler to the output for this maction
177+ */
178+ public setEventHandler ( type : string , handler : EventHandler ) {
179+ ( this . chtml as any ) . addEventListener ( type , handler . bind ( this ) ) ;
180+ }
181+
182+ /*************************************************************/
183+ /*
184+ * Handle statuline changes
185+ */
186+
187+ /*
188+ * Change the selection and rerender the expression
189+ */
190+ public toggleClick ( ) {
191+ let selection = Math . max ( 1 , ( this . node . attributes . get ( 'selection' ) as number ) + 1 ) ;
192+ if ( selection > this . childNodes . length ) {
193+ selection = 1 ;
194+ }
195+ this . node . attributes . set ( 'selection' , selection ) ;
196+ this . toggleRerender ( ) ;
197+ }
198+
199+ /*
200+ * Rerender the complete expression and replace it in the document
201+ */
202+ protected toggleRerender ( ) {
203+ const html = this . getMathHTML ( ) ;
204+ if ( ! html ) return ;
205+ const CHTML = this . factory . chtml ;
206+ const node = CHTML . typeset ( CHTML . math , CHTML . document ) ;
207+ this . adaptor . replace ( node , html ) ;
208+ }
209+
210+ /*
211+ * @return {N } The html for the top-level mjx-chtml node for the expression
212+ */
213+ protected getMathHTML ( ) {
214+ let parent = this as CHTMLWrapper < N , T , D > ;
215+ while ( ! parent . node . isKind ( 'math' ) ) {
216+ parent = parent . parent ;
217+ if ( ! parent ) return null ;
218+ }
219+ return this . adaptor . parent ( parent . chtml ) ;
220+ }
221+
222+ /*************************************************************/
223+ /*
224+ * Handle tool tips containing math
225+ */
226+
227+ /*
228+ * Timers for posting/clearing a math tooltip
229+ */
230+ protected hoverTimer : number = null ;
231+ protected clearTimer : number = null ;
232+
233+ /*
234+ * clear the timers if any are active
235+ */
236+ public clearTimers ( ) {
237+ if ( this . clearTimer ) {
238+ clearTimeout ( this . clearTimer ) ;
239+ this . clearTimer = null ;
240+ }
241+ if ( this . hoverTimer ) {
242+ clearTimeout ( this . hoverTimer ) ;
243+ this . hoverTimer = null ;
244+ }
245+ }
246+
247+ /*
248+ * Handle a tooltip mousover event (start a timer to post the tooltip)
249+ *
250+ * @param {Event } event The mouseover event
251+ */
252+ public tooltipOver ( event : Event ) {
253+ const delay = ( this . constructor as typeof CHTMLmaction ) . postDelay ;
254+ this . clearTimers ( ) ;
255+ this . hoverTimer = setTimeout ( ( ( event :Event ) => {
256+ const adaptor = this . adaptor ;
257+ adaptor . setStyle ( adaptor . lastChild ( this . chtml ) as N , 'display' , 'block' ) ;
258+ } ) . bind ( this ) , delay ) ;
259+ event . stopPropagation ( ) ;
260+ }
261+
262+ /*
263+ * Handle a tooltip mousout event (start a timer to remove the tooltip)
264+ *
265+ * @param {Event } event The mouseout event
266+ */
267+ public tooltipOut ( event : Event ) {
268+ const delay = ( this . constructor as typeof CHTMLmaction ) . clearDelay ;
269+ this . clearTimers ( ) ;
270+ this . clearTimer = setTimeout ( ( ( event : Event ) => {
271+ const adaptor = this . adaptor ;
272+ adaptor . setStyle ( adaptor . lastChild ( this . chtml ) as N , 'display' , '' ) ;
273+ } ) . bind ( this ) , delay ) ;
274+ event . stopPropagation ( ) ;
275+ }
276+
277+ /*************************************************************/
278+ /*
279+ * Handle statuline changes
280+ */
281+
282+ /*
283+ * Cached document title (where the status is shown)
284+ */
285+ protected status : string = null ;
286+
287+ /*
288+ * Set the document title to the status value
289+ *
290+ * @param {Event } event The mouseover event
291+ */
292+ public statusOver ( event : Event ) {
293+ if ( this . status === null ) {
294+ this . status = document . title ;
295+ document . title = this . adaptor . getAttribute ( this . chtml , 'statusline' ) ;
296+ }
297+ event . stopPropagation ( ) ;
298+ }
299+
300+ /*
301+ * Restore the document title to its original value
302+ *
303+ * @param {Event } event The mouseout event
304+ */
305+ public statusOut ( event : Event ) {
306+ if ( this . status ) {
307+ document . title = this . status ;
308+ this . status = null ;
309+ }
310+ event . stopPropagation ( ) ;
311+ }
150312}
0 commit comments