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
## 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);