├── .editorconfig ├── .github └── workflows │ ├── e2e-tests.yml │ ├── npm-publish.yml │ ├── redeploy-docs.yml │ ├── unit-tests.yml │ └── version-update.yml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── SwupFragmentPlugin.ts ├── inc │ ├── Logger.ts │ ├── ParsedRule.ts │ ├── defs.ts │ ├── env.ts │ ├── functions.ts │ └── handlers.ts └── index.ts ├── tests ├── config │ ├── playwright.config.ts │ ├── playwright.setup.ts │ ├── playwright.teardown.ts │ ├── serve.json │ ├── vitest.config.ts │ └── vitest.setup.ts ├── fixtures │ ├── assets │ │ ├── script.js │ │ ├── style.css │ │ └── transitions.css │ ├── detail │ │ └── index.html │ ├── index.html │ └── list │ │ ├── filter │ │ ├── green │ │ │ └── index.html │ │ └── red │ │ │ └── index.html │ │ └── index.html ├── playwright │ ├── fragment-plugin.spec.ts │ └── inc │ │ └── commands.ts ├── prepare.js └── vitest │ ├── ParsedRule.test.ts │ ├── adjustVisitScroll.test.ts │ ├── cloneRules.test.ts │ ├── dedupe.test.ts │ ├── exports.test.ts │ ├── getFragmentVisit.test.ts │ ├── getFragmentVisitContainers.test.ts │ ├── getRoute.test.ts │ ├── handlePageView.test.ts │ ├── inc │ └── helpers.ts │ ├── modifyRules.test.ts │ ├── normalizeUrl.test.ts │ ├── queryFragmentElement.test.ts │ ├── shouldSkipAnimation.test.ts │ └── toggleFragmentVisitClass.test.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | max_line_length = 100 9 | 10 | [*.{js,mjs,ts}] 11 | indent_style = tab 12 | indent_size = 4 13 | 14 | [*.{json,md,yaml,yml}] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | name: E2E tests 2 | 3 | on: 4 | push: 5 | branches: [main, next] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run-tests: 11 | name: E2E tests 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 5 14 | 15 | steps: 16 | - name: Check out repo 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | 24 | - run: npm ci 25 | - run: npm run build 26 | - run: npx playwright install --with-deps 27 | 28 | - name: Run tests 29 | run: npx playwright test --config ./tests/config/playwright.config.ts 30 | 31 | - uses: daun/playwright-report-summary@v2 32 | with: 33 | report-file: playwright-results.json 34 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow publishes a package to NPM 2 | 3 | name: Publish package 4 | 5 | on: 6 | release: 7 | types: [published] 8 | 9 | jobs: 10 | publish-npm: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 16 17 | registry-url: https://registry.npmjs.org 18 | - run: npm ci 19 | - run: npm publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 22 | -------------------------------------------------------------------------------- /.github/workflows/redeploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Redeploy Docs 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | redeploy-docs: 8 | uses: swup/.github/.github/workflows/redeploy-docs.yml@main 9 | secrets: inherit 10 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | push: 5 | branches: [main, next] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run-tests: 11 | name: Run unit tests 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 5 14 | 15 | steps: 16 | - name: Check out repo 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | 24 | - run: npm ci 25 | - run: npm run build 26 | 27 | - name: Run tests 28 | run: npm run test:unit 29 | -------------------------------------------------------------------------------- /.github/workflows/version-update.yml: -------------------------------------------------------------------------------- 1 | # This workflow bumps the package.json version and creates a PR 2 | 3 | name: Update package version 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | segment: 9 | description: 'Semver segment to update' 10 | required: true 11 | default: 'patch' 12 | type: choice 13 | options: 14 | - minor 15 | - patch 16 | 17 | jobs: 18 | update-version: 19 | name: Update package version 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-node@v2 24 | with: 25 | node-version: 16 26 | - run: echo "Updating ${{ inputs.segment }} version" 27 | - name: Update version 28 | run: npm --no-git-tag-version version ${{ inputs.segment }} 29 | - uses: peter-evans/create-pull-request@v4 30 | with: 31 | base: main 32 | branch: 'version/automated' 33 | title: 'Update package version (automated)' 34 | commit-message: 'Update package version' 35 | body: 'Automated update to ${{ inputs.segment }} version' 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.DS_Store 3 | node_modules 4 | Thumbs.db 5 | .idea 6 | dist 7 | *.tgz 8 | src/packages 9 | 10 | # Testing tools and outputs 11 | /tests/reports/ 12 | /tests/results/ 13 | /tests/fixtures/dist/ 14 | /playwright/.cache/ 15 | /playwright-report/ 16 | /coverage 17 | .nyc_output 18 | report.json 19 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '**/*.{js,ts,mjs,cjs,md,html}': ['prettier --write'] 3 | }; 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.1.2] - 2025-05-30 4 | 5 | - Only move `` elements with an `[open]` attribute to the top layer 6 | 7 | ## [1.1.1] - 2024-06-01 8 | 9 | - Optimize logic of persisting fragment elements 10 | - Optimize API usage of `getFragmentVisit` 11 | 12 | ## [1.1.0] - 2024-05-29 13 | 14 | - Match rules conditionally: [`rule.if`](https://swup.js.org/plugins/fragment-plugin/#rule-if) 15 | 16 | ## [1.0.2] - 2024-05-21 17 | 18 | - always update the `href` of `a[data-swup-link-to-fragment]`, even if it's the current URL 19 | 20 | ## [1.0.1] - 2024-03-26 21 | 22 | - Compatibility with swup 4.6.0 23 | 24 | ## [1.0.0] - 2024-01-28 25 | 26 | - Version 1.0.0 🍾 27 | 28 | ## [0.3.7] - 2023-11-14 29 | 30 | - Add playwright tests 31 | 32 | ## [0.3.6] - 2023-11-13 33 | 34 | - Prevent `` fragments from being closed if pressing the Escape key 35 | 36 | ## [0.3.5] - 2023-11-11 37 | 38 | - New [API methods](https://github.com/swup/fragment-plugin#api-methods): 39 | - `getFragmentVisit()` 40 | - `prependRule()` 41 | - `appendRule()` 42 | - `getRules()` 43 | - `setRules()` 44 | - Add vitest tests for many functions 45 | 46 | ## [0.3.4] - 2023-10-15 47 | 48 | - Ignore fragment elements that are not children of swup's default `containers` 49 | 50 | ## [0.3.3] - 2023-10-05 51 | 52 | - Refactor exports 53 | - Ensure all type definitions make it into `/dist` 54 | - Fix return type of `swup.getFragmentVisit()` 55 | 56 | ## [0.3.2] - 2023-09-26 57 | 58 | - Use `@swup/cli` for bundling 59 | 60 | ## [0.3.1] - 2023-09-25 61 | 62 | - Ignore fragment rules where any of the `containers` doesn't return a match 63 | 64 | ## [0.3.0] - 2023-09-07 65 | 66 | - Make `visit.a11y.focus` adjustable 67 | 68 | ## [0.2.6] - 2023-08-09 69 | 70 | - Support for fragment visits to the current URL (handy for @swup/fragment-plugin) 71 | 72 | ## [0.2.5] - 2023-08-04 73 | 74 | - simplify `visit.fragmentVisit` #35 75 | - new option `rule.scroll` #36 76 | 77 | ## [0.2.4] - 2023-07-31 78 | 79 | - Optimize exported types 80 | 81 | ## [0.2.3] - 2023-07-28 82 | 83 | - Sort the `"exports"` field in package.json in the recommended way 84 | 85 | ## [0.2.2] - 2023-07-28 86 | 87 | - use an `interface` for augmenting swup, so that multiple plugins can augment it 88 | 89 | ## [0.2.1] - 2023-07-26 90 | 91 | - Initial Release 92 | 93 | [1.1.2]: https://github.com/swup/fragment-plugin/releases/tag/1.1.2 94 | [1.1.1]: https://github.com/swup/fragment-plugin/releases/tag/1.1.1 95 | [1.1.0]: https://github.com/swup/fragment-plugin/releases/tag/1.1.0 96 | [1.0.2]: https://github.com/swup/fragment-plugin/releases/tag/1.0.2 97 | [1.0.1]: https://github.com/swup/fragment-plugin/releases/tag/1.0.1 98 | [1.0.0]: https://github.com/swup/fragment-plugin/releases/tag/1.0.0 99 | [0.3.7]: https://github.com/swup/fragment-plugin/releases/tag/0.3.7 100 | [0.3.6]: https://github.com/swup/fragment-plugin/releases/tag/0.3.6 101 | [0.3.5]: https://github.com/swup/fragment-plugin/releases/tag/0.3.5 102 | [0.3.4]: https://github.com/swup/fragment-plugin/releases/tag/0.3.4 103 | [0.3.3]: https://github.com/swup/fragment-plugin/releases/tag/0.3.3 104 | [0.3.2]: https://github.com/swup/fragment-plugin/releases/tag/0.3.2 105 | [0.3.1]: https://github.com/swup/fragment-plugin/releases/tag/0.3.1 106 | [0.3.0]: https://github.com/swup/fragment-plugin/releases/tag/0.3.0 107 | [0.2.6]: https://github.com/swup/fragment-plugin/releases/tag/0.2.6 108 | [0.2.5]: https://github.com/swup/fragment-plugin/releases/tag/0.2.5 109 | [0.2.4]: https://github.com/swup/fragment-plugin/releases/tag/0.2.4 110 | [0.2.3]: https://github.com/swup/fragment-plugin/releases/tag/0.2.3 111 | [0.2.2]: https://github.com/swup/fragment-plugin/releases/tag/0.2.2 112 | [0.2.1]: https://github.com/swup/fragment-plugin/releases/tag/0.2.1 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rasso Hilber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swup Fragment Plugin 2 | 3 |
4 | 5 | 6 | 7 | [![Unit Tests](https://img.shields.io/github/actions/workflow/status/swup/fragment-plugin/unit-tests.yml?branch=main&label=vitest)](https://github.com/swup/fragment-plugin/actions/workflows/unit-tests.yml) 8 | [![E2E Tests](https://img.shields.io/github/actions/workflow/status/swup/fragment-plugin/e2e-tests.yml?branch=main&label=playwright)](https://github.com/swup/fragment-plugin/actions/workflows/e2e-tests.yml) 9 | [![License](https://img.shields.io/github/license/swup/fragment-plugin.svg)](https://github.com/swup/fragment-plugin/blob/main/LICENSE) 10 | 11 |
12 | 13 | A [swup](https://swup.js.org) plugin for dynamically replacing containers based on rules 🧩 14 | 15 | - Selectively replace containers instead of the main swup containers, based on custom rules 16 | - Improve orientation by animating only the parts of the page that have actually changed 17 | - Give your site the polish and snappiness of a single-page app 18 | 19 | ## Use cases 20 | 21 | All of the following scenarios require updating only a small content fragment instead of 22 | performing a full page transition: 23 | 24 | - a filter UI that live-updates its list of results on every interaction 25 | - a detail overlay that shows on top of the currently open content 26 | - a tab group that should update only itself when selecting one of the tabs 27 | 28 | If you are looking for selectively replacing forms on submission, you should have a look at 29 | [Forms Plugin](https://swup.js.org/plugins/forms-plugin/#inline-forms). 30 | 31 | ## Demo 32 | 33 | See the plugin in action in [this interactive demo](https://swup-fragment-plugin.netlify.app) 34 | 35 |
36 | 37 | https://github.com/swup/fragment-plugin/assets/869813/ecaf15d7-ec72-43e8-898a-64f61330c6f5 38 | 39 |
40 | 41 | ## Table of contents 42 | 43 | - [Installation](#installation) 44 | - [How it works](#how-it-works) 45 | - [Example](#example) 46 | - [Options](#options) 47 | - [How rules are matched](#how-rules-are-matched) 48 | - [How fragment containers are found](#how-fragment-containers-are-found) 49 | - [Advanced use cases](#advanced-use-cases) 50 | - [Skip animations using `