rotor-framework 0.3.7 β†’ 0.4.1

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/README.md CHANGED
@@ -22,6 +22,12 @@
22
22
 
23
23
  ---
24
24
 
25
+ ## 🎯 Quick Start with Starter Template
26
+
27
+ Want to get started quickly? Check out **[rotor-starter](https://github.com/mobalazs/rotor-starter)** - a ready-to-use project template with Rotor Framework pre-configured, sample components, and best practices built in.
28
+
29
+ ---
30
+
25
31
  ## πŸ“¦ Installation
26
32
 
27
33
  ### Prerequisites
@@ -90,7 +96,7 @@ You can find [🌱](./docs/ai/readme.opt.yaml) symbols in all documentation page
90
96
 
91
97
  ## πŸ“š Learn More
92
98
 
93
- ![Version](https://img.shields.io/badge/version-v0.3.7-blue?label=Documents%20TAG)
99
+ ![Version](https://img.shields.io/badge/version-v0.4.1-blue?label=Documents%20TAG)
94
100
 
95
101
  ### Framework Core
96
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rotor-framework",
3
- "version": "0.3.7",
3
+ "version": "0.4.1",
4
4
  "description": "Roku toolkit library providing a ViewBuilder, full UI lifecycle with focus handling and many core features, plus MVI-based state management.",
5
5
  "author": "BalΓ‘zs MolnΓ‘r",
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  ' β–β–›β–€β–šβ––β–β–Œ β–β–Œ β–ˆ β–β–Œ β–β–Œβ–β–›β–€β–šβ–– β–β–›β–€β–€β–˜β–β–›β–€β–šβ––β–β–›β–€β–œβ–Œβ–β–Œ β–β–Œβ–β–›β–€β–€β–˜β–β–Œ β–β–Œβ–β–Œ β–β–Œβ–β–›β–€β–šβ––β–β–›β–šβ––
5
5
  ' β–β–Œ β–β–Œβ–β–šβ–„β–žβ–˜ β–ˆ β–β–šβ–„β–žβ–˜β–β–Œ β–β–Œ β–β–Œ β–β–Œ β–β–Œβ–β–Œ β–β–Œβ–β–Œ β–β–Œβ–β–™β–„β–„β––β–β–™β–ˆβ–Ÿβ–Œβ–β–šβ–„β–žβ–˜β–β–Œ β–β–Œβ–β–Œ β–β–Œ
6
6
  ' Rotor Frameworkβ„’
7
- ' Version 0.3.7
7
+ ' Version 0.4.1
8
8
  ' Β© 2025 BalΓ‘zs MolnΓ‘r β€” MIT License
9
9
  ' =========================================================================
10
10
 
@@ -85,7 +85,7 @@ namespace Rotor
85
85
  class Framework
86
86
 
87
87
  name = "Rotor Framework"
88
- version = "0.3.7"
88
+ version = "0.4.1"
89
89
 
90
90
  config = {
91
91
  tasks: invalid, ' @array (optional)
@@ -4,7 +4,7 @@
4
4
  ' β–β–›β–€β–šβ––β–β–Œ β–β–Œ β–ˆ β–β–Œ β–β–Œβ–β–›β–€β–šβ–– β–β–›β–€β–€β–˜β–β–›β–€β–šβ––β–β–›β–€β–œβ–Œβ–β–Œ β–β–Œβ–β–›β–€β–€β–˜β–β–Œ β–β–Œβ–β–Œ β–β–Œβ–β–›β–€β–šβ––β–β–›β–šβ––
5
5
  ' β–β–Œ β–β–Œβ–β–šβ–„β–žβ–˜ β–ˆ β–β–šβ–„β–žβ–˜β–β–Œ β–β–Œ β–β–Œ β–β–Œ β–β–Œβ–β–Œ β–β–Œβ–β–Œ β–β–Œβ–β–™β–„β–„β––β–β–™β–ˆβ–Ÿβ–Œβ–β–šβ–„β–žβ–˜β–β–Œ β–β–Œβ–β–Œ β–β–Œ
6
6
  ' Rotor Frameworkβ„’
7
- ' Version 0.3.7
7
+ ' Version 0.4.1
8
8
  ' Β© 2025 BalΓ‘zs MolnΓ‘r β€” MIT License
9
9
  ' =========================================================================
10
10
 
@@ -70,7 +70,7 @@ namespace Rotor
70
70
  class FrameworkTask
71
71
 
72
72
  name = "Rotor Framework"
73
- version = "0.3.7"
73
+ version = "0.4.1"
74
74
 
75
75
  config = {
76
76
  tasks: invalid, ' optional
@@ -184,9 +184,9 @@ namespace Rotor
184
184
  data = msg.getData()
185
185
  extraInfo = msg.GetInfo() ' Info AA passed during observeFieldScoped
186
186
 
187
- if extraInfo?.asyncReducerCallbackId <> invalid and m.dispatcherProvider.get(extraInfo?.asyncReducerCallbackId) <> invalid
187
+ if extraInfo?.dispatcherId <> invalid and m.dispatcherProvider.get(extraInfo?.dispatcherId) <> invalid
188
188
  ' Catch by dispatcherId
189
- m.dispatcherProvider.get(extraInfo?.asyncReducerCallbackId).asyncReducerCallback(msg)
189
+ m.dispatcherProvider.get(extraInfo?.dispatcherId).asyncReducerCallback(msg)
190
190
  else
191
191
  dispatcherId = fieldId
192
192
  dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
@@ -72,9 +72,13 @@ namespace Rotor
72
72
 
73
73
  ' Framework Integration
74
74
  getFrameworkInstance as Function ' Get framework instance
75
- getDispatcher as Function ' Get dispatcher facade by ID
76
- createDispatcher as Function ' Create dispatcher
77
- animator as Function ' Get animator factory
75
+ getDispatcher as Function ' Get dispatcher facade by ID
76
+ createDispatcher as Function ' Create dispatcher
77
+ animator as Function ' Get animator factory
78
+
79
+ ' i18n Integration
80
+ getLocale as String ' Get locale e.g: "en_US"
81
+ isRTL as Boolean ' Indicates whether the current locale uses right-to-left text
78
82
 
79
83
  ' =============================================================
80
84
  ' PLUGIN METHODS
@@ -96,6 +96,18 @@ namespace Rotor.ViewBuilder
96
96
  return m.getFrameworkInstance().builder.widgetTree.getSubtreeClone(searchPattern, keyPathList, m.parentHID)
97
97
  end function
98
98
 
99
+ ' Get isRTL flag
100
+ widget.isRtl = function() as boolean
101
+ i18nService = m.getFrameworkInstance().i18nService
102
+ return i18nService.getIsRtl()
103
+ end function
104
+
105
+ ' Get locale string
106
+ widget.getLocale = function() as string
107
+ i18nService = m.getFrameworkInstance().i18nService
108
+ return i18nService.getLocale()
109
+ end function
110
+
99
111
  ' render - Renders widget updates (self, descendants, or children) *'
100
112
  widget.render = sub(payloads as dynamic, params = {} as object)
101
113
  for each payload in Rotor.Utils.ensureArray(payloads)
@@ -133,6 +145,18 @@ namespace Rotor.ViewBuilder
133
145
  return m.getFrameworkInstance().dispatcherProvider.getFacade(dispatcherId, m)
134
146
  end function
135
147
 
148
+ ' Dispatch shortcut - Dispatches an event via specific dispatcher'
149
+ widget.dispatchTo = sub(dispatcherId as string, dispatchObject as object)
150
+ dispatcherFaced = m.getDispatcher(dispatcherId)
151
+ dispatcherFaced.dispatch(dispatchObject)
152
+ end sub
153
+
154
+ ' Listen shortcut - Listen to specific dispatcher'
155
+ widget.getStateFrom = function(dispatcherId as string)
156
+ dispatcherFaced = m.getDispatcher(dispatcherId)
157
+ return dispatcherFaced.getState()
158
+ end function
159
+
136
160
  ' animator - Gets animator factory by ID *'
137
161
  widget.animator = function(animatorId) as object
138
162
  return m.getFrameworkInstance().animatorProvider.getFactory(animatorId, m)
@@ -159,16 +183,6 @@ namespace Rotor.ViewBuilder
159
183
  keysPaths = Rotor.Utils.isString(config.i18n?.path) ? config.i18n.path : Rotor.Utils.isArray(config.i18n?.paths) ? config.i18n.paths : invalid
160
184
  widget.viewModelState.l10n = i18nService.getL10n(keysPaths)
161
185
 
162
- ' Include isRTL flag if requested
163
- if config?.i18n?.includeIsRtl = true
164
- widget.viewModelState.isRTL = i18nService.getIsRtl()
165
- end if
166
-
167
- ' Include locale string if requested
168
- if config?.i18n?.includeLocale = true
169
- widget.viewModelState.locale = i18nService.getLocale()
170
- end if
171
-
172
186
  ' Call lifecycle hook before template compilation
173
187
  widget.onCreateView()
174
188
 
@@ -32,8 +32,6 @@ namespace Rotor.ViewBuilder
32
32
  sub init(frameworkInstance as Rotor.Framework)
33
33
  ' Set current locale from framework device info
34
34
  currentLocale = frameworkInstance.getInfo().device.currentLocale
35
- ' Store rootWidget reference
36
- m.rootWidget = frameworkInstance.getRootWidget()
37
35
  ' Store current locale
38
36
  m.setLocale(currentLocale)
39
37
  end sub
@@ -46,9 +44,6 @@ namespace Rotor.ViewBuilder
46
44
  sub setLocale(locale as string)
47
45
  m.locale = locale
48
46
  m.isRTL = m.detectRTL(locale)
49
- ' update root widget's locale and isRTL
50
- m.rootWidget.viewModelState.locale = m.locale
51
- m.rootWidget.viewModelState.isRTL = m.isRTL
52
47
  end sub
53
48
 
54
49
  ' ---------------------------------------------------------------------
@@ -88,10 +83,6 @@ namespace Rotor.ViewBuilder
88
83
  '
89
84
  sub setL10n(l10n as object)
90
85
  m.l10n = l10n
91
-
92
- ' Update root widget's l10n reference
93
- m.rootWidget.viewModelState.l10n = m.l10n
94
-
95
86
  m.refreshCache()
96
87
  end sub
97
88
 
@@ -102,7 +93,6 @@ namespace Rotor.ViewBuilder
102
93
  '
103
94
  sub extendL10n(l10n as object)
104
95
  Rotor.Utils.deepExtendAA(m.l10n, l10n)
105
- m.rootWidget.viewModelState.l10n = m.l10n
106
96
  m.refreshCache()
107
97
  end sub
108
98
 
@@ -110,7 +100,6 @@ namespace Rotor.ViewBuilder
110
100
  ' destroy - Cleans up cache and l10n data
111
101
  '
112
102
  sub destroy()
113
- m.rootWidget = invalid
114
103
  m.cache.Clear()
115
104
  m.l10n.Clear()
116
105
  end sub
@@ -972,32 +972,34 @@ namespace Rotor
972
972
 
973
973
  refSegmentTop = focused.metrics.segments[Rotor.Const.Segment.TOP]
974
974
  refSegmentRight = focused.metrics.segments[Rotor.Const.Segment.RIGHT]
975
+ refSegmentLeft = focused.metrics.segments[Rotor.Const.Segment.LEFT]
976
+ refSegmentBottom = focused.metrics.segments[Rotor.Const.Segment.BOTTOM]
975
977
  referencePoint = { x: (refSegmentTop.x1 + refSegmentRight.x2) / 2, y: (refSegmentTop.y1 + refSegmentRight.y2) / 2 }
976
978
 
977
979
  validators = {
978
980
 
979
- "left": function(referencePoint as object, segments as object) as object
981
+ "left": function(referencePoint as object, segments as object, refSegmentLeft as object, refSegmentRight as object) as object
980
982
  right = segments[Rotor.Const.Segment.RIGHT]
981
- ' stop
982
- return right.x1 <= referencePoint.x ? { isValid: true, segment: right } : { isValid: false }
983
+ ' Candidate's right edge must be strictly left of focused element's left edge
984
+ return right.x2 <= refSegmentLeft.x1 ? { isValid: true, segment: right } : { isValid: false }
983
985
  end function,
984
986
 
985
- "up": function(referencePoint as object, segments as object) as object
987
+ "up": function(referencePoint as object, segments as object, refSegmentTop as object, refSegmentBottom as object) as object
986
988
  bottom = segments[Rotor.Const.Segment.BOTTOM]
987
- ' stop
988
- return bottom.y1 <= referencePoint.y ? { isValid: true, segment: bottom } : { isValid: false }
989
+ ' Candidate's bottom edge must be strictly above focused element's top edge
990
+ return bottom.y2 <= refSegmentTop.y1 ? { isValid: true, segment: bottom } : { isValid: false }
989
991
  end function,
990
992
 
991
- "right": function(referencePoint as object, segments as object) as object
993
+ "right": function(referencePoint as object, segments as object, refSegmentLeft as object, refSegmentRight as object) as object
992
994
  left = segments[Rotor.Const.Segment.LEFT]
993
- ' stop
994
- return left.x1 >= referencePoint.x ? { isValid: true, segment: left } : { isValid: false }
995
+ ' Candidate's left edge must be strictly right of focused element's right edge
996
+ return left.x1 >= refSegmentRight.x2 ? { isValid: true, segment: left } : { isValid: false }
995
997
  end function,
996
998
 
997
- "down": function(referencePoint as object, segments as object) as object
999
+ "down": function(referencePoint as object, segments as object, refSegmentTop as object, refSegmentBottom as object) as object
998
1000
  top = segments[Rotor.Const.Segment.TOP]
999
- ' stop
1000
- return top.y1 >= referencePoint.y ? { isValid: true, segment: top } : { isValid: false }
1001
+ ' Candidate's top edge must be strictly below focused element's bottom edge
1002
+ return top.y1 >= refSegmentBottom.y2 ? { isValid: true, segment: top } : { isValid: false }
1001
1003
  end function
1002
1004
  }
1003
1005
  segments = {}
@@ -1006,7 +1008,12 @@ namespace Rotor
1006
1008
  if HID <> focused.HID
1007
1009
  focusItem = m.focusItemStack.get(HID)
1008
1010
  focusItem.refreshBounding()
1009
- result = validator(referencePoint, focusItem.metrics.segments)
1011
+ ' Pass appropriate reference segments based on direction
1012
+ if direction = "left" or direction = "right"
1013
+ result = validator(referencePoint, focusItem.metrics.segments, refSegmentLeft, refSegmentRight)
1014
+ else ' up or down
1015
+ result = validator(referencePoint, focusItem.metrics.segments, refSegmentTop, refSegmentBottom)
1016
+ end if
1010
1017
  if result.isValid
1011
1018
  segments[HID] = result.segment
1012
1019
  end if