native-document 1.0.31 → 1.0.33

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.
@@ -2,6 +2,9 @@ import Validator from "./validator";
2
2
  import ArgTypesError from "../errors/ArgTypesError";
3
3
  import NativeDocumentError from "../errors/NativeDocumentError";
4
4
 
5
+ let withValidation = (fn) => fn;
6
+ let ArgTypes = {};
7
+
5
8
  /**
6
9
  *
7
10
  * @type {{string: (function(*): {name: *, type: string, validate: function(*): boolean}),
@@ -19,91 +22,117 @@ import NativeDocumentError from "../errors/NativeDocumentError";
19
22
  * validate: function(*): boolean})
20
23
  * }}
21
24
  */
22
- export const ArgTypes = {
23
- string: (name) => ({ name, type: 'string', validate: (v) => Validator.isString(v) }),
24
- number: (name) => ({ name, type: 'number', validate: (v) => Validator.isNumber(v) }),
25
- boolean: (name) => ({ name, type: 'boolean', validate: (v) => Validator.isBoolean(v) }),
26
- observable: (name) => ({ name, type: 'observable', validate: (v) => Validator.isObservable(v) }),
27
- element: (name) => ({ name, type: 'element', validate: (v) => Validator.isElement(v) }),
28
- function: (name) => ({ name, type: 'function', validate: (v) => Validator.isFunction(v) }),
29
- object: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v)) }),
30
- objectNotNull: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v) && v !== null) }),
31
- children: (name) => ({ name, type: 'children', validate: (v) => Validator.validateChildren(v) }),
32
- attributes: (name) => ({ name, type: 'attributes', validate: (v) => Validator.validateAttributes(v) }),
33
-
34
- // Optional arguments
35
- optional: (argType) => ({ ...argType, optional: true }),
36
-
37
- // Union types
38
- oneOf: (name, ...argTypes) => ({
39
- name,
40
- type: 'oneOf',
41
- types: argTypes,
42
- validate: (v) => argTypes.some(type => type.validate(v))
43
- })
44
- };
25
+ if(process.env.NODE_ENV === 'development') {
26
+ ArgTypes = {
27
+ string: (name) => ({ name, type: 'string', validate: (v) => Validator.isString(v) }),
28
+ number: (name) => ({ name, type: 'number', validate: (v) => Validator.isNumber(v) }),
29
+ boolean: (name) => ({ name, type: 'boolean', validate: (v) => Validator.isBoolean(v) }),
30
+ observable: (name) => ({ name, type: 'observable', validate: (v) => Validator.isObservable(v) }),
31
+ element: (name) => ({ name, type: 'element', validate: (v) => Validator.isElement(v) }),
32
+ function: (name) => ({ name, type: 'function', validate: (v) => Validator.isFunction(v) }),
33
+ object: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v)) }),
34
+ objectNotNull: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v) && v !== null) }),
35
+ children: (name) => ({ name, type: 'children', validate: (v) => Validator.validateChildren(v) }),
36
+ attributes: (name) => ({ name, type: 'attributes', validate: (v) => Validator.validateAttributes(v) }),
45
37
 
46
- /**
47
- *
48
- * @param {Array} args
49
- * @param {Array} argSchema
50
- * @param {string} fnName
51
- */
52
- const validateArgs = (args, argSchema, fnName = 'Function') => {
53
- if (!argSchema) return;
38
+ // Optional arguments
39
+ optional: (argType) => ({ ...argType, optional: true }),
54
40
 
55
- const errors = [];
41
+ // Union types
42
+ oneOf: (name, ...argTypes) => ({
43
+ name,
44
+ type: 'oneOf',
45
+ types: argTypes,
46
+ validate: (v) => argTypes.some(type => type.validate(v))
47
+ })
48
+ };
56
49
 
57
- // Check the number of arguments
58
- const requiredCount = argSchema.filter(arg => !arg.optional).length;
59
- if (args.length < requiredCount) {
60
- errors.push(`${fnName}: Expected at least ${requiredCount} arguments, got ${args.length}`);
61
- }
62
50
 
