web-signature 0.1.1 → 0.1.2

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/bundle/index.js CHANGED
@@ -1 +1 @@
1
- class e{element;instance;constructor(e,t){this.instance=e,this.element=t}}let t=0;class r{components={};refs={};constructor(){}contact(e){const t=document.querySelector(e);if(!t)throw new Error(`Element not found for selector: ${e}`);const r=document.createElement("div");r.innerHTML=t.innerHTML,this.render(r),t.replaceChildren(...Array.from(r.childNodes))}add(e){this.components[e.name]&&console.warn(new Error(`Component with name ${e.name} already exists.`)),this.components[e.name]=e}ref(e){return this.refs[e]?.element}contactWith(e,...t){const r=this.refs[e];if(!r)throw new Error(`Ref with name ${e} does not exist.`);const n=r.instance;return n.onContact?.(...t)}updateRef(e){const t=this.refs[e];if(!t)throw new Error(`Ref with name ${e} does not exist.`);const r=t.instance,n=r.render().trim(),o=document.createElement("template");if(o.innerHTML=n,1!==o.content.children.length)throw new Error(`Component '${r.name}' must render a single root element.`);const s=o.content.firstElementChild;this.render(o),r.onRender?.(),t.element.replaceWith(s),t.element=s,r.onMount?.(s)}render(r){for(const n of Object.keys(this.components)){const o=this.components[n];for(const s of Array.from(r.querySelectorAll(n))){const r=new o;if(r.onInit?.(),s instanceof HTMLElement){r.content=s.innerHTML.trim();for(const e of Object.keys(r.props)){const t=s.getAttribute(e);if(null===t){if(r.props[e].required)throw new Error(`Property '${e}' in component '${n}' is required but not provided.`);r.data[e]=null}else if(""===t){if(r.props[e].required)throw new Error(`Property '${e}' in component '${n}' is required but not provided.`);r.props[e].isValid(t)&&(r.data[e]=null)}else{let o;switch(r.props[e].type){case"boolean":o=Boolean(t);break;case"number":o=Number(t);break;case"string":o=String(t);break;default:if(r.props[e].required)throw new Error(`Unsupported type for property '${e}' in component '${n}': ${r.props[e].type}`)}if(void 0!==o){if(!r.props[e].isValid(o))throw new Error(`Invalid value for property '${e}' in component '${n}': ${t}`);if(r.props[e].validate&&!r.props[e].validate(o))throw new Error(`Invalid value for property '${e}' in component '${n}': ${o}`);r.data[e]=o,r.onPropParsed?.(r.props[e],o)}}}r.onPropsParsed?.()}const i=document.createElement("template");if(i.innerHTML=r.render().trim(),i.content.children.length>1)throw new Error(`Component '${n}' must render a single root element.`);this.render(i),r.onRender?.();const a=i.content.firstElementChild;if(s.hasAttribute("ref")){let n=s.getAttribute("ref");if(t++,""===n&&(n=`r${t}${Math.random().toString(36).substring(2,15)}${t}`),this.refs[n])throw new Error(`Ref with name ${n} already exists.`);this.refs[n]=new e(r,a),a.setAttribute("ref",n)}s.replaceWith(i.content),r.onMount?.(a)}}}}class n{content;props={};data={}}class o{type;required=!0;validate;constructor(e,t=!0,r){this.type=e,this.required=t,this.validate=r||(()=>!0)}isValid(e){switch(this.type){case"boolean":return"boolean"==typeof e;case"number":return"number"==typeof e&&!isNaN(e);case"string":return"string"==typeof e;case"null":return null===e;default:return!1}}}function s(){return new r}export{n as Component,o as Prop,r as Signature,s as default};
1
+ class e{element;instance;constructor(e,t){this.instance=e,this.element=t}}const t={"element-not-found":"Element not found for selector: #selector","prop-is-required":"Property '#prop' in component '#component' is required but not provided.","unsupported-type-for-property":"Unsupported type for property '#prop' in component '#component': #type","invalid-value-for-property":"Invalid value for property '#prop' in component '#component': #value (value: #attr)","multiple-root-elements":"Component '#component' must render a single root element. \n\t#elements","ref-collision":"Ref collision detected for ref '#ref' in component '#component'.",unknown:"An unknown error occurred.","unknown-from":"An unknown error occurred in component '#from'."};let n=0;class r{components={};refs={};constructor(){}add(e){this.components[e.name]&&console.warn(new Error(`Component with name ${e.name} already exists.`)),this.components[e.name]=e}ref(e){return this.refs[e]?.element}contactWith(e,...t){const n=this.refs[e];if(!n)throw new Error(`Ref with name ${e} does not exist.`);const r=n.instance;return r.onContact?.(...t)}updateRef(e){const t=this.refs[e];if(!t)throw new Error(`Ref with name ${e} does not exist.`);const n=t.instance,r=n.render().trim(),o=document.createElement("template");if(o.innerHTML=r,1!==o.content.children.length)throw new Error(`Component '${n.name}' must render a single root element.`);const i=o.content.firstElementChild;this.render(o),n.onRender?.(),t.element.replaceWith(i),t.element=i,n.onMount?.(i)}contact(e){new Promise((t,n)=>{try{const t=document.querySelector(e);if(!t)return void n({id:"element-not-found",selector:e});const r=document.createElement("div");r.innerHTML=t.innerHTML,this.render(r),t.replaceChildren(...Array.from(r.childNodes))}catch(e){e instanceof Error?n({id:"unknown",err:e}):n(e)}}).then(()=>{}).catch(e=>{let n=t[e.id];throw Object.keys(e).filter(e=>!(e in["id","err"])).forEach(t=>{n=n.replace(`#${t}`,String(e[t]))}),e.id in["unknown","unknown-from"]?console.error(`[${e.id}] ${n}`,e.err):console.error(`[${e.id}] ${n}`),"Page rendering was interrupted by Signature due to the above error."})}render(t){for(const r of Object.keys(this.components)){const o=this.components[r];for(const i of Array.from(t.querySelectorAll(r))){const t=new o;if(t.onInit?.(),i instanceof HTMLElement){t.content=i.innerHTML.trim();for(const e of Object.keys(t.props)){const n=i.getAttribute(e);if(null===n){if(t.props[e].required)throw{id:"prop-is-required",component:r,prop:e};t.data[e]=null}else if(""===n){if(t.props[e].required)throw{id:"prop-is-required",component:r,prop:e};t.props[e].isValid(n)&&(t.data[e]=null)}else{let o;switch(t.props[e].type){case"boolean":o=Boolean(n);break;case"number":o=Number(n);break;case"string":o=String(n);break;default:if(t.props[e].required)throw{id:"unsupported-type-for-property",component:r,prop:e,type:t.props[e].type}}if(void 0!==o){if(!t.props[e].isValid(o))throw{id:"invalid-value-for-property",component:r,prop:e,value:o,attr:n};if(t.props[e].validate&&!t.props[e].validate(o))throw{id:"invalid-value-for-property",component:r,prop:e,value:o,attr:n};t.data[e]=o,t.onPropParsed?.(t.props[e],o)}}}t.onPropsParsed?.()}const s=document.createElement("template");try{s.innerHTML=t.render().trim()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:t.name,err:e}}if(s.content.children.length>1)throw{id:"multiple-root-elements",component:r,elements:s.innerHTML};this.render(s),t.onRender?.();const p=s.content.firstElementChild;if(i.hasAttribute("ref")){let o=i.getAttribute("ref");if(n++,""===o&&(o=`r${n}${Math.random().toString(36).substring(2,15)}${n}`),this.refs[o])throw{id:"ref-collision",ref:o,component:r};this.refs[o]=new e(t,p),p.setAttribute("ref",o)}i.replaceWith(s.content),t.onMount?.(p)}}}}class o{content;props={};data={}}class i{type;required=!0;validate;constructor(e,t=!0,n){this.type=e,this.required=t,this.validate=n||(()=>!0)}isValid(e){switch(this.type){case"boolean":return"boolean"==typeof e;case"number":return"number"==typeof e&&!isNaN(e);case"string":return"string"==typeof e;case"null":return null===e;default:return!1}}}function s(){return new r}export{o as Component,i as Prop,r as Signature,s as default};
package/dist/Errors.js ADDED
@@ -0,0 +1,11 @@
1
+ const errorMessages = {
2
+ "element-not-found": "Element not found for selector: #selector",
3
+ "prop-is-required": "Property '#prop' in component '#component' is required but not provided.",
4
+ "unsupported-type-for-property": "Unsupported type for property '#prop' in component '#component': #type",
5
+ "invalid-value-for-property": "Invalid value for property '#prop' in component '#component': #value (value: #attr)",
6
+ "multiple-root-elements": "Component '#component' must render a single root element. \n\t#elements",
7
+ "ref-collision": "Ref collision detected for ref '#ref' in component '#component'.",
8
+ "unknown": "An unknown error occurred.",
9
+ "unknown-from": "An unknown error occurred in component '#from'."
10
+ };
11
+ export default errorMessages;
package/dist/Signature.js CHANGED
@@ -1,24 +1,11 @@
1
1
  import Ref from "./Ref.js";
