wyreframe 0.7.9 → 0.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyreframe",
3
- "version": "0.7.9",
3
+ "version": "0.7.10",
4
4
  "description": "ASCII wireframe + interaction DSL to HTML converter with scene transitions",
5
5
  "author": "wickedev",
6
6
  "repository": {
@@ -11,6 +11,8 @@ import type {deviceType as Types_deviceType} from '../../src/parser/Core/Types.g
11
11
 
12
12
  import type {t as ErrorTypes_t} from '../../src/parser/Errors/ErrorTypes.gen';
13
13
 
14
+ import type {t as Map_t} from './Map.gen';
15
+
14
16
  export abstract class DomBindings_element { protected opaque!: any }; /* simulate opaque types */
15
17
 
16
18
  /** * Scene change callback type.
@@ -48,7 +50,7 @@ export type renderOptions = {
48
50
 
49
51
  /** * Scene management interface returned by render function. */
50
52
  export type sceneManager = {
51
- readonly goto: (_1:string) => void;
53
+ readonly goto: (_1:string) => boolean;
52
54
  readonly back: () => void;
53
55
  readonly forward: () => void;
54
56
  readonly refresh: () => void;
@@ -74,6 +76,10 @@ export type createUIResult =
74
76
  { TAG: "Ok"; _0: createUISuccessResult }
75
77
  | { TAG: "Error"; _0: ErrorTypes_t[] };
76
78
 
79
+ /** * Creates a scene manager for navigation between scenes.
80
+ * Exported for testing purposes. */
81
+ export const createSceneManager: (scenes:Map_t<string,DomBindings_element>, onSceneChange:(undefined | onSceneChangeCallback)) => sceneManager = RendererJS.createSceneManager as any;
82
+
77
83
  export const render: (ast:Types_ast, options:(undefined | renderOptions)) => renderResult = RendererJS.render as any;
78
84
 
79
85
  export const toHTMLString: (_ast:Types_ast, _options:(undefined | renderOptions)) => string = RendererJS.toHTMLString as any;
@@ -213,8 +213,12 @@ function renderElement(_elem, onAction, onDeadEnd) {
213
213
  if (onAction !== undefined) {
214
214
  btn.addEventListener("click", _event => {
215
215
  let action = actions[0];
216
- if (action !== undefined) {
217
- return onAction(action);
216
+ if (action === undefined) {
217
+ return;
218
+ }
219
+ let success = onAction(action);
220
+ if (!success && onDeadEnd !== undefined) {
221
+ return onDeadEnd(id, text, "button");
218
222
  }
219
223
  });
220
224
  }
@@ -247,8 +251,12 @@ function renderElement(_elem, onAction, onDeadEnd) {
247
251
  link.addEventListener("click", event => {
248
252
  event.preventDefault();
249
253
  let action = actions$1[0];
250
- if (action !== undefined) {
251
- return onAction(action);
254
+ if (action === undefined) {
255
+ return;
256
+ }
257
+ let success = onAction(action);
258
+ if (!success && onDeadEnd !== undefined) {
259
+ return onDeadEnd(id$1, text$1, "link");
252
260
  }
253
261
  });
254
262
  }
@@ -375,12 +383,16 @@ function createSceneManager(scenes, onSceneChange) {
375
383
  }
376
384
  };
377
385
  let goto = id => {
386
+ if (!scenes.has(id)) {
387
+ return false;
388
+ }
378
389
  let currentId = currentScene.contents;
379
390
  if (currentId !== undefined && currentId !== id) {
380
391
  historyStack.contents = historyStack.contents.concat([currentId]);
381
392
  forwardStack.contents = [];
382
393
  }
383
394
  switchToScene(id, undefined);
395
+ return true;
384
396
  };
385
397
  let back = () => {
386
398
  let history = historyStack.contents;
@@ -495,16 +507,18 @@ function render(ast, options) {
495
507
  if (action === "Back") {
496
508
  let back = backRef.contents;
497
509
  if (back !== undefined) {
498
- return back();
510
+ back();
511
+ return true;
499
512
  } else {
500
- return;
513
+ return false;
501
514
  }
502
515
  }
503
516
  let forward = forwardRef.contents;
504
517
  if (forward !== undefined) {
505
- return forward();
518
+ forward();
519
+ return true;
506
520
  } else {
507
- return;
521
+ return false;
508
522
  }
509
523
  } else {
510
524
  switch (action.TAG) {
@@ -513,11 +527,11 @@ function render(ast, options) {
513
527
  if (goto !== undefined) {
514
528
  return goto(action.target);
515
529
  } else {
516
- return;
530
+ return false;
517
531
  }
518
532
  case "Validate" :
519
533
  case "Call" :
520
- return;
534
+ return true;
521
535
  }
522
536
  }
523
537
  };
@@ -108,7 +108,7 @@ let defaultOptions: renderOptions = {
108
108
  * Scene management interface returned by render function.
109
109
  */
110
110
  type sceneManager = {
111
- goto: string => unit,
111
+ goto: string => bool,
112
112
  back: unit => unit,
113
113
  forward: unit => unit,
114
114
  refresh: unit => unit,
@@ -276,8 +276,9 @@ let deviceTypeToClass = (device: deviceType): string => {
276
276
  /**
277
277
  * Action handler function type - called when an element's action is triggered.
278
278
  * The function receives the action and should execute it (e.g., goto scene).
279
+ * Returns true if the action was successful, false if it failed (e.g., scene doesn't exist).
279
280
  */
280
- type actionHandler = interactionAction => unit
281
+ type actionHandler = interactionAction => bool
281
282
 
282
283
  /**
283
284
  * Dead end handler function type - called when an element without navigation is clicked.
@@ -360,7 +361,17 @@ let rec renderElement = (
360
361
  btn->DomBindings.addEventListener("click", _event => {
361
362
  // Execute first action (most common case)
362
363
  switch actions->Array.get(0) {
363
- | Some(action) => handler(action)
364
+ | Some(action) => {
365
+ let success = handler(action)
366
+ // If navigation failed (e.g., target scene doesn't exist),
367
+ // treat it as a dead end (Issue #24)
368
+ if !success {
369
+ switch onDeadEnd {
370
+ | Some(deadEndHandler) => deadEndHandler(id, text, #button)
371
+ | None => ()
372
+ }
373
+ }
374
+ }
364
375
  | None => ()
365
376
  }
366
377
  })
@@ -412,7 +423,17 @@ let rec renderElement = (
412
423
  DomBindings.preventDefault(event)
413
424
  // Execute first action
414
425
  switch actions->Array.get(0) {
415
- | Some(action) => handler(action)
426
+ | Some(action) => {
427
+ let success = handler(action)
428
+ // If navigation failed (e.g., target scene doesn't exist),
429
+ // treat it as a dead end (Issue #24)
430
+ if !success {
431
+ switch onDeadEnd {
432
+ | Some(deadEndHandler) => deadEndHandler(id, text, #link)
433
+ | None => ()
434
+ }
435
+ }
436
+ }
416
437
  | None => ()
417
438
  }
418
439
  })
@@ -545,6 +566,11 @@ let renderScene = (
545
566
  // Scene Manager Implementation
546
567
  // ============================================================================
547
568
 
569
+ /**
570
+ * Creates a scene manager for navigation between scenes.
571
+ * Exported for testing purposes.
572
+ */
573
+ @genType
548
574
  let createSceneManager = (
549
575
  scenes: Map.t<string, DomBindings.element>,
550
576
  ~onSceneChange: option<onSceneChangeCallback>=?,
@@ -591,17 +617,25 @@ let createSceneManager = (
591
617
  }
592
618
  }
593
619
 
594
- let goto = (id: string): unit => {
595
- // Add current scene to history before navigating
596
- switch currentScene.contents {
597
- | Some(currentId) if currentId != id => {
598
- historyStack := historyStack.contents->Array.concat([currentId])
599
- // Clear forward stack when navigating to new scene
600
- forwardStack := []
620
+ let goto = (id: string): bool => {
621
+ // Check if target scene exists first
622
+ if !(scenes->Map.has(id)) {
623
+ // Scene doesn't exist - return false to indicate failure
624
+ // Do NOT modify history or switch scenes
625
+ false
626
+ } else {
627
+ // Add current scene to history before navigating
628
+ switch currentScene.contents {
629
+ | Some(currentId) if currentId != id => {
630
+ historyStack := historyStack.contents->Array.concat([currentId])
631
+ // Clear forward stack when navigating to new scene
632
+ forwardStack := []
633
+ }
634
+ | _ => ()
601
635
  }
602
- | _ => ()
636
+ switchToScene(id)
637
+ true
603
638
  }
604
- switchToScene(id)
605
639
  }
606
640
 
607
641
  let back = (): unit => {
@@ -746,38 +780,45 @@ let render = (ast: ast, options: option<renderOptions>): renderResult => {
746
780
  }
747
781
 
748
782
  // Create refs to hold navigation functions (set after sceneManager is created)
749
- let gotoRef: ref<option<string => unit>> = ref(None)
783
+ let gotoRef: ref<option<string => bool>> = ref(None)
750
784
  let backRef: ref<option<unit => unit>> = ref(None)
751
785
  let forwardRef: ref<option<unit => unit>> = ref(None)
752
786
 
753
787
  // Create action handler that uses the refs
754
- let handleAction = (action: interactionAction): unit => {
788
+ // Returns true if action succeeded, false if it failed (e.g., target scene doesn't exist)
789
+ let handleAction = (action: interactionAction): bool => {
755
790
  switch action {
756
791
  | Goto({target, _}) => {
757
792
  switch gotoRef.contents {
758
793
  | Some(goto) => goto(target)
759
- | None => ()
794
+ | None => false
760
795
  }
761
796
  }
762
797
  | Back => {
763
798
  switch backRef.contents {
764
- | Some(back) => back()
765
- | None => ()
799
+ | Some(back) => {
800
+ back()
801
+ true
802
+ }
803
+ | None => false
766
804
  }
767
805
  }
768
806
  | Forward => {
769
807
  switch forwardRef.contents {
770
- | Some(forward) => forward()
771
- | None => ()
808
+ | Some(forward) => {
809
+ forward()
810
+ true
811
+ }
812
+ | None => false
772
813
  }
773
814
  }
774
815
  | Validate(_) => {
775
816
  // TODO: Implement field validation
776
- ()
817
+ true
777
818
  }
778
819
  | Call(_) => {
779
820
  // TODO: Implement custom function calls
780
- ()
821
+ true
781
822
  }
782
823
  }
783
824
  }
@@ -813,7 +854,9 @@ let render = (ast: ast, options: option<renderOptions>): renderResult => {
813
854
 
814
855
  if ast.scenes->Array.length > 0 {
815
856
  switch ast.scenes->Array.get(0) {
816
- | Some(firstScene) => manager.goto(firstScene.id)
857
+ | Some(firstScene) => {
858
+ let _ = manager.goto(firstScene.id)
859
+ }
817
860
  | None => ()
818
861
  }
819
862
  }