lume-js 2.0.0-beta.1 → 2.0.0-beta.3
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 +18 -11
- package/dist/addons.min.mjs +1 -0
- package/dist/addons.mjs +631 -0
- package/dist/addons.mjs.map +1 -0
- package/dist/handlers.min.mjs +1 -0
- package/dist/handlers.mjs +186 -0
- package/dist/handlers.mjs.map +1 -0
- package/dist/index.min.mjs +1 -0
- package/dist/index.mjs +147 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lume.global.js +2 -0
- package/dist/lume.global.js.map +1 -0
- package/dist/shared-nXhT2Lh7.mjs +249 -0
- package/dist/shared-nXhT2Lh7.mjs.map +1 -0
- package/package.json +15 -8
- package/src/addons/cleanupGroup.js +44 -0
- package/src/addons/computed.js +26 -2
- package/src/addons/debug.js +10 -12
- package/src/addons/hydrateState.js +30 -0
- package/src/addons/index.d.ts +115 -6
- package/src/addons/index.js +14 -1
- package/src/addons/repeat.js +77 -58
- package/src/addons/withPlugins.js +136 -0
- package/src/all.js +11 -0
- package/src/core/bindDom.js +32 -8
- package/src/core/effect.js +52 -18
- package/src/core/state.js +159 -161
- package/src/handlers/index.d.ts +25 -19
- package/src/handlers/index.js +86 -0
- package/src/index.d.ts +37 -51
- package/src/index.js +2 -1
- package/src/utils/log.js +20 -0
- package/src/core/utils.js +0 -41
package/README.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Minimal reactive state management using only standard JavaScript and HTML. No custom syntax, no build step required, no framework lock-in.
|
|
6
6
|
|
|
7
|
-
> **Current Release:**
|
|
8
|
-
> Install
|
|
9
|
-
>
|
|
7
|
+
> **Current Release:** v2.0.0-beta.3 | **Stability Contract:** Core API is frozen forever
|
|
8
|
+
> Install: `npm install lume-js@next`
|
|
9
|
+
> Bundle size: ~2.45KB gzipped | 319 tests passing
|
|
10
10
|
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](package.json)
|
|
13
|
+
[](tests/)
|
|
14
|
+
[](scripts/check-size.js)
|
|
15
15
|
|
|
16
16
|
## Why Lume.js?
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ Minimal reactive state management using only standard JavaScript and HTML. No cu
|
|
|
19
19
|
|---------|---------|-----------|-----|-------|
|
|
20
20
|
| Custom Syntax | ❌ No | ✅ `x-data` | ✅ `v-bind` | ✅ JSX |
|
|
21
21
|
| Build Step | ❌ Optional | ❌ Optional | ⚠️ Recommended | ✅ Required |
|
|
22
|
-
| Bundle Size | ~2.
|
|
22
|
+
| Bundle Size | ~2.45KB | ~15KB | ~35KB | ~45KB |
|
|
23
23
|
| HTML Validation | ✅ Pass | ⚠️ Warnings | ⚠️ Warnings | ❌ JSX |
|
|
24
24
|
| Extensible Handlers | ✅ | ❌ Built-in only | ❌ Built-in only | N/A |
|
|
25
25
|
|
|
@@ -33,7 +33,7 @@ Minimal reactive state management using only standard JavaScript and HTML. No cu
|
|
|
33
33
|
|
|
34
34
|
```html
|
|
35
35
|
<script type="module">
|
|
36
|
-
import { state, bindDom, effect } from 'https://cdn.jsdelivr.net/npm/lume-js/
|
|
36
|
+
import { state, bindDom, effect } from 'https://cdn.jsdelivr.net/npm/lume-js/dist/index.min.mjs';
|
|
37
37
|
</script>
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -48,7 +48,16 @@ import { state, bindDom } from 'lume-js';
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
### Browser Support
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
| Browser | Minimum version |
|
|
53
|
+
|---------|-----------------|
|
|
54
|
+
| Chrome | 49+ |
|
|
55
|
+
| Firefox | 18+ |
|
|
56
|
+
| Safari | 10+ |
|
|
57
|
+
| Edge | 79+ |
|
|
58
|
+
| IE11 | ❌ Not supported |
|
|
59
|
+
|
|
60
|
+
IE11 cannot be polyfilled — Lume uses `Proxy`.
|
|
52
61
|
|
|
53
62
|
---
|
|
54
63
|
|
|
@@ -167,8 +176,6 @@ import { computed, watch, repeat } from 'lume-js/addons';
|
|
|
167
176
|
- **`watch(store, key, fn)`** — Subscribe to state changes
|
|
168
177
|
- **`repeat(container, store, key, options)`** — Keyed list rendering with element reuse
|
|
169
178
|
|
|
170
|
-
> **Note:** The `repeat` addon is *experimental*. Its API may evolve in future releases.
|
|
171
|
-
|
|
172
179
|
---
|
|
173
180
|
|
|
174
181
|
## Documentation
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,...t){void 0!==console&&"function"==typeof console.warn&&console.warn(e,...t)}function t(e,...t){void 0!==console&&"function"==typeof console.error&&console.error(e,...t)}const o=new Set;function n(e,t){o.add(e);try{return t()}finally{o.delete(e)}}let r=null;function c(e,o){if("function"!=typeof e)throw Error("effect() requires a function");const c=[];let s=!1;const i=()=>{if(!s){s=!0;try{e()}catch(e){throw t("[Lume.js effect] Error in effect:",e),e}finally{s=!1}}};if(Array.isArray(o)){for(const e of o)if(Array.isArray(e)&&e.length>=2){const[t,...o]=e;if(t&&"function"==typeof t.$subscribe)for(const e of o){let o=!0;const n=t.$subscribe(e,()=>{o?o=!1:i()});c.push(n)}}i()}else{const o=()=>{if(s)return;const i=c.splice(0),l={fn:e,cleanups:c,execute:o,tracking:{}},u=r;r=l,s=!0;try{n((e,t,o)=>{r===l&&(l.tracking[t]||(l.tracking[t]=!0,l.cleanups.push(o(t,l.execute))))},e)}catch(e){throw c.length=0,c.push(...i),t("[Lume.js effect] Error in effect:",e),e}finally{r=u,s=!1}if(c.length>0)for(const e of i)e();else c.push(...i)};o()}return()=>{for(;c.length;)c.pop()()}}function s(e){if("function"!=typeof e)throw Error("computed() requires a function");let o,n=!1,r=!1,s=!1;const i=[],l=c(()=>{if(!r&&!s){r=!0;try{const t=e();n&&Object.is(t,o)||(o=t,n=!0,i.forEach(e=>e(o)))}catch(e){t("[Lume.js computed] Error in computation:",e),n&&void 0===o||(o=void 0,n=!0,i.forEach(e=>e(o)))}finally{queueMicrotask(()=>{s||(r=!1)})}}});return{get value(){if(!n)throw Error("Computed value accessed before initialization");return o},subscribe(e){if("function"!=typeof e)throw Error("subscribe() requires a function");return i.push(e),n&&e(o),()=>{const t=i.indexOf(e);t>-1&&i.splice(t,1)}},dispose(){s=!0,l(),i.length=0,n=!1,r=!1}}}function i(e,t,o){if(!e.$subscribe)throw Error("store must be created with state()");return e.$subscribe(t,o)}function l(e){const t=document.activeElement;if(!e.contains(t))return null;let o=null,n=null;return"INPUT"!==t.tagName&&"TEXTAREA"!==t.tagName||(o=t.selectionStart,n=t.selectionEnd),()=>{document.body.contains(t)&&(t.focus(),null!==o&&null!==n&&t.setSelectionRange(o,n))}}function u(e,t={}){const{isReorder:o=!1}=t,n=e.scrollTop;if(0===n)return()=>{e.scrollTop=0};let r=null,c=0;if(!o){const t=e.getBoundingClientRect();for(let o=e.firstElementChild;o;o=o.nextElementSibling){const e=o.getBoundingClientRect();if(e.bottom>t.top){r=o,c=e.top-t.top;break}}}return()=>{if(r&&document.body.contains(r)){const t=r.getBoundingClientRect(),o=e.getBoundingClientRect(),n=t.top-o.top-c;e.scrollTop=e.scrollTop+n}else e.scrollTop=n}}function f(o,n,r,c){const{key:s,render:i,create:f,update:a,element:g="div",preserveFocus:d=l,preserveScroll:b=u}=c,p="string"==typeof o?document.querySelector(o):o;if(!p)return e(`[Lume.js] repeat(): container "${o}" not found`),()=>{};if("function"!=typeof s)throw Error("[Lume.js] repeat(): options.key must be a function");if("function"!=typeof i&&"function"!=typeof f)throw Error("[Lume.js] repeat(): options.render or options.create must be a function");const h=new Map,y=new Map,m=new Map,w=new Set;function $(){return"function"==typeof g?g():document.createElement(g)}function E(){const o=n[r];if(!Array.isArray(o))return void e(`[Lume.js] repeat(): store.${r} is not an array`);let c=!1;if(b){const e=new Set(h.keys()),t=new Set(o.map(e=>s(e)));c=e.size===t.size&&[...e].every(e=>t.has(e))}w.clear();const l=new Set,u=[];for(let n=0;n<o.length;n++){const r=o[n],c=s(r);if(w.has(c)){e(`[Lume.js] repeat(): duplicate key "${c}"`);continue}w.add(c),l.add(c);let g=h.get(c);const d=!g;d&&(g=$(),h.set(c,g));try{d&&f&&f(r,g,n);const e=y.get(c),t=m.get(c);a?e===r&&t===n||a(r,g,n,{isFirstRender:d}):i&&i(r,g,n),y.set(c,r),m.set(c,n)}catch(e){t(`[Lume.js] repeat(): error rendering key "${c}":`,e)}u.push(g)}!function(e,t,o){const n=document.body.contains(e),r=n&&d?d(e):null,c=n&&b?b(e,{isReorder:o}):null;(()=>{if(function(e,t){let o=e.firstChild;for(let n=0;n<t.length;n++){const r=t[n];o!==r?e.insertBefore(r,o):o=o.nextSibling}for(;o;){const t=o.nextSibling;e.removeChild(o),o=t}}(p,u),h.size!==l.size)for(const e of h.keys())l.has(e)||(h.delete(e),y.delete(e),m.delete(e))})(),r&&r(),c&&c()}(p,0,c)}let S;if("function"==typeof n.$subscribe)S=n.$subscribe(r,E);else{if("function"!=typeof n.subscribe)return E(),e("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)"),()=>{p.replaceChildren(),h.clear(),y.clear(),m.clear(),w.clear()};{const e=n.subscribe(()=>E());E(),S="function"==typeof e?e:()=>{e?.unsubscribe?.()}}}return()=>{"function"==typeof S&&S(),p.replaceChildren(),h.clear(),y.clear(),m.clear(),w.clear()}}let a=!0,g=null;const d=new Map;function b(e){return null===g||("string"==typeof g?e.includes(g):!(g instanceof RegExp)||g.test(e))}function p(e){return d.has(e)||d.set(e,{gets:new Map,sets:new Map,notifies:new Map}),d.get(e)}function h(e,t,o){const n=p(e)[t];n.set(o,(n.get(o)||0)+1)}const y=100,m=97;function w(e){try{const t=JSON.stringify(e);return t.length>y?t.slice(0,m)+"...":t}catch{return e+""}}function $(e={}){const t=e.label??"store",o=(t,o)=>{const n=e[t];return void 0!==n?n:o};return{name:"debug:"+t,onInit:()=>{a&&console.log(`%c[${t}]%c initialized`,"color: #888; font-weight: bold","color: inherit")},onGet:(e,n)=>("string"==typeof e&&e.startsWith("$")||(h(t,"gets",e),a&&o("logGet",!1)&&b(e)&&console.log(`%c[${t}]%c GET %c${e}%c = ${w(n)}`,"color: #888; font-weight: bold","color: #4CAF50","color: #2196F3; font-weight: bold","color: inherit")),n),onSet:(e,n,r)=>("string"==typeof e&&e.startsWith("$")||(h(t,"sets",e),a&&o("logSet",!0)&&b(e)&&(console.log(`%c[${t}]%c SET %c${e}%c: ${w(r)} → ${w(n)}`,"color: #888; font-weight: bold","color: #FF9800","color: #2196F3; font-weight: bold","color: inherit"),o("trace",!1)&&console.trace(`%c[${t}] Stack trace for ${e}`,"color: #888"))),n),onSubscribe:e=>{a&&b(e)&&console.log(`%c[${t}]%c SUBSCRIBE %c${e}`,"color: #888; font-weight: bold","color: #9C27B0","color: #2196F3; font-weight: bold")},onNotify:(e,n)=>{"string"==typeof e&&e.startsWith("$")||(h(t,"notifies",e),a&&o("logNotify",!0)&&b(e)&&console.log(`%c[${t}]%c NOTIFY %c${e}%c = ${w(n)}`,"color: #888; font-weight: bold","color: #E91E63","color: #2196F3; font-weight: bold","color: inherit"))}}}const E={enable(){a=!0,console.log("%c[lume-debug]%c Logging enabled","color: #888; font-weight: bold","color: #4CAF50")},disable(){a=!1,console.log("%c[lume-debug]%c Logging disabled","color: #888; font-weight: bold","color: #F44336")},isEnabled:()=>a,filter(e){g=e,console.log(null===e?"%c[lume-debug]%c Filter cleared":"%c[lume-debug]%c Filter set: "+e,"color: #888; font-weight: bold","color: inherit")},getFilter:()=>g,stats(){const e={};for(const[t,o]of d)e[t]={gets:Object.fromEntries(o.gets),sets:Object.fromEntries(o.sets),notifies:Object.fromEntries(o.notifies)};return e},logStats(){const e=this.stats();if(0===Object.keys(e).length)return console.log("%c[lume-debug]%c No stats collected yet","color: #888; font-weight: bold","color: inherit"),e;console.group("%c[lume-debug] Statistics","color: #888; font-weight: bold");for(const[t,o]of Object.entries(e)){console.group("%c"+t,"color: #2196F3; font-weight: bold");const e=[],n=new Set([...Object.keys(o.gets),...Object.keys(o.sets),...Object.keys(o.notifies)]);for(const t of n)e.push({key:t,gets:o.gets[t]||0,sets:o.sets[t]||0,notifies:o.notifies[t]||0});e.length>0&&console.table(e),console.groupEnd()}return console.groupEnd(),e},resetStats(){d.clear(),console.log("%c[lume-debug]%c Stats reset","color: #888; font-weight: bold","color: inherit")}};function S(e,o=[]){if(!o.length)return e;for(const e of o)try{e.onInit?.()}catch(o){t(`[Lume.js] Plugin "${e.name}" error in onInit:`,o)}const n=new Map;let r;return"function"==typeof e.$beforeFlush&&(r=e.$beforeFlush(function(){for(const[e,r]of n)for(const n of o)try{n.onNotify?.(e,r)}catch(e){t(`[Lume.js] Plugin "${n.name}" error in onNotify:`,e)}n.clear()})),new Proxy(e,{get(e,c){if("$dispose"===c)return()=>{r&&r(),n.clear()};if("string"==typeof c&&c.startsWith("$")){const n=e[c];return"$subscribe"===c&&"function"==typeof n?(e,r)=>{for(const n of o)try{n.onSubscribe?.(e)}catch(e){t(`[Lume.js] Plugin "${n.name}" error in onSubscribe:`,e)}return n(e,r)}:n}let s=e[c];for(const e of o)try{const t=e.onGet?.(c,s);void 0!==t&&(s=t)}catch(o){t(`[Lume.js] Plugin "${e.name}" error in onGet:`,o)}return s},set(e,r,c){const s=e[r];let i=c;for(const e of o)try{const t=e.onSet?.(r,i,s);void 0!==t&&(i=t)}catch(o){t(`[Lume.js] Plugin "${e.name}" error in onSet:`,o)}return Object.is(i,s)||n.set(r,i),e[r]=i,!0}})}function j(){const e=[];return{add(t){"function"==typeof t&&e.push(t)},dispose(){for(;e.length;){const t=e.pop();try{t()}catch(e){}}}}}function F(e="#__LUME_DATA__"){const t="undefined"!=typeof document?document.querySelector(e):null;if(!t)return{};try{return JSON.parse(t.textContent)}catch{return{}}}function L(e){return!(!e||"object"!=typeof e||"function"!=typeof e.$subscribe)}export{s as computed,j as createCleanupGroup,$ as createDebugPlugin,E as debug,l as defaultFocusPreservation,u as defaultScrollPreservation,F as hydrateState,L as isReactive,f as repeat,i as watch,S as withPlugins};
|