enigmatic 0.23.0 → 0.25.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.
Files changed (3) hide show
  1. package/e2.js +28 -0
  2. package/enigmatic.js +51 -5
  3. package/package.json +1 -1
package/e2.js ADDED
@@ -0,0 +1,28 @@
1
+ window.$ = document.querySelector.bind(document)
2
+ window.$$ = document.querySelectorAll.bind(document)
3
+
4
+ window.custom = {
5
+ "hello-world": (data) => `Hello ${data}`,
6
+ "hello-world-2": {
7
+ prop: (data) => `${data} World`,
8
+ render: function(data) {
9
+ return this.prop(data);
10
+ }
11
+ }
12
+ }
13
+
14
+ window.state = new Proxy({}, {
15
+ set(obj, prop, value) {
16
+ obj[prop] = value
17
+ $$(`[data="${prop}"]`).forEach(el => {
18
+ console.log('setting', el.tagName);
19
+ const f = window.custom[el.tagName.toLowerCase()];
20
+ if(typeof f === 'function') {
21
+ el.innerHTML = f(value);
22
+ } else {
23
+ el.innerHTML = f.render(value);
24
+ }
25
+ });
26
+ return true
27
+ }
28
+ })
package/enigmatic.js CHANGED
@@ -1,6 +1,8 @@
1
+ // Global namespace object and document shortcut
1
2
  const w = {}, d = document
2
3
  w.enigmatic = { version: '2026-01-03 0.23.0' }
3
4
 
