neo.mjs 10.0.0-alpha.5 → 10.0.0-beta.2

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.
Files changed (188) hide show
  1. package/ServiceWorker.mjs +2 -2
  2. package/apps/colors/view/GridContainer.mjs +1 -1
  3. package/apps/covid/view/AttributionComponent.mjs +1 -1
  4. package/apps/covid/view/HeaderContainer.mjs +6 -6
  5. package/apps/covid/view/MainContainerController.mjs +5 -5
  6. package/apps/covid/view/TableContainerController.mjs +1 -1
  7. package/apps/covid/view/country/Gallery.mjs +13 -13
  8. package/apps/covid/view/country/Helix.mjs +13 -13
  9. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -1
  10. package/apps/email/view/Viewport.mjs +2 -2
  11. package/apps/form/view/SideNavList.mjs +1 -1
  12. package/apps/portal/index.html +1 -1
  13. package/apps/portal/resources/data/examples_devmode.json +26 -27
  14. package/apps/portal/resources/data/examples_dist_dev.json +26 -27
  15. package/apps/portal/resources/data/examples_dist_esm.json +25 -26
  16. package/apps/portal/resources/data/examples_dist_prod.json +26 -27
  17. package/apps/portal/view/HeaderToolbar.mjs +3 -3
  18. package/apps/portal/view/about/Container.mjs +2 -2
  19. package/apps/portal/view/about/MemberContainer.mjs +3 -3
  20. package/apps/portal/view/blog/List.mjs +7 -7
  21. package/apps/portal/view/examples/List.mjs +4 -4
  22. package/apps/portal/view/home/ContentBox.mjs +2 -2
  23. package/apps/portal/view/home/FeatureSection.mjs +3 -3
  24. package/apps/portal/view/home/FooterContainer.mjs +7 -7
  25. package/apps/portal/view/home/parts/AfterMath.mjs +3 -3
  26. package/apps/portal/view/home/parts/MainNeo.mjs +3 -3
  27. package/apps/portal/view/home/parts/References.mjs +6 -6
  28. package/apps/portal/view/learn/ContentComponent.mjs +102 -111
  29. package/apps/portal/view/learn/PageSectionsContainer.mjs +1 -1
  30. package/apps/portal/view/learn/PageSectionsList.mjs +2 -2
  31. package/apps/portal/view/services/Component.mjs +16 -16
  32. package/apps/realworld/view/FooterComponent.mjs +1 -1
  33. package/apps/realworld/view/HeaderComponent.mjs +8 -8
  34. package/apps/realworld/view/HomeComponent.mjs +6 -6
  35. package/apps/realworld/view/article/CommentComponent.mjs +4 -4
  36. package/apps/realworld/view/article/Component.mjs +14 -14
  37. package/apps/realworld/view/article/CreateCommentComponent.mjs +3 -3
  38. package/apps/realworld/view/article/CreateComponent.mjs +3 -3
  39. package/apps/realworld/view/article/PreviewComponent.mjs +1 -1
  40. package/apps/realworld/view/article/TagListComponent.mjs +2 -2
  41. package/apps/realworld/view/user/ProfileComponent.mjs +8 -8
  42. package/apps/realworld/view/user/SettingsComponent.mjs +4 -4
  43. package/apps/realworld/view/user/SignUpComponent.mjs +4 -4
  44. package/apps/realworld2/view/FooterComponent.mjs +1 -1
  45. package/apps/realworld2/view/HomeContainer.mjs +3 -3
  46. package/apps/realworld2/view/article/DetailsContainer.mjs +1 -1
  47. package/apps/realworld2/view/article/PreviewComponent.mjs +7 -7
  48. package/apps/realworld2/view/article/TagListComponent.mjs +2 -2
  49. package/apps/realworld2/view/user/ProfileContainer.mjs +1 -1
  50. package/apps/route/view/center/CardAdministration.mjs +2 -2
  51. package/apps/route/view/center/CardAdministrationDenied.mjs +1 -1
  52. package/apps/route/view/center/CardContact.mjs +2 -2
  53. package/apps/route/view/center/CardHome.mjs +1 -1
  54. package/apps/route/view/center/CardSection1.mjs +1 -1
  55. package/apps/route/view/center/CardSection2.mjs +1 -1
  56. package/apps/sharedcovid/view/AttributionComponent.mjs +1 -1
  57. package/apps/sharedcovid/view/HeaderContainer.mjs +6 -6
  58. package/apps/sharedcovid/view/MainContainerController.mjs +5 -5
  59. package/apps/sharedcovid/view/TableContainerController.mjs +1 -1
  60. package/apps/sharedcovid/view/country/Gallery.mjs +13 -13
  61. package/apps/sharedcovid/view/country/Helix.mjs +13 -13
  62. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -1
  63. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +1 -1
  64. package/apps/shareddialog/view/MainContainer.mjs +1 -1
  65. package/buildScripts/createApp.mjs +2 -2
  66. package/examples/table/cellEditing/MainContainer.mjs +1 -1
  67. package/examples/table/container/MainContainer.mjs +3 -3
  68. package/examples/table/nestedRecordFields/Viewport.mjs +6 -6
  69. package/examples/tableFiltering/MainContainer.mjs +1 -1
  70. package/examples/tablePerformance/MainContainer.mjs +1 -1
  71. package/examples/tablePerformance/MainContainer2.mjs +1 -1
  72. package/examples/tablePerformance/MainContainer3.mjs +2 -2
  73. package/examples/tableStore/MainContainer.mjs +2 -2
  74. package/learn/Glossary.md +261 -0
  75. package/learn/UsingTheseTopics.md +2 -2
  76. package/learn/benefits/ConfigSystem.md +538 -28
  77. package/learn/benefits/Effort.md +47 -2
  78. package/learn/benefits/Features.md +50 -32
  79. package/learn/benefits/FormsEngine.md +68 -38
  80. package/learn/benefits/MultiWindow.md +33 -7
  81. package/learn/benefits/OffTheMainThread.md +2 -2
  82. package/learn/benefits/Quick.md +45 -12
  83. package/learn/benefits/RPCLayer.md +75 -0
  84. package/learn/benefits/Speed.md +16 -11
  85. package/learn/gettingstarted/ComponentModels.md +4 -4
  86. package/learn/gettingstarted/Config.md +6 -6
  87. package/learn/gettingstarted/DescribingTheUI.md +4 -4
  88. package/learn/gettingstarted/Events.md +6 -6
  89. package/learn/gettingstarted/Extending.md +4 -4
  90. package/learn/gettingstarted/References.md +6 -6
  91. package/learn/gettingstarted/Workspaces.md +6 -6
  92. package/learn/guides/ApplicationBootstrap.md +26 -26
  93. package/learn/guides/ComponentsAndContainers.md +12 -12
  94. package/learn/guides/ConfigSystemDeepDive.md +280 -0
  95. package/learn/guides/CustomComponents.md +2 -2
  96. package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +17 -17
  97. package/learn/guides/InstanceLifecycle.md +295 -1
  98. package/learn/guides/MainThreadAddons.md +475 -0
  99. package/learn/guides/PortalApp.md +2 -2
  100. package/learn/guides/StateProviders.md +12 -12
  101. package/learn/guides/WorkingWithVDom.md +14 -14
  102. package/learn/guides/events/CustomEvents.md +16 -16
  103. package/learn/guides/events/DomEvents.md +12 -12
  104. package/learn/javascript/ClassFeatures.md +3 -2
  105. package/learn/javascript/Classes.md +8 -8
  106. package/learn/javascript/NewNode.md +4 -4
  107. package/learn/javascript/Overrides.md +8 -8
  108. package/learn/javascript/Super.md +10 -8
  109. package/learn/tree.json +52 -51
  110. package/learn/tutorials/Earthquakes.md +54 -57
  111. package/learn/tutorials/TodoList.md +4 -4
  112. package/package.json +2 -2
  113. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +12 -0
  114. package/resources/scss/src/table/{View.scss → Body.scss} +1 -1
  115. package/resources/scss/src/table/plugin/CellEditing.scss +1 -1
  116. package/resources/scss/theme-dark/table/{View.scss → Body.scss} +1 -1
  117. package/resources/scss/theme-light/table/{View.scss → Body.scss} +1 -1
  118. package/resources/scss/theme-neo-light/Global.scss +1 -2
  119. package/resources/scss/theme-neo-light/table/{View.scss → Body.scss} +1 -1
  120. package/src/DefaultConfig.mjs +2 -2
  121. package/src/Main.mjs +8 -7
  122. package/src/Neo.mjs +16 -2
  123. package/src/button/Base.mjs +2 -2
  124. package/src/calendar/view/SettingsContainer.mjs +2 -2
  125. package/src/calendar/view/YearComponent.mjs +9 -9
  126. package/src/calendar/view/calendars/ColorsList.mjs +1 -1
  127. package/src/calendar/view/calendars/List.mjs +1 -1
  128. package/src/calendar/view/month/Component.mjs +15 -15
  129. package/src/calendar/view/week/Component.mjs +12 -12
  130. package/src/calendar/view/week/EventDragZone.mjs +4 -4
  131. package/src/calendar/view/week/TimeAxisComponent.mjs +3 -3
  132. package/src/component/Base.mjs +17 -2
  133. package/src/component/Carousel.mjs +2 -2
  134. package/src/component/Chip.mjs +3 -3
  135. package/src/component/Circle.mjs +2 -2
  136. package/src/component/DateSelector.mjs +8 -8
  137. package/src/component/Helix.mjs +1 -1
  138. package/src/component/Label.mjs +3 -18
  139. package/src/component/Legend.mjs +3 -3
  140. package/src/component/MagicMoveText.mjs +6 -14
  141. package/src/component/Process.mjs +3 -3
  142. package/src/component/Progress.mjs +1 -1
  143. package/src/component/StatusBadge.mjs +2 -2
  144. package/src/component/Timer.mjs +2 -2
  145. package/src/component/Toast.mjs +5 -3
  146. package/src/container/AccordionItem.mjs +2 -2
  147. package/src/container/Base.mjs +1 -1
  148. package/src/core/Base.mjs +77 -14
  149. package/src/core/Util.mjs +14 -2
  150. package/src/date/DayViewComponent.mjs +2 -2
  151. package/src/date/SelectorContainer.mjs +1 -1
  152. package/src/draggable/grid/header/toolbar/SortZone.mjs +21 -21
  153. package/src/draggable/table/header/toolbar/SortZone.mjs +1 -1
  154. package/src/form/field/CheckBox.mjs +4 -4
  155. package/src/form/field/FileUpload.mjs +25 -39
  156. package/src/form/field/Range.mjs +1 -1
  157. package/src/form/field/Text.mjs +3 -3
  158. package/src/form/field/TextArea.mjs +2 -3
  159. package/src/grid/Body.mjs +8 -5
  160. package/src/grid/_export.mjs +1 -1
  161. package/src/list/Color.mjs +2 -2
  162. package/src/main/DeltaUpdates.mjs +157 -98
  163. package/src/main/addon/AmCharts.mjs +61 -84
  164. package/src/main/addon/Base.mjs +161 -42
  165. package/src/main/addon/GoogleMaps.mjs +9 -16
  166. package/src/main/addon/HighlightJS.mjs +2 -13
  167. package/src/main/addon/IntersectionObserver.mjs +21 -21
  168. package/src/main/addon/MonacoEditor.mjs +32 -64
  169. package/src/manager/ClassHierarchy.mjs +114 -0
  170. package/src/menu/List.mjs +1 -1
  171. package/src/plugin/Popover.mjs +2 -2
  172. package/src/sitemap/Component.mjs +1 -1
  173. package/src/table/{View.mjs → Body.mjs} +25 -22
  174. package/src/table/Container.mjs +43 -43
  175. package/src/table/_export.mjs +2 -2
  176. package/src/table/plugin/CellEditing.mjs +19 -19
  177. package/src/tooltip/Base.mjs +1 -6
  178. package/src/tree/Accordion.mjs +3 -3
  179. package/src/vdom/Helper.mjs +19 -22
  180. package/src/worker/App.mjs +1 -2
  181. package/src/worker/Base.mjs +7 -5
  182. package/src/worker/Canvas.mjs +2 -3
  183. package/src/worker/Data.mjs +5 -7
  184. package/src/worker/Task.mjs +2 -3
  185. package/src/worker/VDom.mjs +3 -4
  186. package/src/worker/mixin/RemoteMethodAccess.mjs +5 -2
  187. package/learn/guides/MainThreadAddonExample.md +0 -15
  188. package/learn/guides/MainThreadAddonIntro.md +0 -44
