diff --git a/.github/actions/setup-monorepo/action.yml b/.github/actions/setup-monorepo/action.yml index 16dfb1f..0264b95 100644 --- a/.github/actions/setup-monorepo/action.yml +++ b/.github/actions/setup-monorepo/action.yml @@ -7,7 +7,7 @@ runs: with: node-version: "24.13.0" - - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 with: version: 10 diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index df073c9..6949d97 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -61,7 +61,7 @@ jobs: key: size-baseline-${{ runner.os }}-${{ github.sha }} - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v3 + uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.github/workflows/deploy-wrenfield.yml b/.github/workflows/deploy-wrenfield.yml new file mode 100644 index 0000000..514a100 --- /dev/null +++ b/.github/workflows/deploy-wrenfield.yml @@ -0,0 +1,42 @@ +# Manual redeploy of wrenfield.excom.dev from `main`. Pushes to `main` deploy +# through the Release workflow (publish.yml), after the version bump, so the +# page always pins a Kit version that is on npm. Run this from `main` only: +# another ref can carry a Kit version that was never published. +# +# CLOUDFLARE_API_TOKEN needs Workers Scripts:Edit (account), plus Workers +# Routes:Edit and DNS:Edit on the excom.dev zone for the custom domain +# declared in packages/wrenfield/wrangler.jsonc. +name: Deploy Wrenfield + +on: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + # No dependencies to install: staging copies files and reads the Kit + # version from packages/nucleus-kit/package.json. + - uses: actions/setup-node@v6 + with: + node-version: "24.13.0" + - run: node packages/wrenfield/scripts/stage.mjs + + # From the repo root like the docs deploy: the action installs wrangler + # with npm where it runs, which cannot read the package's `workspace:` + # dependencies. Config paths resolve relative to the config file. + - name: Deploy to Cloudflare Workers + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: deploy --config packages/wrenfield/wrangler.jsonc diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 51dace3..dca2c20 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -69,7 +69,7 @@ jobs: - uses: actions/setup-node@v6 with: node-version: "24.13.0" - - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 with: version: 10 @@ -82,14 +82,14 @@ jobs: run: pnpm dlx @cyclonedx/cdxgen -t pnpm --no-install-deps --no-babel --spec-version 1.6 -o bom.json - name: Upload SBOM as Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: sbom-cyclonedx.json path: bom.json - name: Grype Dependency Scanner (Report) id: scan-package - uses: anchore/scan-action@40a61b52209e9d50e87917c5b901783d546b12d0 + uses: anchore/scan-action@27805bf3b4e84b4a5c980df22ed233c00390a439 # v7.4.2 with: sbom: bom.json fail-build: false @@ -97,7 +97,7 @@ jobs: timeout-minutes: 10 - name: Upload Grype SARIF Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: grype-scan-results.sarif path: ${{ steps.scan-package.outputs.sarif }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 644c53d..9c02dd3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,14 +8,15 @@ # 2. Build everything from the bumped tree, so package metas, CDN # snippets, npm READMEs and the docs site carry the new versions. # 3. Publish to npm (and the VS Code extension). -# 4. Deploy the docs site, on every push: a docs-only change has nothing -# to publish but still needs a deploy. +# 4. Deploy the docs site and Wrenfield, on every push: a docs-only change +# has nothing to publish but still needs a deploy. Wrenfield pins the Kit +# version just bumped, so it runs after the publish. # 5. Push the bump commit last, so `main` only records versions that # reached npm. # Why one pipeline: a site deployed in parallel is built from the pre-bump # commit and shows the old versions, and the bump commit never starts a run # to correct it (`[skip ci]`; GITHUB_TOKEN pushes start no workflows). -# deploy-docs.yml is for manual redeploys only. +# deploy-docs.yml and deploy-wrenfield.yml are for manual redeploys only. name: Release on: @@ -136,11 +137,23 @@ jobs: path: size-baseline key: size-baseline-${{ runner.os }}-${{ github.sha }} - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v3 + uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy packages/docs-site/dist --project-name=excom-docs + # Wrenfield (wrenfield.excom.dev), a Worker with static assets. The token + # also needs Workers Scripts:Edit (account), plus Workers Routes:Edit and + # DNS:Edit on the excom.dev zone for the custom domain. The stage step + # reads the bumped Kit version, which the publish above put on npm. + - name: Stage Wrenfield + run: node packages/wrenfield/scripts/stage.mjs + - name: Deploy Wrenfield to Cloudflare Workers + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: deploy --config packages/wrenfield/wrangler.jsonc # 5. Push the bump, if one was committed (HEAD moved off the checked-out # commit). Runs whenever npm publish succeeded, even if the extension or diff --git a/README.md b/README.md index 091252c..b07ee3b 100644 --- a/README.md +++ b/README.md @@ -18,22 +18,22 @@ Your HTML __*is*__ the app! Drop-in custom elements that each have a single resp ## How it's different from existing UI solutions -- **Back to the future** Welcome back to building static HTML5 apps. A break from complex JavaScript apps that compile to HTML. -- **Little to no JavaScript** You no longer need to write JS for the vast majority of UI cases. You may still call-out to your own pure functions for complex cases. -- **Native++** It's real HTML. With a separate, synergistic guest: Quark. -- **No magic** No special frameworks, build processes, rendering wizardry, or "HTML-in-my-JS" / "JS-in-my-HTML" DSLs. -- **Progressively enhanced** Drop into existing static/server-side-rendered sites. Neutron and Quark can also be used independently. -- **Fully composable** Templates, templating, behavior, and custom logic are all decoupled & robust. -- **Reactive State Machine** A simple, reliable, declarative syntax for your business logic - Quark. -- **No reconciliation tax** No large memory copies of state/DOM to be rebuilt, diffed against the DOM, recompiled with every state change. -- **Lightweight** All Nucleus Kit elements + Quark + Neutron have a smaller footprint (just over ~50kb compressed) than some UI framework cores alone. +- **Back to the future** — Welcome back to building static HTML5 apps. A break from complex JavaScript apps that compile to HTML. +- **Little to no JavaScript** — You no longer need to write JS for the vast majority of UI cases. You may still call-out to your own pure functions for complex cases. +- **Native++** — It's real HTML. With a separate, synergistic guest: Quark. +- **No magic** — No special frameworks, build processes, rendering wizardry, or "HTML-in-my-JS" / "JS-in-my-HTML" DSLs. +- **Progressively enhanced** — Drop into existing static/server-side-rendered sites. Nucleus elements and Quark can also be used independently. +- **Fully composable** — Templates, templating, behavior, and custom logic are all decoupled & robust. +- **Reactive State Machine** — A simple, reliable, declarative syntax for your business logic - Quark. +- **No reconciliation tax** — No large memory copies of state/DOM to be rebuilt, diffed against the DOM, recompiled with every state change. +- **Lightweight** — All Nucleus elements + Quark + Neutron have a smaller footprint (just over ~50kb compressed) than some UI framework cores alone. ## What's in the stack -- **Nucleus Kit elements** A growing catalog of drop-in custom elements, including: drawers, tabs, tables, lazy views, forms, routing, passkeys, data fetching, and more. -- **Quark** Like CSS, for document mutation. Select elements, bind data, stamp lists, wire events, and drive state transitions with simple rules instead of imperative code. -- **Valence.css** Semantic, classless CSS that caters to both native and custom elements, with themes, light/dark schemes, and design tokens. Designed for easy drop-in. Optional. -- **Neutron** The small JS factory used to author the elements above. Reach for it only when the Nucleus Kit catalog lacks what you need. Optional. +- **Nucleus Kit elements** — A growing catalog of drop-in custom elements, including: drawers, tabs, tables, lazy views, forms, routing, passkeys, data fetching, and more. +- **Quark** — Like CSS, for document mutation. Select elements, bind data, stamp lists, wire events, and drive state transitions with simple rules instead of imperative code. +- **Valence.css** — Semantic, classless CSS that caters to both native and custom elements, with themes, light/dark schemes, and design tokens. Designed for easy drop-in. Optional. +- **Neutron** — The small JS factory used to author the elements above. Reach for it only when the Nucleus Kit catalog lacks what you need. Optional. Every piece stands alone. Use one element on an existing site, or compose the whole stack into a full single-page app. `nucleus-kit` bundles it all behind one import; if you find you only use a handful of elements, install those packages à la carte instead (`@excom/content-drawer`, `@excom/quark-sheet`, …) and skip the rest. @@ -49,19 +49,25 @@ Then `import "@excom/nucleus-kit"` and `@import "@excom/nucleus-kit/basic.css"`. ## Why teams pick it -- **Significantly less app code** This is possible for two primary reasons. First, because Nucleus Kit elements are fully composable, configurable, and controllable, they will likely be compatible with the desired experience of most applications that use them; there is a low likelihood you will need to build your own. Secondly, Quark enables the majority of customization without needing to invite JavaScript. -- **No components** There is no "component" concept in this architecture. This allows application pieces to be maximally reusable and composable, as there is no home to entrap logic with a tightly coupled view. -- **One source of truth** Live markup _is_ the primary application state, so an entire family of bugs ("the UI disagrees with the model") cannot exist. -- **Fully inspectable** Open devtools and the entire application is in front of you: every value, every binding, and every transition. The state is the document/DOM, so all is plainly transparent to see, alter, and debug in your inspector. -- **Accessible by default** Declarative & ARIA state is the state... not a mirror someone forgot to update. -- **Human and machine friendly** Inspect [the docs site](https://excom.dev/nucleus) to see its declarativeness. No more `
` soups bound to untraceable JavaScript. Custom elements make for a beautifully declarative document. A page that is legible, addressable, and serializable is an ideal target for code generation, AI-assisted editing, and confident human auditing. Tools reason about the screen's exact state instead of inferring a component tree. Likewise, writing and debugging UI code by hand has never felt simpler. -- **Declarative business behavior** The vast majority of your proprietary behaviors exist as simple configurations, rather than buried inside imperative spaghetti code. +- **Significantly less app code** — This is possible for two primary reasons. First, because Nucleus Kit elements are fully composable, configurable, and controllable, they will likely be compatible with the desired experience of most applications that use them; there is a low likelihood you will need to build your own. Secondly, Quark enables the majority of customization without needing to invite JavaScript. +- **No components** — There is no "component" concept in this architecture. This allows application pieces to be maximally reusable and composable, as there is no home to entrap logic with a tightly coupled view. +- **One source of truth** — Live markup _is_ the primary application state, so an entire family of bugs ("the UI disagrees with the model") cannot exist. +- **Fully inspectable** — Open devtools and the entire application is in front of you: every value, every binding, and every transition. The state is the document/DOM, so all is plainly transparent to see, alter, and debug in your inspector. +- **Accessible by default** — Declarative & ARIA state is the state... not a mirror someone forgot to update. +- **Human and machine friendly** — Inspect [the docs site](https://excom.dev/nucleus) to see its declarativeness. No more `
` soups bound to untraceable JavaScript. Custom elements make for a beautifully declarative document. A page that is legible, addressable, and serializable is an ideal target for code generation, AI-assisted editing, and confident human auditing. Tools reason about the screen's exact state instead of inferring a component tree. Likewise, writing and debugging UI code by hand has never felt simpler. +- **Declarative business behavior** — The vast majority of your proprietary behaviors exist as simple configurations, rather than buried inside imperative spaghetti code. ## Where it shines Content-rich sites, complex data-driven business rules, progressive enhancement of static/server-rendered pages, embedded user experiences. See [Limitations](https://excom.dev/nucleus/docs/limitations) for the edges. -The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before: zero-build-tool UIs (e.g. on-the-fly generation), declarative & auditable target for LLM UI building, plain text assembly to rich UX (like a CMS), incremental upgrading of static/legacy SSR sites, resource-constrained web UIs (especially where scripting needs to be validated or limited, like an ATM), embedded UX (such as upgrading markdown with embedded functionality). +The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before: +- zero-build-tool UIs (e.g. on-the-fly generation) +- declarative & auditable target for LLM UI building +- programmatic assembly of rich UX (like a CMS) +- incremental upgrading of static/legacy SSR sites +- resource-constrained, secure, or sandboxed web UIs (especially where scripting needs to be validated or limited, like an ATM) +- embedded UX (such as upgrading markdown with embedded functionality) ## Dogfood is nutritious diff --git a/common/changes/@excom/abortable-element/test-rig_2026-10-01-tests.json b/common/changes/@excom/abortable-element/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..2d15109 --- /dev/null +++ b/common/changes/@excom/abortable-element/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/abortable-element", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/abortable-element" +} diff --git a/common/changes/@excom/content-carousel/test-rig_2026-09-30-track.json b/common/changes/@excom/content-carousel/test-rig_2026-09-30-track.json new file mode 100644 index 0000000..4b61aec --- /dev/null +++ b/common/changes/@excom/content-carousel/test-rig_2026-09-30-track.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/content-carousel", + "comment": "Track mode hides every slide beyond the active slide's neighbours and no longer animates the first slide into place", + "type": "patch" + } + ], + "packageName": "@excom/content-carousel" +} diff --git a/common/changes/@excom/content-drawer/test-rig_2026-10-01-tests.json b/common/changes/@excom/content-drawer/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..9468931 --- /dev/null +++ b/common/changes/@excom/content-drawer/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/content-drawer", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/content-drawer" +} diff --git a/common/changes/@excom/content-tabs/test-rig_2026-10-01-tests.json b/common/changes/@excom/content-tabs/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..26149e1 --- /dev/null +++ b/common/changes/@excom/content-tabs/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/content-tabs", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/content-tabs" +} diff --git a/common/changes/@excom/data-table/test-rig_2026-10-01-tests.json b/common/changes/@excom/data-table/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..e09758b --- /dev/null +++ b/common/changes/@excom/data-table/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/data-table", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/data-table" +} diff --git a/common/changes/@excom/detect-browser/test-rig_2026-10-01-tests.json b/common/changes/@excom/detect-browser/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..90fa959 --- /dev/null +++ b/common/changes/@excom/detect-browser/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/detect-browser", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/detect-browser" +} diff --git a/common/changes/@excom/detect-features/test-rig_2026-10-01-tests.json b/common/changes/@excom/detect-features/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..20889d8 --- /dev/null +++ b/common/changes/@excom/detect-features/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/detect-features", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/detect-features" +} diff --git a/common/changes/@excom/detect-media/test-rig_2026-10-01-tests.json b/common/changes/@excom/detect-media/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..e3fc105 --- /dev/null +++ b/common/changes/@excom/detect-media/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/detect-media", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/detect-media" +} diff --git a/common/changes/@excom/dialog-anchor/test-rig_2026-10-01-tests.json b/common/changes/@excom/dialog-anchor/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..7a4e3ef --- /dev/null +++ b/common/changes/@excom/dialog-anchor/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/dialog-anchor", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/dialog-anchor" +} diff --git a/common/changes/@excom/dismiss-watcher/test-rig_2026-10-01-tests.json b/common/changes/@excom/dismiss-watcher/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..734c111 --- /dev/null +++ b/common/changes/@excom/dismiss-watcher/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/dismiss-watcher", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/dismiss-watcher" +} diff --git a/common/changes/@excom/docs-site/test-rig_2026-10-01-test-fixes.json b/common/changes/@excom/docs-site/test-rig_2026-10-01-test-fixes.json new file mode 100644 index 0000000..b0de029 --- /dev/null +++ b/common/changes/@excom/docs-site/test-rig_2026-10-01-test-fixes.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/docs-site", + "comment": "Fix happy-dom iframe throw in live-app test and correct nav order assertion in site-nav test", + "type": "none" + } + ], + "packageName": "@excom/docs-site" +} diff --git a/common/changes/@excom/dom-observer/test-rig_2026-10-01-tests.json b/common/changes/@excom/dom-observer/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..9cc39f8 --- /dev/null +++ b/common/changes/@excom/dom-observer/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/dom-observer", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/dom-observer" +} diff --git a/common/changes/@excom/event-handler/test-rig_2026-10-01-tests.json b/common/changes/@excom/event-handler/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..8603005 --- /dev/null +++ b/common/changes/@excom/event-handler/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/event-handler", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/event-handler" +} diff --git a/common/changes/@excom/fetchable-element/test-rig_2026-10-01-tests.json b/common/changes/@excom/fetchable-element/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..c54d3be --- /dev/null +++ b/common/changes/@excom/fetchable-element/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/fetchable-element", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/fetchable-element" +} diff --git a/common/changes/@excom/gesture-handler/test-rig_2026-09-30-axis-lock.json b/common/changes/@excom/gesture-handler/test-rig_2026-09-30-axis-lock.json new file mode 100644 index 0000000..56a9906 --- /dev/null +++ b/common/changes/@excom/gesture-handler/test-rig_2026-09-30-axis-lock.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/gesture-handler", + "comment": "Pan handlers cancel the page's cross-axis scroll on the first touch move", + "type": "patch" + } + ], + "packageName": "@excom/gesture-handler" +} diff --git a/common/changes/@excom/hash-object/test-rig_2026-10-01-tests.json b/common/changes/@excom/hash-object/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..5562f12 --- /dev/null +++ b/common/changes/@excom/hash-object/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/hash-object", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/hash-object" +} diff --git a/common/changes/@excom/heft-rig/test-rig_2026-09-30-nav-group.json b/common/changes/@excom/heft-rig/test-rig_2026-09-30-nav-group.json new file mode 100644 index 0000000..d17bf10 --- /dev/null +++ b/common/changes/@excom/heft-rig/test-rig_2026-09-30-nav-group.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/heft-rig", + "comment": "Add the optional `excom.navGroup` package.json key (only `\"libraries\"` is accepted by `format-package-json`), carried into the docs-site `index.json` catalog so a package can move from its type's sidebar list into a named group", + "type": "minor" + } + ], + "packageName": "@excom/heft-rig" +} diff --git a/common/changes/@excom/heft-rig/test-rig_2026-09-30-release-notes.json b/common/changes/@excom/heft-rig/test-rig_2026-09-30-release-notes.json new file mode 100644 index 0000000..99921ad --- /dev/null +++ b/common/changes/@excom/heft-rig/test-rig_2026-09-30-release-notes.json @@ -0,0 +1,20 @@ +{ + "changes": [ + { + "packageName": "@excom/heft-rig", + "comment": "Add release notes: `build-package-metas.mjs` appends the `CHANGELOG.json` notes (minus dependency and `none` comments) to the README HTML as a closing `
`, and `build-docs.mjs` adds the three newest releases as `## Release notes` to the primary element's Markdown, so they reach the site and the LLM docs", + "type": "minor" + }, + { + "packageName": "@excom/heft-rig", + "comment": "Fix the library build's type declarations, which pointed at a folder outside the tarball; `verify-publish-outputs.mjs` now fails a publish when a declaration path dangles", + "type": "patch" + }, + { + "packageName": "@excom/heft-rig", + "comment": "Add `excom.umd: false` to skip the UMD bundle of a Node-only library (no CDN script in its install snippet), the `app` package type and unscoped names in `format-package-json`, and the test layer moved to `@excom/nucleus-dom` and `@excom/nucleus-test`", + "type": "minor" + } + ], + "packageName": "@excom/heft-rig" +} diff --git a/common/changes/@excom/heft-rig/test-rig_2026-10-01-app-vite-dev.json b/common/changes/@excom/heft-rig/test-rig_2026-10-01-app-vite-dev.json new file mode 100644 index 0000000..fda76f8 --- /dev/null +++ b/common/changes/@excom/heft-rig/test-rig_2026-10-01-app-vite-dev.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/heft-rig", + "comment": "Support `app` packageType in vite-dev so app packages use the shared Vite dev server", + "type": "patch" + } + ], + "packageName": "@excom/heft-rig" +} diff --git a/common/changes/@excom/include-content/test-rig_2026-10-01-tests.json b/common/changes/@excom/include-content/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..2f3ebf7 --- /dev/null +++ b/common/changes/@excom/include-content/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/include-content", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/include-content" +} diff --git a/common/changes/@excom/kit-devtools/test-rig_2026-09-30-declarations.json b/common/changes/@excom/kit-devtools/test-rig_2026-09-30-declarations.json new file mode 100644 index 0000000..7c23c58 --- /dev/null +++ b/common/changes/@excom/kit-devtools/test-rig_2026-09-30-declarations.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/kit-devtools", + "comment": "Fix the published type declarations: `index.d.ts` pointed at a folder that is not in the tarball, so consumers got `any` for named re-exports and missing-member errors for `export *` instead of the real types", + "type": "patch" + } + ], + "packageName": "@excom/kit-devtools" +} diff --git a/common/changes/@excom/kit-logger/test-rig_2026-10-01-tests.json b/common/changes/@excom/kit-logger/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..022ae3a --- /dev/null +++ b/common/changes/@excom/kit-logger/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/kit-logger", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/kit-logger" +} diff --git a/common/changes/@excom/kit-router/test-rig_2026-10-01-tests.json b/common/changes/@excom/kit-router/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..11d5d2d --- /dev/null +++ b/common/changes/@excom/kit-router/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/kit-router", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/kit-router" +} diff --git a/common/changes/@excom/kit-scroller/test-rig_2026-10-01-tests.json b/common/changes/@excom/kit-scroller/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..189083f --- /dev/null +++ b/common/changes/@excom/kit-scroller/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/kit-scroller", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/kit-scroller" +} diff --git a/common/changes/@excom/kit-shims/test-rig_2026-10-01-tests.json b/common/changes/@excom/kit-shims/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..b2da1a7 --- /dev/null +++ b/common/changes/@excom/kit-shims/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/kit-shims", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/kit-shims" +} diff --git a/common/changes/@excom/kit-utils/test-rig_2026-10-01-tests.json b/common/changes/@excom/kit-utils/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..b85d50c --- /dev/null +++ b/common/changes/@excom/kit-utils/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/kit-utils", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/kit-utils" +} diff --git a/common/changes/@excom/listenable-element/test-rig_2026-10-01-tests.json b/common/changes/@excom/listenable-element/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..9259123 --- /dev/null +++ b/common/changes/@excom/listenable-element/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/listenable-element", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/listenable-element" +} diff --git a/common/changes/@excom/loadable-element/test-rig_2026-10-01-tests.json b/common/changes/@excom/loadable-element/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..a4d13f7 --- /dev/null +++ b/common/changes/@excom/loadable-element/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/loadable-element", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/loadable-element" +} diff --git a/common/changes/@excom/mapbox-view/test-rig_2026-10-01-tests.json b/common/changes/@excom/mapbox-view/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..9bc9d14 --- /dev/null +++ b/common/changes/@excom/mapbox-view/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/mapbox-view", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/mapbox-view" +} diff --git a/common/changes/@excom/network-status/test-rig_2026-10-01-tests.json b/common/changes/@excom/network-status/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..69ddb43 --- /dev/null +++ b/common/changes/@excom/network-status/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/network-status", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/network-status" +} diff --git a/common/changes/@excom/neutron/test-rig_2026-09-30-declarations.json b/common/changes/@excom/neutron/test-rig_2026-09-30-declarations.json new file mode 100644 index 0000000..39832d1 --- /dev/null +++ b/common/changes/@excom/neutron/test-rig_2026-09-30-declarations.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/neutron", + "comment": "Fix the published type declarations: `index.d.ts` pointed at a folder that is not in the tarball, so consumers got `any` for named re-exports and missing-member errors for `export *` instead of the real types", + "type": "patch" + } + ], + "packageName": "@excom/neutron" +} diff --git a/common/changes/@excom/nucleus-devtools/test-rig_2026-10-01-settle-ticks.json b/common/changes/@excom/nucleus-devtools/test-rig_2026-10-01-settle-ticks.json new file mode 100644 index 0000000..d2307c6 --- /dev/null +++ b/common/changes/@excom/nucleus-devtools/test-rig_2026-10-01-settle-ticks.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/nucleus-devtools", + "comment": "Increase settle ticks in element view test to avoid CI timer race", + "type": "none" + } + ], + "packageName": "@excom/nucleus-devtools" +} diff --git a/common/changes/@excom/nucleus-dom/test-rig_2026-09-30-initial.json b/common/changes/@excom/nucleus-dom/test-rig_2026-09-30-initial.json new file mode 100644 index 0000000..6293379 --- /dev/null +++ b/common/changes/@excom/nucleus-dom/test-rig_2026-09-30-initial.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/nucleus-dom", + "comment": "Initial release of `@excom/nucleus-dom`: happy-dom pinned exactly, the browser-parity shims (document-wide queries with `:scope`, MutationObserver pin, the Command API, `checkVisibility`, `ServiceWorkerContainer`) and `createDom()`", + "type": "none" + } + ], + "packageName": "@excom/nucleus-dom" +} diff --git a/common/changes/@excom/nucleus-dom/test-rig_2026-09-30-nav-group.json b/common/changes/@excom/nucleus-dom/test-rig_2026-09-30-nav-group.json new file mode 100644 index 0000000..42fd32f --- /dev/null +++ b/common/changes/@excom/nucleus-dom/test-rig_2026-09-30-nav-group.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/nucleus-dom", + "comment": "Set `excom.navGroup: \"libraries\"` so the docs site lists the package in its Libraries group", + "type": "none" + } + ], + "packageName": "@excom/nucleus-dom" +} diff --git a/common/changes/@excom/nucleus-kit/test-rig_2026-09-30-fix-batch.json b/common/changes/@excom/nucleus-kit/test-rig_2026-09-30-fix-batch.json new file mode 100644 index 0000000..a407716 --- /dev/null +++ b/common/changes/@excom/nucleus-kit/test-rig_2026-09-30-fix-batch.json @@ -0,0 +1,40 @@ +{ + "changes": [ + { + "packageName": "@excom/nucleus-kit", + "comment": "Add `Quark.meter`, the engine's work counters (`counts`, `reset()`, `scopeSelectors()`), always on, for complexity snapshots with `@excom/nucleus-test`", + "type": "minor" + }, + { + "packageName": "@excom/nucleus-kit", + "comment": "Add a warning when a `spa-manager` update settles by `render-timeout` while a route is still pending, so an empty first paint has a cause in the console at log level 2", + "type": "minor" + }, + { + "packageName": "@excom/nucleus-kit", + "comment": "Update `content-carousel` in `slide-animation=\"track\"` to render only the active slide and its two DOM neighbours (a wrap with three or more slides cuts instead of sliding), and to animate nothing before the first move, so an initial `is-active` appears in place", + "type": "minor" + }, + { + "packageName": "@excom/nucleus-kit", + "comment": "Update `gesture-handler` `pan-x` / `pan-y` handlers to hold the page still for the rest of a touch whose first 3 px run along the handler's axis, through a non-passive `touchmove` listener; `touch-action` is unchanged, a touch that starts across the axis scrolls as before, and a same-axis scroller under the finger keeps scrolling", + "type": "minor" + }, + { + "packageName": "@excom/nucleus-kit", + "comment": "Fix `web-authn` after a failed ceremony or options request: `is-loading` is cleared, `is-error` is set with the message in `provision`, and a retry clears `is-error`", + "type": "patch" + }, + { + "packageName": "@excom/nucleus-kit", + "comment": "Fix an issue where an `is-fallback` route stayed off after leaving a nested route for a URL no route matches: the fallback now checks which routes match the path, so its position among its siblings no longer matters, a page without a `spa-manager` gets a working fallback, and a fallback inside a layout can activate on the layout's unmatched child paths", + "type": "patch" + }, + { + "packageName": "@excom/nucleus-kit", + "comment": "Fix the published type declarations of `neutron`, `quark`, `quark-parser`, `quark-formatter` and `kit-devtools`, which pointed at a folder outside their tarballs, so the Kit's re-exported types resolve instead of `any`", + "type": "patch" + } + ], + "packageName": "@excom/nucleus-kit" +} diff --git a/common/changes/@excom/nucleus-test/test-rig_2026-09-30-complexity.json b/common/changes/@excom/nucleus-test/test-rig_2026-09-30-complexity.json new file mode 100644 index 0000000..9b3245b --- /dev/null +++ b/common/changes/@excom/nucleus-test/test-rig_2026-09-30-complexity.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/nucleus-test", + "comment": "measureComplexity, expectComplexity and trackComplexity snapshot Quark and DOM work per test", + "type": "minor" + } + ], + "packageName": "@excom/nucleus-test" +} diff --git a/common/changes/@excom/nucleus-test/test-rig_2026-09-30-initial.json b/common/changes/@excom/nucleus-test/test-rig_2026-09-30-initial.json new file mode 100644 index 0000000..e37af19 --- /dev/null +++ b/common/changes/@excom/nucleus-test/test-rig_2026-09-30-initial.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/nucleus-test", + "comment": "Initial release of `@excom/nucleus-test`: the vitest setup (shims, listener registry, `equalTag`, `toMatchListeners`, `toContainListeners`, the HTML snapshot serializer, the console node guard) and the test utilities (`fixture`, `click`, `wait`, `waitForEvent`, `spyFetch`)", + "type": "none" + } + ], + "packageName": "@excom/nucleus-test" +} diff --git a/common/changes/@excom/provider-fetch/test-rig_2026-10-01-tests.json b/common/changes/@excom/provider-fetch/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..3c5b2e7 --- /dev/null +++ b/common/changes/@excom/provider-fetch/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/provider-fetch", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/provider-fetch" +} diff --git a/common/changes/@excom/provider-geolocation/test-rig_2026-10-01-tests.json b/common/changes/@excom/provider-geolocation/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..b7d8843 --- /dev/null +++ b/common/changes/@excom/provider-geolocation/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/provider-geolocation", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/provider-geolocation" +} diff --git a/common/changes/@excom/provider-orientation/test-rig_2026-10-01-tests.json b/common/changes/@excom/provider-orientation/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..2c60019 --- /dev/null +++ b/common/changes/@excom/provider-orientation/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/provider-orientation", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/provider-orientation" +} diff --git a/common/changes/@excom/provider-storage/test-rig_2026-10-01-tests.json b/common/changes/@excom/provider-storage/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..e1a91fc --- /dev/null +++ b/common/changes/@excom/provider-storage/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/provider-storage", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/provider-storage" +} diff --git a/common/changes/@excom/quark-formatter/test-rig_2026-09-30-declarations.json b/common/changes/@excom/quark-formatter/test-rig_2026-09-30-declarations.json new file mode 100644 index 0000000..65bacd4 --- /dev/null +++ b/common/changes/@excom/quark-formatter/test-rig_2026-09-30-declarations.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/quark-formatter", + "comment": "Fix the published type declarations: `index.d.ts` pointed at a folder that is not in the tarball, so consumers got `any` for named re-exports and missing-member errors for `export *` instead of the real types", + "type": "patch" + } + ], + "packageName": "@excom/quark-formatter" +} diff --git a/common/changes/@excom/quark-formatter/test-rig_2026-09-30-nav-group.json b/common/changes/@excom/quark-formatter/test-rig_2026-09-30-nav-group.json new file mode 100644 index 0000000..34ff0df --- /dev/null +++ b/common/changes/@excom/quark-formatter/test-rig_2026-09-30-nav-group.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/quark-formatter", + "comment": "Set `excom.navGroup: \"libraries\"` so the docs site lists the package in its Libraries group", + "type": "none" + } + ], + "packageName": "@excom/quark-formatter" +} diff --git a/common/changes/@excom/quark-parser/test-rig_2026-09-30-declarations.json b/common/changes/@excom/quark-parser/test-rig_2026-09-30-declarations.json new file mode 100644 index 0000000..9ffa3ed --- /dev/null +++ b/common/changes/@excom/quark-parser/test-rig_2026-09-30-declarations.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/quark-parser", + "comment": "Fix the published type declarations: `index.d.ts` pointed at a folder that is not in the tarball, so consumers got `any` for named re-exports and missing-member errors for `export *` instead of the real types", + "type": "patch" + } + ], + "packageName": "@excom/quark-parser" +} diff --git a/common/changes/@excom/quark-sheet/test-rig_2026-10-01-tests.json b/common/changes/@excom/quark-sheet/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..2dff11b --- /dev/null +++ b/common/changes/@excom/quark-sheet/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/quark-sheet", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/quark-sheet" +} diff --git a/common/changes/@excom/quark/test-rig_2026-09-30-declarations.json b/common/changes/@excom/quark/test-rig_2026-09-30-declarations.json new file mode 100644 index 0000000..069c532 --- /dev/null +++ b/common/changes/@excom/quark/test-rig_2026-09-30-declarations.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/quark", + "comment": "Fix the published type declarations: `index.d.ts` pointed at a folder that is not in the tarball, so consumers got `any` for named re-exports and missing-member errors for `export *` instead of the real types", + "type": "patch" + } + ], + "packageName": "@excom/quark" +} diff --git a/common/changes/@excom/quark/test-rig_2026-09-30-meter.json b/common/changes/@excom/quark/test-rig_2026-09-30-meter.json new file mode 100644 index 0000000..ff1c9e7 --- /dev/null +++ b/common/changes/@excom/quark/test-rig_2026-09-30-meter.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/quark", + "comment": "Quark.meter exposes the engine's work counters for complexity snapshots", + "type": "minor" + } + ], + "packageName": "@excom/quark" +} diff --git a/common/changes/@excom/renderable-element/test-rig_2026-10-01-tests.json b/common/changes/@excom/renderable-element/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..9874e76 --- /dev/null +++ b/common/changes/@excom/renderable-element/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/renderable-element", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/renderable-element" +} diff --git a/common/changes/@excom/routable-element/test-rig_2026-10-01-tests.json b/common/changes/@excom/routable-element/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..0cabefe --- /dev/null +++ b/common/changes/@excom/routable-element/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/routable-element", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/routable-element" +} diff --git a/common/changes/@excom/scroll-into-view/test-rig_2026-10-01-tests.json b/common/changes/@excom/scroll-into-view/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..22a65eb --- /dev/null +++ b/common/changes/@excom/scroll-into-view/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/scroll-into-view", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/scroll-into-view" +} diff --git a/common/changes/@excom/service-worker/test-rig_2026-10-01-tests.json b/common/changes/@excom/service-worker/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..be07caa --- /dev/null +++ b/common/changes/@excom/service-worker/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/service-worker", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/service-worker" +} diff --git a/common/changes/@excom/spa-route/test-rig_2026-09-30-fallback.json b/common/changes/@excom/spa-route/test-rig_2026-09-30-fallback.json new file mode 100644 index 0000000..b84bfe9 --- /dev/null +++ b/common/changes/@excom/spa-route/test-rig_2026-09-30-fallback.json @@ -0,0 +1,20 @@ +{ + "changes": [ + { + "packageName": "@excom/spa-route", + "comment": "Fix an issue where an `is-fallback` route stayed off after leaving a nested route for a URL no route matches: the fallback now checks which routes match the path instead of reading their `is-active` state, so its position among its siblings no longer matters while navigating, a page without a `spa-manager` gets a working fallback, a fallback inside a layout can activate on the layout's unmatched child paths, and other fallbacks never hold it off", + "type": "patch" + }, + { + "packageName": "@excom/spa-route", + "comment": "Add a warning when a `spa-manager` update settles by `render-timeout` while a route is still pending, so an empty first paint has a cause in the console at log level 2", + "type": "patch" + }, + { + "packageName": "@excom/spa-route", + "comment": "Add `@excom/spa-route/testing`, the router helpers for Vitest on happy-dom: `resetRouter()`, `navigate()`, `popstate()`, `installViewTransition()` and `trackUnhandledRejections()`", + "type": "minor" + } + ], + "packageName": "@excom/spa-route" +} diff --git a/common/changes/@excom/super-form/test-rig_2026-10-01-tests.json b/common/changes/@excom/super-form/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..572edfa --- /dev/null +++ b/common/changes/@excom/super-form/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/super-form", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/super-form" +} diff --git a/common/changes/@excom/super-img/test-rig_2026-10-01-tests.json b/common/changes/@excom/super-img/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..7275799 --- /dev/null +++ b/common/changes/@excom/super-img/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/super-img", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/super-img" +} diff --git a/common/changes/@excom/super-input/test-rig_2026-10-01-tests.json b/common/changes/@excom/super-input/test-rig_2026-10-01-tests.json new file mode 100644 index 0000000..1196b91 --- /dev/null +++ b/common/changes/@excom/super-input/test-rig_2026-10-01-tests.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/super-input", + "comment": "Add `@excom/nucleus-test` and update test imports", + "type": "none" + } + ], + "packageName": "@excom/super-input" +} diff --git a/common/changes/@excom/web-authn/test-rig_2026-09-30-error-state.json b/common/changes/@excom/web-authn/test-rig_2026-09-30-error-state.json new file mode 100644 index 0000000..95f4c6d --- /dev/null +++ b/common/changes/@excom/web-authn/test-rig_2026-09-30-error-state.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/web-authn", + "comment": "A failed ceremony or options request now clears is-loading and sets is-error with the message in provision", + "type": "patch" + } + ], + "packageName": "@excom/web-authn" +} diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index bbb8c5a..c9ef66c 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/content-carousel: dependencies: @@ -35,6 +38,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/content-drawer: dependencies: @@ -51,6 +57,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/content-tabs: dependencies: @@ -64,6 +73,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/data-table: dependencies: @@ -80,6 +92,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/detect-browser: dependencies: @@ -90,6 +105,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/detect-features: dependencies: @@ -100,6 +118,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/detect-media: dependencies: @@ -113,6 +134,9 @@ importers: '@excom/include-content': specifier: workspace:^ version: link:../include-content + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/quark': specifier: workspace:^ version: link:../quark @@ -135,6 +159,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/dismiss-watcher: dependencies: @@ -157,6 +184,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/quark': specifier: workspace:^ version: link:../quark @@ -314,6 +344,12 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-dom': + specifier: workspace:^ + version: link:../nucleus-dom + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/quark-formatter': specifier: workspace:^ version: link:../quark-formatter @@ -330,6 +366,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/event-handler: dependencies: @@ -346,6 +385,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/fetchable-element: dependencies: @@ -368,6 +410,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/gesture-handler: dependencies: @@ -390,6 +435,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/quark': specifier: workspace:^ version: link:../quark @@ -402,15 +450,15 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/heft-rig: dependencies: '@microsoft/api-extractor': specifier: ^7.55.5 version: 7.55.5(@types/node@20.19.27) - '@open-wc/semantic-dom-diff': - specifier: ^0.20.1 - version: 0.20.1 '@rushstack/heft': specifier: ^1.1.7 version: 1.1.7(@types/node@20.19.27) @@ -505,6 +553,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/kit-devtools: dependencies: @@ -518,12 +569,18 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/kit-logger: devDependencies: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/kit-router: dependencies: @@ -537,18 +594,27 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/kit-scroller: devDependencies: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/kit-shims: devDependencies: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/kit-utils: dependencies: @@ -559,6 +625,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/listenable-element: dependencies: @@ -575,6 +644,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/loadable-element: dependencies: @@ -585,6 +657,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/mapbox-view: dependencies: @@ -601,6 +676,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/network-status: dependencies: @@ -611,6 +689,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/neutron: dependencies: @@ -627,6 +708,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/nucleus-devtools: dependencies: @@ -652,6 +736,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test typescript: specifier: ^5.9.2 version: 5.9.3 @@ -659,6 +746,16 @@ importers: specifier: ^0.20.5 version: 0.20.27(@types/node@20.19.27)(eslint@9.39.2(jiti@2.7.0))(jiti@2.7.0)(rolldown@1.2.8) + ../../packages/nucleus-dom: + dependencies: + happy-dom: + specifier: 20.8.3 + version: 20.8.3 + devDependencies: + '@excom/heft-rig': + specifier: workspace:^ + version: link:../heft-rig + ../../packages/nucleus-kit: dependencies: '@excom/content-carousel': @@ -752,6 +849,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/nucleus-quark-highlighter: devDependencies: @@ -768,6 +868,22 @@ importers: specifier: ^0.28.2 version: 0.28.2 + ../../packages/nucleus-test: + dependencies: + '@excom/nucleus-dom': + specifier: workspace:^ + version: link:../nucleus-dom + '@open-wc/semantic-dom-diff': + specifier: ^0.20.1 + version: 0.20.1 + devDependencies: + '@excom/heft-rig': + specifier: workspace:^ + version: link:../heft-rig + vitest: + specifier: ^4.1.11 + version: 4.1.11(@types/node@20.19.27)(@vitest/coverage-v8@4.1.11)(happy-dom@20.8.3)(vite@8.3.0(@types/node@20.19.27)(esbuild@0.28.2)(jiti@2.7.0)) + ../../packages/provider-fetch: dependencies: '@excom/fetchable-element': @@ -783,6 +899,9 @@ importers: '@excom/kit-logger': specifier: workspace:^ version: link:../kit-logger + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/provider-geolocation: dependencies: @@ -793,6 +912,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/provider-orientation: dependencies: @@ -803,6 +925,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/provider-storage: dependencies: @@ -816,6 +941,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/quark: dependencies: @@ -838,6 +966,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/provider-fetch': specifier: workspace:^ version: link:../provider-fetch @@ -850,6 +981,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/quark-parser': specifier: workspace:^ version: link:../quark-parser @@ -859,6 +993,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/quark-sheet: dependencies: @@ -881,6 +1018,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/renderable-element: dependencies: @@ -903,6 +1043,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/routable-element: dependencies: @@ -916,6 +1059,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/scroll-into-view: dependencies: @@ -935,6 +1081,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/service-worker: dependencies: @@ -948,6 +1097,9 @@ importers: '@excom/kit-utils': specifier: workspace:^ version: link:../kit-utils + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/spa-route: dependencies: @@ -976,6 +1128,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/super-form: dependencies: @@ -992,6 +1147,9 @@ importers: '@excom/kit-logger': specifier: workspace:^ version: link:../kit-logger + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test '@excom/quark': specifier: workspace:^ version: link:../quark @@ -1008,6 +1166,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/super-input: dependencies: @@ -1024,6 +1185,9 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test ../../packages/valence: devDependencies: @@ -1046,6 +1210,25 @@ importers: '@excom/heft-rig': specifier: workspace:^ version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test + + ../../packages/wrenfield: + dependencies: + '@excom/nucleus-kit': + specifier: workspace:^ + version: link:../nucleus-kit + devDependencies: + '@excom/heft-rig': + specifier: workspace:^ + version: link:../heft-rig + '@excom/nucleus-test': + specifier: workspace:^ + version: link:../nucleus-test + '@excom/spa-route': + specifier: workspace:^ + version: link:../spa-route packages: @@ -6691,7 +6874,7 @@ snapshots: istanbul-reports: 3.2.0 log-update: 4.0.0 nanocolors: 0.2.13 - nanoid: 3.3.11 + nanoid: 3.3.19 open: 8.4.2 picomatch: 2.3.1 source-map: 0.7.6 diff --git a/packages/abortable-element/package.json b/packages/abortable-element/package.json index 92093e7..faeb978 100644 --- a/packages/abortable-element/package.json +++ b/packages/abortable-element/package.json @@ -26,7 +26,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -44,4 +45,4 @@ "excom": { "packageType": "element-base" } -} +} \ No newline at end of file diff --git a/packages/abortable-element/support/tests/abortable-element.test.ts b/packages/abortable-element/support/tests/abortable-element.test.ts index 3f14fef..17bb466 100644 --- a/packages/abortable-element/support/tests/abortable-element.test.ts +++ b/packages/abortable-element/support/tests/abortable-element.test.ts @@ -5,7 +5,7 @@ import { expect, fixture, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; const TAG = "abortable-element-test"; if (!customElements.get(TAG)) { diff --git a/packages/content-carousel/content-carousel.ts b/packages/content-carousel/content-carousel.ts index 245e196..05f9079 100644 --- a/packages/content-carousel/content-carousel.ts +++ b/packages/content-carousel/content-carousel.ts @@ -67,7 +67,7 @@ export const ContentCarousel = Neutron({ /** * @option * Transition used when the active slide changes. - * @values slide | fade + * @values slide | fade | track * @default slide */ slideAnimation: String, @@ -75,7 +75,8 @@ export const ContentCarousel = Neutron({ * @state * Direction of the most recent slide change — drives the CSS * animation direction. `is-active` moved from one slide to another - * counts as `forward` / `back` by position. + * counts as `forward` / `back` by position. Unset until the first move, + * so the initial slide appears without animating in. * @values forward | back */ lastMove: String, diff --git a/packages/content-carousel/package.json b/packages/content-carousel/package.json index 177f38a..a7a283e 100644 --- a/packages/content-carousel/package.json +++ b/packages/content-carousel/package.json @@ -27,7 +27,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -43,4 +44,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/content-carousel/src/content-carousel.css b/packages/content-carousel/src/content-carousel.css index 8782251..e1bd5fc 100644 --- a/packages/content-carousel/src/content-carousel.css +++ b/packages/content-carousel/src/content-carousel.css @@ -180,8 +180,6 @@ 100% ) ); - transition: transform var(--content-carousel-slide-animation-duration) - var(--content-carousel-transition-ease); &:--content-carousel-slide--is-active { --content-carousel-slot: 0; z-index: 2; @@ -189,12 +187,21 @@ &:has(~ :--content-carousel-slide--is-active) { --content-carousel-slot: -1; } - &:is(&:--content-carousel-slide--is-active + & + &), - &:has(+ & + &:--content-carousel-slide--is-active) { - /* hide slides that are two slides away */ + /* Neighbours are DOM siblings; a slide further out would share their + ±100% slot and paint over them. Tests assert this sibling contract. */ + &:not( + :--content-carousel-slide--is-active, + :--content-carousel-slide--is-active + *, + :has(+ :--content-carousel-slide--is-active) + ) { display: none !important; } } + /* Only after a move: the initial `is-active` lands without sliding in */ + &:--content-carousel--last-move :--content-carousel-slide { + transition: transform var(--content-carousel-slide-animation-duration) + var(--content-carousel-transition-ease); + } &:--content-carousel--is-scrubbing { --content-carousel-progress: var(--gesture-progress, 0); :--content-carousel-slide { diff --git a/packages/content-carousel/support/docs/README.md b/packages/content-carousel/support/docs/README.md index cb98b44..bb96652 100644 --- a/packages/content-carousel/support/docs/README.md +++ b/packages/content-carousel/support/docs/README.md @@ -8,7 +8,7 @@ Rotate slides on autopilot or on click — galleries, hero banners, walkthroughs - **Auto-play** Rotate on a timer via `auto-play` - **Manual nav** `--back` / `--next` commands from plain buttons; `rel="prev"` / `rel="next"` positions them -- **Slide or fade** Choose the transition with `slide-animation` +- **Slide or fade** Choose the transition with `slide-animation`: `slide` / `fade` / `track` - **Swipeable** `slide-animation="track"` wrapped in [`gesture-handler`](/nucleus/packages/gesture-handler): drag and flick between slides - **Pauses itself** Manual navigation stops auto-play automatically - **Bindable position** `.provision` is `{ index, count, lastMove }` — a @@ -41,6 +41,8 @@ content-carousel { } ``` +Nothing animates until the first move: `last-move` is unset until then, so an initial `is-active`, in markup or written by the app, appears in place. `slide-animation="track"` renders only the active slide and its two DOM neighbours, so slides must be siblings (a `display: contents` wrapper around them is fine) and, with three or more slides, a wrap — `--next` from the last, `--back` from the first — cuts to the new slide instead of sliding; put it inside [`gesture-handler`](/nucleus/packages/gesture-handler) to drag it. + ### API Reference diff --git a/packages/content-carousel/support/tests/content-carousel.test.ts b/packages/content-carousel/support/tests/content-carousel.test.ts index f57b8c0..7cb54dd 100644 --- a/packages/content-carousel/support/tests/content-carousel.test.ts +++ b/packages/content-carousel/support/tests/content-carousel.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; import { invokeCommand } from "@excom/neutron"; @@ -487,6 +487,52 @@ describe("content-carousel (changes from outside)", () => { }); }); +describe("content-carousel (track mode)", () => { + afterEach(() => { + document.body.innerHTML = ""; + vi.restoreAllMocks(); + }); + + // happy-dom applies no stylesheet (and misreads combinators inside `:not()`): + // the sibling facts content-carousel.css shows slides by, one at a time + const slot = (slide: Element) => + slide.matches("[is-active]") + ? 0 + : slide.matches("[is-active] + *") + ? 1 + : slide.matches(":has(+ [is-active])") + ? -1 + : "hidden"; + + const build = (active = "is-active") => + fixture( + ` + + ${"".repeat(4)} + `, + ); + + it("renders only the active slide and its neighbours", async () => { + const carousel = build(); + const slides = [...carousel.querySelectorAll("content-carousel-slide")]; + expect(slides.map(slot)).toEqual([0, 1, "hidden", "hidden", "hidden"]); + await command(carousel, "--next"); + expect(slides.map(slot)).toEqual([-1, 0, 1, "hidden", "hidden"]); + }); + + it("sets last-move on the first move, not on connect or the initial is-active", async () => { + const carousel = build(""); + const slides = carousel.querySelectorAll("content-carousel-slide"); + expect(carousel.hasAttribute("last-move")).toBe(false); + slides[0].setAttribute("is-active", ""); + await wait(0); + expect(carousel.provision).toEqual({ index: 0, count: 5, lastMove: null }); + expect(carousel.hasAttribute("last-move")).toBe(false); + await command(carousel, "--next"); + expect(carousel.getAttribute("last-move")).toBe("forward"); + }); +}); + describe("content-carousel (auto-play lifecycle)", () => { afterEach(() => { document.body.innerHTML = ""; diff --git a/packages/content-carousel/support/tests/fade.view.test.ts b/packages/content-carousel/support/tests/fade.view.test.ts index 5f10297..000f4dd 100644 --- a/packages/content-carousel/support/tests/fade.view.test.ts +++ b/packages/content-carousel/support/tests/fade.view.test.ts @@ -4,8 +4,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("fade view", () => { afterEach(() => { diff --git a/packages/content-carousel/support/tests/manual.view.test.ts b/packages/content-carousel/support/tests/manual.view.test.ts index b7a5b96..814f67a 100644 --- a/packages/content-carousel/support/tests/manual.view.test.ts +++ b/packages/content-carousel/support/tests/manual.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("manual view", () => { diff --git a/packages/content-carousel/support/tests/simple.view.test.ts b/packages/content-carousel/support/tests/simple.view.test.ts index 74deb65..76f25db 100644 --- a/packages/content-carousel/support/tests/simple.view.test.ts +++ b/packages/content-carousel/support/tests/simple.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { afterEach(() => { diff --git a/packages/content-drawer/package.json b/packages/content-drawer/package.json index dac2fbe..3202848 100644 --- a/packages/content-drawer/package.json +++ b/packages/content-drawer/package.json @@ -28,7 +28,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -44,4 +45,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/content-drawer/support/tests/content-drawer.test.ts b/packages/content-drawer/support/tests/content-drawer.test.ts index 54b2f8a..efdafda 100644 --- a/packages/content-drawer/support/tests/content-drawer.test.ts +++ b/packages/content-drawer/support/tests/content-drawer.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; import { propsFromSource } from "../../content-drawer"; import { invokeCommand } from "@excom/neutron"; diff --git a/packages/content-drawer/support/tests/disappear.view.test.ts b/packages/content-drawer/support/tests/disappear.view.test.ts index 33b3985..57f1fe0 100644 --- a/packages/content-drawer/support/tests/disappear.view.test.ts +++ b/packages/content-drawer/support/tests/disappear.view.test.ts @@ -5,13 +5,13 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("disappear view", () => { diff --git a/packages/content-drawer/support/tests/dismiss.view.test.ts b/packages/content-drawer/support/tests/dismiss.view.test.ts index 1c3aed1..dd3a66a 100644 --- a/packages/content-drawer/support/tests/dismiss.view.test.ts +++ b/packages/content-drawer/support/tests/dismiss.view.test.ts @@ -7,12 +7,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; const outsideMouseup = (target: EventTarget) => diff --git a/packages/content-drawer/support/tests/from-side.view.test.ts b/packages/content-drawer/support/tests/from-side.view.test.ts index 626d83b..bb5c3e1 100644 --- a/packages/content-drawer/support/tests/from-side.view.test.ts +++ b/packages/content-drawer/support/tests/from-side.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("from-side view", () => { diff --git a/packages/content-drawer/support/tests/simple.view.test.ts b/packages/content-drawer/support/tests/simple.view.test.ts index ea72778..a838833 100644 --- a/packages/content-drawer/support/tests/simple.view.test.ts +++ b/packages/content-drawer/support/tests/simple.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { diff --git a/packages/content-drawer/support/tests/singleton.view.test.ts b/packages/content-drawer/support/tests/singleton.view.test.ts index d1b1cde..b4256a8 100644 --- a/packages/content-drawer/support/tests/singleton.view.test.ts +++ b/packages/content-drawer/support/tests/singleton.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("singleton view", () => { diff --git a/packages/content-drawer/support/tests/stages.view.test.ts b/packages/content-drawer/support/tests/stages.view.test.ts index e08305f..a83cacd 100644 --- a/packages/content-drawer/support/tests/stages.view.test.ts +++ b/packages/content-drawer/support/tests/stages.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("stages view", () => { diff --git a/packages/content-tabs/package.json b/packages/content-tabs/package.json index dde60ea..c8ae391 100644 --- a/packages/content-tabs/package.json +++ b/packages/content-tabs/package.json @@ -27,7 +27,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -43,4 +44,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/content-tabs/support/tests/content-tabs.test.ts b/packages/content-tabs/support/tests/content-tabs.test.ts index be1b246..c9fccdc 100644 --- a/packages/content-tabs/support/tests/content-tabs.test.ts +++ b/packages/content-tabs/support/tests/content-tabs.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; /* diff --git a/packages/content-tabs/support/tests/file-tabs.view.test.ts b/packages/content-tabs/support/tests/file-tabs.view.test.ts index 30bc924..e6283e8 100644 --- a/packages/content-tabs/support/tests/file-tabs.view.test.ts +++ b/packages/content-tabs/support/tests/file-tabs.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("file-tabs view", () => { afterEach(() => { diff --git a/packages/content-tabs/support/tests/multi.view.test.ts b/packages/content-tabs/support/tests/multi.view.test.ts index 85c0880..0438d6c 100644 --- a/packages/content-tabs/support/tests/multi.view.test.ts +++ b/packages/content-tabs/support/tests/multi.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("multi view", () => { afterEach(() => { diff --git a/packages/content-tabs/support/tests/named.view.test.ts b/packages/content-tabs/support/tests/named.view.test.ts index 99774c1..c560f1a 100644 --- a/packages/content-tabs/support/tests/named.view.test.ts +++ b/packages/content-tabs/support/tests/named.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("named view", () => { afterEach(() => { diff --git a/packages/content-tabs/support/tests/simple.view.test.ts b/packages/content-tabs/support/tests/simple.view.test.ts index 9bb1eb3..4de6d96 100644 --- a/packages/content-tabs/support/tests/simple.view.test.ts +++ b/packages/content-tabs/support/tests/simple.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { afterEach(() => { diff --git a/packages/data-table/package.json b/packages/data-table/package.json index d396cfa..b3e0338 100644 --- a/packages/data-table/package.json +++ b/packages/data-table/package.json @@ -28,7 +28,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -44,4 +45,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/data-table/support/tests/data-table.test.ts b/packages/data-table/support/tests/data-table.test.ts index dce1eba..cc7383d 100644 --- a/packages/data-table/support/tests/data-table.test.ts +++ b/packages/data-table/support/tests/data-table.test.ts @@ -7,7 +7,7 @@ import { vi, wait, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; import type { DataTableSortDetail } from "../../data-table"; import { invokeCommand } from "@excom/neutron"; diff --git a/packages/data-table/support/tests/filter.view.test.ts b/packages/data-table/support/tests/filter.view.test.ts index 072ffda..0984138 100644 --- a/packages/data-table/support/tests/filter.view.test.ts +++ b/packages/data-table/support/tests/filter.view.test.ts @@ -5,8 +5,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { flush, mountView } from "@excom/quark/support/tests/view-helpers"; const isFilteredOut = (tr: HTMLElement) => tr.style.getPropertyValue("--data-tr-display") === "none"; diff --git a/packages/data-table/support/tests/simple.view.test.ts b/packages/data-table/support/tests/simple.view.test.ts index ff04d8c..30475d7 100644 --- a/packages/data-table/support/tests/simple.view.test.ts +++ b/packages/data-table/support/tests/simple.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { click, flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { click, flush, mountView } from "@excom/quark/support/tests/view-helpers"; const rowOrder = (tr: HTMLElement) => parseInt(tr.style.getPropertyValue("--data-tr-order") || "0", 10); diff --git a/packages/detect-browser/package.json b/packages/detect-browser/package.json index ed57a44..d283c7b 100644 --- a/packages/detect-browser/package.json +++ b/packages/detect-browser/package.json @@ -26,7 +26,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -42,4 +43,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/detect-browser/support/tests/detect-browser.test.ts b/packages/detect-browser/support/tests/detect-browser.test.ts index 65acd5b..d7ce8ae 100644 --- a/packages/detect-browser/support/tests/detect-browser.test.ts +++ b/packages/detect-browser/support/tests/detect-browser.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; const UA = { diff --git a/packages/detect-browser/support/tests/language.view.test.ts b/packages/detect-browser/support/tests/language.view.test.ts index 44d2313..8dc4ec3 100644 --- a/packages/detect-browser/support/tests/language.view.test.ts +++ b/packages/detect-browser/support/tests/language.view.test.ts @@ -4,8 +4,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("language view", () => { afterEach(() => { diff --git a/packages/detect-browser/support/tests/safari.view.test.ts b/packages/detect-browser/support/tests/safari.view.test.ts index c2ac8d4..e21d693 100644 --- a/packages/detect-browser/support/tests/safari.view.test.ts +++ b/packages/detect-browser/support/tests/safari.view.test.ts @@ -6,9 +6,10 @@ import { describe, expect, it, + readDemo, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; const IOS_SAFARI = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1"; diff --git a/packages/detect-browser/support/tests/simple.view.test.ts b/packages/detect-browser/support/tests/simple.view.test.ts index 1fe7d95..9912f66 100644 --- a/packages/detect-browser/support/tests/simple.view.test.ts +++ b/packages/detect-browser/support/tests/simple.view.test.ts @@ -4,8 +4,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { afterEach(() => { diff --git a/packages/detect-features/package.json b/packages/detect-features/package.json index b987dba..5666510 100644 --- a/packages/detect-features/package.json +++ b/packages/detect-features/package.json @@ -26,7 +26,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -42,4 +43,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/detect-features/support/tests/detect-features.test.ts b/packages/detect-features/support/tests/detect-features.test.ts index 7280e1c..5209055 100644 --- a/packages/detect-features/support/tests/detect-features.test.ts +++ b/packages/detect-features/support/tests/detect-features.test.ts @@ -5,7 +5,7 @@ import { expect, it, } from "@excom/heft-rig/node_modules/vitest"; -import { fixture } from "@excom/heft-rig/profiles/default/config/test-utils"; +import { fixture } from "@excom/nucleus-test"; const FEATURES = [ "geolocation", diff --git a/packages/detect-features/support/tests/share.view.test.ts b/packages/detect-features/support/tests/share.view.test.ts index f25a8fc..7bcbe2b 100644 --- a/packages/detect-features/support/tests/share.view.test.ts +++ b/packages/detect-features/support/tests/share.view.test.ts @@ -4,8 +4,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("share view", () => { afterEach(() => { diff --git a/packages/detect-features/support/tests/simple.view.test.ts b/packages/detect-features/support/tests/simple.view.test.ts index 0c43613..82fce7f 100644 --- a/packages/detect-features/support/tests/simple.view.test.ts +++ b/packages/detect-features/support/tests/simple.view.test.ts @@ -4,8 +4,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { afterEach(() => { diff --git a/packages/detect-media/package.json b/packages/detect-media/package.json index 5eb6c63..32e7ac1 100644 --- a/packages/detect-media/package.json +++ b/packages/detect-media/package.json @@ -28,6 +28,7 @@ "devDependencies": { "@excom/heft-rig": "workspace:^", "@excom/include-content": "workspace:^", + "@excom/nucleus-test": "workspace:^", "@excom/quark": "workspace:^", "@excom/quark-sheet": "workspace:^" }, @@ -45,4 +46,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/detect-media/support/tests/detect-media.test.ts b/packages/detect-media/support/tests/detect-media.test.ts index 6467cfe..6ea080c 100644 --- a/packages/detect-media/support/tests/detect-media.test.ts +++ b/packages/detect-media/support/tests/detect-media.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; import { lastFake, resetFakes, stubMatchMedia } from "./fake-match-media"; diff --git a/packages/detect-media/support/tests/fake-match-media.ts b/packages/detect-media/support/tests/fake-match-media.ts index 43d5b1d..22bba50 100644 --- a/packages/detect-media/support/tests/fake-match-media.ts +++ b/packages/detect-media/support/tests/fake-match-media.ts @@ -1,4 +1,4 @@ -import { vi } from "@excom/heft-rig/profiles/default/config/test-utils"; +import { vi } from "@excom/nucleus-test"; export type FakeMediaQueryList = { media: string; diff --git a/packages/detect-media/support/tests/pointer.view.test.ts b/packages/detect-media/support/tests/pointer.view.test.ts index ec0fce4..fd77950 100644 --- a/packages/detect-media/support/tests/pointer.view.test.ts +++ b/packages/detect-media/support/tests/pointer.view.test.ts @@ -6,9 +6,10 @@ import { describe, expect, it, + readDemo, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { flush, mountView } from "@excom/quark/support/tests/view-helpers"; import { lastFake, resetFakes, stubMatchMedia } from "./fake-match-media"; const COARSE = "(pointer: coarse)"; diff --git a/packages/detect-media/support/tests/simple.view.test.ts b/packages/detect-media/support/tests/simple.view.test.ts index 11cba88..c2464a7 100644 --- a/packages/detect-media/support/tests/simple.view.test.ts +++ b/packages/detect-media/support/tests/simple.view.test.ts @@ -4,9 +4,10 @@ import { describe, expect, it, + readDemo, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; +} from "@excom/nucleus-test"; +import { flush, mountView } from "@excom/quark/support/tests/view-helpers"; import { lastFake, resetFakes, stubMatchMedia } from "./fake-match-media"; const DARK = "(prefers-color-scheme: dark)"; diff --git a/packages/dialog-anchor/package.json b/packages/dialog-anchor/package.json index cc54d29..ee67666 100644 --- a/packages/dialog-anchor/package.json +++ b/packages/dialog-anchor/package.json @@ -28,7 +28,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -44,4 +45,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/dialog-anchor/support/tests/auto-close.view.test.ts b/packages/dialog-anchor/support/tests/auto-close.view.test.ts index d7a97eb..e277169 100644 --- a/packages/dialog-anchor/support/tests/auto-close.view.test.ts +++ b/packages/dialog-anchor/support/tests/auto-close.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("auto-close view", () => { diff --git a/packages/dialog-anchor/support/tests/dialog-anchor.test.ts b/packages/dialog-anchor/support/tests/dialog-anchor.test.ts index 8f17e37..6342695 100644 --- a/packages/dialog-anchor/support/tests/dialog-anchor.test.ts +++ b/packages/dialog-anchor/support/tests/dialog-anchor.test.ts @@ -5,7 +5,7 @@ import { fixture, it, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; describe("dialog-anchor", () => { diff --git a/packages/dialog-anchor/support/tests/is-modal.view.test.ts b/packages/dialog-anchor/support/tests/is-modal.view.test.ts index 21cd602..39a4f9e 100644 --- a/packages/dialog-anchor/support/tests/is-modal.view.test.ts +++ b/packages/dialog-anchor/support/tests/is-modal.view.test.ts @@ -4,12 +4,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("is-modal view", () => { diff --git a/packages/dialog-anchor/support/tests/simple.view.test.ts b/packages/dialog-anchor/support/tests/simple.view.test.ts index 24af1c0..2b29c3b 100644 --- a/packages/dialog-anchor/support/tests/simple.view.test.ts +++ b/packages/dialog-anchor/support/tests/simple.view.test.ts @@ -4,12 +4,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { diff --git a/packages/dismiss-watcher/dismiss-watcher.ts b/packages/dismiss-watcher/dismiss-watcher.ts index 420d09d..2cb582d 100644 --- a/packages/dismiss-watcher/dismiss-watcher.ts +++ b/packages/dismiss-watcher/dismiss-watcher.ts @@ -1,11 +1,6 @@ import { CloseWatcher } from "@excom/kit-shims"; import { selectOne } from "@excom/kit-utils"; -import { - ConstructorType, - Neutron, - TEvent, - TokenList, -} from "@excom/neutron"; +import { ConstructorType, Neutron, TEvent, TokenList } from "@excom/neutron"; export type DismissWatcherReason = "escape" | "outside-click"; diff --git a/packages/dismiss-watcher/package.json b/packages/dismiss-watcher/package.json index 2cad47d..ee54ad2 100644 --- a/packages/dismiss-watcher/package.json +++ b/packages/dismiss-watcher/package.json @@ -31,6 +31,7 @@ "@excom/content-drawer": "workspace:^", "@excom/event-handler": "workspace:^", "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^", "@excom/quark": "workspace:^", "@excom/quark-sheet": "workspace:^" }, @@ -48,4 +49,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/dismiss-watcher/support/tests/dismiss-watcher.test.ts b/packages/dismiss-watcher/support/tests/dismiss-watcher.test.ts index 81e2b70..e67679f 100644 --- a/packages/dismiss-watcher/support/tests/dismiss-watcher.test.ts +++ b/packages/dismiss-watcher/support/tests/dismiss-watcher.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; const outsideMouseup = (target: EventTarget = document.body) => diff --git a/packages/dismiss-watcher/support/tests/drawer.view.test.ts b/packages/dismiss-watcher/support/tests/drawer.view.test.ts index cd451fa..0a549eb 100644 --- a/packages/dismiss-watcher/support/tests/drawer.view.test.ts +++ b/packages/dismiss-watcher/support/tests/drawer.view.test.ts @@ -8,11 +8,11 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; const outsideMouseup = (target: EventTarget) => diff --git a/packages/dismiss-watcher/support/tests/simple.view.test.ts b/packages/dismiss-watcher/support/tests/simple.view.test.ts index 87ae538..12db0c5 100644 --- a/packages/dismiss-watcher/support/tests/simple.view.test.ts +++ b/packages/dismiss-watcher/support/tests/simple.view.test.ts @@ -6,12 +6,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; const outsideMouseup = (target: EventTarget) => diff --git a/packages/docs-site/index.html b/packages/docs-site/index.html index 5c38d22..5536ba7 100644 --- a/packages/docs-site/index.html +++ b/packages/docs-site/index.html @@ -108,6 +108,7 @@ @use "/shell" as *; + @use "quark:list" as list; #release-notice { /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ @@ -123,19 +124,24 @@ provider-fetch[api-url*="package-metas/index.json"][is-success] { $package-indices: prop("provision").body; + /* `excom.navGroup` moves a package out of its type's list, into that group */ + $ungrouped: list.reject($package-indices.packages, "navGroup"); .package-links { [bind-elements] ul:not(ul ul) { - content: iterate(getPackagesByType($package-indices, "kit-element")); + content: iterate(getPackagesByType($ungrouped, "kit-element")); } [bind-element-bases] ul:not(ul ul) { - content: iterate(getPackagesByType($package-indices, "element-base")); + content: iterate(getPackagesByType($ungrouped, "element-base")); } [bind-tools] ul:not(ul ul) { - content: iterate(getPackagesByType($package-indices, "tool")); + content: iterate(getPackagesByType($ungrouped, "tool")); } /* the standalone libraries: same rows, rendered flat (shell.css) */ [bind-libraries] ul:not(ul ul) { - content: iterate(getPackagesByType($package-indices, "library")); + content: iterate(getPackagesByType($ungrouped, "library")); + } + [bind-library-group] ul:not(ul ul) { + content: iterate(list.filter($package-indices.packages, "navGroup", "libraries")); } ul:not(ul ul) > li { $pkg: item.shortName; @@ -364,6 +370,7 @@
Packages
+
Elements @@ -372,6 +379,10 @@
Packages
Tools
+
+ Libraries + +
Element Bases @@ -506,7 +517,7 @@
Packages
- + diff --git a/packages/docs-site/package.json b/packages/docs-site/package.json index 3a5bc8e..14c08ae 100644 --- a/packages/docs-site/package.json +++ b/packages/docs-site/package.json @@ -73,6 +73,8 @@ "peerDependencies": {}, "devDependencies": { "@excom/heft-rig": "workspace:^", + "@excom/nucleus-dom": "workspace:^", + "@excom/nucleus-test": "workspace:^", "@excom/quark-formatter": "workspace:^" }, "bugs": "https://github.com/excom-dev/nucleus/issues", @@ -84,4 +86,4 @@ "documented": false, "packageType": "site" } -} +} \ No newline at end of file diff --git a/packages/docs-site/public/views/package/package.css b/packages/docs-site/public/views/package/package.css index 6d6d3a6..3f7cc35 100644 --- a/packages/docs-site/public/views/package/package.css +++ b/packages/docs-site/public/views/package/package.css @@ -59,6 +59,10 @@ } } } + + .release-notes h3 { + font-size: 1rem; + } } /* Pre-load placeholder for lazy live demos: must apply before the @@ -367,6 +371,7 @@ include-content[data-demo] { counter-reset: start-here; > li { + list-style: none; counter-increment: start-here; margin: 0 0 0.55rem; padding: 0.85rem 1rem; diff --git a/packages/docs-site/shell.ts b/packages/docs-site/shell.ts index d9093a2..f9d54fc 100644 --- a/packages/docs-site/shell.ts +++ b/packages/docs-site/shell.ts @@ -54,7 +54,7 @@ export type PackageMeta = { version: string; description?: string; peerDependencies?: Record; - excom?: { packageType?: string; documented?: boolean }; + excom?: { packageType?: string; documented?: boolean; navGroup?: string }; exports?: Record; }; demos: Record; @@ -71,6 +71,8 @@ export type PackageMeta = { export type PackageIndexEntry = { packageType: string; + /** Nav group overriding the `packageType` placement (`libraries`). */ + navGroup?: string; shortName: string; version: string; docSections?: DocSection[]; diff --git a/packages/docs-site/support/docs/CONTRIBUTING.md b/packages/docs-site/support/docs/CONTRIBUTING.md index c4ec22c..95aaa96 100644 --- a/packages/docs-site/support/docs/CONTRIBUTING.md +++ b/packages/docs-site/support/docs/CONTRIBUTING.md @@ -125,7 +125,7 @@ Pick the bump honestly: | `patch` | A fix or internal improvement with the same public contract | | `none` | Docs, demos or tests only | -The comment is published documentation, so write it for an app author who has never seen the implementation. Ask what it means to them: does it break them, fix something that annoyed them, give them something new to try? +The comment is published documentation — every type but `none` appears in the package's release notes, on the site and in the LLM docs — so write it for an app author who has never seen the implementation. Ask what it means to them: does it break them, fix something that annoyed them, give them something new to try? - Imperative mood, starting with a verb — Add, Remove, Fix an issue where, Improve, Update, Upgrade, Initial release of. - The outcome ("Searching now supports wildcards"), not the diff ("Add regex support to `SearchHelper`"). diff --git a/packages/docs-site/support/docs/INTRODUCTION.md b/packages/docs-site/support/docs/INTRODUCTION.md index 5c4b408..7ee5db1 100644 --- a/packages/docs-site/support/docs/INTRODUCTION.md +++ b/packages/docs-site/support/docs/INTRODUCTION.md @@ -16,11 +16,11 @@ Your HTML __*is*__ the app! Drop-in custom elements that each have a single resp - **Little to no JavaScript** You no longer need to write JS for the vast majority of UI cases. You may still call-out to your own pure functions for complex cases. - **Native++** It's real HTML. With a separate, synergistic guest: Quark. - **No magic** No special frameworks, build processes, rendering wizardry, or "HTML-in-my-JS" / "JS-in-my-HTML" DSLs. -- **Progressively enhanced** Drop into existing static/server-side-rendered sites. Neutron and Quark can also be used independently. +- **Progressively enhanced** Drop into existing static/server-side-rendered sites. Nucleus elements and Quark can also be used independently. - **Fully composable** Templates, templating, behavior, and custom logic are all decoupled & robust. - **Reactive State Machine** A simple, reliable, declarative syntax for your business logic - Quark. - **No reconciliation tax** No large memory copies of state/DOM to be rebuilt, diffed against the DOM, recompiled with every state change. -- **Lightweight** All Nucleus Kit elements + Quark + Neutron have a smaller footprint (just over ~50kb compressed) than some UI framework cores alone. +- **Lightweight** All Nucleus elements + Quark + Neutron have a smaller footprint (just over ~50kb compressed) than some UI framework cores alone. ## What's in the stack @@ -45,7 +45,13 @@ Every piece stands alone. Use one element on an existing site, or compose the wh Content-rich sites, complex data-driven business rules, progressive enhancement of static/server-rendered pages, embedded user experiences. See [Limitations](/nucleus/docs/limitations) for the edges. -The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before: zero-build-tool UIs (e.g. on-the-fly generation), declarative & auditable target for LLM UI building, plain text assembly to rich UX (like a CMS), incremental upgrading of static/legacy SSR sites, resource-constrained web UIs (especially where scripting needs to be validated or limited, like an ATM), embedded UX (such as upgrading markdown with embedded functionality). +The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before: +- zero-build-tool UIs (e.g. on-the-fly generation) +- declarative & auditable target for LLM UI building +- programmatic assembly of rich UX (like a CMS) +- incremental upgrading of static/legacy SSR sites +- resource-constrained, secure, or sandboxed web UIs (especially where scripting needs to be validated or limited, like an ATM) +- embedded UX (such as upgrading markdown with embedded functionality) ## Dogfood is nutritious diff --git a/packages/docs-site/support/docs/ROADMAP.md b/packages/docs-site/support/docs/ROADMAP.md index 64e84cf..d9dedce 100644 --- a/packages/docs-site/support/docs/ROADMAP.md +++ b/packages/docs-site/support/docs/ROADMAP.md @@ -20,8 +20,8 @@ Until then: **pin your versions**, and read the change notes before you upgrade. Work that is done or nearly done, landing in the next releases. -- **SSR** Will involve reusing happy-dom test setup for SSR. -- **Releasing the testing library** It currently exists internally, as happy-dom plus modifications. It will be packaged up and released publicly. +- **SSR** `@excom/nucleus-dom` supplies the per-request DOM (happy-dom with browser-parity shims); the render path on top of it is next. +- **The testing packages** `@excom/nucleus-dom` and `@excom/nucleus-test` (Vitest setup, matchers, helpers), extracted from the internal rig, land in the next release. - **More elements in the catalog.** May include some of the following elements. Interaction: `content-sortable`, `content-splitter`, `content-popover`, `super-file-input`. Actions: `clipboard-copy`, `web-share`, `file-download`. Sensors: `detect-visibility`, `detect-size`, `detect-scroll`, `detect-document`, `detect-permission`. Providers: `provider-url-params`, `provider-event-source`, `provider-websocket`, `provider-worker`. - **Polyfills** Some features will be broken for certain browsers whose versions are older than a year (mainly Firefox & Safari, mid-2025). This will be remedied in the first stable release. - **The small-bug backlog.** A coverage sweep across every package surfaced a list of minor defects. They are being triaged and fixed ahead of a stable release. diff --git a/packages/docs-site/support/tests/cells-app.test.ts b/packages/docs-site/support/tests/cells-app.test.ts index 45b3c58..7f1da53 100644 --- a/packages/docs-site/support/tests/cells-app.test.ts +++ b/packages/docs-site/support/tests/cells-app.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { commit, evalCell, range } from "../../public/views/cells-app/cells-app"; /** A tiny grid: one `` per ref. */ diff --git a/packages/docs-site/support/tests/cells-app.view.test.ts b/packages/docs-site/support/tests/cells-app.view.test.ts index 9503820..a90e5cf 100644 --- a/packages/docs-site/support/tests/cells-app.view.test.ts +++ b/packages/docs-site/support/tests/cells-app.view.test.ts @@ -13,7 +13,7 @@ import { it, vi, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/packages/docs-site/support/tests/company.view.test.ts b/packages/docs-site/support/tests/company.view.test.ts index 2eb0c8e..646aca9 100644 --- a/packages/docs-site/support/tests/company.view.test.ts +++ b/packages/docs-site/support/tests/company.view.test.ts @@ -6,16 +6,16 @@ import { expect, fixture, it, + readFileRelative, spyFetch, vi, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { flush, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/company/company.html", ); diff --git a/packages/docs-site/support/tests/counter-app.view.test.ts b/packages/docs-site/support/tests/counter-app.view.test.ts index 1fa688c..20624ce 100644 --- a/packages/docs-site/support/tests/counter-app.view.test.ts +++ b/packages/docs-site/support/tests/counter-app.view.test.ts @@ -4,18 +4,18 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readFileRelative, +} from "@excom/nucleus-test"; import { click, expectComplexity, flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile(import.meta.url, "../../public/views/counter-app/counter-app.html"); -const quarkSrc = readViewFile( +const html = readFileRelative(import.meta.url, "../../public/views/counter-app/counter-app.html"); +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/counter-app/counter-app.quark", ); diff --git a/packages/docs-site/support/tests/demo-utils.test.ts b/packages/docs-site/support/tests/demo-utils.test.ts index a8981b9..6ec4133 100644 --- a/packages/docs-site/support/tests/demo-utils.test.ts +++ b/packages/docs-site/support/tests/demo-utils.test.ts @@ -5,7 +5,7 @@ import { expect, it, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { DEMO_FLAGS, DEMO_STORAGE_KEY, diff --git a/packages/docs-site/support/tests/flight-booker-app.view.test.ts b/packages/docs-site/support/tests/flight-booker-app.view.test.ts index b80788e..264d008 100644 --- a/packages/docs-site/support/tests/flight-booker-app.view.test.ts +++ b/packages/docs-site/support/tests/flight-booker-app.view.test.ts @@ -7,22 +7,22 @@ import { describe, expect, it, + readFileRelative, spyFetch, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { expectComplexity, flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/flight-booker-app/flight-booker-app.html", ); -const quarkSrc = readViewFile( +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/flight-booker-app/flight-booker-app.quark", ); diff --git a/packages/docs-site/support/tests/live-app.view.test.ts b/packages/docs-site/support/tests/live-app.view.test.ts index 07490d0..2f479cf 100644 --- a/packages/docs-site/support/tests/live-app.view.test.ts +++ b/packages/docs-site/support/tests/live-app.view.test.ts @@ -11,10 +11,11 @@ import { describe, expect, it, + readFileRelative, vi, wait, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { Quark } from "@excom/quark"; import { bypassSelectorCache, @@ -22,11 +23,10 @@ import { expectComplexity, flush, measureComplexity, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile(import.meta.url, "../../public/views/live-app/live-app.html"); -const quarkSrc = readViewFile( +const html = readFileRelative(import.meta.url, "../../public/views/live-app/live-app.html"); +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/live-app/live-app.quark", ); @@ -147,9 +147,18 @@ describe("live-app view", () => { beforeEach(() => { calls.length = 0; overrides.clear(); - // happy-dom would try to navigate the preview iframe - const settings = (window as any).happyDOM?.settings; - if (settings) settings.disableIframePageLoading = true; + // happy-dom 20.x throws when an iframe connects to the document; + // suppress it so the rest of the test (which never needs the iframe + // content) proceeds normally. + const origAppendChild = Element.prototype.appendChild; + vi.spyOn(Element.prototype, "appendChild").mockImplementation(function (this: Element, node: Node) { + try { + return origAppendChild.call(this, node); + } catch (e) { + if (e instanceof DOMException && e.message.includes("Iframe")) return node as any; + throw e; + } + }); Quark.moduleLoader = async (url: string) => { if (url.includes("shell")) return shellStub; throw new Error(`unexpected @use module: ${url}`); diff --git a/packages/docs-site/support/tests/not-found.view.test.ts b/packages/docs-site/support/tests/not-found.view.test.ts index e32d55f..7774d1f 100644 --- a/packages/docs-site/support/tests/not-found.view.test.ts +++ b/packages/docs-site/support/tests/not-found.view.test.ts @@ -7,19 +7,19 @@ import { describe, expect, it, + readFileRelative, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { bypassSelectorCache, flush, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/not-found/not-found.html", ); -const quarkSrc = readViewFile( +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/not-found/not-found.quark", ); diff --git a/packages/docs-site/support/tests/package.view.test.ts b/packages/docs-site/support/tests/package.view.test.ts index d4cc297..96791e0 100644 --- a/packages/docs-site/support/tests/package.view.test.ts +++ b/packages/docs-site/support/tests/package.view.test.ts @@ -9,14 +9,14 @@ import { describe, expect, it, + readFileRelative, vi, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { Quark } from "@excom/quark"; import { bypassSelectorCache, flush, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; import { renderMarkdown } from "@excom/heft-rig/scripts/render-markdown.mjs"; import { @@ -28,11 +28,11 @@ import { type PackageMeta, } from "../../shell"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/package/package.html", ); -const quarkSrc = readViewFile( +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/package/package.quark", ); @@ -49,7 +49,7 @@ const shellStub = { /** One `## ` section of a site guide, rendered like the site meta. */ const guideSection = (file: string, heading: string) => { - const md = readViewFile(import.meta.url, `../docs/${file}`); + const md = readFileRelative(import.meta.url, `../docs/${file}`); const section = md .split(/\n(?=## )/) .find((s) => s.startsWith(`## ${heading}\n`)); @@ -121,6 +121,18 @@ const siteMeta = { exportedFiles: {}, }; +/** The closing details `build-package-metas` appends to a README with release notes. */ +const releaseNotes = + '
\nRelease notes\n' + + "

