@viewfly/platform-browser 0.3.1 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import { JSXNode, Application, Config } from '@viewfly/core';
1
+ import { JSXNode, Application, Config, NativeNode } from '@viewfly/core';
2
2
  /**
3
3
  * 创建一个 Viewfly 实例
4
4
  * @param root 应用根节点
@@ -13,5 +13,5 @@ import { JSXNode, Application, Config } from '@viewfly/core';
13
13
  * app.render() // 手动更新视图
14
14
  * ```
15
15
  */
16
- export declare function createApp(root: JSXNode, autoUpdate?: boolean): Application;
17
- export declare function createApp(root: JSXNode, config?: Omit<Config, 'nativeRenderer' | 'root'>): Application;
16
+ export declare function createApp<T extends NativeNode>(root: JSXNode, autoUpdate?: boolean): Application<T>;
17
+ export declare function createApp<T extends NativeNode>(root: JSXNode, config?: Partial<Omit<Config, 'root'>>): Application<T>;
@@ -10,7 +10,6 @@ export declare class DomRenderer extends NativeRenderer<HTMLElement, Text> {
10
10
  propMap: Record<string, Record<string, string>>;
11
11
  createElement(name: string, isSvg: boolean): HTMLElement;
12
12
  createTextNode(textContent: string): Text;
13
- appendChild(parent: HTMLElement, newChild: any): void;
14
13
  prependChild(parent: HTMLElement, newChild: HTMLElement | Text): void;
15
14
  insertAfter(newNode: HTMLElement | Text, ref: HTMLElement | Text): void;
16
15
  remove(node: HTMLElement | Text): void;
@@ -23,4 +22,5 @@ export declare class DomRenderer extends NativeRenderer<HTMLElement, Text> {
23
22
  unListen(node: HTMLElement, type: string, callback: (ev: any) => any): void;
24
23
  syncTextContent(target: Text, content: string): void;
25
24
  private insertBefore;
25
+ private appendChild;
26
26
  }
@@ -0,0 +1,52 @@
1
+ import { NativeRenderer } from '@viewfly/core';
2
+ export declare class VDOMElement {
3
+ name: string;
4
+ props: Map<string, any>;
5
+ children: Array<VDOMElement | VDomText>;
6
+ style: Map<string, any>;
7
+ className: string;
8
+ parent: VDOMElement | null;
9
+ constructor(name: string);
10
+ }
11
+ export declare class VDomText {
12
+ text: string;
13
+ parent: VDOMElement | null;
14
+ constructor(text: string);
15
+ }
16
+ /**
17
+ * 用于生成模拟轻量 DOM 节点的渲染器
18
+ */
19
+ export declare class HTMLRenderer extends NativeRenderer<VDOMElement, VDomText> {
20
+ createElement(name: string): VDOMElement;
21
+ createTextNode(textContent: string): VDomText;
22
+ setProperty(node: VDOMElement, key: string, value: any): void;
23
+ prependChild(parent: VDOMElement, newChild: VDOMElement | VDomText): void;
24
+ removeProperty(node: VDOMElement, key: string): void;
25
+ setStyle(target: VDOMElement, key: string, value: any): void;
26
+ removeStyle(target: VDOMElement, key: string): void;
27
+ setClass(target: VDOMElement, value: string): void;
28
+ listen(): void;
29
+ unListen(): void;
30
+ remove(node: VDOMElement | VDomText): void;
31
+ syncTextContent(target: VDomText, content: string): void;
32
+ insertAfter(newNode: VDOMElement | VDomText, ref: VDOMElement | VDomText): void;
33
+ }
34
+ /**
35
+ * 轻量 DOM 转换为 HTML 字符串的转换器
36
+ */
37
+ export declare class OutputTranslator {
38
+ static singleTags: string[];
39
+ static simpleXSSFilter: {
40
+ text(text: string): string;
41
+ attrName(text: string): string;
42
+ attrValue(text: string): string;
43
+ };
44
+ private singleTagTest;
45
+ /**
46
+ * 将虚拟 DOM 转换为 HTML 字符串的方法
47
+ * @param vDom 虚拟 DOM 节点
48
+ */
49
+ transform(vDom: VDOMElement): string;
50
+ private vDomToHTMLString;
51
+ private replaceEmpty;
52
+ }
@@ -12,12 +12,6 @@ class DomRenderer extends NativeRenderer {
12
12
  }
13
13
  };
