@@ -56,6 +56,24 @@ module DomBindings = {
5656 */
5757type onSceneChangeCallback = (option <string >, string ) => unit
5858
59+ /**
60+ * Dead end click info type.
61+ * Contains information about the clicked element that has no navigation target.
62+ */
63+ type deadEndClickInfo = {
64+ sceneId : string ,
65+ elementId : string ,
66+ elementText : string ,
67+ elementType : [#button | #link ],
68+ }
69+
70+ /**
71+ * Dead end click callback type.
72+ * Called when a button or link without navigation target is clicked.
73+ * @param info Information about the clicked element and current scene
74+ */
75+ type onDeadEndClickCallback = deadEndClickInfo => unit
76+
5977/**
6078 * Configuration for the rendering process.
6179 */
@@ -65,6 +83,7 @@ type renderOptions = {
6583 injectStyles : bool ,
6684 containerClass : option <string >,
6785 onSceneChange : option <onSceneChangeCallback >,
86+ onDeadEndClick : option <onDeadEndClickCallback >,
6887 device : option <deviceType >,
6988}
7089
@@ -77,6 +96,7 @@ let defaultOptions: renderOptions = {
7796 injectStyles : true ,
7897 containerClass : None ,
7998 onSceneChange : None ,
99+ onDeadEndClick : None ,
80100 device : None ,
81101}
82102
@@ -230,17 +250,44 @@ let deviceTypeToClass = (device: deviceType): string => {
230250 */
231251type actionHandler = interactionAction => unit
232252
253+ /**
254+ * Dead end handler function type - called when an element without navigation is clicked.
255+ * Receives element ID, text, and type.
256+ */
257+ type deadEndHandler = (string , string , [#button | #link ]) => unit
258+
259+ /**
260+ * Check if an action is a navigation action (Goto, Back, Forward).
261+ */
262+ let isNavigationAction = (action : interactionAction ): bool => {
263+ switch action {
264+ | Goto (_ ) | Back | Forward => true
265+ | Validate (_ ) | Call (_ ) => false
266+ }
267+ }
268+
269+ /**
270+ * Check if actions array has any navigation actions.
271+ */
272+ let hasNavigationAction = (actions : array <interactionAction >): bool => {
273+ actions -> Array .some (isNavigationAction )
274+ }
275+
233276// ============================================================================
234277// Element Rendering
235278// ============================================================================
236279
237- let rec renderElement = (elem : element , ~onAction : option <actionHandler >= ?): option <DomBindings .element > => {
280+ let rec renderElement = (
281+ elem : element ,
282+ ~onAction : option <actionHandler >= ?,
283+ ~onDeadEnd : option <deadEndHandler >= ?,
284+ ): option <DomBindings .element > => {
238285 // Handle input-only boxes by rendering children directly in a wrapper
239286 if isInputOnlyBox (elem ) {
240287 let inputs = getInputsFromBox (elem )
241288 // If only one input, render it directly
242289 switch inputs -> Array .get (0 ) {
243- | Some (input ) => renderElement (input , ~onAction ?)
290+ | Some (input ) => renderElement (input , ~onAction ?, ~ onDeadEnd ? )
244291 | None => None
245292 }
246293 } else {
@@ -258,7 +305,7 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
258305 }
259306
260307 children -> Array .forEach (child => {
261- switch renderElement (child , ~onAction ?) {
308+ switch renderElement (child , ~onAction ?, ~ onDeadEnd ? ) {
262309 | Some (el ) => div -> DomBindings .appendChild (el )
263310 | None => ()
264311 }
@@ -274,8 +321,11 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
274321 btn -> DomBindings .setTextContent (text )
275322 applyAlignment (btn , align )
276323
277- // Attach click handler for actions
278- if actions -> Array .length > 0 {
324+ // Check if button has navigation actions
325+ let hasNavigation = hasNavigationAction (actions )
326+
327+ if hasNavigation {
328+ // Attach click handler for navigation actions
279329 switch onAction {
280330 | Some (handler ) => {
281331 btn -> DomBindings .addEventListener ("click" , _event => {
@@ -288,6 +338,16 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
288338 }
289339 | None => ()
290340 }
341+ } else {
342+ // No navigation - call dead end handler
343+ switch onDeadEnd {
344+ | Some (handler ) => {
345+ btn -> DomBindings .addEventListener ("click" , _event => {
346+ handler (id , text , #button )
347+ })
348+ }
349+ | None => ()
350+ }
291351 }
292352
293353 Some (btn )
@@ -312,8 +372,11 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
312372 link -> DomBindings .setTextContent (text )
313373 applyAlignment (link , align )
314374
315- // Attach click handler for actions
316- if actions -> Array .length > 0 {
375+ // Check if link has navigation actions
376+ let hasNavigation = hasNavigationAction (actions )
377+
378+ if hasNavigation {
379+ // Attach click handler for navigation actions
317380 switch onAction {
318381 | Some (handler ) => {
319382 link -> DomBindings .addEventListener ("click" , event => {
@@ -327,6 +390,17 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
327390 }
328391 | None => ()
329392 }
393+ } else {
394+ // No navigation - call dead end handler
395+ switch onDeadEnd {
396+ | Some (handler ) => {
397+ link -> DomBindings .addEventListener ("click" , event => {
398+ DomBindings .preventDefault (event )
399+ handler (id , text , #link )
400+ })
401+ }
402+ | None => ()
403+ }
330404 }
331405
332406 Some (link )
@@ -376,7 +450,7 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
376450 applyAlignment (row , align )
377451
378452 children -> Array .forEach (child => {
379- switch renderElement (child , ~onAction ?) {
453+ switch renderElement (child , ~onAction ?, ~ onDeadEnd ? ) {
380454 | Some (el ) => row -> DomBindings .appendChild (el )
381455 | None => ()
382456 }
@@ -397,7 +471,7 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
397471 contentEl -> DomBindings .setClassName ("wf-section-content" )
398472
399473 children -> Array .forEach (child => {
400- switch renderElement (child , ~onAction ?) {
474+ switch renderElement (child , ~onAction ?, ~ onDeadEnd ? ) {
401475 | Some (el ) => contentEl -> DomBindings .appendChild (el )
402476 | None => ()
403477 }
@@ -411,13 +485,17 @@ let rec renderElement = (elem: element, ~onAction: option<actionHandler>=?): opt
411485 }
412486}
413487
414- let renderScene = (scene : scene , ~onAction : option <actionHandler >= ?): DomBindings .element => {
488+ let renderScene = (
489+ scene : scene ,
490+ ~onAction : option <actionHandler >= ?,
491+ ~onDeadEnd : option <deadEndHandler >= ?,
492+ ): DomBindings .element => {
415493 let sceneEl = DomBindings .document -> DomBindings .createElement ("div" )
416494 sceneEl -> DomBindings .setClassName ("wf-scene" )
417495 sceneEl -> DomBindings .dataset -> DomBindings .setDataAttr ("scene" , scene .id )
418496
419497 scene .elements -> Array .forEach (elem => {
420- switch renderElement (elem , ~onAction ?) {
498+ switch renderElement (elem , ~onAction ?, ~ onDeadEnd ? ) {
421499 | Some (el ) => sceneEl -> DomBindings .appendChild (el )
422500 | None => ()
423501 }
@@ -670,7 +748,21 @@ let render = (ast: ast, options: option<renderOptions>): renderResult => {
670748 let sceneMap = Map .make ()
671749
672750 ast .scenes -> Array .forEach (scene => {
673- let sceneEl = renderScene (scene , ~onAction = handleAction )
751+ // Create a scene-specific dead end handler that includes the scene ID
752+ let handleDeadEnd = switch opts .onDeadEndClick {
753+ | Some (callback ) =>
754+ Some ((elementId : string , elementText : string , elementType : [#button | #link ]) => {
755+ callback ({
756+ sceneId : scene .id ,
757+ elementId ,
758+ elementText ,
759+ elementType ,
760+ })
761+ })
762+ | None => None
763+ }
764+
765+ let sceneEl = renderScene (scene , ~onAction = handleAction , ~onDeadEnd = ?handleDeadEnd )
674766 app -> DomBindings .appendChild (sceneEl )
675767 sceneMap -> Map .set (scene .id , sceneEl )
676768 })
0 commit comments