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 +1 -1
- package/src/renderer/Renderer.gen.tsx +7 -1
- package/src/renderer/Renderer.mjs +24 -10
- package/src/renderer/Renderer.res +66 -23
package/package.json
CHANGED
|
@@ -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) =>
|
|
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
|
|
217
|
-
return
|
|
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
|
|
251
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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 =>
|
|
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 =>
|
|
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) =>
|
|
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) =>
|
|
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):
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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 =>
|
|
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
|
-
|
|
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) =>
|
|
765
|
-
|
|
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) =>
|
|
771
|
-
|
|
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) =>
|
|
857
|
+
| Some(firstScene) => {
|
|
858
|
+
let _ = manager.goto(firstScene.id)
|
|
859
|
+
}
|
|
817
860
|
| None => ()
|
|
818
861
|
}
|
|
819
862
|
}
|