lupine.web 1.0.26 → 1.0.28
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/package.json +1 -1
- package/src/core/bind-ref.ts +19 -0
- package/src/core/mount-components.ts +25 -248
- package/src/core/render-components.ts +217 -0
- package/src/core/replace-innerhtml.ts +10 -0
- package/src/jsx.ts +1 -0
- package/src/lib/cookie.ts +3 -0
package/package.json
CHANGED
package/src/core/bind-ref.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// import { mountSelfComponents, renderComponent } from "./mount-components";
|
|
2
2
|
|
|
3
|
+
import { VNode } from "../jsx";
|
|
4
|
+
import { bindAttributes } from "./bind-attributes";
|
|
5
|
+
import { bindLinks } from "./bind-links";
|
|
6
|
+
import { renderComponents } from "./render-components";
|
|
7
|
+
import { replaceInnerhtml } from "./replace-innerhtml";
|
|
8
|
+
|
|
3
9
|
export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
4
10
|
// console.log('========', newProps, el);
|
|
5
11
|
const id = newProps._id;
|
|
@@ -7,6 +13,7 @@ export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
|
7
13
|
newProps['ref'].current = el;
|
|
8
14
|
if (newProps['ref'].onLoad) {
|
|
9
15
|
// setTimeout(() => newProps['ref'].onLoad(el), 0);
|
|
16
|
+
// Promise.resolve().then(fn) will be called in the end of current event loop (quicker than setTimeout)
|
|
10
17
|
const defer = Promise.prototype.then.bind(Promise.resolve());
|
|
11
18
|
defer(() => newProps['ref'].onLoad(el));
|
|
12
19
|
}
|
|
@@ -30,4 +37,16 @@ export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
|
30
37
|
newProps['ref'].$all = (selector: string) => {
|
|
31
38
|
return el.querySelectorAll(`[${id}] ` + selector);
|
|
32
39
|
};
|
|
40
|
+
|
|
41
|
+
newProps['ref'].loadContent = async (content: string | VNode<any>) => {
|
|
42
|
+
if (typeof content === 'object' && content.type && content.props) {
|
|
43
|
+
// await mountComponents(el, content);
|
|
44
|
+
renderComponents(content.type, content.props);
|
|
45
|
+
await replaceInnerhtml(el, content.props._html.join(''));
|
|
46
|
+
bindAttributes(el, content.type, content.props);
|
|
47
|
+
bindLinks(el);
|
|
48
|
+
} else {
|
|
49
|
+
await replaceInnerhtml(el, content as string);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
33
52
|
};
|
|
@@ -1,265 +1,42 @@
|
|
|
1
1
|
import { bindAttributes } from './bind-attributes';
|
|
2
2
|
import { bindLinks } from './bind-links';
|
|
3
3
|
import { VNode } from '../jsx';
|
|
4
|
-
import { Logger } from '../lib/logger';
|
|
5
|
-
import { uniqueIdGenerator } from '../lib/unique-id';
|
|
6
|
-
import { processStyle } from './bind-styles';
|
|
4
|
+
// import { Logger } from '../lib/logger';
|
|
7
5
|
// import { bindPageResetEvent } from './page-reset-events';
|
|
8
6
|
import { replaceInnerhtml } from './replace-innerhtml';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
const logger = new Logger('mount-components');
|
|
12
|
-
export const domUniqueId = uniqueIdGenerator('l'); // l means label
|
|
13
|
-
// bindPageResetEvent(() => {
|
|
14
|
-
// // reset unique id
|
|
15
|
-
// domUniqueId(true);
|
|
16
|
-
// });
|
|
17
|
-
|
|
18
|
-
function renderChildren(html: string[], children: any) {
|
|
19
|
-
if (typeof children === 'string') {
|
|
20
|
-
html.push(children);
|
|
21
|
-
} else if (children === false || children === null || typeof children === 'undefined') {
|
|
22
|
-
// add nothing
|
|
23
|
-
} else if (typeof children === 'number' || typeof children === 'boolean') {
|
|
24
|
-
// true will be added
|
|
25
|
-
html.push(children.toString());
|
|
26
|
-
} else if (Array.isArray(children)) {
|
|
27
|
-
for (let i = 0; i < children.length; i++) {
|
|
28
|
-
const item = children[i];
|
|
29
|
-
renderChildren(html, item);
|
|
30
|
-
}
|
|
31
|
-
} else if (children.type && children.props) {
|
|
32
|
-
renderComponent(children.type, children.props);
|
|
33
|
-
html.push(...children.props._html);
|
|
34
|
-
children.props._html.length = 0;
|
|
35
|
-
} else {
|
|
36
|
-
logger.warn('Unexpected', children);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const selfClosingTags = [
|
|
41
|
-
'area',
|
|
42
|
-
'base',
|
|
43
|
-
'br',
|
|
44
|
-
'col',
|
|
45
|
-
'embed',
|
|
46
|
-
'hr',
|
|
47
|
-
'img',
|
|
48
|
-
'input',
|
|
49
|
-
'link',
|
|
50
|
-
'meta',
|
|
51
|
-
'param',
|
|
52
|
-
'source',
|
|
53
|
-
'track',
|
|
54
|
-
'wbr',
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
const genUniqueId = (props: any) => {
|
|
58
|
-
if (!props._id) {
|
|
59
|
-
props._id = domUniqueId();
|
|
60
|
-
}
|
|
61
|
-
return props._id;
|
|
62
|
-
};
|
|
63
|
-
// data-refid will be assigned with a ref.id
|
|
64
|
-
function renderAttribute(type: any, props: any, jsxNodes: any) {
|
|
65
|
-
const html = [];
|
|
66
|
-
// data-refid is used for nested components like this:
|
|
67
|
-
// <div class='class-name' ref={ref} ...>...
|
|
68
|
-
// <div data-refid={ref}>
|
|
69
|
-
// then data-refid can be located:
|
|
70
|
-
// ref.$(`.class-name[data-refid=${ref.id}]`)
|
|
71
|
-
if (props['data-refid'] && props['data-refid'].id) {
|
|
72
|
-
props['data-refid'] = props['data-refid'].id;
|
|
73
|
-
}
|
|
74
|
-
for (let i in props) {
|
|
75
|
-
if (i === 'ref') {
|
|
76
|
-
if (props[i]) {
|
|
77
|
-
props[i].id = genUniqueId(props);
|
|
78
|
-
html.push('data-ref');
|
|
79
|
-
}
|
|
80
|
-
} else if (!['children', 'key', '_result', '_html', '_id'].includes(i)) {
|
|
81
|
-
//, "_lb"
|
|
82
|
-
// , "value", "checked"
|
|
83
|
-
// style is a string, in-line style
|
|
84
|
-
if (i === 'style') {
|
|
85
|
-
if (typeof props[i] === 'object') {
|
|
86
|
-
let attrs = `${i}="`;
|
|
87
|
-
for (let j in props[i]) {
|
|
88
|
-
attrs += `${camelToHyphens(j)}:${props[i][j]};`;
|
|
89
|
-
}
|
|
90
|
-
attrs += `"`;
|
|
91
|
-
html.push(attrs);
|
|
92
|
-
} else {
|
|
93
|
-
html.push(`${i}="${props[i]}"`);
|
|
94
|
-
}
|
|
95
|
-
} else if (i === 'css') {
|
|
96
|
-
// css is a <style> tag, and is the first element in html
|
|
97
|
-
genUniqueId(props);
|
|
98
|
-
// props._lb = props._id;
|
|
99
|
-
} else if (i[0] === 'o' && i[1] === 'n') {
|
|
100
|
-
genUniqueId(props);
|
|
101
|
-
} else if (i === 'defaultChecked') {
|
|
102
|
-
if (props[i] === true || props[i] === 'checked') {
|
|
103
|
-
html.push(`checked="true"`);
|
|
104
|
-
}
|
|
105
|
-
} else if (i === 'readonly' || i === 'disabled' || i === 'selected' || i === 'checked') {
|
|
106
|
-
if (props[i] !== undefined && props[i] !== false && props[i] !== 'false') {
|
|
107
|
-
html.push(`${i}="${props[i]}"`);
|
|
108
|
-
}
|
|
109
|
-
} else if (i === 'class' || i === 'className') {
|
|
110
|
-
const className = props[i]
|
|
111
|
-
.split(' ')
|
|
112
|
-
.filter((item: string) => item && item !== '')
|
|
113
|
-
.join(' ');
|
|
114
|
-
html.push(`class="${className}"`);
|
|
115
|
-
} else if (i !== 'dangerouslySetInnerHTML') {
|
|
116
|
-
html.push(`${i}="${props[i]}"`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (props._id) {
|
|
121
|
-
// tag id will be after all attributes
|
|
122
|
-
html.push(props._id);
|
|
123
|
-
}
|
|
124
|
-
return html.join(' ');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// assign the same label to all children
|
|
128
|
-
// function assignLabels(label: string, children: any) {
|
|
129
|
-
// if (Array.isArray(children)) {
|
|
130
|
-
// for (let i = 0; i < children.length; i++) {
|
|
131
|
-
// const item = children[i];
|
|
132
|
-
// if (Array.isArray(item) || (item && item.type && item.props)) {
|
|
133
|
-
// assignLabels(label, item);
|
|
134
|
-
// }
|
|
135
|
-
// }
|
|
136
|
-
// } else if (children.type && children.props) {
|
|
137
|
-
// if (typeof children.type === 'string') {
|
|
138
|
-
// children.props._lb = label;
|
|
139
|
-
// }
|
|
140
|
-
// }
|
|
141
|
-
// }
|
|
142
|
-
|
|
143
|
-
// The result has only one element
|
|
144
|
-
const renderComponent = (type: any, props: any) => {
|
|
145
|
-
// logger.log("==================renderComponent", type);
|
|
146
|
-
if (Array.isArray(props)) {
|
|
147
|
-
const jsxNodes = { type: 'Fragment', props: { children: props } } as any;
|
|
148
|
-
return renderComponent(jsxNodes.type, jsxNodes.props);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
props._html = [];
|
|
152
|
-
if (typeof type === 'function') {
|
|
153
|
-
props._result = type.call(null, props);
|
|
154
|
-
if (props._result === null || props._result === undefined || props._result === false) {
|
|
155
|
-
// placeholder for sub components
|
|
156
|
-
props._result = { type: 'Fragment', props };
|
|
157
|
-
}
|
|
158
|
-
if (props._fragment_ref && props._result && props._result.props) {
|
|
159
|
-
// pass the ref to the sub Fragment tag
|
|
160
|
-
props._result.props.ref = props._fragment_ref;
|
|
161
|
-
props._result.props._id = genUniqueId(props._result.props);
|
|
162
|
-
}
|
|
163
|
-
// logger.log('==========props._result', props._result);
|
|
164
|
-
if (typeof props._result.type === 'function') {
|
|
165
|
-
renderComponent(props._result.type, props._result.props);
|
|
166
|
-
if (props._result.props._html) {
|
|
167
|
-
props._html.push(...props._result.props._html);
|
|
168
|
-
props._result.props._html.length = 0;
|
|
169
|
-
}
|
|
170
|
-
// function component doesn't have any attributes
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const newType = (props._result && props._result.type) || type;
|
|
175
|
-
const newProps = (props._result && props._result.props) || props;
|
|
176
|
-
if (newType === 'div' && newProps.class === 'answer-box') {
|
|
177
|
-
console.log('renderComponent', newType, newProps);
|
|
178
|
-
}
|
|
179
|
-
if (typeof newType === 'string') {
|
|
180
|
-
const attrs = renderAttribute(newType, newProps, { type, props });
|
|
181
|
-
if (selfClosingTags.includes(newType.toLowerCase())) {
|
|
182
|
-
props._html.push(`<${newType}${attrs ? ' ' : ''}${attrs} />`);
|
|
183
|
-
if (newProps['css']) {
|
|
184
|
-
console.warn(`ClosingTag [${newType}] doesn't support 'css', please use 'style' instead.`);
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
props._html.push(`<${newType}${attrs ? ' ' : ''}${attrs}>`);
|
|
188
|
-
|
|
189
|
-
if (newProps['css']) {
|
|
190
|
-
const cssText = processStyle(`[${newProps._id}]`, newProps['css']).join('');
|
|
191
|
-
props._html.push(`<style id="sty-${newProps._id}">${cssText}</style>`); // sty means style, and updateStyles has the same name
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (newProps.children) {
|
|
195
|
-
// if (newProps._lb) {
|
|
196
|
-
// assignLabels(newProps._lb, newProps.children);
|
|
197
|
-
// }
|
|
198
|
-
|
|
199
|
-
renderChildren(props._html, newProps.children);
|
|
200
|
-
} else if (newProps['dangerouslySetInnerHTML']) {
|
|
201
|
-
props._html.push(newProps['dangerouslySetInnerHTML']);
|
|
202
|
-
} else {
|
|
203
|
-
// single element
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
props._html.push(`</${newType}>`);
|
|
207
|
-
}
|
|
208
|
-
} else if (newType.name === 'Fragment') {
|
|
209
|
-
renderChildren(props._html, newProps.children);
|
|
210
|
-
} else {
|
|
211
|
-
logger.warn('Unknown type: ', type, props, newType, newProps);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
7
|
+
import { renderComponents } from './render-components';
|
|
214
8
|
|
|
9
|
+
// const logger = new Logger('mount-components');
|
|
215
10
|
export const mountComponents = async (selector: string | null | Element, jsxNodes: VNode<any>) => {
|
|
216
|
-
|
|
11
|
+
renderComponents(jsxNodes.type, jsxNodes.props);
|
|
217
12
|
const el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
218
13
|
if (el) {
|
|
219
|
-
// the parent node shouldn't have any styles
|
|
220
|
-
// el.replaceChildren(...);
|
|
221
|
-
// // keep <style id="sty-${newProps._id}">...</style>
|
|
222
|
-
// const firstDom = el.firstChild as Element;
|
|
223
|
-
// if (firstDom && firstDom.tagName === 'STYLE') {
|
|
224
|
-
// firstDom.parentNode?.removeChild(firstDom);
|
|
225
|
-
// }
|
|
226
|
-
|
|
227
14
|
// call unload before releace innerHTML
|
|
228
|
-
// el.innerHTML = jsxNodes.props._html.join('');
|
|
229
|
-
// remove <Fragment> and </Fragment>
|
|
230
|
-
const _html = jsxNodes.props._html;
|
|
231
|
-
if (_html[0] === '<Fragment>' && _html[_html.length - 1] === '</Fragment>') {
|
|
232
|
-
_html.shift();
|
|
233
|
-
_html.pop();
|
|
234
|
-
}
|
|
235
15
|
await replaceInnerhtml(el, jsxNodes.props._html.join(''));
|
|
236
16
|
|
|
237
|
-
// if (firstDom && firstDom.tagName === 'STYLE') {
|
|
238
|
-
// el.insertBefore(firstDom, el.firstChild);
|
|
239
|
-
// }
|
|
240
17
|
bindAttributes(el, jsxNodes.type, jsxNodes.props);
|
|
241
18
|
bindLinks(el);
|
|
242
19
|
}
|
|
243
20
|
};
|
|
244
21
|
|
|
245
|
-
// suggest to use HtmlVar.
|
|
246
|
-
export const mountSelfComponents = async (selector: string | null | Element, jsxNodes: VNode<any>) => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
};
|
|
22
|
+
// // suggest to use HtmlVar.
|
|
23
|
+
// export const mountSelfComponents = async (selector: string | null | Element, jsxNodes: VNode<any>) => {
|
|
24
|
+
// renderComponents(jsxNodes.type, jsxNodes.props);
|
|
25
|
+
// let el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
26
|
+
// if (el) {
|
|
27
|
+
// const parentNode = el.parentElement;
|
|
28
|
+
// // Can't do outerHTML directly because it will lose attributes
|
|
29
|
+
// const template = document.createElement('template');
|
|
30
|
+
// // template.innerHTML = jsxNodes.props._html.join("");
|
|
31
|
+
// // call unload before releace innerHTML
|
|
32
|
+
// await replaceInnerhtml(template, jsxNodes.props._html.join(''));
|
|
33
|
+
|
|
34
|
+
// // renderComponent should only have one element
|
|
35
|
+
// template.content.children.length > 1 &&
|
|
36
|
+
// console.error('renderComponent should only have one element: ', template.content.children.length);
|
|
37
|
+
// el.replaceWith(template.content.firstChild as Element);
|
|
38
|
+
// el = parentNode as Element;
|
|
39
|
+
// bindAttributes(el, jsxNodes.type, jsxNodes.props);
|
|
40
|
+
// bindLinks(el);
|
|
41
|
+
// }
|
|
42
|
+
// };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Logger } from '../lib/logger';
|
|
2
|
+
import { uniqueIdGenerator } from '../lib/unique-id';
|
|
3
|
+
import { processStyle } from './bind-styles';
|
|
4
|
+
// import { bindPageResetEvent } from './page-reset-events';
|
|
5
|
+
import { camelToHyphens } from './camel-to-hyphens';
|
|
6
|
+
|
|
7
|
+
const logger = new Logger('render-components');
|
|
8
|
+
export const domUniqueId = uniqueIdGenerator('l'); // l means label
|
|
9
|
+
// bindPageResetEvent(() => {
|
|
10
|
+
// // reset unique id
|
|
11
|
+
// domUniqueId(true);
|
|
12
|
+
// });
|
|
13
|
+
|
|
14
|
+
function renderChildren(html: string[], children: any) {
|
|
15
|
+
if (typeof children === 'string') {
|
|
16
|
+
html.push(children);
|
|
17
|
+
} else if (children === false || children === null || typeof children === 'undefined') {
|
|
18
|
+
// add nothing
|
|
19
|
+
} else if (typeof children === 'number' || typeof children === 'boolean') {
|
|
20
|
+
// true will be added
|
|
21
|
+
html.push(children.toString());
|
|
22
|
+
} else if (Array.isArray(children)) {
|
|
23
|
+
for (let i = 0; i < children.length; i++) {
|
|
24
|
+
const item = children[i];
|
|
25
|
+
renderChildren(html, item);
|
|
26
|
+
}
|
|
27
|
+
} else if (children.type && children.props) {
|
|
28
|
+
renderComponents(children.type, children.props);
|
|
29
|
+
html.push(...children.props._html);
|
|
30
|
+
children.props._html.length = 0;
|
|
31
|
+
} else {
|
|
32
|
+
logger.warn('Unexpected', children);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const selfClosingTags = [
|
|
37
|
+
'area',
|
|
38
|
+
'base',
|
|
39
|
+
'br',
|
|
40
|
+
'col',
|
|
41
|
+
'embed',
|
|
42
|
+
'hr',
|
|
43
|
+
'img',
|
|
44
|
+
'input',
|
|
45
|
+
'link',
|
|
46
|
+
'meta',
|
|
47
|
+
'param',
|
|
48
|
+
'source',
|
|
49
|
+
'track',
|
|
50
|
+
'wbr',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const genUniqueId = (props: any) => {
|
|
54
|
+
if (!props._id) {
|
|
55
|
+
props._id = domUniqueId();
|
|
56
|
+
}
|
|
57
|
+
return props._id;
|
|
58
|
+
};
|
|
59
|
+
// data-refid will be assigned with a ref.id
|
|
60
|
+
function renderAttribute(type: any, props: any, jsxNodes: any) {
|
|
61
|
+
const html = [];
|
|
62
|
+
// data-refid is used for nested components like this:
|
|
63
|
+
// <div class='class-name' ref={ref} ...>...
|
|
64
|
+
// <div data-refid={ref}>
|
|
65
|
+
// then data-refid can be located:
|
|
66
|
+
// ref.$(`.class-name[data-refid=${ref.id}]`)
|
|
67
|
+
if (props['data-refid'] && props['data-refid'].id) {
|
|
68
|
+
props['data-refid'] = props['data-refid'].id;
|
|
69
|
+
}
|
|
70
|
+
for (let i in props) {
|
|
71
|
+
if (i === 'ref') {
|
|
72
|
+
if (props[i]) {
|
|
73
|
+
props[i].id = genUniqueId(props);
|
|
74
|
+
html.push('data-ref');
|
|
75
|
+
}
|
|
76
|
+
} else if (!['children', 'key', '_result', '_html', '_id'].includes(i)) {
|
|
77
|
+
//, "_lb"
|
|
78
|
+
// , "value", "checked"
|
|
79
|
+
// style is a string, in-line style
|
|
80
|
+
if (i === 'style') {
|
|
81
|
+
if (typeof props[i] === 'object') {
|
|
82
|
+
let attrs = `${i}="`;
|
|
83
|
+
for (let j in props[i]) {
|
|
84
|
+
attrs += `${camelToHyphens(j)}:${props[i][j]};`;
|
|
85
|
+
}
|
|
86
|
+
attrs += `"`;
|
|
87
|
+
html.push(attrs);
|
|
88
|
+
} else {
|
|
89
|
+
html.push(`${i}="${props[i]}"`);
|
|
90
|
+
}
|
|
91
|
+
} else if (i === 'css') {
|
|
92
|
+
// css is a <style> tag, and is the first element in html
|
|
93
|
+
genUniqueId(props);
|
|
94
|
+
// props._lb = props._id;
|
|
95
|
+
} else if (i[0] === 'o' && i[1] === 'n') {
|
|
96
|
+
genUniqueId(props);
|
|
97
|
+
} else if (i === 'defaultChecked') {
|
|
98
|
+
if (props[i] === true || props[i] === 'checked') {
|
|
99
|
+
html.push(`checked="true"`);
|
|
100
|
+
}
|
|
101
|
+
} else if (i === 'readonly' || i === 'disabled' || i === 'selected' || i === 'checked') {
|
|
102
|
+
if (props[i] !== undefined && props[i] !== false && props[i] !== 'false') {
|
|
103
|
+
html.push(`${i}="${props[i]}"`);
|
|
104
|
+
}
|
|
105
|
+
} else if (i === 'class' || i === 'className') {
|
|
106
|
+
const className = props[i]
|
|
107
|
+
.split(' ')
|
|
108
|
+
.filter((item: string) => item && item !== '')
|
|
109
|
+
.join(' ');
|
|
110
|
+
html.push(`class="${className}"`);
|
|
111
|
+
} else if (i !== 'dangerouslySetInnerHTML') {
|
|
112
|
+
html.push(`${i}="${props[i]}"`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (props._id) {
|
|
117
|
+
// tag id will be after all attributes
|
|
118
|
+
html.push(props._id);
|
|
119
|
+
}
|
|
120
|
+
return html.join(' ');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// assign the same label to all children
|
|
124
|
+
// function assignLabels(label: string, children: any) {
|
|
125
|
+
// if (Array.isArray(children)) {
|
|
126
|
+
// for (let i = 0; i < children.length; i++) {
|
|
127
|
+
// const item = children[i];
|
|
128
|
+
// if (Array.isArray(item) || (item && item.type && item.props)) {
|
|
129
|
+
// assignLabels(label, item);
|
|
130
|
+
// }
|
|
131
|
+
// }
|
|
132
|
+
// } else if (children.type && children.props) {
|
|
133
|
+
// if (typeof children.type === 'string') {
|
|
134
|
+
// children.props._lb = label;
|
|
135
|
+
// }
|
|
136
|
+
// }
|
|
137
|
+
// }
|
|
138
|
+
|
|
139
|
+
// The result has only one element
|
|
140
|
+
export const renderComponents = (type: any, props: any) => {
|
|
141
|
+
// logger.log("==================renderComponent", type);
|
|
142
|
+
if (Array.isArray(props)) {
|
|
143
|
+
const jsxNodes = { type: 'Fragment', props: { children: props } } as any;
|
|
144
|
+
renderComponents(jsxNodes.type, jsxNodes.props);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
props._html = [];
|
|
149
|
+
if (typeof type === 'function') {
|
|
150
|
+
props._result = type.call(null, props);
|
|
151
|
+
if (props._result === null || props._result === undefined || props._result === false) {
|
|
152
|
+
// placeholder for sub components
|
|
153
|
+
props._result = { type: 'Fragment', props };
|
|
154
|
+
}
|
|
155
|
+
// if (props._fragment_ref && props._result && props._result.props) {
|
|
156
|
+
// // pass the ref to the sub Fragment tag
|
|
157
|
+
// props._result.props.ref = props._fragment_ref;
|
|
158
|
+
// props._result.props._id = genUniqueId(props._result.props);
|
|
159
|
+
// }
|
|
160
|
+
// logger.log('==========props._result', props._result);
|
|
161
|
+
if (typeof props._result.type === 'function') {
|
|
162
|
+
renderComponents(props._result.type, props._result.props);
|
|
163
|
+
if (props._result.props._html) {
|
|
164
|
+
props._html.push(...props._result.props._html);
|
|
165
|
+
props._result.props._html.length = 0;
|
|
166
|
+
}
|
|
167
|
+
// function component doesn't have any attributes
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const newType = (props._result && props._result.type) || type;
|
|
172
|
+
const newProps = (props._result && props._result.props) || props;
|
|
173
|
+
if (newType === 'div' && newProps.class === 'answer-box') {
|
|
174
|
+
console.log('renderComponent', newType, newProps);
|
|
175
|
+
}
|
|
176
|
+
if (typeof newType === 'string') {
|
|
177
|
+
const attrs = renderAttribute(newType, newProps, { type, props });
|
|
178
|
+
if (selfClosingTags.includes(newType.toLowerCase())) {
|
|
179
|
+
// for Fragment, only needs this tag when Ref is assigned
|
|
180
|
+
if (newType !== 'Fragment' || newProps.ref) {
|
|
181
|
+
props._html.push(`<${newType}${attrs ? ' ' : ''}${attrs} />`);
|
|
182
|
+
}
|
|
183
|
+
if (newProps['css']) {
|
|
184
|
+
console.warn(`ClosingTag [${newType}] doesn't support 'css', please use 'style' instead.`);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
if (newType !== 'Fragment' || newProps.ref) {
|
|
188
|
+
props._html.push(`<${newType}${attrs ? ' ' : ''}${attrs}>`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (newProps['css']) {
|
|
192
|
+
const cssText = processStyle(`[${newProps._id}]`, newProps['css']).join('');
|
|
193
|
+
props._html.push(`<style id="sty-${newProps._id}">${cssText}</style>`); // sty means style, and updateStyles has the same name
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (newProps.children) {
|
|
197
|
+
// if (newProps._lb) {
|
|
198
|
+
// assignLabels(newProps._lb, newProps.children);
|
|
199
|
+
// }
|
|
200
|
+
|
|
201
|
+
renderChildren(props._html, newProps.children);
|
|
202
|
+
} else if (newProps['dangerouslySetInnerHTML']) {
|
|
203
|
+
props._html.push(newProps['dangerouslySetInnerHTML']);
|
|
204
|
+
} else {
|
|
205
|
+
// single element
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (newType !== 'Fragment' || newProps.ref) {
|
|
209
|
+
props._html.push(`</${newType}>`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else if (newType.name === 'Fragment') {
|
|
213
|
+
renderChildren(props._html, newProps.children);
|
|
214
|
+
} else {
|
|
215
|
+
logger.warn('Unknown type: ', type, props, newType, newProps);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export const replaceInnerhtml = async (el: Element, newHtml: string) => {
|
|
2
|
+
// keep <style id="sty-${newProps._id}">...</style>
|
|
3
|
+
const firstDom = el.firstChild as Element;
|
|
4
|
+
if (firstDom && firstDom.tagName === 'STYLE') {
|
|
5
|
+
firstDom.parentNode?.removeChild(firstDom);
|
|
6
|
+
}
|
|
7
|
+
|
|
2
8
|
const promises: Promise<void>[] = [];
|
|
3
9
|
el.querySelectorAll('[data-ref]').forEach((child: any) => {
|
|
4
10
|
if (child._lj && child._lj.onUnload) {
|
|
@@ -7,4 +13,8 @@ export const replaceInnerhtml = async (el: Element, newHtml: string) => {
|
|
|
7
13
|
});
|
|
8
14
|
await Promise.all(promises);
|
|
9
15
|
el.innerHTML = newHtml;
|
|
16
|
+
|
|
17
|
+
if (firstDom && firstDom.tagName === 'STYLE') {
|
|
18
|
+
el.insertBefore(firstDom, el.firstChild);
|
|
19
|
+
}
|
|
10
20
|
};
|
package/src/jsx.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type RefProps = {
|
|
|
25
25
|
onUnload?: (el: Element) => Promise<void>;
|
|
26
26
|
$?: any; // (selector: string) => undefined | Element,
|
|
27
27
|
$all?: any; // (selector: string) => undefined | Element,
|
|
28
|
+
loadContent?: (content: string | VNode<any>) => Promise<void>;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
export interface ClassAttributes<T> extends Attributes {
|
package/src/lib/cookie.ts
CHANGED
|
@@ -39,3 +39,6 @@ export const clearCookie = (name: string, path?: string, domain?: string, secure
|
|
|
39
39
|
(domain ? ';domain=' + domain : '') +
|
|
40
40
|
(secure ? ';secure' : '');
|
|
41
41
|
};
|
|
42
|
+
|
|
43
|
+
// convinent but not good for tree-shaking
|
|
44
|
+
export const cookie = { set: setCookie, get: getCookie, clear: clearCookie };
|