14
14
  }
15
- // valueProps: Record<string, string[]> = {
16
- // input: ['value'],
17
- // option: ['value'],
18
- // video: ['src'],
19
- // audio: ['src']
20
- // }
21
15
  createElement(name, isSvg) {
22
16
  if (isSvg) {
23
17
  return document.createElementNS(DomRenderer.NAMESPACES.svg, name);
@@ -27,9 +21,6 @@ class DomRenderer extends NativeRenderer {
27
21
  createTextNode(textContent) {
28
22
  return document.createTextNode(textContent);
29
23
  }
30
- appendChild(parent, newChild) {
31
- parent.appendChild(newChild);
32
- }
33
24
  prependChild(parent, newChild) {
34
25
  parent.prepend(newChild);
35
26
  }
@@ -105,6 +96,9 @@ class DomRenderer extends NativeRenderer {
105
96
  insertBefore(newNode, ref) {
106
97
  ref.parentNode.insertBefore(newNode, ref);
107
98
  }
99
+ appendChild(parent, newChild) {
100
+ parent.appendChild(newChild);
101
+ }
108
102
  }
109
103
  DomRenderer.NAMESPACES = {
110
104
  svg: 'http://www.w3.org/2000/svg',
@@ -122,7 +116,7 @@ function createApp(root, config = true) {
122
116
  else if (typeof config === 'object') {
123
117
  Object.assign(c, config);
124
118
  }
125
- return viewfly(Object.assign(Object.assign({}, c), { root, nativeRenderer: new DomRenderer() }));
119
+ return viewfly(Object.assign(Object.assign({}, c), { root, nativeRenderer: c.nativeRenderer || new DomRenderer() }));
126
120
  }
127
121
 
128
122
  const forkErrorFn = makeError('fork');
@@ -148,4 +142,171 @@ function fork(root, config = true) {
148
142
  return app;
149
143
  }
150
144
 
151
- export { DomRenderer, createApp, fork };
145
+ class VDOMElement {
146
+ constructor(name) {
147
+ this.name = name;
148
+ this.props = new Map();
149
+ this.children = [];
150
+ this.style = new Map();
151
+ this.className = '';
152
+ this.parent = null;
153
+ }
154
+ }
155
+ class VDomText {
156
+ constructor(text) {
157
+ this.text = text;
158
+ this.parent = null;
159
+ }
160
+ }
161
+ /**
162
+ * 用于生成模拟轻量 DOM 节点的渲染器
163
+ */
164
+ class HTMLRenderer extends NativeRenderer {
165
+ createElement(name) {
166
+ return new VDOMElement(name);
167
+ }
168
+ createTextNode(textContent) {
169
+ return new VDomText(textContent);
170
+ }
171
+ setProperty(node, key, value) {
172
+ node.props.set(key, value);
173
+ }
174
+ prependChild(parent, newChild) {
175
+ parent.children.unshift(newChild);
176
+ newChild.parent = parent;
177
+ }
178
+ removeProperty(node, key) {
179
+ node.props.delete(key);
180
+ }
181
+ setStyle(target, key, value) {
182
+ target.style.set(key, value);
183
+ }
184
+ removeStyle(target, key) {
185
+ target.style.delete(key);
186
+ }
187
+ setClass(target, value) {
188
+ target.className = value;
189
+ }
190
+ listen() {
191
+ //
192
+ }
193
+ unListen() {
194
+ //
195
+ }
196
+ remove(node) {
197
+ if (node.parent) {
198
+ const i = node.parent.children.indexOf(node);
199
+ if (i > -1) {
200
+ node.parent.children.splice(i, 1);
201
+ }
202
+ }
203
+ node.parent = null;
204
+ }
205
+ syncTextContent(target, content) {
206
+ target.text = content;
207
+ }
208
+ insertAfter(newNode, ref) {
209
+ const parent = ref.parent;
210
+ if (parent) {
211
+ const i = parent.children.indexOf(ref);
212
+ if (i > -1) {
213
+ newNode.parent = parent;
214
+ parent.children.splice(i + 1, 0, newNode);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ /**
220
+ * 轻量 DOM 转换为 HTML 字符串的转换器
221
+ */
222
+ class OutputTranslator {
223
+ constructor() {
224
+ this.singleTagTest = new RegExp(`^(${OutputTranslator.singleTags.join('|')})$`, 'i');
225
+ }
226
+ /**
227
+ * 将虚拟 DOM 转换为 HTML 字符串的方法
228
+ * @param vDom 虚拟 DOM 节点
229
+ */
230
+ transform(vDom) {
231
+ return vDom.children.map(child => {
232
+ return this.vDomToHTMLString(child);
233
+ }).join('');
234
+ }
235
+ vDomToHTMLString(vDom) {
236
+ const xssFilter = OutputTranslator.simpleXSSFilter;
237
+ if (vDom instanceof VDomText) {
238
+ return this.replaceEmpty(xssFilter.text(vDom.text), '&nbsp;');
239
+ }
240
+ const styles = Array.from(vDom.style.keys()).filter(key => {
241
+ const v = vDom.style.get(key);
242
+ return !(v === undefined || v === null || v === '');
243
+ }).map(key => {
244
+ const k = key.replace(/(?=[A-Z])/g, '-').toLowerCase();
245
+ return xssFilter.attrValue(`${k}:${vDom.style.get(key)}`);
246
+ }).join(';');
247
+ const attrs = Array.from(vDom.props.keys()).filter(key => key !== 'ref' && vDom.props.get(key) !== false).map(k => {
248
+ const key = xssFilter.attrName(k);
249
+ const value = vDom.props.get(k);
250
+ return (value === true ? `${key}` : `${key}="${xssFilter.attrValue(`${value}`)}"`);
251
+ });
252
+ if (styles) {
253
+ attrs.push(`style="${styles}"`);
254
+ }
255
+ if (vDom.className) {
256
+ attrs.push(`class="${xssFilter.attrValue(vDom.className)}"`);
257
+ }
258
+ let attrStr = attrs.join(' ');
259
+ attrStr = attrStr ? ' ' + attrStr : '';
260
+ if (this.singleTagTest.test(vDom.name)) {
261
+ return `<${vDom.name}${attrStr}>`;
262
+ }
263
+ const childHTML = vDom.children.map(child => {
264
+ return this.vDomToHTMLString(child);
265
+ }).join('');
266
+ return [
267
+ `<${vDom.name}${attrStr}>`,
268
+ childHTML,
269
+ `</${vDom.name}>`
270
+ ].join('');
271
+ }
272
+ replaceEmpty(s, target) {
273
+ return s.replace(/\s\s+/g, str => {
274
+ return ' ' + Array.from({
275
+ length: str.length - 1
276
+ }).fill(target).join('');
277
+ }).replace(/^\s|\s$/g, target);
278
+ }
279
+ }
280
+ OutputTranslator.singleTags = 'br,img,hr'.split(',');
281
+ OutputTranslator.simpleXSSFilter = {
282
+ text(text) {
283
+ return text.replace(/[><&]/g, str => {
284
+ return {
285
+ '<': '&lt;',
286
+ '>': '&gt;',
287
+ '&': '&amp;'
288
+ }[str];
289
+ });
290
+ },
291
+ attrName(text) {
292
+ return text.replace(/[><"'&]/g, str => {
293
+ return {
294
+ '<': '&lt;',
295
+ '>': '&gt;',
296
+ '"': '&quot;',
297
+ '\'': '&#x27;',
298
+ '&': '&amp;'
299
+ }[str];
300
+ });
301
+ },
302
+ attrValue(text) {
303
+ return text.replace(/["']/g, str => {
304
+ return {
305
+ '"': '&quot;',
306
+ '\'': '&#x27;'
307
+ }[str];
308
+ });
309
+ }
310
+ };
311
+
312
+ export { DomRenderer, HTMLRenderer, OutputTranslator, VDOMElement, VDomText, createApp, fork };
package/bundles/index.js CHANGED
@@ -14,12 +14,6 @@ class DomRenderer extends core.NativeRenderer {
14
14
  }
15
15
  };
16
16
  }
17
- // valueProps: Record<string, string[]> = {
18
- // input: ['value'],
19
- // option: ['value'],
20
- // video: ['src'],
21
- // audio: ['src']
22
- // }
23
17
  createElement(name, isSvg) {
24
18
  if (isSvg) {
25
19
  return document.createElementNS(DomRenderer.NAMESPACES.svg, name);
@@ -29,9 +23,6 @@ class DomRenderer extends core.NativeRenderer {
29
23
  createTextNode(textContent) {
30
24
  return document.createTextNode(textContent);
31
25
  }
32
- appendChild(parent, newChild) {
33
- parent.appendChild(newChild);
34
- }
35
26
  prependChild(parent, newChild) {
36
27
  parent.prepend(newChild);
37
28
  }
@@ -107,6 +98,9 @@ class DomRenderer extends core.NativeRenderer {
107
98
  insertBefore(newNode, ref) {
108
99
  ref.parentNode.insertBefore(newNode, ref);
109
100
  }
101
+ appendChild(parent, newChild) {
102
+ parent.appendChild(newChild);
103
+ }
110
104
  }
111
105
  DomRenderer.NAMESPACES = {
112
106
  svg: 'http://www.w3.org/2000/svg',
@@ -124,7 +118,7 @@ function createApp(root, config = true) {
124
118
  else if (typeof config === 'object') {
125
119
  Object.assign(c, config);
126
120
  }
127
- return core.viewfly(Object.assign(Object.assign({}, c), { root, nativeRenderer: new DomRenderer() }));
121
+ return core.viewfly(Object.assign(Object.assign({}, c), { root, nativeRenderer: c.nativeRenderer || new DomRenderer() }));
128
122
  }
129
123
 
130
124
  const forkErrorFn = core.makeError('fork');
@@ -150,6 +144,177 @@ function fork(root, config = true) {
150
144
  return app;
151
145
  }
152
146
 
147
+ class VDOMElement {
148
+ constructor(name) {
149
+ this.name = name;
150
+ this.props = new Map();
151
+ this.children = [];
152
+ this.style = new Map();
153
+ this.className = '';
154
+ this.parent = null;
155
+ }
156
+ }
157
+ class VDomText {
158
+ constructor(text) {
159
+ this.text = text;
160
+ this.parent = null;
161
+ }
162
+ }
163
+ /**
164
+ * 用于生成模拟轻量 DOM 节点的渲染器
165
+ */
166
+ class HTMLRenderer extends core.NativeRenderer {
167
+ createElement(name) {
168
+ return new VDOMElement(name);
169
+ }
170
+ createTextNode(textContent) {
171
+ return new VDomText(textContent);
172
+ }
173
+ setProperty(node, key, value) {
174
+ node.props.set(key, value);
175
+ }
176
+ prependChild(parent, newChild) {
177
+ parent.children.unshift(newChild);
178
+ newChild.parent = parent;
179
+ }
180
+ removeProperty(node, key) {
181
+ node.props.delete(key);
182
+ }
183
+ setStyle(target, key, value) {
184
+ target.style.set(key, value);
185
+ }
186
+ removeStyle(target, key) {
187
+ target.style.delete(key);
188
+ }
189
+ setClass(target, value) {
190
+ target.className = value;
191
+ }
192
+ listen() {
193
+ //
194
+ }
195
+ unListen() {
196
+ //
197
+ }
198
+ remove(node) {
199
+ if (node.parent) {
200
+ const i = node.parent.children.indexOf(node);
201
+ if (i > -1) {
202
+ node.parent.children.splice(i, 1);
203
+ }
204
+ }
205
+ node.parent = null;
206
+ }
207
+ syncTextContent(target, content) {
208
+ target.text = content;
209
+ }
210
+ insertAfter(newNode, ref) {
211
+ const parent = ref.parent;
212
+ if (parent) {
213
+ const i = parent.children.indexOf(ref);
214
+ if (i > -1) {
215
+ newNode.parent = parent;
216
+ parent.children.splice(i + 1, 0, newNode);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ /**
222
+ * 轻量 DOM 转换为 HTML 字符串的转换器
223
+ */
224
+ class OutputTranslator {
225
+ constructor() {
226
+ this.singleTagTest = new RegExp(`^(${OutputTranslator.singleTags.join('|')})$`, 'i');
227
+ }
228
+ /**
229
+ * 将虚拟 DOM 转换为 HTML 字符串的方法
230
+ * @param vDom 虚拟 DOM 节点
231
+ */
232
+ transform(vDom) {
233
+ return vDom.children.map(child => {
234
+ return this.vDomToHTMLString(child);
235
+ }).join('');
236
+ }
237
+ vDomToHTMLString(vDom) {
238
+ const xssFilter = OutputTranslator.simpleXSSFilter;
239
+ if (vDom instanceof VDomText) {
240
+ return this.replaceEmpty(xssFilter.text(vDom.text), '&nbsp;');
241
+ }
242
+ const styles = Array.from(vDom.style.keys()).filter(key => {
243
+ const v = vDom.style.get(key);
244
+ return !(v === undefined || v === null || v === '');
245
+ }).map(key => {
246
+ const k = key.replace(/(?=[A-Z])/g, '-').toLowerCase();
247
+ return xssFilter.attrValue(`${k}:${vDom.style.get(key)}`);
248
+ }).join(';');
249
+ const attrs = Array.from(vDom.props.keys()).filter(key => key !== 'ref' && vDom.props.get(key) !== false).map(k => {
250
+ const key = xssFilter.attrName(k);
251
+ const value = vDom.props.get(k);
252
+ return (value === true ? `${key}` : `${key}="${xssFilter.attrValue(`${value}`)}"`);
253
+ });
254
+ if (styles) {
255
+ attrs.push(`style="${styles}"`);
256
+ }
257
+ if (vDom.className) {
258
+ attrs.push(`class="${xssFilter.attrValue(vDom.className)}"`);
259
+ }
260
+ let attrStr = attrs.join(' ');
261
+ attrStr = attrStr ? ' ' + attrStr : '';
262
+ if (this.singleTagTest.test(vDom.name)) {
263
+ return `<${vDom.name}${attrStr}>`;
264
+ }
265
+ const childHTML = vDom.children.map(child => {
266
+ return this.vDomToHTMLString(child);
267
+ }).join('');
268
+ return [
269
+ `<${vDom.name}${attrStr}>`,
270
+ childHTML,
271
+ `</${vDom.name}>`
272
+ ].join('');
273
+ }
274
+ replaceEmpty(s, target) {
275
+ return s.replace(/\s\s+/g, str => {
276
+ return ' ' + Array.from({
277
+ length: str.length - 1
278
+ }).fill(target).join('');
279
+ }).replace(/^\s|\s$/g, target);
280
+ }
281
+ }
282
+ OutputTranslator.singleTags = 'br,img,hr'.split(',');
283
+ OutputTranslator.simpleXSSFilter = {
284
+ text(text) {
285
+ return text.replace(/[><&]/g, str => {
286
+ return {
287
+ '<': '&lt;',
288
+ '>': '&gt;',
289
+ '&': '&amp;'
290
+ }[str];
291
+ });
292
+ },
293
+ attrName(text) {
294
+ return text.replace(/[><"'&]/g, str => {
295
+ return {
296
+ '<': '&lt;',
297
+ '>': '&gt;',
298
+ '"': '&quot;',
299
+ '\'': '&#x27;',
300
+ '&': '&amp;'
301
+ }[str];
302
+ });
303
+ },
304
+ attrValue(text) {
305
+ return text.replace(/["']/g, str => {
306
+ return {
307
+ '"': '&quot;',
308
+ '\'': '&#x27;'
309
+ }[str];
310
+ });
311
+ }
312
+ };
313
+
153
314
  exports.DomRenderer = DomRenderer;
315
+ exports.HTMLRenderer = HTMLRenderer;
316
+ exports.OutputTranslator = OutputTranslator;
317
+ exports.VDOMElement = VDOMElement;
318
+ exports.VDomText = VDomText;
154
319
  exports.createApp = createApp;
155
320
  exports.fork = fork;
@@ -1,4 +1,5 @@
1
1
  export * from './create-app';
2
2
  export * from './fork';
3
+ export * from './html-renderer';
3
4
  export * from './dom-renderer';
4
5
  export * from './jsx-dom';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viewfly/platform-browser",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "This project is used to enable the Viewfly framework to run in a browser.",
5
5
  "main": "./bundles/index.js",
6
6
  "module": "./bundles/index.esm.js",
@@ -12,7 +12,7 @@
12
12
  "license": "MIT",
13
13
  "keywords": [],
14
14
  "dependencies": {
15
- "@viewfly/core": "^0.3.1",
15
+ "@viewfly/core": "^0.4.0",
16
16
  "csstype": "^3.1.2"
17
17
  },
18
18
  "devDependencies": {
@@ -33,5 +33,5 @@
33
33
  "bugs": {
34
34
  "url": "https://github.com/viewfly/viewfly.git/issues"
35
35
  },
36
- "gitHead": "b66ca589f7662cd518fc2e5955b3e3ff9de83f94"
36
+ "gitHead": "d14b3cd0247a07f72519745933c3070f12adbfa1"
37
37
  }