Skip to content

Releases: tonioloewald/tosijs

v1.6.0

Choose a tag to compare

@tonioloewald tonioloewald released this 16 Apr 16:01

What's New

Virtual Grid Layouts with itemsPerRow (v1.6.0)

The list binding now supports itemsPerRow in virtual options, enabling flat CSS grid layouts where every cell is a direct child of the scroll container. This means position: sticky works natively for pinned columns — no transform hacks or scroll event jitter.

  • Template builder receives a columnIndex argument (0..N-1)
  • Container auto-gets tosi-virtual-grid class, --tosi-columns CSS variable, and default grid layout
  • Static children (e.g. pinned header rows) are preserved across list updates
  • Includes live example: 2000-row, 11-column virtualized grid with sticky columns

ARIA Attributes for Virtual Lists (v1.5.9)

Virtual lists now announce their structure to screen readers:

  • Lists: role="list" on container, role="listitem" + aria-rowindex on items
  • Grids: role="grid" on container, role="gridcell" + aria-rowindex + aria-colindex on cells
  • Padding elements get role="presentation" and aria-hidden="true"
  • Non-virtual lists are unaffected

List Binding Element Reuse on Array Replacement (v1.5.8)

When a list binding with idPath receives a replaced array (e.g. from polling an endpoint), elements are now reused by id instead of being destroyed and recreated. Previously, replacing the whole array with new objects (same ids, new references) caused every DOM element to be torn down and rebuilt.

.take() Reactive Binding Transforms (v1.5.7)

Inline binding transforms via .take() — single and multi-path.

Other Changes Since v1.4.0

  • TOSI_ACCESSOR symbol for guaranteed collision-free proxy access
  • Accessor delegation — all proxy properties delegate through makeTosiAccessor
  • Proxy pollution fix — shallow unwrap on set, defensive unwrap on get
  • Debug/safe buildstosijs/debug and tosijs/safe subpath exports via tjs-lang
  • bindParts() for applying ElementProps to existing DOM via data attributes
  • share() for cross-tab state sync via BroadcastChannel + IndexedDB
  • sync() for real-time networking via pluggable SyncTransport
  • tosiUnique() for per-instance reactive state with automatic cleanup
  • BoxedScalar delegates to underlying primitive methods
  • SVG list binding support
  • Deprecated: bindText, bindEnabled, bindDisabled, bindList — use property bindings instead
  • Refactored Color.fromCss hex parsing
  • Documented 2.0 refactoring candidates in TODO.md

Bundle Size

19.2 kB gzipped (up from 18.6 kB at v1.5.7 — +600 bytes for virtual grids + ARIA)

Test Coverage

547 tests, 1330 expect() calls across 24 files

🤖 Generated with Claude Code

v1.4.0

Choose a tag to compare

@tonioloewald tonioloewald released this 11 Feb 07:51

New Features

List operations on proxied arrayslistFind, listUpdate, listRemove

Three new methods on proxied arrays that use the same (item) => item.field selector pattern as listBinding:

const app = tosi({ items: [{ id: 'a', name: 'Alice' }, { id: 'b', name: 'Bob' }] })

// Find by field
const item = app.items.listFind((i) => i.id, 'a')

// Find by DOM element (reverse lookup from click handler)
const item = app.items.listFind(clickedElement)

// Update in place (preserves object identity, surgical observer fires)
app.items.listUpdate((i) => i.id, { id: 'a', name: 'Alicia' })

// Remove
app.items.listRemove((i) => i.id, 'b')

listUpdate mutates existing items property-by-property through the proxy, preserving WeakMap references and DOM element reuse — no teardown/recreation.

.touch() on boxed proxies

Any proxied value now has a .touch() method for forcing observer updates, useful when you've mutated data behind the proxy's back:

app.items[0].touch()    // re-fires observers for this item
app.items.touch()       // re-fires observers for the whole array

Fixes

