diff --git a/.github/workflows/test-headless.yml b/.github/workflows/test-headless.yml index 0ada8bcd..0a77dfa1 100644 --- a/.github/workflows/test-headless.yml +++ b/.github/workflows/test-headless.yml @@ -8,6 +8,10 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] container: image: mcr.microsoft.com/playwright:v1.59.1-noble @@ -36,4 +40,4 @@ jobs: run: bunx vitest run env: HOME: /root - VITEST_BROWSERS: chromium,firefox,webkit + VITEST_BROWSERS: ${{ matrix.browser }} diff --git a/.gitignore b/.gitignore index 048fd577..2e2c2be6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ builds/**/meta test-results/ .claude/worktrees/ __screenshots__ +.dts-tmp/ +.nx/ +.vitest-attachments/ diff --git a/tko.io/public/examples/honeycomb.html b/tko.io/public/examples/honeycomb.html index 3b22a442..875575f4 100644 --- a/tko.io/public/examples/honeycomb.html +++ b/tko.io/public/examples/honeycomb.html @@ -557,34 +557,54 @@

Honeycomb

lastKey: ko.observable(null), activeSource: null, activeNeighbors: [], + _pendingHover: null, + _hoverRaf: 0, + _clickRaf: 0, hoverCell(cell) { if (!cell.interactive || cell.key === viewModel.lastKey()) return true - if (viewModel.activeSource) { - viewModel.activeSource.source(false) - } - viewModel.activeNeighbors.forEach(neighbor => neighbor.neighbor(false)) + viewModel._pendingHover = cell + if (!viewModel._hoverRaf) { + viewModel._hoverRaf = requestAnimationFrame(() => { + viewModel._hoverRaf = 0 + const target = viewModel._pendingHover + if (!target) return + + if (viewModel.activeSource) { + viewModel.activeSource.source(false) + } + viewModel.activeNeighbors.forEach(neighbor => neighbor.neighbor(false)) - viewModel.lastKey(cell.key) - viewModel.activeSource = cell - viewModel.activeNeighbors = cell.neighbors + viewModel.lastKey(target.key) + viewModel.activeSource = target + viewModel.activeNeighbors = target.neighbors - cell.source(true) - cell.neighbors.forEach(neighbor => neighbor.neighbor(true)) + target.source(true) + target.neighbors.forEach(neighbor => neighbor.neighbor(true)) - viewModel.vdomCount(viewModel.vdomCount() + viewModel.totalVisible()) - viewModel.obsCount(viewModel.obsCount() + cell.neighbors.length) + viewModel.vdomCount(viewModel.vdomCount() + viewModel.totalVisible()) + viewModel.obsCount(viewModel.obsCount() + target.neighbors.length) + }) + } return true }, clickCell(cell) { if (!cell.interactive) return true + if (viewModel._clickRaf) { + cancelAnimationFrame(viewModel._clickRaf) + viewModel._clickRaf = 0 + } + const queue = buildWaterfallQueue(cell, cells) let index = 0 const batchSize = 24 function advanceBurst() { - if (index >= queue.length) return + if (index >= queue.length) { + viewModel._clickRaf = 0 + return + } let touched = 0 while (index < queue.length && touched < batchSize) { @@ -604,10 +624,10 @@

Honeycomb

viewModel.vdomCount(viewModel.vdomCount() + touched * viewModel.totalVisible()) viewModel.obsCount(viewModel.obsCount() + touched) - requestAnimationFrame(advanceBurst) + viewModel._clickRaf = requestAnimationFrame(advanceBurst) } - requestAnimationFrame(advanceBurst) + viewModel._clickRaf = requestAnimationFrame(advanceBurst) return true } } diff --git a/tko.io/src/content/docs/index.mdx b/tko.io/src/content/docs/index.mdx index fc9b3498..70d85db8 100644 --- a/tko.io/src/content/docs/index.mdx +++ b/tko.io/src/content/docs/index.mdx @@ -6,8 +6,8 @@ description: Modern Knockout — reactive data binding and UI templating with ze import { Tabs, TabItem } from '@astrojs/starlight/components';
-

TKO v4.0.1

-

Modern Knockout, clarified

+

TKO v4.0.1

+

Modern Knockout, clarified

Reactive data binding and UI templating with zero runtime dependencies. Start with the package you need and move from overview to working bindings.

Start with bindings @@ -17,50 +17,53 @@ import { Tabs, TabItem } from '@astrojs/starlight/components'; ## Quick start -### Script tag (global) +### Browser -```html - - -``` - -### ES module (browser) - -```html - -``` + + + ```html + + ``` + + + ```html + + + ``` + + ### Package manager ```sh - npm install @tko/build.knockout + npm install @tko/build.reference ``` ```sh - bun add @tko/build.knockout + bun add @tko/build.reference ``` ```sh - pnpm add @tko/build.knockout + pnpm add @tko/build.reference ``` ```sh - yarn add @tko/build.knockout + yarn add @tko/build.reference ``` -For the modern TSX / `ko-*` path, use `@tko/build.reference` instead. +For Knockout 3.x compatibility, use `@tko/build.knockout` instead. ## First binding example @@ -73,8 +76,9 @@ For the modern TSX / `ko-*` path, use `@tko/build.reference` instead.

Hello, .

- + ``` @@ -82,16 +86,16 @@ For the modern TSX / `ko-*` path, use `@tko/build.reference` instead. ## Choose a build
- - Recommended for migrations -

@tko/build.knockout

-

Compatibility-focused. Closest match to a traditional Knockout application. Includes `data-bind` and `foreach`.

-
- Recommended for new projects + Recommended

@tko/build.reference

Modern path with TSX, `ko-*` attributes, native provider, and strict equality in expressions.

+ + Knockout 3.x migrations +

@tko/build.knockout

+

Compatibility-focused. Drop-in replacement for existing Knockout applications.

+
## What stays familiar @@ -118,6 +122,11 @@ For the modern TSX / `ko-*` path, use `@tko/build.reference` instead.

Components

Reusable UI, loading strategies, and component architecture.

+ + See it in action +

Examples

+

Interactive demos showing observable updates, dependency graphs, and reactive state.

+
## Community diff --git a/tko.io/src/styles/tko.css b/tko.io/src/styles/tko.css index 10faa4c0..46c612d7 100644 --- a/tko.io/src/styles/tko.css +++ b/tko.io/src/styles/tko.css @@ -128,8 +128,7 @@ header { .sidebar, .right-sidebar-panel, -.content-panel, -.sidebar-pane { +.content-panel { background: transparent; } @@ -258,7 +257,15 @@ header { color: var(--sl-color-text-accent); } -.landing-hero h2 { +.landing-hero .landing-version { + margin: 0 0 0.3rem; + font-family: var(--tko-font-display); + font-size: clamp(2rem, 5vw, 3.2rem); + font-weight: 700; + color: var(--sl-color-white); +} + +.landing-hero h2:not(.landing-version) { margin: 0; max-width: 11ch; font-size: clamp(1.55rem, 3.7vw, 2.55rem);