@dinoreic/fez 0.3.2 → 0.4.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dinoreic/fez",
3
- "version": "0.3.2",
4
- "description": "Runtime custom dom elements",
3
+ "version": "0.4.0",
4
+ "description": "Runtime custom DOM elements lib",
5
5
  "main": "dist/fez.js",
6
6
  "type": "module",
7
7
  "exports": {
@@ -70,10 +70,7 @@ const compileToClass = (html) => {
70
70
  }
71
71
 
72
72
  if (String(result.style).includes(':')) {
73
- Object.entries(Fez._styleMacros).forEach(([key, val])=>{
74
- result.style = result.style.replaceAll(`:${key} `, `${val} `)
75
- })
76
-
73
+ result.style = Fez.cssMixin(result.style)
77
74
  result.style = result.style.includes(':fez') || /(?:^|\s)body\s*\{/.test(result.style) ? result.style : `:fez {\n${result.style}\n}`
78
75
  klass = klass.replace(/\}\s*$/, `\n CSS = \`${result.style}\`\n}`)
79
76
  }
@@ -105,8 +102,8 @@ function compile_bulk(data) {
105
102
  if (fezName && !fezName.includes('-')) {
106
103
  console.error(`Fez: Invalid custom element name "${fezName}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
107
104
  }
108
- // Compile the node directly
109
- return compile(fezName, node.innerHTML)
105
+ compile(fezName, node.innerHTML)
106
+ return
110
107
  }
111
108
  }
112
109
  else {
@@ -195,7 +192,7 @@ function compile(tagName, html) {
195
192
  if (klass.includes('import ')) {
196
193
  Fez.head({script: klass})
197
194
 
198
- // best we can do it inform that node did not compile, so we assume there is arrow
195
+ // best we can do it inform that node did not compile, so we assume there is an error
199
196
  setTimeout(()=>{
200
197
  if (!Fez.classes[tagName]) {
201
198
  Fez.error(`Template "${tagName}" possible compile error. (can be a false positive, it imports are not loaded)`)
@@ -1,6 +1,26 @@
1
1
  import createTemplate from './lib/template.js'
2
2
  import FezBase from './instance.js'
3
3
 
4
+ /**
5
+ * Global mutation observer for reactive attribute changes
6
+ * Watches for attribute changes and triggers component updates
7
+ */
8
+ const observer = new MutationObserver((mutationsList, _) => {
9
+ for (const mutation of mutationsList) {
10
+ if (mutation.type === 'attributes') {
11
+ const fez = mutation.target.fez
12
+ const name = mutation.attributeName
13
+ const value = mutation.target.getAttribute(name)
14
+
15
+ if (fez) {
16
+ fez.props[name] = value
17
+ fez.onPropsChange(name, value)
18
+ // console.log(`The [${name}] attribute was modified to [${value}].`);
19
+ }
20
+ }
21
+ }
22
+ });
23
+
4
24
  /**
5
25
  * Registers a new custom element with Fez framework
6
26
  * @param {string} name - Custom element name (must contain a dash)
@@ -31,22 +51,17 @@ export default function connect(name, klass) {
31
51
  props.forEach(prop => newKlass.prototype[prop] = klassObj[prop])
32
52
 
33
53
  // Map component configuration properties
34
- if (klassObj.GLOBAL) { newKlass.fezGlobal = klassObj.GLOBAL } // Global instance reference
35
- if (klassObj.CSS) { newKlass.css = klassObj.CSS } // Component styles
54
+ if (klassObj.FAST) { newKlass.FAST = klassObj.FAST } // Global instance reference
55
+ if (klassObj.GLOBAL) { newKlass.GLOBAL = klassObj.GLOBAL } // Global instance reference
56
+ if (klassObj.CSS) { newKlass.css = klassObj.CSS } // Component styles
36
57
  if (klassObj.HTML) {
37
- newKlass.html = closeCustomTags(klassObj.HTML) // Component template
58
+ newKlass.html = closeCustomTags(klassObj.HTML) // Component template
38
59
  }
39
- if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME } // Custom DOM node name
60
+ if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME } // Custom DOM node name
40
61
 
41
62
  // Auto-mount global components to body
42
63
  if (klassObj.GLOBAL) {
43
- const mountGlobalComponent = () => document.body.appendChild(document.createElement(name))
44
-
45
- if (document.readyState === 'loading') {
46
- document.addEventListener('DOMContentLoaded', mountGlobalComponent);
47
- } else {
48
- mountGlobalComponent()
49
- }
64
+ document.body.appendChild(document.createElement(name))
50
65
  }
51
66
 
52
67
  klass = newKlass
@@ -59,11 +74,11 @@ export default function connect(name, klass) {
59
74
 
60
75
  // Process component template
61
76
  if (klass.html) {
62
- // Replace <slot /> with reactive slot containers
63
- klass.html = klass.html.replace(/<slot\s*\/>|<slot\s*>\s*<\/slot>/g, () => {
64
- const slotTag = klass.SLOT || 'div'
65
- return `<${slotTag} class="fez-slot" fez-keep="default-slot"></${slotTag}>`
66
- })
77
+ let slotTag = klass.SLOT || 'div'
78
+
79
+ klass.html = klass.html
80
+ .replace('<slot', `<${slotTag} class="fez-slot" fez-keep="default-slot"`)
81
+ .replace('</slot>', `</${slotTag}>`)
67
82
 
68
83
  // Compile template function
69
84
  klass.fezHtmlFunc = createTemplate(klass.html)
@@ -76,47 +91,16 @@ export default function connect(name, klass) {
76
91
 
77
92
  Fez.classes[name] = klass
78
93
 
79
- connectCustomElement(name, klass)
80
- }
81
-
82
- /**
83
- * Registers the custom element with the browser
84
- * Sets up batched rendering for optimal performance
85
- */
86
- function connectCustomElement(name, klass) {
87
- const Fez = globalThis.window?.Fez || globalThis.Fez;
88
-
89
94
  if (!customElements.get(name)) {
90
95
  customElements.define(name, class extends HTMLElement {
91
96
  connectedCallback() {
92
- // Batch all renders using microtasks for consistent timing and DOM completeness
93
- if (!Fez._pendingConnections) {
94
- Fez._pendingConnections = []
95
- Fez._batchScheduled = false
96
- }
97
-
98
- Fez._pendingConnections.push({ name, node: this })
99
-
100
- if (!Fez._batchScheduled) {
101
- Fez._batchScheduled = true
102
- Promise.resolve().then(() => {
103
- const connections = Fez._pendingConnections.slice()
104
- // console.error(`Batch processing ${connections.length} components:`, connections.map(c => c.name))
105
- Fez._pendingConnections = []
106
- Fez._batchScheduled = false
107
-
108
- // Sort by DOM order to ensure parent nodes are processed before children
109
- connections.sort((a, b) => {
110
- if (a.node.contains(b.node)) return -1
111
- if (b.node.contains(a.node)) return 1
112
- return 0
113
- })
114
-
115
- connections.forEach(({ name, node }) => {
116
- if (node.isConnected && node.parentNode) {
117
- connectNode(name, node)
118
- }
119
- })
97
+ // Fez.onReady(()=>{connectNode(name, this)})
98
+ // connectNode(name, this)
99
+ if (useFastRender(this, klass)) {
100
+ connectNode(name, this)
101
+ } else {
102
+ requestAnimationFrame(()=>{
103
+ connectNode(name, this)
120
104
  })
121
105
  }
122
106
  }
@@ -124,6 +108,16 @@ function connectCustomElement(name, klass) {
124
108
  }
125
109
  }
126
110
 
111
+ function useFastRender(node, klass) {
112
+ const fezFast = node.getAttribute('fez-fast')
113
+ var isFast = typeof klass.FAST === 'function' ? klass.FAST(node) : klass.FAST
114
+ if (fezFast == 'false') {
115
+ return false
116
+ } else {
117
+ return fezFast || isFast
118
+ }
119
+ }
120
+
127
121
  /**
128
122
  * Converts self-closing custom tags to full open/close format
129
123
  * Required for proper HTML parsing of custom elements
@@ -138,7 +132,6 @@ function closeCustomTags(html) {
138
132
  })
139
133
  }
140
134
 
141
-
142
135
  /**
143
136
  * Initializes a Fez component instance from a DOM node
144
137
  * Replaces the custom element with the component's rendered content
@@ -172,8 +165,8 @@ function connectNode(name, node) {
172
165
 
173
166
  newNode.fez = fez
174
167
 
175
- if (klass.fezGlobal && klass.fezGlobal != true) {
176
- window[klass.fezGlobal] = fez
168
+ if (klass.GLOBAL && klass.GLOBAL != true) {
169
+ window[klass.GLOBAL] = fez
177
170
  }
178
171
 
179
172
  if (window.$) {
@@ -216,23 +209,3 @@ function connectNode(name, node) {
216
209
  }
217
210
  }
218
211
  }
219
-
220
- /**
221
- * Global mutation observer for reactive attribute changes
222
- * Watches for attribute changes and triggers component updates
223
- */
224
- const observer = new MutationObserver((mutationsList, _) => {
225
- for (const mutation of mutationsList) {
226
- if (mutation.type === 'attributes') {
227
- const fez = mutation.target.fez
228
- const name = mutation.attributeName
229
- const value = mutation.target.getAttribute(name)
230
-
231
- if (fez) {
232
- fez.props[name] = value
233
- fez.onPropsChange(name, value)
234
- // console.log(`The [${name}] attribute was modified to [${value}].`);
235
- }
236
- }
237
- }
238
- });
@@ -390,11 +390,24 @@ export default class FezBase {
390
390
 
391
391
  // <button fez-use="animate" -> this.animate(node]
392
392
  fetchAttr('fez-use', (value, n) => {
393
- const target = this[value]
394
- if (typeof target == 'function') {
395
- target(n)
396
- } else {
397
- console.error(`Fez error: "${value}" is not a function in ${this.fezName}`)
393
+ if (value.includes('=>')) {
394
+ // fez-use="el => el.focus()"
395
+ Fez.getFunction(value)(n)
396
+ }
397
+ else {
398
+ if (value.includes('.')) {
399
+ // fez-use="this.focus()"
400
+ Fez.getFunction(value).bind(n)()
401
+ }
402
+ else {
403
+ // fez-use="animate"
404
+ const target = this[value]
405
+ if (typeof target == 'function') {
406
+ target(n)
407
+ } else {
408
+ console.error(`Fez error: "${value}" is not a function in ${this.fezName}`)
409
+ }
410
+ }
398
411
  }
399
412
  })
400
413
 
@@ -406,7 +419,7 @@ export default class FezBase {
406
419
  if (lastClass) {
407
420
  setTimeout(()=>{
408
421
  n.classList.add(lastClass)
409
- }, 300)
422
+ }, 1)
410
423
  }
411
424
  })
412
425
 
@@ -442,8 +455,28 @@ export default class FezBase {
442
455
  // Keep the old element in place of the new one
443
456
  newEl.parentNode.replaceChild(oldEl, newEl)
444
457
  } else if (key === 'default-slot') {
445
- // First render - populate the slot with current root children
446
- Array.from(this.root.childNodes).forEach(child => newEl.appendChild(child))
458
+ if (newEl.getAttribute('hide')) {
459
+ // You cant use state any more
460
+ this.state = null
461
+
462
+ const parent = newEl.parentNode
463
+
464
+ // Insert all root children before the slot's next sibling
465
+ Array.from(this.root.childNodes).forEach(child => {
466
+ parent.insertBefore(child, newEl)
467
+ })
468
+
469
+ // Remove the slot element
470
+ newEl.remove()
471
+ }
472
+ else {
473
+ // First render - populate the slot with current root children
474
+ Array.from(this.root.childNodes).forEach(
475
+ child => {
476
+ newEl.appendChild(child)
477
+ }
478
+ )
479
+ }
447
480
  }
448
481
  })
449
482
  }
@@ -606,7 +639,18 @@ export default class FezBase {
606
639
  methods.forEach(method => this[method] = this[method].bind(this))
607
640
  }
608
641
 
609
- fezHide() {
642
+ // dissolve into parent, if you want to promote first child or given node with this.root
643
+ dissolve(inNode) {
644
+ if (inNode) {
645
+ inNode.classList.add('fez')
646
+ inNode.classList.add(`fez-${this.fezName}`)
647
+ inNode.fez = this
648
+ if (this.attr('id')) inNode.setAttribute('id', this.attr('id'))
649
+
650
+ this.root.innerHTML = ''
651
+ this.root.appendChild(inNode)
652
+ }
653
+
610
654
  const node = this.root
611
655
  const nodes = this.childNodes()
612
656
  const parent = this.root.parentNode
@@ -614,7 +658,12 @@ export default class FezBase {
614
658
  nodes.reverse().forEach(el => parent.insertBefore(el, node.nextSibling))
615
659
 
616
660
  this.root.remove()
617
- this.root = parent
661
+ this.root = undefined
662
+
663
+ if (inNode) {
664
+ this.root = inNode
665
+ }
666
+
618
667
  return nodes
619
668
  }
620
669
 
package/src/fez/root.js CHANGED
@@ -5,6 +5,7 @@ import Gobber from './vendor/gobber.js'
5
5
  import { Idiomorph } from './vendor/idiomorph.js'
6
6
 
7
7
  import objectDump from './utils/dump.js'
8
+ import highlightAll from './utils/highlight_all.js'
8
9
  import connect from './connect.js'
9
10
  import compile from './compile.js'
10
11
  import state from './lib/global-state.js'
@@ -106,13 +107,9 @@ Fez.globalCss = (cssClass, opts = {}) => {
106
107
  cssClass = Fez.cssClass(text)
107
108
  }
108
109
 
109
- if (document.body) {
110
+ Fez.onReady(() => {
110
111
  document.body.parentElement.classList.add(cssClass)
111
- } else {
112
- document.addEventListener("DOMContentLoaded", () => {
113
- document.body.parentElement.classList.add(cssClass)
114
- })
115
- }
112
+ })
116
113
 
117
114
  return cssClass
118
115
  }
@@ -145,27 +142,6 @@ Fez.publish = (channel, ...args) => {
145
142
  })
146
143
  }
147
144
 
148
- // get unique id from string
149
- Fez.fnv1 = (str) => {
150
- var FNV_OFFSET_BASIS, FNV_PRIME, hash, i, j, ref;
151
- FNV_OFFSET_BASIS = 2166136261;
152
- FNV_PRIME = 16777619;
153
- hash = FNV_OFFSET_BASIS;
154
- for (i = j = 0, ref = str.length - 1; (0 <= ref ? j <= ref : j >= ref); i = 0 <= ref ? ++j : --j) {
155
- hash ^= str.charCodeAt(i);
156
- hash *= FNV_PRIME;
157
- }
158
- return hash.toString(36).replaceAll('-', '');
159
- }
160
-
161
- Fez.tag = (tag, opts = {}, html = '') => {
162
- const json = encodeURIComponent(JSON.stringify(opts))
163
- return `<${tag} data-props="${json}">${html}</${tag}>`
164
- // const json = JSON.stringify(opts, null, 2)
165
- // const data = `<script type="text/template">${json}</script><${tag} data-json-template="true">${html}</${tag}>`
166
- // return data
167
- };
168
-
169
145
  Fez.error = (text, show) => {
170
146
  text = `Fez: ${text}`
171
147
  console.error(text)
@@ -173,151 +149,13 @@ Fez.error = (text, show) => {
173
149
  return `<span style="border: 1px solid red; font-size: 14px; padding: 3px 7px; background: #fee; border-radius: 4px;">${text}</span>`
174
150
  }
175
151
  }
152
+
176
153
  Fez.log = (text) => {
177
154
  if (Fez.LOG === true) {
178
155
  text = String(text).substring(0, 180)
179
156
  console.log(`Fez: ${text}`)
180
157
  }
181
158
  }
182
- document.addEventListener('DOMContentLoaded', () => {
183
- Fez.log('Fez.LOG === true, logging enabled.')
184
- })
185
-
186
- // execute function until it returns true
187
- Fez.untilTrue = (func, pingRate) => {
188
- pingRate ||= 200
189
-
190
- if (!func()) {
191
- setTimeout(()=>{
192
- Fez.untilTrue(func, pingRate)
193
- } ,pingRate)
194
- }
195
- }
196
-
197
- // throttle function calls
198
- Fez.throttle = (func, delay = 200) => {
199
- let lastRun = 0;
200
- let timeout;
201
-
202
- return function(...args) {
203
- const now = Date.now();
204
-
205
- if (now - lastRun >= delay) {
206
- func.apply(this, args);
207
- lastRun = now;
208
- } else {
209
- clearTimeout(timeout);
210
- timeout = setTimeout(() => {
211
- func.apply(this, args);
212
- lastRun = Date.now();
213
- }, delay - (now - lastRun));
214
- }
215
- };
216
- }
217
-
218
- // Fetch wrapper with automatic caching and data handling
219
- // Usage:
220
- // Fez.fetch(url) - GET request (default)
221
- // Fez.fetch(url, callback) - GET with callback
222
- // Fez.fetch(url, data) - GET with query params (?foo=bar&baz=qux)
223
- // Fez.fetch(url, data, callback) - GET with query params and callback
224
- // Fez.fetch('POST', url, data) - POST with FormData body (multipart/form-data)
225
- // Fez.fetch('POST', url, data, callback) - POST with FormData and callback
226
- // Data object is automatically converted:
227
- // - GET: appended as URL query parameters
228
- // - POST: sent as FormData (multipart/form-data) without custom headers
229
- Fez.fetch = function(...args) {
230
- // Initialize cache if not exists
231
- Fez._fetchCache ||= {};
232
-
233
- let method = 'GET';
234
- let url;
235
- let callback;
236
-
237
- // Check if first arg is HTTP method (uppercase letters)
238
- if (typeof args[0] === 'string' && /^[A-Z]+$/.test(args[0])) {
239
- method = args.shift();
240
- }
241
-
242
- // URL is required
243
- url = args.shift();
244
-
245
- // Check for data/options object
246
- let opts = {};
247
- let data = null;
248
- if (typeof args[0] === 'object') {
249
- data = args.shift();
250
- }
251
-
252
- // Check for callback function
253
- if (typeof args[0] === 'function') {
254
- callback = args.shift();
255
- }
256
-
257
- // Handle data based on method
258
- if (data) {
259
- if (method === 'GET') {
260
- // For GET, append data as query parameters
261
- const params = new URLSearchParams(data);
262
- url += (url.includes('?') ? '&' : '?') + params.toString();
263
- } else if (method === 'POST') {
264
- // For POST, convert to FormData
265
- const formData = new FormData();
266
- for (const [key, value] of Object.entries(data)) {
267
- formData.append(key, value);
268
- }
269
- opts.body = formData;
270
- }
271
- }
272
-
273
- // Set method
274
- opts.method = method;
275
-
276
- // Create cache key from method, url, and stringified opts
277
- const cacheKey = `${method}:${url}:${JSON.stringify(opts)}`;
278
-
279
- // Check cache first
280
- if (Fez._fetchCache[cacheKey]) {
281
- const cachedData = Fez._fetchCache[cacheKey];
282
- Fez.log(`fetch cache hit: ${method} ${url}`);
283
- if (callback) {
284
- callback(cachedData);
285
- return;
286
- }
287
- return Promise.resolve(cachedData);
288
- }
289
-
290
- // Log live fetch
291
- Fez.log(`fetch live: ${method} ${url}`);
292
-
293
- // Helper to process and cache response
294
- const processResponse = (response) => {
295
- if (response.headers.get('content-type')?.includes('application/json')) {
296
- return response.json();
297
- }
298
- return response.text();
299
- };
300
-
301
- // If callback provided, execute and handle
302
- if (callback) {
303
- fetch(url, opts)
304
- .then(processResponse)
305
- .then(data => {
306
- Fez._fetchCache[cacheKey] = data;
307
- callback(data);
308
- })
309
- .catch(error => Fez.onError('fetch', error));
310
- return;
311
- }
312
-
313
- // Return promise with automatic JSON parsing
314
- return fetch(url, opts)
315
- .then(processResponse)
316
- .then(data => {
317
- Fez._fetchCache[cacheKey] = data;
318
- return data;
319
- });
320
- }
321
159
 
322
160
  Fez.onError = (kind, message) => {
323
161
  // Ensure kind is always a string
@@ -328,14 +166,6 @@ Fez.onError = (kind, message) => {
328
166
  console.error(`${kind}: ${message.toString()}`);
329
167
  }
330
168
 
331
- // define custom style macro
332
- // Fez.styleMacro('mobile', '@media (max-width: 768px)')
333
- // :mobile { ... } -> @media (max-width: 768px) { ... }
334
- Fez._styleMacros = {}
335
- Fez.styleMacro = (name, content) => {
336
- Fez._styleMacros[name] = content
337
- }
338
-
339
169
  // work with tmp store
340
170
  Fez.store = {
341
171
  store: new Map(),
@@ -360,10 +190,17 @@ Fez.store = {
360
190
 
361
191
  // Load utility functions
362
192
  import addUtilities from './utility.js'
193
+ import cssMixin from './utils/css_mixin.js'
363
194
  addUtilities(Fez)
195
+ cssMixin(Fez)
364
196
 
365
197
  Fez.compile = compile
366
198
  Fez.state = state
367
199
  Fez.dump = objectDump
200
+ Fez.dump = highlightAll
201
+
202
+ Fez.onReady(() => {
203
+ Fez.log('Fez.LOG === true, logging enabled.')
204
+ })
368
205
 
369
206
  export default Fez