@dinoreic/fez 0.2.0 → 0.3.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 CHANGED
@@ -29,9 +29,7 @@ It replaces modern JS frameworks by using native Autonomous Custom Elements to c
29
29
 
30
30
  This article, [Web Components Will Replace Your Frontend Framework](https://www.dannymoerkerke.com/blog/web-components-will-replace-your-frontend-framework/), is from 2019. Join the future, ditch React, Angular and other never defined, always "evolving" monstrosities. Vanilla is the way :)
31
31
 
32
- There is no some "internal state" that is by some magic reflected to DOM. No! All methods Fez use to manipulate DOM are just helpers around native DOM interface. Work on DOM raw, use jQuery, use built in [node builder](https://github.com/dux/fez/blob/main/src/lib/n.js) or full template mapping with [morphing](https://github.com/bigskysoftware/idiomorph).
33
-
34
- It great in combination with another widely used JS libs, as jQuery, Zepto, underscore of loDash.
32
+ There is no some "internal state" that is by some magic reflected to DOM. No! All methods Fez use to manipulate DOM are just helpers around native DOM interface. Work on DOM raw, use built in [node builder](https://github.com/dux/fez/blob/main/src/lib/n.js) or full template mapping with [morphing](https://github.com/bigskysoftware/idiomorph).
35
33
 
36
34
  ## How it works
37
35
 
@@ -49,25 +47,17 @@ Here's a simple counter component that demonstrates Fez's core features:
49
47
  ```html
50
48
  <!-- Define a counter component in ex-counter.fez.html -->
51
49
  <script>
50
+ // called when Fez node is connected to DOM
52
51
  init() {
53
- // called when Fez node is connected to DOM
54
52
  this.MAX = 6
55
53
  this.state.count = 0
56
54
  }
57
55
 
58
- onStateChange(key, value, oldValue) {
59
- // called whenever this.state changes
60
- console.log(`State ${key} changed from ${oldValue} to ${value}`)
61
- if (key === 'count' && value >= this.MAX) {
62
- console.log('Counter reached maximum!')
63
- }
64
- }
65
-
66
56
  isMax() {
67
- // is state is changed, template is re-rendered
68
57
  return this.state.count >= this.MAX
69
58
  }
70
59
 
60
+ // is state is changed, template is re-rendered
71
61
  more() {
72
62
  this.state.count += this.isMax() ? 0 : 1
73
63
  }
@@ -153,12 +143,13 @@ This example showcases:
153
143
  * **Powerful Template Engine** - Multiple syntaxes (`{{ }}` and `[[ ]]`), control flow (`#if`, `#unless`, `#for`, `#each`), and block templates
154
144
  * **Reactive State Management** - Built-in reactive `state` object automatically triggers re-renders on property changes
155
145
  * **DOM Morphing** - Uses [Idiomorph](https://github.com/bigskysoftware/idiomorph) for intelligent DOM updates that preserve element state and animations
146
+ * **Preserve DOM Elements** - Use `fez-keep="unique-key"` attribute to preserve DOM elements across re-renders (useful for animations, form inputs, or stateful elements)
156
147
  * **Style Macros** - Define custom CSS shortcuts like `Fez.styleMacro('mobile', '@media (max-width: 768px)')` and use as `:mobile { ... }`
157
148
  * **Scoped & Global Styles** - Components can define both scoped CSS (`:fez { ... }`) and global styles in the same component
158
149
 
159
150
  ### Developer Experience
160
151
 
161
- * **Built-in Utilities** - Helpful methods like `formData()`, `setInterval()` (auto-cleanup), `onResize()`, and `nextTick()`
152
+ * **Built-in Utilities** - Helpful methods like `formData()`, `setInterval()` (auto-cleanup), `onWindowResize()`, and `nextTick()`
162
153
  * **Two-Way Data Binding** - Use `fez-bind` directive for automatic form synchronization
163
154
  * **Advanced Slot System** - Full `<slot />` support with event listener preservation
164
155
  * **Publish/Subscribe** - Built-in pub/sub system for component communication
@@ -168,8 +159,8 @@ This example showcases:
168
159
 
169
160
  ### Performance & Integration
170
161
 
171
- * **Fast/Slow Render Modes** - Optimize initial render with `FAST = true` to prevent flickering
172
- * **Request Animation Frame** - Smart RAF integration for smooth updates
162
+ * **Optimized Rendering** - Batched microtask rendering for flicker-free component initialization
163
+ * **Smart DOM Updates** - Efficient DOM manipulation with minimal reflows
173
164
  * **Built-in Fetch with Caching** - `Fez.fetch()` includes automatic response caching and JSON/FormData handling
174
165
  * **Global Component Access** - Register components globally with `GLOBAL = 'ComponentName'` for easy access
175
166
  * **Rich Lifecycle Hooks** - `init`, `onMount`, `beforeRender`, `afterRender`, `onDestroy`, `onPropsChange`, `onStateChange`, `onGlobalStateChange`
@@ -189,57 +180,9 @@ This example showcases:
189
180
  ### Fez Static Methods
190
181
 
191
182
  ```js
192
- Fez('#foo') // find fez node with id="foo"
193
- Fez('ui-tabs', this) // find first parent node ui-tabs
194
-
195
- // add global scss
196
- Fez.globalCss(`
197
- .some-class {
198
- color: red;
199
- &.foo { ... }
200
- .foo { ... }
201
- }
202
- ...
203
- `)
204
-
205
- // internal, get unique ID for a string, poor mans MD5 / SHA1
206
- Fez.fnv1('some string')
207
-
208
- // get generated css class name, from scss source string
209
- Fez.css(text)
210
-
211
- // get generated css class name without global attachment
212
- Fez.cssClass(text)
213
-
214
- // display information about fast/slow bind components in console
215
- Fez.info()
216
-
217
- // low-level DOM morphing function
218
- Fez.morphdom(target, newNode, opts)
219
-
220
- // HTML escaping utility
221
- Fez.htmlEscape(text)
222
-
223
- // create HTML tags with encoded props
224
- Fez.tag(tag, opts, html)
225
-
226
- // execute function until it returns true
227
- Fez.untilTrue(func, pingRate)
228
-
229
- // add scripts/styles to document head
230
- // Load JavaScript from URL: Fez.head({ js: 'path/to/script.js' })
231
- // Load JavaScript with attributes: Fez.head({ js: 'path/to/script.js', type: 'module', async: true })
232
- // Load JavaScript with callback: Fez.head({ js: 'path/to/script.js' }, () => console.log('loaded'))
233
- // Load JavaScript module and auto-import to window: Fez.head({ js: 'path/to/module.js', module: 'MyModule' })
234
- // Load CSS: Fez.head({ css: 'path/to/styles.css' })
235
- // Load CSS with attributes: Fez.head({ css: 'path/to/styles.css', media: 'print' })
236
- // Execute inline script: Fez.head({ script: 'console.log("Hello world")' })
237
- Fez.head(config, callback)
238
-
239
- // define custom style macro
240
- // Fez.styleMacro('mobile', '@media (max-width: 768px)')
241
- // :mobile { ... } -> @media (max-width: 768px) { ... }
242
- Fez.styleMacro(name, value)
183
+ Fez('#foo') // find fez node with id="foo"
184
+ Fez('ui-tabs', this) // find first parent node ui-tabs
185
+ Fez('ui-tabs', (fez)=> ... ) // loop over all ui-tabs nodes
243
186
 
244
187
  // define custom DOM node name -> <foo-bar>...
245
188
  Fez('foo-bar', class {
@@ -252,14 +195,6 @@ Fez('foo-bar', class {
252
195
  // set element style, set as property or method
253
196
  CSS = `scss string... `
254
197
 
255
- // unless node has no innerHTML on initialization, bind will be set to slow (fastBind = false)
256
- // if you are using components that to not use innerHTML and slots, enable fast bind (fastBind = true)
257
- // component will be rendered as parsed, and not on next tick (reduces flickering)
258
- // <fez-icon name="gear" />
259
- FAST = true
260
- FAST(node) { ... }
261
- // alternative: static fastBind() { return true }
262
-
263
198
  // define static HTML. calling `this.render()` (no arguments) will refresh current node.
264
199
  // if you pair it with `reactiveStore()`, to auto update on props change, you will have Svelte or Vue style reactive behaviour.
265
200
  HTML = `...`
@@ -269,112 +204,175 @@ Fez('foo-bar', class {
269
204
  GLOBAL = 'Dialog'
270
205
  GLOBAL = true // just append node to document, do not create window reference
271
206
 
272
- // use connect or created
273
- init(props) {
274
- // copy original attributes from attr hash to root node
275
- this.copy('href', 'onclick', 'style')
207
+ // called when fez element is connected to dom, before first render
208
+ // here you still have your slot available in this.root
209
+ init(props) { ... }
276
210
 
277
- // set style property to root node. look at a clock example
278
- // shortcut to this.root.style.setProperty(key, value)
279
- this.setStyle('--color', 'red')
211
+ // execute after init and first render
212
+ onMount() { ... }
280
213
 
281
- // clasic interval, that runs only while node is attached
282
- this.setInterval(func, tick) { ... }
214
+ // execute before or after every render
215
+ beforeRender() { ... }
216
+ afterRender() { ... }
283
217
 
284
- // get closest form data, as object
285
- this.formData()
218
+ // if you want to monitor new or changed node attributes
219
+ // monitors all original node attributes
220
+ // <ui-icon name="home" color="red" />
221
+ onPropsChange(attrName, attrValue) { ... }
286
222
 
287
- // mounted DOM node root
288
- this.root
223
+ // called when local component state changes
224
+ onStateChange(key, value, oldValue) { ... }
289
225
 
290
- // mounted DOM node root wrapped in $, only if jQuery is available
291
- this.$root
226
+ // called when global state changes (only if component uses key in question that key)
227
+ onGlobalStateChange(key, value) { ... }
292
228
 
293
- // node attributes, converted to properties
294
- this.props
229
+ // called when component is destroyed
230
+ onDestroy() { ... }
295
231
 
296
- // gets single node attribute or property
297
- this.prop('onclick')
232
+ /* used inside lifecycle methods (init(), onMount(), ... */
298
233
 
299
- // shortcut for this.root.querySelector(selector)
300
- this.find(selector)
234
+ // copy original attributes from attr hash to root node
235
+ this.copy('href', 'onclick', 'style')
301
236
 
302
- // gets value for FORM fields or node innerHTML
303
- this.val(selector)
304
- // set value to a node, uses value or innerHTML
305
- this.val(selector, value)
237
+ // set style property to root node. look at a clock example
238
+ // shortcut to this.root.style.setProperty(key, value)
239
+ this.setStyle('--color', 'red')
306
240
 
307
- // you can publish globally, and subscribe locally
308
- Fez.publish('channel', foo)
309
- this.subscribe('channel', (foo) => { ... })
241
+ // clasic interval, that runs only while node is attached
242
+ this.setInterval(func, tick) { ... }
310
243
 
311
- // gets root childNodes
312
- this.childNodes()
313
- this.childNodes(func) // pass function to loop forEach on selection, removed nodes from DOM
244
+ // get closest form data, as object. Searches for first parent or child FORM element
245
+ this.formData()
314
246
 
315
- // check if the this.root node is attached to dom
316
- this.isConnected()
247
+ // mounted DOM node root. Only in init() point to original <slot /> data, in onMount() to rendered data.
248
+ this.root
317
249
 
318
- // on every "this.state" props change, auto update view.
319
- this.state = this.reactiveStore()
320
- // this.state has reactiveStore() attached by default. any change will trigger this.render()
321
- this.state.foo = 123
250
+ // mounted DOM node root wrapped in $, only if jQuery is available
251
+ this.$root
322
252
 
323
- // generic window event handler with automatic cleanup
324
- // eventName: 'resize', 'scroll', 'mousemove', etc.
325
- // delay: throttle delay in ms (default: 200ms)
326
- // runs immediately on init and then throttled
327
- this.on(eventName, func, delay)
253
+ // node attributes, converted to properties
254
+ this.props
328
255
 
329
- // window resize event with cleanup (shorthand for this.on('resize', func, delay))
330
- this.onResize(func, delay)
331
-
332
- // window scroll event with cleanup (shorthand for this.on('scroll', func, delay))
333
- this.onScroll(func, delay)
256
+ // gets single node attribute or property
257
+ this.prop('onclick')
334
258
 
335
- // requestAnimationFrame wrapper with deduplication
336
- this.nextTick(func, name)
259
+ // shortcut for this.root.querySelector(selector)
260
+ this.find(selector)
337
261
 
338
- // get/set unique ID for root node
339
- this.rootId()
262
+ // gets value for FORM fields or node innerHTML
263
+ this.val(selector)
264
+ // set value to a node, uses value or innerHTML
265
+ this.val(selector, value)
340
266
 
341
- // get/set attributes on root node
342
- this.attr(name, value)
267
+ // you can publish globally, and subscribe locally
268
+ Fez.publish('channel', foo)
269
+ this.subscribe('channel', (foo) => { ... })
343
270
 
344
- // hide the custom element wrapper and move children to parent
345
- this.fezHide()
271
+ // gets root childNodes
272
+ this.childNodes()
273
+ this.childNodes(func) // pass function to loop forEach on selection, mask nodes out of position
346
274
 
347
- // execute after connect and initial component render
348
- this.onMount() { ... } // or this.connected() { ... }
275
+ // check if the this.root node is attached to dom
276
+ this.isConnected
349
277
 
350
- // execute before or after every render
351
- this.beforeRender() { ... }
352
- this.afterRender() { ... }
278
+ // this.state has reactiveStore() attached by default. any change will trigger this.render()
279
+ this.state.foo = 123
353
280
 
354
- // if you want to monitor new or changed node attributes
355
- // monitors all original node attributes
356
- // <ui-icon name="home" color="red" />
357
- this.onPropsChange(attrName, attrValue) { ... }
281
+ // generic window event handler with automatic cleanup
282
+ // eventName: 'resize', 'scroll', 'mousemove', etc.
283
+ // delay: throttle delay in ms (default: 200ms)
284
+ this.on(eventName, func, delay)
358
285
 
359
- // called when local component state changes
360
- this.onStateChange(key, value, oldValue) { ... }
286
+ // window resize event with cleanup (shorthand for this.on('resize', func, delay))
287
+ // runs immediately on init and then throttled
288
+ this.onWindowResize(func, delay)
361
289
 
362
- // called when global state changes (if component reads that key)
363
- this.onGlobalStateChange(key, value) { ... }
290
+ // window scroll event with cleanup (shorthand for this.on('scroll', func, delay))
291
+ // runs immediately on init and then throttled
292
+ this.onWindowScroll(func, delay)
364
293
 
365
- // called when component is destroyed
366
- this.onDestroy() { ... }
294
+ // requestAnimationFrame wrapper with deduplication
295
+ this.nextTick(func, name)
367
296
 
368
- // automatic form submission handling if defined
369
- this.onSubmit(formData) { ... }
297
+ // get unique ID for root node, set one if needed
298
+ this.rootId()
370
299
 
371
- // render template and attach result dom to root. uses Idiomorph for DOM morph
372
- this.render()
300
+ // get/set attributes on root node
301
+ this.attr(name, value)
373
302
 
374
- // you can render to another root too
375
- this.render(this.find('.body'), someHtmlTemplate)
376
- }
303
+ // hide the custom element wrapper and move children to parent
304
+ this.fezHide()
305
+
306
+ // automatic form submission handling if there is FORM as parent or child node
307
+ this.onSubmit(formData) { ... }
308
+
309
+ // render template and attach result dom to root. uses Idiomorph for DOM morph
310
+ this.render()
311
+ this.render(this.find('.body'), someHtmlTemplate) // you can render to another root too
377
312
  })
313
+
314
+ /* Utility methods */
315
+
316
+ // define custom style macro
317
+ // Fez.styleMacro('mobile', '@media (max-width: 768px)')
318
+ // :mobile { ... } -> @media (max-width: 768px) { ... }
319
+ Fez.styleMacro(name, value)
320
+
321
+ // add global scss
322
+ Fez.globalCss(`
323
+ .some-class {
324
+ color: red;
325
+ &.foo { ... }
326
+ .foo { ... }
327
+ }
328
+ ...
329
+ `)
330
+
331
+ // internal, get unique ID for a string, poor mans MD5 / SHA1
332
+ Fez.fnv1('some string')
333
+
334
+ // get dom node containing passed html
335
+ Fez.domRoot(htmlData || htmlNode)
336
+
337
+ // activates node by adding class to node, and removing it from siblings
338
+ Fez.activateNode(node, className = 'active')
339
+
340
+ // get generated css class name, from scss source string
341
+ Fez.css(text)
342
+
343
+ // get generated css class name without global attachment
344
+ Fez.cssClass(text)
345
+
346
+ // display information about registered components in console
347
+ Fez.info()
348
+
349
+ // low-level DOM morphing function
350
+ Fez.morphdom(target, newNode, opts)
351
+
352
+ // HTML escaping utility
353
+ Fez.htmlEscape(text)
354
+
355
+ // create HTML tags with encoded props
356
+ Fez.tag(tag, opts, html)
357
+
358
+ // execute function until it returns true
359
+ Fez.untilTrue(func, pingRate)
360
+
361
+ // resolve and execute a function from string or function reference
362
+ // useful for event handlers that can be either functions or strings
363
+ // Fez.resolveFunction('alert("hi")', element) - creates function and calls with element as this
364
+ // Fez.resolveFunction(myFunc, element) - calls myFunc with element as this
365
+ Fez.resolveFunction(pointer, context)
366
+
367
+ // add scripts/styles to document head
368
+ // Load JavaScript from URL: Fez.head({ js: 'path/to/script.js' })
369
+ // Load JavaScript with attributes: Fez.head({ js: 'path/to/script.js', type: 'module', async: true })
370
+ // Load JavaScript with callback: Fez.head({ js: 'path/to/script.js' }, () => console.log('loaded'))
371
+ // Load JavaScript module and auto-import to window: Fez.head({ js: 'path/to/module.js', module: 'MyModule' })
372
+ // Load CSS: Fez.head({ css: 'path/to/styles.css' })
373
+ // Load CSS with attributes: Fez.head({ css: 'path/to/styles.css', media: 'print' })
374
+ // Execute inline script: Fez.head({ script: 'console.log("Hello world")' })
375
+ Fez.head(config, callback)
378
376
  ```
379
377
 
380
378
  ## Fez script loading and definition
@@ -395,9 +393,6 @@ Fez('foo-bar', class {
395
393
  <!-- pass JSON template via data-json-template -->
396
394
  <script type="text/template">{...}</script>
397
395
  <foo-bar data-json-template="true"></foo-bar>
398
-
399
- <!-- override slow bind behavior -->
400
- <foo-bar fez-fast="true"></foo-bar>
401
396
  ```
402
397
 
403
398
  ## Component structure
@@ -459,7 +454,7 @@ All parts are optional
459
454
  {{json data}} <!-- JSON dump in PRE.json tag -->
460
455
 
461
456
  <!-- fez-this will link DOM node to object property (inspired by Svelte) -->
462
- <!-- this.listRoot -->
457
+ <!-- linkes to -> this.listRoot -->
463
458
  <ul fez-this="listRoot">
464
459
 
465
460
  <!-- when node is added to dom fez-use will call object function by name, and pass current node -->
@@ -468,7 +463,6 @@ All parts are optional
468
463
 
469
464
  <!-- fez-bind for two-way data binding on form elements -->
470
465
  <input type="text" fez-bind="state.username" />
471
- <input onkeyup="fez.list[{{ index }}].name = fez.value" value="{{ name }}" />
472
466
 
473
467
  <!--
474
468
  fez-class for adding classes with optional delay.
@@ -476,6 +470,9 @@ All parts are optional
476
470
  -->
477
471
  <span fez-class="active:100">Delayed class</span>
478
472
 
473
+ <!-- preserve state by key, not affected by state changes-->>
474
+ <p fez-keep="key">...</p>
475
+
479
476
  <!-- :attribute for evaluated attributes (converts to JSON) -->
480
477
  <div :data-config="state.config"></div>
481
478
  </div>