lume-js 2.0.1 → 2.2.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 +54 -11
- package/dist/addons.min.mjs +1 -1
- package/dist/addons.mjs +61 -2
- package/dist/addons.mjs.map +1 -1
- package/dist/handlers.min.mjs +1 -1
- package/dist/handlers.mjs +15 -8
- package/dist/handlers.mjs.map +1 -1
- package/dist/lume.global.js +1 -1
- package/dist/lume.global.js.map +1 -1
- package/dist/shared-Dcokqj5a.mjs.map +1 -1
- package/package.json +12 -5
- package/src/addons/index.d.ts +26 -7
- package/src/addons/repeat.js +63 -3
- package/src/addons/watch.js +10 -1
- package/src/core/effect.js +1 -0
- package/src/core/state.js +1 -0
- package/src/handlers/ariaAttr.js +14 -0
- package/src/handlers/boolAttr.js +14 -0
- package/src/handlers/className.js +5 -0
- package/src/handlers/classToggle.js +14 -0
- package/src/handlers/htmlAttrs.js +57 -0
- package/src/handlers/index.d.ts +13 -0
- package/src/handlers/index.js +8 -196
- package/src/handlers/presets.js +14 -0
- package/src/handlers/show.js +5 -0
- package/src/handlers/stringAttr.js +16 -0
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:** v2.0
|
|
7
|
+
> **Current Release:** v2.2.0
|
|
8
8
|
> Install: `npm install lume-js`
|
|
9
|
-
> Bundle size: ~2.
|
|
9
|
+
> Bundle size: ~2.09KB gzipped | 348 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.09KB | ~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
|
|
|
@@ -107,6 +107,8 @@ That's it — two-way binding, no build step, valid HTML.
|
|
|
107
107
|
|
|
108
108
|
## Extensible Handler System
|
|
109
109
|
|
|
110
|
+
**Handlers** are plain objects that teach `bindDom()` how to interpret new `data-*` attributes. They live in `lume-js/handlers` and are entirely optional — import only the ones you use. You can also write your own with just an `attr` string and an `apply` function.
|
|
111
|
+
|
|
110
112
|
Need more reactive attributes? Import handlers or create your own — no core modification needed.
|
|
111
113
|
|
|
112
114
|
```javascript
|
|
@@ -135,10 +137,12 @@ bindDom(document.body, store, {
|
|
|
135
137
|
| Handler | HTML Example | Effect |
|
|
136
138
|
|---------|-------------|--------|
|
|
137
139
|
| `show` | `data-show="key"` | Shows element when truthy (`el.hidden = !val`) |
|
|
140
|
+
| `className` | `data-classname="key"` | Replaces full class string (`el.className = val`) |
|
|
138
141
|
| `boolAttr(name)` | `data-readonly="key"` | Toggles any boolean attribute |
|
|
139
142
|
| `ariaAttr(name)` | `data-aria-pressed="key"` | Sets ARIA attribute to "true"/"false" |
|
|
140
|
-
| `classToggle(...names)` | `data-class-active="key"` | Toggles CSS classes |
|
|
143
|
+
| `classToggle(...names)` | `data-class-active="key"` | Toggles individual CSS classes |
|
|
141
144
|
| `stringAttr(name)` | `data-href="key"` | Sets string attributes (removes on null) |
|
|
145
|
+
| `htmlAttrs()` | *(all of the above)* | One-import preset — all standard HTML + ARIA attrs |
|
|
142
146
|
|
|
143
147
|
### Presets
|
|
144
148
|
|
|
@@ -166,15 +170,52 @@ bindDom(root, store, { handlers: [tooltip] });
|
|
|
166
170
|
|
|
167
171
|
## Addons
|
|
168
172
|
|
|
169
|
-
Import only what you need from `lume-js/addons
|
|
173
|
+
**Addons** are optional reactive pattern helpers that build on the core primitives. They handle common use cases that would otherwise require boilerplate — derived values, key observation, list rendering. Import only what you need from `lume-js/addons`; none are loaded by default.
|
|
170
174
|
|
|
171
175
|
```javascript
|
|
172
176
|
import { computed, watch, repeat } from 'lume-js/addons';
|
|
173
177
|
```
|
|
174
178
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
| Addon | When to use |
|
|
180
|
+
|-------|-------------|
|
|
181
|
+
| `effect(fn)` *(core)* | Write derived values back into the store, or trigger side effects on state change |
|
|
182
|
+
| `computed(fn)` | Derive a read-only value from state to consume *outside* the store (templates, display logic) |
|
|
183
|
+
| `watch(store, key, fn)` | React to a *specific* key changing — DOM updates, analytics, syncing external state |
|
|
184
|
+
| `repeat(container, store, key, opts)` | Render a keyed list with element reuse (no full re-render on change) |
|
|
185
|
+
|
|
186
|
+
**Quick rule:** `effect` for writing back into state → `computed` for reading outside state → `watch` for observing a single key → `repeat` for arrays in the DOM.
|
|
187
|
+
|
|
188
|
+
### `effect()` vs `computed()` vs `watch()`
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
import { state, effect } from 'lume-js';
|
|
192
|
+
import { computed, watch } from 'lume-js/addons';
|
|
193
|
+
|
|
194
|
+
const store = state({ firstName: 'Ada', lastName: 'Lovelace', count: 0 });
|
|
195
|
+
|
|
196
|
+
// effect() — derives a value and writes it back into the store
|
|
197
|
+
// Use when the result lives in state and drives the DOM via data-bind
|
|
198
|
+
effect(() => {
|
|
199
|
+
store.fullName = `${store.firstName} ${store.lastName}`;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// computed() — derives a value to read externally (e.g. display, logging)
|
|
203
|
+
// Use when the result is consumed outside the store
|
|
204
|
+
const doubled = computed(() => store.count * 2);
|
|
205
|
+
console.log(doubled.value); // 10
|
|
206
|
+
doubled.subscribe(val => document.title = `Count × 2: ${val}`);
|
|
207
|
+
|
|
208
|
+
// watch() — reacts to a single key changing
|
|
209
|
+
// Use for side effects tied to one property: analytics, localStorage, DOM sync
|
|
210
|
+
watch(store, 'count', (val) => {
|
|
211
|
+
localStorage.setItem('count', val);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// watch() with { immediate: false } — skip the initial call
|
|
215
|
+
watch(store, 'count', (val) => {
|
|
216
|
+
sendAnalytics('count_changed', val); // only on actual changes
|
|
217
|
+
}, { immediate: false });
|
|
218
|
+
```
|
|
178
219
|
|
|
179
220
|
---
|
|
180
221
|
|
|
@@ -193,6 +234,8 @@ Full documentation is available in the [docs/](docs/) directory:
|
|
|
193
234
|
- [Handlers](docs/api/core/handlers.md) — Extensible attribute handlers
|
|
194
235
|
- [Plugins](docs/api/core/plugins.md) — State extension system
|
|
195
236
|
- [Addons](docs/api/addons/computed.md) — computed, watch, repeat, debug
|
|
237
|
+
- **Guides**
|
|
238
|
+
- [Choosing reactive primitives](docs/guides/choosing-reactive-primitives.md) — when to use effect vs computed vs watch
|
|
196
239
|
- **Design**
|
|
197
240
|
- [Design Decisions](docs/design/design-decisions.md)
|
|
198
241
|
|
package/dist/addons.min.mjs
CHANGED
|
@@ -1 +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:{}},
|
|
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:{}},f=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=f,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,{immediate:n=!0}={}){if(!e.$subscribe)throw Error("store must be created with state()");if(!n){let n=!1;return e.$subscribe(t,e=>{n?o(e):n=!0})}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 f(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 u(o,n,r,c){const{key:s,render:i,create:u,update:a,remove:g,element:p="div",preserveFocus:d=l,preserveScroll:b=f}=c,h="string"==typeof o?document.querySelector(o):o;if(!h)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 u)throw Error("[Lume.js] repeat(): options.render or options.create must be a function");const y=new Map,m=new Map,$=new Map,w=new Map,E=new Set;function j(){return"function"==typeof p?p():document.createElement(p)}function S(){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&&y.size===o.length){c=!0;for(let e=0;e<o.length;e++)if(!y.has(s(o[e]))){c=!1;break}}E.clear();const l=[];for(let n=0;n<o.length;n++){const r=o[n],c=s(r);if(E.has(c)){e(`[Lume.js] repeat(): duplicate key "${c}"`);continue}E.add(c);let f=y.get(c);const g=!f;g&&(f=j(),y.set(c,f));try{if(g&&u){const e=u(r,f,n);"function"==typeof e&&w.set(c,e)}const e=m.get(c),t=$.get(c);a?e===r&&t===n||a(r,f,n,{isFirstRender:g}):i&&i(r,f,n),m.set(c,r),$.set(c,n)}catch(e){t(`[Lume.js] repeat(): error rendering key "${c}":`,e)}l.push(f)}!function(e,o,n){const r=document.body.contains(e),c=r&&d?d(e):null,s=r&&b?b(e,{isReorder:n}):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}}(h,l),y.size!==E.size)for(const e of y.keys())if(!E.has(e)){const o=y.get(e),n=m.get(e),r=w.get(e);if("function"==typeof r)try{r()}catch(o){t(`[Lume.js] repeat(): cleanup error for key "${e}":`,o)}"function"==typeof g&&o&&g(n,o),y.delete(e),m.delete(e),$.delete(e),w.delete(e)}})(),c&&c(),s&&s()}(h,0,c)}let L;if("function"==typeof n.$subscribe)L=n.$subscribe(r,S);else{if("function"!=typeof n.subscribe)return S(),e("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)"),()=>{for(const[e,o]of y){const n=m.get(e),r=w.get(e);if("function"==typeof r)try{r()}catch(o){t(`[Lume.js] repeat(): cleanup error for key "${e}":`,o)}"function"==typeof g&&g(n,o)}h.replaceChildren(),y.clear(),m.clear(),$.clear(),w.clear(),E.clear()};{const e=n.subscribe(()=>S());S(),L="function"==typeof e?e:()=>{e?.unsubscribe?.()}}}return()=>{"function"==typeof L&&L();for(const[e,o]of y){const n=m.get(e),r=w.get(e);if("function"==typeof r)try{r()}catch(o){t(`[Lume.js] repeat(): cleanup error for key "${e}":`,o)}"function"==typeof g&&g(n,o)}h.replaceChildren(),y.clear(),m.clear(),$.clear(),w.clear(),E.clear()}}let a=!0,g=null;const p=new Map;function d(e){return null===g||("string"==typeof g?e.includes(g):!(g instanceof RegExp)||g.test(e))}function b(e){return p.has(e)||p.set(e,{gets:new Map,sets:new Map,notifies:new Map}),p.get(e)}function h(e,t,o){const n=b(e)[t];n.set(o,(n.get(o)||0)+1)}const y=100,m=97;function $(e){try{const t=JSON.stringify(e);return t.length>y?t.slice(0,m)+"...":t}catch{return e+""}}function w(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)&&d(e)&&console.log(`%c[${t}]%c GET %c${e}%c = ${$(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)&&d(e)&&(console.log(`%c[${t}]%c SET %c${e}%c: ${$(r)} → ${$(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&&d(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)&&d(e)&&console.log(`%c[${t}]%c NOTIFY %c${e}%c = ${$(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 p)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(){p.clear(),console.log("%c[lume-debug]%c Stats reset","color: #888; font-weight: bold","color: inherit")}};function j(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 S(){const e=[];return{add(t){"function"==typeof t&&e.push(t)},dispose(){for(;e.length;){const t=e.pop();try{t()}catch(e){}}}}}function L(e="#__LUME_DATA__"){const t="undefined"!=typeof document?document.querySelector(e):null;if(!t)return{};try{return JSON.parse(t.textContent)}catch{return{}}}function k(e){return!(!e||"object"!=typeof e||"function"!=typeof e.$subscribe)}export{s as computed,S as createCleanupGroup,w as createDebugPlugin,E as debug,l as defaultFocusPreservation,f as defaultScrollPreservation,L as hydrateState,k as isReactive,u as repeat,i as watch,j as withPlugins};
|
package/dist/addons.mjs
CHANGED
|
@@ -76,10 +76,20 @@ function computed(fn) {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
function watch(store, key, callback) {
|
|
79
|
+
function watch(store, key, callback, { immediate = true } = {}) {
|
|
80
80
|
if (!store.$subscribe) {
|
|
81
81
|
throw new Error("store must be created with state()");
|
|
82
82
|
}
|
|
83
|
+
if (!immediate) {
|
|
84
|
+
let skipped = false;
|
|
85
|
+
return store.$subscribe(key, (val) => {
|
|
86
|
+
if (!skipped) {
|
|
87
|
+
skipped = true;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
callback(val);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
83
93
|
return store.$subscribe(key, callback);
|
|
84
94
|
}
|
|
85
95
|
function defaultFocusPreservation(container) {
|
|
@@ -140,6 +150,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
140
150
|
render,
|
|
141
151
|
create,
|
|
142
152
|
update,
|
|
153
|
+
remove,
|
|
143
154
|
element = "div",
|
|
144
155
|
preserveFocus = defaultFocusPreservation,
|
|
145
156
|
preserveScroll = defaultScrollPreservation
|
|
@@ -159,6 +170,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
159
170
|
const elementsByKey = /* @__PURE__ */ new Map();
|
|
160
171
|
const prevItemsByKey = /* @__PURE__ */ new Map();
|
|
161
172
|
const prevIndexByKey = /* @__PURE__ */ new Map();
|
|
173
|
+
const cleanupByKey = /* @__PURE__ */ new Map();
|
|
162
174
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
163
175
|
function createElement() {
|
|
164
176
|
return typeof element === "function" ? element() : document.createElement(element);
|
|
@@ -221,7 +233,10 @@ function repeat(container, store, arrayKey, options) {
|
|
|
221
233
|
}
|
|
222
234
|
try {
|
|
223
235
|
if (isFirstRender && create) {
|
|
224
|
-
create(item, el, i);
|
|
236
|
+
const cleanup = create(item, el, i);
|
|
237
|
+
if (typeof cleanup === "function") {
|
|
238
|
+
cleanupByKey.set(k, cleanup);
|
|
239
|
+
}
|
|
225
240
|
}
|
|
226
241
|
const prevItem = prevItemsByKey.get(k);
|
|
227
242
|
const prevIndex = prevIndexByKey.get(k);
|
|
@@ -244,9 +259,23 @@ function repeat(container, store, arrayKey, options) {
|
|
|
244
259
|
if (elementsByKey.size !== seenKeys.size) {
|
|
245
260
|
for (const k of elementsByKey.keys()) {
|
|
246
261
|
if (!seenKeys.has(k)) {
|
|
262
|
+
const el = elementsByKey.get(k);
|
|
263
|
+
const prevItem = prevItemsByKey.get(k);
|
|
264
|
+
const cleanup = cleanupByKey.get(k);
|
|
265
|
+
if (typeof cleanup === "function") {
|
|
266
|
+
try {
|
|
267
|
+
cleanup();
|
|
268
|
+
} catch (err) {
|
|
269
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (typeof remove === "function" && el) {
|
|
273
|
+
remove(prevItem, el);
|
|
274
|
+
}
|
|
247
275
|
elementsByKey.delete(k);
|
|
248
276
|
prevItemsByKey.delete(k);
|
|
249
277
|
prevIndexByKey.delete(k);
|
|
278
|
+
cleanupByKey.delete(k);
|
|
250
279
|
}
|
|
251
280
|
}
|
|
252
281
|
}
|
|
@@ -265,10 +294,25 @@ function repeat(container, store, arrayKey, options) {
|
|
|
265
294
|
updateList();
|
|
266
295
|
logWarn("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)");
|
|
267
296
|
return () => {
|
|
297
|
+
for (const [k, el] of elementsByKey) {
|
|
298
|
+
const prevItem = prevItemsByKey.get(k);
|
|
299
|
+
const cleanup = cleanupByKey.get(k);
|
|
300
|
+
if (typeof cleanup === "function") {
|
|
301
|
+
try {
|
|
302
|
+
cleanup();
|
|
303
|
+
} catch (err) {
|
|
304
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (typeof remove === "function") {
|
|
308
|
+
remove(prevItem, el);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
268
311
|
containerEl.replaceChildren();
|
|
269
312
|
elementsByKey.clear();
|
|
270
313
|
prevItemsByKey.clear();
|
|
271
314
|
prevIndexByKey.clear();
|
|
315
|
+
cleanupByKey.clear();
|
|
272
316
|
seenKeys.clear();
|
|
273
317
|
};
|
|
274
318
|
}
|
|
@@ -276,10 +320,25 @@ function repeat(container, store, arrayKey, options) {
|
|
|
276
320
|
if (typeof unsubscribe === "function") {
|
|
277
321
|
unsubscribe();
|
|
278
322
|
}
|
|
323
|
+
for (const [k, el] of elementsByKey) {
|
|
324
|
+
const prevItem = prevItemsByKey.get(k);
|
|
325
|
+
const cleanup = cleanupByKey.get(k);
|
|
326
|
+
if (typeof cleanup === "function") {
|
|
327
|
+
try {
|
|
328
|
+
cleanup();
|
|
329
|
+
} catch (err) {
|
|
330
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (typeof remove === "function") {
|
|
334
|
+
remove(prevItem, el);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
279
337
|
containerEl.replaceChildren();
|
|
280
338
|
elementsByKey.clear();
|
|
281
339
|
prevItemsByKey.clear();
|
|
282
340
|
prevIndexByKey.clear();
|
|
341
|
+
cleanupByKey.clear();
|
|
283
342
|
seenKeys.clear();
|
|
284
343
|
};
|
|
285
344
|
}
|
package/dist/addons.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addons.mjs","sources":["../src/addons/computed.js","../src/addons/watch.js","../src/addons/repeat.js","../src/addons/debug.js","../src/addons/withPlugins.js","../src/addons/cleanupGroup.js","../src/addons/hydrateState.js","../src/addons/index.js"],"sourcesContent":["/**\n * Lume-JS Computed Addon\n * \n * Creates computed values that automatically update when dependencies change.\n * Uses core effect() for automatic dependency tracking.\n * \n * Usage:\n * import { computed } from \"lume-js/addons/computed\";\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // Auto-updates when store.count changes\n * \n * Features:\n * - Automatic dependency tracking (no manual recompute)\n * - Cached values (only recomputes when dependencies change)\n * - Subscribe to changes\n * - Cleanup with dispose()\n * \n * @module addons/computed\n */\n\nimport { effect } from '../core/effect.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Creates a computed value with automatic dependency tracking\n * \n * The computation function runs immediately and tracks which state\n * properties are accessed. When any dependency changes, the value\n * is automatically recomputed.\n *\n * ⚠️ Circular self-mutations are automatically suppressed. If a computed\n * mutates a state property it depends on, the flush triggered by that\n * mutation is skipped to prevent an infinite microtask loop.\n *\n * @param {function} fn - Function that computes the value\n * @returns {object} Object with .value property and methods\n * \n * @example\n * const store = state({ count: 5 });\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // 10\n * \n * store.count = 10;\n * // After microtask:\n * console.log(doubled.value); // 20 (auto-updated)\n * \n * @example\n * // Subscribe to changes\n * const unsub = doubled.subscribe(value => {\n * console.log('Doubled changed to:', value);\n * });\n * \n * @example\n * // Cleanup\n * doubled.dispose();\n */\nexport function computed(fn) {\n if (typeof fn !== 'function') {\n throw new Error('computed() requires a function');\n }\n\n let cachedValue;\n let isInitialized = false;\n let isInComputation = false;\n let disposed = false;\n const subscribers = [];\n\n // Use effect to automatically track dependencies\n const cleanupEffect = effect(() => {\n // Skip re-entry from a flush triggered by our own synchronous mutation.\n // The mutation inside fn() queues a microtask flush; we stay flagged\n // until a subsequent microtask clears it, so that flush is dropped.\n if (isInComputation || disposed) return;\n\n isInComputation = true;\n\n try {\n const newValue = fn();\n\n // Check if value actually changed - Object.is() handles NaN and -0\n if (!isInitialized || !Object.is(newValue, cachedValue)) {\n cachedValue = newValue;\n isInitialized = true;\n\n // Notify all subscribers\n subscribers.forEach(callback => callback(cachedValue));\n }\n } catch (error) {\n logError('[Lume.js computed] Error in computation:', error);\n // Set to undefined on error, mark as initialized\n if (!isInitialized || cachedValue !== undefined) {\n cachedValue = undefined;\n isInitialized = true;\n\n // Notify subscribers of error state\n subscribers.forEach(callback => callback(cachedValue));\n }\n } finally {\n // Defer clearing the flag so any flush microtask queued by fn()\n // sees it still set and skips re-entry.\n queueMicrotask(() => {\n if (!disposed) {\n isInComputation = false;\n }\n });\n }\n });\n\n return {\n /**\n * Get the current computed value\n */\n get value() {\n if (!isInitialized) {\n throw new Error('Computed value accessed before initialization');\n }\n return cachedValue;\n },\n\n /**\n * Subscribe to changes in computed value\n * \n * @param {function} callback - Called when value changes\n * @returns {function} Unsubscribe function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('subscribe() requires a function');\n }\n\n subscribers.push(callback);\n\n // Call immediately with current value\n if (isInitialized) {\n callback(cachedValue);\n }\n\n // Return unsubscribe function\n return () => {\n const index = subscribers.indexOf(callback);\n if (index > -1) {\n subscribers.splice(index, 1);\n }\n };\n },\n\n /**\n * Clean up computed value and stop tracking\n */\n dispose() {\n disposed = true;\n cleanupEffect();\n subscribers.length = 0;\n isInitialized = false;\n isInComputation = false;\n }\n };\n}","/**\n * watch - observes changes to a state key and triggers callback\n * @param {Object} store - reactive store created with state()\n * @param {string} key - key in store to watch\n * @param {Function} callback - called with new value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n return store.$subscribe(key, callback);\n}","/**\n * Lume-JS List Rendering (Addon)\n *\n * Renders lists with automatic subscription and element reuse by key.\n * \n * Core guarantees:\n * Element reuse by key (same DOM nodes, not recreated)\n * Minimal DOM operations (only updates what changed)\n * Memory efficiency (cleanup on remove)\n * \n * Default behavior (can be disabled/customized):\n * ✅ Focus preservation (maintains activeElement and selection)\n * ✅ Scroll preservation (intelligent positioning for add/remove/reorder)\n * \n * Philosophy: No artificial limitations\n * - All preservation logic is overridable via options\n * - Set to null/false to disable, or provide custom functions\n * - Export utilities so you can wrap/extend them\n *\n * ⚠️ IMPORTANT: Arrays must be updated immutably!\n * store.items.push(x) // ❌ Won't trigger update\n * store.items = [...items] // ✅ Triggers update\n * \n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 1: Simple (render only) - for simple cases or backward compat\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * render: (todo, el) => {\n * el.textContent = todo.name; // Called on every update\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 2: Clean separation (create + update) - recommended\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * create: (todo, el) => {\n * // Called ONCE when element is created - build DOM structure\n * const nameSpan = document.createElement('span');\n * nameSpan.className = 'name';\n * el.appendChild(nameSpan);\n * const btn = document.createElement('button');\n * btn.textContent = 'Delete';\n * btn.onclick = () => deleteTodo(todo.id);\n * el.appendChild(btn);\n * },\n * update: (todo, el, index, { isFirstRender }) => {\n * // Called on every update - bind data\n * // isFirstRender = true on initial render, false on subsequent\n * // Skipped if same object reference (optimization)\n * el.querySelector('.name').textContent = todo.name;\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * ADVANCED: Custom preservation strategies\n * ═══════════════════════════════════════════════════════════════════════\n * \n * import { defaultFocusPreservation, defaultScrollPreservation } from \"lume-js/addons\";\n * \n * repeat('#list', store, 'items', {\n * key: item => item.id,\n * create: (item, el) => { ... },\n * update: (item, el) => { ... },\n * preserveFocus: null, // disable focus preservation\n * preserveScroll: (container, context) => {\n * const restore = defaultScrollPreservation(container, context);\n * return () => { restore(); console.log('Scroll restored!'); };\n * }\n * });\n */\nimport { logWarn, logError } from '../utils/log.js';\n\n/**\n * Default focus preservation strategy\n * Saves activeElement and selection state before DOM updates\n * \n * @param {HTMLElement} container - The list container\n * @returns {Function|null} Restore function, or null if nothing to restore\n */\nexport function defaultFocusPreservation(container) {\n const activeEl = document.activeElement;\n const shouldRestore = container.contains(activeEl);\n\n if (!shouldRestore) return null;\n\n let selectionStart = null;\n let selectionEnd = null;\n\n if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA') {\n selectionStart = activeEl.selectionStart;\n selectionEnd = activeEl.selectionEnd;\n }\n\n return () => {\n if (document.body.contains(activeEl)) {\n activeEl.focus();\n if (selectionStart !== null && selectionEnd !== null) {\n activeEl.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n };\n}\n\n/**\n * Default scroll preservation strategy\n * Uses anchor-based preservation for add/remove, pixel position for reorder\n * \n * @param {HTMLElement} container - The list container\n * @param {Object} context - Additional context\n * @param {boolean} context.isReorder - Whether this is a reorder operation\n * @returns {Function} Restore function\n */\nexport function defaultScrollPreservation(container, context = {}) {\n const { isReorder = false } = context;\n const scrollTop = container.scrollTop;\n\n // Early return if no scroll\n if (scrollTop === 0) {\n return () => { container.scrollTop = 0; };\n }\n\n let anchorElement = null;\n let anchorOffset = 0;\n\n // Only use anchor-based preservation for add/remove, not reorder\n if (!isReorder) {\n const containerRect = container.getBoundingClientRect();\n // Avoid Array.from - iterate children directly\n for (let child = container.firstElementChild; child; child = child.nextElementSibling) {\n const rect = child.getBoundingClientRect();\n\n if (rect.bottom > containerRect.top) {\n anchorElement = child;\n anchorOffset = rect.top - containerRect.top;\n break;\n }\n }\n }\n\n return () => {\n if (anchorElement && document.body.contains(anchorElement)) {\n const newRect = anchorElement.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n const currentOffset = newRect.top - containerRect.top;\n const scrollAdjustment = currentOffset - anchorOffset;\n\n container.scrollTop = container.scrollTop + scrollAdjustment;\n } else {\n container.scrollTop = scrollTop;\n }\n };\n}\n\n/**\n * Efficiently render a list with element reuse\n * \n * @param {string|HTMLElement} container - Container element or selector\n * @param {Object} store - Reactive state object\n * @param {string} arrayKey - Key in store containing the array\n * @param {Object} options - Configuration\n * @param {Function} options.key - Function to extract unique key: (item) => key\n * @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void\n * @param {Function} [options.create] - Function for new elements only: (item, element, index) => void\n * @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function\n * @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)\n * @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)\n * @returns {Function} Cleanup function\n */\n\nexport function repeat(container, store, arrayKey, options) {\n const {\n key,\n render,\n create,\n update,\n element = 'div',\n preserveFocus = defaultFocusPreservation,\n preserveScroll = defaultScrollPreservation\n } = options;\n\n // Resolve container\n const containerEl =\n typeof container === 'string'\n ? document.querySelector(container)\n : container;\n\n if (!containerEl) {\n logWarn(`[Lume.js] repeat(): container \"${container}\" not found`);\n return () => { };\n }\n\n if (typeof key !== 'function') {\n throw new Error('[Lume.js] repeat(): options.key must be a function');\n }\n\n if (typeof render !== 'function' && typeof create !== 'function') {\n throw new Error('[Lume.js] repeat(): options.render or options.create must be a function');\n }\n\n // key -> HTMLElement\n const elementsByKey = new Map();\n // key -> previous item (for reference comparison)\n const prevItemsByKey = new Map();\n // key -> previous index (for reorder detection)\n const prevIndexByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n const desired = nextEls[i];\n\n if (ptr === desired) {\n ptr = ptr.nextSibling;\n continue;\n }\n\n container.insertBefore(desired, ptr);\n }\n\n // Remove leftover children not in nextEls\n while (ptr) {\n const next = ptr.nextSibling;\n container.removeChild(ptr);\n ptr = next;\n }\n }\n\n function applyPreservation(container, fn, isReorder) {\n const shouldPreserve = document.body.contains(container);\n const restoreFocus = shouldPreserve && preserveFocus ? preserveFocus(container) : null;\n const restoreScroll = shouldPreserve && preserveScroll ? preserveScroll(container, { isReorder }) : null;\n\n fn();\n\n if (restoreFocus) restoreFocus();\n if (restoreScroll) restoreScroll();\n }\n\n function updateList() {\n const items = store[arrayKey];\n\n if (!Array.isArray(items)) {\n logWarn(`[Lume.js] repeat(): store.${arrayKey} is not an array`);\n return;\n }\n\n // Only compute isReorder if scroll preservation needs it.\n // Uses elementsByKey (previous state) and items directly — no Set allocations.\n let isReorder = false;\n if (preserveScroll && elementsByKey.size === items.length) {\n isReorder = true;\n for (let i = 0; i < items.length; i++) {\n if (!elementsByKey.has(key(items[i]))) { isReorder = false; break; }\n }\n }\n\n seenKeys.clear();\n const nextEls = [];\n\n // Build ordered list of DOM nodes (created or reused)\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key(item);\n\n if (seenKeys.has(k)) {\n logWarn(`[Lume.js] repeat(): duplicate key \"${k}\"`);\n continue;\n }\n seenKeys.add(k);\n\n let el = elementsByKey.get(k);\n const isFirstRender = !el;\n\n if (isFirstRender) {\n el = createElement();\n elementsByKey.set(k, el);\n }\n\n try {\n // Call create for new elements (DOM structure)\n if (isFirstRender && create) {\n create(item, el, i);\n }\n\n // Call update for data binding (new and existing elements)\n // Skip if same item reference AND same index (optimization)\n const prevItem = prevItemsByKey.get(k);\n const prevIndex = prevIndexByKey.get(k);\n if (update) {\n if (prevItem !== item || prevIndex !== i) {\n update(item, el, i, { isFirstRender });\n }\n } else if (render) {\n // Backward compatibility: render handles both create and update\n render(item, el, i);\n }\n\n // Store reference and index for next comparison\n prevItemsByKey.set(k, item);\n prevIndexByKey.set(k, i);\n\n } catch (err) {\n logError(`[Lume.js] repeat(): error rendering key \"${k}\":`, err);\n }\n\n nextEls.push(el);\n }\n\n applyPreservation(containerEl, () => {\n reconcileDOM(containerEl, nextEls);\n\n // Clean maps: remove keys not in seenKeys (new state)\n if (elementsByKey.size !== seenKeys.size) {\n for (const k of elementsByKey.keys()) {\n if (!seenKeys.has(k)) {\n elementsByKey.delete(k);\n prevItemsByKey.delete(k);\n prevIndexByKey.delete(k);\n }\n }\n }\n }, isReorder);\n }\n\n // Subscription — $subscribe calls updateList immediately (initial render),\n // so no separate updateList() call is needed for reactive stores.\n let unsubscribe;\n if (typeof store.$subscribe === 'function') {\n unsubscribe = store.$subscribe(arrayKey, updateList);\n } else if (typeof store.subscribe === 'function') {\n // Generic subscribe (e.g. computed) — subscribe first, then initial render\n const subResult = store.subscribe(() => updateList());\n updateList();\n // Normalize both function-style and object-style (RxJS Subscription) returns\n unsubscribe = typeof subResult === 'function'\n ? subResult\n : () => { subResult?.unsubscribe?.(); };\n } else {\n // Non-reactive store — render once and return cleanup\n updateList();\n logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');\n return () => {\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n seenKeys.clear();\n };\n }\n\n return () => {\n if (typeof unsubscribe === 'function') {\n unsubscribe();\n }\n // Clear DOM elements (replaceChildren is faster than loop)\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n seenKeys.clear();\n };\n}\n","/**\n * Lume-JS Debug Addon\n * \n * Developer-friendly logging and inspection of reactive state operations.\n * Critical for adoption - hard to debug = hard to adopt.\n * \n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin, debug } from \"lume-js/addons\";\n * \n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'myStore' })]);\n * \n * debug.enable(); // Enable logging\n * debug.filter('count'); // Only log 'count' key\n * debug.stats(); // Show statistics\n * \n * @module addons/debug\n */\n\n// Global debug state\nlet globalEnabled = true;\nlet globalFilter = null; // string, RegExp, or null\nconst stats = new Map(); // label -> { gets: Map, sets: Map, notifies: Map }\n\n/**\n * Check if a key matches the current filter\n * @param {string} key\n * @returns {boolean}\n */\nfunction matchesFilter(key) {\n if (globalFilter === null) return true;\n if (typeof globalFilter === 'string') {\n return key.includes(globalFilter);\n }\n if (globalFilter instanceof RegExp) {\n return globalFilter.test(key);\n }\n return true;\n}\n\n/**\n * Get or create stats entry for a label\n * @param {string} label\n * @returns {object}\n */\nfunction getStats(label) {\n if (!stats.has(label)) {\n stats.set(label, {\n gets: new Map(),\n sets: new Map(),\n notifies: new Map()\n });\n }\n return stats.get(label);\n}\n\n/**\n * Increment a stat counter\n * @param {string} label\n * @param {'gets'|'sets'|'notifies'} type\n * @param {string} key\n */\nfunction incrementStat(label, type, key) {\n const s = getStats(label);\n const map = s[type];\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nconst MAX_LOG_LEN = 100;\nconst TRUNCATED_LEN = MAX_LOG_LEN - 3;\n\n/**\n * Format value for logging (truncate long values)\n * @param {any} value\n * @returns {string}\n */\nfunction formatValue(value) {\n try {\n const json = JSON.stringify(value);\n if (json.length > MAX_LOG_LEN) {\n return json.slice(0, TRUNCATED_LEN) + '...';\n }\n return json;\n } catch {\n return String(value);\n }\n}\n\n/**\n * Create a debug plugin instance for a reactive state store.\n * \n * @param {object} [options] - Configuration options\n * @param {string} [options.label='store'] - Label for log messages\n * @param {boolean} [options.logGet=false] - Log property reads (can be noisy)\n * @param {boolean} [options.logSet=true] - Log property writes\n * @param {boolean} [options.logNotify=true] - Log subscriber notifications\n * @param {boolean} [options.trace=false] - Show stack trace for SET operations\n * @returns {object} Plugin object for state()\n * \n * @example\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n * \n * @example\n * // With stack traces for debugging where state changes originate\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter', trace: true })]);\n */\nexport function createDebugPlugin(options = {}) {\n const label = options.label ?? 'store';\n\n // IMPORTANT: Do NOT destructure options here!\n // Options may contain getters for dynamic runtime toggling (e.g., from UI).\n // Destructuring would copy values once at creation time, breaking reactivity.\n // Use getOpt() helper to read options dynamically in each hook.\n const getOpt = (name, defaultVal) => {\n const val = options[name];\n return val !== undefined ? val : defaultVal;\n };\n\n return {\n name: `debug:${label}`,\n\n onInit: () => {\n if (globalEnabled) {\n console.log(`%c[${label}]%c initialized`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n onGet: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return value;\n }\n\n incrementStat(label, 'gets', key);\n\n if (globalEnabled && getOpt('logGet', false) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c GET %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #4CAF50',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n\n return value;\n },\n\n onSet: (key, newValue, oldValue) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return newValue;\n }\n\n incrementStat(label, 'sets', key);\n\n if (globalEnabled && getOpt('logSet', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SET %c${key}%c: ${formatValue(oldValue)} → ${formatValue(newValue)}`,\n 'color: #888; font-weight: bold',\n 'color: #FF9800',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n\n // Show stack trace if enabled (helps find where state changes originate)\n if (getOpt('trace', false)) {\n console.trace(`%c[${label}] Stack trace for ${key}`, 'color: #888');\n }\n }\n\n return newValue;\n },\n\n onSubscribe: (key) => {\n if (globalEnabled && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SUBSCRIBE %c${key}`,\n 'color: #888; font-weight: bold',\n 'color: #9C27B0',\n 'color: #2196F3; font-weight: bold'\n );\n }\n },\n\n onNotify: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return;\n }\n\n incrementStat(label, 'notifies', key);\n\n if (globalEnabled && getOpt('logNotify', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c NOTIFY %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #E91E63',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n }\n };\n}\n\n/**\n * Global debug controls\n */\nexport const debug = {\n /**\n * Enable debug logging globally\n */\n enable() {\n globalEnabled = true;\n console.log('%c[lume-debug]%c Logging enabled', 'color: #888; font-weight: bold', 'color: #4CAF50');\n },\n\n /**\n * Disable debug logging globally\n */\n disable() {\n globalEnabled = false;\n console.log('%c[lume-debug]%c Logging disabled', 'color: #888; font-weight: bold', 'color: #F44336');\n },\n\n /**\n * Check if debug logging is currently enabled\n * @returns {boolean}\n */\n isEnabled() {\n return globalEnabled;\n },\n\n /**\n * Filter logs by key pattern\n * @param {string|RegExp|null} pattern - Pattern to match, or null to clear filter\n */\n filter(pattern) {\n globalFilter = pattern;\n if (pattern === null) {\n console.log('%c[lume-debug]%c Filter cleared', 'color: #888; font-weight: bold', 'color: inherit');\n } else {\n console.log(`%c[lume-debug]%c Filter set: ${pattern}`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n /**\n * Get current filter pattern\n * @returns {string|RegExp|null}\n */\n getFilter() {\n return globalFilter;\n },\n\n /**\n * Get statistics data (silent - no console output)\n * Use logStats() if you want to see stats in console.\n * @returns {object} Stats object for programmatic access\n */\n stats() {\n const result = {};\n\n for (const [label, data] of stats) {\n result[label] = {\n gets: Object.fromEntries(data.gets),\n sets: Object.fromEntries(data.sets),\n notifies: Object.fromEntries(data.notifies)\n };\n }\n\n return result;\n },\n\n /**\n * Log statistics summary to console (with formatting)\n * @returns {object} Stats object for programmatic access\n */\n logStats() {\n const result = this.stats();\n\n if (Object.keys(result).length === 0) {\n console.log('%c[lume-debug]%c No stats collected yet', 'color: #888; font-weight: bold', 'color: inherit');\n return result;\n }\n\n console.group('%c[lume-debug] Statistics', 'color: #888; font-weight: bold');\n\n for (const [label, data] of Object.entries(result)) {\n console.group(`%c${label}`, 'color: #2196F3; font-weight: bold');\n\n // Use console.table for better formatted output\n const tableData = [];\n const allKeys = new Set([\n ...Object.keys(data.gets),\n ...Object.keys(data.sets),\n ...Object.keys(data.notifies)\n ]);\n\n for (const key of allKeys) {\n tableData.push({\n key,\n gets: data.gets[key] || 0,\n sets: data.sets[key] || 0,\n notifies: data.notifies[key] || 0\n });\n }\n\n if (tableData.length > 0) {\n console.table(tableData);\n }\n\n console.groupEnd();\n }\n\n console.groupEnd();\n\n return result;\n },\n\n /**\n * Reset all collected statistics\n */\n resetStats() {\n stats.clear();\n console.log('%c[lume-debug]%c Stats reset', 'color: #888; font-weight: bold', 'color: inherit');\n }\n};\n","/**\n * Lume-JS withPlugins Addon\n *\n * Wraps a reactive state proxy with a plugin layer that intercepts\n * get/set/notify/subscribe operations via plugin hooks.\n *\n * Only stores that opt into debugging or custom behaviors need this.\n * Core state() is not aware of plugins.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin } from \"lume-js/addons\";\n *\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n */\n\n/**\n * Wrap a reactive state proxy with plugin hooks.\n *\n * Plugin hooks (all optional):\n * onInit() — called once at wrap time\n * onGet(key, value) → value|void — intercept/transform reads\n * onSet(key, newVal, oldVal) → val|void — intercept/transform writes\n * onNotify(key, value) — called before subscribers are notified\n * onSubscribe(key) — called when $subscribe is invoked\n *\n * @param {object} store - A reactive proxy from state()\n * @param {Array<object>} plugins - Array of plugin objects\n * @returns {Proxy} A new proxy wrapping the store with plugin behavior\n */\nimport { logError } from '../utils/log.js';\n\nexport function withPlugins(store, plugins = []) {\n if (!plugins.length) return store;\n\n // Call onInit hooks once at wrap time\n for (const p of plugins) {\n try {\n p.onInit?.();\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onInit:`, e);\n }\n }\n\n // Track pending notifications for onNotify hooks.\n // Instead of a separate microtask, we hook into the underlying state's\n // flush via $beforeFlush so onNotify and subscribers share one microtask.\n const pendingNotifications = new Map();\n\n function runNotifyHooks() {\n for (const [key, value] of pendingNotifications) {\n for (const p of plugins) {\n try {\n p.onNotify?.(key, value);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onNotify:`, e);\n }\n }\n }\n pendingNotifications.clear();\n }\n\n // Register once on the underlying state; capture unsubscribe for cleanup.\n let flushUnsub;\n if (typeof store.$beforeFlush === 'function') {\n flushUnsub = store.$beforeFlush(runNotifyHooks);\n }\n\n return new Proxy(store, {\n get(target, key) {\n // $dispose — remove the beforeFlush hook and clear pending state\n if (key === '$dispose') {\n return () => {\n if (flushUnsub) flushUnsub();\n pendingNotifications.clear();\n };\n }\n\n // Pass $-prefixed meta methods through without interception\n if (typeof key === 'string' && key.startsWith('$')) {\n const method = target[key];\n if (key === '$subscribe' && typeof method === 'function') {\n // Wrap $subscribe to call onSubscribe hooks\n return (subKey, fn) => {\n for (const p of plugins) {\n try {\n p.onSubscribe?.(subKey);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSubscribe:`, e);\n }\n }\n return method(subKey, fn);\n };\n }\n return method;\n }\n\n let value = target[key];\n\n // onGet chain\n for (const p of plugins) {\n try {\n const r = p.onGet?.(key, value);\n if (r !== undefined) value = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onGet:`, e);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n let newValue = value;\n\n // onSet chain\n for (const p of plugins) {\n try {\n const r = p.onSet?.(key, newValue, oldValue);\n if (r !== undefined) newValue = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSet:`, e);\n }\n }\n\n // Only queue onNotify if the value actually changed after plugin chain\n if (!Object.is(newValue, oldValue)) {\n pendingNotifications.set(key, newValue);\n }\n\n target[key] = newValue;\n return true;\n }\n });\n}\n","/**\n * Creates a cleanup group that can collect and dispose multiple\n * cleanup/unsubscribe functions at once.\n *\n * @returns {CleanupGroup}\n *\n * @example\n * ```js\n * import { createCleanupGroup } from 'lume-js/addons';\n *\n * const group = createCleanupGroup();\n * group.add(bindDom(root, store));\n * group.add(effect(() => { ... }));\n * group.add(store.$subscribe('key', fn));\n *\n * // Dispose everything at once\n * group.dispose();\n * ```\n */\nexport function createCleanupGroup() {\n const cleanups = [];\n\n return {\n /**\n * Add a cleanup function to the group.\n * @param {Function} fn - Cleanup/unsubscribe function\n */\n add(fn) {\n if (typeof fn === 'function') {\n cleanups.push(fn);\n }\n },\n\n /**\n * Run all collected cleanup functions and clear the group.\n */\n dispose() {\n while (cleanups.length) {\n const fn = cleanups.pop();\n try { fn(); } catch (e) { /* ignore cleanup errors */ }\n }\n },\n };\n}\n","/**\n * Reads initial state from a `<script type=\"application/json\">` element\n * embedded in the server-rendered HTML. Useful for SSR / hydration patterns.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @returns {object} Parsed JSON object, or empty object if not found / invalid\n *\n * @example\n * ```html\n * <script id=\"__LUME_DATA__\" type=\"application/json\">\n * {\"title\": \"Welcome\", \"count\": 42}\n * </script>\n * ```\n *\n * ```js\n * import { state } from 'lume-js';\n * import { hydrateState } from 'lume-js/addons';\n *\n * const store = state(hydrateState());\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__') {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n try {\n return JSON.parse(el.textContent);\n } catch {\n return {};\n }\n}\n","export { computed } from \"./computed.js\";\nexport { watch } from \"./watch.js\";\nexport { repeat, defaultFocusPreservation, defaultScrollPreservation } from \"./repeat.js\";\nexport { createDebugPlugin, debug } from \"./debug.js\";\nexport { withPlugins } from \"./withPlugins.js\";\nexport { createCleanupGroup } from \"./cleanupGroup.js\";\nexport { hydrateState } from \"./hydrateState.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n * Uses duck-typing: checks for the presence of $subscribe.\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' && typeof obj.$subscribe === 'function');\n}\n"],"names":["container"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"addons.mjs","sources":["../src/addons/computed.js","../src/addons/watch.js","../src/addons/repeat.js","../src/addons/debug.js","../src/addons/withPlugins.js","../src/addons/cleanupGroup.js","../src/addons/hydrateState.js","../src/addons/index.js"],"sourcesContent":["/**\n * Lume-JS Computed Addon\n * \n * Creates computed values that automatically update when dependencies change.\n * Uses core effect() for automatic dependency tracking.\n * \n * Usage:\n * import { computed } from \"lume-js/addons/computed\";\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // Auto-updates when store.count changes\n * \n * Features:\n * - Automatic dependency tracking (no manual recompute)\n * - Cached values (only recomputes when dependencies change)\n * - Subscribe to changes\n * - Cleanup with dispose()\n * \n * @module addons/computed\n */\n\nimport { effect } from '../core/effect.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Creates a computed value with automatic dependency tracking\n * \n * The computation function runs immediately and tracks which state\n * properties are accessed. When any dependency changes, the value\n * is automatically recomputed.\n *\n * ⚠️ Circular self-mutations are automatically suppressed. If a computed\n * mutates a state property it depends on, the flush triggered by that\n * mutation is skipped to prevent an infinite microtask loop.\n *\n * @param {function} fn - Function that computes the value\n * @returns {object} Object with .value property and methods\n * \n * @example\n * const store = state({ count: 5 });\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // 10\n * \n * store.count = 10;\n * // After microtask:\n * console.log(doubled.value); // 20 (auto-updated)\n * \n * @example\n * // Subscribe to changes\n * const unsub = doubled.subscribe(value => {\n * console.log('Doubled changed to:', value);\n * });\n * \n * @example\n * // Cleanup\n * doubled.dispose();\n */\nexport function computed(fn) {\n if (typeof fn !== 'function') {\n throw new Error('computed() requires a function');\n }\n\n let cachedValue;\n let isInitialized = false;\n let isInComputation = false;\n let disposed = false;\n const subscribers = [];\n\n // Use effect to automatically track dependencies\n const cleanupEffect = effect(() => {\n // Skip re-entry from a flush triggered by our own synchronous mutation.\n // The mutation inside fn() queues a microtask flush; we stay flagged\n // until a subsequent microtask clears it, so that flush is dropped.\n if (isInComputation || disposed) return;\n\n isInComputation = true;\n\n try {\n const newValue = fn();\n\n // Check if value actually changed - Object.is() handles NaN and -0\n if (!isInitialized || !Object.is(newValue, cachedValue)) {\n cachedValue = newValue;\n isInitialized = true;\n\n // Notify all subscribers\n subscribers.forEach(callback => callback(cachedValue));\n }\n } catch (error) {\n logError('[Lume.js computed] Error in computation:', error);\n // Set to undefined on error, mark as initialized\n if (!isInitialized || cachedValue !== undefined) {\n cachedValue = undefined;\n isInitialized = true;\n\n // Notify subscribers of error state\n subscribers.forEach(callback => callback(cachedValue));\n }\n } finally {\n // Defer clearing the flag so any flush microtask queued by fn()\n // sees it still set and skips re-entry.\n queueMicrotask(() => {\n if (!disposed) {\n isInComputation = false;\n }\n });\n }\n });\n\n return {\n /**\n * Get the current computed value\n */\n get value() {\n if (!isInitialized) {\n throw new Error('Computed value accessed before initialization');\n }\n return cachedValue;\n },\n\n /**\n * Subscribe to changes in computed value\n * \n * @param {function} callback - Called when value changes\n * @returns {function} Unsubscribe function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('subscribe() requires a function');\n }\n\n subscribers.push(callback);\n\n // Call immediately with current value\n if (isInitialized) {\n callback(cachedValue);\n }\n\n // Return unsubscribe function\n return () => {\n const index = subscribers.indexOf(callback);\n if (index > -1) {\n subscribers.splice(index, 1);\n }\n };\n },\n\n /**\n * Clean up computed value and stop tracking\n */\n dispose() {\n disposed = true;\n cleanupEffect();\n subscribers.length = 0;\n isInitialized = false;\n isInComputation = false;\n }\n };\n}","/**\n * watch - observes changes to a state key and triggers callback\n * @param {Object} store - reactive store created with state()\n * @param {string} key - key in store to watch\n * @param {Function} callback - called with new value\n * @param {Object} [options]\n * @param {boolean} [options.immediate=true] - call callback immediately with current value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback, { immediate = true } = {}) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n if (!immediate) {\n let skipped = false;\n return store.$subscribe(key, (val) => {\n if (!skipped) { skipped = true; return; }\n callback(val);\n });\n }\n return store.$subscribe(key, callback);\n}","/**\n * Lume-JS List Rendering (Addon)\n *\n * Renders lists with automatic subscription and element reuse by key.\n * \n * Core guarantees:\n * Element reuse by key (same DOM nodes, not recreated)\n * Minimal DOM operations (only updates what changed)\n * Memory efficiency (cleanup on remove)\n * \n * Default behavior (can be disabled/customized):\n * ✅ Focus preservation (maintains activeElement and selection)\n * ✅ Scroll preservation (intelligent positioning for add/remove/reorder)\n * \n * Philosophy: No artificial limitations\n * - All preservation logic is overridable via options\n * - Set to null/false to disable, or provide custom functions\n * - Export utilities so you can wrap/extend them\n *\n * ⚠️ IMPORTANT: Arrays must be updated immutably!\n * store.items.push(x) // ❌ Won't trigger update\n * store.items = [...items] // ✅ Triggers update\n * \n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 1: Simple (render only) - for simple cases or backward compat\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * render: (todo, el) => {\n * el.textContent = todo.name; // Called on every update\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 2: Clean separation (create + update) - recommended\n * ═══════════════════════════════════════════════════════════════════════\n *\n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * create: (todo, el) => {\n * // Called ONCE when element is created - build DOM structure\n * const nameSpan = document.createElement('span');\n * nameSpan.className = 'name';\n * el.appendChild(nameSpan);\n * const btn = document.createElement('button');\n * btn.textContent = 'Delete';\n * btn.onclick = () => deleteTodo(todo.id);\n * el.appendChild(btn);\n *\n * // Return a cleanup function — called automatically when element is removed\n * return () => {\n * // Unsubscribe from external listeners, remove timers, etc.\n * };\n * },\n * update: (todo, el, index, { isFirstRender }) => {\n * // Called on every update - bind data\n * // isFirstRender = true on initial render, false on subsequent\n * // Skipped if same object reference (optimization)\n * el.querySelector('.name').textContent = todo.name;\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * ADVANCED: Custom preservation strategies\n * ═══════════════════════════════════════════════════════════════════════\n * \n * import { defaultFocusPreservation, defaultScrollPreservation } from \"lume-js/addons\";\n * \n * repeat('#list', store, 'items', {\n * key: item => item.id,\n * create: (item, el) => { ... },\n * update: (item, el) => { ... },\n * preserveFocus: null, // disable focus preservation\n * preserveScroll: (container, context) => {\n * const restore = defaultScrollPreservation(container, context);\n * return () => { restore(); console.log('Scroll restored!'); };\n * }\n * });\n */\nimport { logWarn, logError } from '../utils/log.js';\n\n/**\n * Default focus preservation strategy\n * Saves activeElement and selection state before DOM updates\n * \n * @param {HTMLElement} container - The list container\n * @returns {Function|null} Restore function, or null if nothing to restore\n */\nexport function defaultFocusPreservation(container) {\n const activeEl = document.activeElement;\n const shouldRestore = container.contains(activeEl);\n\n if (!shouldRestore) return null;\n\n let selectionStart = null;\n let selectionEnd = null;\n\n if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA') {\n selectionStart = activeEl.selectionStart;\n selectionEnd = activeEl.selectionEnd;\n }\n\n return () => {\n if (document.body.contains(activeEl)) {\n activeEl.focus();\n if (selectionStart !== null && selectionEnd !== null) {\n activeEl.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n };\n}\n\n/**\n * Default scroll preservation strategy\n * Uses anchor-based preservation for add/remove, pixel position for reorder\n * \n * @param {HTMLElement} container - The list container\n * @param {Object} context - Additional context\n * @param {boolean} context.isReorder - Whether this is a reorder operation\n * @returns {Function} Restore function\n */\nexport function defaultScrollPreservation(container, context = {}) {\n const { isReorder = false } = context;\n const scrollTop = container.scrollTop;\n\n // Early return if no scroll\n if (scrollTop === 0) {\n return () => { container.scrollTop = 0; };\n }\n\n let anchorElement = null;\n let anchorOffset = 0;\n\n // Only use anchor-based preservation for add/remove, not reorder\n if (!isReorder) {\n const containerRect = container.getBoundingClientRect();\n // Avoid Array.from - iterate children directly\n for (let child = container.firstElementChild; child; child = child.nextElementSibling) {\n const rect = child.getBoundingClientRect();\n\n if (rect.bottom > containerRect.top) {\n anchorElement = child;\n anchorOffset = rect.top - containerRect.top;\n break;\n }\n }\n }\n\n return () => {\n if (anchorElement && document.body.contains(anchorElement)) {\n const newRect = anchorElement.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n const currentOffset = newRect.top - containerRect.top;\n const scrollAdjustment = currentOffset - anchorOffset;\n\n container.scrollTop = container.scrollTop + scrollAdjustment;\n } else {\n container.scrollTop = scrollTop;\n }\n };\n}\n\n/**\n * Efficiently render a list with element reuse\n * \n * @param {string|HTMLElement} container - Container element or selector\n * @param {Object} store - Reactive state object\n * @param {string} arrayKey - Key in store containing the array\n * @param {Object} options - Configuration\n * @param {Function} options.key - Function to extract unique key: (item) => key\n * @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void\n * @param {Function} [options.create] - Function for new elements only: (item, element, index) => void | Function. If a function is returned, it is registered as the element's cleanup and called automatically when the element is removed (by list update or full cleanup).\n * @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.\n * @param {Function} [options.remove] - Additional cleanup when element is removed: (item, element) => void. Called after any cleanup function returned by create(). Optional — prefer returning a cleanup from create() for automatic lifecycle management.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function\n * @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)\n * @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)\n * @returns {Function} Cleanup function\n */\n\nexport function repeat(container, store, arrayKey, options) {\n const {\n key,\n render,\n create,\n update,\n remove,\n element = 'div',\n preserveFocus = defaultFocusPreservation,\n preserveScroll = defaultScrollPreservation\n } = options;\n\n // Resolve container\n const containerEl =\n typeof container === 'string'\n ? document.querySelector(container)\n : container;\n\n if (!containerEl) {\n logWarn(`[Lume.js] repeat(): container \"${container}\" not found`);\n return () => { };\n }\n\n if (typeof key !== 'function') {\n throw new Error('[Lume.js] repeat(): options.key must be a function');\n }\n\n if (typeof render !== 'function' && typeof create !== 'function') {\n throw new Error('[Lume.js] repeat(): options.render or options.create must be a function');\n }\n\n // key -> HTMLElement\n const elementsByKey = new Map();\n // key -> previous item (for reference comparison)\n const prevItemsByKey = new Map();\n // key -> previous index (for reorder detection)\n const prevIndexByKey = new Map();\n // key -> cleanup function returned by create()\n const cleanupByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n const desired = nextEls[i];\n\n if (ptr === desired) {\n ptr = ptr.nextSibling;\n continue;\n }\n\n container.insertBefore(desired, ptr);\n }\n\n // Remove leftover children not in nextEls\n while (ptr) {\n const next = ptr.nextSibling;\n container.removeChild(ptr);\n ptr = next;\n }\n }\n\n function applyPreservation(container, fn, isReorder) {\n const shouldPreserve = document.body.contains(container);\n const restoreFocus = shouldPreserve && preserveFocus ? preserveFocus(container) : null;\n const restoreScroll = shouldPreserve && preserveScroll ? preserveScroll(container, { isReorder }) : null;\n\n fn();\n\n if (restoreFocus) restoreFocus();\n if (restoreScroll) restoreScroll();\n }\n\n // eslint-disable-next-line sonarjs/cognitive-complexity -- keyed DOM reconciliation: create/reuse/remove nodes, key dedup, scroll/focus preservation\n function updateList() {\n const items = store[arrayKey];\n\n if (!Array.isArray(items)) {\n logWarn(`[Lume.js] repeat(): store.${arrayKey} is not an array`);\n return;\n }\n\n // Only compute isReorder if scroll preservation needs it.\n // Uses elementsByKey (previous state) and items directly — no Set allocations.\n let isReorder = false;\n if (preserveScroll && elementsByKey.size === items.length) {\n isReorder = true;\n for (let i = 0; i < items.length; i++) {\n if (!elementsByKey.has(key(items[i]))) { isReorder = false; break; }\n }\n }\n\n seenKeys.clear();\n const nextEls = [];\n\n // Build ordered list of DOM nodes (created or reused)\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key(item);\n\n if (seenKeys.has(k)) {\n logWarn(`[Lume.js] repeat(): duplicate key \"${k}\"`);\n continue;\n }\n seenKeys.add(k);\n\n let el = elementsByKey.get(k);\n const isFirstRender = !el;\n\n if (isFirstRender) {\n el = createElement();\n elementsByKey.set(k, el);\n }\n\n try {\n // Call create for new elements (DOM structure)\n if (isFirstRender && create) {\n const cleanup = create(item, el, i);\n if (typeof cleanup === 'function') {\n cleanupByKey.set(k, cleanup);\n }\n }\n\n // Call update for data binding (new and existing elements)\n // Skip if same item reference AND same index (optimization)\n const prevItem = prevItemsByKey.get(k);\n const prevIndex = prevIndexByKey.get(k);\n if (update) {\n if (prevItem !== item || prevIndex !== i) {\n update(item, el, i, { isFirstRender });\n }\n } else if (render) {\n // Backward compatibility: render handles both create and update\n render(item, el, i);\n }\n\n // Store reference and index for next comparison\n prevItemsByKey.set(k, item);\n prevIndexByKey.set(k, i);\n\n } catch (err) {\n logError(`[Lume.js] repeat(): error rendering key \"${k}\":`, err);\n }\n\n nextEls.push(el);\n }\n\n // eslint-disable-next-line sonarjs/cognitive-complexity -- DOM cleanup pass: remove stale nodes, call per-item cleanup callbacks, update maps\n applyPreservation(containerEl, () => {\n reconcileDOM(containerEl, nextEls);\n\n // Clean maps: remove keys not in seenKeys (new state)\n if (elementsByKey.size !== seenKeys.size) {\n for (const k of elementsByKey.keys()) {\n if (!seenKeys.has(k)) {\n const el = elementsByKey.get(k);\n const prevItem = prevItemsByKey.get(k);\n // Call create-returned cleanup first, then remove callback\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function' && el) {\n remove(prevItem, el);\n }\n elementsByKey.delete(k);\n prevItemsByKey.delete(k);\n prevIndexByKey.delete(k);\n cleanupByKey.delete(k);\n }\n }\n }\n }, isReorder);\n }\n\n // Subscription — $subscribe calls updateList immediately (initial render),\n // so no separate updateList() call is needed for reactive stores.\n let unsubscribe;\n if (typeof store.$subscribe === 'function') {\n unsubscribe = store.$subscribe(arrayKey, updateList);\n } else if (typeof store.subscribe === 'function') {\n // Generic subscribe (e.g. computed) — subscribe first, then initial render\n const subResult = store.subscribe(() => updateList());\n updateList();\n // Normalize both function-style and object-style (RxJS Subscription) returns\n unsubscribe = typeof subResult === 'function'\n ? subResult\n : () => { subResult?.unsubscribe?.(); };\n } else {\n // Non-reactive store — render once and return cleanup\n updateList();\n logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');\n return () => {\n for (const [k, el] of elementsByKey) {\n const prevItem = prevItemsByKey.get(k);\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function') {\n remove(prevItem, el);\n }\n }\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n seenKeys.clear();\n };\n }\n\n return () => {\n if (typeof unsubscribe === 'function') {\n unsubscribe();\n }\n // Invoke cleanup and remove callback for all remaining elements before clearing\n for (const [k, el] of elementsByKey) {\n const prevItem = prevItemsByKey.get(k);\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function') {\n remove(prevItem, el);\n }\n }\n // Clear DOM elements (replaceChildren is faster than loop)\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n seenKeys.clear();\n };\n}\n","/**\n * Lume-JS Debug Addon\n * \n * Developer-friendly logging and inspection of reactive state operations.\n * Critical for adoption - hard to debug = hard to adopt.\n * \n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin, debug } from \"lume-js/addons\";\n * \n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'myStore' })]);\n * \n * debug.enable(); // Enable logging\n * debug.filter('count'); // Only log 'count' key\n * debug.stats(); // Show statistics\n * \n * @module addons/debug\n */\n\n// Global debug state\nlet globalEnabled = true;\nlet globalFilter = null; // string, RegExp, or null\nconst stats = new Map(); // label -> { gets: Map, sets: Map, notifies: Map }\n\n/**\n * Check if a key matches the current filter\n * @param {string} key\n * @returns {boolean}\n */\nfunction matchesFilter(key) {\n if (globalFilter === null) return true;\n if (typeof globalFilter === 'string') {\n return key.includes(globalFilter);\n }\n if (globalFilter instanceof RegExp) {\n return globalFilter.test(key);\n }\n return true;\n}\n\n/**\n * Get or create stats entry for a label\n * @param {string} label\n * @returns {object}\n */\nfunction getStats(label) {\n if (!stats.has(label)) {\n stats.set(label, {\n gets: new Map(),\n sets: new Map(),\n notifies: new Map()\n });\n }\n return stats.get(label);\n}\n\n/**\n * Increment a stat counter\n * @param {string} label\n * @param {'gets'|'sets'|'notifies'} type\n * @param {string} key\n */\nfunction incrementStat(label, type, key) {\n const s = getStats(label);\n const map = s[type];\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nconst MAX_LOG_LEN = 100;\nconst TRUNCATED_LEN = MAX_LOG_LEN - 3;\n\n/**\n * Format value for logging (truncate long values)\n * @param {any} value\n * @returns {string}\n */\nfunction formatValue(value) {\n try {\n const json = JSON.stringify(value);\n if (json.length > MAX_LOG_LEN) {\n return json.slice(0, TRUNCATED_LEN) + '...';\n }\n return json;\n } catch {\n return String(value);\n }\n}\n\n/**\n * Create a debug plugin instance for a reactive state store.\n * \n * @param {object} [options] - Configuration options\n * @param {string} [options.label='store'] - Label for log messages\n * @param {boolean} [options.logGet=false] - Log property reads (can be noisy)\n * @param {boolean} [options.logSet=true] - Log property writes\n * @param {boolean} [options.logNotify=true] - Log subscriber notifications\n * @param {boolean} [options.trace=false] - Show stack trace for SET operations\n * @returns {object} Plugin object for state()\n * \n * @example\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n * \n * @example\n * // With stack traces for debugging where state changes originate\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter', trace: true })]);\n */\nexport function createDebugPlugin(options = {}) {\n const label = options.label ?? 'store';\n\n // IMPORTANT: Do NOT destructure options here!\n // Options may contain getters for dynamic runtime toggling (e.g., from UI).\n // Destructuring would copy values once at creation time, breaking reactivity.\n // Use getOpt() helper to read options dynamically in each hook.\n const getOpt = (name, defaultVal) => {\n const val = options[name];\n return val !== undefined ? val : defaultVal;\n };\n\n return {\n name: `debug:${label}`,\n\n onInit: () => {\n if (globalEnabled) {\n console.log(`%c[${label}]%c initialized`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n onGet: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return value;\n }\n\n incrementStat(label, 'gets', key);\n\n if (globalEnabled && getOpt('logGet', false) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c GET %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #4CAF50',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n\n return value;\n },\n\n onSet: (key, newValue, oldValue) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return newValue;\n }\n\n incrementStat(label, 'sets', key);\n\n if (globalEnabled && getOpt('logSet', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SET %c${key}%c: ${formatValue(oldValue)} → ${formatValue(newValue)}`,\n 'color: #888; font-weight: bold',\n 'color: #FF9800',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n\n // Show stack trace if enabled (helps find where state changes originate)\n if (getOpt('trace', false)) {\n console.trace(`%c[${label}] Stack trace for ${key}`, 'color: #888');\n }\n }\n\n return newValue;\n },\n\n onSubscribe: (key) => {\n if (globalEnabled && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SUBSCRIBE %c${key}`,\n 'color: #888; font-weight: bold',\n 'color: #9C27B0',\n 'color: #2196F3; font-weight: bold'\n );\n }\n },\n\n onNotify: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return;\n }\n\n incrementStat(label, 'notifies', key);\n\n if (globalEnabled && getOpt('logNotify', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c NOTIFY %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #E91E63',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n }\n };\n}\n\n/**\n * Global debug controls\n */\nexport const debug = {\n /**\n * Enable debug logging globally\n */\n enable() {\n globalEnabled = true;\n console.log('%c[lume-debug]%c Logging enabled', 'color: #888; font-weight: bold', 'color: #4CAF50');\n },\n\n /**\n * Disable debug logging globally\n */\n disable() {\n globalEnabled = false;\n console.log('%c[lume-debug]%c Logging disabled', 'color: #888; font-weight: bold', 'color: #F44336');\n },\n\n /**\n * Check if debug logging is currently enabled\n * @returns {boolean}\n */\n isEnabled() {\n return globalEnabled;\n },\n\n /**\n * Filter logs by key pattern\n * @param {string|RegExp|null} pattern - Pattern to match, or null to clear filter\n */\n filter(pattern) {\n globalFilter = pattern;\n if (pattern === null) {\n console.log('%c[lume-debug]%c Filter cleared', 'color: #888; font-weight: bold', 'color: inherit');\n } else {\n console.log(`%c[lume-debug]%c Filter set: ${pattern}`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n /**\n * Get current filter pattern\n * @returns {string|RegExp|null}\n */\n getFilter() {\n return globalFilter;\n },\n\n /**\n * Get statistics data (silent - no console output)\n * Use logStats() if you want to see stats in console.\n * @returns {object} Stats object for programmatic access\n */\n stats() {\n const result = {};\n\n for (const [label, data] of stats) {\n result[label] = {\n gets: Object.fromEntries(data.gets),\n sets: Object.fromEntries(data.sets),\n notifies: Object.fromEntries(data.notifies)\n };\n }\n\n return result;\n },\n\n /**\n * Log statistics summary to console (with formatting)\n * @returns {object} Stats object for programmatic access\n */\n logStats() {\n const result = this.stats();\n\n if (Object.keys(result).length === 0) {\n console.log('%c[lume-debug]%c No stats collected yet', 'color: #888; font-weight: bold', 'color: inherit');\n return result;\n }\n\n console.group('%c[lume-debug] Statistics', 'color: #888; font-weight: bold');\n\n for (const [label, data] of Object.entries(result)) {\n console.group(`%c${label}`, 'color: #2196F3; font-weight: bold');\n\n // Use console.table for better formatted output\n const tableData = [];\n const allKeys = new Set([\n ...Object.keys(data.gets),\n ...Object.keys(data.sets),\n ...Object.keys(data.notifies)\n ]);\n\n for (const key of allKeys) {\n tableData.push({\n key,\n gets: data.gets[key] || 0,\n sets: data.sets[key] || 0,\n notifies: data.notifies[key] || 0\n });\n }\n\n if (tableData.length > 0) {\n console.table(tableData);\n }\n\n console.groupEnd();\n }\n\n console.groupEnd();\n\n return result;\n },\n\n /**\n * Reset all collected statistics\n */\n resetStats() {\n stats.clear();\n console.log('%c[lume-debug]%c Stats reset', 'color: #888; font-weight: bold', 'color: inherit');\n }\n};\n","/**\n * Lume-JS withPlugins Addon\n *\n * Wraps a reactive state proxy with a plugin layer that intercepts\n * get/set/notify/subscribe operations via plugin hooks.\n *\n * Only stores that opt into debugging or custom behaviors need this.\n * Core state() is not aware of plugins.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin } from \"lume-js/addons\";\n *\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n */\n\n/**\n * Wrap a reactive state proxy with plugin hooks.\n *\n * Plugin hooks (all optional):\n * onInit() — called once at wrap time\n * onGet(key, value) → value|void — intercept/transform reads\n * onSet(key, newVal, oldVal) → val|void — intercept/transform writes\n * onNotify(key, value) — called before subscribers are notified\n * onSubscribe(key) — called when $subscribe is invoked\n *\n * @param {object} store - A reactive proxy from state()\n * @param {Array<object>} plugins - Array of plugin objects\n * @returns {Proxy} A new proxy wrapping the store with plugin behavior\n */\nimport { logError } from '../utils/log.js';\n\nexport function withPlugins(store, plugins = []) {\n if (!plugins.length) return store;\n\n // Call onInit hooks once at wrap time\n for (const p of plugins) {\n try {\n p.onInit?.();\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onInit:`, e);\n }\n }\n\n // Track pending notifications for onNotify hooks.\n // Instead of a separate microtask, we hook into the underlying state's\n // flush via $beforeFlush so onNotify and subscribers share one microtask.\n const pendingNotifications = new Map();\n\n function runNotifyHooks() {\n for (const [key, value] of pendingNotifications) {\n for (const p of plugins) {\n try {\n p.onNotify?.(key, value);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onNotify:`, e);\n }\n }\n }\n pendingNotifications.clear();\n }\n\n // Register once on the underlying state; capture unsubscribe for cleanup.\n let flushUnsub;\n if (typeof store.$beforeFlush === 'function') {\n flushUnsub = store.$beforeFlush(runNotifyHooks);\n }\n\n return new Proxy(store, {\n get(target, key) {\n // $dispose — remove the beforeFlush hook and clear pending state\n if (key === '$dispose') {\n return () => {\n if (flushUnsub) flushUnsub();\n pendingNotifications.clear();\n };\n }\n\n // Pass $-prefixed meta methods through without interception\n if (typeof key === 'string' && key.startsWith('$')) {\n const method = target[key];\n if (key === '$subscribe' && typeof method === 'function') {\n // Wrap $subscribe to call onSubscribe hooks\n return (subKey, fn) => {\n for (const p of plugins) {\n try {\n p.onSubscribe?.(subKey);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSubscribe:`, e);\n }\n }\n return method(subKey, fn);\n };\n }\n return method;\n }\n\n let value = target[key];\n\n // onGet chain\n for (const p of plugins) {\n try {\n const r = p.onGet?.(key, value);\n if (r !== undefined) value = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onGet:`, e);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n let newValue = value;\n\n // onSet chain\n for (const p of plugins) {\n try {\n const r = p.onSet?.(key, newValue, oldValue);\n if (r !== undefined) newValue = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSet:`, e);\n }\n }\n\n // Only queue onNotify if the value actually changed after plugin chain\n if (!Object.is(newValue, oldValue)) {\n pendingNotifications.set(key, newValue);\n }\n\n target[key] = newValue;\n return true;\n }\n });\n}\n","/**\n * Creates a cleanup group that can collect and dispose multiple\n * cleanup/unsubscribe functions at once.\n *\n * @returns {CleanupGroup}\n *\n * @example\n * ```js\n * import { createCleanupGroup } from 'lume-js/addons';\n *\n * const group = createCleanupGroup();\n * group.add(bindDom(root, store));\n * group.add(effect(() => { ... }));\n * group.add(store.$subscribe('key', fn));\n *\n * // Dispose everything at once\n * group.dispose();\n * ```\n */\nexport function createCleanupGroup() {\n const cleanups = [];\n\n return {\n /**\n * Add a cleanup function to the group.\n * @param {Function} fn - Cleanup/unsubscribe function\n */\n add(fn) {\n if (typeof fn === 'function') {\n cleanups.push(fn);\n }\n },\n\n /**\n * Run all collected cleanup functions and clear the group.\n */\n dispose() {\n while (cleanups.length) {\n const fn = cleanups.pop();\n try { fn(); } catch (e) { /* ignore cleanup errors */ }\n }\n },\n };\n}\n","/**\n * Reads initial state from a `<script type=\"application/json\">` element\n * embedded in the server-rendered HTML. Useful for SSR / hydration patterns.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @returns {object} Parsed JSON object, or empty object if not found / invalid\n *\n * @example\n * ```html\n * <script id=\"__LUME_DATA__\" type=\"application/json\">\n * {\"title\": \"Welcome\", \"count\": 42}\n * </script>\n * ```\n *\n * ```js\n * import { state } from 'lume-js';\n * import { hydrateState } from 'lume-js/addons';\n *\n * const store = state(hydrateState());\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__') {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n try {\n return JSON.parse(el.textContent);\n } catch {\n return {};\n }\n}\n","export { computed } from \"./computed.js\";\nexport { watch } from \"./watch.js\";\nexport { repeat, defaultFocusPreservation, defaultScrollPreservation } from \"./repeat.js\";\nexport { createDebugPlugin, debug } from \"./debug.js\";\nexport { withPlugins } from \"./withPlugins.js\";\nexport { createCleanupGroup } from \"./cleanupGroup.js\";\nexport { hydrateState } from \"./hydrateState.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n * Uses duck-typing: checks for the presence of $subscribe.\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' && typeof obj.$subscribe === 'function');\n}\n"],"names":["container"],"mappings":"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"}
|
package/dist/handlers.min.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={attr:"data-show",apply(e,t){e.hidden=!t}};function
|
|
1
|
+
const e={attr:"data-show",apply(e,t){e.hidden=!t}},t={attr:"data-classname",apply(e,t){e.className=t||""}};function a(e){return{attr:"data-"+e,apply(t,a){t.toggleAttribute(e,!!a)}}}function l(e){const t=e.startsWith("aria-")?e:"aria-"+e;return{attr:"data-"+t,apply(e,a){e.setAttribute(t,a?"true":"false")}}}function r(...e){return e.map(e=>({attr:"data-class-"+e,apply(t,a){t.classList.toggle(e,!!a)}}))}function o(e){return{attr:"data-"+e,apply(t,a){null==a?t.removeAttribute(e):t.setAttribute(e,a+"")}}}const n=["readonly","open","novalidate","formnovalidate","multiple","autofocus","autoplay","controls","loop","muted","defer","async","reversed","selected","inert","allowfullscreen"],s=["href","src","alt","title","placeholder","action","method","target","rel","type","name","role","lang","tabindex","pattern","min","max","step","minlength","maxlength","width","height","for","form","accept","autocomplete","loading","decoding","inputmode","enterkeyhint","draggable","contenteditable","spellcheck","translate","dir","id","poster","preload","download","media","sizes","srcset","colspan","rowspan","scope","headers","wrap","sandbox"],i=["pressed","selected","disabled","checked","invalid","required","busy","modal","multiselectable","multiline","readonly","atomic"],d=["current","live","relevant","haspopup","sort","autocomplete","orientation","label","describedby","labelledby","controls","owns","activedescendant","errormessage","details","flowto","valuenow","valuemin","valuemax","valuetext","colcount","colindex","colspan","rowcount","rowindex","rowspan","level","setsize","posinset","placeholder","roledescription","keyshortcuts","braillelabel","brailleroledescription"];function c(){return[e,...n.map(e=>a(e)),...s.map(e=>o(e)),...i.map(e=>l(e)),...d.map(e=>o("aria-"+e))]}const p=[a("readonly")],u=[l("pressed"),l("selected"),l("disabled")];export{u as a11yHandlers,l as ariaAttr,a as boolAttr,t as className,r as classToggle,p as formHandlers,c as htmlAttrs,e as show,o as stringAttr};
|
package/dist/handlers.mjs
CHANGED
|
@@ -4,6 +4,12 @@ const show = {
|
|
|
4
4
|
el.hidden = !Boolean(val);
|
|
5
5
|
}
|
|
6
6
|
};
|
|
7
|
+
const className = {
|
|
8
|
+
attr: "data-classname",
|
|
9
|
+
apply(el, val) {
|
|
10
|
+
el.className = val || "";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
7
13
|
function boolAttr(name) {
|
|
8
14
|
return {
|
|
9
15
|
attr: `data-${name}`,
|
|
@@ -38,14 +44,6 @@ function stringAttr(name) {
|
|
|
38
44
|
}
|
|
39
45
|
};
|
|
40
46
|
}
|
|
41
|
-
const formHandlers = [
|
|
42
|
-
boolAttr("readonly")
|
|
43
|
-
];
|
|
44
|
-
const a11yHandlers = [
|
|
45
|
-
ariaAttr("pressed"),
|
|
46
|
-
ariaAttr("selected"),
|
|
47
|
-
ariaAttr("disabled")
|
|
48
|
-
];
|
|
49
47
|
const BOOL_ATTRS = [
|
|
50
48
|
"readonly",
|
|
51
49
|
"open",
|
|
@@ -173,10 +171,19 @@ function htmlAttrs() {
|
|
|
173
171
|
...ARIA_STRING_ATTRS.map((name) => stringAttr(`aria-${name}`))
|
|
174
172
|
];
|
|
175
173
|
}
|
|
174
|
+
const formHandlers = [
|
|
175
|
+
boolAttr("readonly")
|
|
176
|
+
];
|
|
177
|
+
const a11yHandlers = [
|
|
178
|
+
ariaAttr("pressed"),
|
|
179
|
+
ariaAttr("selected"),
|
|
180
|
+
ariaAttr("disabled")
|
|
181
|
+
];
|
|
176
182
|
export {
|
|
177
183
|
a11yHandlers,
|
|
178
184
|
ariaAttr,
|
|
179
185
|
boolAttr,
|
|
186
|
+
className,
|
|
180
187
|
classToggle,
|
|
181
188
|
formHandlers,
|
|
182
189
|
htmlAttrs,
|