neo.mjs 9.15.0 → 10.0.0-alpha.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.
Files changed (98) hide show
  1. package/ServiceWorker.mjs +2 -2
  2. package/apps/email/view/Viewport.mjs +2 -2
  3. package/apps/form/view/Viewport.mjs +1 -1
  4. package/apps/portal/index.html +1 -1
  5. package/apps/portal/view/examples/List.mjs +1 -1
  6. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  7. package/apps/portal/view/learn/ContentComponent.mjs +5 -5
  8. package/apps/realworld2/view/HomeContainer.mjs +1 -1
  9. package/apps/route/view/center/CardAdministration.mjs +3 -3
  10. package/apps/route/view/center/CardAdministrationDenied.mjs +2 -2
  11. package/apps/route/view/center/CardContact.mjs +2 -2
  12. package/apps/route/view/center/CardHome.mjs +2 -2
  13. package/apps/route/view/center/CardSection1.mjs +2 -2
  14. package/apps/route/view/center/CardSection2.mjs +2 -2
  15. package/buildScripts/createApp.mjs +2 -2
  16. package/docs/app/view/classdetails/HeaderComponent.mjs +3 -3
  17. package/docs/app/view/classdetails/MembersList.mjs +43 -46
  18. package/docs/app/view/classdetails/SourceViewComponent.mjs +1 -1
  19. package/docs/app/view/classdetails/TutorialComponent.mjs +1 -1
  20. package/examples/component/toast/MainContainer.mjs +16 -16
  21. package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +4 -4
  22. package/examples/fields/MainContainer.mjs +1 -1
  23. package/examples/panel/MainContainer.mjs +2 -2
  24. package/examples/tab/container/MainContainer.mjs +3 -3
  25. package/examples/tabs/MainContainer.mjs +2 -2
  26. package/examples/tabs/MainContainer2.mjs +3 -3
  27. package/examples/viewport/MainContainer.mjs +2 -2
  28. package/package.json +3 -3
  29. package/resources/data/deck/learnneo/pages/UsingTheseTopics.md +65 -0
  30. package/resources/data/deck/learnneo/pages/benefits/ConfigSystem.md +1 -1
  31. package/resources/data/deck/learnneo/pages/benefits/FormsEngine.md +7 -7
  32. package/resources/data/deck/learnneo/pages/benefits/FourEnvironments.md +10 -11
  33. package/resources/data/deck/learnneo/pages/benefits/Introduction.md +38 -5
  34. package/resources/data/deck/learnneo/pages/benefits/MultiWindow.md +1 -1
  35. package/resources/data/deck/learnneo/pages/benefits/Speed.md +1 -1
  36. package/resources/data/deck/learnneo/pages/gettingstarted/ComponentModels.md +2 -2
  37. package/resources/data/deck/learnneo/pages/gettingstarted/Config.md +3 -3
  38. package/resources/data/deck/learnneo/pages/gettingstarted/DescribingTheUI.md +2 -2
  39. package/resources/data/deck/learnneo/pages/gettingstarted/Events.md +3 -3
  40. package/resources/data/deck/learnneo/pages/gettingstarted/Extending.md +2 -2
  41. package/resources/data/deck/learnneo/pages/gettingstarted/References.md +3 -3
  42. package/resources/data/deck/learnneo/pages/gettingstarted/Workspaces.md +3 -3
  43. package/resources/data/deck/learnneo/pages/guides/ComponentsAndContainers.md +6 -6
  44. package/resources/data/deck/learnneo/pages/guides/CustomComponents.md +1 -1
  45. package/resources/data/deck/learnneo/pages/guides/MainThreadAddonIntro.md +1 -1
  46. package/resources/data/deck/learnneo/pages/guides/StateProviders.md +6 -6
  47. package/resources/data/deck/learnneo/pages/guides/events/CustomEvents.md +8 -8
  48. package/resources/data/deck/learnneo/pages/guides/events/DomEvents.md +11 -11
  49. package/resources/data/deck/learnneo/pages/javascript/Classes.md +4 -4
  50. package/resources/data/deck/learnneo/pages/javascript/NewNode.md +2 -2
  51. package/resources/data/deck/learnneo/pages/javascript/Overrides.md +4 -4
  52. package/resources/data/deck/learnneo/pages/tutorials/Earthquakes.md +21 -21
  53. package/resources/data/deck/learnneo/pages/tutorials/TodoList.md +2 -2
  54. package/resources/data/deck/learnneo/tree.json +1 -1
  55. package/resources/data/deck/training/pages/2022-12-27T21-55-23-144Z.md +2 -2
  56. package/resources/data/deck/training/pages/2022-12-29T18-36-08-226Z.md +1 -1
  57. package/resources/data/deck/training/pages/2022-12-29T18-36-56-893Z.md +2 -2
  58. package/resources/data/deck/training/pages/2022-12-29T20-37-08-919Z.md +2 -2
  59. package/resources/data/deck/training/pages/2022-12-29T20-37-20-344Z.md +2 -2
  60. package/resources/data/deck/training/pages/2023-01-13T21-48-17-258Z.md +2 -2
  61. package/resources/data/deck/training/pages/2023-02-05T17-44-53-815Z.md +9 -9
  62. package/resources/data/deck/training/pages/2023-10-14T19-25-08-153Z.md +1 -1
  63. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +17 -13
  64. package/src/DefaultConfig.mjs +14 -2
  65. package/src/Main.mjs +14 -5
  66. package/src/button/Base.mjs +1 -1
  67. package/src/calendar/view/calendars/List.mjs +1 -1
  68. package/src/component/Base.mjs +11 -11
  69. package/src/component/Chip.mjs +1 -1
  70. package/src/component/Helix.mjs +3 -3
  71. package/src/component/Process.mjs +2 -2
  72. package/src/component/StatusBadge.mjs +2 -2
  73. package/src/component/Timer.mjs +1 -1
  74. package/src/component/Toast.mjs +2 -2
  75. package/src/container/Base.mjs +1 -1
  76. package/src/form/field/CheckBox.mjs +2 -2
  77. package/src/form/field/FileUpload.mjs +14 -14
  78. package/src/form/field/Range.mjs +1 -1
  79. package/src/form/field/Text.mjs +1 -1
  80. package/src/form/field/trigger/Base.mjs +2 -2
  81. package/src/form/field/trigger/SpinUpDown.mjs +2 -2
  82. package/src/grid/View.mjs +1 -1
  83. package/src/main/DeltaUpdates.mjs +382 -0
  84. package/src/main/DomAccess.mjs +13 -36
  85. package/src/main/render/DomApiRenderer.mjs +138 -0
  86. package/src/main/render/StringBasedRenderer.mjs +58 -0
  87. package/src/table/View.mjs +1 -1
  88. package/src/table/plugin/CellEditing.mjs +1 -1
  89. package/src/tree/Accordion.mjs +11 -11
  90. package/src/tree/List.mjs +12 -5
  91. package/src/vdom/Helper.mjs +174 -292
  92. package/src/vdom/VNode.mjs +47 -11
  93. package/src/vdom/domConstants.mjs +65 -0
  94. package/src/vdom/util/DomApiVnodeCreator.mjs +51 -0
  95. package/src/vdom/util/StringFromVnode.mjs +123 -0
  96. package/src/worker/mixin/RemoteMethodAccess.mjs +13 -1
  97. package/resources/data/deck/learnneo/pages/Welcome.md +0 -64
  98. package/src/main/mixin/DeltaUpdates.mjs +0 -352
