jails-js 5.0.0-beta.7 → 5.0.0
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/.babelrc +6 -0
- package/.editorconfig +2 -0
- package/README.md +109 -61
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/logo.svg +29 -0
- package/package.json +42 -39
- package/src/component.ts +146 -0
- package/src/element.ts +52 -0
- package/src/index.ts +18 -51
- package/src/template-system.ts +86 -0
- package/src/utils/{events.js → events.ts} +1 -1
- package/src/utils/index.ts +80 -0
- package/src/utils/pubsub.ts +20 -0
- package/tsconfig.json +104 -4
- package/types/component.d.ts +32 -0
- package/types/element.d.ts +311 -0
- package/types/index.d.ts +5 -0
- package/types/index.ts +41 -0
- package/types/template-system.d.ts +1 -0
- package/types/utils/events.d.ts +3 -0
- package/types/utils/index.d.ts +8 -0
- package/types/utils/pubsub.d.ts +2 -0
- package/webpack.config.js +13 -13
- package/dist/jails.js +0 -2
- package/dist/jails.js.map +0 -1
- package/src/Component.ts +0 -123
- package/src/Element.ts +0 -126
- package/src/Instances.ts +0 -1
- package/src/Scanner.ts +0 -26
- package/src/soda-config.js +0 -87
- package/src/utils/index.js +0 -41
- package/src/utils/pubsub.js +0 -25
package/package.json
CHANGED
@@ -1,41 +1,44 @@
|
|
1
1
|
{
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
2
|
+
"name": "jails-js",
|
3
|
+
"version": "5.0.0",
|
4
|
+
"description": "Jails | A Functional Component Library",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"types": "types/index.d.ts",
|
7
|
+
"scripts": {
|
8
|
+
"start": "webpack --watch --mode=development",
|
9
|
+
"build": "webpack --mode=production",
|
10
|
+
"publish-beta": "yarn build && npm version prerelease --preid=beta && npm publish --tag beta",
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
12
|
+
},
|
13
|
+
"repository": {
|
14
|
+
"type": "git",
|
15
|
+
"url": "https://github.com/jails-org/Jails.git"
|
16
|
+
},
|
17
|
+
"keywords": [
|
18
|
+
"Jails",
|
19
|
+
"Javascript",
|
20
|
+
"Component",
|
21
|
+
"Micro-Library"
|
22
|
+
],
|
23
|
+
"author": "javiani",
|
24
|
+
"license": "MIT",
|
25
|
+
"bugs": {
|
26
|
+
"url": "https://github.com/jails-org/Jails/issues"
|
27
|
+
},
|
28
|
+
"homepage": "https://github.com/jails-org/Jails",
|
29
|
+
"devDependencies": {
|
30
|
+
"@babel/core": "^7.2.2",
|
31
|
+
"@babel/preset-env": "^7.2.3",
|
32
|
+
"babel-loader": "^8.0.5",
|
33
|
+
"babel-plugin-transform-custom-element-classes": "^0.1.0",
|
34
|
+
"babel-preset-env": "^1.7.0",
|
35
|
+
"ts-loader": "^9.2.6",
|
36
|
+
"typescript": "^4.5.4",
|
37
|
+
"webpack": "^5.59.1",
|
38
|
+
"webpack-cli": "^3.2.1"
|
39
|
+
},
|
40
|
+
"dependencies": {
|
41
|
+
"morphdom": "^2.6.1",
|
42
|
+
"squirrelly": "^8.0.8"
|
43
|
+
}
|
41
44
|
}
|
package/src/component.ts
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
import morphdom from 'morphdom'
|
2
|
+
|
3
|
+
import { rAF, dup, buildtemplates } from './utils'
|
4
|
+
import { on, off, trigger } from './utils/events'
|
5
|
+
import { publish, subscribe } from './utils/pubsub'
|
6
|
+
|
7
|
+
export default function Component( elm, { module, dependencies, templates, components }) {
|
8
|
+
|
9
|
+
const options = getOptions( module )
|
10
|
+
buildtemplates( elm, components, templates )
|
11
|
+
|
12
|
+
const tplid = elm.getAttribute('tplid')
|
13
|
+
const template = tplid ? templates[tplid] : null
|
14
|
+
const state = { data: module.model ? dup(module.model) : {} }
|
15
|
+
|
16
|
+
const base = {
|
17
|
+
template,
|
18
|
+
elm,
|
19
|
+
dependencies,
|
20
|
+
publish,
|
21
|
+
subscribe,
|
22
|
+
|
23
|
+
main(fn) {
|
24
|
+
options.main = fn
|
25
|
+
},
|
26
|
+
|
27
|
+
unmount(fn) {
|
28
|
+
options.unmount = fn
|
29
|
+
},
|
30
|
+
|
31
|
+
onupdate(fn) {
|
32
|
+
options.onupdate = fn
|
33
|
+
},
|
34
|
+
|
35
|
+
on(eventName, selectorOrCallback, callback) {
|
36
|
+
on(elm, eventName, selectorOrCallback, callback)
|
37
|
+
},
|
38
|
+
|
39
|
+
off(eventName, callback) {
|
40
|
+
off(elm, eventName, callback)
|
41
|
+
},
|
42
|
+
|
43
|
+
trigger(eventName, target, args) {
|
44
|
+
if (target.constructor === String) {
|
45
|
+
Array
|
46
|
+
.from(elm.querySelectorAll(target))
|
47
|
+
.forEach( children => trigger(children, eventName, { args: args }) )
|
48
|
+
}
|
49
|
+
else trigger(elm, eventName, { args: target })
|
50
|
+
},
|
51
|
+
|
52
|
+
emit: ( ...args ) => {
|
53
|
+
trigger(elm, args.shift(), { args: args })
|
54
|
+
},
|
55
|
+
|
56
|
+
state: {
|
57
|
+
set( data ) {
|
58
|
+
if (data.constructor === Function) {
|
59
|
+
const newstate = dup(state.data)
|
60
|
+
data(newstate)
|
61
|
+
base.render(newstate)
|
62
|
+
} else {
|
63
|
+
base.render(data)
|
64
|
+
}
|
65
|
+
return new Promise((resolve) => rAF(_ => rAF(resolve)))
|
66
|
+
},
|
67
|
+
get() {
|
68
|
+
return dup(state.data)
|
69
|
+
}
|
70
|
+
},
|
71
|
+
|
72
|
+
render(data = state.data) {
|
73
|
+
|
74
|
+
if (!document.body.contains(elm)) {
|
75
|
+
return
|
76
|
+
}
|
77
|
+
|
78
|
+
state.data = Object.assign(state.data, data)
|
79
|
+
|
80
|
+
const newdata = dup(state.data)
|
81
|
+
const newhtml = base.template(options.view(newdata))
|
82
|
+
|
83
|
+
morphdom(elm, newhtml, morphdomOptions(elm, options))
|
84
|
+
|
85
|
+
rAF(_ => {
|
86
|
+
Array
|
87
|
+
.from(elm.querySelectorAll('[tplid]'))
|
88
|
+
.forEach((child: any) => {
|
89
|
+
child.options.onupdate(newdata)
|
90
|
+
child.base.render(newdata)
|
91
|
+
})
|
92
|
+
})
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
return { base, options }
|
97
|
+
}
|
98
|
+
|
99
|
+
const getOptions = (module) => ({
|
100
|
+
main: (a) => a,
|
101
|
+
unmount: (a) => a,
|
102
|
+
onupdate: (a) => a,
|
103
|
+
view: module.view ? module.view : (a) => a
|
104
|
+
})
|
105
|
+
|
106
|
+
const morphdomOptions = (_parent, options ) => ({
|
107
|
+
|
108
|
+
onNodeAdded: onUpdates(_parent, options),
|
109
|
+
onElUpdated: onUpdates(_parent, options),
|
110
|
+
onBeforeElChildrenUpdated: checkStatic,
|
111
|
+
onBeforeElUpdated: checkStatic,
|
112
|
+
|
113
|
+
getNodeKey(node) {
|
114
|
+
if (node.nodeType === 1 && node.getAttribute('tplid')){
|
115
|
+
return 'key' in node.attributes? node.attributes.key.value : node.getAttribute('tplid')
|
116
|
+
}
|
117
|
+
return false
|
118
|
+
}
|
119
|
+
})
|
120
|
+
|
121
|
+
const checkStatic = (node) => {
|
122
|
+
if ('html-static' in node.attributes) {
|
123
|
+
return false
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
const onUpdates = (_parent, options) => (node) => {
|
128
|
+
|
129
|
+
if (node.nodeType === 1) {
|
130
|
+
|
131
|
+
if (node.getAttribute && node.getAttribute('scope')) {
|
132
|
+
const json = node.getAttribute('scope')
|
133
|
+
const scope = (new Function(`return ${json}`))()
|
134
|
+
Array.from(node.querySelectorAll('[tplid]'))
|
135
|
+
.map((el) => {
|
136
|
+
const data = Object.assign({}, _parent.base.state.get(), scope)
|
137
|
+
options.onupdate(data)
|
138
|
+
el.base.render(data)
|
139
|
+
})
|
140
|
+
|
141
|
+
node.removeAttribute('scope')
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
return node
|
146
|
+
}
|
package/src/element.ts
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
import Component from './component'
|
2
|
+
import { purge } from './utils'
|
3
|
+
|
4
|
+
export default function Element(module, dependencies, templates, components) {
|
5
|
+
|
6
|
+
return class extends HTMLElement {
|
7
|
+
|
8
|
+
base: any
|
9
|
+
options: any
|
10
|
+
returns : any
|
11
|
+
__events: any
|
12
|
+
|
13
|
+
constructor() {
|
14
|
+
|
15
|
+
super()
|
16
|
+
|
17
|
+
const { base, options } = Component(this, { module, dependencies, templates, components })
|
18
|
+
|
19
|
+
this.base = base
|
20
|
+
this.options = options
|
21
|
+
this.returns = module.default(base)
|
22
|
+
}
|
23
|
+
|
24
|
+
connectedCallback() {
|
25
|
+
this.base.render()
|
26
|
+
|
27
|
+
if( this.returns && this.returns.constructor === Promise ) {
|
28
|
+
this.returns.then( _ => {
|
29
|
+
if( this.base ) {
|
30
|
+
this.options.main().forEach(f => f(this.base))
|
31
|
+
}
|
32
|
+
})
|
33
|
+
}else {
|
34
|
+
this.options.main().forEach(f => f(this.base))
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
disconnectedCallback() {
|
39
|
+
this.options.unmount(this.base)
|
40
|
+
if(!document.body.contains(this) ) {
|
41
|
+
this.__events = null
|
42
|
+
this.base.elm = null
|
43
|
+
this.base = null
|
44
|
+
purge(this)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
attributeChangedCallback() {
|
49
|
+
//TODO
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
package/src/index.ts
CHANGED
@@ -1,62 +1,29 @@
|
|
1
|
-
import { Element } from './Element'
|
2
|
-
import { Scanner } from './Scanner'
|
3
|
-
import { Component } from './Component'
|
4
|
-
import { Instances } from './Instances'
|
5
|
-
import { stripTemplateTag, dup } from './utils'
|
6
1
|
|
2
|
+
import { buildtemplates } from './utils'
|
3
|
+
import Element from './element'
|
4
|
+
|
5
|
+
const templates = {}
|
7
6
|
const components = {}
|
8
7
|
|
9
8
|
export default {
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
const body: HTMLElement = document.body
|
14
|
-
|
15
|
-
stripTemplateTag( body )
|
16
|
-
|
17
|
-
Scanner.observe( body, createElement, disposeElement )
|
18
|
-
Scanner.scan( body, createElement )
|
10
|
+
register( name:string, module:any, dependencies: object ) {
|
11
|
+
components[name] = { name, module, dependencies }
|
19
12
|
},
|
20
13
|
|
21
|
-
|
22
|
-
|
14
|
+
start() {
|
15
|
+
const body = document.body
|
16
|
+
buildtemplates( body, components, templates )
|
17
|
+
registerComponents()
|
23
18
|
}
|
24
19
|
}
|
25
20
|
|
26
|
-
const
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
if( !C ) {
|
36
|
-
console.warn(`Jails - Module ${name} not registered`)
|
37
|
-
return
|
38
|
-
}
|
39
|
-
|
40
|
-
const { module, dependencies } = C
|
41
|
-
ElementInterface.model = Object.assign({}, module.model? dup(module.model) : null, ElementInterface.model )
|
42
|
-
|
43
|
-
const base = Component({ name, element, dependencies, ElementInterface })
|
44
|
-
const promise = module.default(base)
|
45
|
-
|
46
|
-
if( promise && promise.then ) {
|
47
|
-
ElementInterface.promises.push(promise)
|
48
|
-
}
|
49
|
-
|
50
|
-
base.__initialize()
|
51
|
-
ElementInterface.view = module.view || ElementInterface.view
|
52
|
-
ElementInterface.instances[name] = { methods: {} }
|
53
|
-
})
|
54
|
-
|
55
|
-
ElementInterface.update()
|
56
|
-
}
|
57
|
-
|
58
|
-
const disposeElement = ( node ) => {
|
59
|
-
const instance = Instances.get(node)
|
60
|
-
if( Instances.get(node) )
|
61
|
-
instance.dispose()
|
21
|
+
const registerComponents = () => {
|
22
|
+
Object
|
23
|
+
.values( components )
|
24
|
+
.forEach( (component) => {
|
25
|
+
const { name, module, dependencies } = component
|
26
|
+
const Base = Element(module, dependencies, templates, components)
|
27
|
+
customElements.define(name, Base)
|
28
|
+
})
|
62
29
|
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import { compile, defaultConfig, filters } from 'squirrelly'
|
2
|
+
import { decodeHtmlEntities } from './utils'
|
3
|
+
|
4
|
+
defaultConfig.tags = ['{', '}']
|
5
|
+
defaultConfig.useWith = true
|
6
|
+
|
7
|
+
export default function templateSystem( element ) {
|
8
|
+
|
9
|
+
const tree = document.createElement('template')
|
10
|
+
|
11
|
+
tree.innerHTML = element.outerHTML.replace(/<\/?template[^>]*>/g, '')
|
12
|
+
|
13
|
+
directives(tree.content)
|
14
|
+
|
15
|
+
const html = decodeHtmlEntities(
|
16
|
+
tree.innerHTML
|
17
|
+
.replace(/html-(selected|checked|readonly|disabled|autoplay)=\"(.*)\"/g, `{@if ($2) }$1{/if}`)
|
18
|
+
.replace(/html-/g, '')
|
19
|
+
)
|
20
|
+
|
21
|
+
const template = compile(html, defaultConfig)
|
22
|
+
|
23
|
+
return ( data ) => {
|
24
|
+
return template(data, defaultConfig)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
/**@Directives */
|
29
|
+
|
30
|
+
const directives = (vdom) => {
|
31
|
+
|
32
|
+
const nodes = Array
|
33
|
+
.from(vdom.querySelectorAll('[html-for],[html-if],[html-foreach]'))
|
34
|
+
.reverse()
|
35
|
+
|
36
|
+
if (nodes.length) {
|
37
|
+
|
38
|
+
nodes.forEach(( node ) => {
|
39
|
+
if (node.getAttribute('html-foreach')) {
|
40
|
+
const instruction = node.getAttribute('html-foreach') || ''
|
41
|
+
const split = instruction.match(/(.*)\sin\s(.*)/) || ''
|
42
|
+
const varname = split[1]
|
43
|
+
const object = split[2]
|
44
|
+
node.removeAttribute('html-foreach')
|
45
|
+
node.setAttribute('scope', `{${varname} | JSON($key, '${varname}')}`)
|
46
|
+
const open = document.createTextNode(`{@foreach(${object}) => $key, ${varname}}`)
|
47
|
+
const close = document.createTextNode('{/foreach}')
|
48
|
+
wrap(open, node, close)
|
49
|
+
} else if (node.getAttribute('html-for')) {
|
50
|
+
const instruction = node.getAttribute('html-for') || ''
|
51
|
+
const split = instruction.match(/(.*)\sin\s(.*)/) || ''
|
52
|
+
const varname = split[1]
|
53
|
+
const object = split[2]
|
54
|
+
node.removeAttribute('html-for')
|
55
|
+
node.setAttribute('scope', `{${varname} | JSON($index, '${varname}')}`)
|
56
|
+
const open = document.createTextNode(`{@each(${object}) => ${varname}, $index}`)
|
57
|
+
const close = document.createTextNode('{/each}')
|
58
|
+
wrap(open, node, close)
|
59
|
+
} else if (node.getAttribute('html-if')) {
|
60
|
+
const instruction = node.getAttribute('html-if')
|
61
|
+
node.removeAttribute('html-if')
|
62
|
+
const open = document.createTextNode(`{@if (${instruction}) }`)
|
63
|
+
const close = document.createTextNode('{/if}')
|
64
|
+
wrap(open, node, close)
|
65
|
+
}
|
66
|
+
})
|
67
|
+
}
|
68
|
+
|
69
|
+
return vdom
|
70
|
+
}
|
71
|
+
|
72
|
+
filters.define('JSON', (scope, index, varname) => {
|
73
|
+
|
74
|
+
const key = index.constructor == String ? '$key' : '$index'
|
75
|
+
const newobject = { $index: index } as any
|
76
|
+
|
77
|
+
newobject[varname] = scope
|
78
|
+
newobject[key] = index
|
79
|
+
|
80
|
+
return JSON.stringify(newobject)
|
81
|
+
})
|
82
|
+
|
83
|
+
const wrap = (open, node, close) => {
|
84
|
+
node.parentNode?.insertBefore(open, node)
|
85
|
+
node.parentNode?.insertBefore(close, node.nextSibling)
|
86
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import templateSystem from '../template-system'
|
2
|
+
|
3
|
+
const textarea = document.createElement('textarea')
|
4
|
+
|
5
|
+
export const rAF = (fn) => {
|
6
|
+
if (requestAnimationFrame)
|
7
|
+
return requestAnimationFrame(fn)
|
8
|
+
else
|
9
|
+
return setTimeout(fn, 1000 / 60)
|
10
|
+
}
|
11
|
+
|
12
|
+
export const uuid = () => {
|
13
|
+
return 'xxxxxxxx'.replace(/[xy]/g, (c) => {
|
14
|
+
const r = Math.random() * 8 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
|
15
|
+
return v.toString(8)
|
16
|
+
})
|
17
|
+
}
|
18
|
+
|
19
|
+
export const stripTemplateTag = ( element ) => {
|
20
|
+
const templates = Array.from(element.querySelectorAll('template'))
|
21
|
+
// https://gist.github.com/harmenjanssen/07e425248779c65bc5d11b02fb913274
|
22
|
+
templates.forEach((template) => {
|
23
|
+
template.parentNode?.replaceChild(template.content, template)
|
24
|
+
stripTemplateTag(template.content)
|
25
|
+
})
|
26
|
+
}
|
27
|
+
|
28
|
+
export const dup = (o) => {
|
29
|
+
return JSON.parse(JSON.stringify(o))
|
30
|
+
}
|
31
|
+
|
32
|
+
export const createTemplateId = (element, templates ) => {
|
33
|
+
|
34
|
+
const tplid = element.getAttribute('tplid')
|
35
|
+
|
36
|
+
if (!tplid) {
|
37
|
+
const id = uuid()
|
38
|
+
element.setAttribute('tplid', id)
|
39
|
+
templates[id] = templateSystem(element)
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
export const buildtemplates = ( target, components, templates ) => {
|
44
|
+
|
45
|
+
return Array
|
46
|
+
.from(target.querySelectorAll('*'))
|
47
|
+
.filter((node) => node.tagName.toLowerCase() in components)
|
48
|
+
.reverse()
|
49
|
+
.map((node) => {
|
50
|
+
Array.from(node.querySelectorAll('template'))
|
51
|
+
.map((template) => buildtemplates(template.content, components, templates))
|
52
|
+
createTemplateId(node, templates)
|
53
|
+
return node
|
54
|
+
})
|
55
|
+
}
|
56
|
+
|
57
|
+
export const decodeHtmlEntities = ( str ) => {
|
58
|
+
textarea.innerHTML = str
|
59
|
+
return textarea.value
|
60
|
+
}
|
61
|
+
|
62
|
+
// http://crockford.com/javascript/memory/leak.html
|
63
|
+
export const purge = (d) => {
|
64
|
+
var a = d.attributes, i, l, n
|
65
|
+
if (a) {
|
66
|
+
for (i = a.length - 1; i >= 0; i -= 1) {
|
67
|
+
n = a[i].name
|
68
|
+
if (typeof d[n] === 'function') {
|
69
|
+
d[n] = null
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
a = d.childNodes
|
74
|
+
if (a) {
|
75
|
+
l = a.length
|
76
|
+
for (i = 0; i < l; i += 1) {
|
77
|
+
purge(d.childNodes[i])
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
const topics: any = {}
|
2
|
+
const _async: any = {}
|
3
|
+
|
4
|
+
export const publish = (name, params) => {
|
5
|
+
_async[name] = Object.assign({}, _async[name], params)
|
6
|
+
if (topics[name])
|
7
|
+
topics[name].forEach(topic => topic(params))
|
8
|
+
}
|
9
|
+
|
10
|
+
export const subscribe = (name, method) => {
|
11
|
+
topics[name] = topics[name] || []
|
12
|
+
topics[name].push(method)
|
13
|
+
if (name in _async) {
|
14
|
+
method(_async[name])
|
15
|
+
}
|
16
|
+
return () => {
|
17
|
+
topics[name] = topics[name].filter( fn => fn != method )
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|