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 +1 -1
- package/dist/Errors.js +11 -0
- package/dist/Signature.js +80 -22
- package/dist/d/Errors.d.ts +3 -0
- package/dist/d/Signature.d.ts +5 -5
- package/dist/d/types/Component.d.ts +6 -0
- package/dist/d/types/Errors.d.ts +48 -0
- package/dist/types/Errors.js +1 -0
- package/package.json +1 -1
- package/src/Errors.ts +14 -0
- package/src/Signature.ts +88 -27
- package/src/types/Component.ts +7 -1
- package/src/types/Errors.ts +76 -0
package/bundle/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class e{element;instance;constructor(e,t){this.instance=e,this.element=t}}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|
package/dist/d/Signature.d.ts
CHANGED
|
@@ -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
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
258
|
+
throw {id: "ref-collision", ref: refName, component: com} as ErrorUnion;
|
|
198
259
|
}
|
|
199
260
|
|
|
200
261
|
this.refs[refName] = new Ref(renderer, mountEl);
|
package/src/types/Component.ts
CHANGED
|
@@ -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;
|