63
- // Validate each argument
64
- argSchema.forEach((schema, index) => {
65
- const position = index + 1;
66
- const value = args[index];
51
+ /**
52
+ *
53
+ * @param {Array} args
54
+ * @param {Array} argSchema
55
+ * @param {string} fnName
56
+ */
57
+ const validateArgs = (args, argSchema, fnName = 'Function') => {
58
+ if (!argSchema) return;
67
59
 
68
- if (value === undefined) {
69
- if (!schema.optional) {
70
- errors.push(`${fnName}: Missing required argument '${schema.name}' at position ${position}`);
60
+ const errors = [];
61
+
62
+ // Check the number of arguments
63
+ const requiredCount = argSchema.filter(arg => !arg.optional).length;
64
+ if (args.length < requiredCount) {
65
+ errors.push(`${fnName}: Expected at least ${requiredCount} arguments, got ${args.length}`);
66
+ }
67
+
68
+ // Validate each argument
69
+ argSchema.forEach((schema, index) => {
70
+ const position = index + 1;
71
+ const value = args[index];
72
+
73
+ if (value === undefined) {
74
+ if (!schema.optional) {
75
+ errors.push(`${fnName}: Missing required argument '${schema.name}' at position ${position}`);
76
+ }
77
+ return;
71
78
  }
72
- return;
79
+
80
+ if (!schema.validate(value)) {
81
+ const valueTypeOf = value?.constructor?.name || typeof value;
82
+ errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
83
+ }
84
+ });
85
+
86
+ if (errors.length > 0) {
87
+ throw new ArgTypesError(`Argument validation failed`, errors);
73
88
  }
89
+ };
90
+
91
+
74
92
 
75
- if (!schema.validate(value)) {
76
- const valueTypeOf = value?.constructor?.name || typeof value;
77
- errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
93
+ /**
94
+ * @param {Function} fn
95
+ * @param {Array} argSchema
96
+ * @param {string} fnName
97
+ * @returns {Function}
98
+ */
99
+ withValidation = (fn, argSchema, fnName = 'Function') => {
100
+ if(!Validator.isArray(argSchema)) {
101
+ throw new NativeDocumentError('withValidation : argSchema must be an array');
78
102
  }
79
- });
103
+ return function(...args) {
104
+ validateArgs(args, argSchema, fn.name || fnName);
105
+ return fn.apply(this, args);
106
+ };
107
+ };
108
+ }
109
+ if(process.env.NODE_ENV === 'production') {
110
+ ArgTypes = {
111
+ string: () => true,
112
+ number: () => true,
113
+ boolean: () => true,
114
+ observable: () => true,
115
+ element: () => true,
116
+ function: () => true,
117
+ object: () => true,
118
+ objectNotNull: () => true,
119
+ children: () => true,
120
+ attributes: () => true,
80
121
 
81
- if (errors.length > 0) {
82
- throw new ArgTypesError(`Argument validation failed`, errors);
83
- }
84
- };
122
+ // Optional arguments
123
+ optional: () => true,
85
124
 
86
- /**
87
- * @param {Function} fn
88
- * @param {Array} argSchema
89
- * @param {string} fnName
90
- * @returns {Function}
91
- */
92
- export const withValidation = (fn, argSchema, fnName = 'Function') => {
93
- if(!Validator.isArray(argSchema)) {
94
- throw new NativeDocumentError('withValidation : argSchema must be an array');
95
- }
96
- return function(...args) {
97
- validateArgs(args, argSchema, fn.name || fnName);
98
- return fn.apply(this, args);
125
+ // Union types
126
+ oneOf: () => true
99
127
  };
100
- };
128
+ }
101
129
 
102
130
  export const normalizeComponentArgs = function(props, children = null) {
103
- if(!Validator.isJson(props)) {
131
+ if(!Validator.isJson(props) || props?.$hydrate) {
104
132
  const temp = children;
105
133
  children = props;
106
134
  props = temp;
107
135
  }
108
136
  return { props, children };
109
- }
137
+ }
138
+ export { ArgTypes, withValidation };
@@ -1,12 +1,29 @@
1
1
  import {withValidation} from "./args-types.js";
2
2
  import {Observable} from "../data/Observable";
3
3
  import Validator from "./validator";
4
+ import {NDElement} from "../wrappers/NDElement";
4
5
 
5
6
 
6
7
  Function.prototype.args = function(...args) {
7
8
  return withValidation(this, args);
8
9
  };
9
10
 
11
+ Function.prototype.cached = function(...args) {
12
+ let $cache = null;
13
+ let getCache = function(){ return $cache; };
14
+ return () => {
15
+ if(!$cache) {
16
+ $cache = this.apply(this, args);
17
+ if($cache.cloneNode) {
18
+ getCache = function() { return $cache.cloneNode(true); };
19
+ } else if($cache.$element) {
20
+ getCache = function() { return new NDElement($cache.$element.cloneNode(true)); };
21
+ }
22
+ }
23
+ return getCache();
24
+ };
25
+ };
26
+
10
27
  Function.prototype.errorBoundary = function(callback) {
11
28
  return (...args) => {
12
29
  try {
@@ -23,6 +23,10 @@ function bindClassAttribute(element, data) {
23
23
  });
24
24
  continue;
25
25
  }
26
+ if(value.$hydrate) {
27
+ value.$hydrate(element, className);
28
+ continue;
29
+ }
26
30
  element.classList.toggle(className, value)
27
31
  }
28
32
  }
@@ -21,11 +21,22 @@ export const ElementCreator = {
21
21
  */
22
22
  createObservableNode(parent, observable) {
23
23
  const text = ElementCreator.createTextNode();
24
- observable.subscribe(value => text.nodeValue = String(value));
24
+ observable.subscribe(value => text.nodeValue = value);
25
25
  text.nodeValue = observable.val();
26
26
  parent && parent.appendChild(text);
27
27
  return text;
28
28
  },
29
+ /**
30
+ *
31
+ * @param {HTMLElement|DocumentFragment} parent
32
+ * @param {{$hydrate: Function}} item
33
+ * @returns {Text}
34
+ */
35
+ createHydratableNode(parent, item) {
36
+ const text = ElementCreator.createTextNode();
37
+ item.$hydrate(text);
38
+ return text;
39
+ },
29
40
 
30
41
  /**
31
42
  *
@@ -35,7 +46,7 @@ export const ElementCreator = {
35
46
  */
36
47
  createStaticTextNode(parent, value) {
37
48
  let text = ElementCreator.createTextNode();
38
- text.nodeValue = String(value);
49
+ text.nodeValue = value;
39
50
  parent && parent.appendChild(text);
40
51
  return text;
41
52
  },
@@ -104,6 +115,9 @@ export const ElementCreator = {
104
115
  PluginsManager.emit('BeforeProcessComponent', child);
105
116
  return this.getChild(child());
106
117
  }
118
+ if(child?.$hydrate) {
119
+ return ElementCreator.createHydratableNode(null, child);
120
+ }
107
121
  return ElementCreator.createStaticTextNode(null, child);
108
122
  },
109
123
  /**
@@ -1,5 +1,4 @@
1
1
  import Validator from "../utils/validator";
2
- import DebugManager from "../utils/debug-manager";
3
2
  import {ElementCreator} from "./ElementCreator";
4
3
  import './NdPrototype';
5
4
  import {normalizeComponentArgs} from "../utils/args-types";
@@ -16,6 +15,17 @@ export const createTextNode = function(value) {
16
15
  };
17
16
 
18
17
 
18
+ function createHtmlElement($tagName, _attributes, _children = null, customWrapper) {
19
+ const { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
20
+ const element = ElementCreator.createElement($tagName);
21
+ const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
22
+
23
+ ElementCreator.processAttributes(finalElement, attributes);
24
+ ElementCreator.processChildren(children, finalElement);
25
+
26
+ return ElementCreator.setup(finalElement, attributes, customWrapper);
27
+ }
28
+
19
29
  /**
20
30
  *
21
31
  * @param {string} name
@@ -23,21 +33,6 @@ export const createTextNode = function(value) {
23
33
  * @returns {Function}
24
34
  */
25
35
  export default function HtmlElementWrapper(name, customWrapper) {
26
- const $tagName = name.toLowerCase();
27
-
28
- return function(_attributes, _children = null) {
29
- try {
30
- const { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
31
- const element = ElementCreator.createElement($tagName);
32
- const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
33
-
34
- ElementCreator.processAttributes(finalElement, attributes);
35
- ElementCreator.processChildren(children, finalElement);
36
-
37
- return ElementCreator.setup(finalElement, attributes, customWrapper);
38
- } catch (error) {
39
- DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
40
- }
41
- };
42
- }
36
+ return (_attributes, _children = null) => createHtmlElement(name.toLowerCase(), _attributes, _children, customWrapper);
37
+ };
43
38
 
@@ -91,4 +91,9 @@ NDElement.prototype.htmlElement = function() {
91
91
  return this.$element;
92
92
  };
93
93
 
94
- NDElement.prototype.node = NDElement.prototype.htmlElement;
94
+ NDElement.prototype.node = NDElement.prototype.htmlElement;
95
+
96
+ NDElement.prototype.attach = function(methodName, bindingHydrator) {
97
+ bindingHydrator.$hydrate(this.$element, methodName);
98
+ return this.$element;
99
+ };
@@ -0,0 +1,129 @@
1
+ import {ElementCreator} from "./ElementCreator";
2
+ import {createTextNode} from "./HtmlElementWrapper";
3
+
4
+ const cloneBindingsDataCache = new WeakMap();
5
+
6
+
7
+ const bindAttributes = (node, bindDingData, data) => {
8
+ if(!bindDingData) {
9
+ return null;
10
+ }
11
+ const attributes = { };
12
+ if(bindDingData.attributes) {
13
+ for (const attr in bindDingData.attributes) {
14
+ attributes[attr] = bindDingData.attributes[attr](...data);
15
+ }
16
+ }
17
+
18
+ if(bindDingData.classes) {
19
+ attributes.class = {};
20
+ for (const className in bindDingData.classes) {
21
+ attributes.class[className] = bindDingData.classes[className](...data);
22
+ }
23
+ }
24
+
25
+ if(bindDingData.styles) {
26
+ attributes.style = {};
27
+ for (const property in bindDingData.styles) {
28
+ attributes.style[property] = bindDingData.styles[property](...data);
29
+ }
30
+ }
31
+
32
+ if(Object.keys(attributes)) {
33
+ ElementCreator.processAttributes(node, attributes);
34
+ return attributes;
35
+ }
36
+
37
+ return null;
38
+ };
39
+
40
+
41
+ const bindAttachesMethods = function(node, bindDingData, data) {
42
+ if(!bindDingData?.attaches) {
43
+ return null;
44
+ }
45
+ for(const methodName in bindDingData.attaches) {
46
+ node.nd[methodName](function(...args) {
47
+ bindDingData.attaches[methodName].call(this, ...[...args, ...data]);
48
+ });
49
+ }
50
+ }
51
+
52
+ export function TemplateCloner($fn) {
53
+ let $node = null;
54
+
55
+ const clone = (node, data) => {
56
+ const bindDingData = cloneBindingsDataCache.get(node);
57
+ if(node instanceof Text) {
58
+ if(bindDingData?.value) {
59
+ return bindDingData.value(data);
60
+ }
61
+ return node.cloneNode(true);
62
+ }
63
+ const nodeCloned = node.cloneNode();
64
+ bindAttributes(nodeCloned, bindDingData, data);
65
+ bindAttachesMethods(nodeCloned, bindDingData, data);
66
+
67
+ for(let i = 0, length = node.childNodes.length; i < length; i++) {
68
+ const childNode = node.childNodes[i];
69
+ const childNodeCloned = clone(childNode, data);
70
+ nodeCloned.appendChild(childNodeCloned);
71
+ }
72
+ return nodeCloned;
73
+ };
74
+
75
+ this.clone = (data) => {
76
+ if(!$node) {
77
+ $node = $fn(this);
78
+ }
79
+ return clone($node, data);
80
+ };
81
+
82
+ const createBinding = (hydrateFunction, target) => {
83
+ return {
84
+ $hydrate : function(element, property) {
85
+ if(!cloneBindingsDataCache.has(element)) {
86
+ // { classes, styles, attributes, value, attaches }
87
+ cloneBindingsDataCache.set(element, {});
88
+ }
89
+ const hydrationState = cloneBindingsDataCache.get(element);
90
+ if(target === 'value') {
91
+ hydrationState.value = hydrateFunction;
92
+ return;
93
+ }
94
+ hydrationState[target] = hydrationState[target] || {};
95
+ hydrationState[target][property] = hydrateFunction;
96
+ }
97
+ }
98
+ };
99
+
100
+ this.style = (fn) => {
101
+ return createBinding(fn, 'styles');
102
+ };
103
+ this.class = (fn) => {
104
+ return createBinding(fn, 'classes');
105
+ };
106
+ this.value = (fn) => {
107
+ return createBinding(function(data) {
108
+ return createTextNode(fn(...data));
109
+ }, 'value');
110
+ };
111
+ this.attr = (fn) => {
112
+ return createBinding(fn, 'attributes');
113
+ };
114
+ this.attach = (fn) => {
115
+ return createBinding(fn, 'attaches');
116
+ };
117
+ }
118
+
119
+ export function useCache(fn) {
120
+ let $cache = null;
121
+
122
+ return function(...args) {
123
+ if(!$cache) {
124
+ $cache = new TemplateCloner(fn);
125
+ }
126
+
127
+ return $cache.clone(args);
128
+ };
129
+ }
@@ -1,5 +1,6 @@
1
1
  // DOM elements and components type definitions - Version complète
2
2
  import { ObservableItem } from './observable';
3
+ import {BindingHydrator} from "./template-cloner";
3
4
 
4
5
  export interface NDElement {
5
6
  readonly __$isNDElement: true;
@@ -16,6 +17,7 @@ export interface NDElement {
16
17
 
17
18
  htmlElement(): HTMLElement;
18
19
  node(): HTMLElement;
20
+ attach(methodeName: string, bindingHydrator: BindingHydrator): HTMLElement;
19
21
 
20
22
  // Mouse Events
21
23
  onClick(callback: (event: MouseEvent) => void): this;
@@ -0,0 +1,36 @@
1
+
2
+ interface BindingData {
3
+ attributes?: Record<string, (...data: any[]) => any>;
4
+ classes?: Record<string, (...data: any[]) => any>;
5
+ styles?: Record<string, (...data: any[]) => any>;
6
+ value?: (...data: any[]) => any;
7
+ events?: Record<string, (this: Element, event: Event, ...data: any[]) => void>;
8
+ }
9
+
10
+ interface ProcessedAttributes {
11
+ class?: Record<string, any>;
12
+ style?: Record<string, any>;
13
+ [key: string]: any;
14
+ }
15
+
16
+ export interface BindingHydrator {
17
+ $hydrate: (element: Element | Text, property?: string) => void;
18
+ }
19
+
20
+ export type TemplateBuilder = (templateCloner: TemplateCloner) => Node;
21
+
22
+ type CachedTemplateFunction = (...args: any[]) => Node;
23
+
24
+ export declare class TemplateCloner {
25
+ constructor($fn: TemplateBuilder);
26
+
27
+ clone(data: any[]): Node;
28
+
29
+ style(fn: (...data: any[]) => any): BindingHydrator;
30
+ class(fn: (...data: any[]) => any): BindingHydrator;
31
+ value(fn: (...data: any[]) => any): BindingHydrator;
32
+ attr(fn: (...data: any[]) => any): BindingHydrator;
33
+ event(fn: (event: Event, ...data: any[]) => void): BindingHydrator;
34
+ }
35
+
36
+ export declare function useCache(fn: TemplateBuilder): CachedTemplateFunction;