Skip to content

v4.0.2

Choose a tag to compare

@n-devs n-devs released this 24 Jul 02:20
· 13 commits to main since this release
cd04442

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

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 a reactive() 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() and reactiveAs() update paths.
    • Verified by simulating four consecutive input events on an input nested inside reactive(): focus and the underlying <input> DOM node are preserved across all keystrokes, while sibling reactive content (a mirror span) stays in sync.
  • Stale props on reused elements - updateElementProps now diffs the new props against the previous render's props (tracked via __elitPrevProps on 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 removed onclick would keep firing (memory leak + wrong behavior), a removed class would linger, and a removed value/checked on a form control would keep the old input state. Initial-render applyProps (@elitjs/dom) also seeds __elitPrevProps so the first reactive update can diff against it.
  • Form-control props during reactive updates - updateElementProps now assigns value on <input>/<textarea>/<select> and checked on <input> via the DOM property (with an equality check) instead of setAttribute. The previous setAttribute('value', …) path was semantically wrong for live form values and could cause cursor jumps when the input was the root of a reactive() block.
  • State-as-prop regression - updateElementProps now skips State values (they already have their own subscription from the initial applyProps pass) instead of coercing them to "[object Object]" via String(), which broke form controls that received value: 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 __elitPrevProps expando property on the element). Removes DOM pollution and as any casts; entries are GC'd with the element. The accompanying snapshotProps helper shallow-clones nested object/array values (notably style, arrays passed to class/className, and dangerouslySetInnerHTML), so diffing detects in-place mutation of reused prop objects — e.g. a caller doing delete sharedStyle.fontSize between renders now correctly clears font-size on 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 textContent and createTextNode (not parsed as HTML), element props use setAttribute and direct property assignment (also not HTML-parsed). The only innerHTML write is for the explicit dangerouslySetInnerHTML prop (user opt-in, same contract as React); updateElementProps now 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 track v4.0.2. Rebuilt @elitjs/dom and @elitjs/state dist so the baked-in ELIT_VERSION constant matches.