enigmatic 0.21.7 → 0.23.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/README.md +181 -44
- package/__tests__/enigmatic.test.js +328 -0
- package/components.js +166 -13
- package/enigmatic.js +134 -150
- package/index.html +30 -30
- package/jest.config.js +6 -0
- package/jest.setup.js +5 -0
- package/package.json +5 -4
- package/authn.mjs +0 -123
- package/tests/test-error.html +0 -5
- package/tests/test-fetch.html +0 -26
- package/tests/test-flatten.html +0 -38
- package/tests/test-layout.html +0 -32
- package/tests/test-schema.html +0 -10
- package/tests/test.html +0 -44
package/components.js
CHANGED
|
@@ -1,16 +1,169 @@
|
|
|
1
1
|
window.components = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
"hello-world": (data) => `Hello ${data?.name || 'World'}!`,
|
|
3
|
+
"markdown-block": {
|
|
4
|
+
async init() {
|
|
5
|
+
await loadJS('https://cdn.jsdelivr.net/npm/marked/marked.min.js')
|
|
6
|
+
this.innerHTML = marked.parse(this.innerText)
|
|
7
|
+
}
|
|
6
8
|
},
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
"auto-complete": {
|
|
10
|
+
init() {
|
|
11
|
+
// Prevent re-initialization
|
|
12
|
+
if (this._initialized) return
|
|
13
|
+
this._initialized = true
|
|
14
|
+
|
|
15
|
+
// Ensure element is visible
|
|
16
|
+
this.style.display = 'block'
|
|
17
|
+
this.style.width = '100%'
|
|
18
|
+
this.style.minWidth = '200px'
|
|
19
|
+
this.style.margin = '10px 0'
|
|
20
|
+
this.style.padding = '0'
|
|
21
|
+
this.style.backgroundColor = 'transparent'
|
|
22
|
+
|
|
23
|
+
this.input = document.createElement('input')
|
|
24
|
+
this.input.type = 'text'
|
|
25
|
+
this.input.placeholder = this.getAttribute('placeholder') || 'Type to search...'
|
|
26
|
+
this.input.style.cssText = 'width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;font-size:14px;'
|
|
27
|
+
|
|
28
|
+
this.dropdown = document.createElement('div')
|
|
29
|
+
this.dropdown.style.cssText = 'position:absolute;top:100%;left:0;right:0;background:white;border:1px solid #ccc;border-top:none;max-height:200px;overflow-y:auto;z-index:1000;display:none;box-shadow:0 2px 4px rgba(0,0,0,0.1);'
|
|
30
|
+
|
|
31
|
+
this.container = document.createElement('div')
|
|
32
|
+
this.container.style.cssText = 'position:relative;width:100%;min-width:200px;'
|
|
33
|
+
this.container.appendChild(this.input)
|
|
34
|
+
this.container.appendChild(this.dropdown)
|
|
35
|
+
|
|
36
|
+
// Clear existing content
|
|
37
|
+
this.innerHTML = ''
|
|
38
|
+
this.appendChild(this.container)
|
|
39
|
+
|
|
40
|
+
this.items = []
|
|
41
|
+
this.filtered = []
|
|
42
|
+
this.selectedIndex = -1
|
|
43
|
+
|
|
44
|
+
this.input.addEventListener('input', () => this.filter())
|
|
45
|
+
this.input.addEventListener('keydown', (e) => this.handleKey(e))
|
|
46
|
+
this.input.addEventListener('focus', () => {
|
|
47
|
+
if (this.filtered.length > 0) this.dropdown.style.display = 'block'
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Use a unique handler per instance
|
|
51
|
+
this._clickHandler = (e) => {
|
|
52
|
+
if (!this.contains(e.target)) {
|
|
53
|
+
this.dropdown.style.display = 'none'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
document.addEventListener('click', this._clickHandler)
|
|
57
|
+
},
|
|
58
|
+
set(data) {
|
|
59
|
+
this.items = Array.isArray(data) ? data : (data?.items || [])
|
|
60
|
+
this.filter()
|
|
61
|
+
},
|
|
62
|
+
filter() {
|
|
63
|
+
const query = this.input.value.toLowerCase()
|
|
64
|
+
this.filtered = this.items.filter(item => {
|
|
65
|
+
const text = typeof item === 'string' ? item : (item.label || item.name || String(item))
|
|
66
|
+
return text.toLowerCase().includes(query)
|
|
67
|
+
})
|
|
68
|
+
this.render()
|
|
69
|
+
},
|
|
70
|
+
render() {
|
|
71
|
+
if (this.filtered.length === 0) {
|
|
72
|
+
this.dropdown.style.display = 'none'
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.dropdown.innerHTML = this.filtered.map((item, i) => {
|
|
77
|
+
const text = typeof item === 'string' ? item : (item.label || item.name || String(item))
|
|
78
|
+
const selected = i === this.selectedIndex ? 'background:#f0f0f0;' : ''
|
|
79
|
+
return `<div data-index="${i}" style="padding:8px;cursor:pointer;${selected}">${text}</div>`
|
|
80
|
+
}).join('')
|
|
81
|
+
|
|
82
|
+
this.dropdown.style.display = 'block'
|
|
83
|
+
|
|
84
|
+
this.dropdown.querySelectorAll('div').forEach((div, i) => {
|
|
85
|
+
div.addEventListener('click', () => this.select(i))
|
|
86
|
+
div.addEventListener('mouseenter', () => {
|
|
87
|
+
this.selectedIndex = i
|
|
88
|
+
this.render()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
},
|
|
92
|
+
select(index) {
|
|
93
|
+
const item = this.filtered[index]
|
|
94
|
+
const text = typeof item === 'string' ? item : (item.label || item.name || String(item))
|
|
95
|
+
this.input.value = text
|
|
96
|
+
this.dropdown.style.display = 'none'
|
|
97
|
+
this.selectedIndex = -1
|
|
98
|
+
|
|
99
|
+
const event = new CustomEvent('select', { detail: item })
|
|
100
|
+
this.dispatchEvent(event)
|
|
101
|
+
},
|
|
102
|
+
handleKey(e) {
|
|
103
|
+
if (e.key === 'ArrowDown') {
|
|
104
|
+
e.preventDefault()
|
|
105
|
+
this.selectedIndex = Math.min(this.selectedIndex + 1, this.filtered.length - 1)
|
|
106
|
+
this.render()
|
|
107
|
+
} else if (e.key === 'ArrowUp') {
|
|
108
|
+
e.preventDefault()
|
|
109
|
+
this.selectedIndex = Math.max(this.selectedIndex - 1, -1)
|
|
110
|
+
this.render()
|
|
111
|
+
} else if (e.key === 'Enter' && this.selectedIndex >= 0) {
|
|
112
|
+
e.preventDefault()
|
|
113
|
+
this.select(this.selectedIndex)
|
|
114
|
+
} else if (e.key === 'Escape') {
|
|
115
|
+
this.dropdown.style.display = 'none'
|
|
116
|
+
this.selectedIndex = -1
|
|
117
|
+
}
|
|
118
|
+
}
|
|
15
119
|
}
|
|
16
|
-
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Component Definitions
|
|
124
|
+
*
|
|
125
|
+
* Components are registered via window.components object.
|
|
126
|
+
* Each component is a custom HTML element that can be used in your HTML.
|
|
127
|
+
*
|
|
128
|
+
* Structure:
|
|
129
|
+
* window.components = {
|
|
130
|
+
* "component-name": {
|
|
131
|
+
* // Component methods and properties
|
|
132
|
+
* }
|
|
133
|
+
* }
|
|
134
|
+
*
|
|
135
|
+
* Available Options:
|
|
136
|
+
*
|
|
137
|
+
* 1. init(element) - Called when component is first connected to DOM
|
|
138
|
+
* - Receives the element instance as parameter
|
|
139
|
+
* - Use for setup, loading resources, initial rendering
|
|
140
|
+
* - Can be async
|
|
141
|
+
*
|
|
142
|
+
* 2. set(data) - Called when component receives data (via data attribute binding)
|
|
143
|
+
* - Receives data object from state or fetch
|
|
144
|
+
* - Use to update component content based on data
|
|
145
|
+
*
|
|
146
|
+
* 3. click(ev), mouseover(ev), etc. - Event handlers
|
|
147
|
+
* - Automatically bound as event listeners
|
|
148
|
+
* - Any method matching /click|mouseover/ pattern is auto-bound
|
|
149
|
+
* - Receives the event object
|
|
150
|
+
*
|
|
151
|
+
* 4. style - Object with CSS properties
|
|
152
|
+
* - Applied as inline styles when component connects
|
|
153
|
+
* - Use camelCase or kebab-case (with quotes) for CSS properties
|
|
154
|
+
*
|
|
155
|
+
* 5. Any other methods - Available as instance methods
|
|
156
|
+
* - Can be called directly on element: element.myMethod()
|
|
157
|
+
*
|
|
158
|
+
* Usage in HTML:
|
|
159
|
+
* <component-name data="stateKey"></component-name>
|
|
160
|
+
*
|
|
161
|
+
* Example:
|
|
162
|
+
* window.components = {
|
|
163
|
+
* "my-button": {
|
|
164
|
+
* init: (e) => e.innerText = 'Click me',
|
|
165
|
+
* click: (ev) => alert('Clicked!'),
|
|
166
|
+
* style: { color: 'blue', padding: '10px' }
|
|
167
|
+
* }
|
|
168
|
+
* }
|
|
169
|
+
*/
|
package/enigmatic.js
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
const w = {}, d = document
|
|
2
|
-
w.enigmatic = { version: '
|
|
2
|
+
w.enigmatic = { version: '2026-01-03 0.23.0' }
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const showError = (err, source, line, col) => {
|
|
5
|
+
const errorDiv = d.createElement('div')
|
|
6
|
+
errorDiv.style.cssText = 'position:fixed;top:0;left:0;right:0;background:#ff4444;color:white;padding:20px;z-index:10000;font-family:monospace;border-bottom:4px solid #cc0000;'
|
|
7
|
+
errorDiv.innerHTML = `<strong>JavaScript Error:</strong><br>${err}<br><small>${source ? source + ':' : ''}${line ? ' line ' + line : ''}${col ? ':' + col : ''}</small>`
|
|
8
|
+
d.body.insertBefore(errorDiv, d.body.firstChild)
|
|
6
9
|
}
|
|
7
10
|
|
|
11
|
+
window.onerror = (err, source, line, col) => {
|
|
12
|
+
showError(err, source, line, col)
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
17
|
+
showError(e.reason?.message || e.reason || 'Unhandled Promise Rejection', '', '', '')
|
|
18
|
+
})
|
|
19
|
+
|
|
8
20
|
w.$ = d.querySelector.bind(d)
|
|
9
21
|
w.$$ = d.querySelectorAll.bind(d)
|
|
22
|
+
|
|
10
23
|
w.loadJS = (src) => {
|
|
11
24
|
return new Promise((r, j) => {
|
|
12
|
-
if (
|
|
25
|
+
if (w.$(`script[src="${src}"]`)) return r(true)
|
|
13
26
|
const s = d.createElement('script')
|
|
14
27
|
s.src = src
|
|
15
28
|
s.addEventListener('load', r)
|
|
16
|
-
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
w.loadCSS = (src) => {
|
|
21
|
-
return new Promise((r, j) => {
|
|
22
|
-
const s = document.createElement('link')
|
|
23
|
-
s.rel = 'stylesheet'
|
|
24
|
-
s.href = src
|
|
25
|
-
s.addEventListener('load', r)
|
|
29
|
+
s.addEventListener('error', j)
|
|
26
30
|
d.head.appendChild(s)
|
|
27
31
|
})
|
|
28
32
|
}
|
|
@@ -31,188 +35,168 @@ w.wait = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
|
31
35
|
|
|
32
36
|
w.ready = async () => {
|
|
33
37
|
return new Promise((r) => {
|
|
34
|
-
if (document.readyState === 'complete') r(true)
|
|
35
|
-
document.
|
|
38
|
+
if (document.readyState === 'complete') return r(true)
|
|
39
|
+
document.addEventListener('readystatechange', () => {
|
|
36
40
|
if (document.readyState === 'complete') r()
|
|
37
|
-
}
|
|
41
|
+
})
|
|
38
42
|
})
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
w.flattenMap = (obj, text) => {
|
|
42
|
-
let template = ''
|
|
43
|
-
if (text.match(/\$key|\$val/i)) {
|
|
44
|
-
for (let k in obj) {
|
|
45
|
-
template += text.replaceAll('{$key}', k).replaceAll('{$val}', obj[k])
|
|
46
|
-
}
|
|
47
|
-
return template
|
|
48
|
-
}
|
|
49
|
-
for (let k in obj) {
|
|
50
|
-
text = text.replaceAll(`{${k}}`, obj[k])
|
|
51
|
-
}
|
|
52
|
-
return text
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
w.flatten = (obj, text) => {
|
|
56
|
-
if (!(obj instanceof Array) && typeof Object.values(obj)[0] === 'string') {
|
|
57
|
-
return w.flattenMap(obj, text)
|
|
58
|
-
}
|
|
59
|
-
let htmls = ''
|
|
60
|
-
if (obj instanceof Array) obj = { ...obj }
|
|
61
|
-
for (let k in obj) {
|
|
62
|
-
let html = text.replaceAll('{$key}', k)
|
|
63
|
-
for (let j in obj[k]) {
|
|
64
|
-
const val = typeof obj[k] === 'object' ? obj[k][j] : obj[k]
|
|
65
|
-
html = html.replaceAll('{_key_}', j).replaceAll('{$val}', val)
|
|
66
|
-
html = html.replaceAll(`{${j}}`, val)
|
|
67
|
-
}
|
|
68
|
-
htmls += html
|
|
69
|
-
}
|
|
70
|
-
return htmls
|
|
71
|
-
}
|
|
72
|
-
|
|
73
45
|
w.e = (name, fn = {}, style = {}) => {
|
|
46
|
+
console.log(`registering component ${name}`)
|
|
74
47
|
customElements.define(name, class extends HTMLElement {
|
|
75
48
|
connectedCallback() {
|
|
76
49
|
Object.assign(this.style, style)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
if(this.init) this.init(this)
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
w.element = (
|
|
87
|
-
name,
|
|
88
|
-
{ onMount = x => x, beforeData = (x) => x, style, template = '', fn = {} }
|
|
89
|
-
) => {
|
|
90
|
-
customElements.define(
|
|
91
|
-
name,
|
|
92
|
-
class extends HTMLElement {
|
|
93
|
-
connectedCallback(props) {
|
|
94
|
-
onMount(this)
|
|
95
|
-
if (style) {
|
|
96
|
-
const s = document.createElement('style')
|
|
97
|
-
s.innerHTML = `${name} {${style}}`
|
|
98
|
-
d.body.appendChild(s)
|
|
50
|
+
if (typeof fn === 'function') {
|
|
51
|
+
this.set = (data) => {
|
|
52
|
+
this.innerHTML = fn(data)
|
|
99
53
|
}
|
|
100
|
-
|
|
101
|
-
if (!this.template.match('{')) this.innerHTML = this.template
|
|
54
|
+
} else {
|
|
102
55
|
Object.assign(this, fn)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
return o
|
|
56
|
+
Object.keys(fn).filter(k=>k.match(/click|mouseover/)).forEach(k=>{
|
|
57
|
+
this.addEventListener(k, fn[k], true)
|
|
58
|
+
})
|
|
59
|
+
if(this.init) this.init(this)
|
|
108
60
|
}
|
|
109
61
|
}
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (window.components) {
|
|
114
|
-
for (let name in window.components) w.element(name, window.components[name])
|
|
62
|
+
})
|
|
115
63
|
}
|
|
116
64
|
|
|
117
65
|
w.state = new Proxy({}, {
|
|
118
66
|
set: async (obj, prop, value) => {
|
|
119
67
|
await w.ready()
|
|
120
68
|
console.log('state change:', prop, value)
|
|
121
|
-
if (
|
|
69
|
+
if (obj[prop] === value) {
|
|
122
70
|
return true
|
|
123
71
|
}
|
|
124
|
-
for (const e of
|
|
72
|
+
for (const e of w.$$(`[data="${prop}"]`)) {
|
|
125
73
|
if (e.set) e.set(value)
|
|
126
74
|
}
|
|
127
75
|
obj[prop] = value
|
|
128
|
-
return
|
|
76
|
+
return true
|
|
129
77
|
},
|
|
130
78
|
get: (obj, prop, receiver) => {
|
|
131
79
|
if (prop == '_all') return obj
|
|
132
80
|
return obj[prop]
|
|
133
81
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
let data
|
|
140
|
-
if (
|
|
141
|
-
data = JSON.parse(url)
|
|
142
|
-
} else {
|
|
143
|
-
let f = await fetch(url, options)
|
|
144
|
-
if (!f.ok) throw Error(`Could not fetch ${url}`)
|
|
145
|
-
data = await f.json()
|
|
146
|
-
}
|
|
147
|
-
if (transform) {
|
|
148
|
-
console.log('transforming ' + data)
|
|
149
|
-
data = transform(data)
|
|
150
|
-
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
w.get = async (url, opts = {}, transform, key) => {
|
|
85
|
+
const res = await fetch(url, opts)
|
|
86
|
+
if (!res.ok) throw Error(`Could not fetch ${url}`)
|
|
87
|
+
let data = await res.json()
|
|
88
|
+
if (transform) data = transform(data)
|
|
151
89
|
if (key) w.state[key] = data
|
|
152
90
|
return data
|
|
153
91
|
}
|
|
154
92
|
|
|
155
93
|
w.stream = async (url, key) => {
|
|
156
94
|
const ev = new EventSource(url)
|
|
157
|
-
ev.onmessage = (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
95
|
+
ev.onmessage = (e) => {
|
|
96
|
+
try {
|
|
97
|
+
const data = JSON.parse(e.data)
|
|
98
|
+
if (key) w.state[key] = data
|
|
99
|
+
return data
|
|
100
|
+
} catch (err) {
|
|
101
|
+
showError(err.message || err, '', '', '')
|
|
102
|
+
}
|
|
161
103
|
}
|
|
104
|
+
ev.onerror = (e) => {
|
|
105
|
+
showError('EventSource error', '', '', '')
|
|
106
|
+
ev.close()
|
|
107
|
+
}
|
|
108
|
+
return ev
|
|
162
109
|
}
|
|
163
110
|
|
|
164
|
-
w.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
e.children[c].set(obj[pos++])
|
|
182
|
-
}
|
|
183
|
-
return obj
|
|
111
|
+
w.flatten = (obj, text, context = {}) => {
|
|
112
|
+
const hasSpecialVars = /{\$key}|{\$val}|{\$index}/.test(text)
|
|
113
|
+
|
|
114
|
+
if (obj instanceof Array)
|
|
115
|
+
return obj.map((o, i) => w.flatten(o, text, { ...context, $key: i, $index: i, $val: o })).join('')
|
|
116
|
+
|
|
117
|
+
if (hasSpecialVars && obj && typeof obj === 'object' && !Array.isArray(obj))
|
|
118
|
+
return Object.entries(obj).map(([k, v]) => w.flatten(v, text, { ...context, $key: k, $val: v })).join('')
|
|
119
|
+
|
|
120
|
+
const m = text.match(/{([^}]*)}/gm) || []
|
|
121
|
+
for (const txt of m) {
|
|
122
|
+
const key = txt.replaceAll(/{|}/g, '')
|
|
123
|
+
let val = context[key] !== undefined ? context[key] : undefined
|
|
124
|
+
if (val === undefined && obj && typeof obj === 'object') {
|
|
125
|
+
val = obj
|
|
126
|
+
for (let k of key.split('.')) {
|
|
127
|
+
val = val?.[k]
|
|
184
128
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
if (e.attr?.stream) {
|
|
189
|
-
e.stream = w.stream.bind(null, e.pr.stream, null, window[e.pr.transform], e.id)
|
|
129
|
+
} else if (val === undefined) {
|
|
130
|
+
val = obj
|
|
190
131
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
132
|
+
text = text.replaceAll(txt, val ?? '')
|
|
133
|
+
}
|
|
134
|
+
return text
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const props = {
|
|
138
|
+
async init() {
|
|
139
|
+
let ignore = this.innerHTML.match(/<!--IGNORE-->.*?<!--ENDIGNORE-->/gms) || []
|
|
140
|
+
if (!ignore.length) {
|
|
141
|
+
this.template = this.innerHTML
|
|
142
|
+
} else {
|
|
143
|
+
this.ignoreblock = ignore
|
|
144
|
+
this.template = this.innerHTML
|
|
145
|
+
ignore.forEach(block => {
|
|
146
|
+
this.template = this.template.replace(block, '')
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
this.innerHTML = ''
|
|
150
|
+
if (!this.hasAttribute('defer')) {
|
|
151
|
+
this.fetch()
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
set(o) {
|
|
155
|
+
console.log('setting', this, this.template, o)
|
|
156
|
+
this.innerHTML = w.flatten(o, this.template)
|
|
157
|
+
const dt = this.getAttribute('data')
|
|
158
|
+
if(dt)
|
|
159
|
+
w.state[dt] = o
|
|
160
|
+
},
|
|
161
|
+
async fetch() {
|
|
162
|
+
try {
|
|
163
|
+
const u = this.getAttribute('fetch')
|
|
164
|
+
if(!u) return
|
|
165
|
+
if (u.startsWith('[') || u.startsWith('{'))
|
|
166
|
+
return this.set(JSON.parse(u))
|
|
167
|
+
const opts = {}
|
|
168
|
+
const f = await fetch(u, opts)
|
|
169
|
+
console.log(f)
|
|
170
|
+
if (!f.ok) throw Error(`Could not fetch ${u}`)
|
|
171
|
+
let data = await f.json()
|
|
172
|
+
const tf = this.getAttribute('t')
|
|
173
|
+
if (tf) {
|
|
174
|
+
try {
|
|
175
|
+
data = new Function('return ' + tf)()(data)
|
|
176
|
+
} catch (err) {
|
|
177
|
+
showError(`Transform error: ${err.message}`, '', '', '')
|
|
199
178
|
}
|
|
200
179
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
if (e.attr.value) {
|
|
206
|
-
let o = e.attr.value
|
|
207
|
-
try { o = JSON.parse(o) } catch (e) { }
|
|
208
|
-
w.state[dta] = o
|
|
180
|
+
this.set(data)
|
|
181
|
+
} catch (err) {
|
|
182
|
+
showError(err.message || err, '', '', '')
|
|
209
183
|
}
|
|
210
184
|
}
|
|
211
|
-
})
|
|
212
185
|
}
|
|
213
186
|
|
|
187
|
+
for (let name in window.components)
|
|
188
|
+
w.e(name, window.components[name], window.components[name]?.style)
|
|
189
|
+
|
|
214
190
|
Object.assign(window, w);
|
|
215
191
|
|
|
216
192
|
(async () => {
|
|
217
|
-
|
|
193
|
+
try {
|
|
194
|
+
await ready()
|
|
195
|
+
for (const i of w.$$('div')) {
|
|
196
|
+
Object.assign(i, props)
|
|
197
|
+
i?.init()
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
showError(err.message || err, '', '', '')
|
|
201
|
+
}
|
|
218
202
|
})()
|
package/index.html
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
<script src=components.js></script>
|
|
2
|
-
<script src=enigmatic.js></script>
|
|
3
|
-
<link rel=stylesheet href=enigmatic.css>
|
|
1
|
+
<script src="components.js"></script>
|
|
2
|
+
<script src="enigmatic.js"></script>
|
|
3
|
+
<link rel="stylesheet" href="enigmatic.css">
|
|
4
4
|
|
|
5
|
-
<body style="--cols:
|
|
5
|
+
<body style="--cols:1fr; --rows:1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr">
|
|
6
|
+
<hello-world data="name"></hello-world>
|
|
7
|
+
<my-element></my-element>
|
|
6
8
|
|
|
7
|
-
<div
|
|
8
|
-
<div class="bg-white">
|
|
9
|
-
<img height='62' style='filter:invert(50%)'
|
|
10
|
-
src='https://unpkg.com/simple-icons@v7/icons/apple.svg'>
|
|
9
|
+
<div id=one fetch="https://randomuser.me/api/?results=1" t="e=>e.results">{email}</div>
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</
|
|
11
|
+
<script>
|
|
12
|
+
// Wait for DOM and enigmatic.js to be ready
|
|
13
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
14
|
+
// Wait for e to be available (enigmatic.js assigns it)
|
|
15
|
+
while (typeof window.e === 'undefined') {
|
|
16
|
+
await new Promise(r => setTimeout(r, 10))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
e('my-element', {
|
|
20
|
+
init: (e) => e.innerText = 'ready',
|
|
21
|
+
click: (ev) => ev.target.style.color = 'green',
|
|
22
|
+
mouseover: (ev) => ev.target.style.color = 'yellow',
|
|
23
|
+
}, {
|
|
24
|
+
color: 'red',
|
|
25
|
+
"text-decoration": 'underline'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Set state for components
|
|
29
|
+
await ready()
|
|
30
|
+
state.name = { name: 'World' }
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
</body>
|
package/jest.config.js
ADDED
package/jest.setup.js
ADDED
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigmatic",
|
|
3
3
|
"main": "enigmatic.js",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"test": "
|
|
6
|
+
"test": "jest",
|
|
7
|
+
"test:watch": "jest --watch"
|
|
7
8
|
},
|
|
8
9
|
"devDependencies": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
10
|
+
"jest": "^29.7.0",
|
|
11
|
+
"jest-environment-jsdom": "^29.7.0"
|
|
11
12
|
}
|
|
12
13
|
}
|