@@ -1,5 +1,12 @@
1
+ import StringUtil from '../util/String.mjs';
2
+
1
3
  /**
2
- * Wrapper class for vnode objects. See the tutorials for further infos.
4
+ * Wrapper class for vnode objects.
5
+ * For convenience, a VNode instance will always contain a childNodes array, which can be empty.
6
+ * A VNode can optionally have `innerHTML` xor `textContent`
7
+ * `textContent` is better from a XSS security perspective.
8
+ * If by accident both are set, `innerHTML` will get the priority.
9
+ *
3
10
  * @class Neo.vdom.VNode
4
11
  */
5
12
  class VNode {
@@ -8,7 +15,8 @@ class VNode {
8
15
  */
9
16
  constructor(config) {
10
17
  /**
11
- * @member {Array} attributes=[]
18
+ * Not set for vtype='text' nodes
19
+ * @member {Object} attributes={}
12
20
  */
13
21
 
14
22
  /**
@@ -16,6 +24,7 @@ class VNode {
16
24
  */
17
25
 
18
26
  /**
27
+ * Not set for vtype='text' nodes
19
28
  * @member {Array} className=[]
20
29
  */
21
30
 
@@ -37,33 +46,60 @@ class VNode {
37
46
  */
38
47
 
39
48
  /**
49
+ * Not set for vtype='text' nodes
40
50
  * @member {Object} style
41
51
  */
42
52
 
53
+ /**
54
+ * @member {String} textContent
55
+ */
56
+
43
57
  /**
44
58
  * Valid values are "root", "text" & "vnode"
45
59
  * @member {String} vtype='vnode'
46
60
  */
47
61
 
48
- Object.assign(this, {
49
- attributes: config.attributes || [],
62
+ let me = this,
63
+ {textContent} = config,
64
+ hasInnerHtml = Object.hasOwn(config, 'innerHTML'),
65
+ isVText = config.vtype === 'text';
66
+
67
+ Object.assign(me, {
50
68
  childNodes: config.childNodes || [],
51
- className : config.className || [],
52
- id : config.id || Neo.getId('vnode'),
53
- innerHTML : config.innerHTML,
54
- nodeName : config.nodeName,
55
- style : config.style,
69
+ id : config.id || Neo.getId(isVText ? 'vtext' : 'vnode'),
56
70
  vtype : config.vtype || 'vnode'
57
71
  });
58
72
 
73
+ if (isVText) {
74
+ // XSS Security: a pure text node is not supposed to contain HTML
75
+ me.textContent = StringUtil.escapeHtml(hasInnerHtml ? config.innerHTML : textContent)
76
+ } else {
77
+ Object.assign(me, {
78
+ attributes: config.attributes || {},
79
+ className : config.className || [],
80
+ nodeName : config.nodeName || 'div',
81
+ style : config.style
82
+ });
83
+
84
+ // Use vdom.html on your own risk, it is not fully XSS secure.
85
+ if (hasInnerHtml) {
86
+ me.innerHTML = config.innerHTML
87
+ }
88
+
89
+ // We only apply textContent, in case it has content
90
+ else if (Object.hasOwn(config, 'textContent')) {
91
+ me.textContent = Neo.config.useStringBasedMounting ? StringUtil.escapeHtml(textContent) : textContent
92
+ }
93
+ }
94
+
59
95
  // We only apply the static attribute, in case the value is true
60
96
  if (config.static) {
61
- this.static = true
97
+ me.static = true
62
98
  }
63
99
  }
64
100
  }
65
101
 
66
102
  const ns = Neo.ns('Neo.vdom', true);
67
- ns['VNode'] = VNode;
103
+ ns.VNode = VNode;
68
104
 
69
105
  export default VNode;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The following top-level attributes will get converted into styles:
3
+ * height, maxHeight, maxWidth, minHeight, minWidth, width
4
+ *
5
+ * Some tags must not do the transformation, so we add them here.
6
+ * @member {Set} rawDimensionTags
7
+ */
8
+ export const rawDimensionTags = new Set([
9
+ 'circle',
10
+ 'clipPath',
11
+ 'ellipse',
12
+ 'filter',
13
+ 'foreignObject',
14
+ 'image',
15
+ 'marker',
16
+ 'mask',
17
+ 'pattern',
18
+ 'rect',
19
+ 'svg',
20
+ 'use'
21
+ ]);
22
+
23
+ /**
24
+ * Void attributes inside html tags
25
+ * @member {Set} voidAttributes
26
+ * @protected
27
+ */
28
+ export const voidAttributes = new Set([
29
+ 'checked',
30
+ 'defer',
31
+ 'disabled',
32
+ 'ismap',
33
+ 'multiple',
34
+ 'nohref',
35
+ 'noresize',
36
+ 'noshade',
37
+ 'nowrap',
38
+ 'open',
39
+ 'readonly',
40
+ 'required',
41
+ 'reversed',
42
+ 'selected'
43
+ ]);
44
+
45
+ /**
46
+ * Void html tags
47
+ * @member {Set} voidElements
48
+ * @protected
49
+ */
50
+ export const voidElements = new Set([
51
+ 'area',
52
+ 'base',
53
+ 'br',
54
+ 'col',
55
+ 'embed',
56
+ 'hr',
57
+ 'img',
58
+ 'input',
59
+ 'link',
60
+ 'meta',
61
+ 'param',
62
+ 'source',
63
+ 'track',
64
+ 'wbr'
65
+ ]);
@@ -0,0 +1,51 @@
1
+ const DomApiVnodeCreator = {
2
+ /**
3
+ * Recursively creates a VNode tree suitable for direct DOM API insertion.
4
+ * This tree excludes any nodes that are marked as 'moved' within the movedNodes map,
5
+ * as their DOM manipulation will be handled by separate moveNode deltas.
6
+ *
7
+ * @param {Neo.vdom.VNode} vnode The VNode to process.
8
+ * @param {Map} [movedNodes] A map of VNodes that are being moved.
9
+ * @returns {Object|null} A new VNode tree (or subtree) with moved nodes pruned, or null if the root is a moved node.
10
+ */
11
+ create(vnode, movedNodes) {
12
+ /*
13
+ * A vnode itself can be null (removeDom: true) => opt out.
14
+ *
15
+ * If the node has a componentId, there is nothing to do (scoped vdom updates), opt out.
16
+ *
17
+ * If this specific vnode is in the movedNodes map, it means its DOM element
18
+ * will be moved by a separate delta. So, we should not include it in this fragment.
19
+ */
20
+ if (!vnode || vnode.componentId || (vnode.id && movedNodes?.get(vnode.id))) {
21
+ return null // Prune this branch
22
+ }
23
+
24
+ // For text nodes, we can return the original VNode directly, as they have no childNodes array to modify.
25
+ if (vnode.vtype === 'text') {
26
+ return vnode
27
+ }
28
+
29
+ // For other VNodes (vnode or root), create a shallow clone first.
30
+ let clonedVnode = {...vnode, childNodes: []};
31
+
32
+ // Recursively process children
33
+ if (vnode.childNodes.length > 0) {
34
+ vnode.childNodes.forEach(child => {
35
+ const processedChild = DomApiVnodeCreator.create(child, movedNodes);
36
+
37
+ // Only add if not pruned
38
+ if (processedChild) {
39
+ clonedVnode.childNodes.push(processedChild)
40
+ }
41
+ });
42
+ }
43
+
44
+ return clonedVnode
45
+ }
46
+ };
47
+
48
+ const ns = Neo.ns('Neo.vdom.util', true);
49
+ ns.DomApiVnodeCreator = DomApiVnodeCreator;
50
+
51
+ export default DomApiVnodeCreator;
@@ -0,0 +1,123 @@
1
+ import NeoString from '../../util/String.mjs';
2
+ import {voidAttributes, voidElements} from '../domConstants.mjs';
3
+
4
+ const StringFromVnode = {
5
+ /**
6
+ * @param {Object} vnode
7
+ * @protected
8
+ */
9
+ createCloseTag(vnode) {
10
+ return voidElements.has(vnode.nodeName) ? '' : '</' + vnode.nodeName + '>'
11
+ },
12
+
13
+ /**
14
+ * @param {Object} vnode
15
+ * @protected
16
+ */
17
+ createOpenTag(vnode) {
18
+ let string = '<' + vnode.nodeName,
19
+ {attributes} = vnode,
20
+ cls = vnode.className,
21
+ style;
22
+
23
+ if (vnode.style) {
24
+ style = Neo.createStyles(vnode.style);
25
+
26
+ if (style !== '') {
27
+ string += ` style="${style}"`
28
+ }
29
+ }
30
+
31
+ if (cls) {
32
+ if (Array.isArray(cls)) {
33
+ cls = cls.join(' ')
34
+ }
35
+
36
+ if (cls !== '') {
37
+ string += ` class="${cls}"`
38
+ }
39
+ }
40
+
41
+ if (vnode.id) {
42
+ if (Neo.config.useDomIds) {
43
+ string += ` id="${vnode.id}"`
44
+ } else {
45
+ string += ` data-neo-id="${vnode.id}"`
46
+ }
47
+ }
48
+
49
+ Object.entries(attributes).forEach(([key, value]) => {
50
+ if (voidAttributes.has(key)) {
51
+ if (value === 'true') { // vnode attribute values get converted into strings
52
+ string += ` ${key}`
53
+ }
54
+ } else if (key !== 'removeDom') {
55
+ if (key === 'value') {
56
+ value = NeoString.escapeHtml(value)
57
+ }
58
+
59
+ string += ` ${key}="${value?.replaceAll?.('"', '&quot;') ?? value}"`
60
+ }
61
+ });
62
+
63
+ return string + '>'
64
+ },
65
+
66
+ /**
67
+ * @param {Neo.vdom.VNode} vnode
68
+ * @param {Map} [movedNodes]
69
+ */
70
+ create(vnode, movedNodes) {
71
+ let me = this,
72
+ id = vnode?.id;
73
+
74
+ // If a content node will get moved by a delta update OP, there is no need to regenerate it. Opt out.
75
+ if (id && movedNodes?.get(id)) {
76
+ return ''
77
+ }
78
+
79
+ switch (vnode.vtype) {
80
+ case 'root':
81
+ return me.create(vnode.childNodes[0], movedNodes)
82
+ case 'text':
83
+ // For text VNodes, `vnode.textContent` holds the HTML-escaped content.
84
+ // Add the comment wrappers here for string output, aligning with main.mixin.DeltaUpdates.createDomTree().
85
+ // `vnode.textContent || ''` ensures robustness in case vnode.textContent is not a string (e.g., a number or null).
86
+ return `<!-- ${vnode.id} -->${vnode.textContent}<!-- /neo-vtext -->`
87
+ case 'vnode':
88
+ return me.createOpenTag(vnode) + me.createTagContent(vnode, movedNodes) + me.createCloseTag(vnode)
89
+ default:
90
+ return ''
91
+ }
92
+ },
93
+
94
+ /**
95
+ * @param {Neo.vdom.VNode} vnode
96
+ * @param {Map} [movedNodes]
97
+ * @protected
98
+ */
99
+ createTagContent(vnode, movedNodes) {
100
+ const hasContent = vnode.innerHTML || vnode.textContent;
101
+
102
+ if (hasContent) {
103
+ return hasContent
104
+ }
105
+
106
+ let string = '',
107
+ len = vnode.childNodes ? vnode.childNodes.length : 0,
108
+ i = 0,
109
+ childNode;
110
+
111
+ for (; i < len; i++) {
112
+ childNode = vnode.childNodes[i];
113
+ string += this.create(childNode, movedNodes)
114
+ }
115
+
116
+ return string
117
+ }
118
+ };
119
+
120
+ const ns = Neo.ns('Neo.vdom.util', true);
121
+ ns.StringFromVnode = StringFromVnode;
122
+
123
+ export default StringFromVnode;
@@ -98,7 +98,19 @@ class RemoteMethodAccess extends Base {
98
98
 
99
99
  if (out instanceof Promise) {
100
100
  out
101
- .catch(err => {me.reject(msg, err)})
101
+ /*
102
+ * Intended logic:
103
+ * If the code of a remote method fails, it would not show any errors inside the console,
104
+ * so we want to manually log the error for debugging.
105
+ * Rejecting the Promise gives us the chance to recover.
106
+ *
107
+ * Example:
108
+ * Neo.vdom.Helper.update(opts).catch(err => {
109
+ * me.isVdomUpdating = false;
110
+ * reject?.()
111
+ * }).then(data => {...})
112
+ */
113
+ .catch(err => {console.error(err); me.reject(msg, err)})
102
114
  .then(data => {me.resolve(msg, data)})
103
115
  } else {
104
116
  me.resolve(msg, out)
@@ -1,64 +0,0 @@
1
- Welcome to Neo.mjs! This set of topics contains information to help you use Neo.mjs.
2
-
3
-
4
- ## Topics
5
-
6
- ### Benefits
7
-
8
- Describes technical and business reasons for using Neo.mjs
9
-
10
- ### Getting Started
11
-
12
- Install instructions, along with fundamental concepts that are good to understand before diving into Neo.mjs.
13
-
14
- ### Tutorials
15
-
16
- Hands-on tutorials where you'll code a few simple Neo.mjs applications.
17
-
18
- ### Guides
19
-
20
- These are in-depth discussions of various topics.
21
-
22
- ## Using these topics
23
-
24
- ### Layout
25
-
26
- As you can see, the topics table of contents is on the left. Topic sections and sub-sections are shown on the right.
27
- And content is here in the middle. There are "next" and "previous" buttons at the bottom of each page, to make it
28
- easier to read several topics in sequence.
29
-
30
- ### Disclosure widgets
31
-
32
- Topics sometimes contain "disclosure" widgets, which are just &lt;details> tags. These are used in cases
33
- where we want to present high-level points and reveal details when the disclosure is expanded.
34
-
35
- <details>
36
- <summary>This is a disclosure widget</summary>
37
- <p style="background-color:lightgreen;padding:8px">This is a fascinating piece of information which is revealed when the widget is expanded.</p>
38
- </details>
39
-
40
- ### Runnable examples
41
-
42
- Topics also sometimes contain runnable examples. These are shown as tab panels with Source and Preview tabs.
43
-
44
- You can also launch the preview in a window by going to the Preview tab, then clicking on the little window
45
- icon on the right <span class="far fa-xs fa-window-maximize"></span>. This web site is a Neo.mjs application,
46
- and the ability to launch browser windows &mdash; all integrated within a single app &mdash; is a unique feature of Neo.mjs!
47
-
48
- <pre data-neo>
49
- import Button from '../button/Base.mjs';
50
- import Container from '../container/Base.mjs';
51
-
52
- class MainView extends Container {
53
- static config = {
54
- className: 'Example.view.MainView',
55
- layout : {ntype:'vbox', align:'start'},
56
- items : [{
57
- module : Button,
58
- text : 'Button'
59
- }]
60
- }
61
- }
62
-
63
- MainView = Neo.setupClass(MainView);
64
- </pre>