5
+ // Display error banner at top of page
4
6
  const showError = (err, source, line, col) => {
5
7
  const errorDiv = d.createElement('div')
6
8
  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;'
@@ -8,21 +10,25 @@ const showError = (err, source, line, col) => {
8
10
  d.body.insertBefore(errorDiv, d.body.firstChild)
9
11
  }
10
12
 
13
+ // Global error handler for uncaught JavaScript errors
11
14
  window.onerror = (err, source, line, col) => {
12
15
  showError(err, source, line, col)
13
16
  return false
14
17
  }
15
18
 
19
+ // Handle unhandled promise rejections
16
20
  window.addEventListener('unhandledrejection', (e) => {
17
21
  showError(e.reason?.message || e.reason || 'Unhandled Promise Rejection', '', '', '')
18
22
  })
19
23
 
24
+ // DOM query shortcuts
20
25
  w.$ = d.querySelector.bind(d)
21
26
  w.$$ = d.querySelectorAll.bind(d)
22
27
 
28
+ // Dynamically load JavaScript file (prevents duplicate loading)
23
29
  w.loadJS = (src) => {
24
30
  return new Promise((r, j) => {
25
- if (w.$(`script[src="${src}"]`)) return r(true)
31
+ if (w.$(`script[src="${src}"]`)) return r(true) // Already loaded
26
32
  const s = d.createElement('script')
27
33
  s.src = src
28
34
  s.addEventListener('load', r)
@@ -31,8 +37,10 @@ w.loadJS = (src) => {
31
37
  })
32
38
  }
33
39
 
40
+ // Utility: wait for specified milliseconds
34
41
  w.wait = (ms) => new Promise((r) => setTimeout(r, ms))
35
42
 
43
+ // Wait for document to be fully loaded
36
44
  w.ready = async () => {
37
45
  return new Promise((r) => {
38
46
  if (document.readyState === 'complete') return r(true)
@@ -42,33 +50,44 @@ w.ready = async () => {
42
50
  })
43
51
  }
44
52
 
53
+ // Register custom web component
54
+ // If fn is a function, it becomes a render function that receives data
55
+ // If fn is an object, it can contain init, set, event handlers, etc.
45
56
  w.e = (name, fn = {}, style = {}) => {
46
57
  console.log(`registering component ${name}`)
47
58
  customElements.define(name, class extends HTMLElement {
48
59
  connectedCallback() {
60
+ // Apply styles to element
49
61
  Object.assign(this.style, style)
50
62
  if (typeof fn === 'function') {
63
+ // Function mode: create set() method that renders HTML from function
51
64
  this.set = (data) => {
52
65
  this.innerHTML = fn(data)
53
66
  }
54
67
  } else {
68
+ // Object mode: assign all methods/properties to element
55
69
  Object.assign(this, fn)
70
+ // Auto-bind event handlers (click, mouseover, etc.)
56
71
  Object.keys(fn).filter(k=>k.match(/click|mouseover/)).forEach(k=>{
57
72
  this.addEventListener(k, fn[k], true)
58
73
  })
74
+ // Call init if provided
59
75
  if(this.init) this.init(this)
60
76
  }
61
77
  }
62
78
  })
63
79
  }
64
80
 
81
+ // Reactive state: automatically updates elements when state changes
65
82
  w.state = new Proxy({}, {
66
83
  set: async (obj, prop, value) => {
67
84
  await w.ready()
68
85
  console.log('state change:', prop, value)
86
+ // Skip update if value hasn't changed
69
87
  if (obj[prop] === value) {
70
88
  return true
71
89
  }
90
+ // Find all elements with matching data attribute and call their set() method
72
91
  for (const e of w.$$(`[data="${prop}"]`)) {
73
92
  if (e.set) e.set(value)
74
93
  }
@@ -76,26 +95,29 @@ w.state = new Proxy({}, {
76
95
  return true
77
96
  },
78
97
  get: (obj, prop, receiver) => {
98
+ // Special property to get entire state object
79
99
  if (prop == '_all') return obj
80
100
  return obj[prop]
81
101
  }
82
102
  })
83
103
 
104
+ // Fetch JSON from URL, optionally transform and store in state
84
105
  w.get = async (url, opts = {}, transform, key) => {
85
106
  const res = await fetch(url, opts)
86
107
  if (!res.ok) throw Error(`Could not fetch ${url}`)
87
108
  let data = await res.json()
88
- if (transform) data = transform(data)
89
- if (key) w.state[key] = data
109
+ if (transform) data = transform(data) // Apply transform function if provided
110
+ if (key) w.state[key] = data // Store in state if key provided
90
111
  return data
91
112
  }
92
113
 
114
+ // Stream data via Server-Sent Events (SSE)
93
115
  w.stream = async (url, key) => {
94
116
  const ev = new EventSource(url)
95
117
  ev.onmessage = (e) => {
96
118
  try {
97
119
  const data = JSON.parse(e.data)
98
- if (key) w.state[key] = data
120
+ if (key) w.state[key] = data // Update state with each message
99
121
  return data
100
122
  } catch (err) {
101
123
  showError(err.message || err, '', '', '')
@@ -108,60 +130,79 @@ w.stream = async (url, key) => {
108
130
  return ev
109
131
  }
110
132
 
133
+ // Template engine: replace {placeholder} with values from object
134
+ // Supports nested properties (e.g., {user.name}) and special vars ($key, $val, $index)
111
135
  w.flatten = (obj, text, context = {}) => {
136
+ // Check if template uses special iteration variables
112
137
  const hasSpecialVars = /{\$key}|{\$val}|{\$index}/.test(text)
113
138
 
139
+ // Arrays: map over each item, providing $key, $index, $val
114
140
  if (obj instanceof Array)
115
141
  return obj.map((o, i) => w.flatten(o, text, { ...context, $key: i, $index: i, $val: o })).join('')
116
142
 
143
+ // Objects with special vars: iterate over entries, providing $key and $val
117
144
  if (hasSpecialVars && obj && typeof obj === 'object' && !Array.isArray(obj))
118
145
  return Object.entries(obj).map(([k, v]) => w.flatten(v, text, { ...context, $key: k, $val: v })).join('')
119
146
 
147
+ // Find all {placeholder} patterns in template
120
148
  const m = text.match(/{([^}]*)}/gm) || []
121
149
  for (const txt of m) {
122
150
  const key = txt.replaceAll(/{|}/g, '')
151
+ // Check context first (for special vars), then object properties
123
152
  let val = context[key] !== undefined ? context[key] : undefined
124
153
  if (val === undefined && obj && typeof obj === 'object') {
154
+ // Support dot notation: {user.name}
125
155
  val = obj
126
156
  for (let k of key.split('.')) {
127
157
  val = val?.[k]
128
158
  }
129
159
  } else if (val === undefined) {
160
+ // Fallback to obj itself if key not found
130
161
  val = obj
131
162
  }
163
+ // Replace placeholder with value (or empty string if undefined)
132
164
  text = text.replaceAll(txt, val ?? '')
133
165
  }
134
166
  return text
135
167
  }
136
168
 
169
+ // Methods added to div elements for data binding
137
170
  const props = {
171
+ // Initialize: save template, clear content, optionally fetch data
138
172
  async init() {
173
+ // Extract and remove IGNORE blocks from template
139
174
  let ignore = this.innerHTML.match(/<!--IGNORE-->.*?<!--ENDIGNORE-->/gms) || []
140
175
  if (!ignore.length) {
141
176
  this.template = this.innerHTML
142
177
  } else {
143
178
  this.ignoreblock = ignore
144
179
  this.template = this.innerHTML
180
+ // Remove all IGNORE blocks from template
145
181
  ignore.forEach(block => {
146
182
  this.template = this.template.replace(block, '')
147
183
  })
148
184
  }
149
- this.innerHTML = ''
185
+ this.innerHTML = '' // Clear for rendering
186
+ // Auto-fetch unless defer attribute is present
150
187
  if (!this.hasAttribute('defer')) {
151
188
  this.fetch()
152
189
  }
153
190
  },
191
+ // Update element content with data using template
154
192
  set(o) {
155
193
  console.log('setting', this, this.template, o)
156
194
  this.innerHTML = w.flatten(o, this.template)
195
+ // Sync to state if data attribute exists
157
196
  const dt = this.getAttribute('data')
158
197
  if(dt)
159
198
  w.state[dt] = o
160
199
  },
200
+ // Fetch data from URL or parse inline JSON
161
201
  async fetch() {
162
202
  try {
163
203
  const u = this.getAttribute('fetch')
164
204
  if(!u) return
205
+ // Inline JSON: parse directly
165
206
  if (u.startsWith('[') || u.startsWith('{'))
166
207
  return this.set(JSON.parse(u))
167
208
  const opts = {}
@@ -169,6 +210,7 @@ const props = {
169
210
  console.log(f)
170
211
  if (!f.ok) throw Error(`Could not fetch ${u}`)
171
212
  let data = await f.json()
213
+ // Apply transform function if 't' attribute exists
172
214
  const tf = this.getAttribute('t')
173
215
  if (tf) {
174
216
  try {
@@ -184,14 +226,18 @@ const props = {
184
226
  }
185
227
  }
186
228
 
229
+ // Register components from window.components object
187
230
  for (let name in window.components)
188
231
  w.e(name, window.components[name], window.components[name]?.style)
189
232
 
233
+ // Expose all functions to global scope (window)
190
234
  Object.assign(window, w);
191
235
 
236
+ // Initialize: wait for DOM, then enhance all divs with data binding
192
237
  (async () => {
193
238
  try {
194
239
  await ready()
240
+ // Add props to all divs and initialize them
195
241
  for (const i of w.$$('div')) {
196
242
  Object.assign(i, props)
197
243
  i?.init()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "enigmatic",
3
3
  "main": "enigmatic.js",
4
- "version": "0.23.0",
4
+ "version": "0.25.0",
5
5
  "scripts": {
6
6
  "test": "jest",
7
7
  "test:watch": "jest --watch"