1.0.0

\n
    \n
  • Add x
  • \n
\n" + + "
\n"; + +const notedMeta: PackageMeta = { + ...neutronMeta, + shortName: "noted", + readme: `${neutronMeta.readme}${releaseNotes}`, +}; + const requested: string[] = []; const mockFetch = () => @@ -129,9 +141,11 @@ const mockFetch = () => requested.push(url); const body = url.endsWith("/neutron.json") ? neutronMeta - : url.endsWith("/docs-site.json") - ? siteMeta - : null; + : url.endsWith("/noted.json") + ? notedMeta + : url.endsWith("/docs-site.json") + ? siteMeta + : null; return new Response(JSON.stringify(body), { status: body ? 200 : 404, headers: { "content-type": "application/json" }, @@ -319,6 +333,29 @@ describe("package view", () => { expect(page.querySelector(".md-content")?.innerHTML).toBe(""); }); + it("ends the README article with the release notes its meta carries, closed", async () => { + const { page } = await mountPage({ packageName: "noted" }); + + const notes = page.querySelector( + ".md-content > details.release-notes:last-child", + )!; + expect(notes.open).toBe(false); + expect(notes.querySelector("summary")?.textContent).toBe("Release notes"); + expect(notes.querySelector("h3")?.textContent).toBe("1.0.0 2026-09-30"); + expect(notes.querySelector("li")?.innerHTML).toBe("Add x"); + expect(page.querySelectorAll(".release-notes")).toHaveLength(1); + }); + + it("keeps release notes off the doc pages of that package", async () => { + const { page } = await mountPage({ + packageName: "noted", + docName: "props", + }); + + expect(page.querySelector(".md-content h1")?.textContent).toBe("Props"); + expect(page.querySelector(".release-notes")).toBeNull(); + }); + it("marks guide links the router has a past visit to", async () => { const quickStart = "/nucleus/docs/quick_start"; const styling = "/nucleus/docs/styling"; diff --git a/packages/docs-site/support/tests/release-notice.view.test.ts b/packages/docs-site/support/tests/release-notice.view.test.ts index a20b3a4..8146270 100644 --- a/packages/docs-site/support/tests/release-notice.view.test.ts +++ b/packages/docs-site/support/tests/release-notice.view.test.ts @@ -8,17 +8,17 @@ import { expect, fixture, it, + readFileRelative, spyFetch, vi, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { flush, mountView as mountWithSheet, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/release-notice/release-notice.html", ); @@ -27,7 +27,7 @@ const stripAssets = (s: string) => s.replace(//g, ""); /** The `#release-notice { … }` rule of the shell sheet in `index.html`. */ const shellRule = () => { - const shell = readViewFile(import.meta.url, "../../index.html"); + const shell = readFileRelative(import.meta.url, "../../index.html"); const start = shell.indexOf("#release-notice {"); let depth = 0; for (let i = shell.indexOf("{", start); i < shell.length; i++) { diff --git a/packages/docs-site/support/tests/returns-app.view.test.ts b/packages/docs-site/support/tests/returns-app.view.test.ts index 05cbb61..336d0b8 100644 --- a/packages/docs-site/support/tests/returns-app.view.test.ts +++ b/packages/docs-site/support/tests/returns-app.view.test.ts @@ -8,23 +8,23 @@ import { describe, expect, it, + readFileRelative, spyFetch, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { bypassSelectorCache, expectComplexity, flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/returns-app/returns-app.html" ); -const quarkSrc = readViewFile( +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/returns-app/returns-app.quark" ); diff --git a/packages/docs-site/support/tests/shell.test.ts b/packages/docs-site/support/tests/shell.test.ts index b7594b8..2aa5a1e 100644 --- a/packages/docs-site/support/tests/shell.test.ts +++ b/packages/docs-site/support/tests/shell.test.ts @@ -4,11 +4,11 @@ import { describe, expect, it, + readFileRelative, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { readdirSync, readFileSync } from "node:fs"; import { KitRoute, KitRouter } from "@excom/kit-router"; -import { readViewFile } from "@excom/quark/support/tests/view-helpers"; import { createRequire } from "node:module"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -614,7 +614,7 @@ type SiteRoute = { const siteRoutes = (): SiteRoute[] => { // ``s are dropped: happy-dom would try to fetch them while parsing. - const shell = readViewFile(import.meta.url, "../../index.html").replace( + const shell = readFileRelative(import.meta.url, "../../index.html").replace( //g, "" ); @@ -660,7 +660,7 @@ describe("site route table", () => { /* The two areas differ only by the company route's title: every docs route is untitled, so `spa-manager` puts `index.html`'s own `` back. */ it("carries the company title on the company route alone", () => { - const shell = readViewFile(import.meta.url, "../../index.html"); + const shell = readFileRelative(import.meta.url, "../../index.html"); expect(shell).toContain("<title>Nucleus · docs"); expect(routes[0]!.documentTitle).toBe("Excom"); expect(routes.filter((r) => r.documentTitle)).toHaveLength(1); diff --git a/packages/docs-site/support/tests/site-nav.view.test.ts b/packages/docs-site/support/tests/site-nav.view.test.ts new file mode 100644 index 0000000..287041d --- /dev/null +++ b/packages/docs-site/support/tests/site-nav.view.test.ts @@ -0,0 +1,230 @@ +import "@excom/include-content"; +import "@excom/provider-fetch"; +import "@excom/quark-sheet"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + readFileRelative, + vi, + waitForEvent, +} from "@excom/nucleus-test"; +import { Quark } from "@excom/quark"; +import { + bypassSelectorCache, + flush, +} from "@excom/quark/support/tests/view-helpers"; +import { + displayName, + getPackagesByType, + type PackageIndexEntry, +} from "../../shell"; + +const shell = readFileRelative(import.meta.url, "../../index.html"); +// ``s are dropped: happy-dom would try to fetch them while parsing. +const page = new DOMParser().parseFromString( + shell.replace(//g, ""), + "text/html" +); + +/** A `selector { … }` rule of the shell sheet, braces balanced. */ +const shellRule = (selector: string) => { + const start = shell.indexOf(`${selector} {`); + let depth = 0; + for (let i = shell.indexOf("{", start); start >= 0 && i < shell.length; i++) { + depth += shell[i] === "{" ? 1 : shell[i] === "}" ? -1 : 0; + if (!depth) return shell.slice(start, i + 1); + } + throw new Error(`index.html has no ${selector} rule`); +}; + +// happy-dom matches nothing for `:not(ul ul)`; a tree's root is the `ul` +// straight under its `include-content` +const sheetSrc = [ + ...(shell.match(/^\s*@use .+;$/gm) ?? []), + shellRule('provider-fetch[api-url*="package-metas/index.json"][is-success]'), + shellRule("[data-site-nav]"), +] + .join("\n") + .replaceAll("ul:not(ul ul)", "include-content > ul"); + +/** The desktop aside and the mobile sheet both stamp the one nav template. */ +const stampers = page.querySelectorAll( + 'include-content[template-ref="#template-site-nav"]' +).length; + +type NavEntry = PackageIndexEntry & { navGroup?: string }; + +const entry = ( + shortName: string, + packageType: string, + navGroup?: string +): NavEntry => ({ + shortName, + packageType, + version: "1.0.0", + ...(navGroup ? { navGroup } : {}), +}); + +const index = { + packages: [ + entry("content-tabs", "kit-element"), + entry("fetchable-element", "element-base"), + entry("neutron", "library"), + entry("nucleus-dom", "library", "libraries"), + entry("nucleus-kit", "library"), + entry("nucleus-quark-highlighter", "tool"), + entry("nucleus-test", "library", "libraries"), + entry("quark", "library"), + entry("quark-formatter", "tool", "libraries"), + entry("valence", "library"), + ], + docs: [], +}; + +const GROUPED = ["nucleus-dom", "nucleus-test", "quark-formatter"]; + +const originalLoader = Quark.moduleLoader; + +const mountNav = async () => { + vi.spyOn(globalThis, "fetch").mockImplementation( + async () => + new Response(JSON.stringify(index), { + status: 200, + headers: { "content-type": "application/json" }, + }) + ); + const host = document.createElement("div"); + host.innerHTML = ` + + ${page.querySelector("#template-site-nav")!.outerHTML} + ${page.querySelector("#template-tree")!.outerHTML} + ${"
".repeat(stampers)} +
`; + const sheet = host.querySelector("quark-sheet")!; + sheet.textContent = sheetSrc; + const provider = host.querySelector("provider-fetch")!; + document.body.append(host); + // happy-dom connects an `include-content` nested in another one's output + // before it is attached, so the stamping is done here + const nav = host.querySelector("#template-site-nav")!; + host.querySelectorAll("[data-stamp]").forEach((el) => { + el.append(document.importNode(nav.content, true)); + }); + await waitForEvent(provider, "provider-fetch-success"); + for (let i = 0; i < 6; i++) await flush(); + return [...host.querySelectorAll("[data-site-nav]")]; +}; + +const linksOf = (group: Element | null) => + [...(group?.querySelectorAll("spa-a") ?? [])].map((a) => ({ + text: a.textContent, + href: a.getAttribute("route-href"), + })); + +const hrefs = (names: string[]) => names.map((n) => `/nucleus/packages/${n}`); + +describe("site nav packages", () => { + let restoreSelectors: () => void; + + beforeEach(() => { + restoreSelectors = bypassSelectorCache(); + Quark.moduleLoader = async (url: string) => { + if (url.includes("shell")) return { displayName, getPackagesByType }; + throw new Error(`unexpected @use module: ${url}`); + }; + }); + + afterEach(() => { + document.body.innerHTML = ""; + restoreSelectors(); + Quark.moduleLoader = originalLoader; + vi.restoreAllMocks(); + }); + + it("is one template, stamped by the desktop aside and the mobile sheet", async () => { + expect(stampers).toBe(2); + expect(await mountNav()).toHaveLength(2); + }); + + it("lists the libraries without a nav group at the top level", async () => { + for (const nav of await mountNav()) { + expect( + linksOf(nav.querySelector("[bind-libraries]")).map((l) => l.text) + ).toEqual(["Neutron", "Nucleus Kit", "Quark", "Valence.css"]); + } + }); + + it("puts every nav-group package in a collapsed Libraries group after the top level", async () => { + for (const nav of await mountNav()) { + const order = [...nav.querySelector(".package-links")!.children].map( + (el) => + el.getAttributeNames().find((n) => n.startsWith("bind-")) ?? + el.tagName.toLowerCase() + ); + expect(order).toEqual([ + "hgroup", + "bind-libraries", + "bind-elements", + "bind-tools", + "bind-library-group", + "bind-element-bases", + ]); + + const group = nav.querySelector("details[bind-library-group]")!; + expect(group.querySelector(":scope > summary")?.textContent).toBe( + "Libraries" + ); + expect(group.hasAttribute("open")).toBe(false); + expect(linksOf(group)).toEqual( + GROUPED.map((name) => ({ + text: name, + href: `/nucleus/packages/${name}`, + })) + ); + } + }); + + it("keeps the nav-group packages out of the top level and Tools", async () => { + for (const nav of await mountNav()) { + const top = linksOf(nav.querySelector("[bind-libraries]")); + const tools = linksOf(nav.querySelector("[bind-tools]")); + expect(tools.map((l) => l.href)).toEqual( + hrefs(["nucleus-quark-highlighter"]) + ); + for (const href of hrefs(GROUPED)) { + expect(top.map((l) => l.href)).not.toContain(href); + expect(tools.map((l) => l.href)).not.toContain(href); + } + } + }); + + it("leaves Elements and Element Bases as they were", async () => { + for (const nav of await mountNav()) { + expect(linksOf(nav.querySelector("[bind-elements]"))).toEqual([ + { text: "content-tabs", href: "/nucleus/packages/content-tabs" }, + ]); + expect(linksOf(nav.querySelector("[bind-element-bases]"))).toEqual([ + { + text: "fetchable-element", + href: "/nucleus/packages/fetchable-element", + }, + ]); + } + }); + + it("opens the group while one of its pages is active", async () => { + const [nav] = await mountNav(); + const group = nav!.querySelector("details[bind-library-group]")!; + expect(group.hasAttribute("open")).toBe(false); + + group + .querySelector('spa-a[route-href="/nucleus/packages/nucleus-dom"]')! + .setAttribute("is-active", ""); + for (let i = 0; i < 3; i++) await flush(); + + expect(group.hasAttribute("open")).toBe(true); + }); +}); diff --git a/packages/docs-site/support/tests/temperature-app.view.test.ts b/packages/docs-site/support/tests/temperature-app.view.test.ts index e5fa111..f5ce0b7 100644 --- a/packages/docs-site/support/tests/temperature-app.view.test.ts +++ b/packages/docs-site/support/tests/temperature-app.view.test.ts @@ -6,19 +6,19 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readFileRelative, +} from "@excom/nucleus-test"; import { expectComplexity, flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile( +const html = readFileRelative( import.meta.url, "../../public/views/temperature-app/temperature-app.html", ); -const quarkSrc = readViewFile( +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/temperature-app/temperature-app.quark", ); diff --git a/packages/docs-site/support/tests/timer-app.view.test.ts b/packages/docs-site/support/tests/timer-app.view.test.ts index 3e86e90..9da888a 100644 --- a/packages/docs-site/support/tests/timer-app.view.test.ts +++ b/packages/docs-site/support/tests/timer-app.view.test.ts @@ -5,18 +5,18 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readFileRelative, +} from "@excom/nucleus-test"; import { click, expectComplexity, flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile(import.meta.url, "../../public/views/timer-app/timer-app.html"); -const quarkSrc = readViewFile( +const html = readFileRelative(import.meta.url, "../../public/views/timer-app/timer-app.html"); +const quarkSrc = readFileRelative( import.meta.url, "../../public/views/timer-app/timer-app.quark", ); diff --git a/packages/docs-site/support/tests/todo-app.view.test.ts b/packages/docs-site/support/tests/todo-app.view.test.ts index ca47027..04299f1 100644 --- a/packages/docs-site/support/tests/todo-app.view.test.ts +++ b/packages/docs-site/support/tests/todo-app.view.test.ts @@ -6,19 +6,19 @@ import { describe, expect, it, + readFileRelative, spyFetch, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { expectComplexity, flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile(import.meta.url, "../../public/views/todo-app/todo-app.html"); -const quarkSrc = readViewFile(import.meta.url, "../../public/views/todo-app/todo-app.quark"); +const html = readFileRelative(import.meta.url, "../../public/views/todo-app/todo-app.html"); +const quarkSrc = readFileRelative(import.meta.url, "../../public/views/todo-app/todo-app.quark"); const todos = [ { id: 1, title: "Buy milk", completed: false }, diff --git a/packages/docs-site/support/tests/view-transitions-app.view.test.ts b/packages/docs-site/support/tests/view-transitions-app.view.test.ts index 94fb2f8..e574107 100644 --- a/packages/docs-site/support/tests/view-transitions-app.view.test.ts +++ b/packages/docs-site/support/tests/view-transitions-app.view.test.ts @@ -7,7 +7,8 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readFileRelative, +} from "@excom/nucleus-test"; import { installViewTransitionStub } from "@excom/quark/support/tests/helpers"; import { bypassSelectorCache, @@ -16,11 +17,10 @@ import { flush, measureComplexity, mountView, - readViewFile, } from "@excom/quark/support/tests/view-helpers"; -const html = readViewFile(import.meta.url, "../../public/views/view-transitions-app/view-transitions-app.html"); -const quarkSrc = readViewFile(import.meta.url, "../../public/views/view-transitions-app/view-transitions-app.quark"); +const html = readFileRelative(import.meta.url, "../../public/views/view-transitions-app/view-transitions-app.html"); +const quarkSrc = readFileRelative(import.meta.url, "../../public/views/view-transitions-app/view-transitions-app.quark"); /** Past the first render, whose paints never transition. */ const settle = async () => { diff --git a/packages/dom-observer/package.json b/packages/dom-observer/package.json index fe84da8..3433648 100644 --- a/packages/dom-observer/package.json +++ b/packages/dom-observer/package.json @@ -27,7 +27,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -45,4 +46,4 @@ "documented": true, "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/dom-observer/support/tests/dom-observer.test.ts b/packages/dom-observer/support/tests/dom-observer.test.ts index b21ba81..e1b97f1 100644 --- a/packages/dom-observer/support/tests/dom-observer.test.ts +++ b/packages/dom-observer/support/tests/dom-observer.test.ts @@ -7,7 +7,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; describe("dom-observer", () => { diff --git a/packages/dom-observer/support/tests/simple.view.test.ts b/packages/dom-observer/support/tests/simple.view.test.ts index f38c3c7..31e35ee 100644 --- a/packages/dom-observer/support/tests/simple.view.test.ts +++ b/packages/dom-observer/support/tests/simple.view.test.ts @@ -5,13 +5,13 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { expectComplexity, flush, measureComplexity, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("simple view", () => { diff --git a/packages/event-handler/package.json b/packages/event-handler/package.json index 833b8ef..db30083 100644 --- a/packages/event-handler/package.json +++ b/packages/event-handler/package.json @@ -28,7 +28,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -44,4 +45,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/event-handler/support/tests/command-dialog.view.test.ts b/packages/event-handler/support/tests/command-dialog.view.test.ts index 321d202..69c7cdb 100644 --- a/packages/event-handler/support/tests/command-dialog.view.test.ts +++ b/packages/event-handler/support/tests/command-dialog.view.test.ts @@ -4,12 +4,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("command-dialog view", () => { diff --git a/packages/event-handler/support/tests/debounce.view.test.ts b/packages/event-handler/support/tests/debounce.view.test.ts index 673e9ef..4a2d024 100644 --- a/packages/event-handler/support/tests/debounce.view.test.ts +++ b/packages/event-handler/support/tests/debounce.view.test.ts @@ -5,14 +5,14 @@ import { describe, expect, it, + readDemo, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { expectComplexity, flush, measureComplexity, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("debounce view", () => { diff --git a/packages/event-handler/support/tests/enter-close.view.test.ts b/packages/event-handler/support/tests/enter-close.view.test.ts index e5f65d8..90295a5 100644 --- a/packages/event-handler/support/tests/enter-close.view.test.ts +++ b/packages/event-handler/support/tests/enter-close.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("enter-close view", () => { diff --git a/packages/event-handler/support/tests/escape-close.view.test.ts b/packages/event-handler/support/tests/escape-close.view.test.ts index ffd4776..632ceac 100644 --- a/packages/event-handler/support/tests/escape-close.view.test.ts +++ b/packages/event-handler/support/tests/escape-close.view.test.ts @@ -4,12 +4,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("escape-close view", () => { diff --git a/packages/event-handler/support/tests/event-handler.form-fallback.test.ts b/packages/event-handler/support/tests/event-handler.form-fallback.test.ts index 3eef231..1eee7a3 100644 --- a/packages/event-handler/support/tests/event-handler.form-fallback.test.ts +++ b/packages/event-handler/support/tests/event-handler.form-fallback.test.ts @@ -5,7 +5,7 @@ import { expect, fixture, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; /* * `mutate-target` guards a form that yields no data. `formToJson` always diff --git a/packages/event-handler/support/tests/event-handler.test.ts b/packages/event-handler/support/tests/event-handler.test.ts index 284a97e..5f16232 100644 --- a/packages/event-handler/support/tests/event-handler.test.ts +++ b/packages/event-handler/support/tests/event-handler.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; if (!navigator.vibrate) { diff --git a/packages/event-handler/support/tests/fire-event.view.test.ts b/packages/event-handler/support/tests/fire-event.view.test.ts index 929dfef..130c024 100644 --- a/packages/event-handler/support/tests/fire-event.view.test.ts +++ b/packages/event-handler/support/tests/fire-event.view.test.ts @@ -5,14 +5,14 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, expectComplexity, flush, measureComplexity, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("fire-event view", () => { diff --git a/packages/event-handler/support/tests/prevent-default.view.test.ts b/packages/event-handler/support/tests/prevent-default.view.test.ts index 45d8d43..5146878 100644 --- a/packages/event-handler/support/tests/prevent-default.view.test.ts +++ b/packages/event-handler/support/tests/prevent-default.view.test.ts @@ -4,8 +4,9 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; -import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers"; + readDemo, +} from "@excom/nucleus-test"; +import { mountView } from "@excom/quark/support/tests/view-helpers"; describe("prevent-default view", () => { afterEach(() => { diff --git a/packages/event-handler/support/tests/retarget.view.test.ts b/packages/event-handler/support/tests/retarget.view.test.ts index 51f25d0..60f5435 100644 --- a/packages/event-handler/support/tests/retarget.view.test.ts +++ b/packages/event-handler/support/tests/retarget.view.test.ts @@ -5,14 +5,14 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, expectComplexity, flush, measureComplexity, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("retarget view", () => { diff --git a/packages/event-handler/support/tests/with-as.view.test.ts b/packages/event-handler/support/tests/with-as.view.test.ts index c7d6ae7..49126a7 100644 --- a/packages/event-handler/support/tests/with-as.view.test.ts +++ b/packages/event-handler/support/tests/with-as.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, -} from "@excom/heft-rig/profiles/default/config/test-utils"; + readDemo, +} from "@excom/nucleus-test"; import { click, flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; describe("with-as view", () => { diff --git a/packages/fetchable-element/package.json b/packages/fetchable-element/package.json index 18b727e..db48646 100644 --- a/packages/fetchable-element/package.json +++ b/packages/fetchable-element/package.json @@ -30,7 +30,8 @@ }, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -48,4 +49,4 @@ "excom": { "packageType": "element-base" } -} +} \ No newline at end of file diff --git a/packages/fetchable-element/support/tests/fetch-lifecycle.test.ts b/packages/fetchable-element/support/tests/fetch-lifecycle.test.ts index 102a53e..830ee0d 100644 --- a/packages/fetchable-element/support/tests/fetch-lifecycle.test.ts +++ b/packages/fetchable-element/support/tests/fetch-lifecycle.test.ts @@ -9,7 +9,7 @@ import { vi, wait, waitForEvent, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { KitLogger } from "@excom/kit-logger"; const TAG = "fetchable-lifecycle-test"; diff --git a/packages/fetchable-element/support/tests/fetchable-element.test.ts b/packages/fetchable-element/support/tests/fetchable-element.test.ts index a72d72d..f3429c2 100644 --- a/packages/fetchable-element/support/tests/fetchable-element.test.ts +++ b/packages/fetchable-element/support/tests/fetchable-element.test.ts @@ -6,7 +6,7 @@ import { fixture, it, vi, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { KitLogger } from "@excom/kit-logger"; const TAG = "fetchable-element-test"; diff --git a/packages/gesture-handler/gesture-handler.ts b/packages/gesture-handler/gesture-handler.ts index 8248032..ad6e709 100644 --- a/packages/gesture-handler/gesture-handler.ts +++ b/packages/gesture-handler/gesture-handler.ts @@ -118,6 +118,8 @@ interface Session { /** Pointer ids captured since recognition */ captured: Set; longPressed: boolean; + /** `pan-x` / `pan-y`: native scroll cancelled for this touch; `null` unjudged */ + scrollLock: boolean | null; type: GestureType | null; axis: Vec; rangePx: number; @@ -152,6 +154,7 @@ interface Methods { _handleMove: (e: PointerEvent) => void; _handleUp: (e: PointerEvent) => void; _handleHandoff: EventListener; + _lockScroll: EventListener; } /** @@ -190,8 +193,8 @@ const OPPOSITE: Record = { const PAN_TYPES = ["pan", "pan-x", "pan-y"]; const MULTI_TYPES: GestureType[] = ["pinch", "rotate"]; const VELOCITY_WINDOW_MS = 100; -/** Travel (px) before a `handoff-ref` move is judged */ -const HANDOFF_MIN_PX = 3; +/** Travel (px) before a first touch move is judged: handoff, pan scroll lock */ +const FIRST_MOVE_PX = 3; /** Slack (px) still counted as at scroll limit */ const LIMIT_EPSILON = 1; /** Computed `overflow-*` of an element the user can scroll */ @@ -320,9 +323,10 @@ export const GestureHandler = Neutron({ // options /** * @option - * Gestures to recognize. `pan-x` / `pan-y` one axis (other stays native - * scroll); `pan` both; `pinch` / `rotate` need two fingers; `swipe` - * velocity on release; `tap` / `double-tap` / `long-press` fire events. + * Gestures to recognize. `pan-x` / `pan-y` one axis (a touch starting + * along it holds the page still, one across it scrolls); `pan` both; + * `pinch` / `rotate` need two fingers; `swipe` velocity on release; + * `tap` / `double-tap` / `long-press` fire events. * @values pan | pan-x | pan-y | pinch | rotate | swipe | tap | double-tap | long-press * @default pan */ @@ -684,7 +688,41 @@ GestureHandler.defineMethods({ * opens a session. */ _handleHandoff: (element, event: Event) => handoffEffects(element, event), + /** + * `pan-x` / `pan-y` `touchmove`: a first move along the pan axis cancels + * native scroll for the rest of the touch, before `threshold-px`, until + * the pan is rejected. iOS would otherwise scroll the page across the + * axis mid-swipe. + */ + _lockScroll: (element, event: Event) => { + const { _session: s, gestureTypes } = element; + const { targetTouches, cancelable, target } = event as TouchEvent; + if (s?.scrollLock === null && s.armed && targetTouches.length === 1) { + const dx = s.acc[0] + targetTouches[0].clientX - s.base[0]; + const dy = s.acc[1] + targetTouches[0].clientY - s.base[1]; + if (Math.hypot(dx, dy) < FIRST_MOVE_PX) return; // too early to tell + const along = gestureTypes.includes( + Math.abs(dx) >= Math.abs(dy) ? "pan-x" : "pan-y" + ); + const direction = dominantDirection(dx, dy); + // A scroller under the finger with room that way keeps the touch + if (along && canScroll(element, target as Element, direction)) return; + s.scrollLock = !s.rejected && along; + } + if (s?.scrollLock && !s.rejected && cancelable) event.preventDefault(); + }, }) + .onPropChanged("gestureTypes", ({ gestureTypes, _lockScroll }) => ({ + toggleListeners: [ + [ + "touchmove", + _lockScroll, + gestureTypes.some((type) => type === "pan-x" || type === "pan-y"), + // Only a non-passive `touchmove` can stop the page scrolling + { passive: false }, + ], + ], + })) .onPropSet("handoffRef", ({ _handleHandoff }) => ({ addListeners: HANDOFF_EVENTS.map((name) => [ name, @@ -892,7 +930,7 @@ function handsOver(element: El, target: Element, dx: number, dy: number) { const [ax, ay] = AXES[forward]; const along = dx * ax + dy * ay; const across = Math.abs(dx * ay - dy * ax); - if (Math.abs(along) < HANDOFF_MIN_PX || Math.abs(along) <= across) { + if (Math.abs(along) < FIRST_MOVE_PX || Math.abs(along) <= across) { return false; } const { progressOffset, progressMin, progressMax } = element; @@ -919,7 +957,7 @@ function handoffEffects(element: El, event: Event): unknown[] | undefined { } const dx = pointer.clientX - h.x; const dy = pointer.clientY - h.y; - if (Math.hypot(dx, dy) < HANDOFF_MIN_PX) return; // too early to tell + if (Math.hypot(dx, dy) < FIRST_MOVE_PX) return; // too early to tell if (!handsOver(element, h.target, dx, dy)) { return [{ _handoff: null }]; // container scrolls; leave it } @@ -968,6 +1006,7 @@ function createSession(element: El, pointerType: string): Session { rejected: false, captured: new Set(), longPressed: false, + scrollLock: null, type: null, axis, rangePx: range, diff --git a/packages/gesture-handler/package.json b/packages/gesture-handler/package.json index 2430f09..7902dc1 100644 --- a/packages/gesture-handler/package.json +++ b/packages/gesture-handler/package.json @@ -31,6 +31,7 @@ "@excom/content-drawer": "workspace:^", "@excom/event-handler": "workspace:^", "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^", "@excom/quark": "workspace:^", "@excom/quark-sheet": "workspace:^" }, @@ -52,4 +53,4 @@ "excom": { "packageType": "kit-element" } -} +} \ No newline at end of file diff --git a/packages/gesture-handler/support/docs/README.md b/packages/gesture-handler/support/docs/README.md index 583cbbd..16c20f6 100644 --- a/packages/gesture-handler/support/docs/README.md +++ b/packages/gesture-handler/support/docs/README.md @@ -51,7 +51,7 @@ The handoff is one Quark commit: the block that writes the final state (`is-open Anything else follows the same recipe: read `--gesture-*` in your own CSS, gate the mapping on a fact your sheet writes on start and clears on end. -Give a drag handle `touch-action: none` when using `from-ref`, so the browser does not scroll it away; without `from-ref` the element sets `touch-action` itself from `gesture-types`. Add `mouse` to `pointer-types` for desktop dragging. +Give a drag handle `touch-action: none` when using `from-ref`, so the browser does not scroll it away; without `from-ref` the element sets `touch-action` itself from `gesture-types`. For `pan-x` / `pan-y`, a touch whose first 3 px run along the handler's axis holds the page still for the rest of that touch (a non-passive `touchmove` listener does this; `touch-action` is unchanged), while a touch that starts across the axis scrolls the page as before. Add `mouse` to `pointer-types` for desktop dragging. ### Scroll handoff diff --git a/packages/gesture-handler/support/tests/carousel.view.test.ts b/packages/gesture-handler/support/tests/carousel.view.test.ts index 0c22f8f..351d989 100644 --- a/packages/gesture-handler/support/tests/carousel.view.test.ts +++ b/packages/gesture-handler/support/tests/carousel.view.test.ts @@ -6,13 +6,13 @@ import { describe, expect, it, + readDemo, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; import { drag } from "./pointer-utils"; diff --git a/packages/gesture-handler/support/tests/gesture-handler.test.ts b/packages/gesture-handler/support/tests/gesture-handler.test.ts index 4173dd8..e3dd501 100644 --- a/packages/gesture-handler/support/tests/gesture-handler.test.ts +++ b/packages/gesture-handler/support/tests/gesture-handler.test.ts @@ -6,7 +6,7 @@ import { it, vi, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import "../../index"; type PointerInit = { @@ -37,14 +37,23 @@ const up = (init?: PointerInit) => window.dispatchEvent(pointerEvent("pointerup", init)); const cancel = (init?: PointerInit) => window.dispatchEvent(pointerEvent("pointercancel", init)); -/** One-finger `touchmove` on `target`; returns the event to read `defaultPrevented`. */ -const touchMove = (target: Element, x: number, y: number) => { - const touch = new Touch({ identifier: 1, target, clientX: x, clientY: y }); +/** A `touchmove` on `target` (one finger unless `fingers`); returns the event to read `defaultPrevented`. */ +const touchMove = ( + target: Element, + x: number, + y: number, + { fingers = 1, cancelable = true } = {} +) => { + const touches = Array.from( + { length: fingers }, + (_, i) => new Touch({ identifier: i + 1, target, clientX: x + i * 50, clientY: y }) + ); const e = new TouchEvent("touchmove", { - touches: [touch], - changedTouches: [touch], + touches, + targetTouches: touches, + changedTouches: touches.slice(0, 1), bubbles: true, - cancelable: true, + cancelable, }); target.dispatchEvent(e); return e; @@ -278,6 +287,109 @@ describe("gesture-handler", () => { up(); }); + it("pan-x / pan-y cancel the page's scroll from the first touch move along their axis", async () => { + const x = mount(`gesture-types="pan-x swipe"`); + const surface = x.el.querySelector("p")!; + down(x.el, { x: 100, y: 100 }, surface); + // too short to judge + expect(touchMove(surface, 101, 100).defaultPrevented).toBe(false); + // sideways: cancelled before threshold-px, then for the rest of the touch + expect(touchMove(surface, 104, 101).defaultPrevented).toBe(true); + expect(x.el.hasAttribute("gesture-type")).toBe(false); + expect(touchMove(surface, 104, 140).defaultPrevented).toBe(true); + up(); + // downward first: the page scrolls, whatever follows + down(x.el, { x: 100, y: 100 }, surface); + expect(touchMove(surface, 101, 104).defaultPrevented).toBe(false); + expect(touchMove(surface, 140, 104).defaultPrevented).toBe(false); + up(); + + const y = mount(`gesture-types="pan-y"`); + const sheet = y.el.querySelector("p")!; + down(y.el, { x: 100, y: 100 }, sheet); + expect(touchMove(sheet, 100, 104).defaultPrevented).toBe(true); + up(); + down(y.el, { x: 100, y: 100 }, sheet); + expect(touchMove(sheet, 104, 100).defaultPrevented).toBe(false); + up(); + // no session, nothing to lock + expect(touchMove(sheet, 100, 110).defaultPrevented).toBe(false); + }); + + it("the pan scroll lock listens, non-passive, only while pan-x / pan-y is listed", () => { + const { el } = mount(`gesture-types="pan-y"`); + const add = vi.spyOn(el, "addEventListener"); + const remove = vi.spyOn(el, "removeEventListener"); + el.setAttribute("gesture-types", "pan"); + expect(remove).toHaveBeenCalledWith("touchmove", el._lockScroll, { passive: false }); + el.setAttribute("gesture-types", "pan-x swipe"); + expect(add).toHaveBeenCalledWith("touchmove", el._lockScroll, { passive: false }); + }); + + it("the pan scroll lock lets go once the pan is rejected", async () => { + const { el } = mount(`gesture-types="pan-x"`); + const surface = el.querySelector("p")!; + down(el, { x: 100, y: 100 }, surface); + expect(touchMove(surface, 104, 100).defaultPrevented).toBe(true); + // the finger turns down past threshold-px: pan-x rejects the gesture + move({ x: 104, y: 140 }); + await frame(); + expect(el.hasAttribute("gesture-type")).toBe(false); + expect(touchMove(surface, 104, 140).defaultPrevented).toBe(false); + up(); + }); + + it("the pan scroll lock skips a second finger and a move it cannot cancel", () => { + const { el } = mount(`gesture-types="pan-x"`); + const surface = el.querySelector("p")!; + down(el, { x: 100, y: 100 }, surface); + // two fingers on the surface: not judged + expect(touchMove(surface, 110, 100, { fingers: 2 }).defaultPrevented).toBe(false); + // judged and locked, but the browser already owns this move + expect(touchMove(surface, 110, 100, { cancelable: false }).defaultPrevented).toBe(false); + expect(touchMove(surface, 120, 100).defaultPrevented).toBe(true); + up(); + }); + + it("the pan scroll lock leaves a scroller under the finger that can still move that way", () => { + const { el } = mount( + `gesture-types="pan-x"`, + `

card

` + ); + const rail = el.querySelector("[data-rail]")!; + const card = rail.querySelector("p")!; + rail.scrollLeft = 30; + down(el, { x: 100, y: 100 }, card); + // rightward scrolls the rail back towards its start + expect(touchMove(card, 104, 100).defaultPrevented).toBe(false); + up(); + rail.scrollLeft = 0; + down(el, { x: 100, y: 100 }, card); + expect(touchMove(card, 104, 100).defaultPrevented).toBe(true); + up(); + }); + + it("the pan scroll lock waits for arm-after and skips a rejected pan", async () => { + const { el } = mount(`gesture-types="pan-x" arm-after="long-press" long-press-ms="20"`); + const surface = el.querySelector("p")!; + // not armed yet: the page may scroll + down(el, { x: 100, y: 100 }, surface); + expect(touchMove(surface, 110, 100).defaultPrevented).toBe(false); + up(); + // held, then sideways: locked + down(el, { x: 100, y: 100 }, surface); + await wait(40); + expect(touchMove(surface, 106, 100).defaultPrevented).toBe(true); + up(); + + // a vertical move past threshold-px rejects the pan before any touch move + const plain = mount(`gesture-types="pan-x"`); + const p = plain.el.querySelector("p")!; + await pan(plain.el, 0, 40); + expect(touchMove(p, 140, 100).defaultPrevented).toBe(false); + up(); + }); + it("from-ref and from-edge gate where a gesture may start", async () => { const { el } = mount( `from-ref="[data-handle]" from-edge="left" edge-px="20"`, diff --git a/packages/gesture-handler/support/tests/sheet.view.test.ts b/packages/gesture-handler/support/tests/sheet.view.test.ts index 18aba77..5b069eb 100644 --- a/packages/gesture-handler/support/tests/sheet.view.test.ts +++ b/packages/gesture-handler/support/tests/sheet.view.test.ts @@ -6,12 +6,12 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; import { drag, frame, pull } from "./pointer-utils"; diff --git a/packages/gesture-handler/support/tests/swipe.view.test.ts b/packages/gesture-handler/support/tests/swipe.view.test.ts index 4d10520..507c194 100644 --- a/packages/gesture-handler/support/tests/swipe.view.test.ts +++ b/packages/gesture-handler/support/tests/swipe.view.test.ts @@ -5,12 +5,12 @@ import { describe, expect, it, + readDemo, wait, -} from "@excom/heft-rig/profiles/default/config/test-utils"; +} from "@excom/nucleus-test"; import { flush, mountView, - readDemo, } from "@excom/quark/support/tests/view-helpers"; import { drag, tap } from "./pointer-utils"; diff --git a/packages/hash-object/package.json b/packages/hash-object/package.json index 03f45c1..5c88385 100644 --- a/packages/hash-object/package.json +++ b/packages/hash-object/package.json @@ -21,7 +21,8 @@ "dependencies": {}, "peerDependencies": {}, "devDependencies": { - "@excom/heft-rig": "workspace:^" + "@excom/heft-rig": "workspace:^", + "@excom/nucleus-test": "workspace:^" }, "repository": { "url": "excom-dev/nucleus", @@ -35,4 +36,4 @@ "excom": { "packageType": "library" } -} +} \ No newline at end of file diff --git a/packages/heft-rig/package.json b/packages/heft-rig/package.json index 953ac3b..c4930fa 100644 --- a/packages/heft-rig/package.json +++ b/packages/heft-rig/package.json @@ -14,7 +14,7 @@ ], "scripts": { "build": "node -e \"console.log('heft-rig: no build output')\"", - "build:watch": "node -e \"console.log('heft-rig: no build watch')", + "build:watch": "node -e \"console.log('heft-rig: no build watch')\"", "format": "node -e \"console.log('heft-rig: no format')\"", "test": "node scripts/vitest.mjs", "coverage": "node scripts/coverage.mjs", @@ -24,7 +24,6 @@ }, "dependencies": { "@microsoft/api-extractor": "^7.55.5", - "@open-wc/semantic-dom-diff": "^0.20.1", "@rushstack/heft": "^1.1.7", "@rushstack/heft-typescript-plugin": "^1.1.7", "@types/dom-view-transitions": "^1.0.5", @@ -59,4 +58,4 @@ "documented": false, "packageType": "heft-rig" } -} +} \ No newline at end of file diff --git a/packages/heft-rig/profiles/default/config/command-shim.ts b/packages/heft-rig/profiles/default/config/command-shim.ts deleted file mode 100644 index e6a8d09..0000000 --- a/packages/heft-rig/profiles/default/config/command-shim.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * happy-dom has no HTML Command API. Emulate the part the stack relies on - * in view tests: `button.command` / `button.commandForElement`, and a click - * on a `