touch() now synthesizes id-path touches — When touching a list item by index (e.g. items[0]), observers using id-path bindings (e.g. items[id=abc].name) are now correctly notified. Previously, index-based touches wouldn't reach id-path-bound DOM elements.

Performance

Memoized elementPropBinding — Avoids per-use closure allocation in the element factory.

Documentation

  • Comprehensive Building Apps guide rewritten: paths as core concept, proxy-vs-raw values, binding shorthand, conditional UI, async safety, lazy mount pattern, light DOM as a feature
  • listFind/listUpdate/listRemove documented in README, Building Apps guide, and inline
  • .touch() method documented with id-path synthesis behavior
  • Warning about ] characters in id-path values (breaks path parser)

Test Coverage

  • Form validation: 0% → 100% (32 tests)
  • Fine-grained DOM integration tests for list operations (8 tests)
  • Touch-after-mutation tests with id-path synthesis (4 tests)

tosijs 1.0.10

Choose a tag to compare

@tonioloewald tonioloewald released this 04 Dec 12:25

BoxedProxy wraps null and undefined

This may cause breakage!

BoxedProxy (e.g. anything you get from tosi() no longer returns undefined if you access a missing path, or bare null values. These are instead passed back as boxed proxies that have a xinValue of undefined or null but also have a useful xinPath.

This means you can find to as-yet unpopulated values and should be able to set values similarly. (This may cause breakage if you were testing for missing values, which tosijs-ui's localize does).

Despite the possibility of breakage, I have found over the past few years than the existing behavior (where paths come back as undefined and in particular you cannot replace a null value in a BoxedProxy is worse). This new solution leans heavily into using .xinValue as the actual value for all purposes, essentially making life a little harder of Javascript developers and easier for Typescript developers.

BoxedProxy.tosiListBinding()

This new syntax sugar function lets you bind lists much more conveniently and intuitively. Basically you can bind a simple list like this: `

select( 
  ...my.options.tosiListBinding(
    ( {option}, item ) => option(
      item.text, 
      {idPath: 'value', value: item.value)
    )
  )
)

I particularly like this new syntax sugar because it exactly mirrors what you'd do if you were simply mapping the array to elements in the list, except you get a dynamically bound list with virtualization support etc., and yet all it's doing under the hood is building out the stuff you'd need to insert to do the binding manually.

The template-building function is used to create the list template, and it will be passed the elements proxy and a dummy item. TypeScript knows the type of item autocompletes and generates binding paths (e.g. ^.caption) for you.

Component.content(elements: ElementsProxy)

You might notice that the template creator function is passed elements and thus can eliminate a destructuring assignment somewhere else in the code (and likely an import line).

This is also true of the Component.content property when you set it up as a function. (Always use a function if you can—it also lets you set up event handlers and do so many nice things).

List Utility Functions

getListItem() is joined by getListInstance(), getListBinding(), and deleteListItem().

link to the binding-arrays documentation

v0.5.0 SVG, MathML, and varDefine

Choose a tag to compare

@tonioloewald tonioloewald released this 02 Jul 17:49

xinjs 0.5.0 adds support for SVG, MathML, and syntax-sugar for css-variables with fallback values

After working with SVGs a lot to get the xinjs-timezone-picker working, I decided to add full support for SVG and MathML to xinjs

  • svgElements is a proxy, like elements but for SVG name-spaced elements
  • mathML is the same thing for MathML name-spaced elements.
  • varDefault is a proxy like vars but instead of producing a reference string, e.g. "var(--foo-bar)" it produces a function, so varDefault.fooBar('30px') produces "var(--foo-bar, 30px)"

I'd also mention that the polygons module from yesterday is also out as xinjs-polygons, here's the polygons demo—btw 583b—and yeah, if it looks a lot like the <timezone-picker> demo, that's because I'm working on a nice template for all these libraries.

Aside—I also fixed/improved the code which generates the tagName for custom-elements if the class code has been mangled to the point of anonymity (thanks uglify) and the coder hasn't explicitly specified a tagName. This code got sufficiently smaller than xinjs is actually smaller now than before this last set of features was added.