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.
- package/ServiceWorker.mjs +2 -2
- package/apps/email/view/Viewport.mjs +2 -2
- package/apps/form/view/Viewport.mjs +1 -1
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/examples/List.mjs +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/portal/view/learn/ContentComponent.mjs +5 -5
- package/apps/realworld2/view/HomeContainer.mjs +1 -1
- package/apps/route/view/center/CardAdministration.mjs +3 -3
- package/apps/route/view/center/CardAdministrationDenied.mjs +2 -2
- package/apps/route/view/center/CardContact.mjs +2 -2
- package/apps/route/view/center/CardHome.mjs +2 -2
- package/apps/route/view/center/CardSection1.mjs +2 -2
- package/apps/route/view/center/CardSection2.mjs +2 -2
- package/buildScripts/createApp.mjs +2 -2
- package/docs/app/view/classdetails/HeaderComponent.mjs +3 -3
- package/docs/app/view/classdetails/MembersList.mjs +43 -46
- package/docs/app/view/classdetails/SourceViewComponent.mjs +1 -1
- package/docs/app/view/classdetails/TutorialComponent.mjs +1 -1
- package/examples/component/toast/MainContainer.mjs +16 -16
- package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +4 -4
- package/examples/fields/MainContainer.mjs +1 -1
- package/examples/panel/MainContainer.mjs +2 -2
- package/examples/tab/container/MainContainer.mjs +3 -3
- package/examples/tabs/MainContainer.mjs +2 -2
- package/examples/tabs/MainContainer2.mjs +3 -3
- package/examples/viewport/MainContainer.mjs +2 -2
- package/package.json +3 -3
- package/resources/data/deck/learnneo/pages/UsingTheseTopics.md +65 -0
- package/resources/data/deck/learnneo/pages/benefits/ConfigSystem.md +1 -1
- package/resources/data/deck/learnneo/pages/benefits/FormsEngine.md +7 -7
- package/resources/data/deck/learnneo/pages/benefits/FourEnvironments.md +10 -11
- package/resources/data/deck/learnneo/pages/benefits/Introduction.md +38 -5
- package/resources/data/deck/learnneo/pages/benefits/MultiWindow.md +1 -1
- package/resources/data/deck/learnneo/pages/benefits/Speed.md +1 -1
- package/resources/data/deck/learnneo/pages/gettingstarted/ComponentModels.md +2 -2
- package/resources/data/deck/learnneo/pages/gettingstarted/Config.md +3 -3
- package/resources/data/deck/learnneo/pages/gettingstarted/DescribingTheUI.md +2 -2
- package/resources/data/deck/learnneo/pages/gettingstarted/Events.md +3 -3
- package/resources/data/deck/learnneo/pages/gettingstarted/Extending.md +2 -2
- package/resources/data/deck/learnneo/pages/gettingstarted/References.md +3 -3
- package/resources/data/deck/learnneo/pages/gettingstarted/Workspaces.md +3 -3
- package/resources/data/deck/learnneo/pages/guides/ComponentsAndContainers.md +6 -6
- package/resources/data/deck/learnneo/pages/guides/CustomComponents.md +1 -1
- package/resources/data/deck/learnneo/pages/guides/MainThreadAddonIntro.md +1 -1
- package/resources/data/deck/learnneo/pages/guides/StateProviders.md +6 -6
- package/resources/data/deck/learnneo/pages/guides/events/CustomEvents.md +8 -8
- package/resources/data/deck/learnneo/pages/guides/events/DomEvents.md +11 -11
- package/resources/data/deck/learnneo/pages/javascript/Classes.md +4 -4
- package/resources/data/deck/learnneo/pages/javascript/NewNode.md +2 -2
- package/resources/data/deck/learnneo/pages/javascript/Overrides.md +4 -4
- package/resources/data/deck/learnneo/pages/tutorials/Earthquakes.md +21 -21
- package/resources/data/deck/learnneo/pages/tutorials/TodoList.md +2 -2
- package/resources/data/deck/learnneo/tree.json +1 -1
- package/resources/data/deck/training/pages/2022-12-27T21-55-23-144Z.md +2 -2
- package/resources/data/deck/training/pages/2022-12-29T18-36-08-226Z.md +1 -1
- package/resources/data/deck/training/pages/2022-12-29T18-36-56-893Z.md +2 -2
- package/resources/data/deck/training/pages/2022-12-29T20-37-08-919Z.md +2 -2
- package/resources/data/deck/training/pages/2022-12-29T20-37-20-344Z.md +2 -2
- package/resources/data/deck/training/pages/2023-01-13T21-48-17-258Z.md +2 -2
- package/resources/data/deck/training/pages/2023-02-05T17-44-53-815Z.md +9 -9
- package/resources/data/deck/training/pages/2023-10-14T19-25-08-153Z.md +1 -1
- package/resources/scss/src/apps/portal/learn/ContentComponent.scss +17 -13
- package/src/DefaultConfig.mjs +14 -2
- package/src/Main.mjs +14 -5
- package/src/button/Base.mjs +1 -1
- package/src/calendar/view/calendars/List.mjs +1 -1
- package/src/component/Base.mjs +11 -11
- package/src/component/Chip.mjs +1 -1
- package/src/component/Helix.mjs +3 -3
- package/src/component/Process.mjs +2 -2
- package/src/component/StatusBadge.mjs +2 -2
- package/src/component/Timer.mjs +1 -1
- package/src/component/Toast.mjs +2 -2
- package/src/container/Base.mjs +1 -1
- package/src/form/field/CheckBox.mjs +2 -2
- package/src/form/field/FileUpload.mjs +14 -14
- package/src/form/field/Range.mjs +1 -1
- package/src/form/field/Text.mjs +1 -1
- package/src/form/field/trigger/Base.mjs +2 -2
- package/src/form/field/trigger/SpinUpDown.mjs +2 -2
- package/src/grid/View.mjs +1 -1
- package/src/main/DeltaUpdates.mjs +382 -0
- package/src/main/DomAccess.mjs +13 -36
- package/src/main/render/DomApiRenderer.mjs +138 -0
- package/src/main/render/StringBasedRenderer.mjs +58 -0
- package/src/table/View.mjs +1 -1
- package/src/table/plugin/CellEditing.mjs +1 -1
- package/src/tree/Accordion.mjs +11 -11
- package/src/tree/List.mjs +12 -5
- package/src/vdom/Helper.mjs +174 -292
- package/src/vdom/VNode.mjs +47 -11
- package/src/vdom/domConstants.mjs +65 -0
- package/src/vdom/util/DomApiVnodeCreator.mjs +51 -0
- package/src/vdom/util/StringFromVnode.mjs +123 -0
- package/src/worker/mixin/RemoteMethodAccess.mjs +13 -1
- package/resources/data/deck/learnneo/pages/Welcome.md +0 -64
- package/src/main/mixin/DeltaUpdates.mjs +0 -352
package/src/vdom/VNode.mjs
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
import StringUtil from '../util/String.mjs';
|
2
|
+
|
1
3
|
/**
|
2
|
-
* Wrapper class for vnode objects.
|
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
|
-
*
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
97
|
+
me.static = true
|
62
98
|
}
|
63
99
|
}
|
64
100
|
}
|
65
101
|
|
66
102
|
const ns = Neo.ns('Neo.vdom', true);
|
67
|
-
ns
|
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?.('"', '"') ?? 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
|
-
|
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 <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 — all integrated within a single app — 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>
|