@@ -37,6 +37,18 @@ class AmCharts extends Base {
37
37
  * @protected
38
38
  */
39
39
  fallbackPath: 'https://raw.githubusercontent.com/neomjs/pages/main/resources_pub/amCharts/',
40
+ /**
41
+ * List methods which must get cached until the addon reaches its `isReady` state
42
+ * @member {String[]} interceptRemotes
43
+ */
44
+ interceptRemotes: [
45
+ 'callMethod',
46
+ 'create',
47
+ 'destroy',
48
+ 'setProperties',
49
+ 'setProperty',
50
+ 'updateData'
51
+ ],
40
52
  /**
41
53
  * Remote method access for other workers
42
54
  * @member {Object} remote
@@ -91,19 +103,15 @@ class AmCharts extends Base {
91
103
  callMethod(data) {
92
104
  let me = this;
93
105
 
94
- if (!me.isReady) {
95
- return me.cacheMethodCall({fn: 'callMethod', data})
96
- } else {
97
- if (me.hasChart(data.id)) {
98
- let chart = me.charts[data.id],
99
- pathArray = data.path.split('.'),
100
- methodName = pathArray.pop(),
101
- scope = pathArray.length < 1 ? chart: Neo.ns(pathArray.join('.'), false, chart);
106
+ if (me.hasChart(data.id)) {
107
+ let chart = me.charts[data.id],
108
+ pathArray = data.path.split('.'),
109
+ methodName = pathArray.pop(),
110
+ scope = pathArray.length < 1 ? chart: Neo.ns(pathArray.join('.'), false, chart);
102
111
 
103
- scope[methodName].call(scope, ...data.params || [])
104
- } else {
105
- // todo
106
- }
112
+ scope[methodName].call(scope, ...data.params || [])
113
+ } else {
114
+ // todo
107
115
  }
108
116
  }
109
117
 
@@ -137,29 +145,25 @@ class AmCharts extends Base {
137
145
  create(data) {
138
146
  let me = this;
139
147
 
140
- if (!me.isReady) {
141
- return me.cacheMethodCall({fn: 'create', data})
142
- } else {
143
- // todo: check if globalThis[data.package] exists, if not load it and call create afterwards
144
- am4core.useTheme(am4themes_dark);
148
+ // todo: check if globalThis[data.package] exists, if not load it and call create afterwards
149
+ am4core.useTheme(am4themes_dark);
145
150
 
146
- me.charts[data.id] = am4core.createFromConfig(data.config, data.id, globalThis[data.package][data.type || 'XYChart']);
151
+ me.charts[data.id] = am4core.createFromConfig(data.config, data.id, globalThis[data.package][data.type || 'XYChart']);
147
152
 
148
- if (data.combineSeriesTooltip) {
149
- me.combineSeriesTooltip(me.charts[data.id])
150
- }
153
+ if (data.combineSeriesTooltip) {
154
+ me.combineSeriesTooltip(me.charts[data.id])
155
+ }
151
156
 
152
- // in case data has arrived before the chart got created, apply it now
153
- if (data.data) {
154
- me.updateData({
155
- data : data.data,
156
- dataPath: data.dataPath,
157
- id : data.id
158
- })
159
- } else if (me.dataMap[data.id]) {
160
- me.updateData(me.dataMap[data.id]);
161
- delete me.dataMap[data.id]
162
- }
157
+ // in case data has arrived before the chart got created, apply it now
158
+ if (data.data) {
159
+ me.updateData({
160
+ data : data.data,
161
+ dataPath: data.dataPath,
162
+ id : data.id
163
+ })
164
+ } else if (me.dataMap[data.id]) {
165
+ me.updateData(me.dataMap[data.id]);
166
+ delete me.dataMap[data.id]
163
167
  }
164
168
  }
165
169
 
@@ -167,15 +171,9 @@ class AmCharts extends Base {
167
171
  * @param {Object} data
168
172
  * @param {String} data.id
169
173
  */
170
- destroy(data) {
171
- let me = this;
172
-
173
- if (!me.isReady) {
174
- return me.cacheMethodCall({fn: 'destroy', data})
175
- } else {
176
- me.charts[data.id]?.dispose?.();
177
- delete me.charts[data.id]
178
- }
174
+ destroy({id}) {
175
+ this.charts[id]?.dispose?.();
176
+ delete this.charts[id]
179
177
  }
180
178
 
181
179
  /**
@@ -192,7 +190,7 @@ class AmCharts extends Base {
192
190
  * => fetching the other files after core.js is loaded
193
191
  * @param {Boolean} useFallback=false
194
192
  */
195
- loadFiles(useFallback=false) {
193
+ async loadFiles(useFallback=false) {
196
194
  let me = this,
197
195
  useFallbackPath = me.useFallbackPath || useFallback,
198
196
  basePath;
@@ -207,24 +205,21 @@ class AmCharts extends Base {
207
205
  basePath = useFallbackPath ? me.fallbackPath : me.downloadPath
208
206
  }
209
207
 
210
- me.isLoading = true;
208
+ try {
209
+ await DomAccess.loadScript(basePath + 'core.js');
211
210
 
212
- DomAccess.loadScript(basePath + 'core.js').then(() => {
213
- Promise.all([
211
+ await Promise.all([
214
212
  DomAccess.loadScript(basePath + 'charts.js'),
215
213
  DomAccess.loadScript(basePath + 'maps.js'),
216
214
  DomAccess.loadScript(basePath + 'themes/dark.js'),
217
215
  DomAccess.loadScript(basePath + 'geodata/worldLow.js')
218
- ]).then(() => {
219
- me.isLoading = false;
220
- me.isReady = true
221
- })
222
- }).catch(e => {
216
+ ])
217
+ } catch(e) {
223
218
  if (!useFallback && !me.useFallbackPath) {
224
219
  console.log('Download from amcharts.com failed, switching to fallback', e);
225
- me.loadFiles(true)
220
+ await me.loadFiles(true)
226
221
  }
227
- })
222
+ }
228
223
  }
229
224
 
230
225
  /**
@@ -232,20 +227,10 @@ class AmCharts extends Base {
232
227
  * @param {String} data.id
233
228
  * @param {Object} data.properties
234
229
  */
235
- setProperties(data) {
236
- let me = this;
237
-
238
- if (!me.isReady) {
239
- return me.cacheMethodCall({fn: 'setProperties', data})
240
- } else {
241
- Object.entries(data.properties).forEach(([key, value]) => {
242
- me.setProperty({
243
- id : data.id,
244
- path : key,
245
- value
246
- })
247
- })
248
- }
230
+ setProperties({id, properties}) {
231
+ Object.entries(properties).forEach(([key, value]) => {
232
+ this.setProperty({id, path: key, value})
233
+ })
249
234
  }
250
235
 
251
236
  /**
@@ -255,22 +240,16 @@ class AmCharts extends Base {
255
240
  * @param {String} data.path
256
241
  * @param {*} data.value
257
242
  */
258
- setProperty(data) {
259
- let me = this;
260
-
261
- if (!me.isReady) {
262
- return me.cacheMethodCall({fn: 'setProperty', data})
243
+ setProperty({id, isColor=false, path, value}) {
244
+ if (this.hasChart(id)) {
245
+ let chart = this.charts[id],
246
+ pathArray = path.split('.'),
247
+ propertyName = pathArray.pop(),
248
+ scope = Neo.ns(pathArray.join('.'), false, chart);
249
+
250
+ scope[propertyName] = isColor ? am4core.color(value) : value
263
251
  } else {
264
- if (this.hasChart(data.id)) {
265
- let chart = this.charts[data.id],
266
- pathArray = data.path.split('.'),
267
- propertyName = pathArray.pop(),
268
- scope = Neo.ns(pathArray.join('.'), false, chart);
269
-
270
- scope[propertyName] = data.isColor ? am4core.color(data.value) : data.value
271
- } else {
272
- // todo
273
- }
252
+ // todo
274
253
  }
275
254
  }
276
255
 
@@ -283,9 +262,7 @@ class AmCharts extends Base {
283
262
  updateData(data) {
284
263
  let me = this;
285
264
 
286
- if (!me.isReady) {
287
- return me.cacheMethodCall({fn: 'updateData', data})
288
- } else if (!me.hasChart(data.id)) {
265
+ if (!me.hasChart(data.id)) {
289
266
  me.dataMap[data.id] = data
290
267
  } else {
291
268
  let chart = me.charts[data.id];
@@ -4,6 +4,11 @@ import CoreBase from '../../core/Base.mjs';
4
4
  * Base class for main thread addons
5
5
  * @class Neo.main.addon.Base
6
6
  * @extends Neo.core.Base
7
+ *
8
+ * This version aligns the file loading and readiness state according to the rule:
9
+ * `initAsync()` MUST await for `loadFiles()` to be completed before the addon is considered `isReady`.
10
+ * `preloadFilesDelay` controls when `loadFiles()` is initiated in the background, but can be
11
+ * overridden by `cacheMethodCall()`.
7
12
  */
8
13
  class Base extends CoreBase {
9
14
  static config = {
@@ -19,34 +24,39 @@ class Base extends CoreBase {
19
24
  */
20
25
  isMainThreadAddon: true,
21
26
  /**
22
- * Will get set to true once all addon related files got loaded (if there is a need to load)
23
- * @member {Boolean} isReady_=false
24
- * @protected
25
- */
26
- isReady_: false,
27
- /**
28
- * Amount in ms to delay the loading of library files, unless remote method access happens
29
- * Change the value to false in case you don't want an automated preloading
27
+ * Amount in ms to delay the background loading of library files.
28
+ * Set to `false` to disable automated preloading and rely solely on lazy loading
29
+ * via `cacheMethodCall()`. Set to `0` for immediate background preload.
30
30
  * @member {Boolean|Number} preloadFilesDelay=5000
31
31
  * @protected
32
32
  */
33
- preloadFilesDelay: 5000,
33
+ preloadFilesDelay: 5000
34
34
  }
35
35
 
36
36
  /**
37
+ * Internal cache for remote method calls received when `isReady` is false.
37
38
  * @member {Object[]} cache=[]
38
39
  */
39
40
  cache = []
40
41
  /**
41
- * Will get set to true once we start loading Monaco related files
42
- * @member {Boolean} isLoading=false
42
+ * Returns true if `loadFiles()` has been initiated and is currently in progress.
43
+ * @member {Boolean} isLoading
43
44
  */
44
- isLoading = false
45
+ get isLoading() {
46
+ // isLoading is true if the promise exists and its resolver is still available (meaning it's pending).
47
+ return !!this.#loadFilesPromise && !!this.#loadFilesPromiseResolver
48
+ }
45
49
  /**
46
- * Internal flag to store the setTimeout() id for loading external files
47
- * @member {Number|null} loadingTimeoutId=null
50
+ * A private promise that tracks the completion of `loadFiles()`.
51
+ * This ensures `loadFiles()` is called only once and can be awaited by multiple consumers.
52
+ * @member {Promise<void>|null} #loadFilesPromise=null
48
53
  */
49
- loadingTimeoutId = null
54
+ #loadFilesPromise = null
55
+ /**
56
+ * The `resolve` function for `#loadFilesPromise`, allowing external control over its resolution.
57
+ * @member {Function|null} #loadFilesPromiseResolver=null
58
+ */
59
+ #loadFilesPromiseResolver = null
50
60
 
51
61
  /**
52
62
  * @param {Object} config
@@ -56,56 +66,165 @@ class Base extends CoreBase {
56
66
 
57
67
  let me = this;
58
68
 
59
- if (me.loadFiles) {
60
- if (me.preloadFilesDelay === 0) {
61
- me.loadFiles()
62
- } else if (Neo.isNumber(me.preloadFilesDelay)) {
63
- me.loadingTimeoutId = setTimeout(() => {
64
- me.loadFiles()
65
- }, me.preloadFilesDelay)
69
+ // Initialize #loadFilesPromise as a controllable promise.
70
+ // This promise will be awaited by initAsync and resolved by executeLoadFiles.
71
+ me.#loadFilesPromise = new Promise(resolve => {
72
+ me.#loadFilesPromiseResolver = resolve
73
+ });
74
+
75
+ if (me.preloadFilesDelay === false) {
76
+ // No automated preload: resolve #loadFilesPromise immediately as it won't be triggered by delay.
77
+ // It will only be triggered by cacheMethodCall or initAsync if needed.
78
+ me.#loadFilesPromiseResolver();
79
+ me.#loadFilesPromiseResolver = null // Mark as resolved/no longer pending
80
+ } else {
81
+ const delay = Neo.isNumber(me.preloadFilesDelay) ? me.preloadFilesDelay : 0;
82
+
83
+ if (delay === 0) {
84
+ // Immediate preload: Directly execute loadFiles and resolve the promise.
85
+ me.#executeLoadFiles()
86
+ } else {
87
+ // Delayed preload: Set up a timer to execute loadFiles later.
88
+ me.timeout(delay).then(() => {
89
+ // This callback checks if #loadFilesPromise is still pending (resolver is available).
90
+ if (me.#loadFilesPromiseResolver) {
91
+ me.#executeLoadFiles()
92
+ }
93
+ })
66
94
  }
67
95
  }
68
96
  }
69
97
 
70
98
  /**
71
- * Triggered after the isReady config got changed
99
+ * Executes the actual `loadFiles()` method and resolves `#loadFilesPromise`.
100
+ * This method is called internally to manage the single execution of `loadFiles()`.
101
+ * It ensures `loadFiles()` is only truly called once.
102
+ * @private
103
+ */
104
+ async #executeLoadFiles() {
105
+ let me = this;
106
+
107
+ // Only execute if the promise is still pending (resolver is available).
108
+ if (me.#loadFilesPromiseResolver) {
109
+ const resolver = me.#loadFilesPromiseResolver;
110
+ me.#loadFilesPromiseResolver = null; // Mark as no longer pending/resolved
111
+
112
+ await me.loadFiles();
113
+ resolver() // Resolve the main #loadFilesPromise
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Async initialization hook for instances.
119
+ * `initAsync` MUST await for `loadFiles()` to be completed. Only then the addon is ready.
120
+ * @returns {Promise<void>}
121
+ */
122
+ async initAsync() {
123
+ await super.initAsync();
124
+
125
+ let me = this;
126
+
127
+ // `initAsync` must always wait for `me.#loadFilesPromise` to complete its resolution,
128
+ // regardless of how it was triggered (immediate, delayed, or by cacheMethodCall).
129
+ // `me.#loadFilesPromise` is always initialized in `construct()`.
130
+ await me.#loadFilesPromise
131
+ }
132
+
133
+ /**
134
+ * Triggered after the `isReady` config got changed.
135
+ * When `isReady` becomes true, any cached remote method calls are executed.
136
+ * At this point, `initAsync` has already ensured that `me.#loadFilesPromise` is resolved.
137
+ *
138
+ * This method is kept synchronous, delegating the async cache processing to a private method.
139
+ *
72
140
  * @param {Boolean} value
73
141
  * @param {Boolean} oldValue
74
142
  * @protected
75
143
  */
76
- afterSetIsReady(value, oldValue) {
144
+ afterSetIsReady(value, oldValue) { // Keep this synchronous
77
145
  if (value) {
78
- let me = this,
79
- returnValue;
80
-
81
- me.cache.forEach(item => {
82
- returnValue = me[item.fn](item.data);
83
- item.resolve(returnValue)
84
- });
85
-
86
- me.cache = []
146
+ // Initiate the asynchronous processing of cached method calls.
147
+ // This method itself does not need to be awaited here.
148
+ this.#processCachedMethodCalls();
87
149
  }
88
150
  }
89
151
 
90
152
  /**
91
- * Internally caches call when isReady===false
92
- * Loads the library files in case this is not already happening
93
- * @param item
94
- * @returns {Promise<unknown>}
153
+ * Internally caches remote method calls if `isReady` is false.
154
+ * It also ensures that `loadFiles()` is initiated immediately, bypassing `preloadFilesDelay`.
155
+ * @param {Object} item - Contains method name (`fn`) and data (`data`).
156
+ * @returns {Promise<unknown>} A promise that resolves with the method's return value.
95
157
  */
96
158
  cacheMethodCall(item) {
97
159
  let me = this;
98
160
 
99
- if (!me.isLoading) {
100
- me.loadingTimeoutId && clearTimeout(me.loadingTimeoutId);
101
- me.loadingTimeoutId = null;
102
- me.loadFiles()
161
+ // If loadFiles is defined, and it hasn't started yet (i.e., #loadFilesPromiseResolver is still available),
162
+ // execute it now, bypassing any pending preloadFilesDelay timer.
163
+ if (me.#loadFilesPromiseResolver) {
164
+ me.#executeLoadFiles() // This will resolve #loadFilesPromise immediately
103
165
  }
104
166
 
105
167
  return new Promise((resolve, reject) => {
106
- me.cache.push({...item, resolve})
168
+ me.cache.push({...item, reject, resolve})
107
169
  })
108
170
  }
171
+
172
+ /**
173
+ * Placeholder method for loading external files.
174
+ * Subclasses (e.g., `Neo.main.addon.AmCharts`) must implement this.
175
+ * It **must** return a Promise that resolves when all necessary files are loaded.
176
+ * If `loadFiles()` is called multiple times, it should return the same pending promise
177
+ * or a resolved promise if files are already loaded.
178
+ * @returns {Promise<void>}
179
+ */
180
+ async loadFiles() {}
181
+
182
+ /**
183
+ * Handles intercepted remote method calls.
184
+ * If the addon is not ready, the call is cached using `cacheMethodCall()`.
185
+ * Otherwise, the original method is executed.
186
+ * @param {Object} msg The remote message object.
187
+ * @returns {Promise<any>} A promise that resolves with the method's return value.
188
+ */
189
+ onInterceptRemotes(msg) {
190
+ return this.cacheMethodCall({fn: msg.remoteMethod, data: msg.data})
191
+ }
192
+
193
+ /**
194
+ * Sequentially processes any method calls that were cached while the addon was not ready.
195
+ * This method is asynchronous to allow awaiting the execution of individual cached methods.
196
+ * @returns {Promise<void>} A promise that resolves when all cached methods have been processed.
197
+ * @private
198
+ */
199
+ async #processCachedMethodCalls() {
200
+ let me = this;
201
+
202
+ // Iterate over the cache items and await each one in sequence
203
+ for (const item of me.cache) {
204
+ let returnValue;
205
+
206
+ try {
207
+ returnValue = me[item.fn](item.data);
208
+
209
+ if (Neo.isPromise(returnValue)) {
210
+ returnValue = await returnValue;
211
+ }
212
+
213
+ item.resolve(returnValue)
214
+ } catch (e) {
215
+ // If an error occurs (either synchronous or a promise rejection),
216
+ // reject the promise associated with the current cached item.
217
+ item.reject(e)
218
+
219
+ // *** FAIL-FAST STRATEGY ***
220
+ // If any cached method call fails, we assume subsequent cached calls
221
+ // (especially for the same addon instance) are likely to also fail.
222
+ break
223
+ }
224
+ }
225
+
226
+ me.cache = []
227
+ }
109
228
  }
110
229
 
111
230
  export default Neo.setupClass(Base);
@@ -8,16 +8,19 @@ import Observable from '../../core/Observable.mjs';
8
8
  * @extends Neo.main.addon.Base
9
9
  */
10
10
  class GoogleMaps extends Base {
11
+ /**
12
+ * True automatically applies the core.Observable mixin
13
+ * @member {Boolean} observable=true
14
+ * @static
15
+ */
16
+ static observable = true
17
+
11
18
  static config = {
12
19
  /**
13
20
  * @member {String} className='Neo.main.addon.GoogleMaps'
14
21
  * @protected
15
22
  */
16
23
  className: 'Neo.main.addon.GoogleMaps',
17
- /**
18
- * @member {Neo.core.Base[]} mixins=[Observable]
19
- */
20
- mixins: [Observable],
21
24
  /**
22
25
  * @member {Object} remote
23
26
  * @protected
@@ -52,14 +55,6 @@ class GoogleMaps extends Base {
52
55
  */
53
56
  markers = {}
54
57
 
55
- /**
56
- * @param {Object} config
57
- */
58
- construct(config) {
59
- super.construct(config);
60
- this.loadApi()
61
- }
62
-
63
58
  /**
64
59
  * @param {Object} data
65
60
  * @param {Object} [data.anchorPoint] x & y
@@ -176,13 +171,11 @@ class GoogleMaps extends Base {
176
171
  /**
177
172
  * @protected
178
173
  */
179
- loadApi() {
174
+ async loadFiles() {
180
175
  let key = Neo.config.googleMapsApiKey,
181
176
  url = ' https://maps.googleapis.com/maps/api/js';
182
177
 
183
- DomAccess.loadScript(`${url}?key=${key}&v=weekly&callback=Neo.emptyFn`).then(() => {
184
- console.log('GoogleMaps API loaded')
185
- })
178
+ await DomAccess.loadScript(`${url}?key=${key}&v=weekly&callback=Neo.emptyFn`)
186
179
  }
187
180
 
188
181
  /**
@@ -68,17 +68,11 @@ class HighlightJS extends Base {
68
68
 
69
69
  /**
70
70
  * @param {Object} data
71
- * @returns {Boolean}
71
+ * @returns {Promise<void>}
72
72
  */
73
73
  async loadFiles(data) {
74
74
  let me = this;
75
75
 
76
- if (me.isLoading || me.isReady) {
77
- return true
78
- }
79
-
80
- me.isLoading = true;
81
-
82
76
  if (data) {
83
77
  delete data.appName;
84
78
  delete data.windowId;
@@ -91,12 +85,7 @@ class HighlightJS extends Base {
91
85
  await Promise.all([
92
86
  DomAccess.loadScript(me.highlightJsLineNumbersPath),
93
87
  Neo.main.addon.Stylesheet.createStyleSheet(null, 'hljs-theme', me.themePath)
94
- ]);
95
-
96
- me.isLoading = false;
97
- me.isReady = true;
98
-
99
- return true
88
+ ])
100
89
  }
101
90
 
102
91
  /**
@@ -29,17 +29,17 @@ class NeoIntersectionObserver extends Base {
29
29
  }
30
30
 
31
31
  /**
32
- * Storing data from observe() calls which arrived prior to register()
32
+ * Storing component ids and their IntersectionObservers
33
33
  * @member {Object} map={}
34
34
  * @protected
35
35
  */
36
- cache = {}
36
+ map = {}
37
37
  /**
38
- * Storing component ids and their IntersectionObservers
39
- * @member {Object} map={}
38
+ * Storing data from observe() calls which arrived prior to register()
39
+ * @member {Object} observeCache={}
40
40
  * @protected
41
41
  */
42
- map = {}
42
+ observeCache = {}
43
43
 
44
44
  /**
45
45
  * @param {Object} data
@@ -107,12 +107,12 @@ class NeoIntersectionObserver extends Base {
107
107
  * {Number} opts.countTargets: amount of found target nodes inside the DOM
108
108
  */
109
109
  observe(data) {
110
- let me = this,
111
- cache = me.cache,
112
- cached = false,
113
- {id, observe} = data,
114
- observer = me.map[data.id],
115
- targets = [];
110
+ let me = this,
111
+ cached = false,
112
+ {id, observe} = data,
113
+ {observeCache} = me,
114
+ observer = me.map[data.id],
115
+ targets = [];
116
116
 
117
117
  if (!Neo.isArray(observe)) {
118
118
  observe = [observe]
@@ -131,11 +131,11 @@ class NeoIntersectionObserver extends Base {
131
131
  } else {
132
132
  cached = true;
133
133
 
134
- if (!cache[id]) {
135
- cache[id] = []
134
+ if (!observeCache[id]) {
135
+ observeCache[id] = []
136
136
  }
137
137
 
138
- cache[id].push(data);
138
+ observeCache[id].push(data);
139
139
  }
140
140
 
141
141
  return {
@@ -157,10 +157,10 @@ class NeoIntersectionObserver extends Base {
157
157
  * if data.observe is not passed: true
158
158
  */
159
159
  register(data) {
160
- let me = this,
161
- {cache} = me,
162
- {id, observe} = data,
163
- returnValue = true,
160
+ let me = this,
161
+ {observeCache} = me,
162
+ {id, observe} = data,
163
+ returnValue = true,
164
164
  observer;
165
165
 
166
166
  me.map[id] = observer = new IntersectionObserver(me[data.callback].bind(me), {
@@ -175,9 +175,9 @@ class NeoIntersectionObserver extends Base {
175
175
  returnValue = me.observe({id, observe})
176
176
  }
177
177
 
178
- if (cache[id]) {
179
- cache[id].forEach(item => me.observe(item));
180
- delete cache[id]
178
+ if (observeCache[id]) {
179
+ observeCache[id].forEach(item => me.observe(item));
180
+ delete observeCache[id]
181
181
  }
182
182
 
183
183
  return returnValue