2
+ import Errors from "./Errors.js";
2
3
  let _counter = 0;
3
4
  export default class Signature {
4
5
  components = {};
5
6
  refs = {};
6
7
  constructor() {
7
8
  }
8
- /**
9
- * Starts rendering in the specified area.
10
- * @param {string} selector The selector of the element where the signature should be rendered.
11
- */
12
- contact(selector) {
13
- const mainFrame = document.querySelector(selector);
14
- if (!mainFrame) {
15
- throw new Error(`Element not found for selector: ${selector}`);
16
- }
17
- const secondaryFrame = document.createElement("div");
18
- secondaryFrame.innerHTML = mainFrame.innerHTML;
19
- this.render(secondaryFrame);
20
- mainFrame.replaceChildren(...Array.from(secondaryFrame.childNodes));
21
- }
22
9
  /**
23
10
  * Adds a component to the signature.
24
11
  * @param {ComponentConstructor} component The component to add.
@@ -77,25 +64,68 @@ export default class Signature {
77
64
  ref.element = newElement;
78
65
  component.onMount?.(newElement); // lifecycle hook
79
66
  }
67
+ /**
68
+ * Starts rendering in the specified area.
69
+ * @param {string} selector The selector of the element where the signature should be rendered.
70
+ */
71
+ contact(selector) {
72
+ const hunter = new Promise((_r, reject) => {
73
+ try {
74
+ const mainFrame = document.querySelector(selector);
75
+ if (!mainFrame) {
76
+ reject({ id: "element-not-found", selector: selector });
77
+ return;
78
+ }
79
+ const secondaryFrame = document.createElement("div");
80
+ secondaryFrame.innerHTML = mainFrame.innerHTML;
81
+ this.render(secondaryFrame);
82
+ mainFrame.replaceChildren(...Array.from(secondaryFrame.childNodes));
83
+ }
84
+ catch (err) {
85
+ if (err instanceof Error) {
86
+ reject({ id: "unknown", err: err });
87
+ }
88
+ else
89
+ reject(err);
90
+ }
91
+ });
92
+ // Handle errors
93
+ hunter.then(() => {
94
+ }).catch((err) => {
95
+ let message = Errors[err.id];
96
+ Object.keys(err).filter(key => !(key in ["id", "err"])).forEach((key) => {
97
+ message = message.replace(`#${key}`, String(err[key]));
98
+ });
99
+ if (err.id in ["unknown", "unknown-from"]) {
100
+ console.error(`[${err.id}] ${message}`, err.err);
101
+ }
102
+ else
103
+ console.error(`[${err.id}] ${message}`);
104
+ throw "Page rendering was interrupted by Signature due to the above error.";
105
+ });
106
+ }
80
107
  render(frame) {
81
108
  for (const com of Object.keys(this.components)) {
82
109
  const component = this.components[com];
110
+ // Find all elements with the component name in the frame
83
111
  for (const el of Array.from(frame.querySelectorAll(com))) {
84
112
  const renderer = new component();
85
113
  renderer.onInit?.(); // lifecycle hook
86
114
  if (el instanceof HTMLElement) {
115
+ // Fill the renderer's content
87
116
  renderer.content = el.innerHTML.trim();
117
+ // Parse properties
88
118
  for (const prop of Object.keys(renderer.props)) {
89
119
  const attr = el.getAttribute(prop);
90
120
  if (attr === null) {
91
121
  if (renderer.props[prop].required) {
92
- throw new Error(`Property '${prop}' in component '${com}' is required but not provided.`);
122
+ throw { id: "prop-is-required", component: com, prop: prop };
93
123
  }
94
124
  renderer.data[prop] = null;
95
125
  }
96
126
  else if (attr === "") {
97
127
  if (renderer.props[prop].required) {
98
- throw new Error(`Property '${prop}' in component '${com}' is required but not provided.`);
128
+ throw { id: "prop-is-required", component: com, prop: prop };
99
129
  }
100
130
  if (renderer.props[prop].isValid(attr)) {
101
131
  renderer.data[prop] = null;
@@ -103,6 +133,7 @@ export default class Signature {
103
133
  }
104
134
  else {
105
135
  let val;
136
+ // Determine the type of the property and convert the attribute value accordingly
106
137
  switch (renderer.props[prop].type) {
107
138
  case "boolean":
108
139
  val = Boolean(attr);
@@ -115,7 +146,12 @@ export default class Signature {
115
146
  break;
116
147
  default:
117
148
  if (renderer.props[prop].required) {
118
- throw new Error(`Unsupported type for property '${prop}' in component '${com}': ${renderer.props[prop].type}`);
149
+ throw {
150
+ id: "unsupported-type-for-property",
151
+ component: com,
152
+ prop: prop,
153
+ type: renderer.props[prop].type
154
+ };
119
155
  }
120
156
  break;
121
157
  }
@@ -123,36 +159,58 @@ export default class Signature {
123
159
  if (renderer.props[prop].isValid(val)) {
124
160
  if (renderer.props[prop].validate) {
125
161
  if (!renderer.props[prop].validate(val)) {
126
- throw new Error(`Invalid value for property '${prop}' in component '${com}': ${val}`);
162
+ throw {
163
+ id: "invalid-value-for-property",
164
+ component: com,
165
+ prop: prop,
166
+ value: val,
167
+ attr: attr
168
+ };
127
169
  }
128
170
  }
129
171
  renderer.data[prop] = val;
130
172
  renderer.onPropParsed?.(renderer.props[prop], val); // lifecycle hook
131
173
  }
132
174
  else {
133
- throw new Error(`Invalid value for property '${prop}' in component '${com}': ${attr}`);
175
+ throw {
176
+ id: "invalid-value-for-property",
177
+ component: com,
178
+ prop: prop,
179
+ value: val,
180
+ attr: attr
181
+ };
134
182
  }
135
183
  }
136
184
  }
137
185
  }
138
186
  renderer.onPropsParsed?.(); // lifecycle hook
139
187
  }
188
+ // Create a template for rendering
140
189
  const body = document.createElement("template");
141
- body.innerHTML = renderer.render().trim();
190
+ try {
191
+ body.innerHTML = renderer.render().trim();
192
+ }
193
+ catch (err) {
194
+ if (err instanceof Error) {
195
+ throw { id: "unknown-from", from: renderer.name, err: err };
196
+ }
197
+ }
142
198
  if (body.content.children.length > 1) {
143
- throw new Error(`Component '${com}' must render a single root element.`);
199
+ throw { id: "multiple-root-elements", component: com, elements: body.innerHTML };
144
200
  }
145
201
  this.render(body);
146
202
  renderer.onRender?.(); // lifecycle hook
147
203
  const mountEl = body.content.firstElementChild;
204
+ // Processing ref
148
205
  if (el.hasAttribute("ref")) {
149
206
  let refName = el.getAttribute("ref");
150
207
  _counter++;
208
+ // If the ref name is empty, generate a unique name
151
209
  if (refName === "") {
152
210
  refName = `r${_counter}${Math.random().toString(36).substring(2, 15)}${_counter}`;
153
211
  }
154
212
  if (this.refs[refName]) {
155
- throw new Error(`Ref with name ${refName} already exists.`);
213
+ throw { id: "ref-collision", ref: refName, component: com };
156
214
  }
157
215
  this.refs[refName] = new Ref(renderer, mountEl);
158
216
  mountEl.setAttribute("ref", refName);
@@ -0,0 +1,3 @@
1
+ import { Errors } from "./types/Errors.js";
2
+ declare const errorMessages: Record<Errors, string>;
3
+ export default errorMessages;
@@ -3,11 +3,6 @@ export default class Signature {
3
3
  private components;
4
4
  private refs;
5
5
  constructor();
6
- /**
7
- * Starts rendering in the specified area.
8
- * @param {string} selector The selector of the element where the signature should be rendered.
9
- */
10
- contact(selector: string): void;
11
6
  /**
12
7
  * Adds a component to the signature.
13
8
  * @param {ComponentConstructor} component The component to add.
@@ -30,5 +25,10 @@ export default class Signature {
30
25
  * @param {string} name The name of the reference to update.
31
26
  */
32
27
  updateRef(name: string): void;
28
+ /**
29
+ * Starts rendering in the specified area.
30
+ * @param {string} selector The selector of the element where the signature should be rendered.
31
+ */
32
+ contact(selector: string): void;
33
33
  private render;
34
34
  }
@@ -5,7 +5,13 @@ interface Component {
5
5
  * Optional content that is specified in the component tag.
6
6
  */
7
7
  content?: string;
8
+ /**
9
+ * Defining component properties.
10
+ */
8
11
  props: Record<string, Prop>;
12
+ /**
13
+ * The properties of the component.
14
+ */
9
15
  data: Record<string, string | number | boolean | null>;
10
16
  /**
11
17
  * Returns the component as a string.
@@ -0,0 +1,48 @@
1
+ type Errors = "unknown" | "unknown-from" | "element-not-found" | "prop-is-required" | "unsupported-type-for-property" | "invalid-value-for-property" | "multiple-root-elements" | "ref-collision";
2
+ export { Errors };
3
+ export interface error {
4
+ id: Errors;
5
+ }
6
+ export interface UnknownError extends error {
7
+ id: "unknown";
8
+ err?: Error;
9
+ }
10
+ export interface UnknownFromError extends error {
11
+ id: "unknown-from";
12
+ from: string;
13
+ err?: Error;
14
+ }
15
+ export interface ElementNotFoundError extends error {
16
+ id: "element-not-found";
17
+ selector: string;
18
+ }
19
+ export interface PropIsRequiredError extends error {
20
+ id: "prop-is-required";
21
+ component: string;
22
+ prop: string;
23
+ }
24
+ export interface UnsupportedTypeForPropertyError extends error {
25
+ id: "unsupported-type-for-property";
26
+ component: string;
27
+ prop: string;
28
+ type: "string" | "number" | "boolean" | "null";
29
+ }
30
+ export interface InvalidValueForPropertyError extends error {
31
+ id: "invalid-value-for-property";
32
+ component: string;
33
+ prop: string;
34
+ value: string | number | boolean | null;
35
+ attr: string;
36
+ }
37
+ export interface MultipleRootElementsError extends error {
38
+ id: "multiple-root-elements";
39
+ component: string;
40
+ elements: string;
41
+ }
42
+ export interface RefCollisionError extends error {
43
+ id: "ref-collision";
44
+ ref: string;
45
+ component: string;
46
+ }
47
+ type ErrorUnion = UnknownError | UnknownFromError | ElementNotFoundError | PropIsRequiredError | UnsupportedTypeForPropertyError | InvalidValueForPropertyError | MultipleRootElementsError | RefCollisionError;
48
+ export default ErrorUnion;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-signature",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Primitive and fast framework for rendering web interfaces",
5
5
  "license": "ISC",
6
6
  "author": "PinBib",
package/src/Errors.ts ADDED
@@ -0,0 +1,14 @@
1
+ import {Errors} from "./types/Errors.js";
2
+
3
+ const errorMessages: Record<Errors, string> = {
4
+ "element-not-found": "Element not found for selector: #selector",
5
+ "prop-is-required": "Property '#prop' in component '#component' is required but not provided.",
6
+ "unsupported-type-for-property": "Unsupported type for property '#prop' in component '#component': #type",
7
+ "invalid-value-for-property": "Invalid value for property '#prop' in component '#component': #value (value: #attr)",
8
+ "multiple-root-elements": "Component '#component' must render a single root element. \n\t#elements",
9
+ "ref-collision": "Ref collision detected for ref '#ref' in component '#component'.",
10
+ "unknown": "An unknown error occurred.",
11
+ "unknown-from": "An unknown error occurred in component '#from'."
12
+ }
13
+
14
+ export default errorMessages;
package/src/Signature.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import Component, {ComponentConstructor} from "./Component.js";
2
2
  import Ref from "./Ref.js";
3
+ import ErrorUnion from "./types/Errors.js";
4
+ import Errors from "./Errors.js";
3
5
 
4
6
  let _counter = 0;
5
7
 
@@ -10,25 +12,6 @@ export default class Signature {
10
12
  constructor() {
11
13
  }
12
14
 
13
- /**
14
- * Starts rendering in the specified area.
15
- * @param {string} selector The selector of the element where the signature should be rendered.
16
- */
17
- public contact(selector: string): void {
18
- const mainFrame = document.querySelector(selector);
19
-
20
- if (!mainFrame) {
21
- throw new Error(`Element not found for selector: ${selector}`);
22
- }
23
-
24
- const secondaryFrame = document.createElement("div");
25
- secondaryFrame.innerHTML = mainFrame.innerHTML;
26
-
27
- this.render(secondaryFrame);
28
-
29
- mainFrame.replaceChildren(...Array.from(secondaryFrame.childNodes));
30
- }
31
-
32
15
  /**
33
16
  * Adds a component to the signature.
34
17
  * @param {ComponentConstructor} component The component to add.
@@ -104,28 +87,77 @@ export default class Signature {
104
87
  component.onMount?.(newElement); // lifecycle hook
105
88
  }
106
89
 
90
+ /**
91
+ * Starts rendering in the specified area.
92
+ * @param {string} selector The selector of the element where the signature should be rendered.
93
+ */
94
+ public contact(selector: string): void {
95
+ const hunter: Promise<void> = new Promise((_r, reject: (reason?: ErrorUnion) => void) => {
96
+ try {
97
+ const mainFrame = document.querySelector(selector);
98
+
99
+ if (!mainFrame) {
100
+ reject({id: "element-not-found", selector: selector} as ErrorUnion);
101
+ return;
102
+ }
103
+
104
+ const secondaryFrame = document.createElement("div");
105
+ secondaryFrame.innerHTML = mainFrame.innerHTML;
106
+
107
+ this.render(secondaryFrame);
108
+
109
+ mainFrame.replaceChildren(...Array.from(secondaryFrame.childNodes));
110
+ } catch (err) {
111
+ if (err instanceof Error) {
112
+ reject({id: "unknown", err: err} as ErrorUnion);
113
+ } else reject(err as ErrorUnion);
114
+ }
115
+ });
116
+
117
+ // Handle errors
118
+ hunter.then(() => {
119
+ }).catch((err: ErrorUnion) => {
120
+ let message: string = Errors[err.id];
121
+
122
+ Object.keys(err).filter(key => !(key in ["id", "err"])).forEach((key) => {
123
+ message = message.replace(`#${key}`, String(err[key as keyof typeof err]));
124
+ });
125
+
126
+ if (err.id in ["unknown", "unknown-from"]) {
127
+ console.error(`[${err.id}] ${message}`, (err as {
128
+ err: Error
129
+ }).err);
130
+ } else console.error(`[${err.id}] ${message}`);
131
+
132
+ throw "Page rendering was interrupted by Signature due to the above error.";
133
+ });
134
+ }
135
+
107
136
  private render(frame: Element): void {
108
137
  for (const com of Object.keys(this.components)) {
109
138
  const component: ComponentConstructor = this.components[com];
110
139
 
140
+ // Find all elements with the component name in the frame
111
141
  for (const el of Array.from(frame.querySelectorAll(com))) {
112
142
  const renderer: Component = new component();
113
143
  renderer.onInit?.(); // lifecycle hook
114
144
 
115
145
  if (el instanceof HTMLElement) {
146
+ // Fill the renderer's content
116
147
  renderer.content = el.innerHTML.trim();
117
148
 
149
+ // Parse properties
118
150
  for (const prop of Object.keys(renderer.props)) {
119
151
  const attr = el.getAttribute(prop);
120
152
 
121
153
  if (attr === null) {
122
154
  if (renderer.props[prop].required) {
123
- throw new Error(`Property '${prop}' in component '${com}' is required but not provided.`);
155
+ throw {id: "prop-is-required", component: com, prop: prop} as ErrorUnion;
124
156
  }
125
157
  renderer.data[prop] = null;
126
158
  } else if (attr === "") {
127
159
  if (renderer.props[prop].required) {
128
- throw new Error(`Property '${prop}' in component '${com}' is required but not provided.`);
160
+ throw {id: "prop-is-required", component: com, prop: prop} as ErrorUnion;
129
161
  }
130
162
 
131
163
  if (renderer.props[prop].isValid(attr)) {
@@ -134,6 +166,7 @@ export default class Signature {
134
166
  } else {
135
167
  let val;
136
168
 
169
+ // Determine the type of the property and convert the attribute value accordingly
137
170
  switch (renderer.props[prop].type) {
138
171
  case "boolean":
139
172
  val = Boolean(attr);
@@ -146,16 +179,28 @@ export default class Signature {
146
179
  break;
147
180
  default:
148
181
  if (renderer.props[prop].required) {
149
- throw new Error(`Unsupported type for property '${prop}' in component '${com}': ${renderer.props[prop].type}`);
182
+ throw {
183
+ id: "unsupported-type-for-property",
184
+ component: com,
185
+ prop: prop,
186
+ type: renderer.props[prop].type
187
+ } as ErrorUnion;
150
188
  }
151
189
  break;
152
190
  }
191
+
153
192
  if (val !== undefined) {
154
193
  if (renderer.props[prop].isValid(val)) {
155
194
 
156
195
  if (renderer.props[prop].validate) {
157
196
  if (!renderer.props[prop].validate(val)) {
158
- throw new Error(`Invalid value for property '${prop}' in component '${com}': ${val}`);
197
+ throw {
198
+ id: "invalid-value-for-property",
199
+ component: com,
200
+ prop: prop,
201
+ value: val,
202
+ attr: attr
203
+ } as ErrorUnion;
159
204
  }
160
205
  }
161
206
 
@@ -163,7 +208,13 @@ export default class Signature {
163
208
 
164
209
  renderer.onPropParsed?.(renderer.props[prop], val); // lifecycle hook
165
210
  } else {
166
- throw new Error(`Invalid value for property '${prop}' in component '${com}': ${attr}`);
211
+ throw {
212
+ id: "invalid-value-for-property",
213
+ component: com,
214
+ prop: prop,
215
+ value: val,
216
+ attr: attr
217
+ } as ErrorUnion;
167
218
  }
168
219
  }
169
220
  }
@@ -172,11 +223,19 @@ export default class Signature {
172
223
  renderer.onPropsParsed?.(); // lifecycle hook
173
224
  }
174
225
 
226
+ // Create a template for rendering
175
227
  const body = document.createElement("template");
176
- body.innerHTML = renderer.render().trim();
228
+
229
+ try {
230
+ body.innerHTML = renderer.render().trim();
231
+ } catch (err) {
232
+ if (err instanceof Error) {
233
+ throw {id: "unknown-from", from: renderer.name, err: err} as ErrorUnion;
234
+ }
235
+ }
177
236
 
178
237
  if (body.content.children.length > 1) {
179
- throw new Error(`Component '${com}' must render a single root element.`);
238
+ throw {id: "multiple-root-elements", component: com, elements: body.innerHTML} as ErrorUnion;
180
239
  }
181
240
 
182
241
  this.render(body);
@@ -184,17 +243,19 @@ export default class Signature {
184
243
 
185
244
  const mountEl: Element = body.content.firstElementChild as Element;
186
245
 
246
+ // Processing ref
187
247
  if (el.hasAttribute("ref")) {
188
248
  let refName = el.getAttribute("ref") as string;
189
249
 
190
250
  _counter++;
191
251
 
252
+ // If the ref name is empty, generate a unique name
192
253
  if (refName === "") {
193
254
  refName = `r${_counter}${Math.random().toString(36).substring(2, 15)}${_counter}`;
194
255
  }
195
256
 
196
257
  if (this.refs[refName]) {
197
- throw new Error(`Ref with name ${refName} already exists.`);
258
+ throw {id: "ref-collision", ref: refName, component: com} as ErrorUnion;
198
259
  }
199
260
 
200
261
  this.refs[refName] = new Ref(renderer, mountEl);
@@ -8,8 +8,14 @@ interface Component {
8
8
  */
9
9
  content?: string;
10
10
 
11
-
11
+ /**
12
+ * Defining component properties.
13
+ */
12
14
  props: Record<string, Prop>;
15
+
16
+ /**
17
+ * The properties of the component.
18
+ */
13
19
  data: Record<string, string | number | boolean | null>;
14
20
 
15
21
 
@@ -0,0 +1,76 @@
1
+ type Errors =
2
+ "unknown"
3
+ | "unknown-from"
4
+ | "element-not-found"
5
+ | "prop-is-required"
6
+ | "unsupported-type-for-property"
7
+ | "invalid-value-for-property"
8
+ | "multiple-root-elements"
9
+ | "ref-collision";
10
+
11
+ export {Errors};
12
+
13
+ export interface error {
14
+ id: Errors;
15
+ }
16
+
17
+ export interface UnknownError extends error {
18
+ id: "unknown";
19
+ err?: Error;
20
+ }
21
+
22
+ export interface UnknownFromError extends error {
23
+ id: "unknown-from";
24
+ from: string;
25
+ err?: Error;
26
+ }
27
+
28
+ export interface ElementNotFoundError extends error {
29
+ id: "element-not-found";
30
+ selector: string;
31
+ }
32
+
33
+ export interface PropIsRequiredError extends error {
34
+ id: "prop-is-required";
35
+ component: string;
36
+ prop: string;
37
+ }
38
+
39
+ export interface UnsupportedTypeForPropertyError extends error {
40
+ id: "unsupported-type-for-property";
41
+ component: string;
42
+ prop: string;
43
+ type: "string" | "number" | "boolean" | "null";
44
+ }
45
+
46
+ export interface InvalidValueForPropertyError extends error {
47
+ id: "invalid-value-for-property";
48
+ component: string;
49
+ prop: string;
50
+ value: string | number | boolean | null;
51
+ attr: string;
52
+ }
53
+
54
+ export interface MultipleRootElementsError extends error {
55
+ id: "multiple-root-elements";
56
+ component: string;
57
+ elements: string;
58
+ }
59
+
60
+ export interface RefCollisionError extends error {
61
+ id: "ref-collision";
62
+ ref: string;
63
+ component: string;
64
+ }
65
+
66
+ type ErrorUnion =
67
+ UnknownError
68
+ | UnknownFromError
69
+ | ElementNotFoundError
70
+ | PropIsRequiredError
71
+ | UnsupportedTypeForPropertyError
72
+ | InvalidValueForPropertyError
73
+ | MultipleRootElementsError
74
+ | RefCollisionError;
75
+
76
+ export default ErrorUnion;