Lighweight Embeddable Web UI Library
New context implementation provides a slightly different API that makes it possible to remove dirty context checking during updates and dirty checking, doesn't require to provide value type when retrieving context values, and doesn't use global namespace for context keys.
BEFORE:
const C = component((c) => {
const getValue = useSelect((c) => context<{ value: number }>().value);
return () => getValue();
});
render(
Context({ value: 123 },
C(),
),
container,
);
AFTER:
const Value = contextValue<number>();
const C = component((c) => {
const getValue = useSelect((c) => Value.get());
return () => getValue();
});
render(
Value.set(123,
C(),
),
container,
);
Children reconciliation algorithm for fragments were changed to dynamically add/remove at the end of the fragment instead of reinstantiating entire fragment when fragment length is changed.
shouldUpdate
All shouldUpdate
functions are replaced with their inverse function areEqual
.
APIs affected by this change:
component(_, shouldUpdate)
statelessComponent(_, shouldUpdate)
useSelect(_, shouldUpdate)
useEffect(_, shouldUpdate)
useLayoutEffect(_, shouldUpdate)
useMutationEffect(_, shouldUpdate)
memo(_, shouldUpdate)
selector(_, shouldUpdate)
New package ivi-jest
adds a collection of useful tools for testing with jest library. All ivi tests were completely
rewritten using a new ivi-jest
library.
ivi-scheduler
and ivi-test-scheduler
packages were removed. Old unit tests were using package aliasing to mock scheduler behavior and now it is not needed anymore.
Removed optional bubbling, all events are now always bubble.
TrackByKey
hasn't been executed correctly when nodes doesn't change their positions. It is hard to test because it is still works correctly even when this fast path doesn't execute.VALUE()
and CONTENT()
attribute directives from accepting numbers, they should work only with string values.shouldUpdate
functions hasn't been executed correctly in component hooks and caused more updates than it was necessary.Synthetic event internals were heavily redesigned to reduce overall complexity and improve API flexibility for customsynthetic events.
Custom synthetic events can now inject their own behavior into event flow of native events. It should significantly improve performance when there are many custom synthetic events as it won't be necessary to traverse virtual dom tocollect dispatch targets for each custom synthetic event.
API for creating custom synthetic events is still an unstable API and it is most likely that there will be changes in the future, but it is an extremely useful API that solves alot of problems with UI applications.
SyntheticNativeEvent
objectBEFORE
onClick((ev) => {
console.log(ev.native.target); // target
});
AFTER:
onClick((ev) => {
console.log(ev.target);
});
EventFlags
is removedTo stop event propagation event handler should return true
value.
BEFORE:
onClick((ev) => {
return EventFlags.PreventDefault | EventFlags.StopPropagation;
});
AFTER:
onClick((ev) => {
ev.preventDefault();
return true;
});
currentTarget
is now accessible as a second argumentBEFORE
onClick((ev) => {
console.log(ev.node); // currentTarget
});
AFTER:
onClick((ev, currentTarget) => {
console.log(currentTarget);
});
SyntheticEvent
interface is removedSyntheticEvent
interface had two properties: node
and timestamp
. node
were used to assign current target, it is replaced with an additional argument in all event handler functions. timestamp
is a leftover from an old synthetic events implementation that tried to fix cross-browser quirks. For many custom synthetic events this property doesn't have any value and it custom event implementor should decide when timestamp
value is necessary.
beforeNativeEvent()
and afterNativeEvent()
are removedIt is replaced with an addNativeEventMiddleware()
.
addNativeEventMiddleware(MOUSE_DOWN, (event, next) => {
// beforeNativeEvent...
next(event);
// afterNativeEvent...
});
Portals were completely redesigned and moved to ivi-portal
package. Portals now correctly propagate context through portal entries.
import { _, render, component, invalidate, Events, onClick, } from "ivi";
import { div, button } from "ivi-html";
import { portal } from "ivi-portal";
const MODAL = portal();
const App = component((c) => {
let showModal = false;
const showEvent = onClick(() => { showModal = true; invalidate(c); });
return () => (
[
showModal ? MODAL.entry(div("modal", _, "This is being rendered inside the #modal-root div.")) : null,
Events(showEvent,
button(_, _, "Show modal"),
),
]
);
});
render(App(), document.getElementById("app"));
render(MODAL.root, document.getElementById("modal-root"));
Unhandled exceptions raised inside of a catchError()
block are now considered as userspace bugs and will change application state to "error". When application state is "error", all entry points wrapped in catchError()
will be blocked to prevent potential security issues because it is impossible to infer which part of an application state caused a bug.
All ivi entry points like render()
, synthetic event handlers, etc are wrapped with a catchError()
block.
catchError()
const entryFn = catchError((arg1, arg2) => {
// ...
});
entryFn(arg1, arg2);
Context()
node triggers replace operation.useUnmount()
hook hasn't been receiving an undocumented true
value as a first argument. It is an unstable feature that can be used for micro optimizations in custom hooks.containsRelatedTarget()
with a generic function containsDOMElement()
.hasDOMElementChild()
function.VisitNodesDirective
to get a better control over visitNodes()
algorithm.onTransitionRun()
and onTransitionStart()
events.Added support for events:
onBeforeInput()
onTransitionCancel()
onTransitionEnd()
__IVI_DEBUG__
and __IVI_TARGET__
were replaced with process.env.NODE_ENV !== "production"
and process.env.IVI_TARGET
to support parcel bundler Issue #10.
renderToString()
DEBUG
and TARGET
were renamed to __IVI_DEBUG__
and __IVI_TARGET__
to prevent name conflicts with variables that can be used in different packages.
useSelect()
Context argument is removed from selectors, context()
function should be used to access current context.
function useSelect<T>(
c: StateNode,
selector: (props?: undefined, prev?: T | undefined) => T,
): () => T;
function useSelect<T, P>(
c: StateNode,
selector: (props: P, prev?: T | undefined) => T,
shouldUpdate?: undefined extends P ? undefined : (prev: P, next: P) => boolean,
): undefined extends P ? () => T : (props: P) => T;
Attribute directives were changed to support server-side rendering:
interface AttributeDirective<P> {
v: P;
u?: (element: Element, key: string, prev: P | undefined, next: P | undefined) => void;
s?: (key: string, next: P) => void;
}
s()
method can be used to alter renderToString()
behavior.
VALUE()
directiveVALUE()
directive now works only with HTMLInputElement
elements. New CONTENT()
directive should be used to assign value for HTMLTextArea
elements.
VALUE()
directive emits value="${v}"
attribute when rendered to string.
CONTENT()
directive emits children string <textarea>${v}</textarea>
when rendered to string.
flags
to render()
function (6be1314)isHidden()
(23ab024)EVENT()
(437e4ce)AUTOFOCUS()
(18460e7)EVENT()
(2546f8e)t()
from html package to ivi (d53d8e9)ivi-events
=> events
(368c387)newPropsReceived()
to propsChanged()
(6434f5a)_
as an alias to undefined
(35834f4)beforeUpdate
/ afterUpdate
repeatable tasks (f599405)after
(d3c4f72)mapIterable()
to support iterable objects (c09c5cb)PROPERTY()
(111c309)updated()
for all parents (5c75401)mapIterable()
(e3c88a5)a()
and s()
with factory args (4f00a52)getDOMInstanceFromVNode()
and getComponentInstanceFromVNode()
were renamed to getDOMNode()
and
getComponent()
VNode
methods value()
and unsafeHTML()
were removed.Before:
input("", { type: "checked" }).value(true);
use("", { "xlink:href": "sprite.svg#a" });
div().unsafeHTML("abc");
After:
input("", { type: "checked", checked: CHECKED(true) });
use("", { "xlink:href": XLINK_ATTR("sprite.svg#a") });
div("", { unsafeHTML: UNSAFE_HTML("abc") });
currentFrameAfter()
and nextFrameAfter()
functions were removed.beforeUpdate()
and afterUpdate()
task lists.newPropsReceived()
renamed to propsChanged()
.a()
and s()
were replaced with optional arguments for all element factory functions.Before:
div("className").a({ id: "ID" }).s({ color: "red" });
After:
div("className", { id: "ID" }, { color: "red" });
updated()
lifecycle is now triggered only for components that were updated. Parent components won't be
receiving any information that their children were updated.Initially it was implemented to solve use cases with jumping scroll positions when children layout is modified. But there is a better way, scheduler allows to register repeatable tasks that will be invoked before any rendering to read from the DOM, and after all rendering is done, this hooks solve this use case perfectly. And since I don't know about any use cases that would require such behavior, it would be better to reduce API surface. All major frameworks doesn't support such behavior.
ivi-events
package were removed, event handler factories should be imported from ivi
package.preventDefault()
and stopPropagation()
were removed.Before:
onClick((ev) => {
ev.preventDefault();
ev.stopPropagation();
});
After:
onClick(() => EventFlags.PreventDefault | EventFlags.StopPropagation);