apprun 3.38.0 → 6.0.0-rc.0
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/CHANGELOG.md +12 -1
- package/README.md +18 -10
- package/apprun.d.ts +67 -23
- package/dist/apprun-code.js +1 -1
- package/dist/apprun-code.js.map +1 -1
- package/dist/apprun-dev-tools.js +1 -1
- package/dist/apprun-dev-tools.js.map +1 -1
- package/dist/apprun-html.esm.js +7 -7
- package/dist/apprun-html.esm.js.map +1 -1
- package/dist/apprun-html.js +1 -1
- package/dist/apprun-html.js.map +1 -1
- package/dist/apprun-play.js +1 -1
- package/dist/apprun-play.js.map +1 -1
- package/dist/apprun.esm.js +1 -1
- package/dist/apprun.esm.js.map +1 -1
- package/dist/apprun.js +1 -1
- package/dist/apprun.js.map +1 -1
- package/esm/add-components.js +7 -7
- package/esm/add-components.js.map +1 -1
- package/esm/app.js +81 -32
- package/esm/app.js.map +1 -1
- package/esm/apprun-code.js +8 -2
- package/esm/apprun-code.js.map +1 -1
- package/esm/apprun-html.js +14 -9
- package/esm/apprun-html.js.map +1 -1
- package/esm/apprun-play.js +7 -1
- package/esm/apprun-play.js.map +1 -1
- package/esm/apprun.js +38 -19
- package/esm/apprun.js.map +1 -1
- package/esm/component.js +90 -37
- package/esm/component.js.map +1 -1
- package/esm/directive.js +1 -1
- package/esm/directive.js.map +1 -1
- package/esm/router.js +73 -19
- package/esm/router.js.map +1 -1
- package/esm/types.js +2 -1
- package/esm/types.js.map +1 -1
- package/esm/vdom-my.js +52 -52
- package/esm/vdom-my.js.map +1 -1
- package/esm/vdom-to-html.js +8 -1
- package/esm/vdom-to-html.js.map +1 -1
- package/esm/vdom.js +3 -3
- package/esm/vdom.js.map +1 -1
- package/esm/version.js +1 -1
- package/esm/version.js.map +1 -1
- package/esm/web-component.js +23 -6
- package/esm/web-component.js.map +1 -1
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +62 -2
- package/.clinerules +0 -1
- package/.travis.yml +0 -10
- package/BACKERS.md +0 -8
- package/CNAME +0 -1
- package/WHATSNEW.md +0 -367
- package/ai/apprun-html.prompt.md +0 -290
- package/ai/apprun.prompt.md +0 -602
- package/apprun-book.jpg +0 -0
- package/dist/apprun-dev-tools-tests.js +0 -1
- package/dist/apprun-dev-tools.js.LICENSE.txt +0 -1
- package/dist/apprun-play-html.esm.js +0 -34
- package/dist/apprun-play-html.esm.js.map +0 -1
- package/error.log +0 -0
- package/esm/apprun-play-html.js +0 -210
- package/esm/apprun-play-html.js.map +0 -1
- package/esm/shadow.js +0 -8
- package/esm/shadow.js.map +0 -1
- package/esm/vdom-html.js +0 -22
- package/esm/vdom-html.js.map +0 -1
- package/esm/vdom-my-new.js +0 -327
- package/esm/vdom-my-new.js.map +0 -1
- package/esm/vdom-patch.js +0 -103
- package/esm/vdom-patch.js.map +0 -1
- package/index.html +0 -87
- package/jest.config.js +0 -57
- package/jest.setup.js +0 -73
- package/logo.png +0 -0
- package/react.js +0 -13
- package/rollup.config.js +0 -62
- package/src/add-components.ts +0 -103
- package/src/app.ts +0 -173
- package/src/apprun-code.tsx +0 -177
- package/src/apprun-dev-tools-tests.tsx +0 -101
- package/src/apprun-dev-tools.tsx +0 -307
- package/src/apprun-html.ts +0 -25
- package/src/apprun-play.tsx +0 -249
- package/src/apprun.ts +0 -238
- package/src/component.ts +0 -388
- package/src/createState.ts +0 -11
- package/src/decorator.ts +0 -98
- package/src/directive.ts +0 -303
- package/src/global.d.ts +0 -22
- package/src/router.ts +0 -311
- package/src/shadow.tsx +0 -8
- package/src/tsconfig.json +0 -16
- package/src/type-utils.ts +0 -130
- package/src/types.ts +0 -151
- package/src/vdom-html.ts_ +0 -20
- package/src/vdom-lit-html.ts +0 -67
- package/src/vdom-my-new.ts +0 -311
- package/src/vdom-my-prop-attr.ts +0 -241
- package/src/vdom-my.ts +0 -286
- package/src/vdom-patch.ts +0 -108
- package/src/vdom-to-html.tsx +0 -49
- package/src/vdom.ts +0 -28
- package/src/version.ts +0 -16
- package/src/web-component.ts +0 -154
- package/tsconfig.jest.json +0 -49
- package/tsconfig.json +0 -16
- package/tslint.json +0 -114
- package/typescriptreact.json +0 -87
- package/viewEngine.js +0 -54
- package/webpack.config.cjs +0 -41
package/src/vdom-my.ts
DELETED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/** * VDOM Implementation for AppRun
|
|
2
|
-
*
|
|
3
|
-
* Notes for AppRun’s key prop
|
|
4
|
-
* Use the key prop only if you need to preserve browser-side DOM state (such as cursor
|
|
5
|
-
* position in an <input>, focus, or maintaining state in inline components).
|
|
6
|
-
*
|
|
7
|
-
* For most use cases, especially with stateless or purely data-driven UIs, key is not
|
|
8
|
-
* needed and may decrease performance by forcing DOM moves.
|
|
9
|
-
*
|
|
10
|
-
* AppRun aggressively updates DOM to match your vdom, so DOM node preservation is
|
|
11
|
-
* only valuable when you intentionally depend on browser-managed state.
|
|
12
|
-
*
|
|
13
|
-
* | Use Case | Should I use `key`? | Why |
|
|
14
|
-
* | ----------------------------- | ------------------- | --------------------- |
|
|
15
|
-
* | Preserve input cursor/focus | ✅ Yes | Keeps browser state |
|
|
16
|
-
* | Inline stateful component | ✅ Yes | Preserves component |
|
|
17
|
-
* | Regular data list (stateless) | 🚫 No | Unnecessary DOM moves |
|
|
18
|
-
* | Purely visual updates | 🚫 No | No state to preserve |
|
|
19
|
-
*
|
|
20
|
-
* Features:
|
|
21
|
-
* - Virtual DOM rendering and diffing for efficient DOM updates
|
|
22
|
-
* - JSX Fragment support for both Babel and TypeScript
|
|
23
|
-
* - Element creation with props, children, and event handling
|
|
24
|
-
* - SVG element support with proper namespace handling
|
|
25
|
-
* - Component lifecycle management and caching
|
|
26
|
-
* - Keyed element optimization with automatic cleanup for memory management
|
|
27
|
-
* - Safe HTML insertion and text node creation
|
|
28
|
-
* - Directive processing integration
|
|
29
|
-
*
|
|
30
|
-
* Implementation:
|
|
31
|
-
* - Uses plain JavaScript object for keyCache instead of Map for better performance
|
|
32
|
-
* - Implements automatic cleanup of disconnected elements from keyCache
|
|
33
|
-
* - Supports both string and function-based tags
|
|
34
|
-
* - Handles component mounting and state management
|
|
35
|
-
* - Optimized children updating with minimal DOM operations
|
|
36
|
-
* - Memory-efficient caching with configurable thresholds (500 ops, 1000 max size)
|
|
37
|
-
*
|
|
38
|
-
* Recent Changes:
|
|
39
|
-
* - 2025-07-15: Converted keyCache from Map to plain object ({}) for improved performance
|
|
40
|
-
* - Updated cleanup functions to use object property deletion instead of Map methods
|
|
41
|
-
* - Enhanced memory management with automatic cleanup of disconnected elements
|
|
42
|
-
* - Added comprehensive key prop usage documentation and guidelines
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
import { VDOM, VNode } from './types';
|
|
46
|
-
import directive from './directive';
|
|
47
|
-
import { updateProps } from './vdom-my-prop-attr';
|
|
48
|
-
export type Element = any; //HTMLElement | SVGSVGElement | SVGElement;
|
|
49
|
-
|
|
50
|
-
export function Fragment(props, ...children): any[] {
|
|
51
|
-
return collect(children);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function collect(children) {
|
|
55
|
-
const ch = [];
|
|
56
|
-
const push = (c) => {
|
|
57
|
-
if (c !== null && c !== undefined && c !== '' && c !== false) {
|
|
58
|
-
ch.push((typeof c === 'function' || typeof c === 'object') ? c : `${c}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
children && children.forEach(c => {
|
|
62
|
-
if (Array.isArray(c)) {
|
|
63
|
-
c.forEach(i => push(i));
|
|
64
|
-
} else {
|
|
65
|
-
push(c);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
return ch;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const keyCache: { [key: string]: Element } = {};
|
|
72
|
-
let cleanupCounter = 0;
|
|
73
|
-
const CLEANUP_THRESHOLD = 500; // Cleanup every 500 operations
|
|
74
|
-
const MAX_CACHE_SIZE = 1000;
|
|
75
|
-
|
|
76
|
-
// Lightweight cleanup function - only runs when needed
|
|
77
|
-
function cleanupKeyCache() {
|
|
78
|
-
if (Object.keys(keyCache).length <= MAX_CACHE_SIZE) return; // Skip if under limit
|
|
79
|
-
|
|
80
|
-
for (const [key, element] of Object.entries(keyCache)) {
|
|
81
|
-
if (!element.isConnected) {
|
|
82
|
-
delete keyCache[key];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Export cleanup function for manual cleanup if needed
|
|
88
|
-
export function clearKeyCache() {
|
|
89
|
-
for (const key in keyCache) {
|
|
90
|
-
delete keyCache[key];
|
|
91
|
-
}
|
|
92
|
-
cleanupCounter = 0;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function createElement(tag: string | Function | [], props?: {}, ...children) {
|
|
96
|
-
const ch = collect(children);
|
|
97
|
-
if (typeof tag === 'string') return { tag, props, children: ch };
|
|
98
|
-
else if (Array.isArray(tag)) return tag; // JSX fragments - babel
|
|
99
|
-
else if (tag === undefined && children) return ch; // JSX fragments - typescript
|
|
100
|
-
else if (Object.getPrototypeOf(tag).__isAppRunComponent) return { tag, props, children: ch } // createComponent(tag, { ...props, children });
|
|
101
|
-
else if (typeof tag === 'function') return tag(props, ch);
|
|
102
|
-
else throw new Error(`Unknown tag in vdom ${tag}`);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export const updateElement = (element: Element | string, nodes: VDOM, component = {}) => {
|
|
106
|
-
// tslint:disable-next-line
|
|
107
|
-
if (nodes == null || nodes === false) return;
|
|
108
|
-
const el = (typeof element === 'string' && element) ?
|
|
109
|
-
document.getElementById(element) || document.querySelector(element) : element;
|
|
110
|
-
nodes = directive(nodes, component);
|
|
111
|
-
render(el, nodes, component);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function render(element: Element, nodes: VDOM, parent = {}) {
|
|
115
|
-
// tslint:disable-next-line
|
|
116
|
-
if (nodes == null || nodes === false) return;
|
|
117
|
-
nodes = createComponent(nodes, parent);
|
|
118
|
-
if (!element) return;
|
|
119
|
-
const isSvg = element.nodeName === "SVG";
|
|
120
|
-
if (Array.isArray(nodes)) {
|
|
121
|
-
updateChildren(element, nodes, isSvg);
|
|
122
|
-
} else {
|
|
123
|
-
updateChildren(element, [nodes], isSvg);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function same(el: Element, node: VNode) {
|
|
128
|
-
// if (!el || !node) return false;
|
|
129
|
-
const key1 = el.nodeName;
|
|
130
|
-
const key2 = `${node.tag || ''}`;
|
|
131
|
-
return key1.toUpperCase() === key2.toUpperCase();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function update(element: Element, node: VNode, isSvg: boolean) {
|
|
135
|
-
// console.assert(!!element);
|
|
136
|
-
isSvg = isSvg || node.tag === "svg";
|
|
137
|
-
if (!same(element, node)) {
|
|
138
|
-
element.parentNode.replaceChild(create(node, isSvg), element);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
updateChildren(element, node.children, isSvg);
|
|
142
|
-
updateProps(element, node.props, isSvg);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function updateChildren(element, children, isSvg: boolean) {
|
|
146
|
-
const old_len = element.childNodes?.length || 0;
|
|
147
|
-
const new_len = children?.length || 0;
|
|
148
|
-
const len = Math.min(old_len, new_len);
|
|
149
|
-
for (let i = 0; i < len; i++) {
|
|
150
|
-
const child = children[i];
|
|
151
|
-
const el = element.childNodes[i];
|
|
152
|
-
if (typeof child === 'string') {
|
|
153
|
-
if (el.textContent !== child) {
|
|
154
|
-
if (el.nodeType === 3) {
|
|
155
|
-
el.nodeValue = child
|
|
156
|
-
} else {
|
|
157
|
-
element.replaceChild(createText(child), el);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} else if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
161
|
-
element.insertBefore(child, el);
|
|
162
|
-
} else {
|
|
163
|
-
const key = child.props && child.props['key'];
|
|
164
|
-
if (key) {
|
|
165
|
-
if (el.key === key) {
|
|
166
|
-
update(element.childNodes[i], child, isSvg);
|
|
167
|
-
} else {
|
|
168
|
-
// console.log(el.key, key);
|
|
169
|
-
const old = keyCache[key];
|
|
170
|
-
if (old) {
|
|
171
|
-
// const temp = old.nextSibling;
|
|
172
|
-
element.insertBefore(old, el);
|
|
173
|
-
// temp ? element.insertBefore(el, temp) : element.appendChild(el);
|
|
174
|
-
update(element.childNodes[i], child, isSvg);
|
|
175
|
-
} else {
|
|
176
|
-
element.replaceChild(create(child, isSvg), el);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
update(element.childNodes[i], child, isSvg);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let n = element.childNodes?.length || 0;
|
|
186
|
-
while (n > len) {
|
|
187
|
-
element.removeChild(element.lastChild);
|
|
188
|
-
n--;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (new_len > len) {
|
|
192
|
-
const d = document.createDocumentFragment();
|
|
193
|
-
for (let i = len; i < children.length; i++) {
|
|
194
|
-
d.appendChild(create(children[i], isSvg));
|
|
195
|
-
}
|
|
196
|
-
element.appendChild(d);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export const safeHTML = (html: string) => {
|
|
201
|
-
const div = document.createElement('section');
|
|
202
|
-
div.insertAdjacentHTML('afterbegin', html)
|
|
203
|
-
return Array.from(div.children);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function createText(node) {
|
|
207
|
-
if (node?.indexOf('_html:') === 0) { // ?
|
|
208
|
-
const div = document.createElement('div');
|
|
209
|
-
div.insertAdjacentHTML('afterbegin', node.substring(6))
|
|
210
|
-
return div;
|
|
211
|
-
} else {
|
|
212
|
-
return document.createTextNode(node ?? '');
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function create(node: VNode | string | HTMLElement | SVGElement, isSvg: boolean): Element {
|
|
217
|
-
// console.assert(node !== null && node !== undefined);
|
|
218
|
-
if ((node instanceof HTMLElement) || (node instanceof SVGElement)) return node;
|
|
219
|
-
if (typeof node === "string") return createText(node);
|
|
220
|
-
if (!node.tag || (typeof node.tag === 'function')) return createText(JSON.stringify(node));
|
|
221
|
-
isSvg = isSvg || node.tag === "svg";
|
|
222
|
-
const element = isSvg
|
|
223
|
-
? document.createElementNS("http://www.w3.org/2000/svg", node.tag)
|
|
224
|
-
: document.createElement(node.tag);
|
|
225
|
-
|
|
226
|
-
updateProps(element, node.props, isSvg);
|
|
227
|
-
if (node.children) node.children.forEach(child => element.appendChild(create(child, isSvg)));
|
|
228
|
-
|
|
229
|
-
if (node.props && (node.props as any).key !== undefined) {
|
|
230
|
-
(element as any).key = (node.props as any).key;
|
|
231
|
-
keyCache[(node.props as any).key] = element;
|
|
232
|
-
|
|
233
|
-
// Lightweight cleanup - only when counter reaches threshold
|
|
234
|
-
if (++cleanupCounter >= CLEANUP_THRESHOLD) {
|
|
235
|
-
cleanupKeyCache();
|
|
236
|
-
cleanupCounter = 0;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return element
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function render_component(node, parent, idx) {
|
|
243
|
-
const { tag, props, children } = node;
|
|
244
|
-
let key = `_${idx}`;
|
|
245
|
-
let id = props && props['id'];
|
|
246
|
-
if (!id) id = `_${idx}${Date.now()}`;
|
|
247
|
-
else key = id;
|
|
248
|
-
let asTag = 'section';
|
|
249
|
-
if (props && props['as']) {
|
|
250
|
-
asTag = props['as'];
|
|
251
|
-
delete props['as'];
|
|
252
|
-
}
|
|
253
|
-
if (!parent.__componentCache) parent.__componentCache = {};
|
|
254
|
-
let component = parent.__componentCache[key];
|
|
255
|
-
if (!component || !(component instanceof tag) || !component.element) {
|
|
256
|
-
const element = document.createElement(asTag);
|
|
257
|
-
component = parent.__componentCache[key] = new tag({ ...props, children }).mount(element, { render: true });
|
|
258
|
-
} else {
|
|
259
|
-
component.renderState(component.state);
|
|
260
|
-
}
|
|
261
|
-
if (component.mounted) {
|
|
262
|
-
const new_state = component.mounted(props, children, component.state);
|
|
263
|
-
(typeof new_state !== 'undefined') && component.setState(new_state);
|
|
264
|
-
}
|
|
265
|
-
updateProps(component.element, props, false);
|
|
266
|
-
return component.element;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function createComponent(node, parent, idx = 0) {
|
|
270
|
-
if (typeof node === 'string') return node;
|
|
271
|
-
if (Array.isArray(node)) return node.map(child => createComponent(child, parent, idx++));
|
|
272
|
-
let vdom = node;
|
|
273
|
-
if (node && typeof node.tag === 'function' && Object.getPrototypeOf(node.tag).__isAppRunComponent) {
|
|
274
|
-
vdom = render_component(node, parent, idx);
|
|
275
|
-
}
|
|
276
|
-
if (vdom && Array.isArray(vdom.children)) {
|
|
277
|
-
const new_parent = vdom.props?._component;
|
|
278
|
-
if (new_parent) {
|
|
279
|
-
let i = 0;
|
|
280
|
-
vdom.children = vdom.children.map(child => createComponent(child, new_parent, i++));
|
|
281
|
-
} else {
|
|
282
|
-
vdom.children = vdom.children.map(child => createComponent(child, parent, idx++));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return vdom;
|
|
286
|
-
}
|
package/src/vdom-patch.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
enum CHANGES {
|
|
2
|
-
NO_ATTR_CHANGE = 1,
|
|
3
|
-
NO_TREE_CHANGE = 2,
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function patch(vdom1, vdom2) {
|
|
7
|
-
if (!vdom1?.length || !vdom2?.length) return;
|
|
8
|
-
const old_len = vdom1.length;
|
|
9
|
-
const new_len = vdom2.length;
|
|
10
|
-
const len = Math.min(old_len, new_len);
|
|
11
|
-
for (let i = 0; i < len; i++) {
|
|
12
|
-
const node1 = vdom1[i];
|
|
13
|
-
const node2 = vdom2[i];
|
|
14
|
-
if (!node2 || typeof node2 !== 'object') continue;
|
|
15
|
-
if (_areEquals(node1, node2)) {
|
|
16
|
-
node2._op = CHANGES.NO_ATTR_CHANGE | CHANGES.NO_TREE_CHANGE;
|
|
17
|
-
} else {
|
|
18
|
-
if (_areEquals(node1.props, node2.props)) {
|
|
19
|
-
node2._op = CHANGES.NO_ATTR_CHANGE;
|
|
20
|
-
}
|
|
21
|
-
if (_areEquals(node1.children, node2.children)) {
|
|
22
|
-
node2._op = node2._op | CHANGES.NO_TREE_CHANGE;
|
|
23
|
-
} else {
|
|
24
|
-
patch(node1.children, node2.children);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function get_vdom(node: Node) {
|
|
31
|
-
if (!node) return null;
|
|
32
|
-
if (node.nodeType === 3) return node.nodeValue;
|
|
33
|
-
const children = [];
|
|
34
|
-
if (node.childNodes) {
|
|
35
|
-
for (let i = 0; i < node.childNodes.length; i++) {
|
|
36
|
-
children.push(get_vdom(node.childNodes[i]));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const tag = node['tagName'] ? node['tagName'].toLocaleLowerCase() : node;
|
|
40
|
-
return {
|
|
41
|
-
tag,
|
|
42
|
-
props: node['_props'],
|
|
43
|
-
children
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// based on https://github.com/epoberezkin/fast-deep-equal
|
|
48
|
-
// MIT License
|
|
49
|
-
|
|
50
|
-
// Copyright (c) 2017 Evgeny Poberezkin
|
|
51
|
-
|
|
52
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
53
|
-
// of this software and associated documentation files (the "Software"), to deal
|
|
54
|
-
// in the Software without restriction, including without limitation the rights
|
|
55
|
-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
56
|
-
// copies of the Software, and to permit persons to whom the Software is
|
|
57
|
-
// furnished to do so, subject to the following conditions:
|
|
58
|
-
|
|
59
|
-
// The above copyright notice and this permission notice shall be included in all
|
|
60
|
-
// copies or substantial portions of the Software.
|
|
61
|
-
|
|
62
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
63
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
64
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
65
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
66
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
67
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
68
|
-
// SOFTWARE.
|
|
69
|
-
export function _areEquals(a: any, b: any): boolean {
|
|
70
|
-
if (a === b) return true;
|
|
71
|
-
|
|
72
|
-
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
|
73
|
-
var arrA = Array.isArray(a)
|
|
74
|
-
, arrB = Array.isArray(b)
|
|
75
|
-
, i
|
|
76
|
-
, length
|
|
77
|
-
, key;
|
|
78
|
-
|
|
79
|
-
if (arrA && arrB) {
|
|
80
|
-
length = a.length;
|
|
81
|
-
if (length != b.length) return false;
|
|
82
|
-
for (i = length; i-- !== 0;)
|
|
83
|
-
if (!_areEquals(a[i], b[i])) return false;
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (arrA != arrB) return false;
|
|
88
|
-
|
|
89
|
-
var keys = Object.keys(a);
|
|
90
|
-
length = keys.length;
|
|
91
|
-
|
|
92
|
-
if (length !== Object.keys(b).length)
|
|
93
|
-
return false;
|
|
94
|
-
|
|
95
|
-
for (i = length; i-- !== 0;)
|
|
96
|
-
if (!b.hasOwnProperty(keys[i])) return false;
|
|
97
|
-
|
|
98
|
-
for (i = length; i-- !== 0;) {
|
|
99
|
-
key = keys[i];
|
|
100
|
-
if (!_areEquals(a[key], b[key])) return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return a!==a && b!==b;
|
|
107
|
-
};
|
|
108
|
-
export default patch;
|
package/src/vdom-to-html.tsx
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { VDOM } from './types';
|
|
3
|
-
import { TemplateResult } from 'lit-html';
|
|
4
|
-
|
|
5
|
-
function getProp(prop) {
|
|
6
|
-
if (typeof prop === 'object') {
|
|
7
|
-
return Object.keys(prop).map(name => `${name}:${prop[name]}`).join(';');
|
|
8
|
-
}
|
|
9
|
-
else return prop.toString();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function toProps(props) {
|
|
13
|
-
return Object.keys(props)
|
|
14
|
-
.map(name => ` ${name === 'className' ? 'class' : name}="${getProp(props[name])}"`)
|
|
15
|
-
.join('');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function toHTMLArray(nodes) {
|
|
19
|
-
return nodes.map(node => toHTML(node)).join('');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function clean(obj) {
|
|
23
|
-
for (var i in obj) {
|
|
24
|
-
if (obj[i] == null) {
|
|
25
|
-
delete obj[i];
|
|
26
|
-
} else if (typeof obj[i] === 'object') {
|
|
27
|
-
clean(obj[i]);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function toHTML (vdom) {
|
|
33
|
-
if (!vdom) return '';
|
|
34
|
-
if (vdom['_$litType$']) {
|
|
35
|
-
return vdom.toString();
|
|
36
|
-
}
|
|
37
|
-
clean(vdom);
|
|
38
|
-
if (Array.isArray(vdom)) return toHTMLArray(vdom);
|
|
39
|
-
if (typeof vdom === 'string') {
|
|
40
|
-
return vdom.startsWith('_html:') ? vdom.substring(6) : vdom;
|
|
41
|
-
} else if (vdom.tag) {
|
|
42
|
-
const props = vdom.props ? toProps(vdom.props) : '';
|
|
43
|
-
const children = vdom.children ? toHTMLArray(vdom.children) : '';
|
|
44
|
-
return `<${vdom.tag}${props}>${children}</${vdom.tag}>`;
|
|
45
|
-
}
|
|
46
|
-
if (typeof vdom === 'object') return JSON.stringify(vdom);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default toHTML;
|
package/src/vdom.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Virtual DOM Implementation
|
|
3
|
-
*
|
|
4
|
-
* This file provides the core virtual DOM functionality:
|
|
5
|
-
* 1. createElement: Creates virtual DOM nodes
|
|
6
|
-
* 2. Fragment: Support for fragments
|
|
7
|
-
* 3. render: Renders virtual DOM to real DOM
|
|
8
|
-
* 4. safeHTML: Safely renders HTML strings
|
|
9
|
-
*
|
|
10
|
-
* The virtual DOM system:
|
|
11
|
-
* - Provides efficient DOM updates
|
|
12
|
-
* - Supports JSX compilation
|
|
13
|
-
* - Handles component rendering
|
|
14
|
-
* - Manages DOM diffing and patching
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* ```ts
|
|
18
|
-
* // JSX gets compiled to createElement calls
|
|
19
|
-
* const vdom = <div id="app">Hello</div>;
|
|
20
|
-
*
|
|
21
|
-
* // Render to DOM
|
|
22
|
-
* render(document.body, vdom);
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { createElement, updateElement, Fragment, safeHTML } from './vdom-my';
|
|
27
|
-
export { createElement, Fragment, updateElement as render, safeHTML };
|
|
28
|
-
export { createElement as jsx, createElement as jsxs };
|
package/src/version.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version Management Utility
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for AppRun version information.
|
|
5
|
-
* This file ensures version consistency across all modules.
|
|
6
|
-
*
|
|
7
|
-
* The version is derived from package.json and should be updated
|
|
8
|
-
* only when the package version changes.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Import version from package.json to maintain single source of truth
|
|
12
|
-
// This version string is used across the framework
|
|
13
|
-
export const APPRUN_VERSION = '3.38.0';
|
|
14
|
-
|
|
15
|
-
// Version string with prefix for global tracking
|
|
16
|
-
export const APPRUN_VERSION_GLOBAL = `AppRun-${APPRUN_VERSION}`;
|
package/src/web-component.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web Components Integration
|
|
3
|
-
*
|
|
4
|
-
* This file enables using AppRun components as Web Components:
|
|
5
|
-
* 1. Custom Element Creation
|
|
6
|
-
* - Converts AppRun components to custom elements
|
|
7
|
-
* - Handles lifecycle (connected, disconnected, attributeChanged)
|
|
8
|
-
* - Manages shadow DOM and light DOM rendering options
|
|
9
|
-
* - Automatic element registration with customElements.define()
|
|
10
|
-
*
|
|
11
|
-
* 2. Property/Attribute Sync
|
|
12
|
-
* - Observes attribute changes with attributeChangedCallback
|
|
13
|
-
* - Updates component state automatically on changes
|
|
14
|
-
* - Handles camelCase/kebab-case conversion automatically
|
|
15
|
-
* - Supports complex property types and JSON parsing
|
|
16
|
-
* - Two-way data binding between attributes and state
|
|
17
|
-
*
|
|
18
|
-
* 3. Event Handling
|
|
19
|
-
* - Proxies events between component and element
|
|
20
|
-
* - Supports both global and local event systems
|
|
21
|
-
* - Maintains proper event bubbling and capturing
|
|
22
|
-
* - Custom event dispatching for component communication
|
|
23
|
-
*
|
|
24
|
-
* Features:
|
|
25
|
-
* - Shadow DOM encapsulation support
|
|
26
|
-
* - Attribute observation and change detection
|
|
27
|
-
* - Lifecycle management and cleanup
|
|
28
|
-
* - Property reflection to attributes
|
|
29
|
-
* - Event proxy system
|
|
30
|
-
* - Flexible rendering options
|
|
31
|
-
*
|
|
32
|
-
* Type Safety Improvements (v3.35.1):
|
|
33
|
-
* - Enhanced custom element options typing
|
|
34
|
-
* - Better lifecycle method signatures
|
|
35
|
-
* - Improved attribute/property type safety
|
|
36
|
-
* - Safer DOM manipulation with null checks
|
|
37
|
-
*
|
|
38
|
-
* Usage:
|
|
39
|
-
* ```ts
|
|
40
|
-
* // Register web component
|
|
41
|
-
* @customElement('my-element', {
|
|
42
|
-
* shadow: true,
|
|
43
|
-
* observedAttributes: ['name', 'value']
|
|
44
|
-
* })
|
|
45
|
-
* class MyComponent extends Component {
|
|
46
|
-
* // Regular AppRun component code
|
|
47
|
-
* }
|
|
48
|
-
*
|
|
49
|
-
* // Use in HTML
|
|
50
|
-
* <my-element name="value" data-prop="complex"></my-element>
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
declare var customElements;
|
|
55
|
-
|
|
56
|
-
import { CustomElementOptions } from './types';
|
|
57
|
-
|
|
58
|
-
export const customElement = (componentClass, options: CustomElementOptions = {}) => class CustomElement extends HTMLElement {
|
|
59
|
-
public _shadowRoot;
|
|
60
|
-
public _component;
|
|
61
|
-
public _attrMap: (arg0: string) => string;
|
|
62
|
-
public on;
|
|
63
|
-
public run;
|
|
64
|
-
constructor() {
|
|
65
|
-
super();
|
|
66
|
-
}
|
|
67
|
-
get component() { return this._component; }
|
|
68
|
-
get state() { return this._component.state; }
|
|
69
|
-
|
|
70
|
-
static get observedAttributes() {
|
|
71
|
-
// attributes need to be set to lowercase in order to get observed
|
|
72
|
-
return (options.observedAttributes || []).map(attr => attr.toLowerCase());
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
connectedCallback() {
|
|
76
|
-
if (this.isConnected && !this._component) {
|
|
77
|
-
const opts = options || {};
|
|
78
|
-
this._shadowRoot = opts.shadow ? this.attachShadow({ mode: 'open' }) : this;
|
|
79
|
-
const observedAttributes = (opts.observedAttributes || [])
|
|
80
|
-
|
|
81
|
-
const attrMap = observedAttributes.reduce((map, name) => {
|
|
82
|
-
const lc = name.toLowerCase()
|
|
83
|
-
if (lc !== name) {
|
|
84
|
-
map[lc] = name
|
|
85
|
-
}
|
|
86
|
-
return map
|
|
87
|
-
}, {})
|
|
88
|
-
this._attrMap = (name: string): string => attrMap[name] || name
|
|
89
|
-
|
|
90
|
-
const props = {};
|
|
91
|
-
Array.from(this.attributes).forEach(item => props[this._attrMap(item.name)] = item.value);
|
|
92
|
-
|
|
93
|
-
// add getters/ setters to allow observation on observedAttributes
|
|
94
|
-
observedAttributes.forEach(name => {
|
|
95
|
-
if (this[name] !== undefined) props[name] = this[name];
|
|
96
|
-
Object.defineProperty(this, name, {
|
|
97
|
-
get(): any {
|
|
98
|
-
return props[name];
|
|
99
|
-
},
|
|
100
|
-
set(this: CustomElement, value: unknown) {
|
|
101
|
-
// trigger change event
|
|
102
|
-
this.attributeChangedCallback(name, props[name], value)
|
|
103
|
-
},
|
|
104
|
-
configurable: true,
|
|
105
|
-
enumerable: true
|
|
106
|
-
});
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
requestAnimationFrame(() => {
|
|
110
|
-
const children = this.children ? Array.from(this.children) : [];
|
|
111
|
-
// children.forEach(el => el.parentElement.removeChild(el));
|
|
112
|
-
this._component = new componentClass({ ...props, children }).mount(this._shadowRoot, opts);
|
|
113
|
-
// attach props to component
|
|
114
|
-
this._component._props = props;
|
|
115
|
-
// expose dispatchEvent
|
|
116
|
-
this._component.dispatchEvent = this.dispatchEvent.bind(this)
|
|
117
|
-
if (this._component.mounted) {
|
|
118
|
-
const new_state = this._component.mounted(props, children, this._component.state);
|
|
119
|
-
if (typeof new_state !== 'undefined') this._component.state = new_state;
|
|
120
|
-
}
|
|
121
|
-
this.on = this._component.on.bind(this._component);
|
|
122
|
-
this.run = this._component.run.bind(this._component);
|
|
123
|
-
if (!(opts.render === false)) this._component.run('.');
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
disconnectedCallback() {
|
|
129
|
-
this._component?.unload?.();
|
|
130
|
-
this._component?.unmount?.();
|
|
131
|
-
this._component = null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
attributeChangedCallback(name: string, oldValue: unknown, value: unknown) {
|
|
135
|
-
if (this._component) {
|
|
136
|
-
// camelCase attributes arrive only in lowercase
|
|
137
|
-
const mappedName = this._attrMap(name);
|
|
138
|
-
// store the new property/ attribute
|
|
139
|
-
this._component._props[mappedName] = value;
|
|
140
|
-
this._component.run('attributeChanged', mappedName, oldValue, value);
|
|
141
|
-
|
|
142
|
-
if (value !== oldValue && !(options.render === false)) {
|
|
143
|
-
window.requestAnimationFrame(() => {
|
|
144
|
-
// re-render state with new combined props on next animation frame
|
|
145
|
-
this._component.run('.')
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export default (name: string, componentClass, options?: CustomElementOptions) => {
|
|
153
|
-
(typeof customElements !== 'undefined') && customElements.define(name, customElement(componentClass, options))
|
|
154
|
-
}
|
package/tsconfig.jest.json
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es2020",
|
|
4
|
-
"module": "esnext",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"jsx": "react",
|
|
7
|
-
"reactNamespace": "app",
|
|
8
|
-
"lib": [
|
|
9
|
-
"dom",
|
|
10
|
-
"esnext"
|
|
11
|
-
],
|
|
12
|
-
"experimentalDecorators": true,
|
|
13
|
-
"sourceMap": true,
|
|
14
|
-
"esModuleInterop": true,
|
|
15
|
-
"downlevelIteration": true,
|
|
16
|
-
"allowJs": true,
|
|
17
|
-
"outDir": "coverage/src",
|
|
18
|
-
"isolatedModules": true,
|
|
19
|
-
"strict": true,
|
|
20
|
-
"noImplicitAny": true,
|
|
21
|
-
"strictNullChecks": true,
|
|
22
|
-
"strictFunctionTypes": true,
|
|
23
|
-
"strictBindCallApply": true,
|
|
24
|
-
"strictPropertyInitialization": true,
|
|
25
|
-
"noImplicitThis": true,
|
|
26
|
-
"alwaysStrict": true,
|
|
27
|
-
"skipLibCheck": true,
|
|
28
|
-
"forceConsistentCasingInFileNames": true,
|
|
29
|
-
"baseUrl": ".",
|
|
30
|
-
"paths": {
|
|
31
|
-
"*": [
|
|
32
|
-
"src/types/*"
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
"typeRoots": [
|
|
36
|
-
"./node_modules/@types",
|
|
37
|
-
"./src/types"
|
|
38
|
-
],
|
|
39
|
-
"types": [
|
|
40
|
-
"jest",
|
|
41
|
-
"node"
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
"include": [
|
|
45
|
-
"src/**/*",
|
|
46
|
-
"tests/**/*",
|
|
47
|
-
"demo/**/*"
|
|
48
|
-
]
|
|
49
|
-
}
|