web-signature 0.2.6 → 0.2.9
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/README.MD +5 -0
- package/bundle/d/index.d.ts +37 -3
- package/bundle/index.js +52 -1
- package/bundle/index.min.js +1 -1
- package/dist/Component.js +2 -0
- package/dist/Plugin.js +2 -0
- package/dist/Signature.js +45 -0
- package/dist/d/Component.d.ts +2 -0
- package/dist/d/Plugin.d.ts +12 -0
- package/dist/d/Signature.d.ts +10 -0
- package/dist/d/html.d.ts +6 -2
- package/dist/d/index.d.ts +1 -0
- package/dist/d/types/Component.d.ts +8 -0
- package/dist/d/types/MetaPlugin.d.ts +5 -0
- package/dist/html.js +13 -0
- package/dist/index.js +1 -0
- package/dist/types/MetaPlugin.js +1 -0
- package/package.json +1 -1
- package/src/Component.ts +4 -0
- package/src/Plugin.ts +13 -0
- package/src/Signature.ts +61 -2
- package/src/html.ts +25 -4
- package/src/index.ts +2 -1
- package/src/types/Component.ts +9 -0
- package/src/types/MetaPlugin.ts +6 -0
package/README.MD
CHANGED
|
@@ -31,12 +31,17 @@ npm run dev
|
|
|
31
31
|
|
|
32
32
|
* [Signature](#signature)
|
|
33
33
|
* [Quick Start](#quick-start)
|
|
34
|
+
* [Try online](#try-online)
|
|
34
35
|
* [Introduction](#introduction)
|
|
35
36
|
* [Briefly about Signature](#briefly-about-signature)
|
|
36
37
|
* [About Component](#about-component)
|
|
37
38
|
* [Quick about the Library](#quick-about-the-library)
|
|
38
39
|
* [See more here](#see-more-here)
|
|
39
40
|
|
|
41
|
+
## Try online
|
|
42
|
+
|
|
43
|
+
You can also try Signature online using [StackBlitz](https://stackblitz.com/fork/signature-starter)
|
|
44
|
+
|
|
40
45
|
# Introduction
|
|
41
46
|
|
|
42
47
|
Signature uses four classes as its basis:
|
package/bundle/d/index.d.ts
CHANGED
|
@@ -66,6 +66,10 @@ interface Component$1 {
|
|
|
66
66
|
* Optional content that is specified in the component tag.
|
|
67
67
|
*/
|
|
68
68
|
content?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Optional groups that the component belongs to.
|
|
71
|
+
*/
|
|
72
|
+
groups: string[];
|
|
69
73
|
/**
|
|
70
74
|
* Defining component properties.
|
|
71
75
|
*/
|
|
@@ -74,6 +78,10 @@ interface Component$1 {
|
|
|
74
78
|
* The properties of the component.
|
|
75
79
|
*/
|
|
76
80
|
data: Record<string, string | number | boolean | null>;
|
|
81
|
+
/**
|
|
82
|
+
* Plugins are stored here.
|
|
83
|
+
*/
|
|
84
|
+
$: Record<string, unknown>;
|
|
77
85
|
/**
|
|
78
86
|
* Returns the component as a string (template).
|
|
79
87
|
* @returns {html | Promise<html>} The rendered component as a string (template).
|
|
@@ -112,10 +120,12 @@ interface Component$1 {
|
|
|
112
120
|
declare abstract class Component implements Component$1 {
|
|
113
121
|
abstract readonly name: string;
|
|
114
122
|
content?: string;
|
|
123
|
+
groups: string[];
|
|
115
124
|
options: Options;
|
|
116
125
|
ref?: Ref;
|
|
117
126
|
readonly props: Record<string, Prop$1>;
|
|
118
127
|
readonly data: Record<string, string | number | boolean | null>;
|
|
128
|
+
$: Record<string, unknown>;
|
|
119
129
|
abstract render(): html$1 | Promise<html$1>;
|
|
120
130
|
onInit?(): void;
|
|
121
131
|
onRender?(): void;
|
|
@@ -180,6 +190,19 @@ declare class Library {
|
|
|
180
190
|
}>;
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
declare abstract class Plugin {
|
|
194
|
+
/**
|
|
195
|
+
* Modules are mini plugins that are required for the plugin to work, they are processed and stored before the plugin itself is installed and stored in a safe place.
|
|
196
|
+
*/
|
|
197
|
+
readonly abstract modules: Record<string, () => Record<string, unknown>>;
|
|
198
|
+
/**
|
|
199
|
+
* The function returns an object that will be installed as a plugin in Signature.$
|
|
200
|
+
* @param modules
|
|
201
|
+
* @returns {Record<string, unknown>} The object that will be installed as a plugin
|
|
202
|
+
*/
|
|
203
|
+
abstract define(modules: Record<string, Record<string, unknown>>): Record<string, unknown>;
|
|
204
|
+
}
|
|
205
|
+
|
|
183
206
|
type ResolvedLib = {
|
|
184
207
|
components: string[];
|
|
185
208
|
dependencies: Record<string, ResolvedLib>;
|
|
@@ -189,6 +212,8 @@ declare class Signature {
|
|
|
189
212
|
private refs;
|
|
190
213
|
private libs;
|
|
191
214
|
private bank;
|
|
215
|
+
private $;
|
|
216
|
+
private $g;
|
|
192
217
|
constructor();
|
|
193
218
|
/**
|
|
194
219
|
* Adds a component to the signature.
|
|
@@ -214,6 +239,13 @@ declare class Signature {
|
|
|
214
239
|
* @return {Record<string, ResolvedLib>} A object of formatted libraries with their components and dependencies.
|
|
215
240
|
*/
|
|
216
241
|
libraries(): Record<string, ResolvedLib>;
|
|
242
|
+
/**
|
|
243
|
+
* Registers a plugin in the signature.
|
|
244
|
+
* @import {Plugin} from "./Plugin.js";
|
|
245
|
+
* @param {string} name The name of the plugin.
|
|
246
|
+
* @param {Plugin} plugin The plugin to register.
|
|
247
|
+
*/
|
|
248
|
+
use(name: string, plugin: Plugin): void;
|
|
217
249
|
/**
|
|
218
250
|
* Contacts the Component.onContact method through its reference.
|
|
219
251
|
* @param {string} name The name of the reference.
|
|
@@ -252,15 +284,17 @@ declare class Prop<T extends keyof TypesMap> implements Prop$1 {
|
|
|
252
284
|
isValid(value: TypesMap[keyof TypesMap]): boolean;
|
|
253
285
|
}
|
|
254
286
|
|
|
255
|
-
|
|
287
|
+
type HTMLTemplate = {
|
|
256
288
|
strings: TemplateStringsArray;
|
|
257
289
|
values: any[];
|
|
258
290
|
};
|
|
259
|
-
declare function
|
|
291
|
+
declare function html(strings: TemplateStringsArray, ...values: any[]): HTMLTemplate;
|
|
292
|
+
type unsafeHTMLTemplate = {
|
|
260
293
|
type: "unsafeHTML";
|
|
261
294
|
value: any;
|
|
262
295
|
};
|
|
296
|
+
declare function unsafeHTML(value: any): unsafeHTMLTemplate;
|
|
263
297
|
|
|
264
298
|
declare function export_default(): Signature;
|
|
265
299
|
|
|
266
|
-
export { Component, Library, Prop, Signature, export_default as default, html, unsafeHTML };
|
|
300
|
+
export { Component, Library, Plugin, Prop, Signature, export_default as default, html, unsafeHTML };
|
package/bundle/index.js
CHANGED
|
@@ -26,6 +26,8 @@ class Signature {
|
|
|
26
26
|
refs = {};
|
|
27
27
|
libs = {};
|
|
28
28
|
bank = new Map();
|
|
29
|
+
$ = {};
|
|
30
|
+
$g = {};
|
|
29
31
|
constructor() {
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
@@ -99,6 +101,26 @@ class Signature {
|
|
|
99
101
|
};
|
|
100
102
|
return resolve(this.libs);
|
|
101
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Registers a plugin in the signature.
|
|
106
|
+
* @import {Plugin} from "./Plugin.js";
|
|
107
|
+
* @param {string} name The name of the plugin.
|
|
108
|
+
* @param {Plugin} plugin The plugin to register.
|
|
109
|
+
*/
|
|
110
|
+
use(name, plugin) {
|
|
111
|
+
if (this.$[name]) {
|
|
112
|
+
throw new Error(`Plugin with name ${name} already exists.`);
|
|
113
|
+
}
|
|
114
|
+
let modules = {};
|
|
115
|
+
Object.keys(plugin.modules).forEach((key) => {
|
|
116
|
+
modules[key] = plugin.modules[key]();
|
|
117
|
+
});
|
|
118
|
+
this.$[name] = {
|
|
119
|
+
plugin: plugin.define(modules),
|
|
120
|
+
modules
|
|
121
|
+
};
|
|
122
|
+
this.$g[name] = this.$[name].plugin;
|
|
123
|
+
}
|
|
102
124
|
/**
|
|
103
125
|
* Contacts the Component.onContact method through its reference.
|
|
104
126
|
* @param {string} name The name of the reference.
|
|
@@ -153,6 +175,15 @@ class Signature {
|
|
|
153
175
|
throw new Error(`Component '${component.name}' must render a single root element.`);
|
|
154
176
|
}
|
|
155
177
|
const newElement = template.content.firstElementChild;
|
|
178
|
+
// Preserve old ref attribute
|
|
179
|
+
let oldRef = ref.element.getAttribute("ref");
|
|
180
|
+
if (oldRef) {
|
|
181
|
+
newElement.setAttribute("ref", oldRef);
|
|
182
|
+
}
|
|
183
|
+
// Preserve si-group attribute
|
|
184
|
+
if (component.groups.length > 0) {
|
|
185
|
+
newElement.setAttribute("si-group", component.groups.join(" "));
|
|
186
|
+
}
|
|
156
187
|
this.render(template.content);
|
|
157
188
|
component.onRender?.(); // lifecycle hook
|
|
158
189
|
ref.element.replaceWith(newElement);
|
|
@@ -286,6 +317,8 @@ class Signature {
|
|
|
286
317
|
// Find all elements with the component name in the frame
|
|
287
318
|
for (const el of Array.from(frame.querySelectorAll(com)).concat(Array.from(frame.querySelectorAll(`[si-component="${com}"]`)))) {
|
|
288
319
|
const renderer = new component();
|
|
320
|
+
renderer.$ = this.$g; // injecting plugins
|
|
321
|
+
Object.freeze(renderer.$); // prevent plugins from being modified by components
|
|
289
322
|
renderer.onInit?.(); // lifecycle hook
|
|
290
323
|
if (el instanceof HTMLElement) {
|
|
291
324
|
// Fill the renderer's content
|
|
@@ -411,6 +444,10 @@ class Signature {
|
|
|
411
444
|
this.render(body.content);
|
|
412
445
|
renderer.onRender?.(); // lifecycle hook
|
|
413
446
|
const mountEl = body.firstElementChild;
|
|
447
|
+
// Processing group
|
|
448
|
+
if (renderer?.groups.length > 0) {
|
|
449
|
+
mountEl.setAttribute("si-group", renderer.groups.join(" "));
|
|
450
|
+
}
|
|
414
451
|
// Processing ref
|
|
415
452
|
if (el.hasAttribute("ref") || renderer.options.generateRefIfNotSpecified) {
|
|
416
453
|
let refName = el.getAttribute("ref");
|
|
@@ -433,6 +470,14 @@ class Signature {
|
|
|
433
470
|
update: () => this.updateRef(refName)
|
|
434
471
|
};
|
|
435
472
|
}
|
|
473
|
+
// Copying another attributes from the original element to the new one, with exceptions
|
|
474
|
+
for (const attr of Array.from(el.attributes)) {
|
|
475
|
+
const name = attr.name;
|
|
476
|
+
if (renderer.props[name] !== undefined || name === "ref" || name === "si-component" || name === "si-group")
|
|
477
|
+
continue;
|
|
478
|
+
if (!mountEl.hasAttribute(name))
|
|
479
|
+
mountEl.setAttribute(name, attr.value);
|
|
480
|
+
}
|
|
436
481
|
el.replaceWith(body.firstElementChild);
|
|
437
482
|
renderer.onMount?.(mountEl); // lifecycle hook
|
|
438
483
|
});
|
|
@@ -443,12 +488,14 @@ class Signature {
|
|
|
443
488
|
|
|
444
489
|
class Component {
|
|
445
490
|
content;
|
|
491
|
+
groups = [];
|
|
446
492
|
options = {
|
|
447
493
|
generateRefIfNotSpecified: false,
|
|
448
494
|
};
|
|
449
495
|
ref;
|
|
450
496
|
props = {};
|
|
451
497
|
data = {};
|
|
498
|
+
$ = {};
|
|
452
499
|
onInit() {
|
|
453
500
|
}
|
|
454
501
|
;
|
|
@@ -586,9 +633,13 @@ function html(strings, ...values) {
|
|
|
586
633
|
function unsafeHTML(value) {
|
|
587
634
|
return { type: "unsafeHTML", value: value };
|
|
588
635
|
}
|
|
636
|
+
// todo: make a loop function
|
|
637
|
+
|
|
638
|
+
class Plugin {
|
|
639
|
+
}
|
|
589
640
|
|
|
590
641
|
function index () {
|
|
591
642
|
return new Signature();
|
|
592
643
|
}
|
|
593
644
|
|
|
594
|
-
export { Component, Library, Prop, Signature, index as default, html, unsafeHTML };
|
|
645
|
+
export { Component, Library, Plugin, Prop, Signature, index as default, html, unsafeHTML };
|
package/bundle/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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'.","stack-overflow":"Stack Overflow detected: possible recursive component rendering.","render-async-failed":"Error during asynchronous rendering of the component #component."};let n=0;class r{components={};refs={};libs={};bank=new Map;constructor(){}add(e,t){const n="string"==typeof t?t:e.name;this.components[n]&&console.warn(new Error(`Component with name ${n} already exists.`)),this.components[n]=e}register(e,...t){this.libs[e.name]&&console.warn(new Error(`Library with name ${e.name} already exists.`));const n=e.list().filter(e=>!(e.name in t));this.libs[e.name]={name:e.name,version:e.version,author:e.author,components:n.map(e=>e.name),dependencies:e.libs};for(const t of n)this.add(t.component,`${e.name}-${t.name}`)}lib(e){return this.libs[e]}libraries(){const e=e=>{let t=e.name;return e.version&&(t+=`@${e.version}`),e.author&&(t+=`#${e.author}`),t},t=(n,r=new Set)=>{const o={};for(const[s,i]of Object.entries(n)){const n=e(i);r.has(n)||(r.add(n),o[n]={components:i.components,dependencies:t(i.dependencies,r)})}return o};return t(this.libs)}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;let r={strings:Object.assign([],{raw:[]}),values:[]};try{r=n.render()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:n.name,err:e}}const o=document.createElement("template");(e=>{r instanceof Promise?r.then(t=>{o.content.appendChild(this.templateToElement(t,n.name)),e()}).catch(e=>{throw{id:"unknown-from",from:n.name,err:e}}):"object"==typeof r&&(o.content.appendChild(this.templateToElement(r,n.name)),e())})(()=>{if(1!==o.content.children.length)throw new Error(`Component '${n.name}' must render a single root element.`);const e=o.content.firstElementChild;this.render(o.content),n.onRender?.(),t.element.replaceWith(e),t.element=e,n.onMount?.(e)})}contact(e,n){new Promise((t,r)=>{try{const t=document.querySelector(e);if(!t)return void r({id:"element-not-found",selector:e});const o=document.createElement("div");o.innerHTML=t.innerHTML,this.render(o),t.replaceChildren(...Array.from(o.childNodes)),n&&n()}catch(e){e instanceof Error?e instanceof RangeError&&e.message.includes("stack")?r({id:"stack-overflow",err:e}):r({id:"unknown",err:e}):r(e)}}).catch(e=>{let n=t[e.id];throw Object.keys(e).filter(e=>!(e in["id","err"])).forEach(t=>{n=n.replace(new RegExp(`#${t}`,"gm"),String(e[t]))}),window.SIGNATURE?.DEV_MODE&&console.log(e),e.id in["unknown","unknown-from","render-async-failed"]?console.error(`[${e.id}] ${n}`,e.err):console.error(`[${e.id}] ${n}`),"Page rendering was interrupted by Signature due to the above error."})}templateToString(e){let t="";for(let n=0;n<e.strings.length;n++)t+=e.strings[n],n<e.values.length&&(t+=`\x3c!--si-mark-${n}--\x3e`);return t}fillTemplate(e,t){let n;return this.bank.has(e.strings.join("@@"))?n=this.bank.get(e.strings.join("@@"))?.cloneNode(!0):(n=document.createElement("template"),n.innerHTML=t,this.bank.set(e.strings.join("@@"),n.cloneNode(!0))),(()=>{let t,r=document.createTreeWalker(n.content,NodeFilter.SHOW_COMMENT),o=[];for(;t=r.nextNode();)/si-mark-\d+/gm.test(t.nodeValue??"")&&o.push(t);for(const t of o){const n=e.values[Number((t.nodeValue??"").match(/si-mark-(\d+)/m)[1])];if("object"==typeof n&&"unsafeHTML"===n.type){let e=document.createElement("div");for(e.innerHTML=n.value;e.firstChild;)t.parentNode?.insertBefore(e.firstChild,t);t.remove()}else t.replaceWith(document.createTextNode(String(n)))}})(),(()=>{let t,r=document.createTreeWalker(n.content,NodeFilter.SHOW_ELEMENT);for(;t=r.nextNode();)for(const n of Array.from(t.attributes))if(/<!--si-mark-\d+-->/gm.test(n.value)){const r=n.value.match(/si-mark-(\d+)/m);if(r){const o=e.values[Number(r[1])];t.setAttribute(n.name,String(o))}}})(),n}templateToElement(e,t){const n=this.templateToString(e),r=this.fillTemplate(e,n);if(1!==r.content.children.length)throw{id:"multiple-root-elements",elements:r.innerHTML,component:t};return r.content.firstElementChild}render(t){for(const r of Object.keys(this.components)){const o=this.components[r];for(const s of Array.from(t.querySelectorAll(r)).concat(Array.from(t.querySelectorAll(`[si-component="${r}"]`)))){const t=new o;if(t.onInit?.(),s instanceof HTMLElement){t.content=s.innerHTML.trim();for(const e of Object.keys(t.props)){const n=s.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;case"array":try{o=JSON.parse(n)}catch(t){throw{id:"invalid-value-for-property",component:r,prop:e,value:n,attr: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 i=document.createElement("template");let a={strings:Object.assign([],{raw:[]}),values:[]};try{a=t.render()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:t.name,err:e}}(e=>{if(a instanceof Promise)try{a.then(t=>{i.appendChild(this.templateToElement(t,r)),e()}).catch(e=>{throw{id:"unknown-from",from:t.name,err:e}})}catch(e){throw{id:"render-async-failed",component:r,err:e}}else"object"==typeof a&&(i.appendChild(this.templateToElement(a,r)),e())})(()=>{this.render(i.content),t.onRender?.();const o=i.firstElementChild;if(s.hasAttribute("ref")||t.options.generateRefIfNotSpecified){let i=s.getAttribute("ref");if(null===i&&(i=""),n++,""===i&&(i=`r${n}${Math.random().toString(36).substring(2,15)}${n}`),this.refs[i])throw{id:"ref-collision",ref:i,component:r};this.refs[i]=new e(t,o),o.setAttribute("ref",i),t.ref={id:i,contact:(...e)=>this.contactWith(i,...e),update:()=>this.updateRef(i)}}s.replaceWith(i.firstElementChild),t.onMount?.(o)})}}}}class o{content;options={generateRefIfNotSpecified:!1};ref;props={};data={};onInit(){}onRender(){}onMount(e){}onContact(...e){}onPropsParsed(){}onPropParsed(e,t){}}class s{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"array":return Array.isArray(e);case"null":return null===e;default:return!1}}}class i{name;version;author;libs={};components={};constructor(e,t,n){this.name=e,this.author=t,this.version=n}add(e,t){const n="string"==typeof t?t:e.name;this.components[n]&&console.warn(new Error(`Component with name ${n} already exists.`)),this.components[n]=e}register(e,...t){this.libs[e.name]&&console.warn(new Error(`Library with name ${e.name} already exists in ${this.name}.`));const n=e.list().filter(e=>!(e.name in t));this.libs[e.name]={name:e.name,version:e.version,author:e.author,components:n.map(e=>e.name),dependencies:e.libs};for(const t of n)this.add(t.component,`${e.name}-${t.name}`)}get(e){return this.components[e]}lib(e){return this.libs[e]}list(){return Object.entries(this.components).map(([e,t])=>({component:t,name:e}))}}function a(e,...t){return{strings:e,values:t}}function c(e){return{type:"unsafeHTML",value:e}}function
|
|
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'.","stack-overflow":"Stack Overflow detected: possible recursive component rendering.","render-async-failed":"Error during asynchronous rendering of the component #component."};let n=0;class r{components={};refs={};libs={};bank=new Map;$={};$g={};constructor(){}add(e,t){const n="string"==typeof t?t:e.name;this.components[n]&&console.warn(new Error(`Component with name ${n} already exists.`)),this.components[n]=e}register(e,...t){this.libs[e.name]&&console.warn(new Error(`Library with name ${e.name} already exists.`));const n=e.list().filter(e=>!(e.name in t));this.libs[e.name]={name:e.name,version:e.version,author:e.author,components:n.map(e=>e.name),dependencies:e.libs};for(const t of n)this.add(t.component,`${e.name}-${t.name}`)}lib(e){return this.libs[e]}libraries(){const e=e=>{let t=e.name;return e.version&&(t+=`@${e.version}`),e.author&&(t+=`#${e.author}`),t},t=(n,r=new Set)=>{const o={};for(const[s,i]of Object.entries(n)){const n=e(i);r.has(n)||(r.add(n),o[n]={components:i.components,dependencies:t(i.dependencies,r)})}return o};return t(this.libs)}use(e,t){if(this.$[e])throw new Error(`Plugin with name ${e} already exists.`);let n={};Object.keys(t.modules).forEach(e=>{n[e]=t.modules[e]()}),this.$[e]={plugin:t.define(n),modules:n},this.$g[e]=this.$[e].plugin}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;let r={strings:Object.assign([],{raw:[]}),values:[]};try{r=n.render()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:n.name,err:e}}const o=document.createElement("template");(e=>{r instanceof Promise?r.then(t=>{o.content.appendChild(this.templateToElement(t,n.name)),e()}).catch(e=>{throw{id:"unknown-from",from:n.name,err:e}}):"object"==typeof r&&(o.content.appendChild(this.templateToElement(r,n.name)),e())})(()=>{if(1!==o.content.children.length)throw new Error(`Component '${n.name}' must render a single root element.`);const e=o.content.firstElementChild;let r=t.element.getAttribute("ref");r&&e.setAttribute("ref",r),n.groups.length>0&&e.setAttribute("si-group",n.groups.join(" ")),this.render(o.content),n.onRender?.(),t.element.replaceWith(e),t.element=e,n.onMount?.(e)})}contact(e,n){new Promise((t,r)=>{try{const t=document.querySelector(e);if(!t)return void r({id:"element-not-found",selector:e});const o=document.createElement("div");o.innerHTML=t.innerHTML,this.render(o),t.replaceChildren(...Array.from(o.childNodes)),n&&n()}catch(e){e instanceof Error?e instanceof RangeError&&e.message.includes("stack")?r({id:"stack-overflow",err:e}):r({id:"unknown",err:e}):r(e)}}).catch(e=>{let n=t[e.id];throw Object.keys(e).filter(e=>!(e in["id","err"])).forEach(t=>{n=n.replace(new RegExp(`#${t}`,"gm"),String(e[t]))}),window.SIGNATURE?.DEV_MODE&&console.log(e),e.id in["unknown","unknown-from","render-async-failed"]?console.error(`[${e.id}] ${n}`,e.err):console.error(`[${e.id}] ${n}`),"Page rendering was interrupted by Signature due to the above error."})}templateToString(e){let t="";for(let n=0;n<e.strings.length;n++)t+=e.strings[n],n<e.values.length&&(t+=`\x3c!--si-mark-${n}--\x3e`);return t}fillTemplate(e,t){let n;return this.bank.has(e.strings.join("@@"))?n=this.bank.get(e.strings.join("@@"))?.cloneNode(!0):(n=document.createElement("template"),n.innerHTML=t,this.bank.set(e.strings.join("@@"),n.cloneNode(!0))),(()=>{let t,r=document.createTreeWalker(n.content,NodeFilter.SHOW_COMMENT),o=[];for(;t=r.nextNode();)/si-mark-\d+/gm.test(t.nodeValue??"")&&o.push(t);for(const t of o){const n=e.values[Number((t.nodeValue??"").match(/si-mark-(\d+)/m)[1])];if("object"==typeof n&&"unsafeHTML"===n.type){let e=document.createElement("div");for(e.innerHTML=n.value;e.firstChild;)t.parentNode?.insertBefore(e.firstChild,t);t.remove()}else t.replaceWith(document.createTextNode(String(n)))}})(),(()=>{let t,r=document.createTreeWalker(n.content,NodeFilter.SHOW_ELEMENT);for(;t=r.nextNode();)for(const n of Array.from(t.attributes))if(/<!--si-mark-\d+-->/gm.test(n.value)){const r=n.value.match(/si-mark-(\d+)/m);if(r){const o=e.values[Number(r[1])];t.setAttribute(n.name,String(o))}}})(),n}templateToElement(e,t){const n=this.templateToString(e),r=this.fillTemplate(e,n);if(1!==r.content.children.length)throw{id:"multiple-root-elements",elements:r.innerHTML,component:t};return r.content.firstElementChild}render(t){for(const r of Object.keys(this.components)){const o=this.components[r];for(const s of Array.from(t.querySelectorAll(r)).concat(Array.from(t.querySelectorAll(`[si-component="${r}"]`)))){const t=new o;if(t.$=this.$g,Object.freeze(t.$),t.onInit?.(),s instanceof HTMLElement){t.content=s.innerHTML.trim();for(const e of Object.keys(t.props)){const n=s.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;case"array":try{o=JSON.parse(n)}catch(t){throw{id:"invalid-value-for-property",component:r,prop:e,value:n,attr: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 i=document.createElement("template");let a={strings:Object.assign([],{raw:[]}),values:[]};try{a=t.render()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:t.name,err:e}}(e=>{if(a instanceof Promise)try{a.then(t=>{i.appendChild(this.templateToElement(t,r)),e()}).catch(e=>{throw{id:"unknown-from",from:t.name,err:e}})}catch(e){throw{id:"render-async-failed",component:r,err:e}}else"object"==typeof a&&(i.appendChild(this.templateToElement(a,r)),e())})(()=>{this.render(i.content),t.onRender?.();const o=i.firstElementChild;if(t?.groups.length>0&&o.setAttribute("si-group",t.groups.join(" ")),s.hasAttribute("ref")||t.options.generateRefIfNotSpecified){let i=s.getAttribute("ref");if(null===i&&(i=""),n++,""===i&&(i=`r${n}${Math.random().toString(36).substring(2,15)}${n}`),this.refs[i])throw{id:"ref-collision",ref:i,component:r};this.refs[i]=new e(t,o),o.setAttribute("ref",i),t.ref={id:i,contact:(...e)=>this.contactWith(i,...e),update:()=>this.updateRef(i)}}for(const e of Array.from(s.attributes)){const n=e.name;void 0===t.props[n]&&"ref"!==n&&"si-component"!==n&&"si-group"!==n&&(o.hasAttribute(n)||o.setAttribute(n,e.value))}s.replaceWith(i.firstElementChild),t.onMount?.(o)})}}}}class o{content;groups=[];options={generateRefIfNotSpecified:!1};ref;props={};data={};$={};onInit(){}onRender(){}onMount(e){}onContact(...e){}onPropsParsed(){}onPropParsed(e,t){}}class s{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"array":return Array.isArray(e);case"null":return null===e;default:return!1}}}class i{name;version;author;libs={};components={};constructor(e,t,n){this.name=e,this.author=t,this.version=n}add(e,t){const n="string"==typeof t?t:e.name;this.components[n]&&console.warn(new Error(`Component with name ${n} already exists.`)),this.components[n]=e}register(e,...t){this.libs[e.name]&&console.warn(new Error(`Library with name ${e.name} already exists in ${this.name}.`));const n=e.list().filter(e=>!(e.name in t));this.libs[e.name]={name:e.name,version:e.version,author:e.author,components:n.map(e=>e.name),dependencies:e.libs};for(const t of n)this.add(t.component,`${e.name}-${t.name}`)}get(e){return this.components[e]}lib(e){return this.libs[e]}list(){return Object.entries(this.components).map(([e,t])=>({component:t,name:e}))}}function a(e,...t){return{strings:e,values:t}}function c(e){return{type:"unsafeHTML",value:e}}class l{}function p(){return new r}export{o as Component,i as Library,l as Plugin,s as Prop,r as Signature,p as default,a as html,c as unsafeHTML};
|
package/dist/Component.js
CHANGED
package/dist/Plugin.js
ADDED
package/dist/Signature.js
CHANGED
|
@@ -6,6 +6,8 @@ export default class Signature {
|
|
|
6
6
|
refs = {};
|
|
7
7
|
libs = {};
|
|
8
8
|
bank = new Map();
|
|
9
|
+
$ = {};
|
|
10
|
+
$g = {};
|
|
9
11
|
constructor() {
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
@@ -79,6 +81,26 @@ export default class Signature {
|
|
|
79
81
|
};
|
|
80
82
|
return resolve(this.libs);
|
|
81
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Registers a plugin in the signature.
|
|
86
|
+
* @import {Plugin} from "./Plugin.js";
|
|
87
|
+
* @param {string} name The name of the plugin.
|
|
88
|
+
* @param {Plugin} plugin The plugin to register.
|
|
89
|
+
*/
|
|
90
|
+
use(name, plugin) {
|
|
91
|
+
if (this.$[name]) {
|
|
92
|
+
throw new Error(`Plugin with name ${name} already exists.`);
|
|
93
|
+
}
|
|
94
|
+
let modules = {};
|
|
95
|
+
Object.keys(plugin.modules).forEach((key) => {
|
|
96
|
+
modules[key] = plugin.modules[key]();
|
|
97
|
+
});
|
|
98
|
+
this.$[name] = {
|
|
99
|
+
plugin: plugin.define(modules),
|
|
100
|
+
modules
|
|
101
|
+
};
|
|
102
|
+
this.$g[name] = this.$[name].plugin;
|
|
103
|
+
}
|
|
82
104
|
/**
|
|
83
105
|
* Contacts the Component.onContact method through its reference.
|
|
84
106
|
* @param {string} name The name of the reference.
|
|
@@ -133,6 +155,15 @@ export default class Signature {
|
|
|
133
155
|
throw new Error(`Component '${component.name}' must render a single root element.`);
|
|
134
156
|
}
|
|
135
157
|
const newElement = template.content.firstElementChild;
|
|
158
|
+
// Preserve old ref attribute
|
|
159
|
+
let oldRef = ref.element.getAttribute("ref");
|
|
160
|
+
if (oldRef) {
|
|
161
|
+
newElement.setAttribute("ref", oldRef);
|
|
162
|
+
}
|
|
163
|
+
// Preserve si-group attribute
|
|
164
|
+
if (component.groups.length > 0) {
|
|
165
|
+
newElement.setAttribute("si-group", component.groups.join(" "));
|
|
166
|
+
}
|
|
136
167
|
this.render(template.content);
|
|
137
168
|
component.onRender?.(); // lifecycle hook
|
|
138
169
|
ref.element.replaceWith(newElement);
|
|
@@ -266,6 +297,8 @@ export default class Signature {
|
|
|
266
297
|
// Find all elements with the component name in the frame
|
|
267
298
|
for (const el of Array.from(frame.querySelectorAll(com)).concat(Array.from(frame.querySelectorAll(`[si-component="${com}"]`)))) {
|
|
268
299
|
const renderer = new component();
|
|
300
|
+
renderer.$ = this.$g; // injecting plugins
|
|
301
|
+
Object.freeze(renderer.$); // prevent plugins from being modified by components
|
|
269
302
|
renderer.onInit?.(); // lifecycle hook
|
|
270
303
|
if (el instanceof HTMLElement) {
|
|
271
304
|
// Fill the renderer's content
|
|
@@ -391,6 +424,10 @@ export default class Signature {
|
|
|
391
424
|
this.render(body.content);
|
|
392
425
|
renderer.onRender?.(); // lifecycle hook
|
|
393
426
|
const mountEl = body.firstElementChild;
|
|
427
|
+
// Processing group
|
|
428
|
+
if (renderer?.groups.length > 0) {
|
|
429
|
+
mountEl.setAttribute("si-group", renderer.groups.join(" "));
|
|
430
|
+
}
|
|
394
431
|
// Processing ref
|
|
395
432
|
if (el.hasAttribute("ref") || renderer.options.generateRefIfNotSpecified) {
|
|
396
433
|
let refName = el.getAttribute("ref");
|
|
@@ -413,6 +450,14 @@ export default class Signature {
|
|
|
413
450
|
update: () => this.updateRef(refName)
|
|
414
451
|
};
|
|
415
452
|
}
|
|
453
|
+
// Copying another attributes from the original element to the new one, with exceptions
|
|
454
|
+
for (const attr of Array.from(el.attributes)) {
|
|
455
|
+
const name = attr.name;
|
|
456
|
+
if (renderer.props[name] !== undefined || name === "ref" || name === "si-component" || name === "si-group")
|
|
457
|
+
continue;
|
|
458
|
+
if (!mountEl.hasAttribute(name))
|
|
459
|
+
mountEl.setAttribute(name, attr.value);
|
|
460
|
+
}
|
|
416
461
|
el.replaceWith(body.firstElementChild);
|
|
417
462
|
renderer.onMount?.(mountEl); // lifecycle hook
|
|
418
463
|
});
|
package/dist/d/Component.d.ts
CHANGED
|
@@ -5,10 +5,12 @@ import type { Ref } from "./types/Component.js";
|
|
|
5
5
|
export default abstract class Component implements component {
|
|
6
6
|
abstract readonly name: string;
|
|
7
7
|
content?: string;
|
|
8
|
+
groups: string[];
|
|
8
9
|
options: Options;
|
|
9
10
|
ref?: Ref;
|
|
10
11
|
readonly props: Record<string, Prop>;
|
|
11
12
|
readonly data: Record<string, string | number | boolean | null>;
|
|
13
|
+
$: Record<string, unknown>;
|
|
12
14
|
abstract render(): html | Promise<html>;
|
|
13
15
|
onInit?(): void;
|
|
14
16
|
onRender?(): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default abstract class Plugin {
|
|
2
|
+
/**
|
|
3
|
+
* Modules are mini plugins that are required for the plugin to work, they are processed and stored before the plugin itself is installed and stored in a safe place.
|
|
4
|
+
*/
|
|
5
|
+
readonly abstract modules: Record<string, () => Record<string, unknown>>;
|
|
6
|
+
/**
|
|
7
|
+
* The function returns an object that will be installed as a plugin in Signature.$
|
|
8
|
+
* @param modules
|
|
9
|
+
* @returns {Record<string, unknown>} The object that will be installed as a plugin
|
|
10
|
+
*/
|
|
11
|
+
abstract define(modules: Record<string, Record<string, unknown>>): Record<string, unknown>;
|
|
12
|
+
}
|
package/dist/d/Signature.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ComponentConstructor } from "./Component.js";
|
|
2
2
|
import Library, { LibMeta } from "./Library.js";
|
|
3
|
+
import Plugin from "./Plugin.js";
|
|
3
4
|
type ResolvedLib = {
|
|
4
5
|
components: string[];
|
|
5
6
|
dependencies: Record<string, ResolvedLib>;
|
|
@@ -9,6 +10,8 @@ export default class Signature {
|
|
|
9
10
|
private refs;
|
|
10
11
|
private libs;
|
|
11
12
|
private bank;
|
|
13
|
+
private $;
|
|
14
|
+
private $g;
|
|
12
15
|
constructor();
|
|
13
16
|
/**
|
|
14
17
|
* Adds a component to the signature.
|
|
@@ -34,6 +37,13 @@ export default class Signature {
|
|
|
34
37
|
* @return {Record<string, ResolvedLib>} A object of formatted libraries with their components and dependencies.
|
|
35
38
|
*/
|
|
36
39
|
libraries(): Record<string, ResolvedLib>;
|
|
40
|
+
/**
|
|
41
|
+
* Registers a plugin in the signature.
|
|
42
|
+
* @import {Plugin} from "./Plugin.js";
|
|
43
|
+
* @param {string} name The name of the plugin.
|
|
44
|
+
* @param {Plugin} plugin The plugin to register.
|
|
45
|
+
*/
|
|
46
|
+
use(name: string, plugin: Plugin): void;
|
|
37
47
|
/**
|
|
38
48
|
* Contacts the Component.onContact method through its reference.
|
|
39
49
|
* @param {string} name The name of the reference.
|
package/dist/d/html.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
type HTMLTemplate = {
|
|
2
2
|
strings: TemplateStringsArray;
|
|
3
3
|
values: any[];
|
|
4
4
|
};
|
|
5
|
-
export
|
|
5
|
+
export default function html(strings: TemplateStringsArray, ...values: any[]): HTMLTemplate;
|
|
6
|
+
type unsafeHTMLTemplate = {
|
|
6
7
|
type: "unsafeHTML";
|
|
7
8
|
value: any;
|
|
8
9
|
};
|
|
10
|
+
export declare function unsafeHTML(value: any): unsafeHTMLTemplate;
|
|
11
|
+
export declare function joinTemplates(...templates: HTMLTemplate[]): HTMLTemplate;
|
|
12
|
+
export {};
|
package/dist/d/index.d.ts
CHANGED
|
@@ -37,6 +37,10 @@ interface Component {
|
|
|
37
37
|
* Optional content that is specified in the component tag.
|
|
38
38
|
*/
|
|
39
39
|
content?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Optional groups that the component belongs to.
|
|
42
|
+
*/
|
|
43
|
+
groups: string[];
|
|
40
44
|
/**
|
|
41
45
|
* Defining component properties.
|
|
42
46
|
*/
|
|
@@ -45,6 +49,10 @@ interface Component {
|
|
|
45
49
|
* The properties of the component.
|
|
46
50
|
*/
|
|
47
51
|
data: Record<string, string | number | boolean | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Plugins are stored here.
|
|
54
|
+
*/
|
|
55
|
+
$: Record<string, unknown>;
|
|
48
56
|
/**
|
|
49
57
|
* Returns the component as a string (template).
|
|
50
58
|
* @returns {html | Promise<html>} The rendered component as a string (template).
|
package/dist/html.js
CHANGED
|
@@ -4,3 +4,16 @@ export default function html(strings, ...values) {
|
|
|
4
4
|
export function unsafeHTML(value) {
|
|
5
5
|
return { type: "unsafeHTML", value: value };
|
|
6
6
|
}
|
|
7
|
+
export function joinTemplates(...templates) {
|
|
8
|
+
let strings = [];
|
|
9
|
+
let values = [];
|
|
10
|
+
templates.forEach((t) => {
|
|
11
|
+
strings.push(...t.strings);
|
|
12
|
+
values.push(...t.values);
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
strings: strings,
|
|
16
|
+
values: values
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// todo: make a loop function
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
package/src/Component.ts
CHANGED
|
@@ -8,6 +8,8 @@ export default abstract class Component implements component {
|
|
|
8
8
|
abstract readonly name: string;
|
|
9
9
|
content?: string;
|
|
10
10
|
|
|
11
|
+
groups: string[] = [];
|
|
12
|
+
|
|
11
13
|
options: Options = {
|
|
12
14
|
generateRefIfNotSpecified: false,
|
|
13
15
|
}
|
|
@@ -17,6 +19,8 @@ export default abstract class Component implements component {
|
|
|
17
19
|
readonly props: Record<string, Prop> = {};
|
|
18
20
|
readonly data: Record<string, string | number | boolean | null> = {};
|
|
19
21
|
|
|
22
|
+
public $: Record<string, unknown> = {};
|
|
23
|
+
|
|
20
24
|
abstract render(): html | Promise<html>;
|
|
21
25
|
|
|
22
26
|
onInit?(): void {
|
package/src/Plugin.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default abstract class Plugin {
|
|
2
|
+
/**
|
|
3
|
+
* Modules are mini plugins that are required for the plugin to work, they are processed and stored before the plugin itself is installed and stored in a safe place.
|
|
4
|
+
*/
|
|
5
|
+
readonly abstract modules: Record<string, () => Record<string, unknown>>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The function returns an object that will be installed as a plugin in Signature.$
|
|
9
|
+
* @param modules
|
|
10
|
+
* @returns {Record<string, unknown>} The object that will be installed as a plugin
|
|
11
|
+
*/
|
|
12
|
+
public abstract define(modules: Record<string, Record<string, unknown>>): Record<string, unknown>;
|
|
13
|
+
}
|
package/src/Signature.ts
CHANGED
|
@@ -3,7 +3,9 @@ import Ref from "./Ref.js";
|
|
|
3
3
|
import ErrorUnion from "./types/Errors.js";
|
|
4
4
|
import Errors from "./Errors.js";
|
|
5
5
|
import Library, {LibMeta} from "./Library.js";
|
|
6
|
-
import {html} from "./types/Component";
|
|
6
|
+
import {html} from "./types/Component.js";
|
|
7
|
+
import MetaPlugin from "./types/MetaPlugin.js";
|
|
8
|
+
import Plugin from "./Plugin.js";
|
|
7
9
|
|
|
8
10
|
let _counter = 0;
|
|
9
11
|
|
|
@@ -16,7 +18,10 @@ export default class Signature {
|
|
|
16
18
|
private components: Record<string, ComponentConstructor> = {};
|
|
17
19
|
private refs: Record<string, Ref> = {};
|
|
18
20
|
private libs: Record<string, LibMeta> = {};
|
|
19
|
-
private bank: Map<string, HTMLTemplateElement> = new Map<string, HTMLTemplateElement>()
|
|
21
|
+
private bank: Map<string, HTMLTemplateElement> = new Map<string, HTMLTemplateElement>();
|
|
22
|
+
|
|
23
|
+
private $: Record<string, MetaPlugin> = {};
|
|
24
|
+
private $g: Record<string, unknown> = {};
|
|
20
25
|
|
|
21
26
|
constructor() {
|
|
22
27
|
}
|
|
@@ -109,6 +114,31 @@ export default class Signature {
|
|
|
109
114
|
return resolve(this.libs);
|
|
110
115
|
}
|
|
111
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Registers a plugin in the signature.
|
|
119
|
+
* @import {Plugin} from "./Plugin.js";
|
|
120
|
+
* @param {string} name The name of the plugin.
|
|
121
|
+
* @param {Plugin} plugin The plugin to register.
|
|
122
|
+
*/
|
|
123
|
+
public use(name: string, plugin: Plugin): void {
|
|
124
|
+
if (this.$[name]) {
|
|
125
|
+
throw new Error(`Plugin with name ${name} already exists.`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let modules: Record<string, Record<string, unknown>> = {};
|
|
129
|
+
|
|
130
|
+
Object.keys(plugin.modules).forEach((key) => {
|
|
131
|
+
modules[key] = plugin.modules[key]();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.$[name] = {
|
|
135
|
+
plugin: plugin.define(modules),
|
|
136
|
+
modules
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.$g[name] = this.$[name].plugin;
|
|
140
|
+
}
|
|
141
|
+
|
|
112
142
|
/**
|
|
113
143
|
* Contacts the Component.onContact method through its reference.
|
|
114
144
|
* @param {string} name The name of the reference.
|
|
@@ -172,6 +202,17 @@ export default class Signature {
|
|
|
172
202
|
|
|
173
203
|
const newElement = template.content.firstElementChild as Element;
|
|
174
204
|
|
|
205
|
+
// Preserve old ref attribute
|
|
206
|
+
let oldRef = ref.element.getAttribute("ref");
|
|
207
|
+
if (oldRef) {
|
|
208
|
+
newElement.setAttribute("ref", oldRef);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Preserve si-group attribute
|
|
212
|
+
if (component.groups.length > 0) {
|
|
213
|
+
newElement.setAttribute("si-group", component.groups.join(" "));
|
|
214
|
+
}
|
|
215
|
+
|
|
175
216
|
this.render(template.content);
|
|
176
217
|
|
|
177
218
|
component.onRender?.(); // lifecycle hook
|
|
@@ -341,6 +382,10 @@ export default class Signature {
|
|
|
341
382
|
// Find all elements with the component name in the frame
|
|
342
383
|
for (const el of Array.from(frame.querySelectorAll(com)).concat(Array.from(frame.querySelectorAll(`[si-component="${com}"]`)))) {
|
|
343
384
|
const renderer: Component = new component();
|
|
385
|
+
|
|
386
|
+
renderer.$ = this.$g; // injecting plugins
|
|
387
|
+
Object.freeze(renderer.$); // prevent plugins from being modified by components
|
|
388
|
+
|
|
344
389
|
renderer.onInit?.(); // lifecycle hook
|
|
345
390
|
|
|
346
391
|
if (el instanceof HTMLElement) {
|
|
@@ -476,6 +521,11 @@ export default class Signature {
|
|
|
476
521
|
|
|
477
522
|
const mountEl: Element = body.firstElementChild as Element;
|
|
478
523
|
|
|
524
|
+
// Processing group
|
|
525
|
+
if (renderer?.groups.length > 0) {
|
|
526
|
+
mountEl.setAttribute("si-group", renderer.groups.join(" "));
|
|
527
|
+
}
|
|
528
|
+
|
|
479
529
|
// Processing ref
|
|
480
530
|
if (el.hasAttribute("ref") || renderer.options.generateRefIfNotSpecified) {
|
|
481
531
|
let refName = el.getAttribute("ref");
|
|
@@ -506,6 +556,15 @@ export default class Signature {
|
|
|
506
556
|
};
|
|
507
557
|
}
|
|
508
558
|
|
|
559
|
+
// Copying another attributes from the original element to the new one, with exceptions
|
|
560
|
+
for (const attr of Array.from(el.attributes)) {
|
|
561
|
+
const name: string = attr.name;
|
|
562
|
+
|
|
563
|
+
if (renderer.props[name] !== undefined || name === "ref" || name === "si-component" || name === "si-group") continue;
|
|
564
|
+
|
|
565
|
+
if (!mountEl.hasAttribute(name)) mountEl.setAttribute(name, attr.value);
|
|
566
|
+
}
|
|
567
|
+
|
|
509
568
|
el.replaceWith(body.firstElementChild as Element);
|
|
510
569
|
|
|
511
570
|
renderer.onMount?.(mountEl); // lifecycle hook
|
package/src/html.ts
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
type HTMLTemplate = {
|
|
2
2
|
strings: TemplateStringsArray,
|
|
3
3
|
values: any[]
|
|
4
|
-
}
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export default function html(strings: TemplateStringsArray, ...values: any[]): HTMLTemplate {
|
|
5
7
|
return {strings, values};
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
type unsafeHTMLTemplate = { type: "unsafeHTML", value: any };
|
|
11
|
+
|
|
12
|
+
export function unsafeHTML(value: any): unsafeHTMLTemplate {
|
|
9
13
|
return {type: "unsafeHTML", value: value};
|
|
10
|
-
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function joinTemplates(...templates: HTMLTemplate[]): HTMLTemplate {
|
|
17
|
+
let strings: string[] = [];
|
|
18
|
+
let values: any[] = [];
|
|
19
|
+
|
|
20
|
+
templates.forEach((t) => {
|
|
21
|
+
strings.push(...t.strings);
|
|
22
|
+
values.push(...t.values);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
strings: strings as unknown as TemplateStringsArray,
|
|
27
|
+
values: values
|
|
28
|
+
} as HTMLTemplate;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// todo: make a loop function
|
package/src/index.ts
CHANGED
|
@@ -8,4 +8,5 @@ export {default as Signature} from './Signature.js';
|
|
|
8
8
|
export {default as Component} from './Component.js';
|
|
9
9
|
export {default as Prop} from './Prop.js';
|
|
10
10
|
export {default as Library} from './Library.js';
|
|
11
|
-
export {default as html, unsafeHTML} from './html.js';
|
|
11
|
+
export {default as html, unsafeHTML} from './html.js';
|
|
12
|
+
export {default as Plugin} from './Plugin.js';
|
package/src/types/Component.ts
CHANGED
|
@@ -47,6 +47,11 @@ interface Component {
|
|
|
47
47
|
*/
|
|
48
48
|
content?: string;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Optional groups that the component belongs to.
|
|
52
|
+
*/
|
|
53
|
+
groups: string[];
|
|
54
|
+
|
|
50
55
|
/**
|
|
51
56
|
* Defining component properties.
|
|
52
57
|
*/
|
|
@@ -57,6 +62,10 @@ interface Component {
|
|
|
57
62
|
*/
|
|
58
63
|
data: Record<string, string | number | boolean | null>;
|
|
59
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Plugins are stored here.
|
|
67
|
+
*/
|
|
68
|
+
$: Record<string, unknown>;
|
|
60
69
|
|
|
61
70
|
/**
|
|
62
71
|
* Returns the component as a string (template).
|