native-document 1.0.108 → 1.0.110

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.
@@ -3,150 +3,309 @@ import TemplateBinding from "./TemplateBinding";
3
3
 
4
4
  const cloneBindingsDataCache = new WeakMap();
5
5
 
6
+ const OPERATIONS = {
7
+ HYDRATE_TEXT: 1,
8
+ ATTACH_METHOD: 2,
9
+ HYDRATE_ATTRIBUTES: 3,
10
+ HYDRATE_FULL: 4,
11
+ };
6
12
 
7
- const bindAttributes = (node, bindDingData, data) => {
8
- let attributes = null;
9
- if(bindDingData.attributes) {
10
- attributes = {};
11
- for (const attr in bindDingData.attributes) {
12
- attributes[attr] = bindDingData.attributes[attr].apply(null, data);
13
- }
13
+ const hydrateFull = (node, bindDingData, data) => {
14
+ const cacheAttributes = bindDingData._cache;
15
+
16
+ for(let i = 0, length = bindDingData._flatAttributesLength; i < length; i++) {
17
+ const attr = bindDingData._flatAttributes[i];
18
+ cacheAttributes[attr.name] = attr.value.apply(null, data);
14
19
  }
15
20
 
16
- if(bindDingData.classes) {
17
- attributes = attributes || {};
18
- attributes.class = {};
19
- for (const className in bindDingData.classes) {
20
- attributes.class[className] = bindDingData.classes[className].apply(null, data);
21
- }
21
+ for(let i = 0, length = bindDingData._flatDynamiqueLength; i < length; i++) {
22
+ const dyn = bindDingData._flatDynamique[i];
23
+ cacheAttributes[dyn.name][dyn.key] = dyn.value.apply(null, data);
22
24
  }
23
25
 
24
- if(bindDingData.styles) {
25
- attributes = attributes || {};
26
- attributes.style = {};
27
- for (const property in bindDingData.styles) {
28
- attributes.style[property] = bindDingData.styles[property].apply(null, data);
29
- }
26
+ ElementCreator.processAttributesDirect(node, cacheAttributes);
27
+ return true;
28
+ };
29
+
30
+ const hydrateDynamic = (node, bindDingData, data) => {
31
+ const cacheAttributes = bindDingData._cache;
32
+
33
+ for(let i = 0, length = bindDingData._flatDynamiqueLength; i < length; i++) {
34
+ const dyn = bindDingData._flatDynamique[i];
35
+ cacheAttributes[dyn.name][dyn.key] = dyn.value.apply(null, data);
30
36
  }
31
37
 
32
- if(attributes) {
33
- ElementCreator.processAttributes(node, attributes);
34
- return true;
38
+ ElementCreator.processClassAttribute(node, cacheAttributes.class);
39
+ ElementCreator.processStyleAttribute(node, cacheAttributes.style);
40
+ return true;
41
+ };
42
+
43
+ const hydrateClassAttribute = (node, bindDingData, data) => {
44
+ const classAttributes = bindDingData._cache.class;
45
+
46
+ for(let i = 0, length = bindDingData._flatDynamiqueLength; i < length; i++) {
47
+ const dyn = bindDingData._flatDynamique[i];
48
+ classAttributes[dyn.key] = dyn.value.apply(null, data);
35
49
  }
36
50
 
37
- return null;
51
+ ElementCreator.processClassAttribute(node, classAttributes);
52
+ return true;
53
+ };
54
+
55
+ const hydrateStyleAttribute = (node, bindDingData, data) => {
56
+ const styleAttributes = bindDingData._cache;
57
+
58
+ for(let i = 0, length = bindDingData._flatDynamiqueLength; i < length; i++) {
59
+ const dyn = bindDingData._flatDynamique[i];
60
+ styleAttributes[dyn.key] = dyn.value.apply(null, data);
61
+ }
62
+
63
+ ElementCreator.processStyleAttribute(node, styleAttributes);
64
+ return true;
65
+ };
66
+
67
+ const hydrateAttributes = (node, bindDingData, data) => {
68
+ const cacheAttributes = bindDingData._cache;
69
+
70
+ for(let i = 0, length = bindDingData._flatAttributesLength; i < length; i++) {
71
+ const attr = bindDingData._flatAttributes[i];
72
+ cacheAttributes[attr.name] = attr.value.apply(null, data);
73
+ }
74
+
75
+ ElementCreator.processAttributesDirect(node, cacheAttributes);
76
+ return true;
77
+ };
78
+
79
+ const getHydrator = (bindDingData) => {
80
+ if(!bindDingData._cache) {
81
+ return noUpdate;
82
+ }
83
+ if(bindDingData._flatAttributesLength && bindDingData._flatDynamiqueLength) {
84
+ return hydrateFull;
85
+ }
86
+ if(bindDingData._flatAttributesLength) {
87
+ return hydrateAttributes;
88
+ }
89
+ if(bindDingData._hasClassAttribute && bindDingData._hasStyleAttribute) {
90
+ return hydrateDynamic;
91
+ }
92
+ if(bindDingData._hasClassAttribute) {
93
+ return hydrateClassAttribute;
94
+ }
95
+ return hydrateStyleAttribute;
38
96
  };
39
97
 
40
98
  const $hydrateFn = function(hydrateFunction, targetType, element, property) {
41
99
  if(!cloneBindingsDataCache.has(element)) {
42
- // { classes, styles, attributes, value, attach }
43
- cloneBindingsDataCache.set(element, {});
100
+ cloneBindingsDataCache.set(element, { attach: [] });
44
101
  }
45
102
  const hydrationState = cloneBindingsDataCache.get(element);
103
+
46
104
  if(targetType === 'value') {
47
105
  hydrationState.value = hydrateFunction;
48
106
  return;
49
107
  }
108
+ if(targetType === 'attach') {
109
+ hydrationState.attach = hydrationState.attach || [];
110
+ hydrationState.attach.push({ methodName: property, fn: hydrateFunction});
111
+ return;
112
+ }
50
113
  hydrationState[targetType] = hydrationState[targetType] || {};
51
114
  hydrationState[targetType][property] = hydrateFunction;
52
- }
115
+ };
53
116
 
54
117
  const bindAttachMethods = (node, bindDingData, data) => {
55
- if(!bindDingData.attach) {
56
- return null;
57
- }
58
- for(const methodName in bindDingData.attach) {
59
- node.nd[methodName](function(...args) {
60
- bindDingData.attach[methodName].apply(this, [...args, ...data]);
118
+ for(let i = 0, length = bindDingData._attachLength; i < length; i++) {
119
+ const method = bindDingData.attach[i];
120
+ node.nd[method.methodName](function() {
121
+ method.fn.call(this, ...data, ...arguments);
61
122
  });
62
123
  }
63
124
  };
64
125
 
65
126
 
66
- const applyBindingTreePath = (root, target, data, path) => {
67
- if(path.fn) {
68
- if(typeof path.fn === 'string') {
69
- ElementCreator.bindTextNode(target, data[0][path.fn]);
127
+ const $applyBindingParents = [];
128
+ const pathProcess = (target, path, data) => {
129
+ if(path.operation === OPERATIONS.HYDRATE_TEXT) {
130
+ const value = path.value;
131
+ ElementCreator.bindTextNode(target, path.isString ? data[0][value] : value.apply(null, data));
132
+ return;
133
+ }
134
+ if(path.operation === OPERATIONS.ATTACH_METHOD || path.operation === OPERATIONS.HYDRATE_FULL) {
135
+ const bindingData = path.bindingData;
136
+ for(let i = 0, length = bindingData._attachLength; i < length; i++) {
137
+ const method = bindingData.attach[i];
138
+ target.nd[method.methodName](function() {
139
+ method.fn.call(this, ...data, ...arguments);
140
+ });
70
141
  }
71
- else {
72
- path.fn(data, target, root);
142
+ }
143
+ if(path.operation === OPERATIONS.HYDRATE_ATTRIBUTES || path.operation === OPERATIONS.HYDRATE_FULL) {
144
+ path.hydrator(target, path.bindingData, data);
145
+ }
146
+ };
147
+
148
+ const applyBindingTreePath = (root, data, paths, pathSize) => {
149
+ const rootPath = paths[pathSize];
150
+ $applyBindingParents[rootPath.id] = root;
151
+ pathProcess(root, rootPath, data);
152
+
153
+ let target = null, path = null;
154
+ for(let i = 0; i < pathSize; i++) {
155
+ path = paths[i];
156
+ target = $applyBindingParents[path.parentId].childNodes[path.index];
157
+ $applyBindingParents[path.id] = target;
158
+
159
+ pathProcess(target, path, data);
160
+ }
161
+
162
+ for (let i = 0; i <= pathSize; i++) {
163
+ $applyBindingParents[i] = null;
164
+ }
165
+ };
166
+ const buildAttributesCache = (bindDingData) => {
167
+ const cache = { };
168
+ if(bindDingData.attributes) cache.attributes = {};
169
+ if(bindDingData.classes) cache.class = {};
170
+ if(bindDingData.styles) cache.style = {};
171
+ bindDingData._cache = cache;
172
+ };
173
+
174
+ const prepareBindingMetadata = (bindDingData) => {
175
+ const attributes = [];
176
+ const classAndStyles = [];
177
+
178
+ if(bindDingData.attributes) {
179
+ for (const attr in bindDingData.attributes) {
180
+ attributes.push({
181
+ name: attr,
182
+ value: bindDingData.attributes[attr]
183
+ });
73
184
  }
74
185
  }
75
- if(path.children) {
76
- for(let i = 0, length = path.children.length; i < length; i++) {
77
- const currentPath = path.children[i];
78
- const pathTargetNode = target.childNodes[currentPath.index];
79
- applyBindingTreePath(root, pathTargetNode, data, currentPath);
186
+
187
+ if(bindDingData.classes) {
188
+ for (const className in bindDingData.classes) {
189
+ bindDingData._hasClassAttribute = true;
190
+ classAndStyles.push({
191
+ name: 'class',
192
+ key: className,
193
+ value: bindDingData.classes[className]
194
+ });
80
195
  }
81
196
  }
197
+
198
+ if(bindDingData.styles) {
199
+ for (const property in bindDingData.styles) {
200
+ bindDingData._hasStyleAttribute = true;
201
+ classAndStyles.push({
202
+ name: 'style',
203
+ key: property,
204
+ value: bindDingData.styles[property]
205
+ });
206
+ }
207
+ }
208
+
209
+ bindDingData._flatAttributes = attributes;
210
+ bindDingData._flatAttributesLength = attributes.length;
211
+ bindDingData._flatDynamique = classAndStyles;
212
+ bindDingData._flatDynamiqueLength = classAndStyles.length;
213
+ bindDingData._attachLength = bindDingData.attach.length;
214
+ };
215
+
216
+ const optimizeBindingData = (bindDingData) => {
217
+ buildAttributesCache(bindDingData);
218
+ prepareBindingMetadata(bindDingData);
82
219
  };
83
220
 
221
+ const noUpdate = () => {};
84
222
  export function TemplateCloner($fn) {
85
223
  let $node = null;
86
224
  let $hasBindingData = false;
87
225
 
88
- const $bindingTreePath = {
89
- fn: null,
90
- children: [],
91
- };
226
+ let $bindingTreePathSize = 0;
227
+ const $bindingTreePath = [
228
+ {
229
+ id: 0,
230
+ parentId: null
231
+ }
232
+ ];
92
233
 
234
+ let pathCounter = 0;
93
235
  const clone = (node, data, currentPath) => {
94
236
  const bindDingData = cloneBindingsDataCache.get(node);
237
+ if(bindDingData) {
238
+ optimizeBindingData(bindDingData);
239
+ }
95
240
  if(node.nodeType === 3) {
96
241
  if(bindDingData && bindDingData.value) {
97
- currentPath.fn = bindDingData.value;
242
+ const value = bindDingData.value;
98
243
  const textNode = node.cloneNode();
99
- if(typeof bindDingData.value === 'string') {
100
- ElementCreator.bindTextNode(textNode, data[0][bindDingData.value]);
101
- return textNode;
102
- }
103
- bindDingData.value(data, textNode);
244
+ currentPath.value = value;
245
+ currentPath.operation = OPERATIONS.HYDRATE_TEXT;
246
+ currentPath.isString = (typeof value === 'string');
247
+ ElementCreator.bindTextNode(textNode, (currentPath.isString ? data[0][value] : value.apply(null, data)));
104
248
  return textNode;
105
249
  }
106
250
  return node.cloneNode(true);
107
251
  }
108
252
  const nodeCloned = node.cloneNode();
109
253
  if(bindDingData) {
110
- bindAttributes(nodeCloned, bindDingData, data);
254
+ const hydrator = getHydrator(bindDingData);
255
+ hydrator(nodeCloned, bindDingData, data);
111
256
  bindAttachMethods(nodeCloned, bindDingData, data);
112
- currentPath.fn = (data, targetNode) => {
113
- bindAttributes(targetNode, bindDingData, data);
114
- bindAttachMethods(targetNode, bindDingData, data);
115
- };
257
+
258
+ const hasAttributes = bindDingData.classes || bindDingData.styles || bindDingData.attributes;
259
+ const hasAttachMethods = bindDingData.attach.length;
260
+
261
+ currentPath.bindingData = bindDingData;
262
+ currentPath.hydrator = hydrator;
263
+
264
+ if(hasAttributes && hasAttachMethods) {
265
+ currentPath.operation = OPERATIONS.HYDRATE_FULL;
266
+ }
267
+ else if(hasAttributes) {
268
+ currentPath.operation = OPERATIONS.HYDRATE_ATTRIBUTES;
269
+ }
270
+ else if(hasAttachMethods) {
271
+ currentPath.operation = OPERATIONS.ATTACH_METHOD;
272
+ }
116
273
  }
117
274
  const childNodes = node.childNodes;
118
- const bindingPathChildren = [];
275
+ const parentId = currentPath.id;
276
+
119
277
  for(let i = 0, length = childNodes.length; i < length; i++) {
120
278
  const childNode = childNodes[i];
121
- const path = { index: i, fn: null };
279
+ const path = { parentId, id: ++pathCounter, index: i };
122
280
  const childNodeCloned = clone(childNode, data, path);
123
- if(path.children || path.fn) {
124
- bindingPathChildren.push(path);
281
+ if(path.hasChildren || path.operation) {
282
+ $bindingTreePath.push(path);
283
+ currentPath.hasChildren = true;
125
284
  }
126
285
  nodeCloned.appendChild(childNodeCloned);
127
286
  }
128
- if(bindingPathChildren.length) {
129
- currentPath.children = currentPath.children || [];
130
- currentPath.children = bindingPathChildren;
131
- }
132
287
  return nodeCloned;
133
288
  };
134
289
 
135
290
  const cloneWithBindingPaths = (data) => {
136
291
  let root = $node.cloneNode(true);
137
292
 
138
- applyBindingTreePath(root, root, data, $bindingTreePath);
293
+ applyBindingTreePath(root, data, $bindingTreePath, $bindingTreePathSize);
139
294
  return root;
140
295
  };
141
296
 
142
297
  this.clone = (data) => {
143
- $node = $fn(this);
298
+ const binder = createTemplateCloner(this);
299
+ $node = $fn(binder);
144
300
  if(!$hasBindingData) {
145
301
  this.clone = () => $node.cloneNode(true);
146
302
  return $node.cloneNode(true);
147
303
  }
148
304
 
149
- const firstClone = clone($node, data, $bindingTreePath);
305
+ const firstClone = clone($node, data, $bindingTreePath[0]);
306
+ $bindingTreePath.reverse();
307
+ $bindingTreePathSize = $bindingTreePath.length - 1;
308
+
150
309
  this.clone = cloneWithBindingPaths;
151
310
  return firstClone;
152
311
  };
@@ -169,12 +328,7 @@ export function TemplateCloner($fn) {
169
328
  return this.value(propertyName);
170
329
  }
171
330
  this.value = (callbackOrProperty) => {
172
- if(typeof callbackOrProperty !== 'function') {
173
- return createBinding(callbackOrProperty, 'value');
174
- }
175
- return createBinding((data, textNode) => {
176
- ElementCreator.bindTextNode(textNode, callbackOrProperty(...data));
177
- }, 'value');
331
+ return createBinding(callbackOrProperty, 'value');
178
332
  };
179
333
  this.text = this.value;
180
334
  this.attr = (fn) => {
@@ -183,16 +337,31 @@ export function TemplateCloner($fn) {
183
337
  this.attach = (fn) => {
184
338
  return createBinding(fn, 'attach');
185
339
  };
340
+ this.callback = this.attach;
186
341
 
187
342
  }
188
343
 
344
+ function createTemplateCloner($binder) {
345
+ return new Proxy($binder, {
346
+ get(target, prop) {
347
+ if(prop in target) {
348
+ return target[prop];
349
+ }
350
+ if (typeof prop === 'symbol') return target[prop];
351
+ return target.value(prop);
352
+ }
353
+ });
354
+ }
355
+
189
356
  export function useCache(fn) {
190
357
  let $cache = null;
191
358
 
192
- const wrapper = function(args) {
193
- if(!$cache) {
194
- $cache = new TemplateCloner(fn);
195
- }
359
+ let wrapper = function(args) {
360
+ $cache = new TemplateCloner(fn);
361
+
362
+ wrapper = function(args) {
363
+ return $cache.clone(args);
364
+ };
196
365
  return $cache.clone(args);
197
366
  };
198
367
 
@@ -37,6 +37,7 @@ export declare class TemplateCloner {
37
37
  property(fn: (...data: any[]) => any): BindingHydrator;
38
38
 
39
39
  attach(fn: (element: Element, ...data: any[]) => void): BindingHydrator;
40
+ callback(fn: (element: Element, ...data: any[]) => void): BindingHydrator;
40
41
  }
41
42
 
42
43
  export declare function useCache(fn: TemplateBuilder): CachedTemplateFunction;