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
@@ -1,6 +1,5 @@
1
- import Field from './Base.mjs';
2
- import NeoArray from '../../util/Array.mjs';
3
- import StringUtil from '../../util/String.mjs';
1
+ import Field from './Base.mjs';
2
+ import NeoArray from '../../util/Array.mjs';
4
3
 
5
4
  const
6
5
  sizeRE = /^(\d+)(kb|mb|gb)?$/i,
@@ -352,7 +351,7 @@ class FileUpload extends Field {
352
351
  onConstructed() {
353
352
  super.onConstructed(...arguments);
354
353
 
355
- this.vdom.cn[4].html = this.chooseFile;
354
+ this.vdom.cn[4].text = this.chooseFile;
356
355
  }
357
356
 
358
357
  /**
@@ -392,12 +391,9 @@ class FileUpload extends Field {
392
391
  */
393
392
  onInputValueChange({ files }) {
394
393
  const
395
- me = this,
396
- {
397
- types,
398
- cls
399
- } = me,
400
- body = me.vdom.cn[1];
394
+ me = this,
395
+ {cls, types} = me,
396
+ body = me.vdom.cn[1];
401
397
 
402
398
  if (files.length) {
403
399
  NeoArray.remove(cls, 'neo-field-empty');
@@ -406,17 +402,16 @@ class FileUpload extends Field {
406
402
  const
407
403
  file = files.item(0),
408
404
  pointPos = file.name.lastIndexOf('.'),
409
- type = pointPos > -1 ? file.name.slice(pointPos + 1) : '',
410
- escapedFileName = StringUtil.escapeHtml(file.name);
405
+ type = pointPos > -1 ? file.name.slice(pointPos + 1) : '';
411
406
 
412
407
  if (me.types && !types[type]) {
413
- body.cn[0].html = escapedFileName;
414
- body.cn[1].html = `${me.invalidFileFormat} (.${type}) ${me.formatSize(file.size)}`;
408
+ body.cn[0].text = file.name;
409
+ body.cn[1].text = `${me.invalidFileFormat} (.${type}) ${me.formatSize(file.size)}`;
415
410
  me.error = me.pleaseUseTheseTypes?.replace('{allowedFileTypes}', Object.keys(types).join(' .'))
416
411
  }
417
412
  else if (file.size > me.maxSize) {
418
- body.cn[0].html = escapedFileName;
419
- body.cn[1].html = me.formatSize(file.size);
413
+ body.cn[0].text = file.name;
414
+ body.cn[1].text = me.formatSize(file.size);
420
415
  me.error = me.fileSizeMoreThan?.replace('{allowedFileSize}', String(me._maxSize).toUpperCase());
421
416
  }
422
417
  // If it passes the type and maxSize check, upload it
@@ -448,7 +443,7 @@ class FileUpload extends Field {
448
443
  await me.timeout(100);
449
444
  me.focus(me.vdom.cn[2].id);
450
445
 
451
- me.vdom.cn[1].cn[0].html = StringUtil.escapeHtml(file.name);
446
+ me.vdom.cn[1].cn[0].text = file.name;
452
447
  me.update();
453
448
  me.state = 'uploading';
454
449
 
@@ -492,10 +487,10 @@ class FileUpload extends Field {
492
487
 
493
488
  (vdom.style || (vdom.style = {}))['--upload-progress'] = `${progress}turn`;
494
489
 
495
- vdom.cn[1].cn[1].html = `${this.uploading}... (${Math.round(progress * 100)}%)`;
490
+ vdom.cn[1].cn[1].text = `${this.uploading}... (${Math.round(progress * 100)}%)`;
496
491
 
497
492
  this.uploadSize = loaded;
498
- this.update();
493
+ this.update()
499
494
  }
500
495
 
501
496
  onUploadAbort(e) {
@@ -665,15 +660,15 @@ class FileUpload extends Field {
665
660
  afterSetDocument(document) {
666
661
  if (document) {
667
662
  const
668
- me = this,
669
- { cls } = me;
663
+ me = this,
664
+ {cls} = me;
670
665
 
671
666
  NeoArray.remove(cls, 'neo-field-empty');
672
667
  me.cls = cls;
673
668
 
674
669
  me.documentId = document.id;
675
670
  me.fileSize = me.formatSize(document.size);
676
- me.vdom.cn[1].cn[0].html = StringUtil.escapeHtml(document.fileName);
671
+ me.vdom.cn[1].cn[0].text = document.fileName;
677
672
  me.state = me.documentStatusMap[document.status];
678
673
  }
679
674
  }
@@ -704,15 +699,15 @@ class FileUpload extends Field {
704
699
  isChangeEventNeeded = true;
705
700
  break;
706
701
  case 'upload-failed':
707
- status.html = `${me.uploadFailed}${isNaN(me.progress) ? '' : `... (${Math.round(me.progress * 100)}%)`}`;
702
+ status.text = `${me.uploadFailed}${isNaN(me.progress) ? '' : `... (${Math.round(me.progress * 100)}%)`}`;
708
703
  isChangeEventNeeded = true;
709
704
  break;
710
705
  case 'processing':
711
- status.html = `${me.scanning}... (${me.formatSize(me.uploadSize)})`;
706
+ status.text = `${me.scanning}... (${me.formatSize(me.uploadSize)})`;
712
707
  vdom.inert = true;
713
708
  break;
714
709
  case 'scan-failed':
715
- status.html = `${me.malwareFoundInFile}. \u2022 ${me.fileSize}`;
710
+ status.text = `${me.malwareFoundInFile}. \u2022 ${me.fileSize}`;
716
711
  me.error = me.pleaseCheck;
717
712
  isChangeEventNeeded = true;
718
713
  break;
@@ -721,19 +716,19 @@ class FileUpload extends Field {
721
716
  anchor.href = me.createUrl(me.downloadUrl, {
722
717
  [me.documentIdParameter] : me.documentId
723
718
  });
724
- status.html = me.fileSize;
719
+ status.text = me.fileSize;
725
720
  isChangeEventNeeded = true;
726
721
  break;
727
722
  case 'not-downloadable':
728
- status.html = me.document ? me.fileSize : `${me.successfullyUploaded} \u2022 ${me.fileSize}`;
723
+ status.text = me.document ? me.fileSize : `${me.successfullyUploaded} \u2022 ${me.fileSize}`;
729
724
  isChangeEventNeeded = true;
730
725
  break;
731
726
  case 'deleted':
732
- status.html = me.fileWasDeleted;
727
+ status.text = me.fileWasDeleted;
733
728
  isChangeEventNeeded = true;
734
729
  break;
735
730
  case 'error':
736
- status.html = me.fileIsInAnErrorState;
731
+ status.text = me.fileIsInAnErrorState;
737
732
  me.error = me.pleaseCheck;
738
733
  isChangeEventNeeded = true;
739
734
  }
@@ -812,16 +807,7 @@ class FileUpload extends Field {
812
807
  }
813
808
 
814
809
  afterSetError(text) {
815
- if (text) {
816
- this.vdom.cn[5].cn = [{
817
- vtype : 'text',
818
- html : text
819
- }];
820
- }
821
- else {
822
- this.vdom.cn[5].cn = [];
823
- }
824
-
810
+ this.vdom.cn[5].cn = text ? [{vtype : 'text', text}] : [];
825
811
  this.validate();
826
812
  this.update();
827
813
  }
@@ -109,7 +109,7 @@ class Range extends Number {
109
109
  let me = this;
110
110
 
111
111
  if (me.showResultInLabel) {
112
- me.getLabelEl().html = `[${me.value}] ` + me.labelText
112
+ me.getLabelEl().text = `[${me.value}] ` + me.labelText
113
113
  }
114
114
  }
115
115
  }
@@ -788,8 +788,8 @@ class Text extends Field {
788
788
  showLabel = me.labelPosition === 'top',
789
789
  subLabel = me.vdom.cn[1];
790
790
 
791
- subLabel.html = value;
792
791
  subLabel.removeDom = !showLabel;
792
+ subLabel.text = value;
793
793
 
794
794
  me.update()
795
795
  }
@@ -1494,9 +1494,9 @@ class Text extends Field {
1494
1494
  errorNode = errorWrapper.cn[0];
1495
1495
 
1496
1496
  if (value) {
1497
- errorNode.html = value
1497
+ errorNode.text = value
1498
1498
  } else {
1499
- delete errorNode.html
1499
+ delete errorNode.text
1500
1500
  }
1501
1501
 
1502
1502
  errorWrapper.removeDom = !value;
@@ -1,5 +1,4 @@
1
- import StringUtil from '../../util/String.mjs';
2
- import Text from './Text.mjs';
1
+ import Text from './Text.mjs';
3
2
 
4
3
  /**
5
4
  *
@@ -162,7 +161,7 @@ class TextArea extends Text {
162
161
  inputEl = me.getInputEl();
163
162
 
164
163
  if (inputEl) {
165
- inputEl.html = StringUtil.escapeHtml(value)
164
+ inputEl.text = value
166
165
  }
167
166
 
168
167
  super.afterSetValue(value, oldValue);
package/src/grid/Body.mjs CHANGED
@@ -419,9 +419,8 @@ class GridBody extends Component {
419
419
  * @param {Number} data.rowIndex
420
420
  * @returns {Object}
421
421
  */
422
- applyRendererOutput(data) {
423
- let {cellId, column, columnIndex, record, rowIndex} = data,
424
- me = this,
422
+ applyRendererOutput({cellId, column, columnIndex, record, rowIndex}) {
423
+ let me = this,
425
424
  gridContainer = me.parent,
426
425
  {selectedCells, store} = me,
427
426
  cellCls = ['neo-grid-cell'],
@@ -461,7 +460,7 @@ class GridBody extends Component {
461
460
 
462
461
  switch (Neo.typeOf(rendererOutput)) {
463
462
  case 'Object': {
464
- if (rendererOutput.html) {
463
+ if (rendererOutput.html || rendererOutput.text) {
465
464
  rendererOutput.cls && cellCls.push(...rendererOutput.cls);
466
465
  } else {
467
466
  rendererOutput = [rendererOutput];
@@ -522,7 +521,11 @@ class GridBody extends Component {
522
521
  }
523
522
 
524
523
  if (Neo.typeOf(rendererOutput) === 'Object') {
525
- cellConfig.html = rendererOutput.html || ''
524
+ if (Object.hasOwn(rendererOutput, 'html')) {
525
+ cellConfig.html = rendererOutput.html || ''
526
+ } else {
527
+ cellConfig.text = rendererOutput.text || ''
528
+ }
526
529
  } else {
527
530
  cellConfig.cn = rendererOutput
528
531
  }
@@ -2,4 +2,4 @@ import * as header from './header/_export.mjs';
2
2
  import Body from './Body.mjs';
3
3
  import Container from './Container.mjs';
4
4
 
5
- export {Body, header, Container};
5
+ export {header, Body, Container};
@@ -60,8 +60,8 @@ class Color extends List {
60
60
  }
61
61
  }, {
62
62
  vtype: 'text',
63
- html : record[me.displayField],
64
- id : me.getListItemVtextId(id)
63
+ id : me.getListItemVtextId(id),
64
+ text : record[me.displayField]
65
65
  }]
66
66
  }
67
67
 
@@ -5,7 +5,13 @@ import {voidAttributes} from '../vdom/domConstants.mjs';
5
5
  const NeoConfig = Neo.config;
6
6
 
7
7
  /**
8
- * Logic to apply the deltas generated by vdom.Helper to the real DOM
8
+ * Manages and applies the Virtual DOM (VDom) delta updates generated by `Neo.vdom.Helper` to the real browser DOM.
9
+ * This class acts as the bridge between the VDom worker's calculated changes and the actual rendering on the main thread.
10
+ * It orchestrates various DOM manipulation operations such as node insertions, removals, moves, attribute updates,
11
+ * and handles dynamic renderer switching based on `Neo.config.useDomApiRenderer`.
12
+ *
13
+ * As a singleton per browser window, it provides a centralized and efficient mechanism for synchronized DOM updates,
14
+ * ensuring the UI accurately reflects the application state.
9
15
  * @class Neo.main.DeltaUpdates
10
16
  * @extends Neo.core.Base
11
17
  * @singleton
@@ -48,19 +54,6 @@ class DeltaUpdates extends Base {
48
54
  * @protected
49
55
  */
50
56
  logDeltasIntervalId = 0
51
- /**
52
- * Private property to store the dynamically loaded renderer module.
53
- * @member {Neo.main.render.DomApiRenderer|Neo.main.render.DomApiRenderer|null} #renderer=null
54
- * @private
55
- */
56
- #renderer = null
57
- /**
58
- * Private property to signal that the renderer module has been loaded.
59
- * This will be a Promise that resolves when the module is ready.
60
- * @private
61
- * @member {Promise<void>|null} #_readyPromise
62
- */
63
- #_readyPromise = null
64
57
 
65
58
  /**
66
59
  * @param {Object} config
@@ -68,11 +61,10 @@ class DeltaUpdates extends Base {
68
61
  construct(config) {
69
62
  super.construct(config);
70
63
 
71
- let me = this,
72
- {environment} = NeoConfig;
64
+ let {environment} = NeoConfig;
73
65
 
74
66
  if (NeoConfig.renderCountDeltas) {
75
- me.renderCountDeltas = true
67
+ this.renderCountDeltas = true
76
68
  }
77
69
 
78
70
  // We need different publicPath values for the main thread inside the webpack based dist envs,
@@ -80,24 +72,6 @@ class DeltaUpdates extends Base {
80
72
  if (environment === 'dist/development' || environment === 'dist/production') {
81
73
  __webpack_require__.p = NeoConfig.basePath.substring(6)
82
74
  }
83
-
84
- // Initiate the asynchronous loading of the renderer here.
85
- me.#_readyPromise = (async () => {
86
- try {
87
- let module;
88
-
89
- if (NeoConfig.useDomApiRenderer) {
90
- module = await import('./render/DomApiRenderer.mjs')
91
- } else {
92
- module = await import('./render/StringBasedRenderer.mjs')
93
- }
94
-
95
- me.#renderer = module.default
96
- } catch (err) {
97
- console.error('DeltaUpdates: Failed to load renderer module:', err);
98
- throw err // Re-throw to propagate initialization failures
99
- }
100
- })()
101
75
  }
102
76
 
103
77
  /**
@@ -130,8 +104,13 @@ class DeltaUpdates extends Base {
130
104
  }
131
105
 
132
106
  /**
133
- * @param {HTMLElement} node
134
- * @param {String} nodeName
107
+ * Changes the tag name (nodeName) of an existing HTMLElement in the DOM.
108
+ * This operation is performed by creating a new HTML element with the desired `nodeName`,
109
+ * meticulously copying all attributes and the `innerHTML` from the original `node` to the new one,
110
+ * and then seamlessly replacing the original `node` with the newly created element within its parent.
111
+ *
112
+ * @param {HTMLElement} node The existing DOM HTMLElement whose tag name needs to be changed.
113
+ * @param {String} nodeName The new tag name (e.g., 'div', 'span', 'p') for the element.
135
114
  */
136
115
  changeNodeName(node, nodeName) {
137
116
  let {attributes} = node,
@@ -160,21 +139,49 @@ class DeltaUpdates extends Base {
160
139
  DomAccess.getElement(id)?.focus()
161
140
  }
162
141
 
142
+ /**
143
+ * Imports either (if not already imported):
144
+ * `Neo.main.render.DomApiRenderer` if Neo.config.useDomApiRenderer === true
145
+ * `Neo.main.render.StringBasedRenderer` if Neo.config.useDomApiRenderer === false
146
+ * @returns {Promise<void>}
147
+ * @protected
148
+ */
149
+ async importRenderer() {
150
+ const {render} = Neo.main;
151
+
152
+ if (NeoConfig.useDomApiRenderer) {
153
+ if (!render?.DomApiRenderer) {
154
+ await import('./render/DomApiRenderer.mjs')
155
+ }
156
+ } else {
157
+ if (!render?.StringBasedRenderer) {
158
+ await import('./render/StringBasedRenderer.mjs')
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * @returns {Promise<void>}
165
+ */
166
+ async initAsync() {
167
+ super.initAsync();
168
+
169
+ let me = this;
170
+
171
+ // Subscribe to global Neo.config changes for dynamic renderer switching.
172
+ Neo.worker.Manager.on({
173
+ neoConfigChange: me.onNeoConfigChange,
174
+ scope : me
175
+ });
176
+
177
+ await me.importRenderer()
178
+ }
179
+
163
180
  /**
164
181
  * Inserts a new node into the DOM tree based on delta updates.
165
182
  * This method handles both string-based (outerHTML) and direct DOM API (vnode) mounting.
166
183
  * It ensures the node is inserted at the correct index within the parent.
167
- *
168
- * Implementation Details & Considerations:
169
- * - `parentNode.children` contains only element nodes (tags).
170
- * - `parentNode.childNodes` contains all nodes, including text and comment nodes.
171
- * - Since every `vtype:'text'` is wrapped inside a comment block (as an ID),
172
- * calculating a "realIndex" is necessary for string-based insertions to
173
- * correctly account for non-element nodes.
174
- * - `insertAdjacentHTML()` is generally faster than creating a node via template,
175
- * but it's only available for manipulating children (elements), not `childNodes` (all nodes).
176
- * - For performance, in cases where there are no comment nodes (i.e., no wrapped text nodes),
177
- * the method prioritizes `insertAdjacentHTML()` when `useDomApiRenderer` is false.
184
+ * This method is synchronous and *expects* the appropriate renderer (DomApiRenderer or StringBasedRenderer) to be already loaded.
178
185
  *
179
186
  * @param {Object} delta
180
187
  * @param {Boolean} delta.hasLeadingTextChildren Flag to honor leading comments, which require special treatment.
@@ -184,33 +191,47 @@ class DeltaUpdates extends Base {
184
191
  * @param {Neo.vdom.VNode} [delta.vnode] The VNode representation of the new node (for direct DOM API mounting).
185
192
  */
186
193
  insertNode({hasLeadingTextChildren, index, outerHTML, parentId, vnode}) {
187
- let me = this;
188
-
189
- // This method is synchronous and *expects* the renderer to be loaded
190
- if (!me.#renderer) {
191
- console.error('DeltaUpdates renderer not ready during insertNode!');
192
- return
193
- }
194
+ this.checkRendererAvailability();
194
195
 
195
- const parentNode = DomAccess.getElementOrBody(parentId);
196
+ let {render} = Neo.main,
197
+ parentNode = DomAccess.getElementOrBody(parentId);
196
198
 
197
199
  if (parentNode) {
198
200
  if (NeoConfig.useDomApiRenderer) {
199
- me.#renderer.createDomTree({index, isRoot: true, parentNode, vnode})
201
+ render.DomApiRenderer.createDomTree({index, isRoot: true, parentNode, vnode})
200
202
  } else {
201
- me.#renderer.insertNodeAsString({hasLeadingTextChildren, index, outerHTML, parentNode})
203
+ render.StringBasedRenderer.insertNodeAsString({hasLeadingTextChildren, index, outerHTML, parentNode})
204
+ }
205
+ }
206
+ }
207
+
208
+ /**
209
+ *
210
+ */
211
+ checkRendererAvailability() {
212
+ const {render} = Neo.main;
213
+
214
+ if (NeoConfig.useDomApiRenderer) {
215
+ if (!render?.DomApiRenderer) {
216
+ throw new Error('Neo.main.DeltaUpdates: DomApiRenderer is not loaded yet!')
217
+ }
218
+ } else {
219
+ if (!render?.StringBasedRenderer) {
220
+ throw new Error('Neo.main.DeltaUpdates: StringBasedRenderer is not loaded yet!')
202
221
  }
203
222
  }
204
223
  }
205
224
 
206
225
  /**
207
- * Moves an existing DOM node to a new position within its parent
208
- * or to a new parent.
209
- * This method directly manipulates the DOM using the pre-calculated physical index.
226
+ * Moves an existing DOM node to a new position within its parent or to a new parent.
227
+ * This method directly manipulates the DOM using the pre-calculated physical index,
228
+ * accounting for potential text nodes wrapped in comments.
229
+ * It performs a direct sibling swap when an element is immediately followed by its target position,
230
+ * which is necessary to prevent attempting to replace a node with itself.
210
231
  *
211
232
  * @param {Object} delta
212
233
  * @param {String} delta.id The ID of the DOM node to move.
213
- * @param {Number} delta.index The physical index at which to insert the node
234
+ * @param {Number} delta.index The physical index at which to insert the node within the target parent's childNodes.
214
235
  * @param {String} delta.parentId The ID of the target parent DOM node.
215
236
  */
216
237
  moveNode({id, index, parentId}) {
@@ -239,8 +260,25 @@ class DeltaUpdates extends Base {
239
260
  }
240
261
 
241
262
  /**
263
+ * Handler for global Neo.config changes.
264
+ * If the `Neo.config.useDomApiRenderer` value changes, this method dynamically loads the renderer.
265
+ * @param {Object} config
266
+ * @return {Promise<void>}
267
+ */
268
+ async onNeoConfigChange(config) {
269
+ if (Object.hasOwn(config, 'useDomApiRenderer')) {
270
+ await this.importRenderer()
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Clears all child nodes of a given parent DOM node.
276
+ * This is achieved by setting its `innerHTML` property to an empty string,
277
+ * which is generally considered the fastest and most efficient way to remove
278
+ * all children from a DOM element in modern browsers.
279
+ *
242
280
  * @param {Object} delta
243
- * @param {String} delta.parentId
281
+ * @param {String} delta.parentId The ID of the parent DOM node whose children will be removed.
244
282
  */
245
283
  removeAll({parentId}) {
246
284
  let node = DomAccess.getElement(parentId);
@@ -251,9 +289,13 @@ class DeltaUpdates extends Base {
251
289
  }
252
290
 
253
291
  /**
292
+ * Removes a DOM node from its parent.
293
+ * This method handles both standard HTML elements and virtual text nodes,
294
+ * which are typically wrapped within comment nodes in the DOM.
295
+ *
254
296
  * @param {Object} delta
255
- * @param {String} delta.id
256
- * @param {String} delta.parentId
297
+ * @param {String} delta.id The ID of the DOM node to remove.
298
+ * @param {String} delta.parentId The ID of the parent DOM node (required for text node removal).
257
299
  */
258
300
  removeNode({id, parentId}) {
259
301
  const node = DomAccess.getElement(id);
@@ -289,10 +331,17 @@ class DeltaUpdates extends Base {
289
331
  }
290
332
 
291
333
  /**
334
+ * Replaces an existing child DOM node (`fromId`) with a new DOM node (`toId`)
335
+ * within a specified parent DOM node (`parentId`).
336
+ * This operation directly invokes the native `Node.replaceChild()` API,
337
+ * performing an atomic swap of the elements in the DOM tree.
338
+ * It is typically used when a specific DOM element needs to be completely
339
+ * exchanged for a different one at the same position.
340
+ *
292
341
  * @param {Object} delta
293
- * @param {String} delta.fromId
294
- * @param {String} delta.parentId
295
- * @param {String} delta.toId
342
+ * @param {String} delta.fromId The ID of the existing child DOM node to be replaced.
343
+ * @param {String} delta.parentId The ID of the parent DOM node containing the child to be replaced.
344
+ * @param {String} delta.toId The ID of the new DOM node that will replace the old one.
296
345
  */
297
346
  replaceChild({fromId, parentId, toId}) {
298
347
  let node = DomAccess.getElement(parentId);
@@ -301,26 +350,20 @@ class DeltaUpdates extends Base {
301
350
  }
302
351
 
303
352
  /**
353
+ * Updates various properties of an existing DOM node based on the provided delta.
354
+ * This includes updating attributes, class names, inner HTML, node name, and inline styles.
355
+ * It handles specific cases for attribute types (e.g., boolean attributes, 'value')
356
+ * and style properties (e.g., '!important').
357
+ *
304
358
  * @param {Object} delta
305
- * @param {String} [delta.id]
306
- * @param {String} [delta.value
307
- */
308
- setTextContent({id, value}) {
309
- let node = DomAccess.getElement(id);
310
-
311
- if (node) {
312
- node.textContent = value
313
- }
314
- }
315
-
316
- /**
317
- * @param {Object} delta
318
- * @param {Object} [delta.attributes]
319
- * @param {String} [delta.cls]
320
- * @param {String} [delta.id]
321
- * @param {String} [delta.innerHTML]
322
- * @param {String} [delta.outerHTML]
323
- * @param {Object} [delta.style]
359
+ * @param {String} delta.id The ID of the DOM node to update.
360
+ * @param {Object} [delta.attributes] An object containing attribute key-value pairs to update or remove (if value is null/empty).
361
+ * @param {Object} [delta.cls] An object containing 'add' and/or 'remove' arrays for CSS classes.
362
+ * @param {String} [delta.innerHTML] The new inner HTML content for the node.
363
+ * @param {String} [delta.nodeName] The new tag name for the node (will trigger a node replacement).
364
+ * @param {String} [delta.outerHTML] The new outer HTML content for the node (will trigger a node replacement).
365
+ * @param {Object} [delta.style] An object containing CSS style properties to update. Values can include '!important'.
366
+ * @param {String} [delta.textContent] The new text content for the node (replaces innerHTML if present).
324
367
  */
325
368
  updateNode(delta) {
326
369
  let me = this,
@@ -384,16 +427,25 @@ class DeltaUpdates extends Base {
384
427
  })
385
428
  }
386
429
  break
430
+ case 'textContent':
431
+ node.textContent = value;
432
+ break
387
433
  }
388
434
  })
389
435
  }
390
436
  }
391
437
 
392
438
  /**
439
+ * Updates the text content of a virtual text node within the DOM.
440
+ * Virtual text nodes are rendered within the DOM as a pair of HTML comments,
441
+ * with their content embedded between them. This method locates the specific
442
+ * text node by its ID (embedded in the start comment tag) within its parent's
443
+ * innerHTML and replaces its content using a regular expression.
444
+ *
393
445
  * @param {Object} delta
394
- * @param {String} delta.id
395
- * @param {String} delta.parentId
396
- * @param {String} delta.value
446
+ * @param {String} delta.id The unique ID of the virtual text node, which is embedded in its opening comment tag.
447
+ * @param {String} delta.parentId The ID of the parent DOM node whose `innerHTML` contains the virtual text node.
448
+ * @param {String} delta.value The new text content to be applied to the virtual text node.
397
449
  */
398
450
  updateVtext({id, parentId, value}) {
399
451
  let node = DomAccess.getElement(parentId),
@@ -405,17 +457,24 @@ class DeltaUpdates extends Base {
405
457
  }
406
458
 
407
459
  /**
460
+ * Applies a set of VDom delta updates to the real DOM.
461
+ * This method is the core entry point for rendering changes initiated from the VDom worker.
462
+ * It iterates through the provided deltas and dispatches them to specific DOM manipulation
463
+ * methods (e.g., insertNode, removeNode, updateNode) based on their `action` property.
464
+ * This method expects the appropriate renderer (DomApiRenderer or StringBasedRenderer)
465
+ * to be loaded based on `Neo.config.useDomApiRenderer`.
466
+ *
408
467
  * @param {Object} data
409
- * @param {Object|Object[]} data.deltas
410
- * @param {String} data.id
411
- * @param {String} [data.origin='app']
468
+ * @param {Object|Object[]} data.deltas An array of delta objects, or a single delta object,
469
+ * representing changes to be applied to the DOM.
470
+ * Each delta object contains an `action` property
471
+ * (e.g., 'insertNode', 'removeNode', 'updateNode', 'moveNode')
472
+ * and additional properties relevant to the specific action.
473
+ * @param {String} data.id The unique ID of the request, used for sending a reply back to the origin.
474
+ * @param {String} [data.origin='app'] The origin of the message (e.g., 'app'), used for sending replies.
412
475
  */
413
476
  update(data) {
414
- // This method is synchronous and *expects* the renderer to be loaded
415
- if (!this.#renderer) {
416
- console.error('DeltaUpdates renderer not ready during insertNode!');
417
- return
418
- }
477
+ this.checkRendererAvailability();
419
478
 
420
479
  let me = this,
421
480
  {deltas} = data,