v4.0.2
What's Changed
- chore: remove old publish workflow and add new automated publish work… by @nattbl49 in #4
- Fix website deploy workflow failures on Pages setup and clean-checkout builds by @nattbl49 with @Copilot in #5
- refactor: update import paths to use package structure and clean up u… by @nattbl49 in #6
- chore: update version to 4.0.1 and add new dependencies in package.js… by @nattbl49 in #7
- chore: update package.json to include @elitjs/server in dependencies … by @nattbl49 in #8
- feat: add @elitjs/fs dependency and update dev tools configuration by @nattbl49 in #9
- ๊update: version 4.0.2 by @n-devs in #10
- Feature/v4.0.2 by @n-devs in #12
- update: v4.0.2 by @n-devs in #13
- fix(ci): allow same-version npm version in publish workflow by @n-devs in #14
New Contributors
- @nattbl49 made their first contribution in #4
- @nattbl49 with @Copilot made their first contribution in #5
Full Changelog: v4.0.0...v4.0.2
[4.0.2] - 2026-07-23
Fixed
reactive()no longer drops input focus on each keystroke - when an<input>,<textarea>, or<select>lived anywhere inside areactive()block, every state update destroyed and recreated the element, so users had to re-click the input before each character ("พิมพ์ได้ทีละครั้ง"). The update path now reconciles children positionally: nodes whose text content or tagName matches the previous render are patched in place (props + recursive children), and only genuinely different nodes are replaced. Comment placeholders left by null/conditional reactive results are skipped so they keep working as anchors.- Applies to both
reactive()andreactiveAs()update paths. - Verified by simulating four consecutive
inputevents on an input nested insidereactive(): focus and the underlying<input>DOM node are preserved across all keystrokes, while sibling reactive content (a mirror span) stays in sync.
- Applies to both
- Stale props on reused elements -
updateElementPropsnow diffs the new props against the previous render's props (tracked via__elitPrevPropson the element) and removes any prop that is no longer present. Without this, properties and event handlers from earlier renders accumulated on reused DOM nodes: a removedonclickwould keep firing (memory leak + wrong behavior), a removedclasswould linger, and a removedvalue/checkedon a form control would keep the old input state. Initial-renderapplyProps(@elitjs/dom) also seeds__elitPrevPropsso the first reactive update can diff against it. - Form-control props during reactive updates -
updateElementPropsnow assignsvalueon<input>/<textarea>/<select>andcheckedon<input>via the DOM property (with an equality check) instead ofsetAttribute. The previoussetAttribute('value', …)path was semantically wrong for live form values and could cause cursor jumps when the input was the root of areactive()block. - State-as-prop regression -
updateElementPropsnow skipsStatevalues (they already have their own subscription from the initialapplyPropspass) instead of coercing them to"[object Object]"viaString(), which broke form controls that receivedvalue: someState. - Snapshot-based prev-props storage - the previous-props cache used to diff reused DOM nodes is now stored in a shared
WeakMap<HTMLElement | SVGElement, Props>exported from@elitjs/dom(instead of an__elitPrevPropsexpando property on the element). Removes DOM pollution andas anycasts; entries are GC'd with the element. The accompanyingsnapshotPropshelper shallow-clones nested object/array values (notablystyle, arrays passed toclass/className, anddangerouslySetInnerHTML), so diffing detects in-place mutation of reused prop objects — e.g. a caller doingdelete sharedStyle.fontSizebetween renders now correctly clearsfont-sizeon the element.
Security
- XSS audit of the reactive update path - confirmed that all assignments in the new reconcile/patch logic are XSS-safe by construction: text children use
textContentandcreateTextNode(not parsed as HTML), element props usesetAttributeand direct property assignment (also not HTML-parsed). The onlyinnerHTMLwrite is for the explicitdangerouslySetInnerHTMLprop (user opt-in, same contract as React);updateElementPropsnow handles that key for consistency with initial render. Verified with a<img src=x onerror=…>payload that was rendered as visible text without any script execution.
Changed
- Version metadata refresh - release-facing version references across
package.json,package-lock.json, all@elitjs/*subpackages,create-elit,create-elit-skills, and the docs hero on the website now trackv4.0.2. Rebuilt@elitjs/domand@elitjs/statedist so the baked-inELIT_VERSIONconstant matches.